Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge pull request #25 from alphagov/curated_lists

Curated lists
  • Loading branch information...
commit ff9956f78a935ebf84b706b748867f79d5ffe47c 2 parents 4a6f113 + 336415a
@jystewart jystewart authored
View
80 app/controllers/curated_list_controller.rb
@@ -0,0 +1,80 @@
+require 'curated_list/file_verifier'
+require 'govspeak'
+
+class CuratedListController < ApplicationController
+ respond_to :html
+ class HtmlValidationError < StandardError; end
+ class EmptyArtefactArray < StandardError; end
+
+ def create
+ prohibit_non_csv_uploads
+ @data_file = params[:data_file]
+ process_data_file
+ flash[:success] = "Hooray! That worked and you can now upload new data."
+ redirect_to curated_list_path
+ rescue CSV::MalformedCSVError => e
+ flash[:error] = "That looks like it isn't a CSV file."
+ redirect_to curated_list_path
+ rescue HtmlValidationError => e
+ flash[:error] = "Failed at being a valid document."
+ redirect_to curated_list_path
+ rescue EmptyArtefactArray => e
+ flash[:error] = "There's an empty row of artefact slugs against a sub category."
+ redirect_to curated_list_path
+ end
+
+ protected
+ def prohibit_non_csv_uploads
+ if params[:data_file]
+ file = get_file_from_param(params[:data_file])
+ fv = CuratedListImport::FileVerifier.new(file)
+ unless fv.type == 'text'
+ Rails.logger.info "Rejecting file with content type: #{fv.mime_type}"
+ raise CSV::MalformedCSVError
+ end
+ end
+ end
+
+ def process_data_file
+ if @data_file
+ data = @data_file.read.force_encoding('UTF-8')
+ if Govspeak::Document.new(data).valid?
+ csv_obj = CSV.parse(data, headers: true)
+ # eg: [sub_category_slug, artefact, artefact, artefact]
+ csv_obj.each do |row|
+ row = row.map { |k,v| v && v.strip }
+ # lookup if curated list exists
+ curated_list = CuratedList.any_in(tag_ids: [row[0]]).first
+ if curated_list.nil?
+ curated_list = CuratedList.new()
+ # remove the slash from our tag_id
+ tag_id = row[0].slice(1..-1)
+
+ # HACKY: slug can't be empty, so for now we'll use the tag_id. Ick.
+ curated_list.slug = tag_id.parameterize
+ curated_list.sections = [tag_id]
+ end
+ artefact_slugs = row.select {|x| !x.nil?}
+ artefact_slugs.shift
+ if artefact_slugs.length > 0
+ curated_list.artefact_ids = Artefact.any_in(slug: artefact_slugs).collect(&:_id)
+ curated_list.save!
+ else
+ raise EmptyArtefactArray
+ end
+ end
+ else
+ raise HtmlValidationError
+ end
+ end
+ end
+
+ def get_file_from_param(param)
+ if param.respond_to?(:tempfile)
+ param.tempfile
+ else
+ param
+ end
+ end
+
+end
View
11 app/controllers/curated_lists_controller.rb
@@ -1,11 +0,0 @@
-class CuratedListsController < ApplicationController
- respond_to :json
-
- def index
- curated_lists = {}
- CuratedList.all.map do |curated_list|
- curated_lists[curated_list.slug] = curated_list.artefacts.map(&:slug)
- end
- respond_with(curated_lists)
- end
-end
View
26 app/controllers/tags_controller.rb
@@ -1,5 +1,5 @@
class TagsController < ApplicationController
- respond_to :json
+ respond_to :json, :html
def index
@tags = TagRepository.load_all(tag_type: params[:type])
@@ -16,4 +16,28 @@ def show
end
end
+ def edit
+ @tag = TagRepository.load(params[:id])
+ respond_with @tag
+ end
+
+ def update
+ # Semantic form uses, by default, the to_param method on the model, _id
+ # our tag models are somewhat non conventional and ideally we'd need it to
+ # use tag_id, rather than _id
+ # This is a work around, let semantic form send us the _id and find it ourselves.
+ # TODO: modify the to_param within govuk_content_models
+ @tag = Tag.find(params[:id])
+ if @tag.update_attributes(params[:tag])
+ flash[:notice] = "Successfully updated"
+ redirect_to edit_tag_path(CGI.escape(@tag.tag_id))
+ else
+ respond_with @tag
+ end
+ end
+
+ def categories
+ @tags = TagRepository.load_all({:tag_type => 'section'})
+ respond_with @tags
+ end
end
View
22 app/views/curated_list/import.html.erb
@@ -0,0 +1,22 @@
+<%= content_for :page_title, "Import for Curated List" %>
+<div class="page-header">
+ <h1>Import Curated lists</h1>
+</div>
+<div class="row-fluid">
+<% if flash[:success].present? %>
+ <div class="alert alert-success"><%= flash[:success] %></div>
+<% end %>
+<% if flash[:error].present? %>
+ <div class="alert alert-danger"><%= flash[:error] %></div>
+<% end %>
+
+ <div class="well" id="new-data">
+ <h2>Load New Data</h2>
+ <form accept-charset="UTF-8" action="/curated_list/create" enctype="multipart/form-data" id="new_data_set" method="post" novalidate="novalidate">
+ <label for="curated_list_data_file">CSV of curated lists</label>
+ <input id="curated_list_data_file" name="data_file" type="file">
+ <input class="btn btn-primary" name="commit" type="submit" value="Upload CSV">
+ </form>
+ </div>
+
+</div>
View
39 app/views/curated_list/index.html.erb
@@ -0,0 +1,39 @@
+<%= content_for :page_title, "Curated Lists" %>
+<div class="row-fluid">
+ <div class="span12">
+
+ <div class="page-header">
+ <h1>Curated lists</h1>
+ </div>
+
+ <div class="row-fluid">
+ <div class="span12">
+ <table class="table table-striped table-bordered table-condensed" id="curated-list" summary="All curated lists">
+ <thead>
+ <tr>
+ <th scope="col">Slug</th>
+ <th scope="col">Section</th>
+ </tr>
+ </thead>
+ <tbody>
+ <% @curated_lists.each do |cl| %>
+ <tr>
+ <td><%= link_to cl.slug, edit_curated_list_path(cl.slug) %></td>
+ <td><%= cl.sections[0] %></td>
+ </tr>
+ <% end %>
+ </tbody>
+ </table>
+ </div><!-- ./span10 -->
+ </div><!-- ./row fluid -->
+ </div><!-- ./span12 -->
+</div><!-- ./row-fluid-->
+
+<%= content_for :extra_javascript do %>
+ <%= javascript_include_tag 'jquery.tablesorter.min.js' %>
+ <script type="text/javascript">
+ $(function () {
+ $('#curated-list').tablesorter();
+ });
+ </script>
+<% end %>
View
22 app/views/tags/categories.html.erb
@@ -0,0 +1,22 @@
+<%= content_for :page_title, "All categories" %>
+<div class="page-header">
+ <h1>All categories</h1>
+</div>
+<div class="row-fluid">
+ <table class="table table-striped table-condensed" id="categories-list" summary="All categories">
+ <thead>
+ <tr>
+ <th scope="col">Title</th>
+ <th scope="col">Slug</th>
+ </tr>
+ </thead>
+ <tbody>
+ <% @tags.each do |tag| %>
+ <tr>
+ <td><%= tag.title %></td>
+ <td><%= link_to tag.tag_id, edit_tag_path(CGI.escape(tag.tag_id)) %></td>
+ </tr>
+ <% end %>
+ </tbody>
+ </table>
+</div>
View
28 app/views/tags/edit.html.erb
@@ -0,0 +1,28 @@
+<%= content_for :page_title, "Editing Category #{@tag.title}" %>
+<div class="page-header">
+ <h1>Editing &ldquo;<%= @tag.title %>&rdquo;</h1>
+</div>
+<div class="tab-pane active" id="edit">
+<% if flash[:notice].present? %>
+ <div class="alert alert-success"><%= flash[:notice] %></div>
+<% end %>
+<% if @tag.errors.count > 0 %>
+ <div class="alert alert-error">
+ <ul>
+ <% @tag.errors.full_messages.each do |message| %>
+ <li><%= message %></li>
+ <% end %>
+ </ul>
+ </div>
+<% end %>
+
+<div class="well">
+ <%= semantic_form_for(@tag, :html => { :class => '', :id => 'tag'}) do |f| %>
+ <%= f.inputs do %>
+ <%= f.input :title, :input_html => { :class => "span6" } %>
+ <%= f.input :description, :as => :text, :input_html => { :class => "span6", :rows => 6} %>
+ <% end %>
+ <%= f.submit :value => "Save", :class => "btn btn-success" %>
+ <% end %>
+</div>
+</div>
View
9 config/routes.rb
@@ -1,12 +1,11 @@
Panopticon::Application.routes.draw do
resources :artefacts, :constraints => { :id => /[^\.]+/ }
- resources :tags, :defaults => {:format => 'json'}
-
- resources :curated_lists, only: :index
+ resources :tags
match 'tags/:id' => 'tags#show', :id => /[^\.]+/, :defaults => {:format => 'json'}
-
+ match 'categories' => 'tags#categories', :as => :categories
match 'google_insight' => 'seo#show'
-
+ match 'curated_list' => 'curated_list#import', :as => :curated_list
+ match 'curated_list/create' => 'curated_list#create'
root :to => redirect("/artefacts")
end
View
14 features/curated_lists.feature
@@ -0,0 +1,14 @@
+Feature: Managing curated lists
+ In order to curate GovUK content
+ I want to manage curated lists in panopticon
+
+ Background:
+ Given I am an admin
+ And my basic set of tags exist
+
+ Scenario: Uploading a new CSV file
+ When I visit the curated list admin page
+ And I upload my CSV file
+
+ Then I should see "Hooray! That worked and you can now upload new data."
+ And the curated lists should exist
View
47 features/step_definitions/category_steps.rb
@@ -0,0 +1,47 @@
+Given /^a category tag called "(.*?)" exists$/ do |tag_name|
+ @tag = Tag.create!(tag_type: 'section', title: tag_name,
+ tag_id: tag_name.parameterize)
+end
+
+Given /^my basic set of tags exist$/ do
+ Tag.create!(tag_id: 'employing-people/pensions', title: 'Pensions',
+ tag_type: 'section')
+ Tag.create!(tag_id: 'business/selling-closing', title: 'Selling and closing',
+ tag_type: 'section')
+end
+
+When /^I visit the curated list admin page$/ do
+ visit curated_list_path
+end
+
+When /^I upload my CSV file$/ do
+ attach_file "CSV of curated lists", csv_path_for_data("curated_list_dummy")
+ click_button "Upload CSV"
+end
+
+When /^I visit the categories page$/ do
+ visit categories_path
+end
+
+When /^I follow the link to edit the category$/ do
+ click_link @tag.tag_id
+end
+
+When /^I change the category title to "(.*?)"$/ do |new_title|
+ fill_in "Title", with: new_title
+ click_button "Save"
+end
+
+Then /^I should be on the categories page$/ do
+ assert_equal categories_path, page.current_url
+end
+
+Then /^I should see "(.*?)"$/ do |content|
+ assert_match /#{content}/, page.body
+end
+
+Then /^the curated lists should exist$/ do
+ Tag.where(tag_type: 'section').each do |t|
+ assert CuratedList.any_in(tag_ids: [t.tag_id]).first, "List for #{t.tag_id} missing"
+ end
+end
View
7 features/support/csv_file_helper.rb
@@ -0,0 +1,7 @@
+module CsvFileHelper
+ def csv_path_for_data(name)
+ File.expand_path('../../support/data/' + name.parameterize + '.csv', __FILE__)
+ end
+end
+
+World(CsvFileHelper)
View
3  features/support/data/curated_list_dummy.csv
@@ -0,0 +1,3 @@
+sub category slug,artefact slug,artefact slug,artefact slug,...
+/employing-people/pensions,business-transfers-takeovers-workers-rights,plug-in-car-van-grants,aids-and-driving,
+/business/selling-closing,1619-bursary-fund,,,
View
15 features/tag_management.feature
@@ -0,0 +1,15 @@
+Feature: Managing tags
+ In order to curate GovUK content
+ I want to manage tags in panopticon
+
+ Background:
+ Given I am an admin
+ And a category tag called "Crime and Justice" exists
+
+ Scenario: Updating a tag title
+ When I visit the categories page
+ And I follow the link to edit the category
+ And I change the category title to "Crime, justice and superheroes"
+
+ Then I should see "Successfully updated"
+ And I should see "Crime, justice and superheroes"
View
31 lib/curated_list/file_verifier.rb
@@ -0,0 +1,31 @@
+module CuratedListImport
+ class FileVerifier
+ attr_accessor :filename
+
+ def initialize(file)
+ if file.respond_to?(:path)
+ @filename = file.path
+ else
+ @filename = file.to_s
+ end
+ end
+
+ def type
+ mime_type.split('/').first
+ end
+
+ def sub_type
+ mime_type.split('/').last
+ end
+
+ def mime_type
+ shell_result = IO.popen(["file", "--brief", "--mime-type", filename],
+ in: :close, err: :close)
+ shell_result.read.chomp
+ end
+
+ def is_mime_type?(comparison_mime_type)
+ mime_type == comparison_mime_type
+ end
+ end
+end
View
30 lib/tasks/rename_slugs.rake
@@ -0,0 +1,30 @@
+# This is a one time run script to change the slugs of two categories
+# and update all artefacts that have been tagged with those sections
+# TODO: Once this has been run, this script can be deleted
+
+namespace :migrate do
+ desc "Change slug for two categories"
+ task :rename_two_category_slug => :environment do
+ tag_mappings = [
+ ["business/corporation-tax-capital-allowance", "business/business-tax"],
+ ["driving/blue-badges-parking", "driving/blue-badge-parking"]
+ ]
+ tag_mappings.each do |old_tag_id, new_tag_id|
+ t = Tag.where(tag_id: "#{old_tag_id}").first
+ if t.nil?
+ raise "Wa? No frakkin' tag with tag_id #{old_tag_id}"
+ end
+ t.tag_id = new_tag_id
+ t.save!
+ artefacts = Artefact.any_in(tag_ids: [old_tag_id])
+ puts "#{artefacts.count} of artefacts tagged with #{old_tag_id}"
+ artefacts.each do |a|
+ puts "Artefact: #{a.slug} with tag_ids #{a.tag_ids}"
+ index = a.tag_ids.index(old_tag_id)
+ a.tag_ids[index] = new_tag_id
+ a.save!
+ puts "new tag_ids #{a.tag_ids}"
+ end
+ end
+ end
+end
View
42 test/functional/curated_lists_controller_test.rb
@@ -1,42 +0,0 @@
-require "test_helper"
-
-class CuratedListsControllerTest < ActionController::TestCase
- setup do
- login_as_stub_user
- end
-
- test "return a json representation of the lists" do
- jury = Artefact.create(name: "Jury Service", slug: "jury-service",
- kind: "guide", owning_app: "publisher")
- crime = CuratedList.create(slug: "crime-and-justice")
- crime.artefact_ids << jury.id
- crime.save
-
- expected = {
- "crime-and-justice" => ["jury-service"]
- }
- get :index, format: :json
- assert_equal 200, response.status
- assert_equal expected, JSON.parse(response.body)
- end
-
- test "should retain the order of the IDs in artefact_ids" do
- jury = Artefact.create(name: "Jury Service", slug: "jury-service",
- kind: "guide", owning_app: "publisher")
- to_court = Artefact.create(name: "Court", slug: "going-to-court-victim-witness", kind: "guide", owning_app: "publisher")
- crime = CuratedList.create(slug: "crime-and-justice")
- crime.artefact_ids << jury.id
- crime.artefact_ids << to_court.id
- crime.save
-
- expected = {
- "crime-and-justice" => [
- "jury-service",
- "going-to-court-victim-witness"
- ]
- }
- get :index, format: :json
- assert_equal 200, response.status
- assert_equal expected, JSON.parse(response.body)
- end
-end
Please sign in to comment.
Something went wrong with that request. Please try again.