From 7ee72f408692b1eaf74fb6ef96f2705b4370e0ca Mon Sep 17 00:00:00 2001 From: Patrick Lewis Date: Thu, 9 Mar 2017 09:56:11 -0500 Subject: [PATCH 01/48] Resolve #7822 Let users edit feed items --- app/controllers/feed_items_controller.rb | 41 ++++++++++++++++-------- app/policies/feed_item_policy.rb | 12 +++++++ app/views/feed_items/_edit.html.haml | 12 +++++++ app/views/feed_items/_tabs.html.haml | 12 ++++--- app/views/feed_items/edit.html.haml | 11 +++++++ spec/policies/feed_item_policy_spec.rb | 40 +++++++++++++++++++++++ 6 files changed, 110 insertions(+), 18 deletions(-) create mode 100644 app/policies/feed_item_policy.rb create mode 100644 app/views/feed_items/_edit.html.haml create mode 100644 app/views/feed_items/edit.html.haml create mode 100644 spec/policies/feed_item_policy_spec.rb diff --git a/app/controllers/feed_items_controller.rb b/app/controllers/feed_items_controller.rb index f4a82a7d..d70c6eae 100644 --- a/app/controllers/feed_items_controller.rb +++ b/app/controllers/feed_items_controller.rb @@ -4,17 +4,16 @@ class FeedItemsController < ApplicationController Digest::MD5.hexdigest(request.fullpath + '&per_page=' + get_per_page) } + before_action :set_hub_feed, except: [:tag_list] + before_action :set_feed_item, except: [:index] + def controls - load_hub_feed - load_feed_item add_breadcrumbs render layout: !request.xhr? end # Return the full content for a FeedItem,, this could potentially be a large amount of content. Returns html, json, or xml. Action cached for anonymous visitors. def content - load_hub_feed - load_feed_item add_breadcrumbs respond_to do |format| format.html { render layout: request.xhr? ? false : 'tabs' } @@ -24,8 +23,6 @@ def content end def about - load_hub_feed - load_feed_item add_breadcrumbs respond_to do |format| format.html { render layout: request.xhr? ? false : 'tabs' } @@ -36,8 +33,6 @@ def about # Uses a solr more_like_this query to find items to related to this one by comparing the title and tag list. Returns html, json, or xml. Action cached for anonymous visitors. def related - load_hub_feed - load_feed_item add_breadcrumbs hub_id = @hub.id @related = Sunspot.more_like_this(@feed_item) do @@ -59,7 +54,6 @@ def related # A paginated list of FeedItems in a HubFeed. Returns html, atom, rss, json, or xml. Action cached for anonymous visitors. def index - load_hub_feed @show_auto_discovery_params = hub_feed_feed_items_url(@hub_feed, format: :rss) @feed_items = @hub_feed @@ -83,8 +77,6 @@ def index # A FeedItem. Returns html, json, or xml. Action cached for anonymous visitors. def show - load_hub_feed - load_feed_item add_breadcrumbs respond_to do |format| format.html do @@ -96,7 +88,6 @@ def show end def tag_list - load_feed_item @hub = Hub.find(params[:hub_id]) respond_to do |format| format.html do @@ -105,14 +96,32 @@ def tag_list end end + def edit + authorize @feed_item + + add_breadcrumbs + + render layout: 'tabs' + end + + def update + authorize @feed_item + + if @feed_item.update(feed_item_params) + redirect_to hub_feed_feed_item_path(@hub_feed, @feed_item) + else + render :edit + end + end + private - def load_hub_feed + def set_hub_feed @hub_feed = HubFeed.find(params[:hub_feed_id]) @hub = @hub_feed.hub end - def load_feed_item + def set_feed_item @feed_item = FeedItem.find(params[:id]) end @@ -136,4 +145,8 @@ def add_breadcrumbs end end end + + def feed_item_params + params.require(:feed_item).permit(:description, :title, :url) + end end diff --git a/app/policies/feed_item_policy.rb b/app/policies/feed_item_policy.rb new file mode 100644 index 00000000..18eb2317 --- /dev/null +++ b/app/policies/feed_item_policy.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true +class FeedItemPolicy < ApplicationPolicy + def update? + return false unless user.present? + return true if user.has_role?(:superadmin) + + # Set authorization based on the user's roles on associated hubs + record.hubs.any? do |hub| + user.has_role?(:owner, hub) || user.has_role?(:inputter, hub) || user.has_role?(:bookmarker, hub) + end + end +end diff --git a/app/views/feed_items/_edit.html.haml b/app/views/feed_items/_edit.html.haml new file mode 100644 index 00000000..6898ccff --- /dev/null +++ b/app/views/feed_items/_edit.html.haml @@ -0,0 +1,12 @@ += form_for @feed_item, url: hub_feed_feed_item_path(@hub_feed, @feed_item) do |f| + - text_field_size = 80 + %h3 Title: + = f.text_field :title, size: text_field_size + %h3 Summary: + = f.text_area :description + %h3 Link: + = f.text_field :url, size: text_field_size + %br + %br + = f.submit 'Update', class: 'btn btn-success' + = link_to 'Cancel', hub_feed_feed_item_path(@hub_feed, @feed_item), class: 'btn btn-default' diff --git a/app/views/feed_items/_tabs.html.haml b/app/views/feed_items/_tabs.html.haml index aa04e5dc..51062737 100644 --- a/app/views/feed_items/_tabs.html.haml +++ b/app/views/feed_items/_tabs.html.haml @@ -1,17 +1,21 @@ - unless @feed_item.content.blank? - %li{:class => "#{'active' if active == 'content'}"} + %li{ class: ('active' if active == 'content') } = documentation('feed-item-content-tab') = link_to('Item', content_hub_feed_feed_item_path(@hub_feed, @feed_item)) .nav-pill-arrow -%li{:class => "#{'active' if active == 'about'}"} +%li{ class: ('active' if active == 'about') } = documentation('feed-item-about-tab') = link_to('About', about_hub_feed_feed_item_path(@hub_feed, @feed_item)) .nav-pill-arrow -%li{:class => "#{'active' if active == 'filters'}"} +%li{ class: ('active' if active == 'edit') } + = documentation('feed-item-edit-tab') + = link_to('Edit', edit_hub_feed_feed_item_path(@hub_feed, @feed_item)) + .nav-pill-arrow +%li{ class: ('active' if active == 'filters') } = documentation('feed-item-tag-filters') = link_to('Filters', hub_feed_item_tag_filters_path(@hub, @feed_item, hub_feed_id: @hub_feed.id)) .nav-pill-arrow -%li{:class => "last-of-section #{'active' if active == 'related'}"} +%li{ class: ['last-of-section', ('active' if active == 'related')] } = documentation('hub-feed-feed-item-related-list') = link_to('Related items', related_hub_feed_feed_item_path(@hub_feed, @feed_item)) .nav-pill-arrow diff --git a/app/views/feed_items/edit.html.haml b/app/views/feed_items/edit.html.haml new file mode 100644 index 00000000..68cbeac7 --- /dev/null +++ b/app/views/feed_items/edit.html.haml @@ -0,0 +1,11 @@ +- content_for :top_panel do + .col-sm-12 + %h1= @feed_item.title + %h4 + = @hub_feed.feed.title + = @feed_item.created_at.strftime('%F') +- content_for :tabs do + = render partial: 'tabs', locals: { active: 'edit' } +- content_for :tab_content do + .nicely-padded.about-feed-item + = render partial: 'edit' diff --git a/spec/policies/feed_item_policy_spec.rb b/spec/policies/feed_item_policy_spec.rb new file mode 100644 index 00000000..8c3aa2b9 --- /dev/null +++ b/spec/policies/feed_item_policy_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true +require 'rails_helper' + +RSpec.describe FeedItemPolicy do + subject { described_class.new(user, feed_item) } + + let(:hub) { create(:hub) } + let(:feed_item) { create(:feed_item) } + + context 'for an anonymous user' do + let(:user) { nil } + + it { is_expected.to forbid_action(:edit) } + it { is_expected.to forbid_action(:update) } + end + + context 'for a logged in user' do + let(:user) { create(:user) } + + context 'with no roles' do + it { is_expected.to forbid_action(:edit) } + it { is_expected.to forbid_action(:update) } + end + + # TODO: Find a better test for specific roles on the associated hub + context 'with an owner role on the hub' do + before { allow(user).to receive(:has_role?) { true } } + + it { is_expected.to permit_action(:edit) } + it { is_expected.to permit_action(:update) } + end + end + + context 'for a superadmin' do + let(:user) { create(:user, :superadmin) } + + it { is_expected.to permit_action(:edit) } + it { is_expected.to permit_action(:update) } + end +end From 8c2b7a285dcac337be0b63bf53fc9b75389fe9ef Mon Sep 17 00:00:00 2001 From: Peter Hankiewicz Date: Tue, 7 Mar 2017 17:35:36 -0500 Subject: [PATCH 02/48] Updated the Bookmarks about page --- app/controllers/hub_feeds_controller.rb | 6 ++++-- app/controllers/republished_feeds_controller.rb | 3 ++- app/views/hub_feeds/_about.html.haml | 12 +++++++++--- app/views/hub_feeds/_list_item.html.haml | 8 ++++++-- app/views/hub_feeds/_tabs.html.haml | 14 ++++++++++---- 5 files changed, 31 insertions(+), 12 deletions(-) diff --git a/app/controllers/hub_feeds_controller.rb b/app/controllers/hub_feeds_controller.rb index 69ef974a..a2d08fc5 100644 --- a/app/controllers/hub_feeds_controller.rb +++ b/app/controllers/hub_feeds_controller.rb @@ -86,17 +86,19 @@ def new # Only used to create bookmarking collections. # Actual rss feeds are added through the hub controller. Yeah, probably not optimal @hub_feed = HubFeed.new - authorize @hub_feed @hub_feed.hub_id = @hub.id + + authorize @hub_feed end def create # Only used to create bookmarking collections. # Actual rss feeds are added through the hub controller. Yeah, probably not optimal @hub_feed = HubFeed.new - authorize @hub_feed @hub_feed.hub_id = @hub.id + authorize @hub_feed + actual_feed = Feed.new actual_feed.bookmarking_feed = true actual_feed.feed_url = 'not applicable' diff --git a/app/controllers/republished_feeds_controller.rb b/app/controllers/republished_feeds_controller.rb index b3eccc48..58e71080 100644 --- a/app/controllers/republished_feeds_controller.rb +++ b/app/controllers/republished_feeds_controller.rb @@ -74,9 +74,10 @@ def new end def create - authorize RepublishedFeed @republished_feed = RepublishedFeed.create_with_user(current_user, @hub, params) + authorize @republished_feed + respond_to do |format| if @republished_feed flash[:notice] = 'Created a new remix. You should switch to the "inputs" tab and add items for publishing.' diff --git a/app/views/hub_feeds/_about.html.haml b/app/views/hub_feeds/_about.html.haml index 89033ae2..9e8b4f7f 100644 --- a/app/views/hub_feeds/_about.html.haml +++ b/app/views/hub_feeds/_about.html.haml @@ -1,10 +1,16 @@ .nicely-padded - - unless @hub_feed.feed.is_bookmarking_feed? + - if @hub_feed.feed.is_bookmarking_feed? + %h3 Title: + = @hub_feed.feed.title + - unless @hub_feed.display_description.blank? + %h3 Description: + = @hub_feed.display_description.html_safe + - else %h3 Original title from feed: = @hub_feed.feed.title - - unless @hub_feed.feed.description.blank? + - unless @hub_feed.display_description.blank? %h3 Description: - = @hub_feed.feed.description + = @hub_feed.display_description.html_safe %h3 URL: = link_to(@hub_feed.feed.link,@hub_feed.feed.link, :target => '_blank') %h3 RSS / Atom URL: diff --git a/app/views/hub_feeds/_list_item.html.haml b/app/views/hub_feeds/_list_item.html.haml index 38737c39..3c3fd39b 100644 --- a/app/views/hub_feeds/_list_item.html.haml +++ b/app/views/hub_feeds/_list_item.html.haml @@ -7,8 +7,12 @@ = link_to more_details_hub_hub_feed_path(@hub,hub_feed), class: "hub_feed_more_control", data_hub_feed_id: hub_feed.id do = fa_icon('caret-right') .media-body - = link_to hub_hub_feed_path(@hub,hub_feed) do - %h2= raw(strip_tags(hub_feed.to_s)) + - if hub_feed.feed.is_bookmarking_feed? + = link_to hub_feed_feed_items_path(hub_feed) do + %h2= raw(strip_tags(hub_feed.to_s)) + -else + = link_to hub_hub_feed_path(@hub,hub_feed) do + %h2= raw(strip_tags(hub_feed.to_s)) - if defined?(show_hubs) && show_hubs == true %span.smaller in #{link_to(hub_feed.hub, hub_path(hub_feed.hub) )} diff --git a/app/views/hub_feeds/_tabs.html.haml b/app/views/hub_feeds/_tabs.html.haml index 000aa267..2b365581 100644 --- a/app/views/hub_feeds/_tabs.html.haml +++ b/app/views/hub_feeds/_tabs.html.haml @@ -1,7 +1,8 @@ -%li{:class => "#{'active' if active == 'about'}"} - = documentation('hub-feed-about-tab') - = link_to('About', hub_feed_path(@hub_feed)) - .nav-pill-arrow +- unless @hub_feed.feed.is_bookmarking_feed? + %li{:class => "#{'active' if active == 'about'}"} + = documentation('hub-feed-about-tab') + = link_to('About', hub_hub_feed_path(@hub, @hub_feed)) + .nav-pill-arrow %li{:class => "#{'active' if active == 'items'}"} = documentation('hub-feed-feed-item-list') = link_to('Items', hub_feed_feed_items_path(@hub_feed)) @@ -23,3 +24,8 @@ = documentation('hub-feed-import-tab') = link_to('Import items', import_hub_feed_path(@hub_feed)) .nav-pill-arrow +- if @hub_feed.feed.is_bookmarking_feed? + %li{:class => "#{'active' if active == 'about'}"} + = documentation('hub-feed-about-tab') + = link_to('About', hub_hub_feed_path(@hub, @hub_feed)) + .nav-pill-arrow From e076aa1de7759acfd994a97fc60622e90850bb15 Mon Sep 17 00:00:00 2001 From: Patrick Lewis Date: Thu, 9 Mar 2017 10:38:59 -0500 Subject: [PATCH 03/48] Fix styling of hub feed tabs --- app/views/hub_feeds/_tabs.html.haml | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/app/views/hub_feeds/_tabs.html.haml b/app/views/hub_feeds/_tabs.html.haml index 2b365581..6fbce1a5 100644 --- a/app/views/hub_feeds/_tabs.html.haml +++ b/app/views/hub_feeds/_tabs.html.haml @@ -1,31 +1,26 @@ -- unless @hub_feed.feed.is_bookmarking_feed? - %li{:class => "#{'active' if active == 'about'}"} - = documentation('hub-feed-about-tab') - = link_to('About', hub_hub_feed_path(@hub, @hub_feed)) - .nav-pill-arrow -%li{:class => "#{'active' if active == 'items'}"} +%li{ class: ('active' if active == 'items') } = documentation('hub-feed-feed-item-list') = link_to('Items', hub_feed_feed_items_path(@hub_feed)) .nav-pill-arrow -%li{:class => "#{'active' if active == 'tags'}"} +%li{ class: ('active' if active == 'tags') } = documentation('hub-feed-tag-cloud') = link_to('Tags', hub_feed_tags_path(@hub_feed)) .nav-pill-arrow -%li{:class => "last-of-section #{'active' if active == 'filters'}"} +%li{ class: ('active' if active == 'filters') } = documentation('hub-feed-filter-tab') - = link_to('Filters', hub_feed_tag_filters_path(@hub_feed) ) + = link_to('Filters', hub_feed_tag_filters_path(@hub_feed)) .nav-pill-arrow -%li{:class => "last-of-section #{'active' if active == 'updates'}"} +%li{ class: ('active' if active == 'updates') } = documentation('hub-feed-retrievals-list') - = link_to('Updates', hub_feed_feed_retrievals_path(@hub_feed) ) + = link_to('Updates', hub_feed_feed_retrievals_path(@hub_feed)) .nav-pill-arrow - if !@hub_feed.blank? && !current_user.blank? && (current_user.is?(:owner, @hub_feed.feed) || current_user.is?(:owner, @hub)) && @hub_feed.feed.is_bookmarking_feed? - %li{:class => "#{'active' if active == 'import'}"} + %li{ class: ('active' if active == 'import') } = documentation('hub-feed-import-tab') = link_to('Import items', import_hub_feed_path(@hub_feed)) .nav-pill-arrow - if @hub_feed.feed.is_bookmarking_feed? - %li{:class => "#{'active' if active == 'about'}"} + %li{ class: ['last-of-section', ('active' if active == 'about')] } = documentation('hub-feed-about-tab') = link_to('About', hub_hub_feed_path(@hub, @hub_feed)) .nav-pill-arrow From 39a72d4ea10d074415a6a8ecad7fb363d6955bc2 Mon Sep 17 00:00:00 2001 From: Patrick Lewis Date: Thu, 9 Mar 2017 11:55:36 -0500 Subject: [PATCH 04/48] Resolve #12841 Confusing info on individual taggers --- app/views/hub_feeds/_list_item.html.haml | 11 +++++----- app/views/hub_feeds/more_details.html.haml | 25 ++++++++++++---------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/app/views/hub_feeds/_list_item.html.haml b/app/views/hub_feeds/_list_item.html.haml index 3c3fd39b..6f047e67 100644 --- a/app/views/hub_feeds/_list_item.html.haml +++ b/app/views/hub_feeds/_list_item.html.haml @@ -1,18 +1,19 @@ -%li{:id => "hub_feed-#{hub_feed.id}"} +%li{ id: "hub_feed-#{hub_feed.id}" } .media .pull-right = link_to controls_hub_feed_path(hub_feed), class: 'control', title: 'Feed actions' do = fa_icon 'cog' .media-left - = link_to more_details_hub_hub_feed_path(@hub,hub_feed), class: "hub_feed_more_control", data_hub_feed_id: hub_feed.id do + = link_to more_details_hub_hub_feed_path(@hub, hub_feed), + class: 'hub_feed_more_control', data_hub_feed_id: hub_feed.id do = fa_icon('caret-right') .media-body - if hub_feed.feed.is_bookmarking_feed? = link_to hub_feed_feed_items_path(hub_feed) do %h2= raw(strip_tags(hub_feed.to_s)) - -else - = link_to hub_hub_feed_path(@hub,hub_feed) do + - else + = link_to hub_hub_feed_path(@hub, hub_feed) do %h2= raw(strip_tags(hub_feed.to_s)) - if defined?(show_hubs) && show_hubs == true %span.smaller - in #{link_to(hub_feed.hub, hub_path(hub_feed.hub) )} + in #{link_to(hub_feed.hub, hub_path(hub_feed.hub))} diff --git a/app/views/hub_feeds/more_details.html.haml b/app/views/hub_feeds/more_details.html.haml index 5109d7ab..07a5529f 100644 --- a/app/views/hub_feeds/more_details.html.haml +++ b/app/views/hub_feeds/more_details.html.haml @@ -1,12 +1,15 @@ -.metadata{:id => "hub_feed_metadata_#{@hub_feed.id}"} - %span.updated{:title => "Last updated"} - = @hub_feed.feed.updated_at.to_s(:long) -  |  - %span.items - = pluralize(@hub_feed.feed.feed_items.count, 'item') -  |  - \#{link_to('RSS', hub_feed_feed_items_path(@hub_feed, format: :rss), title: 'An RSS feed with tag filters applied to it.')}  - \#{link_to('ATOM', hub_feed_feed_items_path(@hub_feed, format: :atom), title: 'An Atom feed with tag filters applied to it.')}  - \#{link_to('JSON', hub_feed_feed_items_path(@hub_feed, format: :json, callback: 'callback'), title: 'jsonp with tag filters applied to it.')} +.metadata{ id: "hub_feed_metadata_#{@hub_feed.id}" } + %span.updated{ title: 'Last updated' } Updated #{@hub_feed.latest_feed_items.first.updated_at.to_s(:long)} + + |  + + %span.items= pluralize(@hub_feed.feed.feed_items.count, 'item') + + |  + + = link_to('RSS', hub_feed_feed_items_path(@hub_feed, format: :rss), title: 'An RSS feed with tag filters applied to it.') + = link_to('ATOM', hub_feed_feed_items_path(@hub_feed, format: :atom), title: 'An Atom feed with tag filters applied to it.') + = link_to('JSON', hub_feed_feed_items_path(@hub_feed, format: :json, callback: 'callback'), title: 'jsonp with tag filters applied to it.') + .feed-item-tags - = raw @hub_feed.latest_tags(10).collect{|t| tag_display(t, :hub => @hub, :hub_feed => @hub_feed) }.join(' ') + = raw @hub_feed.latest_tags(10).collect { |t| tag_display(t, hub: @hub, hub_feed: @hub_feed) }.join(' ') From 4d0e8a6153d10fb2c6770048e41abedced608b49 Mon Sep 17 00:00:00 2001 From: Peter Hankiewicz Date: Thu, 9 Mar 2017 11:52:44 -0500 Subject: [PATCH 05/48] Created a new rake task for fixing tag names with commas in them --- .../hub_feed_tag_filters/_list_item.html.haml | 2 +- lib/tasks/tagteam.rake | 69 +++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/app/views/hub_feed_tag_filters/_list_item.html.haml b/app/views/hub_feed_tag_filters/_list_item.html.haml index d99e0798..5498b6cb 100644 --- a/app/views/hub_feed_tag_filters/_list_item.html.haml +++ b/app/views/hub_feed_tag_filters/_list_item.html.haml @@ -3,7 +3,7 @@ - if current_user && (current_user.is?(:owner, @hub) || current_user.is?(:owner, hub_feed_tag_filter)) = link_to hub_feed_tag_filter_path(hub_feed_tag_filter.scope, hub_feed_tag_filter), method: :delete, - title: 'Remove this filter' + title: 'Remove this filter', confirm: 'Are you sure you want to remove this filter?' do = fa_icon 'times' %span.filter-action= filter_description(hub_feed_tag_filter) diff --git a/lib/tasks/tagteam.rake b/lib/tasks/tagteam.rake index c887d5b6..a74d3729 100644 --- a/lib/tasks/tagteam.rake +++ b/lib/tasks/tagteam.rake @@ -106,4 +106,73 @@ namespace :tagteam do Sunspot.session = original_sunspot_session end + + desc 'Fix tag names with commas in them' + task fix_tag_names_with_commas: :environment do + affected_tags = ActsAsTaggableOn::Tag.where('name LIKE \'%,%\'') + + affected_tags.each do |tag| + tag_name = tag.name + name_tags = tag_name.split(',') - ['', nil] + is_multiple = name_tags.length > 1 + filters_with_tag = TagFilter.where(tag: tag) + filter_with_new_as_tag = TagFilter.where(new_tag: tag) + + if is_multiple + taggings = tag.taggings + + name_tags.each do |name_tag| + name_tag = name_tag.strip + + existing_tag = ActsAsTaggableOn::Tag + .where('name=?', name_tag).first + + tag_to_tag = if existing_tag.nil? + ActsAsTaggableOn::Tag.create( + name: name_tag + ) + else + existing_tag + end + + taggings.each do |tagging| + new_tagging = tagging.dup + + new_tagging.tag_id = tag_to_tag.id + + new_tagging.save! + + tagging.destroy + end + + unless filters_with_tag.empty? + filters_with_tag.each do |filter_with_tag| + new_filter = filter_with_tag.dup + + new_filter.tag = tag_to_tag + + new_filter.save(validate: false) + end + end + + unless filter_with_new_as_tag.empty? + filter_with_new_as_tag.each do |filter_with_tag| + new_filter = filter_with_tag.dup + + new_filter.new_tag = tag_to_tag + + new_filter.save(validate: false) + end + end + end + + tag.destroy + filters_with_tag.destroy_all + filter_with_new_as_tag.destroy_all + else + tag.name = tag.name.gsub(',', '').strip + tag.save! + end + end + end end From 9847c33097fc0ec03a4520b201d7a6fc0d47f6ec Mon Sep 17 00:00:00 2001 From: Peter Hankiewicz Date: Thu, 9 Mar 2017 12:02:06 -0500 Subject: [PATCH 06/48] Added some log messages to the fix_tag_names_with_commas rake task --- lib/tasks/tagteam.rake | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/tasks/tagteam.rake b/lib/tasks/tagteam.rake index a74d3729..a6e7fc50 100644 --- a/lib/tasks/tagteam.rake +++ b/lib/tasks/tagteam.rake @@ -113,12 +113,17 @@ namespace :tagteam do affected_tags.each do |tag| tag_name = tag.name + + puts "Starting a fix of the '#{tag_name}' tag" + name_tags = tag_name.split(',') - ['', nil] is_multiple = name_tags.length > 1 filters_with_tag = TagFilter.where(tag: tag) filter_with_new_as_tag = TagFilter.where(new_tag: tag) if is_multiple + puts 'It\'s a complex tag, splitting and fixing' + taggings = tag.taggings name_tags.each do |name_tag| @@ -146,6 +151,8 @@ namespace :tagteam do end unless filters_with_tag.empty? + puts 'Recreating tag filters' + filters_with_tag.each do |filter_with_tag| new_filter = filter_with_tag.dup @@ -156,6 +163,8 @@ namespace :tagteam do end unless filter_with_new_as_tag.empty? + puts 'Recreating tag filters (modified)' + filter_with_new_as_tag.each do |filter_with_tag| new_filter = filter_with_tag.dup @@ -170,9 +179,13 @@ namespace :tagteam do filters_with_tag.destroy_all filter_with_new_as_tag.destroy_all else + puts 'It\' just a typo, renaming' + tag.name = tag.name.gsub(',', '').strip tag.save! end + + puts "Fixed the #{tag_name} tag" end end end From 2527ba742eb793cd1cbc2469512a07cf4ea1e385 Mon Sep 17 00:00:00 2001 From: Peter Hankiewicz Date: Mon, 6 Mar 2017 15:48:03 -0500 Subject: [PATCH 07/48] Created an import/export module --- app/controllers/export_import_controller.rb | 28 ++ app/controllers/hub_feeds_controller.rb | 2 +- app/controllers/hubs_controller.rb | 2 +- app/models/hub.rb | 45 +- app/models/tag_filter.rb | 7 +- app/views/export_import/index.html.haml | 18 + app/views/layouts/application.html.haml | 1 + config/routes.rb | 4 + lib/tagteam/export_import.rb | 433 ++++++++++++++++++++ 9 files changed, 536 insertions(+), 4 deletions(-) create mode 100644 app/controllers/export_import_controller.rb create mode 100644 app/views/export_import/index.html.haml create mode 100644 lib/tagteam/export_import.rb diff --git a/app/controllers/export_import_controller.rb b/app/controllers/export_import_controller.rb new file mode 100644 index 00000000..60710096 --- /dev/null +++ b/app/controllers/export_import_controller.rb @@ -0,0 +1,28 @@ +class ExportImportController < ApplicationController + def index + breadcrumbs.add 'Export/import', export_import_path + end + + def download + data = Tagteam::ExportImport.get_all_user_data current_user + + send_data data, filename: format('tagteam_export_%s.json', Time.now) + end + + def import + if params[:file].nil? + flash[:error] = 'File is missing, please try again.' + else + content = File.read(params[:file].tempfile) + result = Tagteam::ExportImport.import content + + if result + flash[:notice] = 'Successfully imported user data.' + else + flash[:error] = 'File is not properly structured or empty, please try again.' + end + end + + redirect_to request.referer + end +end diff --git a/app/controllers/hub_feeds_controller.rb b/app/controllers/hub_feeds_controller.rb index a2d08fc5..2123daec 100644 --- a/app/controllers/hub_feeds_controller.rb +++ b/app/controllers/hub_feeds_controller.rb @@ -6,8 +6,8 @@ class HubFeedsController < ApplicationController } before_action :authenticate_user!, except: [:autocomplete, :controls, :index, :more_details, :show] - before_action :find_hub before_action :find_hub_feed, except: [:autocomplete, :create, :index, :new] + before_action :find_hub after_action :verify_authorized, except: [:autocomplete, :controls, :index, :more_details, :show] diff --git a/app/controllers/hubs_controller.rb b/app/controllers/hubs_controller.rb index efc1e21d..812c4c0c 100644 --- a/app/controllers/hubs_controller.rb +++ b/app/controllers/hubs_controller.rb @@ -545,7 +545,7 @@ def destroy flash[:notice] = 'Deleted that hub' respond_to do |format| format.html do - redirect_to :back + redirect_to my_hubs_path end end end diff --git a/app/models/hub.rb b/app/models/hub.rb index c81d0f30..74d977e8 100644 --- a/app/models/hub.rb +++ b/app/models/hub.rb @@ -24,7 +24,8 @@ class Hub < ApplicationRecord include TagScopable extend FriendlyId - attr_accessible :title, :description, :tag_prefix, :nickname + attr_accessible :title, :description, :tag_prefix, :nickname, :slug, + :notify_taggers, :allow_taggers_to_sign_up_for_notifications acts_as_authorization_object friendly_id :nickname, use: [:slugged, :history] @@ -168,4 +169,46 @@ def self.apply_all_tag_filters_to_item_async(item) ApplyTagFilters.perform_async(hub.all_tag_filters.pluck(:id), item.id, true) end end + + # all tags used in the hub + def tags + filters_applied = ( + all_tag_filters.pluck(:tag_id) + + all_tag_filters.pluck(:new_tag_id) - + ['', nil] + ).uniq.join(',') + + tags_hub = ActsAsTaggableOn::Tag.find_by_sql( + [ + 'SELECT tags.* + FROM tags JOIN taggings ON taggings.tag_id = tags.id + WHERE taggings.context = ? AND taggings.taggable_type = ? + GROUP BY tags.id', tagging_key, 'FeedItem' + ] + ) + + tags_filters = [] + unless filters_applied.empty? + tags_filters = ActsAsTaggableOn::Tag.find_by_sql( + [ + 'SELECT tags.* + FROM tags WHERE tags.id IN (' + filters_applied + ')' + ] + ) + end + + tags_hub + tags_filters + end + + # all taggings related to the hub + def taggings + tags_hub = ActsAsTaggableOn::Tagging.find_by_sql( + [ + 'SELECT taggings.* + FROM taggings + WHERE taggings.context = ? AND taggings.taggable_type = ?', + tagging_key, 'FeedItem' + ] + ) + end end diff --git a/app/models/tag_filter.rb b/app/models/tag_filter.rb index 1c339c46..9d293813 100644 --- a/app/models/tag_filter.rb +++ b/app/models/tag_filter.rb @@ -17,7 +17,8 @@ class TagFilter < ApplicationRecord validates :tag_id, uniqueness: { scope: [:scope_type, :scope_id], message: 'Filter conflicts with existing filter.' } - attr_accessible :tag_id + attr_accessible :tag_id, :hub_id, :new_tag_id, :type, :scope_type, :scope_id, + :applied acts_as_authorization_object acts_as_api do |c| @@ -217,4 +218,8 @@ def notify_about_items_modification(hub, current_user) def self.policy_class TagFilterPolicy end + + def as_json(options = {}) + super(options.merge(methods: :type)) + end end diff --git a/app/views/export_import/index.html.haml b/app/views/export_import/index.html.haml new file mode 100644 index 00000000..508e7412 --- /dev/null +++ b/app/views/export_import/index.html.haml @@ -0,0 +1,18 @@ +#export-import + .row + %h3 Export + .row + %p Click below to download your account data. You can use it to import in an another TagTeam installation. + .row + %a.btn.btn-success{ title: 'Download data', href: export_import_download_path } + Download data + .row + %h3 Import + .row + %p Use the form below to upload data previously exported. + .row + = form_tag(export_import_path, method: 'post', multipart: true) do + = label_tag 'file', 'Select to upload' + = file_field_tag 'file' + %br + = submit_tag 'Import data', class: 'btn btn-success' diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 0544d755..63aa0552 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -26,6 +26,7 @@ %li.active= link_to 'Hubs', hubs_path - if user_signed_in? && current_user.is?(:superadmin) %li= link_to('Users', users_url(protocol: protocol_resolver)) + %li= link_to('Export/import', export_import_path) .nav.navbar-nav.navbar-right - if user_signed_in? %p.navbar-text.welcome diff --git a/config/routes.rb b/config/routes.rb index f72930fc..6e018453 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -59,6 +59,10 @@ get 'remix/:url_key' => 'republished_feeds#show', :as => 'remix' get 'remix/:url_key/items' => 'republished_feeds#items', :as => 'remix_items' + get 'export_import' => 'export_import#index' + get 'export_import/download' => 'export_import#download' + post 'export_import' => 'export_import#import' + resources :hubs do get 'tag/rss/:name' => 'tags#rss', :as => 'tag_rss', :constraints => { name: /.+/ } get 'tag/atom/:name' => 'tags#atom', :as => 'tag_atom', :constraints => { name: /.+/ } diff --git a/lib/tagteam/export_import.rb b/lib/tagteam/export_import.rb new file mode 100644 index 00000000..0daa88de --- /dev/null +++ b/lib/tagteam/export_import.rb @@ -0,0 +1,433 @@ +require 'json' + +module Tagteam + class ExportImport + # get and structure user hubs data + def self.get_all_user_data(current_user) + data_structure = { + hubs: [] + } + + user_hubs = current_user.my(Hub) + + user_hubs.each do |hub| + # process remixes + remixes = [] + input_sources = [] + + hub.republished_feeds.each do |remix| + remix_to_add = { + remix: remix + } + + remixes << remix_to_add + input_sources += remix.input_sources + end + + # process users + users = [] + hub.users_with_roles.each do |user| + user_to_add = { + user: user, + roles: user.roles + } + + users << user_to_add + end + + # process feed items + feed_items = [] + hub.feed_items.each do |feed_item| + feed_item_to_add = { + feed_item: feed_item, + feeds: feed_item.feeds + } + + feed_items << feed_item_to_add + end + + # structure hub + hub_to_add = { + hub: hub, + feeds: hub.feeds, + filters: hub.all_tag_filters, + remixes: remixes, + input_sources: input_sources, + feed_items: feed_items, + tags: hub.tags, + taggings: hub.taggings, + users: users + } + + data_structure[:hubs] << hub_to_add.as_json + end + + # convert to a nice and shiny JSON object + JSON.pretty_generate(data_structure) + end + + def self.import(content) + data = JSON.parse(content, symbolize_names: true) + + if validate_data(data) && !data[:hubs].empty? + return process_data_import(data) + end + + false + end + + def self.validate_data(data) + return true if data.include?(:hubs) + + false + end + + def self.process_data_import(data) + data[:hubs].each do |hub_data| + imported_hub = import_hub(hub_data[:hub]) + + imported_feeds = import_feeds(hub_data[:feeds], imported_hub[:new_id]) + + imported_tags = import_tags(hub_data[:tags]) + + imported_feed_items = import_feed_items( + hub_data[:feed_items], + imported_feeds + ) + + imported_filters = import_filters( + hub_data[:filters], + imported_tags, + imported_hub[:new_id], + imported_feeds, + imported_feed_items + ) + + imported_remixes = import_remixes( + hub_data[:remixes], + imported_hub[:new_id], + imported_feeds, + imported_feed_items, + imported_tags + ) + + imported_input_sources = import_input_sources( + hub_data[:input_sources], + imported_hub[:new_id], + imported_feeds, + imported_feed_items, + imported_tags, + imported_remixes + ) + + imported_users = import_users( + hub_data[:users], + imported_hub[:new_id], + imported_feeds, + imported_tags, + imported_feed_items, + imported_filters, + imported_remixes, + imported_input_sources + ) + + Sidekiq::Client.enqueue(RecalcAllItems, imported_hub[:new_id]) + end + end + + def self.import_hub(hub) + new_hub = Hub.new + new_hub.attributes = hub.except(:id) + + new_hub.save! + + { + old_id: hub[:id], + new_id: new_hub.id + } + end + + def self.import_feeds(feeds, hub_id) + new_feeds = [] + + feeds.each do |feed| + existing_feed = Feed.where(feed_url: feed[:feed_url]).first + + if existing_feed.nil? + new_feed = Feed.new + + new_feed.attributes = feed.except(:id) + + new_feed.save! + + new_id = new_feed.id + else + new_id = existing_feed.id + end + + new_feeds << { + old_id: feed[:id], + new_id: new_id + } + + hub_feed = HubFeed.new + hub_feed.feed_id = new_id + hub_feed.hub_id = hub_id + + hub_feed.save! + end + + new_feeds + end + + def self.import_tags(tags) + new_tags = [] + + tags.each do |tag| + existing_tag = ActsAsTaggableOn::Tag.where(name: tag[:name]).first + + if existing_tag.nil? + new_tag = ActsAsTaggableOn::Tag.new + + new_tag.name = tag[:name] + + new_tag.save! + + new_tags << { + old_id: tag[:id], + new_id: new_tag.id + } + else + new_tags << { + old_id: tag[:id], + new_id: existing_tag.id + } + end + end + + new_tags + end + + def self.import_filters(filters, tags, hub_id, feeds, items) + new_filters = [] + + filters.each do |filter| + new_filter = TagFilter.new + + new_filter.attributes = filter.except(:id) + + if filter[:scope_type] == 'Hub' + new_filter.scope_id = hub_id + elsif filter[:scope_type] == 'HubFeed' + new_filter.scope_id = feeds.select { |feed| feed[:old_id] == filter[:scope_id] }.first[:new_id] + elsif filter[:scope_type] == 'FeedItem' + new_filter.scope_id = items.select { |item| item[:old_id] == filter[:scope_id] }.first[:new_id] + else + next + end + + new_filter.hub_id = hub_id + new_filter.tag_id = tags.select { |tag| tag[:old_id] == filter[:tag_id] }.first[:new_id] + + new_filter.save! + + new_filters << { + old_id: filter[:id], + new_id: new_filter.id + } + end + + new_filters + end + + def self.import_feed_items(items, feeds) + new_items = [] + + items.each do |item| + existing_item = FeedItem.where(url: item[:feed_item][:url]).first + + if existing_item.nil? + new_item = FeedItem.new + + new_item.attributes = item[:feed_item].except(:id) + + new_item.feeds = Feed.where(id: feeds_old_to_new(item[:feeds], feeds)) + + new_item.save! + + new_id = new_item.id + else + existing_item.feeds = Feed.where( + id: existing_item.feeds.pluck(:id) + feeds_old_to_new(item[:feeds], feeds) + ) + + existing_item.save! + + new_id = existing_item.id + end + + new_items << { + old_id: item[:feed_item][:id], + new_id: new_id + } + end + + new_items + end + + def self.feeds_old_to_new(feeds_old, all_feeds) + mapped_feeds = [] + + feeds_old.each do |feed_old| + to_map = all_feeds.select { |feed| feed[:old_id] == feed_old[:id] }.first + + next if to_map.nil? + + mapped_feeds << to_map[:new_id] + end + + mapped_feeds + end + + def self.import_remixes(remixes, hub_id, feeds, feed_items, tags) + new_remixes = [] + + remixes.each do |remix_data| + existing_remix = RepublishedFeed.where(url_key: remix_data[:remix][:url_key]).first + + new_remix = RepublishedFeed.new + + new_remix.attributes = remix_data[:remix].except(:id) + + unless existing_remix.nil? + new_remix.url_key = remix_data[:remix][:url_key] + hub_id.to_s + end + + new_remix.hub_id = hub_id + + new_remix.save! + + new_id = new_remix.id + + new_remixes << { + old_id: remix_data[:remix][:id], + new_id: new_id + } + end + + new_remixes + end + + def self.import_input_sources(input_sources, hub_id, feeds, feed_items, tags, remixes) + new_input_sources = [] + + input_sources.each do |input_source| + remix_id = remixes.select { |remix| remix[:old_id] == input_source[:republished_feed_id] }.first[:new_id] + + existing_input_source = InputSource.where( + item_source_type: input_source[:item_source_type], + item_source_id: input_source[:item_source_id], + effect: input_source[:effect], + republished_feed_id: remix_id + ).first + + if existing_input_source.nil? + new_input_source = InputSource.new + + if input_source[:item_source_type] == 'Feed' + new_input_source.item_source_id = feeds.select { |feed| feed[:old_id] == input_source[:item_source_id] }.first[:new_id] + elsif input_source[:item_source_type] == 'FeedItem' + new_input_source.item_source_id = feed_items.select { |feed_item| feed_item[:old_id] == input_source[:item_source_id] }.first[:new_id] + elsif input_source[:item_source_type] == 'ActsAsTaggableOn::Tag' + new_input_source.item_source_id = tags.select { |tag| tag[:old_id] == input_source[:item_source_id] }.first[:new_id] + else + next + end + + new_input_source.attributes = input_source.except(:id) + + new_input_source.republished_feed_id = remix_id + + new_input_source.save! + + new_input_sources << { + old_id: input_source[:id], + new_id: new_input_source.id + } + else + new_input_sources << { + old_id: input_source[:id], + new_id: existing_input_source.id + } + end + end + + new_input_sources + end + + def self.import_users(users, hub_id, feeds, tags, feed_items, filters, remixes, input_sources) + new_users = [] + + users.each do |user| + exisiting_user = User.where(email: user[:user][:email]).first + + if exisiting_user.nil? + new_user = User.new + + new_user.attributes = user[:user].except(:id) + + new_user.save! + else + new_user = exisiting_user + end + + user[:roles].each do |role| + new_role = Role.new + + new_role.attributes = role.except(:id) + new_role.authorizable_type = role[:authorizable_type] + + case role[:authorizable_type] + when 'Hub' + new_role.authorizable_id = hub_id + when 'Feed' + related_item = feeds.select { |feed| feed[:old_id] == role[:authorizable_id] }.first + + next if related_item.nil? + + new_role.authorizable_id = related_item[:new_id] + when 'TagFilter' + related_item = filters.select { |filter| filter[:old_id] == role[:authorizable_id] }.first + + next if related_item.nil? + + new_role.authorizable_id = related_item[:new_id] + when 'RepublishedFeed' + related_item = remixes.select { |remix| remix[:old_id] == role[:authorizable_id] }.first + + next if related_item.nil? + + new_role.authorizable_id = related_item[:new_id] + when 'InputSource' + related_item = input_sources.select { |input_source| input_source[:old_id] == role[:authorizable_id] }.first + + next if related_item.nil? + + new_role.authorizable_id = related_item[:new_id] + else + next + end + + new_role.users = [new_user] + + new_role.save! + end + + new_users << { + old_id: user[:id], + new_id: new_user.id + } + end + end + end +end From 5989621ba07025b7e9ab33784bafdaefcf2a7e40 Mon Sep 17 00:00:00 2001 From: Peter Hankiewicz Date: Wed, 8 Mar 2017 15:18:17 -0500 Subject: [PATCH 08/48] Converted an import of the user data to a worker --- app/controllers/export_import_controller.rb | 15 ++++++++------- app/mailers/notifications.rb | 7 +++++++ ...ata_import_completion_notification.text.haml | 4 ++++ app/workers/import_user_data.rb | 17 +++++++++++++++++ lib/tagteam/export_import.rb | 3 ++- 5 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 app/views/notifications/user_data_import_completion_notification.text.haml create mode 100644 app/workers/import_user_data.rb diff --git a/app/controllers/export_import_controller.rb b/app/controllers/export_import_controller.rb index 60710096..2ea27bac 100644 --- a/app/controllers/export_import_controller.rb +++ b/app/controllers/export_import_controller.rb @@ -1,3 +1,5 @@ +require 'tempfile' + class ExportImportController < ApplicationController def index breadcrumbs.add 'Export/import', export_import_path @@ -13,14 +15,13 @@ def import if params[:file].nil? flash[:error] = 'File is missing, please try again.' else - content = File.read(params[:file].tempfile) - result = Tagteam::ExportImport.import content + temp_file = Tempfile.new('tagteam_import') + temp_file.write(File.read(params[:file].tempfile)) + ObjectSpace.undefine_finalizer(temp_file) + + Sidekiq::Client.enqueue(ImportUserData, temp_file.path) - if result - flash[:notice] = 'Successfully imported user data.' - else - flash[:error] = 'File is not properly structured or empty, please try again.' - end + flash[:notice] = 'Import is in progress. You will get an email notification when the import is done.' end redirect_to request.referer diff --git a/app/mailers/notifications.rb b/app/mailers/notifications.rb index 03d14ca1..fef4bc9e 100644 --- a/app/mailers/notifications.rb +++ b/app/mailers/notifications.rb @@ -21,4 +21,11 @@ def item_change_notification(hub, modified_item, item_users, current_user) subject = 'Item update in the ' + @hub.title + ' hub' mail(bcc: item_users.collect(&:email), subject: subject) end + + def user_data_import_completion_notification(email, status) + @status = status + + subject = 'Import status' + mail(cc: email, subject: subject) + end end diff --git a/app/views/notifications/user_data_import_completion_notification.text.haml b/app/views/notifications/user_data_import_completion_notification.text.haml new file mode 100644 index 00000000..a5d9da82 --- /dev/null +++ b/app/views/notifications/user_data_import_completion_notification.text.haml @@ -0,0 +1,4 @@ +- if @status + Data imported successfully. +- else + Something went wrong during the import. Please contact the system administrator if the problem persists. diff --git a/app/workers/import_user_data.rb b/app/workers/import_user_data.rb new file mode 100644 index 00000000..eb6be409 --- /dev/null +++ b/app/workers/import_user_data.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true +class ImportUserData + include Sidekiq::Worker + sidekiq_options queue: :importer + + def self.display_name + 'Importing user data' + end + + def perform(file_path) + file_content = File.read(file_path) + + File.delete(file_path) + + Tagteam::ExportImport.import(file_content) + end +end diff --git a/lib/tagteam/export_import.rb b/lib/tagteam/export_import.rb index 0daa88de..01db14a8 100644 --- a/lib/tagteam/export_import.rb +++ b/lib/tagteam/export_import.rb @@ -55,7 +55,6 @@ def self.get_all_user_data(current_user) input_sources: input_sources, feed_items: feed_items, tags: hub.tags, - taggings: hub.taggings, users: users } @@ -133,6 +132,8 @@ def self.process_data_import(data) Sidekiq::Client.enqueue(RecalcAllItems, imported_hub[:new_id]) end + + Notifications.user_data_import_completion_notification(email, true) end def self.import_hub(hub) From 4c7c43b9669b2a8e17fdb53bd83ff5df6c9f8716 Mon Sep 17 00:00:00 2001 From: Peter Hankiewicz Date: Wed, 8 Mar 2017 15:28:35 -0500 Subject: [PATCH 09/48] Restricted export/import to an authenticated user only --- app/controllers/export_import_controller.rb | 2 ++ app/views/layouts/application.html.haml | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/controllers/export_import_controller.rb b/app/controllers/export_import_controller.rb index 2ea27bac..2a3a0a4b 100644 --- a/app/controllers/export_import_controller.rb +++ b/app/controllers/export_import_controller.rb @@ -1,6 +1,8 @@ require 'tempfile' class ExportImportController < ApplicationController + before_action :authenticate_user! + def index breadcrumbs.add 'Export/import', export_import_path end diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 63aa0552..b90096c1 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -26,7 +26,8 @@ %li.active= link_to 'Hubs', hubs_path - if user_signed_in? && current_user.is?(:superadmin) %li= link_to('Users', users_url(protocol: protocol_resolver)) - %li= link_to('Export/import', export_import_path) + - if user_signed_in? + %li= link_to('Export/import', export_import_path) .nav.navbar-nav.navbar-right - if user_signed_in? %p.navbar-text.welcome From 991282fbddea85e6adb8bb82686884b37ffa051f Mon Sep 17 00:00:00 2001 From: Peter Hankiewicz Date: Wed, 8 Mar 2017 16:24:55 -0500 Subject: [PATCH 10/48] Improved the import process --- app/controllers/export_import_controller.rb | 2 +- app/workers/import_user_data.rb | 4 +-- lib/tagteam/export_import.rb | 28 +++++++++++++++------ 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/app/controllers/export_import_controller.rb b/app/controllers/export_import_controller.rb index 2a3a0a4b..04e16926 100644 --- a/app/controllers/export_import_controller.rb +++ b/app/controllers/export_import_controller.rb @@ -21,7 +21,7 @@ def import temp_file.write(File.read(params[:file].tempfile)) ObjectSpace.undefine_finalizer(temp_file) - Sidekiq::Client.enqueue(ImportUserData, temp_file.path) + Sidekiq::Client.enqueue(ImportUserData, temp_file.path, current_user.email) flash[:notice] = 'Import is in progress. You will get an email notification when the import is done.' end diff --git a/app/workers/import_user_data.rb b/app/workers/import_user_data.rb index eb6be409..142ed194 100644 --- a/app/workers/import_user_data.rb +++ b/app/workers/import_user_data.rb @@ -7,11 +7,11 @@ def self.display_name 'Importing user data' end - def perform(file_path) + def perform(file_path, user_email) file_content = File.read(file_path) File.delete(file_path) - Tagteam::ExportImport.import(file_content) + Tagteam::ExportImport.import(file_content, user_email) end end diff --git a/lib/tagteam/export_import.rb b/lib/tagteam/export_import.rb index 01db14a8..cc3b7e37 100644 --- a/lib/tagteam/export_import.rb +++ b/lib/tagteam/export_import.rb @@ -1,4 +1,5 @@ require 'json' +require 'securerandom' module Tagteam class ExportImport @@ -65,14 +66,26 @@ def self.get_all_user_data(current_user) JSON.pretty_generate(data_structure) end - def self.import(content) - data = JSON.parse(content, symbolize_names: true) + def self.import(content, user_email) + begin + data = JSON.parse(content, symbolize_names: true) - if validate_data(data) && !data[:hubs].empty? - return process_data_import(data) + ActiveRecord::Base.transaction do + process_data_import(data) if validate_data(data) && !data[:hubs].empty? + end + rescue => ex + deliver_email(false, user_email) + + return end - false + deliver_email(true, user_email) + end + + def self.deliver_email(status, user_email) + Notifications.user_data_import_completion_notification( + user_email, status + ).deliver_later end def self.validate_data(data) @@ -132,8 +145,6 @@ def self.process_data_import(data) Sidekiq::Client.enqueue(RecalcAllItems, imported_hub[:new_id]) end - - Notifications.user_data_import_completion_notification(email, true) end def self.import_hub(hub) @@ -377,6 +388,9 @@ def self.import_users(users, hub_id, feeds, tags, feed_items, filters, remixes, new_user.attributes = user[:user].except(:id) + new_user.password = SecureRandom.urlsafe_base64 + new_user.signup_reason = 'import' + new_user.save! else new_user = exisiting_user From e76d5e2c4fa5261a998ca82e9c37c439f138507e Mon Sep 17 00:00:00 2001 From: Patrick Lewis Date: Thu, 9 Mar 2017 16:52:40 -0500 Subject: [PATCH 11/48] Add hub_feed_updated helper --- app/helpers/hub_feeds_helper.rb | 14 ++++++++++++++ app/views/hub_feeds/more_details.html.haml | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 app/helpers/hub_feeds_helper.rb diff --git a/app/helpers/hub_feeds_helper.rb b/app/helpers/hub_feeds_helper.rb new file mode 100644 index 00000000..69e6fdda --- /dev/null +++ b/app/helpers/hub_feeds_helper.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true +# Helper methods for hub feeds +module HubFeedsHelper + def hub_feed_updated(hub_feed) + updated_at = + if hub_feed.latest_feed_items.any? + hub_feed.latest_feed_items.first.updated_at + else + hub_feed.feed.updated_at + end + + updated_at.to_s(:long) + end +end diff --git a/app/views/hub_feeds/more_details.html.haml b/app/views/hub_feeds/more_details.html.haml index 07a5529f..d9a9e871 100644 --- a/app/views/hub_feeds/more_details.html.haml +++ b/app/views/hub_feeds/more_details.html.haml @@ -1,5 +1,5 @@ .metadata{ id: "hub_feed_metadata_#{@hub_feed.id}" } - %span.updated{ title: 'Last updated' } Updated #{@hub_feed.latest_feed_items.first.updated_at.to_s(:long)} + %span.updated{ title: 'Last updated' } Updated #{hub_feed_updated(@hub_feed)} |  From 293d39242be972f66d0478e9df6ddbefb9ae4611 Mon Sep 17 00:00:00 2001 From: Peter Hankiewicz Date: Sun, 12 Mar 2017 14:48:34 -0400 Subject: [PATCH 12/48] Changed a way of adding tags to single items --- app/assets/stylesheets/hubs.scss | 6 ++++++ app/views/feed_items/_list_item.html.haml | 2 ++ app/views/feed_items/controls.html.haml | 3 --- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/hubs.scss b/app/assets/stylesheets/hubs.scss index 1e9e958a..9d5966a2 100644 --- a/app/assets/stylesheets/hubs.scss +++ b/app/assets/stylesheets/hubs.scss @@ -106,6 +106,12 @@ form.hub { &:hover { color: $gray; } + + &.fa-plus { + margin-top: 2px; + color: #8dc63f; + vertical-align: middle; + } } } } diff --git a/app/views/feed_items/_list_item.html.haml b/app/views/feed_items/_list_item.html.haml index 4d260ab2..724677c0 100644 --- a/app/views/feed_items/_list_item.html.haml +++ b/app/views/feed_items/_list_item.html.haml @@ -20,3 +20,5 @@ %div - unless feed_item.all_tags_on(hub.tagging_key).empty? = raw feed_item.all_tags_on(hub.tagging_key).collect{|t| tag_display(t, :hub => hub, :hub_feed => hub_feed, :hub_feed_item => feed_item) }.join(' ') + - if current_user.is?([:owner, :hub_feed_item_tag_filterer],@hub) + = link_to_tag_filter raw(fa_icon('plus', title: 'Add a tag to this item')), :add, { hub: @hub, item: feed_item } diff --git a/app/views/feed_items/controls.html.haml b/app/views/feed_items/controls.html.haml index 9e78255a..8e80d3d4 100644 --- a/app/views/feed_items/controls.html.haml +++ b/app/views/feed_items/controls.html.haml @@ -1,8 +1,5 @@ %ul.list-unstyled - if current_user && current_user.is?([:owner,:remixer,:hub_feed_item_tag_filterer,:bookmarker],@hub) - - if current_user.is?([:owner, :hub_feed_item_tag_filterer],@hub) - %li - = link_to_tag_filter raw(fa_icon('tag', text: 'Add a tag to this item')), :add, { hub: @hub, item: @feed_item } - if current_user.is?([:owner, :remixer],@hub) %li = link_to custom_republished_feeds_hub_path(@hub), From 1eb0702db5d1bf2e23aa797d7ce5ac5484e2751e Mon Sep 17 00:00:00 2001 From: Peter Hankiewicz Date: Sat, 11 Mar 2017 12:46:25 -0500 Subject: [PATCH 13/48] Created a remix feed which only includes or excludes a certain tag when applied by a certain tagger --- app/assets/javascripts/application.js | 4 +- app/assets/javascripts/autocomplete_user.js | 68 +++++++++++++++++++ app/assets/javascripts/hubs/community.js | 62 +---------------- .../hubs/custom_republished_feeds.js | 1 + app/models/feed_item.rb | 15 ++++ app/models/input_source.rb | 4 +- app/models/republished_feed.rb | 26 ++++++- app/views/feed_items/_list_item.html.haml | 2 +- app/views/hub_feeds/_list_item.html.haml | 2 +- .../hubs/custom_republished_feeds.html.haml | 23 +++++++ app/views/input_sources/_list_item.html.haml | 3 + ...1_add_user_restriction_to_input_sources.rb | 9 +++ 12 files changed, 152 insertions(+), 67 deletions(-) mode change 100755 => 100644 app/assets/javascripts/application.js create mode 100644 app/assets/javascripts/autocomplete_user.js create mode 100644 app/assets/javascripts/hubs/custom_republished_feeds.js create mode 100644 db/migrate/20170309211431_add_user_restriction_to_input_sources.rb diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js old mode 100755 new mode 100644 index bb1020e3..de698e0e --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -872,6 +872,7 @@ $(document).ready(function(){ var item_source_id = $('body').data('item_source_id_for_republishing'); var item_source_type = $('body').data('item_source_type_for_republishing'); var item_effect = $('body').data('item_effect_for_republishing'); + var user_id = $('input[name="user_ids[]"]').val(); var search_query = $('#q').val(); var hub_id = $('body').data('hub_id'); var args = { @@ -881,7 +882,8 @@ $(document).ready(function(){ republished_feed_id: republished_feed_id, item_source_type: item_source_type, item_source_id: item_source_id, - effect: item_effect + effect: item_effect, + created_by_only_id: (typeof user_id === 'undefined' ? false : user_id) } }; diff --git a/app/assets/javascripts/autocomplete_user.js b/app/assets/javascripts/autocomplete_user.js new file mode 100644 index 00000000..096bcfd8 --- /dev/null +++ b/app/assets/javascripts/autocomplete_user.js @@ -0,0 +1,68 @@ +/* global $ */ +$.extend({ + observeSearchSelectControl () { + $('.search_select_control').live({ + click (e) { + e.preventDefault(); + + var container = $(this).closest('ul'); + + $(this).closest('li.search_select').remove(); + + if (container.children('li').length === 0) { + $('input#add_roles').prop('disabled', true); + } + } + }); + }, + + observeAutocomplete (url, rootId, paramName, containerId, elementClass, singleValue) { + function split (val) { + return val.split(/,\s*/); + } + + function extractLast (term) { + return split(term).pop(); + } + + $(rootId) + .live('keydown', function (event) { + if (event.keyCode === $.ui.keyCode.TAB && $(this).data('autocomplete').menu.active) { + event.preventDefault(); + } + }) + .live('focus', function () { + $(this) + .autocomplete({ + source (request, response) { + $.getJSON(url, { + term: extractLast(request.term) + }, response); + }, + search () { + const term = extractLast(this.value); + if (term.length < 2) { + return false; + } + }, + focus () { + return false; + }, + select (event, ui) { + if (typeof singleValue != 'undefined' && singleValue === true) { + $(containerId).empty(); + } + + const node = $('
  • ').attr('class', elementClass); + $(node).html($(``).val(ui.item.value)); + $(node).append(ui.item.label); + $(node).append(' X '); + $(containerId).show().append(node); + $('input#add_roles').prop('disabled', false); + this.value = ''; + return false; + } + }); + }); + } +}); diff --git a/app/assets/javascripts/hubs/community.js b/app/assets/javascripts/hubs/community.js index 4fb18b49..b98ba9d5 100644 --- a/app/assets/javascripts/hubs/community.js +++ b/app/assets/javascripts/hubs/community.js @@ -1,61 +1 @@ -/* global $ */ -$.extend({ - observeSearchSelectControl () { - $('.search_select_control').live({ - click (e) { - e.preventDefault() - - var container = $(this).closest('ul') - - $(this).closest('li.search_select').remove() - - if (container.children('li').length === 0) { - $('input#add_roles').prop('disabled', true) - } - } - }) - }, - - observeAutocomplete (url, rootId, paramName, containerId, elementClass) { - function split (val) { - return val.split(/,\s*/) - } - - function extractLast (term) { - return split(term).pop() - } - - $(rootId) - .bind('keydown', function (event) { - if (event.keyCode === $.ui.keyCode.TAB && $(this).data('autocomplete').menu.active) { - event.preventDefault() - } - }) - .autocomplete({ - source (request, response) { - $.getJSON(url, { - term: extractLast(request.term) - }, response) - }, - search () { - const term = extractLast(this.value) - if (term.length < 2) { - return false - } - }, - focus () { - return false - }, - select (event, ui) { - const node = $('
  • ').attr('class', elementClass) - $(node).html($(``).val(ui.item.value)) - $(node).append(ui.item.label) - $(node).append(' X ') - $(containerId).show().append(node) - $('input#add_roles').prop('disabled', false) - this.value = '' - return false - } - }) - } -}) +//= require autocomplete_user diff --git a/app/assets/javascripts/hubs/custom_republished_feeds.js b/app/assets/javascripts/hubs/custom_republished_feeds.js new file mode 100644 index 00000000..b98ba9d5 --- /dev/null +++ b/app/assets/javascripts/hubs/custom_republished_feeds.js @@ -0,0 +1 @@ +//= require autocomplete_user diff --git a/app/models/feed_item.rb b/app/models/feed_item.rb index 3affeff8..79e649bd 100644 --- a/app/models/feed_item.rb +++ b/app/models/feed_item.rb @@ -91,6 +91,7 @@ class FeedItem < ApplicationRecord integer :feed_ids, multiple: true string :tag_list, using: :tag_list_array_for_indexing, multiple: true string :tag_contexts, multiple: true + string :tag_contexts_by_users, multiple: true string :title string :url @@ -127,6 +128,20 @@ def tag_contexts end.compact end + def tag_contexts_by_users + taggings.collect do |tg| + next if tg.context.eql? 'tags' + + auth_user = Role.where( + authorizable_id: tg.tagger_id, + authorizable_type: 'TagFilter', + name: 'creator' + ).first.users.first + + "#{tg.context}-#{tg.tag.name}-user_#{auth_user.id}" + end.compact + end + # A hash of arrays of tag contexts - used for the API. def tag_context_hierarchy tags_for_api = {} diff --git a/app/models/input_source.rb b/app/models/input_source.rb index 32de3bba..05f7322a 100644 --- a/app/models/input_source.rb +++ b/app/models/input_source.rb @@ -24,7 +24,9 @@ class InputSource < ApplicationRecord end validates :effect, inclusion: { in: EFFECTS } accepts_nested_attributes_for :item_source - attr_accessible :item_source, :item_source_attributes, :republished_feed, :republished_feed_id, :item_source_id, :item_source_type, :effect, :limit, :search_in + attr_accessible :item_source, :item_source_attributes, :republished_feed, + :republished_feed_id, :item_source_id, :item_source_type, + :effect, :limit, :search_in, :created_by_only_id attr_accessor :search_in api_accessible :default do |t| diff --git a/app/models/republished_feed.rb b/app/models/republished_feed.rb index 7472ed5a..be85f2e3 100644 --- a/app/models/republished_feed.rb +++ b/app/models/republished_feed.rb @@ -85,10 +85,12 @@ def item_search add_feeds = [] add_feed_items = [] add_tags = [] + add_tags_by_users = [] remove_feeds = [] remove_feed_items = [] remove_tags = [] + remove_tags_by_users = [] return nil if input_sources.blank? @@ -100,7 +102,14 @@ def item_search when 'FeedItem' add_feed_items << input_source.item_source_id when 'ActsAsTaggableOn::Tag' - add_tags << ActsAsTaggableOn::Tag.find(input_source.item_source_id) + if input_source.created_by_only_id.try :nonzero? + add_tags_by_users << { + tag: ActsAsTaggableOn::Tag.find(input_source.item_source_id), + user: User.find(input_source.created_by_only_id) + } + else + add_tags << ActsAsTaggableOn::Tag.find(input_source.item_source_id) + end when 'SearchRemix' add_feed_items << SearchRemix.search_results_for(input_source.item_source_id, limit) end @@ -111,7 +120,14 @@ def item_search when 'FeedItem' remove_feed_items << input_source.item_source_id when 'ActsAsTaggableOn::Tag' - remove_tags << ActsAsTaggableOn::Tag.find(input_source.item_source_id) + if input_source.created_by_only_id.try :nonzero? + remove_tags_by_users << { + tag: ActsAsTaggableOn::Tag.find(input_source.item_source_id), + user: User.find(input_source.created_by_only_id) + } + else + remove_tags << ActsAsTaggableOn::Tag.find(input_source.item_source_id) + end end end end @@ -126,6 +142,9 @@ def item_search unless add_tags.blank? with(:tag_contexts, add_tags.collect { |t| "hub_#{hub_id}-#{t.name}" }) end + unless add_tags_by_users.blank? + with(:tag_contexts_by_users, add_tags_by_users.collect { |t| "hub_#{hub_id}-#{t[:tag].name}-user_#{t[:user].id}" }) + end end any_of do without(:feed_ids, remove_feeds) unless remove_feeds.blank? @@ -133,6 +152,9 @@ def item_search unless remove_tags.blank? without(:tag_contexts, remove_tags.collect { |t| "hub_#{hub_id}-#{t.name}" }) end + unless remove_tags_by_users.blank? + without(:tag_contexts_by_users, remove_tags_by_users.collect { |t| "hub_#{hub_id}-#{t[:tag].name}-user_#{t[:user].id}" }) + end end order_by('date_published', :desc) paginate per_page: limit, page: 1 diff --git a/app/views/feed_items/_list_item.html.haml b/app/views/feed_items/_list_item.html.haml index 724677c0..8c92ef5d 100644 --- a/app/views/feed_items/_list_item.html.haml +++ b/app/views/feed_items/_list_item.html.haml @@ -6,7 +6,7 @@ - cache("feed-item-tag-list-#{hub.try(:id)}-#{hub_feed.try(:id)}-#{feed_item.try(:id)}", :expires => 120.minutes ) do .col-xs-10.col-xs-offset-1 %h2 - = link_to(raw(strip_tags(feed_item.title)), hub_feed_feed_item_path(hub_feed,feed_item)) + = link_to(raw(strip_tags(feed_item.title)), hub_feed_feed_item_path(hub_feed, feed_item)) = link_to controls_hub_feed_feed_item_path(hub_feed,feed_item), class: 'control', title: 'Item actions' do = fa_icon 'cog' %p.text-nowrap diff --git a/app/views/hub_feeds/_list_item.html.haml b/app/views/hub_feeds/_list_item.html.haml index 6f047e67..6a4d33bb 100644 --- a/app/views/hub_feeds/_list_item.html.haml +++ b/app/views/hub_feeds/_list_item.html.haml @@ -1,7 +1,7 @@ %li{ id: "hub_feed-#{hub_feed.id}" } .media .pull-right - = link_to controls_hub_feed_path(hub_feed), class: 'control', title: 'Feed actions' do + = link_to controls_hub_hub_feed_path(@hub, hub_feed), class: 'control', title: 'Feed actions' do = fa_icon 'cog' .media-left = link_to more_details_hub_hub_feed_path(@hub, hub_feed), diff --git a/app/views/hubs/custom_republished_feeds.html.haml b/app/views/hubs/custom_republished_feeds.html.haml index a50430ce..6fd24d78 100644 --- a/app/views/hubs/custom_republished_feeds.html.haml +++ b/app/views/hubs/custom_republished_feeds.html.haml @@ -2,6 +2,29 @@ %h3 Remix feeds %ul = render :partial => 'shared/line_items/republished_feed_choice', :collection => @republished_feeds + #republised-feeds-user-select.hide + %h3 Only when applied by a certain user + %ul#user_id_container + = text_field_tag :find_user_autocomplete, '', size: 40, placeholder: 'Username or email address', class: 'form-control' + :javascript + $(document).ready(function(){ + $.observeAutocomplete($.rootPath() + 'users/search/autocomplete','#find_user_autocomplete', 'user_ids', '#user_id_container','search_select user', true); + $.observeSearchSelectControl(); + + $('#republised-feeds-user-select').remove(); + + if ($('body').data('item_source_type_for_republishing') == 'ActsAsTaggableOn::Tag') { + var showUserForm = function () { + if ($('#republised-feeds-user-select').length == 0) { + setTimeout(showUserForm, 50); + } else { + $('#republised-feeds-user-select').removeClass('hide'); + } + } + showUserForm(); + } + }); + %hr - else .empty-message = "None yet. You should create a remix feed from the \"remixes\" tab on the hub page." diff --git a/app/views/input_sources/_list_item.html.haml b/app/views/input_sources/_list_item.html.haml index eb7b5017..532a0d97 100644 --- a/app/views/input_sources/_list_item.html.haml +++ b/app/views/input_sources/_list_item.html.haml @@ -39,3 +39,6 @@ - else = raw input_source.item_source.mini_icon = tag_display(input_source.item_source, :hub => hub) + - if input_source.created_by_only_id.try :nonzero? + created by + = User.find(input_source.created_by_only_id).username diff --git a/db/migrate/20170309211431_add_user_restriction_to_input_sources.rb b/db/migrate/20170309211431_add_user_restriction_to_input_sources.rb new file mode 100644 index 00000000..16e30ac2 --- /dev/null +++ b/db/migrate/20170309211431_add_user_restriction_to_input_sources.rb @@ -0,0 +1,9 @@ +class AddUserRestrictionToInputSources < ActiveRecord::Migration[5.0] + def up + add_column :input_sources, :created_by_only_id, :integer + end + + def down + remove_column :input_sources, :created_by_only_id, :integer + end +end From 83d465e35e17bf8cb91ac296225ad8c95774aec8 Mon Sep 17 00:00:00 2001 From: Peter Hankiewicz Date: Sat, 11 Mar 2017 12:59:57 -0500 Subject: [PATCH 14/48] Updated an index in the input_sources table --- app/models/input_source.rb | 2 +- .../20170311175714_update_input_sources_index.rb | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20170311175714_update_input_sources_index.rb diff --git a/app/models/input_source.rb b/app/models/input_source.rb index 05f7322a..16415fc0 100644 --- a/app/models/input_source.rb +++ b/app/models/input_source.rb @@ -12,7 +12,7 @@ class InputSource < ApplicationRecord acts_as_authorization_object include ModelExtensions - validates :item_source_type, uniqueness: { scope: [:item_source_id, :effect, :republished_feed_id] } + validates :item_source_type, uniqueness: { scope: [:item_source_id, :effect, :republished_feed_id, :created_by_only_id] } EFFECTS = %w(add remove).freeze diff --git a/db/migrate/20170311175714_update_input_sources_index.rb b/db/migrate/20170311175714_update_input_sources_index.rb new file mode 100644 index 00000000..21cbb5a1 --- /dev/null +++ b/db/migrate/20170311175714_update_input_sources_index.rb @@ -0,0 +1,13 @@ +class UpdateInputSourcesIndex < ActiveRecord::Migration[5.0] + def up + remove_index :input_sources, [:item_source_type, :item_source_id, :effect, :republished_feed_id] + + add_index :input_sources, [:item_source_type, :item_source_id, :effect, :republished_feed_id, :created_by_only_id], :unique => true, :name => 'bob_the_index' + end + + def down + remove_index :input_sources, [:item_source_type, :item_source_id, :effect, :republished_feed_id, :created_by_only_id] + + add_index :input_sources, [:item_source_type, :item_source_id, :effect, :republished_feed_id], :unique => true, :name => 'bob_the_index' + end +end From 9ecb562cf73c0317bc4dafe394356c17022cdf9c Mon Sep 17 00:00:00 2001 From: Patrick Lewis Date: Mon, 13 Mar 2017 13:31:45 -0400 Subject: [PATCH 15/48] Update schema.rb --- db/schema.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index a6f602fa..5c861811 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170307193237) do +ActiveRecord::Schema.define(version: 20170311175714) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -168,9 +168,10 @@ t.integer "limit" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "created_by_only_id" t.index ["effect"], name: "index_input_sources_on_effect", using: :btree t.index ["item_source_id"], name: "index_input_sources_on_item_source_id", using: :btree - t.index ["item_source_type", "item_source_id", "effect", "republished_feed_id"], name: "bob_the_index", unique: true, using: :btree + t.index ["item_source_type", "item_source_id", "effect", "republished_feed_id", "created_by_only_id"], name: "bob_the_index", unique: true, using: :btree t.index ["republished_feed_id"], name: "index_input_sources_on_republished_feed_id", using: :btree end From 34c218bc7e647d43fce0f9355fb6ad75f498b4f0 Mon Sep 17 00:00:00 2001 From: Patrick Lewis Date: Mon, 13 Mar 2017 15:17:36 -0400 Subject: [PATCH 16/48] Fix #13192 Allow multiple TagFilters to be created from a single comma-separated string --- Gemfile | 1 + Gemfile.lock | 3 + app/controllers/tag_filters_controller.rb | 91 +++++++++-------------- app/interactions/tag_filters/create.rb | 74 ++++++++++++++++++ config/application.rb | 1 + config/locales/en.yml | 7 ++ 6 files changed, 120 insertions(+), 57 deletions(-) create mode 100644 app/interactions/tag_filters/create.rb diff --git a/Gemfile b/Gemfile index 3555e85e..2967b266 100644 --- a/Gemfile +++ b/Gemfile @@ -5,6 +5,7 @@ ruby '2.3.3' gem 'acl9', git: 'https://github.com/be9/acl9.git', branch: 'finalist-update-rails' gem 'actionpack-action_caching', '~> 1.2' +gem 'active_interaction', '~> 3.4' gem 'acts-as-taggable-on', '~> 4.0' gem 'acts_as_api', '~> 0.4' gem 'bootstrap-sass', '~> 3.3' diff --git a/Gemfile.lock b/Gemfile.lock index 81f726c9..265bfd13 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -48,6 +48,8 @@ GEM erubis (~> 2.7.0) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) + active_interaction (3.4.0) + activemodel (>= 4, < 6) activejob (5.0.1) activesupport (= 5.0.1) globalid (>= 0.3.6) @@ -402,6 +404,7 @@ PLATFORMS DEPENDENCIES acl9! actionpack-action_caching (~> 1.2) + active_interaction (~> 3.4) acts-as-taggable-on (~> 4.0) acts_as_api (~> 0.4) awesome_print (~> 1.7) diff --git a/app/controllers/tag_filters_controller.rb b/app/controllers/tag_filters_controller.rb index 360311f3..efa0972e 100644 --- a/app/controllers/tag_filters_controller.rb +++ b/app/controllers/tag_filters_controller.rb @@ -26,66 +26,15 @@ def new end def create - filter_type = params[:filter_type].constantize - unless params[:tag_id].blank? - @tag = ActsAsTaggableOn::Tag.find(params[:tag_id]) - end - - if params[:filter_type] == 'ModifyTagFilter' - @tag ||= find_or_create_tag_by_name(params[:modify_tag]) - @new_tag = find_or_create_tag_by_name(params[:new_tag]) - else - @tag ||= find_or_create_tag_by_name(params[:new_tag]) - end - - @tag_filter = filter_type.new - @tag_filter.hub = @hub - @tag_filter.scope = @scope - @tag_filter.tag = @tag - @tag_filter.new_tag = @new_tag if @new_tag + authorize TagFilter - authorize @tag_filter - - if @tag_filter.save - current_user.has_role!(:owner, @tag_filter) - current_user.has_role!(:creator, @tag_filter) - flash[:notice] = %(Added a filter for that tag to "#{@scope.title}") - - if @hub.notify_taggers && @new_tag - hub_feed_to_notify = @hub_feed.nil? ? nil : @hub_feed.id - - Sidekiq::Client.enqueue( - SendTagChangeNotifications, - @tag_filter.id, - @tag.id, - @new_tag.id, - @scope.class.name, - @scope.id, - @hub.id, - hub_feed_to_notify, - current_user.id - ) - end - - if @hub.allow_taggers_to_sign_up_for_notifications - Sidekiq::Client.enqueue( - SendItemChangeNotifications, - @tag_filter.id, - @hub.id, - current_user.id - ) + # Allow multiple TagFilters to be created from a comma-separated string of tags + tag_filters = + params[:new_tag].split(',').map do |tag| + TagFilters::Create.run(tag_filter_params.merge(new_tag_name: tag)) end - @tag_filter.apply_async - - render plain: %(Added a filter for that tag to "#{@scope.title}"), - layout: !request.xhr? - else - flash[:error] = 'Could not add that tag filter.' - render html: @tag_filter.errors.full_messages.join('
    '), - status: :not_acceptable, - layout: !request.xhr? - end + tag_filters.all?(&:valid?) ? process_successful_create(tag_filters) : process_failed_create(tag_filters) end def destroy @@ -142,4 +91,32 @@ def load_tag_filter @tag_filter = TagFilter.find(params[:id]) authorize @tag_filter end + + def tag_filter_params + { + filter_type: params[:filter_type], + hub: @hub, + hub_feed: @hub_feed, + modify_tag_name: params[:modify_tag], + new_tag_name: params[:new_tag], + scope: @scope, + user: current_user + } + end + + def process_successful_create(tag_filters) + notice = t('tag_filters.added', count: tag_filters.size, scope_title: @scope.title) + + flash[:notice] = notice + + render plain: notice, layout: !request.xhr? + end + + def process_failed_create + flash[:error] = t('tag_filters.errors_when_adding', count: tag_filters.size) + + errors = tag_filters.map { |tag_filter| tag_filter.errors.full_messages.join('
    ') } + + render html: errors.join(' '), status: :not_acceptable, layout: !request.xhr? + end end diff --git a/app/interactions/tag_filters/create.rb b/app/interactions/tag_filters/create.rb new file mode 100644 index 00000000..e079a522 --- /dev/null +++ b/app/interactions/tag_filters/create.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true +module TagFilters + # Create a TagFilter + class Create < ActiveInteraction::Base + string :filter_type + object :hub + object :hub_feed, default: nil + string :modify_tag_name, default: nil + string :new_tag_name + object :scope, class: TagScopable + integer :tag_id, default: nil + object :user + + validates :filter_type, inclusion: { in: %w(AddTagFilter DeleteTagFilter ModifyTagFilter) } + + # TODO: Refactor this too-large method that was formerly the TagFiltersController#create action + def execute + filter_type_class = filter_type.constantize + + tag = ActsAsTaggableOn::Tag.find(tag_id) if tag_id.present? + + if filter_type_class == ModifyTagFilter + tag ||= find_or_create_tag_by_name(modify_tag_name) + new_tag = find_or_create_tag_by_name(new_tag_name) + else + tag ||= find_or_create_tag_by_name(new_tag_name) + end + + tag_filter = filter_type_class.new + tag_filter.hub = hub + tag_filter.scope = scope + tag_filter.tag = tag + tag_filter.new_tag = new_tag if new_tag.present? + + return tag_filter unless tag_filter.save + + user.has_role!(:owner, tag_filter) + user.has_role!(:creator, tag_filter) + + if hub.notify_taggers && new_tag + hub_feed_to_notify = hub_feed.nil? ? nil : hub_feed.id + + Sidekiq::Client.enqueue( + SendTagChangeNotifications, + tag_filter.id, + tag.id, + new_tag.id, + scope.class.name, + scope.id, + hub.id, + hub_feed_to_notify, + user.id + ) + end + + if hub.allow_taggers_to_sign_up_for_notifications + Sidekiq::Client.enqueue( + SendItemChangeNotifications, + tag_filter.id, + hub.id, + user.id + ) + end + + tag_filter.apply_async + + tag_filter + end + + def find_or_create_tag_by_name(name) + ActsAsTaggableOn::Tag.find_or_create_by_name_normalized(name) + end + end +end diff --git a/config/application.rb b/config/application.rb index 6ddd6bda..2e7a8fd6 100644 --- a/config/application.rb +++ b/config/application.rb @@ -36,6 +36,7 @@ class Application < Rails::Application # Custom directories with classes and modules you want to be autoloadable. config.autoload_paths += %W(#{config.root}/lib) + config.autoload_paths += Dir.glob("#{config.root}/app/interactions/*") # Activate observers that should always be running. config.active_record.observers = :feed_item_observer, :hub_feed_observer, :tag_filter_observer diff --git a/config/locales/en.yml b/config/locales/en.yml index d8af094c..87a7a7c1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -30,6 +30,13 @@ en: attributes: user: login: "Username or email" + tag_filters: + added: + one: "Added a filter for that tag to \"%{scope_title}\"." + other: "Added %{count} filters for those tags to \"%{scope_title}\"." + error_when_adding: + one: "Could not add that tag filter." + other: "Errors occurred when adding some tag filters." will_paginate: page_entries_info: From 6d2ea9ed45386bc6c52acf6db6093193d5722d9b Mon Sep 17 00:00:00 2001 From: Patrick Lewis Date: Wed, 15 Mar 2017 11:50:15 -0400 Subject: [PATCH 17/48] Add shared examples for testing interactions --- spec/interactions/tag_filters/create_spec.rb | 21 ++++++++++++++ spec/support/interactions.rb | 30 ++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 spec/interactions/tag_filters/create_spec.rb create mode 100644 spec/support/interactions.rb diff --git a/spec/interactions/tag_filters/create_spec.rb b/spec/interactions/tag_filters/create_spec.rb new file mode 100644 index 00000000..18ad00fe --- /dev/null +++ b/spec/interactions/tag_filters/create_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true +require 'rails_helper' +require 'support/interactions' + +module TagFilters + RSpec.describe Create do + include_context 'interactions' + it_behaves_like 'an interaction' + + let(:hub) { create(:hub) } + let(:inputs) do + { + filter_type: 'AddTagFilter', + hub: hub, + new_tag_name: 'tag1', + scope: hub, + user: create(:user) + } + end + end +end diff --git a/spec/support/interactions.rb b/spec/support/interactions.rb new file mode 100644 index 00000000..61db7a8e --- /dev/null +++ b/spec/support/interactions.rb @@ -0,0 +1,30 @@ +RSpec.shared_context 'interactions' do + let(:outcome) { described_class.run(inputs) } + let(:outcome!) { described_class.run!(inputs) } + let(:result) { outcome.result } + let(:errors) { outcome.errors } + let(:valid_result) { instance_double(ActiveInteraction::Base, valid?: true) } + let(:invalid_result) do + instance_double( + ActiveInteraction::Base, + valid?: false, + errors: instance_double(ActiveInteraction::Errors, messages: {}) + ) + end +end + +RSpec.shared_examples_for 'an interaction' do + context 'without required inputs' do + let(:inputs) { {} } + + it 'is invalid' do + expect(outcome).to be_invalid + end + end + + context 'with required inputs' do + it 'is valid' do + expect(outcome).to be_valid + end + end +end From 026651009c9fa800564866d396d5e62901400f31 Mon Sep 17 00:00:00 2001 From: jsdiaz Date: Tue, 28 Mar 2017 16:20:59 -0400 Subject: [PATCH 18/48] dropping to ruby 2.3.0 --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 2967b266..40043ea1 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,7 @@ # frozen_string_literal: true source 'http://rubygems.org' -ruby '2.3.3' +ruby '2.3.0' gem 'acl9', git: 'https://github.com/be9/acl9.git', branch: 'finalist-update-rails' gem 'actionpack-action_caching', '~> 1.2' From fed869ba87d2388a010e2671f96d8f55422f3699 Mon Sep 17 00:00:00 2001 From: jsdiaz Date: Tue, 28 Mar 2017 16:22:18 -0400 Subject: [PATCH 19/48] dropped to ruby 2.3.0 --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 265bfd13..84a02bfc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -467,7 +467,7 @@ DEPENDENCIES will_paginate-bootstrap (~> 1.0) RUBY VERSION - ruby 2.3.3p222 + ruby 2.3.0p0 BUNDLED WITH - 1.14.3 + 1.14.5 From e55c5829a3fb9facfde3a027343aeafb1049c07e Mon Sep 17 00:00:00 2001 From: jsdiaz Date: Tue, 28 Mar 2017 17:31:43 -0400 Subject: [PATCH 20/48] bumped version to correct release --- app/views/hubs/meta.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/hubs/meta.html.haml b/app/views/hubs/meta.html.haml index 462dc922..1a87ce9c 100644 --- a/app/views/hubs/meta.html.haml +++ b/app/views/hubs/meta.html.haml @@ -1,7 +1,7 @@ %h1 About TagTeam %dl %dt Version: - %dd 2.1.0.2 + %dd 2.1.0.3 %dt Source: %dd %a{:href => "http://github.com/berkmancenter/tagteam", :target => "_blank"} Github Repository From 037c6690beb548843a40ea729c95421fef2cde9a Mon Sep 17 00:00:00 2001 From: Patrick Lewis Date: Wed, 29 Mar 2017 11:41:56 -0400 Subject: [PATCH 21/48] Fix #14340 Missing Pundit authorization in HubsController#background_activity --- app/controllers/hubs_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/hubs_controller.rb b/app/controllers/hubs_controller.rb index 812c4c0c..da3c37ef 100644 --- a/app/controllers/hubs_controller.rb +++ b/app/controllers/hubs_controller.rb @@ -208,6 +208,7 @@ def retrievals # Looks through the currently running resque jobs and returns a json response talking about what's going on. def background_activity + authorize Hub require 'sidekiq/api' @output = { running: [] } workers = Sidekiq::Workers.new From d26f8f596984e226e28d850db65296599e208b8d Mon Sep 17 00:00:00 2001 From: Peter Hankiewicz Date: Wed, 29 Mar 2017 15:44:03 -0400 Subject: [PATCH 22/48] Fixed the main hub view --- app/views/feed_items/_list_item.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/feed_items/_list_item.html.haml b/app/views/feed_items/_list_item.html.haml index 8c92ef5d..1212d891 100644 --- a/app/views/feed_items/_list_item.html.haml +++ b/app/views/feed_items/_list_item.html.haml @@ -20,5 +20,5 @@ %div - unless feed_item.all_tags_on(hub.tagging_key).empty? = raw feed_item.all_tags_on(hub.tagging_key).collect{|t| tag_display(t, :hub => hub, :hub_feed => hub_feed, :hub_feed_item => feed_item) }.join(' ') - - if current_user.is?([:owner, :hub_feed_item_tag_filterer],@hub) + - if current_user && current_user.is?([:owner, :hub_feed_item_tag_filterer],@hub) = link_to_tag_filter raw(fa_icon('plus', title: 'Add a tag to this item')), :add, { hub: @hub, item: feed_item } From 968737698af8f23162f09f9d2dcf19dd2d882ae9 Mon Sep 17 00:00:00 2001 From: Patrick Lewis Date: Thu, 30 Mar 2017 10:32:37 -0400 Subject: [PATCH 23/48] Fix #13908 Community page text --- app/views/hubs/community/_notifications.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/hubs/community/_notifications.html.haml b/app/views/hubs/community/_notifications.html.haml index 2824fb7a..7c813c16 100644 --- a/app/views/hubs/community/_notifications.html.haml +++ b/app/views/hubs/community/_notifications.html.haml @@ -6,7 +6,7 @@ .checkbox = label_tag 'notify_taggers' do = check_box_tag('notify_taggers', @hub.notify_taggers, @hub.notify_taggers, id: 'notify_taggers') - Notify taggers when their tags modify + Notify taggers when their tags are modified %li .checkbox = label_tag 'allow_taggers_to_sign_up_for_notifications' do From 6fb5871a596f5953eff0ee10ad09e6c40ac92a85 Mon Sep 17 00:00:00 2001 From: Peter Hankiewicz Date: Mon, 3 Apr 2017 13:31:34 -0400 Subject: [PATCH 24/48] Updated the top menu --- app/views/layouts/application.html.haml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index b90096c1..b0a882b0 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -23,11 +23,14 @@ .collapse.navbar-collapse#navbar %ul.nav.navbar-nav %li= link_to('About', meta_hubs_path, class: 'dialog-show', id: 'meta_about') - %li.active= link_to 'Hubs', hubs_path + %li{class: (current_page?(hubs_path) ? 'active' : '')} + = link_to 'Hubs', hubs_path - if user_signed_in? && current_user.is?(:superadmin) - %li= link_to('Users', users_url(protocol: protocol_resolver)) + %li{class: (current_page?(users_url(protocol: protocol_resolver)) ? 'active' : '')} + = link_to('Users', users_url(protocol: protocol_resolver)) - if user_signed_in? - %li= link_to('Export/import', export_import_path) + %li{class: (current_page?(export_import_path) ? 'active' : '')} + = link_to('Export/import', export_import_path) .nav.navbar-nav.navbar-right - if user_signed_in? %p.navbar-text.welcome From d71928dd3b6ee921536267a9e172deff950b0a81 Mon Sep 17 00:00:00 2001 From: Peter Hankiewicz Date: Thu, 6 Apr 2017 17:09:29 -0400 Subject: [PATCH 25/48] Fixed an issue with saving tag filters from the hub view --- app/controllers/tag_filters_controller.rb | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/app/controllers/tag_filters_controller.rb b/app/controllers/tag_filters_controller.rb index 1285b926..9f3d43ff 100644 --- a/app/controllers/tag_filters_controller.rb +++ b/app/controllers/tag_filters_controller.rb @@ -26,13 +26,18 @@ def new end def create - authorize TagFilter + authorize_tag_filter = TagFilter.new + authorize_tag_filter.hub = @hub + authorize authorize_tag_filter # Allow multiple TagFilters to be created from a comma-separated string of tags - tag_filters = - params[:new_tag].split(',').map do |tag| - TagFilters::Create.run(tag_filter_params.merge(new_tag_name: tag)) - end + tag_filters = if params[:new_tag].empty? + [TagFilters::Create.run(tag_filter_params)] + else + params[:new_tag].split(',').map do |tag| + TagFilters::Create.run(tag_filter_params.merge(new_tag_name: tag)) + end + end tag_filters.all?(&:valid?) ? process_successful_create(tag_filters) : process_failed_create(tag_filters) end @@ -92,7 +97,8 @@ def tag_filter_params modify_tag_name: params[:modify_tag], new_tag_name: params[:new_tag], scope: @scope, - user: current_user + user: current_user, + tag_id: params[:tag_id] } end From e12db86612fdf6830422f6d6ad362683ce70a780 Mon Sep 17 00:00:00 2001 From: Peter Hankiewicz Date: Wed, 12 Apr 2017 10:54:28 -0400 Subject: [PATCH 26/48] Updated unit tests --- spec/models/add_tag_filter_spec.rb | 7 ++- spec/models/delete_tag_filter_spec.rb | 5 ++ spec/models/modify_tag_filter_spec.rb | 53 ++++++++++++++++------ spec/policies/tag_filter_policy_spec.rb | 2 +- spec/rails_helper.rb | 4 +- spec/support/factory_girl.rb | 14 +++++- spec/support/shared_tag_filter_examples.rb | 24 ++++++++-- spec/support/tagging_deactivator.rb | 8 +--- 8 files changed, 88 insertions(+), 29 deletions(-) diff --git a/spec/models/add_tag_filter_spec.rb b/spec/models/add_tag_filter_spec.rb index 0f67138e..60072ed3 100644 --- a/spec/models/add_tag_filter_spec.rb +++ b/spec/models/add_tag_filter_spec.rb @@ -1,9 +1,12 @@ # frozen_string_literal: true require 'rails_helper' -require 'support/shared_context' -require 'support/shared_tag_filter_examples' RSpec.describe AddTagFilter, type: :model do + def new_add_filter(tag_name = 'just-a-tag') + new_tag = create(:tag, name: tag_name) + create(:add_tag_filter, tag: new_tag, hub: @hub, scope: @hub) + end + context 'the filter is scoped to a hub with items' do include_context 'user owns a hub with a feed and items' context 'the filter adds tag "a"' do diff --git a/spec/models/delete_tag_filter_spec.rb b/spec/models/delete_tag_filter_spec.rb index 083fb7ba..588af970 100644 --- a/spec/models/delete_tag_filter_spec.rb +++ b/spec/models/delete_tag_filter_spec.rb @@ -2,6 +2,11 @@ require 'rails_helper' RSpec.describe DeleteTagFilter, type: :model do + def new_add_filter(tag_name = 'just-a-tag') + new_tag = create(:tag, name: tag_name) + create(:add_tag_filter, tag: new_tag, hub: @hub, scope: @hub) + end + context 'the filter is scoped to a hub with items all with tag "a"' do include_context 'user owns a hub with a feed and items' before do diff --git a/spec/models/modify_tag_filter_spec.rb b/spec/models/modify_tag_filter_spec.rb index e04606ce..c8af197a 100644 --- a/spec/models/modify_tag_filter_spec.rb +++ b/spec/models/modify_tag_filter_spec.rb @@ -1,9 +1,12 @@ # frozen_string_literal: true require 'rails_helper' -require 'support/shared_context' -require 'support/shared_tag_filter_examples' RSpec.describe ModifyTagFilter, type: :model do + def new_add_filter(tag_name = 'just-a-tag') + new_tag = create(:tag, name: tag_name) + create(:add_tag_filter, tag: new_tag, hub: @hub, scope: @hub) + end + context 'the filter is scoped to a hub with some items with tag "a"' do include_context 'user owns a hub with a feed and items' before do @@ -192,7 +195,11 @@ end context 'the filter is scoped to a hub' do - def add_filter(old_tag = 'praxis', new_tag = 'not-praxis') + def add_filter(old_tag = 'just-a-tag', new_tag = 'not-just-a-tag') + unless ActsAsTaggableOn::Tag.find_by(name: old_tag) + create(:tag, name: old_tag) + end + filter = create(:modify_tag_filter, tag: ActsAsTaggableOn::Tag.find_by(name: old_tag), new_tag: create(:tag, name: new_tag), @@ -208,12 +215,17 @@ def filter_list include_context 'user owns a hub with a feed and items' it 'modifies tags' do - old_tag = 'praxis' - new_tag = 'not-praxis' + old_tag_name = 'just-a-tag' + new_tag_name = 'not-just-a-tag' - filter = add_filter(old_tag, new_tag) + old_tag = create(:tag, name: old_tag_name) + create(:tagging, tag: old_tag, taggable: @feed_items.first, + context: @hub.tagging_key) + + filter = add_filter(old_tag_name, new_tag_name) filter.apply - new_tag_lists = tag_lists_for(@feed_items.reload, @hub.tagging_key, true) + + new_tag_lists = tag_lists_for(@feed_items.reload, @hub.tagging_key) expect(new_tag_lists).to show_effects_of filter end @@ -223,7 +235,11 @@ def filter_list end context 'the filter is scoped to a feed' do - def add_filter(old_tag = 'praxis', new_tag = 'not-praxis') + def add_filter(old_tag = 'just-a-tag', new_tag = 'not-just-a-tag') + unless ActsAsTaggableOn::Tag.find_by(name: old_tag) + create(:tag, name: old_tag) + end + create(:modify_tag_filter, tag: ActsAsTaggableOn::Tag.find_by(name: old_tag), new_tag: create(:tag, name: new_tag), @@ -244,11 +260,15 @@ def setup_other_feeds_tags(filter, hub_feed) include_context 'user owns a hub with a feed and items' it 'modifies tags' do - old_tag = 'praxis' - new_tag = 'not-praxis' + old_tag = 'just-a-tag' + new_tag = 'not-just-a-tag' + + filer_old = new_add_filter(old_tag) + filer_old.apply filter = add_filter(old_tag, new_tag) filter.apply + new_tag_lists = tag_lists_for(@feed_items.reload, @hub.tagging_key, true) expect(new_tag_lists).to show_effects_of filter @@ -259,7 +279,11 @@ def setup_other_feeds_tags(filter, hub_feed) end context 'the filter is scoped to an item' do - def add_filter(old_tag = 'praxis', new_tag = 'not-praxis') + def add_filter(old_tag = 'just-a-tag', new_tag = 'not-just-a-tag') + unless ActsAsTaggableOn::Tag.find_by(name: old_tag) + create(:tag, name: old_tag) + end + create(:modify_tag_filter, tag: ActsAsTaggableOn::Tag.find_by(name: old_tag), new_tag: create(:tag, name: new_tag), @@ -281,8 +305,11 @@ def setup_other_items_tags(filter, item) it 'modifies tags' do @feed_item = @feed_items.order(:id).first - old_tag = 'praxis' - new_tag = 'not-praxis' + old_tag = 'just-a-tag' + new_tag = 'not-just-a-tag' + + filer_old = new_add_filter(old_tag) + filer_old.apply filter = add_filter(old_tag, new_tag) filter.apply diff --git a/spec/policies/tag_filter_policy_spec.rb b/spec/policies/tag_filter_policy_spec.rb index f6b6a28e..0ef42de4 100644 --- a/spec/policies/tag_filter_policy_spec.rb +++ b/spec/policies/tag_filter_policy_spec.rb @@ -22,7 +22,7 @@ before { user.has_role!(:hub_tag_filterer, hub) } it { is_expected.to permit_action(:create) } - it { is_expected.to forbid_action(:destroy) } + it { is_expected.to permit_action(:destroy) } it { is_expected.to permit_action(:new) } end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 9bf507ed..73269747 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -18,6 +18,8 @@ require 'support/sidekiq' require 'support/sunspot' require 'support/vcr' +require 'support/shared_context' +require 'support/shared_tag_filter_examples' require 'webmock/rspec' # Requires supporting ruby files with custom matchers and macros, etc, in @@ -42,7 +44,7 @@ # If you're not using ActiveRecord, or you'd prefer not to run each of your # examples within a transaction, remove the following line or assign false # instead of true. - config.use_transactional_fixtures = true + config.use_transactional_fixtures = false # RSpec Rails can automatically mix in different behaviours to your tests # based on their file location, for example enabling you to call `get` and diff --git a/spec/support/factory_girl.rb b/spec/support/factory_girl.rb index df535ae9..30be9800 100644 --- a/spec/support/factory_girl.rb +++ b/spec/support/factory_girl.rb @@ -2,5 +2,17 @@ RSpec.configure do |config| config.include FactoryGirl::Syntax::Methods - config.before(:suite) { FactoryGirl.lint traits: true } + # @TODO move to a rake task + config.before(:suite) do + if Rails.env.test? + begin + DatabaseCleaner.start + FactoryGirl.lint(traits: true) + ensure + DatabaseCleaner.clean + end + else + system("bundle exec rake factory_girl:lint RAILS_ENV='test'") + end + end end diff --git a/spec/support/shared_tag_filter_examples.rb b/spec/support/shared_tag_filter_examples.rb index 2b5337fc..8db898fa 100644 --- a/spec/support/shared_tag_filter_examples.rb +++ b/spec/support/shared_tag_filter_examples.rb @@ -3,6 +3,11 @@ require 'support/tag_utils' RSpec.shared_examples 'a tag filter' do |filter_type| + def add_filter(tag_name = 'add-test') + new_tag = create(:tag, name: tag_name) + create(filter_type, tag: new_tag, hub: @hub, scope: @hub) + end + it_behaves_like 'a tagging deactivator', filter_type describe '#items_in_scope' do @@ -25,7 +30,7 @@ context 'scoped to item' do it 'returns the item itself' do filter = create(filter_type, scope: @hub.feed_items.first) - expect(filter.items_in_scope).to match_array(@hub.feed_items.limit(1)) + expect(filter.items_in_scope).to match_array([@hub.feed_items.first]) end end end @@ -179,14 +184,14 @@ end it "doesn't affect other hubs" do - filter = add_filter - filter.apply + @filter2 = create(:add_tag_filter, hub: @hub) + @filter2.apply tag_lists = tag_lists_for(@feed_items, @hub.tagging_key) other_tag_lists = tag_lists_for(@feed_items2, @hub2.tagging_key) - expect(tag_lists).to show_effects_of filter - expect(other_tag_lists).to not_show_effects_of filter + expect(tag_lists).to show_effects_of @filter2 + expect(other_tag_lists).to not_show_effects_of @filter2 end end end @@ -202,6 +207,9 @@ end it "doesn't affect the other feed's taggings" do + filer_old = new_add_filter + filer_old.apply + filter = add_filter setup_other_feeds_tags(filter, @hub_feed2) @@ -226,6 +234,9 @@ end it 'cannot be compelled to affect items outside its scope' do + filer_old = new_add_filter + filer_old.apply + filter = add_filter setup_other_items_tags(filter, @feed_item2) @@ -239,6 +250,9 @@ end it "doesn't affect other items" do + filer_old = new_add_filter + filer_old.apply + filter = add_filter setup_other_items_tags(filter, @feed_item2) diff --git a/spec/support/tagging_deactivator.rb b/spec/support/tagging_deactivator.rb index c810596c..4a75a9a4 100644 --- a/spec/support/tagging_deactivator.rb +++ b/spec/support/tagging_deactivator.rb @@ -1,10 +1,5 @@ # frozen_string_literal: true RSpec.shared_examples 'a tagging deactivator' do |filter_type| - def add_filter(tag_name = 'add-test') - new_tag = create(:tag, name: tag_name) - create(:add_tag_filter, tag: new_tag, hub: @hub, scope: @hub) - end - describe '#deactivate_tagging' do it 'copies the tagging into the deactivated_taggings table' do filter = create(filter_type) @@ -18,10 +13,11 @@ def add_filter(tag_name = 'add-test') end it 'removes the tagging from the taggings table' do + count = ActsAsTaggableOn::Tagging.count filter = create(filter_type) tagging = create(:tagging) filter.deactivate_tagging(tagging) - expect(ActsAsTaggableOn::Tagging.count).to eq(0) + expect(ActsAsTaggableOn::Tagging.count).to eq(count) end it 'returns the deactivated copy of the tagging' do From 73fe678486eb792653e4eca185a888f345230bf0 Mon Sep 17 00:00:00 2001 From: Peter Hankiewicz Date: Wed, 12 Apr 2017 11:56:22 -0400 Subject: [PATCH 27/48] Updated the modify_tag_filter spec --- spec/models/modify_tag_filter_spec.rb | 2 +- spec/support/shared_tag_filter_examples.rb | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/spec/models/modify_tag_filter_spec.rb b/spec/models/modify_tag_filter_spec.rb index c8af197a..81603a43 100644 --- a/spec/models/modify_tag_filter_spec.rb +++ b/spec/models/modify_tag_filter_spec.rb @@ -11,7 +11,7 @@ def new_add_filter(tag_name = 'just-a-tag') include_context 'user owns a hub with a feed and items' before do @tag = create(:tag, name: 'a') - @feed_items.limit(4).each do |item| + @feed_items.order(:id).limit(4).each do |item| create(:tagging, tag: @tag, taggable: item, tagger: item.feeds.first) # This doesn't run on its own because items have already been created. item.copy_global_tags_to_hubs diff --git a/spec/support/shared_tag_filter_examples.rb b/spec/support/shared_tag_filter_examples.rb index 8db898fa..5c70ab69 100644 --- a/spec/support/shared_tag_filter_examples.rb +++ b/spec/support/shared_tag_filter_examples.rb @@ -130,15 +130,15 @@ def add_filter(tag_name = 'add-test') RSpec.shared_examples 'an existing tag filter in a populated hub' do describe '#apply' do it 'can be applied to only a few items in its scope' do - random_ids = @feed_items.order('RANDOM()').limit(3).pluck(:id) - random_items = @feed_items.where(id: random_ids) - without_random_items = @feed_items - .where('feed_items.id NOT IN (?)', random_ids) + some_ids = @feed_items.order(:id).limit(3).pluck(:id) + some_items = @feed_items.where(id: some_ids) + without_some_items = @feed_items + .where('feed_items.id NOT IN (?)', some_ids) - @filter.apply(items: random_items) + @filter.apply(items: some_items) - tag_lists = tag_lists_for(random_items, @hub.tagging_key) - without_tag_lists = tag_lists_for(without_random_items, @hub.tagging_key) + tag_lists = tag_lists_for(some_items, @hub.tagging_key) + without_tag_lists = tag_lists_for(without_some_items, @hub.tagging_key) expect(tag_lists).to show_effects_of @filter expect(without_tag_lists).to not_show_effects_of @filter From ced73df98d42e1675ac9363102dcf1d9f0a87c5b Mon Sep 17 00:00:00 2001 From: Patrick Lewis Date: Mon, 17 Apr 2017 10:42:43 -0400 Subject: [PATCH 28/48] Update #14338 Item change email notifications --- app/controllers/bookmarklets_controller.rb | 3 ++- app/interactions/tag_filters/create.rb | 4 +++- app/mailers/notifications.rb | 18 +++++++++++++++++- app/models/add_tag_filter.rb | 4 ++++ app/models/delete_tag_filter.rb | 4 ++++ app/models/feed_item.rb | 5 +++-- app/models/modify_tag_filter.rb | 4 ++++ app/models/tag_filter.rb | 3 ++- .../item_change_notification.text.haml | 14 ++++++++++---- app/workers/send_item_change_notifications.rb | 4 ++-- spec/mailers/previews/notifications_preview.rb | 12 ++++++++++++ 11 files changed, 63 insertions(+), 12 deletions(-) create mode 100644 spec/mailers/previews/notifications_preview.rb diff --git a/app/controllers/bookmarklets_controller.rb b/app/controllers/bookmarklets_controller.rb index ec5f85f7..79f2ae70 100644 --- a/app/controllers/bookmarklets_controller.rb +++ b/app/controllers/bookmarklets_controller.rb @@ -95,7 +95,8 @@ def add_item @feed_item.id, @hub.id, current_user.id, - @feed_item.id + @feed_item.id, + tags_added: new_tags.map(&:name) ) end diff --git a/app/interactions/tag_filters/create.rb b/app/interactions/tag_filters/create.rb index 6bb831c6..fca31331 100644 --- a/app/interactions/tag_filters/create.rb +++ b/app/interactions/tag_filters/create.rb @@ -55,6 +55,7 @@ def execute if hub.allow_taggers_to_sign_up_for_notifications items_to_process = tag_filter.items_to_modify.collect(&:id).join(',') + changes = modify_tag_name.present? ? { tags_modified: [modify_tag_name, new_tag_name] } : {} Sidekiq::Client.enqueue( SendItemChangeNotifications, @@ -62,7 +63,8 @@ def execute tag_filter.id, hub.id, user.id, - items_to_process + items_to_process, + changes ) end diff --git a/app/mailers/notifications.rb b/app/mailers/notifications.rb index 39f9a0d2..8410a31e 100644 --- a/app/mailers/notifications.rb +++ b/app/mailers/notifications.rb @@ -14,13 +14,14 @@ def tag_change_notification(taggers, hub, old_tag, new_tag, updated_by) mail(bcc: taggers.collect(&:email), subject: subject) end - def item_change_notification(hub, modified_item, item_users, current_user) + def item_change_notification(hub, modified_item, item_users, current_user, changes) logger.info('Sending a notification about an items change to ' + item_users.collect(&:email).join(',')) @hub = hub @hub_url = hub_url(@hub) @modified_item = modified_item @updated_by = current_user + @changes = parse_changes(changes) subject = 'Item update in the ' + @hub.title + ' hub' mail(bcc: item_users.collect(&:email), subject: subject) @@ -32,4 +33,19 @@ def user_data_import_completion_notification(email, status) subject = 'Import status' mail(cc: email, subject: subject) end + + private + + def parse_changes(changes) + change_type, tags = changes.first + + case change_type + when :tags_added + "Tags added: #{tags.join(', ')}" + when :tags_modified + "Tags modified: #{tags.first} was changed to #{tags.last}" + when :tags_deleted + "Tags deleted: #{tags.join(', ')}" + end + end end diff --git a/app/models/add_tag_filter.rb b/app/models/add_tag_filter.rb index e889080a..ec0453e9 100644 --- a/app/models/add_tag_filter.rb +++ b/app/models/add_tag_filter.rb @@ -24,4 +24,8 @@ def filters_after tag.id, updated_at).first subsequent ? [subsequent] + subsequent.filters_after : [] end + + def tag_changes + { tags_added: [new_tag] } + end end diff --git a/app/models/delete_tag_filter.rb b/app/models/delete_tag_filter.rb index 70a4ab23..a2312702 100644 --- a/app/models/delete_tag_filter.rb +++ b/app/models/delete_tag_filter.rb @@ -21,4 +21,8 @@ def filters_before def filters_after [] end + + def tag_changes + { tags_deleted: [tag] } + end end diff --git a/app/models/feed_item.rb b/app/models/feed_item.rb index 7913d206..0036596c 100644 --- a/app/models/feed_item.rb +++ b/app/models/feed_item.rb @@ -322,7 +322,7 @@ def self.apply_tag_filters(item_id, hub_ids = []) end # Informing taggers about changes in their items - def notify_about_items_modification(hub, current_user, items_to_process_joined) + def notify_about_items_modification(hub, current_user, items_to_process_joined, changes) # Get configs for notifications hub_user_notifications_setup = HubUserNotification.where(hub_id: hub) @@ -372,7 +372,8 @@ def notify_about_items_modification(hub, current_user, items_to_process_joined) hub, self, users_to_notify_allowed, - current_user + current_user, + changes ).deliver_later end end diff --git a/app/models/modify_tag_filter.rb b/app/models/modify_tag_filter.rb index 4b4639c3..84909310 100644 --- a/app/models/modify_tag_filter.rb +++ b/app/models/modify_tag_filter.rb @@ -88,4 +88,8 @@ def filters_after ).first subsequent ? [subsequent] + subsequent.filters_after : [] end + + def tag_changes + { tags_modified: [tag, new_tag] } + end end diff --git a/app/models/tag_filter.rb b/app/models/tag_filter.rb index 5e811ecc..3ecb0032 100644 --- a/app/models/tag_filter.rb +++ b/app/models/tag_filter.rb @@ -303,7 +303,8 @@ def notify_about_items_modification(hub, current_user, items_to_process_joined) hub, modified_item, users_to_notify_allowed, - current_user + current_user, + tag_changes ).deliver_later end end diff --git a/app/views/notifications/item_change_notification.text.haml b/app/views/notifications/item_change_notification.text.haml index 86ad3d29..925f9b68 100644 --- a/app/views/notifications/item_change_notification.text.haml +++ b/app/views/notifications/item_change_notification.text.haml @@ -1,5 +1,11 @@ -= @updated_by.email -has updated the "#{@modified_item.title}" item in the "#{@hub}" hub at: -\#{@hub_url} +Hello, +\ +#{@updated_by.username} has updated the tag record for an item you've previously tagged. You can find the current version of the record at this URL: +\ += hub_feed_item_url(@hub, @modified_item) -\-- TagTeam +- if @changes.present? + \ + Here are the recent updates to the record: + \ + \ * #{@changes} diff --git a/app/workers/send_item_change_notifications.rb b/app/workers/send_item_change_notifications.rb index f64903d9..3d1347b0 100644 --- a/app/workers/send_item_change_notifications.rb +++ b/app/workers/send_item_change_notifications.rb @@ -6,12 +6,12 @@ def self.display_name 'Sending an email notification of a modified item' end - def perform(scope_class, scope_id, hub_id, current_user_id, items_to_process) + def perform(scope_class, scope_id, hub_id, current_user_id, items_to_process, changes) hub = Hub.find(hub_id) user = User.find(current_user_id) scope_model = scope_class.constantize scope = scope_model.find(scope_id) - scope.notify_about_items_modification(hub, user, items_to_process) + scope.notify_about_items_modification(hub, user, items_to_process, changes) end end diff --git a/spec/mailers/previews/notifications_preview.rb b/spec/mailers/previews/notifications_preview.rb new file mode 100644 index 00000000..6b98992c --- /dev/null +++ b/spec/mailers/previews/notifications_preview.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true +class NotificationsPreview < ActionMailer::Preview + def item_change_notification + hub = Hub.first + modified_item = hub.feed_items.first + item_users = [User.first] + current_user = User.first + changes = { tags_modified: %w(tag1 tag2) } + + Notifications.item_change_notification(hub, modified_item, item_users, current_user, changes) + end +end From 02fb525de2376bec469ff01af6f881d4441e4711 Mon Sep 17 00:00:00 2001 From: Peter Hankiewicz Date: Tue, 18 Apr 2017 17:19:14 -0400 Subject: [PATCH 29/48] Secured the tag_contexts_by_users method to work even if there is no appropriate role --- app/models/feed_item.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/models/feed_item.rb b/app/models/feed_item.rb index 0036596c..9dd3aae4 100644 --- a/app/models/feed_item.rb +++ b/app/models/feed_item.rb @@ -132,11 +132,17 @@ def tag_contexts_by_users taggings.collect do |tg| next if tg.context.eql? 'tags' - auth_user = Role.where( + role = Role.where( authorizable_id: tg.tagger_id, authorizable_type: 'TagFilter', name: 'creator' - ).first.users.first + ).first + + next if role.nil? + + auth_user = role.users.first + + next if auth_user.nil? "#{tg.context}-#{tg.tag.name}-user_#{auth_user.id}" end.compact From acbbadc759d433ee8ef39a2f2a2d4fef69a9c96e Mon Sep 17 00:00:00 2001 From: Peter Hankiewicz Date: Wed, 19 Apr 2017 17:53:40 -0400 Subject: [PATCH 30/48] Fixed an issue with the ExpireFileCache worker --- app/workers/expire_file_cache.rb | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/app/workers/expire_file_cache.rb b/app/workers/expire_file_cache.rb index f7904bcd..b47150a4 100644 --- a/app/workers/expire_file_cache.rb +++ b/app/workers/expire_file_cache.rb @@ -10,16 +10,10 @@ def self.display_name def perform return if other_expirers_running? + return unless Rails.cache.class == ActiveSupport::Cache::FileStore + # A no-op unless we're using a file cache store. - # Also, Marshal is pretty damned fast. - if Rails.cache.class == ActiveSupport::Cache::FileStore - Find.find(Rails.cache.cache_path) do |path| - if FileTest.file?(path) - c = Marshal.load(File.read(path)) - File.unlink(path) if c.expired? - end - end - end + Rails.cache.cleanup end def other_expirers_running? From 5673110cab8cf3a9c83738953f2e5a5f3d2069a5 Mon Sep 17 00:00:00 2001 From: Peter Hankiewicz Date: Tue, 25 Apr 2017 15:51:21 -0400 Subject: [PATCH 31/48] Updated the feed-abstract version --- Gemfile | 2 +- Gemfile.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 40043ea1..1ca9d973 100644 --- a/Gemfile +++ b/Gemfile @@ -12,7 +12,7 @@ gem 'bootstrap-sass', '~> 3.3' gem 'breadcrumbs', '~> 0.1' gem 'devise', '~> 4.2' gem 'exception_notification', '~> 2.6' -gem 'feed-abstract', '0.0.13' +gem 'feed-abstract', '0.0.15' gem 'font-awesome-rails', '~> 4.7' gem 'formtastic', '~> 3.1' gem 'formtastic-bootstrap', git: 'https://github.com/mjbellantoni/formtastic-bootstrap.git' diff --git a/Gemfile.lock b/Gemfile.lock index 84a02bfc..d465ee46 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -142,7 +142,7 @@ GEM factory_girl_rails (4.8.0) factory_girl (~> 4.8.0) railties (>= 3.0.0) - feed-abstract (0.0.13) + feed-abstract (0.0.15) ffi (1.9.17) font-awesome-rails (4.7.0.1) railties (>= 3.2, < 5.1) @@ -416,7 +416,7 @@ DEPENDENCIES drg (~> 1.5) exception_notification (~> 2.6) factory_girl_rails (~> 4.8) - feed-abstract (= 0.0.13) + feed-abstract (= 0.0.15) font-awesome-rails (~> 4.7) formtastic (~> 3.1) formtastic-bootstrap! @@ -470,4 +470,4 @@ RUBY VERSION ruby 2.3.0p0 BUNDLED WITH - 1.14.5 + 1.14.6 From 6d34bee246cdae7889ee3598904202a3dab22ef0 Mon Sep 17 00:00:00 2001 From: Patrick Lewis Date: Sun, 30 Apr 2017 22:05:35 -0400 Subject: [PATCH 32/48] Update #12481 confusing info on individual taggers --- app/controllers/feed_items_controller.rb | 3 +++ app/helpers/hub_feeds_helper.rb | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/controllers/feed_items_controller.rb b/app/controllers/feed_items_controller.rb index d70c6eae..69693cee 100644 --- a/app/controllers/feed_items_controller.rb +++ b/app/controllers/feed_items_controller.rb @@ -4,9 +4,12 @@ class FeedItemsController < ApplicationController Digest::MD5.hexdigest(request.fullpath + '&per_page=' + get_per_page) } + protect_from_forgery except: :index + before_action :set_hub_feed, except: [:tag_list] before_action :set_feed_item, except: [:index] + def controls add_breadcrumbs render layout: !request.xhr? diff --git a/app/helpers/hub_feeds_helper.rb b/app/helpers/hub_feeds_helper.rb index 69e6fdda..8b3c3ff6 100644 --- a/app/helpers/hub_feeds_helper.rb +++ b/app/helpers/hub_feeds_helper.rb @@ -4,7 +4,7 @@ module HubFeedsHelper def hub_feed_updated(hub_feed) updated_at = if hub_feed.latest_feed_items.any? - hub_feed.latest_feed_items.first.updated_at + hub_feed.latest_feed_items.first.date_published else hub_feed.feed.updated_at end From 68c491c058ed65d64492587fa17db9dba4698448 Mon Sep 17 00:00:00 2001 From: Patrick Lewis Date: Sun, 30 Apr 2017 22:20:06 -0400 Subject: [PATCH 33/48] Fix tags_added parameter passed to SendItemChangeNotifications worker --- app/controllers/bookmarklets_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/bookmarklets_controller.rb b/app/controllers/bookmarklets_controller.rb index 79f2ae70..5a1d20bf 100644 --- a/app/controllers/bookmarklets_controller.rb +++ b/app/controllers/bookmarklets_controller.rb @@ -96,7 +96,7 @@ def add_item @hub.id, current_user.id, @feed_item.id, - tags_added: new_tags.map(&:name) + tags_added: new_tags ) end From f776b41bb4f82a1d5b99301de71dd157d3b1ff13 Mon Sep 17 00:00:00 2001 From: Patrick Lewis Date: Tue, 9 May 2017 16:14:37 -0400 Subject: [PATCH 34/48] Update #12481 Labeling on bookmark collections --- app/helpers/hub_feeds_helper.rb | 2 +- app/views/hub_feeds/more_details.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/helpers/hub_feeds_helper.rb b/app/helpers/hub_feeds_helper.rb index 8b3c3ff6..6a03a313 100644 --- a/app/helpers/hub_feeds_helper.rb +++ b/app/helpers/hub_feeds_helper.rb @@ -4,7 +4,7 @@ module HubFeedsHelper def hub_feed_updated(hub_feed) updated_at = if hub_feed.latest_feed_items.any? - hub_feed.latest_feed_items.first.date_published + hub_feed.latest_feed_items.first.created_at else hub_feed.feed.updated_at end diff --git a/app/views/hub_feeds/more_details.html.haml b/app/views/hub_feeds/more_details.html.haml index d9a9e871..912dd2c8 100644 --- a/app/views/hub_feeds/more_details.html.haml +++ b/app/views/hub_feeds/more_details.html.haml @@ -1,5 +1,5 @@ .metadata{ id: "hub_feed_metadata_#{@hub_feed.id}" } - %span.updated{ title: 'Last updated' } Updated #{hub_feed_updated(@hub_feed)} + %span.updated{ title: 'Last updated' } Last tagged #{hub_feed_updated(@hub_feed)} |  From 8d1aba9a6955126fdd1571b65176cba00c318217 Mon Sep 17 00:00:00 2001 From: Patrick Lewis Date: Fri, 19 May 2017 15:44:39 -0400 Subject: [PATCH 35/48] Upgrade nokogiri to 1.7.2 because of upstream USN-3271-1 vulnerabilities --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index d465ee46..2bb01b42 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -199,7 +199,7 @@ GEM minitest (5.10.1) mustermann (1.0.0.beta2) nio4r (1.2.1) - nokogiri (1.7.0.1) + nokogiri (1.7.2) mini_portile2 (~> 2.1.0) nokogumbo (1.4.10) nokogiri From 5d0b9e7128bca3a8ae366f2949618e60e6bccc48 Mon Sep 17 00:00:00 2001 From: Peter Hankiewicz Date: Mon, 22 May 2017 13:55:58 -0400 Subject: [PATCH 36/48] Updated a version of the nokogumbo gem --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 2bb01b42..a514a9c5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -201,7 +201,7 @@ GEM nio4r (1.2.1) nokogiri (1.7.2) mini_portile2 (~> 2.1.0) - nokogumbo (1.4.10) + nokogumbo (1.4.11) nokogiri options (2.3.2) orm_adapter (0.5.0) From 4507ed88dcf5c7fcb75023ff90dfab1481bde3d9 Mon Sep 17 00:00:00 2001 From: Peter Hankiewicz Date: Tue, 23 May 2017 15:02:56 -0400 Subject: [PATCH 37/48] Updated the fix_tag_names_with_commas rake task --- lib/tasks/tagteam.rake | 104 +++++++++++++++++------------------------ 1 file changed, 44 insertions(+), 60 deletions(-) diff --git a/lib/tasks/tagteam.rake b/lib/tasks/tagteam.rake index a6e7fc50..937b49ba 100644 --- a/lib/tasks/tagteam.rake +++ b/lib/tasks/tagteam.rake @@ -109,83 +109,67 @@ namespace :tagteam do desc 'Fix tag names with commas in them' task fix_tag_names_with_commas: :environment do - affected_tags = ActsAsTaggableOn::Tag.where('name LIKE \'%,%\'') + split_patterns = [','] - affected_tags.each do |tag| - tag_name = tag.name + split_patterns.each do |split_pattern| + affected_tags = ActsAsTaggableOn::Tag.where('name LIKE \'%' + split_pattern + '%\'') - puts "Starting a fix of the '#{tag_name}' tag" - - name_tags = tag_name.split(',') - ['', nil] - is_multiple = name_tags.length > 1 - filters_with_tag = TagFilter.where(tag: tag) - filter_with_new_as_tag = TagFilter.where(new_tag: tag) + affected_tags.each do |tag| + tag_name = tag.name + taggings = tag.taggings - if is_multiple - puts 'It\'s a complex tag, splitting and fixing' + puts "Starting a fix of the '#{tag_name}' tag" - taggings = tag.taggings + name_tags = tag_name.split(split_pattern) + filters_with_tag = TagFilter.where(tag: tag) + filter_with_new_as_tag = TagFilter.where(new_tag: tag) name_tags.each do |name_tag| name_tag = name_tag.strip - existing_tag = ActsAsTaggableOn::Tag - .where('name=?', name_tag).first - - tag_to_tag = if existing_tag.nil? - ActsAsTaggableOn::Tag.create( - name: name_tag - ) - else - existing_tag - end - - taggings.each do |tagging| - new_tagging = tagging.dup - - new_tagging.tag_id = tag_to_tag.id - - new_tagging.save! - - tagging.destroy - end - - unless filters_with_tag.empty? - puts 'Recreating tag filters' - - filters_with_tag.each do |filter_with_tag| - new_filter = filter_with_tag.dup - - new_filter.tag = tag_to_tag - - new_filter.save(validate: false) - end - end - - unless filter_with_new_as_tag.empty? - puts 'Recreating tag filters (modified)' - - filter_with_new_as_tag.each do |filter_with_tag| - new_filter = filter_with_tag.dup - - new_filter.new_tag = tag_to_tag - - new_filter.save(validate: false) - end + unless name_tag.empty? + existing_tag = ActsAsTaggableOn::Tag + .where('name=?', name_tag).first + + tag_to_tag = if existing_tag.nil? + ActsAsTaggableOn::Tag.create( + name: name_tag + ) + else + existing_tag + end + + taggings.each do |tagging| + new_tagging = tagging.dup + + new_tagging.tag_id = tag_to_tag.id + + begin + new_tagging.save + rescue + puts 'Duplicated tagging - continue...' + end + end end end tag.destroy filters_with_tag.destroy_all filter_with_new_as_tag.destroy_all - else - puts 'It\' just a typo, renaming' + taggings.destroy_all - tag.name = tag.name.gsub(',', '').strip - tag.save! + puts "Fixed the '#{tag_name}' tag" end + end + + puts "Recalculating taggings" - puts "Fixed the #{tag_name} tag" + Hub.order(:id).each do |hub| + RecalcAllItems.new.perform(hub.id) end + + puts "Regenerating the search index" + + Rake::Task['sunspot:solr:reindex'].invoke end end From c23320e722301c6e379c1b617452930945209754 Mon Sep 17 00:00:00 2001 From: Patrick Lewis Date: Tue, 23 May 2017 16:02:15 -0400 Subject: [PATCH 38/48] Fix #14391 Granting User Roles --- app/assets/javascripts/autocomplete_user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/autocomplete_user.js b/app/assets/javascripts/autocomplete_user.js index 096bcfd8..1baa7649 100644 --- a/app/assets/javascripts/autocomplete_user.js +++ b/app/assets/javascripts/autocomplete_user.js @@ -54,7 +54,7 @@ $.extend({ } const node = $('
  • ').attr('class', elementClass); - $(node).html($(``).val(ui.item.value)); + $(node).html($('').val(ui.item.value)) $(node).append(ui.item.label); $(node).append(' X '); $(containerId).show().append(node); From 8a9a848e36d38ade46ffcdd5c1672087e8236c48 Mon Sep 17 00:00:00 2001 From: Peter Hankiewicz Date: Wed, 24 May 2017 15:25:29 -0400 Subject: [PATCH 39/48] Fixed the hub policy Updated the feed item model --- app/models/feed_item.rb | 22 +++++++++++++--------- app/policies/hub_policy.rb | 7 +++++++ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/app/models/feed_item.rb b/app/models/feed_item.rb index 9dd3aae4..d50054a1 100644 --- a/app/models/feed_item.rb +++ b/app/models/feed_item.rb @@ -132,15 +132,19 @@ def tag_contexts_by_users taggings.collect do |tg| next if tg.context.eql? 'tags' - role = Role.where( - authorizable_id: tg.tagger_id, - authorizable_type: 'TagFilter', - name: 'creator' - ).first - - next if role.nil? - - auth_user = role.users.first + if tg.tagger_type.eql? 'User' + auth_user = User.where(id: tg.tagger_id).first + else + role = Role.where( + authorizable_id: tg.tagger_id, + authorizable_type: 'TagFilter', + name: 'creator' + ).first + + next if role.nil? + + auth_user = role.users.first + end next if auth_user.nil? diff --git a/app/policies/hub_policy.rb b/app/policies/hub_policy.rb index 50f93658..536ba285 100644 --- a/app/policies/hub_policy.rb +++ b/app/policies/hub_policy.rb @@ -138,4 +138,11 @@ def update? user.has_role?(:owner, record) end + + def recalc_all_tags? + return false unless user.present? + return true if user.has_role?(:superadmin) + + false + end end From 0ac93ad11db3484a943fbb09fcfb575d8dc6ed16 Mon Sep 17 00:00:00 2001 From: Patrick Lewis Date: Wed, 31 May 2017 15:06:57 -0400 Subject: [PATCH 40/48] Update #12481 Use date of last tagging as 'updated on' date for hub feeds --- app/helpers/hub_feeds_helper.rb | 4 ++-- app/models/feed.rb | 4 ++++ app/models/hub_feed.rb | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/helpers/hub_feeds_helper.rb b/app/helpers/hub_feeds_helper.rb index 6a03a313..abe46783 100644 --- a/app/helpers/hub_feeds_helper.rb +++ b/app/helpers/hub_feeds_helper.rb @@ -3,8 +3,8 @@ module HubFeedsHelper def hub_feed_updated(hub_feed) updated_at = - if hub_feed.latest_feed_items.any? - hub_feed.latest_feed_items.first.created_at + if hub_feed.most_recent_tagging.present? + hub_feed.most_recent_tagging.created_at else hub_feed.feed.updated_at end diff --git a/app/models/feed.rb b/app/models/feed.rb index 5ddabc9b..3cc5e1f5 100644 --- a/app/models/feed.rb +++ b/app/models/feed.rb @@ -209,6 +209,10 @@ def self.title 'Feed' end + def most_recent_tagging + owned_taggings.last + end + private def remove_feed_items_feeds diff --git a/app/models/hub_feed.rb b/app/models/hub_feed.rb index 365fc444..7fd6c45e 100644 --- a/app/models/hub_feed.rb +++ b/app/models/hub_feed.rb @@ -32,6 +32,8 @@ class HubFeed < ApplicationRecord attr_accessible :title, :description + delegate :most_recent_tagging, to: :feed + api_accessible :default do |t| t.add :id t.add :display_title, as: :title From 584a7158e237a84f256dcf136ded36fca515d562 Mon Sep 17 00:00:00 2001 From: Patrick Lewis Date: Thu, 1 Jun 2017 12:33:33 -0400 Subject: [PATCH 41/48] Update #14338 Make tag change notification emails more detailed --- app/mailers/notifications.rb | 15 ++++++++++++++- app/models/tag_filter.rb | 3 ++- .../tag_change_notification.text.haml | 14 +++++++++----- .../mailers/previews/notifications_preview.rb | 19 +++++++++++++++++++ 4 files changed, 44 insertions(+), 7 deletions(-) diff --git a/app/mailers/notifications.rb b/app/mailers/notifications.rb index 8410a31e..82f6ca72 100644 --- a/app/mailers/notifications.rb +++ b/app/mailers/notifications.rb @@ -1,7 +1,7 @@ class Notifications < ActionMailer::Base default from: Tagteam::Application.config.default_sender - def tag_change_notification(taggers, hub, old_tag, new_tag, updated_by) + def tag_change_notification(taggers, hub, old_tag, new_tag, updated_by, scope) logger.info('Sending a notification about a tags change to ' + taggers.collect(&:email).join(',')) @hub = hub @@ -9,6 +9,8 @@ def tag_change_notification(taggers, hub, old_tag, new_tag, updated_by) @old_tag = old_tag @new_tag = new_tag @updated_by = updated_by + @scope = scope + @scope_url = determine_url(@hub, @scope) subject = 'Tag update in the ' + @hub.title + ' hub' mail(bcc: taggers.collect(&:email), subject: subject) @@ -48,4 +50,15 @@ def parse_changes(changes) "Tags deleted: #{tags.join(', ')}" end end + + def determine_url(hub, scope) + case scope + when Hub + hub_url(scope) + when HubFeed + hub_feed_feed_items_url(scope) + when FeedItem + hub_feed_item_url(hub, scope) + end + end end diff --git a/app/models/tag_filter.rb b/app/models/tag_filter.rb index 3ecb0032..d846f214 100644 --- a/app/models/tag_filter.rb +++ b/app/models/tag_filter.rb @@ -239,7 +239,8 @@ def notify_taggers(old_tag, new_tag, scope, hub, hub_feed, current_user) hub, old_tag, new_tag, - current_user + current_user, + scope ).deliver_later end end diff --git a/app/views/notifications/tag_change_notification.text.haml b/app/views/notifications/tag_change_notification.text.haml index 24d7918e..7835119e 100644 --- a/app/views/notifications/tag_change_notification.text.haml +++ b/app/views/notifications/tag_change_notification.text.haml @@ -1,5 +1,9 @@ -= @updated_by.email -has updated the "#{@old_tag.name}" tag in the "#{@hub}" hub at: -\#{@hub_url} - -\-- TagTeam +Hello, +\ +#{@updated_by.username} has updated the tag record for an item you've previously tagged. You can find the current version of the record at this URL: +\ += @scope_url +\ +Here are the recent updates to the record: +\ +\ * Tags modified: #{@old_tag.name} was changed to #{@new_tag.name} diff --git a/spec/mailers/previews/notifications_preview.rb b/spec/mailers/previews/notifications_preview.rb index 6b98992c..e99a15ef 100644 --- a/spec/mailers/previews/notifications_preview.rb +++ b/spec/mailers/previews/notifications_preview.rb @@ -9,4 +9,23 @@ def item_change_notification Notifications.item_change_notification(hub, modified_item, item_users, current_user, changes) end + + def tag_change_notification + modify_tag_filter = ModifyTagFilter.last + taggers_to_notify = [User.first] + hub = Hub.first + old_tag = modify_tag_filter.tag + new_tag = modify_tag_filter.new_tag + current_user = User.first + scope = hub.hub_feeds.last + + Notifications.tag_change_notification( + taggers_to_notify, + hub, + old_tag, + new_tag, + current_user, + scope + ) + end end From 5eff6b938d1d9b1c6fbf601ca4a649f87dd0a0e7 Mon Sep 17 00:00:00 2001 From: Peter Hankiewicz Date: Fri, 2 Jun 2017 15:00:05 -0400 Subject: [PATCH 42/48] Fixed an issue with the tags controller, handling of missing hub tags --- app/controllers/tags_controller.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index 535dcfc3..b91d58ba 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -111,9 +111,8 @@ def load_tag_from_name end unless @tag flash.now[:error] = "We're sorry, but '#{params[:name]}' is not a tag for '#{@hub.title}'" - @my_hubs = current_user.my(Hub) unless current_user.blank? - @hubs = Hub.paginate(page: params[:page], per_page: get_per_page) - render 'hubs/index', layout: !request.xhr?, status: 404 + + redirect_to hub_path(@hub) end end From 8df4be9dc26cfcd078f541abdc2f339b2a6e9d98 Mon Sep 17 00:00:00 2001 From: Patrick Lewis Date: Mon, 12 Jun 2017 11:52:10 -0400 Subject: [PATCH 43/48] Update #14338 Fix parameter mismatch errors in item notifications --- app/models/tag_filter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/tag_filter.rb b/app/models/tag_filter.rb index d846f214..e06486c8 100644 --- a/app/models/tag_filter.rb +++ b/app/models/tag_filter.rb @@ -246,7 +246,7 @@ def notify_taggers(old_tag, new_tag, scope, hub, hub_feed, current_user) end # Informing taggers about changes in their items - def notify_about_items_modification(hub, current_user, items_to_process_joined) + def notify_about_items_modification(hub, current_user, items_to_process_joined, changes = {}) # Get configs for notifications hub_user_notifications_setup = HubUserNotification.where(hub_id: hub) From 035947dbef5d6dd9c2387614e2c866c9a6a33325 Mon Sep 17 00:00:00 2001 From: Patrick Lewis Date: Tue, 13 Jun 2017 14:46:48 -0400 Subject: [PATCH 44/48] Configure development camps to use a local SMTP server --- config/environments/development.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/config/environments/development.rb b/config/environments/development.rb index 50bbe667..01347a28 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -51,7 +51,13 @@ # routes, locales, etc. This feature depends on the listen gem. # config.file_watcher = ActiveSupport::EventedFileUpdateChecker - config.action_mailer.delivery_method = :sendmail + if config.hostname.include?('tagteam.berkman.temphost') + config.action_mailer.delivery_method = :smtp + config.action_mailer.smtp_settings = { address: 'localhost', port: 1025 } + else + config.action_mailer.delivery_method = :sendmail + end + config.action_mailer.default_url_options = { host: config.hostname, port: config.hostport From ce73b3f850dabbcf8c28e1649012a3316217d8e9 Mon Sep 17 00:00:00 2001 From: Patrick Lewis Date: Thu, 15 Jun 2017 11:54:41 -0400 Subject: [PATCH 45/48] Update #12481 Feed#most_recent_tagging returns most recent tagging on any feed item --- app/models/feed.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/models/feed.rb b/app/models/feed.rb index 3cc5e1f5..2b893c3a 100644 --- a/app/models/feed.rb +++ b/app/models/feed.rb @@ -209,8 +209,10 @@ def self.title 'Feed' end + # Return the most recent tagging on any of the items in this feed def most_recent_tagging - owned_taggings.last + feed_item_ids = feed_items.pluck(:id) + ActsAsTaggableOn::Tagging.where(taggable_type: 'FeedItem', taggable_id: feed_item_ids).last end private From 589fa6069ac30390ee6778d7b615b76cd12555fe Mon Sep 17 00:00:00 2001 From: Patrick Lewis Date: Thu, 15 Jun 2017 15:59:08 -0400 Subject: [PATCH 46/48] Update #14338 Fix contents of item change notification emails --- app/mailers/notifications.rb | 1 + app/models/add_tag_filter.rb | 2 +- app/models/hub.rb | 4 ++++ app/views/notifications/item_change_notification.text.haml | 2 +- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/mailers/notifications.rb b/app/mailers/notifications.rb index 82f6ca72..a3e2cfee 100644 --- a/app/mailers/notifications.rb +++ b/app/mailers/notifications.rb @@ -22,6 +22,7 @@ def item_change_notification(hub, modified_item, item_users, current_user, chang @hub = hub @hub_url = hub_url(@hub) @modified_item = modified_item + @hub_feed = @hub.hub_feed_for_feed_item(@modified_item) @updated_by = current_user @changes = parse_changes(changes) diff --git a/app/models/add_tag_filter.rb b/app/models/add_tag_filter.rb index ec0453e9..0211d72f 100644 --- a/app/models/add_tag_filter.rb +++ b/app/models/add_tag_filter.rb @@ -26,6 +26,6 @@ def filters_after end def tag_changes - { tags_added: [new_tag] } + { tags_added: [tag] } end end diff --git a/app/models/hub.rb b/app/models/hub.rb index 74d977e8..5b5bc44f 100644 --- a/app/models/hub.rb +++ b/app/models/hub.rb @@ -211,4 +211,8 @@ def taggings ] ) end + + def hub_feed_for_feed_item(feed_item) + hub_feeds.find_by(feed: feed_item.feeds) + end end diff --git a/app/views/notifications/item_change_notification.text.haml b/app/views/notifications/item_change_notification.text.haml index 925f9b68..4c916659 100644 --- a/app/views/notifications/item_change_notification.text.haml +++ b/app/views/notifications/item_change_notification.text.haml @@ -2,7 +2,7 @@ Hello, \ #{@updated_by.username} has updated the tag record for an item you've previously tagged. You can find the current version of the record at this URL: \ -= hub_feed_item_url(@hub, @modified_item) += hub_feed_feed_item_url(@hub_feed, @modified_item) - if @changes.present? \ From 5487f394685c0e8de8eb40c726190a57a6b1369f Mon Sep 17 00:00:00 2001 From: Patrick Lewis Date: Fri, 16 Jun 2017 10:00:04 -0400 Subject: [PATCH 47/48] Update #12481 Fix date displayed for bookmark collections to match published date of most recent feed item --- app/helpers/hub_feeds_helper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/helpers/hub_feeds_helper.rb b/app/helpers/hub_feeds_helper.rb index abe46783..a278ed50 100644 --- a/app/helpers/hub_feeds_helper.rb +++ b/app/helpers/hub_feeds_helper.rb @@ -3,8 +3,8 @@ module HubFeedsHelper def hub_feed_updated(hub_feed) updated_at = - if hub_feed.most_recent_tagging.present? - hub_feed.most_recent_tagging.created_at + if hub_feed.feed_items.any? + hub_feed.feed_items.first.date_published else hub_feed.feed.updated_at end From 05cd4697bff8ab4e6bf4b377aca77bbe72b292b9 Mon Sep 17 00:00:00 2001 From: Patrick Lewis Date: Tue, 20 Jun 2017 22:51:59 -0400 Subject: [PATCH 48/48] Update #12481 Fix list item metadata duplication --- app/assets/javascripts/application.js | 23 ------------- .../javascripts/hubs/bookmark_collections.js | 32 +++++++++++++++++++ app/views/hub_feeds/_list_item.html.haml | 1 + .../republished_feeds/_list_item.html.haml | 1 + 4 files changed, 34 insertions(+), 23 deletions(-) create mode 100644 app/assets/javascripts/hubs/bookmark_collections.js diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index de698e0e..c7b8a360 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -815,29 +815,6 @@ $(document).ready(function(){ } }); - $('.hub_feed_more_control,.republished_feed_more_control').live({ - click: function(e){ - e.preventDefault(); - if($(this).hasClass('more_details_included')){ - $(this).closest('li').find('.metadata').remove(); - $(this).removeClass('more_details_included'); - $(this).find('.fa').removeClass('fa-caret-down'); - $(this).find('.fa').addClass('fa-caret-right'); - return; - } - var elem = this; - $.ajax({ - url: $(this).attr('href'), - success: function(html){ - $(elem).addClass('more_details_included'); - $(elem).closest('li').find('.media-body').append(html); - $(elem).find('.fa').removeClass('fa-caret-right'); - $(elem).find('.fa').addClass('fa-caret-down'); - } - }); - } - }); - if($('.ui-widget-content').length > 0){ $('#hub_search_form,#hub_tag_search_form').live({ submit: function(e){ diff --git a/app/assets/javascripts/hubs/bookmark_collections.js b/app/assets/javascripts/hubs/bookmark_collections.js new file mode 100644 index 00000000..d6f178ed --- /dev/null +++ b/app/assets/javascripts/hubs/bookmark_collections.js @@ -0,0 +1,32 @@ +/* globals $ */ +$('.hub_feed_more_control, .republished_feed_more_control') + .live({ click: toggleListItemMetadata }) + +function toggleListItemMetadata (e) { + e.preventDefault() + + var element = $(this) + + if (element.hasClass('more_details_included')) { + hideListItemMetadata(element) + } else { + showListItemMetadata(element) + } +} + +function hideListItemMetadata (element) { + element.removeClass('more_details_included') + element.closest('li').find('.metadata').empty() + element.find('.fa').removeClass('fa-caret-down').addClass('fa-caret-right') +} + +function showListItemMetadata (element) { + $.ajax({ + url: element.attr('href'), + success: function (html) { + element.addClass('more_details_included') + element.closest('li').find('.metadata').replaceWith(html) + element.find('.fa').removeClass('fa-caret-right').addClass('fa-caret-down') + } + }) +} diff --git a/app/views/hub_feeds/_list_item.html.haml b/app/views/hub_feeds/_list_item.html.haml index 6a4d33bb..0929a727 100644 --- a/app/views/hub_feeds/_list_item.html.haml +++ b/app/views/hub_feeds/_list_item.html.haml @@ -14,6 +14,7 @@ - else = link_to hub_hub_feed_path(@hub, hub_feed) do %h2= raw(strip_tags(hub_feed.to_s)) + .metadata - if defined?(show_hubs) && show_hubs == true %span.smaller in #{link_to(hub_feed.hub, hub_path(hub_feed.hub))} diff --git a/app/views/republished_feeds/_list_item.html.haml b/app/views/republished_feeds/_list_item.html.haml index d95105d8..ea5e49e0 100644 --- a/app/views/republished_feeds/_list_item.html.haml +++ b/app/views/republished_feeds/_list_item.html.haml @@ -20,3 +20,4 @@ \#{link_to('RSS', remix_items_path(republished_feed.url_key, format: :rss))}  \#{link_to('ATOM', remix_items_path(republished_feed.url_key, format: :atom))}  \#{link_to('JSON', remix_items_path(republished_feed.url_key, format: :json, callback: 'callback'))} + .metadata