diff --git a/app/controllers/my_profile/follow_categories_controller.rb b/app/controllers/my_profile/follow_categories_controller.rb new file mode 100644 index 0000000..a27eb5c --- /dev/null +++ b/app/controllers/my_profile/follow_categories_controller.rb @@ -0,0 +1,46 @@ +class FollowCategoriesController < MyProfileController + + def index + @categories = current_person.follow_categories + end + + def new + @follow_category = FollowCategory.new + end + + def create + @follow_category = FollowCategory.new(:name => params[:follow_category][:name], + :person => current_person) + if @follow_category.save + redirect_to :action => 'index' + else + render :action => 'new' + end + end + + def edit + @follow_category = FollowCategory.find_by_id(params[:id]) + render_not_found if @follow_category.nil? + end + + def update + @follow_category = FollowCategory.find_by_id(params[:id]) + return render_not_found if @follow_category.nil? + + if @follow_category.update(params[:follow_category]) + redirect_to :action => 'index' + else + render :action => 'edit' + end + end + + def destroy + @follow_category = FollowCategory.find_by_id(params[:id]) + return render_not_found if @follow_category.nil? + + if !@follow_category.destroy + session[:notice] = _('Failed to remove category') + end + redirect_to :action => 'index' + end +end diff --git a/app/controllers/my_profile/followers_controller.rb b/app/controllers/my_profile/followers_controller.rb new file mode 100644 index 0000000..75d13fa --- /dev/null +++ b/app/controllers/my_profile/followers_controller.rb @@ -0,0 +1,39 @@ +class FollowersController < MyProfileController + + before_filter :only_for_person, :only => :index + + def index + @followed_people = current_person.following_profiles.order(:type).paginate(:per_page => 15, :page => params[:npage]) + end + + def set_category_modal + categories = FollowCategory.where(:person => current_person).map(&:name) + profile = Profile.find(params[:followed_profile_id]) + render :partial => 'blocks/profile_info_actions/follow_categories', :locals => { :categories => categories, :profile => profile } + end + + def update_category + params["followed_profile_id"] ||= profile.id + follower = ProfileFollower.find_by(follower_id: current_person.id, profile_id: params[:followed_profile_id]) + if params[:category_name] + category = FollowCategory.find_or_create_by(:name => params[:category_name], :person => current_person) + else + category = nil + end + + if follower + follower.follow_category = category + follower.save + end + + redirect_url = params["redirect_to"] ? params["redirect_to"] : url_for(:controller => "followers", :action => "index", :profile => current_person.identifier) + redirect_to redirect_url + end + + protected + + def only_for_person + render_not_found unless profile.person? + end + +end diff --git a/app/controllers/public/content_viewer_controller.rb b/app/controllers/public/content_viewer_controller.rb index 24b633f..0a4ccfe 100644 --- a/app/controllers/public/content_viewer_controller.rb +++ b/app/controllers/public/content_viewer_controller.rb @@ -128,9 +128,9 @@ class ContentViewerController < ApplicationController end unless @page.display_to?(user) - if !profile.visible? || profile.secret? || (user && user.follows?(profile)) || user.blank? + if !profile.visible? || profile.secret? || (user && profile.in_social_circle?(user)) || user.blank? render_access_denied - else #!profile.public? + else private_profile_partial_parameters render :template => 'profile/_private_profile', :status => 403, :formats => [:html] end diff --git a/app/controllers/public/profile_controller.rb b/app/controllers/public/profile_controller.rb index cccc352..92291c8 100644 --- a/app/controllers/public/profile_controller.rb +++ b/app/controllers/public/profile_controller.rb @@ -3,7 +3,8 @@ class ProfileController < PublicController needs_profile before_filter :check_access_to_profile, :except => [:join, :join_not_logged, :index, :add] before_filter :store_location, :only => [:join, :join_not_logged, :report_abuse, :send_mail] - before_filter :login_required, :only => [:add, :join, :leave, :unblock, :leave_scrap, :remove_scrap, :remove_activity, :view_more_activities, :view_more_network_activities, :report_abuse, :register_report, :leave_comment_on_activity, :send_mail] + before_filter :login_required, :only => [:add, :join, :leave, :unblock, :leave_scrap, :remove_scrap, :remove_activity, :view_more_activities, :view_more_network_activities, :report_abuse, :register_report, :leave_comment_on_activity, :send_mail, :follow, :unfollow] + before_filter :allow_following?, :only => [:follow, :unfollow] helper TagsHelper helper ActionTrackerHelper @@ -65,6 +66,10 @@ class ProfileController < PublicController end end + def following + @followed_people = [].paginate(:per_page => per_page, :page => params[:npage], :total_entries => profile.friends.count) + end + def members if is_cache_expired?(profile.members_cache_key(params)) sort = (params[:sort] == 'desc') ? params[:sort] : 'asc' @@ -151,6 +156,26 @@ class ProfileController < PublicController end end + def follow + if !current_person.follows?(profile) + group = params['follow'] ? params['follow']['category'] : '' + current_person.follow(profile, group) + + categories = FollowCategory.where(:person => current_person).map(&:name) + render :partial => 'blocks/profile_info_actions/follow_categories', :locals => { :categories => categories } + else + render :text => _("It was not possible to follow %s") % profile.name, :status => 400 + end + end + + def unfollow + if current_person.follows?(profile) + current_person.unfollow(profile) + end + redirect_url = params["redirect_to"] ? params["redirect_to"] : profile.url + redirect_to redirect_url + end + def check_friendship unless logged_in? render :text => '' @@ -437,4 +462,8 @@ class ProfileController < PublicController [:image, :domains, :preferred_domain, :environment] end + def allow_following? + render_not_found unless profile.allow_following? + end + end diff --git a/app/helpers/action_tracker_helper.rb b/app/helpers/action_tracker_helper.rb index ef21767..8e66349 100644 --- a/app/helpers/action_tracker_helper.rb +++ b/app/helpers/action_tracker_helper.rb @@ -14,13 +14,23 @@ module ActionTrackerHelper } end + def new_follower_description ta + n_('has 1 new follower:
%{name}', 'has %{num} new followers:
%{name}', ta.get_follower_name.size).html_safe % { + num: ta.get_follower_name.size, + name: safe_join(ta.collect_group_with_index(:follower_name) do |n,i| + link_to image_tag(ta.get_follower_profile_custom_icon[i] || default_or_themed_icon("/images/icons-app/person-icon.png")), + ta.get_follower_url[i], title: n + end) + } + end + def join_community_description ta - n_('has joined 1 community:
%{name}'.html_safe, 'has joined %{num} communities:
%{name}'.html_safe, ta.get_resource_name.size) % { + n_('has joined 1 community:
%{name}', 'has joined %{num} communities:
%{name}', ta.get_resource_name.size).html_safe % { num: ta.get_resource_name.size, - name: ta.collect_group_with_index(:resource_name) do |n,i| + name: safe_join(ta.collect_group_with_index(:resource_name) do |n,i| link = link_to image_tag(ta.get_resource_profile_custom_icon[i] || default_or_themed_icon("/images/icons-app/community-icon.png")), ta.get_resource_url[i], title: n - end.join.html_safe + end) } end @@ -68,9 +78,9 @@ module ActionTrackerHelper end def favorite_enterprise_description ta - _('favorited enterprise %{title}') % { + (_('favorited enterprise %{title}') % { title: link_to(truncate(ta.get_enterprise_name), ta.get_enterprise_url), - } + }).html_safe end end diff --git a/app/jobs/notify_activity_to_profiles_job.rb b/app/jobs/notify_activity_to_profiles_job.rb index b9a5a80..4510315 100644 --- a/app/jobs/notify_activity_to_profiles_job.rb +++ b/app/jobs/notify_activity_to_profiles_job.rb @@ -19,8 +19,9 @@ class NotifyActivityToProfilesJob < Struct.new(:tracked_action_id) # Notify the user ActionTrackerNotification.create(:profile_id => tracked_action.user.id, :action_tracker_id => tracked_action.id) - # Notify all friends - ActionTrackerNotification.connection.execute("insert into action_tracker_notifications(profile_id, action_tracker_id) select f.friend_id, #{tracked_action.id} from friendships as f where person_id=#{tracked_action.user.id} and f.friend_id not in (select atn.profile_id from action_tracker_notifications as atn where atn.action_tracker_id = #{tracked_action.id})") + #TODO fix spaming notifications when following and unfolling many times + # Notify all followers + ActionTrackerNotification.connection.execute("INSERT INTO action_tracker_notifications(profile_id, action_tracker_id) SELECT f.follower_id, #{tracked_action.id} FROM profile_followers AS f WHERE profile_id=#{tracked_action.user.id} AND (f.follower_id NOT IN (SELECT atn.profile_id FROM action_tracker_notifications AS atn WHERE atn.action_tracker_id = #{tracked_action.id}))") if tracked_action.user.is_a? Organization ActionTrackerNotification.connection.execute "insert into action_tracker_notifications(profile_id, action_tracker_id) " + diff --git a/app/models/add_member.rb b/app/models/add_member.rb index 5e978ef..ffa2374 100644 --- a/app/models/add_member.rb +++ b/app/models/add_member.rb @@ -22,6 +22,7 @@ class AddMember < Task self.roles = [Profile::Roles.member(organization.environment.id).id] end target.affiliate(requestor, self.roles.select{|r| !r.to_i.zero? }.map{|i| Role.find(i)}) + person.follow(organization, _('memberships')) end def title diff --git a/app/models/article.rb b/app/models/article.rb index e5898f2..aaff0a6 100644 --- a/app/models/article.rb +++ b/app/models/article.rb @@ -534,13 +534,13 @@ class Article < ApplicationRecord scope :display_filter, lambda {|user, profile| return published if (user.nil? && profile && profile.public?) - return [] if user.nil? || (profile && !profile.public? && !user.follows?(profile)) + return [] if user.nil? || (profile && !profile.public? && !profile.in_social_circle?(user)) where( [ "published = ? OR last_changed_by_id = ? OR profile_id = ? OR ? OR (show_to_followers = ? AND ? AND profile_id IN (?))", true, user.id, user.id, profile.nil? ? false : user.has_permission?(:view_private_content, profile), - true, (profile.nil? ? true : user.follows?(profile)), ( profile.nil? ? (user.friends.select('profiles.id')) : [profile.id]) + true, (profile.nil? ? true : profile.in_social_circle?(user)), ( profile.nil? ? (user.friends.select('profiles.id')) : [profile.id]) ] ) } diff --git a/app/models/block.rb b/app/models/block.rb index a32bfbb..93f71aa 100644 --- a/app/models/block.rb +++ b/app/models/block.rb @@ -88,7 +88,7 @@ class Block < ApplicationRecord end def display_to_user?(user) - display_user == 'all' || (user.nil? && display_user == 'not_logged') || (user && display_user == 'logged') || (user && display_user == 'followers' && user.follows?(owner)) + display_user == 'all' || (user.nil? && display_user == 'not_logged') || (user && display_user == 'logged') || (user && display_user == 'followers' && owner.in_social_circle?(user)) end def display_always(context) diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb index de68ab9..76d9500 100644 --- a/app/models/enterprise.rb +++ b/app/models/enterprise.rb @@ -174,8 +174,4 @@ class Enterprise < Organization '' end - def followed_by? person - super or self.fans.where(id: person.id).count > 0 - end - end diff --git a/app/models/favorite_enterprise_person.rb b/app/models/favorite_enterprise_person.rb index 6820f34..deb6562 100644 --- a/app/models/favorite_enterprise_person.rb +++ b/app/models/favorite_enterprise_person.rb @@ -7,6 +7,10 @@ class FavoriteEnterprisePerson < ApplicationRecord belongs_to :enterprise belongs_to :person + after_create do |favorite| + favorite.person.follow(favorite.enterprise, _('favorites')) + end + protected def is_trackable? diff --git a/app/models/follow_category.rb b/app/models/follow_category.rb new file mode 100644 index 0000000..b4dca5d --- /dev/null +++ b/app/models/follow_category.rb @@ -0,0 +1,8 @@ +class FollowCategory < ApplicationRecord + belongs_to :environment + has_many :profile_followers + belongs_to :person + + attr_accessible :name, :person + validates :name, presence: true +end diff --git a/app/models/friendship.rb b/app/models/friendship.rb index c497b1b..d3ff315 100644 --- a/app/models/friendship.rb +++ b/app/models/friendship.rb @@ -9,11 +9,13 @@ class Friendship < ApplicationRecord after_create do |friendship| Friendship.update_cache_counter(:friends_count, friendship.person, 1) Friendship.update_cache_counter(:friends_count, friendship.friend, 1) + friendship.person.follow(friendship.friend, friendship.group) end after_destroy do |friendship| Friendship.update_cache_counter(:friends_count, friendship.person, -1) Friendship.update_cache_counter(:friends_count, friendship.friend, -1) + friendship.person.unfollow(friendship.friend) end def self.remove_friendship(person1, person2) diff --git a/app/models/person.rb b/app/models/person.rb index 323e5b6..3c83e4c 100644 --- a/app/models/person.rb +++ b/app/models/person.rb @@ -8,7 +8,6 @@ class Person < Profile :display => %w[compact] } - def self.type_name _('Person') end @@ -93,6 +92,7 @@ class Person < Profile has_many :following_articles, :class_name => 'Article', :through => :article_followers, :source => :article has_many :friendships, :dependent => :destroy has_many :friends, :class_name => 'Person', :through => :friendships + has_many :follow_categories scope :online, -> { joins(:user).where("users.chat_status != '' AND users.chat_status_at >= ?", DateTime.now - User.expires_chat_status_every.minutes) @@ -200,6 +200,17 @@ class Person < Profile end end + def follow(profile, category_name = "") + unless self.following_profiles.include?(profile) + profile_follower = ProfileFollower.new + profile_follower.profile = profile + profile_follower.follower = self + category = FollowCategory.find_or_create_by(:name => category_name, :person => self) + profile_follower.follow_category = category + profile_follower.save + end + end + def already_request_friendship?(person) person.tasks.where(requestor_id: self.id, type: 'AddFriend', status: Task::Status::ACTIVE).first end @@ -208,6 +219,11 @@ class Person < Profile Friendship.where(friend_id: friend, person_id: id).first.destroy end + def unfollow(profile) + follower = ProfileFollower.where(follower_id: id, profile_id: profile.id) if profile + follower.first.destroy if follower.present? + end + FIELDS = %w[ description image @@ -580,9 +596,12 @@ class Person < Profile person.has_permission?(:manage_friends, self) end - protected + def following_profiles + Profile.following_profiles self + end - def followed_by?(profile) - self == profile || self.is_a_friend?(profile) + def in_social_circle?(person) + self.is_a_friend?(person) || super end + end diff --git a/app/models/profile.rb b/app/models/profile.rb index 1d9f516..2da0d77 100644 --- a/app/models/profile.rb +++ b/app/models/profile.rb @@ -5,7 +5,7 @@ class Profile < ApplicationRecord attr_accessible :name, :identifier, :public_profile, :nickname, :custom_footer, :custom_header, :address, :zip_code, :contact_phone, :image_builder, :description, :closed, :template_id, :environment, :lat, :lng, :is_template, :fields_privacy, :preferred_domain_id, :category_ids, :country, :city, :state, :national_region_code, :email, :contact_email, :redirect_l10n, :notification_time, :redirection_after_login, :custom_url_redirection, - :email_suggestions, :allow_members_to_invite, :invite_friends_only, :secret, :profile_admin_mail_notification + :email_suggestions, :allow_members_to_invite, :invite_friends_only, :secret, :profile_admin_mail_notification, :allow_following # use for internationalizable human type names in search facets # reimplement on subclasses @@ -203,6 +203,16 @@ class Profile < ApplicationRecord scope :more_active, -> { order 'activities_count DESC' } scope :more_recent, -> { order "created_at DESC" } + scope :following_profiles, -> person { + distinct.select('profiles.*, follow_categories.name AS category'). + joins('left join profile_followers ON profile_followers.profile_id = profiles.id'). + joins('left join follow_categories ON follow_categories.id = profile_followers.follow_category_id'). + where('profile_followers.follower_id = ?', person.id) + } + + settings_items :allow_following, :type => :boolean, :default => true + alias_method :allow_following?, :allow_following + acts_as_trackable :dependent => :destroy has_many :profile_activities @@ -215,6 +225,9 @@ class Profile < ApplicationRecord has_many :email_templates, :foreign_key => :owner_id + has_many :profile_followers + has_many :followers, :class_name => 'Person', :through => :profile_followers + # Although this should be a has_one relation, there are no non-silly names for # a foreign key on article to reference the template to which it is # welcome_page... =P @@ -764,6 +777,7 @@ private :generate_url, :url_options else self.affiliate(person, Profile::Roles.admin(environment.id), attributes) if members.count == 0 self.affiliate(person, Profile::Roles.member(environment.id), attributes) + person.follow(self, _('memberships')) end person.tasks.pending.of("InviteMember").select { |t| t.data[:community_id] == self.id }.each { |invite| invite.cancel } remove_from_suggestion_list person @@ -1107,7 +1121,11 @@ private :generate_url, :url_options end def followed_by?(person) - person.is_member_of?(self) + (person == self) || (person.in? self.followers) + end + + def in_social_circle?(person) + (person == self) || (person.is_member_of?(self)) end def display_private_info_to?(user) diff --git a/app/models/profile_follower.rb b/app/models/profile_follower.rb new file mode 100644 index 0000000..c35b199 --- /dev/null +++ b/app/models/profile_follower.rb @@ -0,0 +1,13 @@ +class ProfileFollower < ApplicationRecord + track_actions :new_follower, :after_create, :keep_params => ["follower.name", "follower.url", "follower.profile_custom_icon"], :custom_user => :profile + + attr_accessible :profile, :follower, :follow_category + + belongs_to :profile, :foreign_key => :profile_id + belongs_to :follower, :class_name => 'Person', :foreign_key => :follower_id + belongs_to :follow_category, :foreign_key => :follow_category_id + + validates_presence_of :profile_id, :follower_id + validates :profile_id, :uniqueness => {:scope => :follower_id, :message => "can't follow the same profile twice"} + +end diff --git a/app/views/blocks/profile_info_actions/_common.html.erb b/app/views/blocks/profile_info_actions/_common.html.erb index 8596139..49baca6 100644 --- a/app/views/blocks/profile_info_actions/_common.html.erb +++ b/app/views/blocks/profile_info_actions/_common.html.erb @@ -1,2 +1,13 @@
  • <%= report_abuse(profile, :button) %>
  • +<%if logged_in? && (user != profile) && profile.allow_following?%> +
  • + <% if user.follows?(profile) %> + <%= button(:unfollow, content_tag('span', _('Unfollow')), {:profile => profile.identifier, :controller => 'profile', :action => 'unfollow'}) %> + <% else %> + <%= button(:follow, content_tag('span', _('Follow')), {:profile => profile.identifier, :controller => 'profile', :action => 'follow'}, :class => 'action-follow') %> + + <% end %> +
  • +<%end%> <%= render_environment_features(:profile_actions) %> diff --git a/app/views/blocks/profile_info_actions/_follow_categories.html.erb b/app/views/blocks/profile_info_actions/_follow_categories.html.erb new file mode 100644 index 0000000..b84a5f5 --- /dev/null +++ b/app/views/blocks/profile_info_actions/_follow_categories.html.erb @@ -0,0 +1,24 @@ +
    +

    <%= _("You can set a category for %s") % profile.name %>

    + <% categories.each do |category| %> +
    + + <%= category %> + +
    + <% end %> + + <%= form_for :follow_categories, :url => {:controller => 'followers', :action => 'update_category'} do |f|%> + <%= text_field_tag(:category_name,"", :id=>"new-category-field-actions-block") %> + <%= hidden_field_tag(:redirect_to, url_for(profile.url)) %> +
    + <%= submit_button :add, '', :id =>"new-category-submit-inline"%> +
    + <% end %> +
    diff --git a/app/views/follow_categories/_form.html.erb b/app/views/follow_categories/_form.html.erb new file mode 100644 index 0000000..3ddcf7d --- /dev/null +++ b/app/views/follow_categories/_form.html.erb @@ -0,0 +1,12 @@ +<%= error_messages_for :follow_category %> + +<%= labelled_form_for :follow_category, :url => (mode == :edit) ? {:action => 'update', :id => category} : {:action => 'create'} do |f| %> + + <%= required_fields_message %> + + <%= required f.text_field(:name) %> + + <%= button_bar do %> + <%= submit_button('save', (mode == :edit) ? _('Save changes') : _('Create category'), :cancel => {:action => 'index'} ) %> + <% end %> +<% end %> diff --git a/app/views/follow_categories/edit.html.erb b/app/views/follow_categories/edit.html.erb new file mode 100644 index 0000000..903f43e --- /dev/null +++ b/app/views/follow_categories/edit.html.erb @@ -0,0 +1,3 @@ +

    <%= _("Edit follow category") %>

    + +<%= render :partial => 'form', :locals => { :mode => :edit, :category => @follow_category } %> diff --git a/app/views/follow_categories/index.html.erb b/app/views/follow_categories/index.html.erb new file mode 100644 index 0000000..836ad81 --- /dev/null +++ b/app/views/follow_categories/index.html.erb @@ -0,0 +1,26 @@ +

    <%= _('Manage follow categories') %>

    + + + + + + + <% @categories.each do |category| %> + + + + + <% end %> +
    <%= _('Category') %><%= _('Actions') %>
    + <%= category.name %> + +
    + <%= button_without_text :edit, _('Edit'), :action => 'edit', :id => category %> + <%= button_without_text :delete, _('Delete'), :action => 'destroy', :id => category %> +
    +
    + +<%= button_bar do %> + <%= button :add, _('Create a new category'), :action => 'new' %> + <%= button :back, _('Back to control panel'), :controller => 'profile_editor' %> +<% end %> diff --git a/app/views/follow_categories/new.html.erb b/app/views/follow_categories/new.html.erb new file mode 100644 index 0000000..61a02fc --- /dev/null +++ b/app/views/follow_categories/new.html.erb @@ -0,0 +1,3 @@ +

    <%= _("Nww follow category") %>

    + +<%= render :partial => 'form', :locals => { :mode => :new, :category => @follow_category } %> diff --git a/app/views/followers/_profile_list.html.erb b/app/views/followers/_profile_list.html.erb new file mode 100644 index 0000000..fe1d7a3 --- /dev/null +++ b/app/views/followers/_profile_list.html.erb @@ -0,0 +1,19 @@ + diff --git a/app/views/followers/_set_category_modal.html.erb b/app/views/followers/_set_category_modal.html.erb new file mode 100644 index 0000000..b63507f --- /dev/null +++ b/app/views/followers/_set_category_modal.html.erb @@ -0,0 +1,13 @@ +
    +

    <%= _("Choose a new category") %>

    + <%= form_for :follower_category, :url => url_for(:controller => 'followers', :action => 'set_category') do |f| %> +
    + <%= labelled_text_field _("Category: "), "category_name" %> +
    + <%= hidden_field_tag 'followed_profile_id', followed_profile_id %> +
    + <%= submit_button('save', _('Save')) %> + <%= modal_close_button _("Cancel") %> +
    + <% end %> +
    diff --git a/app/views/followers/index.html.erb b/app/views/followers/index.html.erb new file mode 100644 index 0000000..c496b67 --- /dev/null +++ b/app/views/followers/index.html.erb @@ -0,0 +1,25 @@ +
    + +

    <%= _("%s following") % profile.name %>

    + +<% cache_timeout(profile.manage_friends_cache_key(params), 4.hours) do %> + <% if @followed_people.empty? %> +

    + + <%= _("You don't follow anybody yet.") %> + +

    + <% end %> + + <%= button_bar do %> + <%= button(:back, _('Back to control panel'), :controller => 'profile_editor') %> + <%= button(:search, _('Find people'), :controller => 'search', :action => 'assets', :asset => 'people') %> + <% end %> + + <%= render :partial => 'profile_list', :locals => { :profiles => @followed_people } %> + +
    + <%= pagination_links @followed_people, :param_name => 'npage' %> +<% end %> + +
    diff --git a/app/views/person_notifier/mailer/_new_follower.html.erb b/app/views/person_notifier/mailer/_new_follower.html.erb new file mode 100644 index 0000000..ce1d787 --- /dev/null +++ b/app/views/person_notifier/mailer/_new_follower.html.erb @@ -0,0 +1 @@ +<%= render :partial => 'default_activity', :locals => { :activity => activity } %> diff --git a/app/views/profile/_new_follower.html.erb b/app/views/profile/_new_follower.html.erb new file mode 100644 index 0000000..3cc3227 --- /dev/null +++ b/app/views/profile/_new_follower.html.erb @@ -0,0 +1 @@ +<%= render :partial => 'default_activity', :locals => { :activity => activity, :tab_action => tab_action } %> diff --git a/app/views/profile/following.html.erb b/app/views/profile/following.html.erb new file mode 100644 index 0000000..3492e78 --- /dev/null +++ b/app/views/profile/following.html.erb @@ -0,0 +1,24 @@ +
    + +

    <%= _("%s is following") % profile.name %>

    + +<% cache_timeout(profile.friends_cache_key(params), 4.hours) do %> + + +
    + <%= pagination_links @followed_people, :param_name => 'npage' %> +
    +<% end %> + +<%= button_bar do %> + <%= button :back, _('Go back'), { :controller => 'profile' } %> + <% if user == profile %> + <%= button :edit, _('Manage followed people'), :controller => 'friends', :action => 'index', :profile => profile.identifier %> + <% end %> +<% end %> + +
    diff --git a/app/views/profile_editor/edit.html.erb b/app/views/profile_editor/edit.html.erb index 2bc3c45..7c262be 100644 --- a/app/views/profile_editor/edit.html.erb +++ b/app/views/profile_editor/edit.html.erb @@ -23,8 +23,11 @@

    <%= _('Privacy options') %>

    - +
    + <%= labelled_check_box _("Allow other users to follow me"), 'profile_data[allow_following]', true, @profile.allow_following?, :class => "person-can-be-followed" %> +
    <% if profile.person? %> +
    <%= labelled_radio_button _('Public — show my contents to all internet users').html_safe, 'profile_data[public_profile]', true, @profile.public_profile? %>
    diff --git a/app/views/profile_editor/index.html.erb b/app/views/profile_editor/index.html.erb index 9c99687..dac82de 100644 --- a/app/views/profile_editor/index.html.erb +++ b/app/views/profile_editor/index.html.erb @@ -72,6 +72,12 @@ <%= control_panel_button(_('Email Templates'), 'email-templates', :controller => :profile_email_templates) if profile.organization? %> + <% if profile.person? %> + <%= control_panel_button(_('Manage followed profiles'), 'manage-followed-people', :controller => :followers) %> + <% end %> + + <%= control_panel_button(_('Manage follow categories'), 'manage-follow-categories', :controller => :follow_categories) if profile.person? %> + <% @plugins.dispatch(:control_panel_buttons).each do |button| %> <%= control_panel_button(button[:title], button[:icon], button[:url], button[:html_options]) %> <% end %> diff --git a/config/initializers/action_tracker.rb b/config/initializers/action_tracker.rb index 7080775..9bfcfa3 100644 --- a/config/initializers/action_tracker.rb +++ b/config/initializers/action_tracker.rb @@ -12,6 +12,10 @@ ActionTrackerConfig.verbs = { type: :groupable }, + new_follower: { + type: :groupable + }, + join_community: { type: :groupable }, diff --git a/db/migrate/20160608123748_create_profile_followers_table.rb b/db/migrate/20160608123748_create_profile_followers_table.rb new file mode 100644 index 0000000..3e8a853 --- /dev/null +++ b/db/migrate/20160608123748_create_profile_followers_table.rb @@ -0,0 +1,41 @@ +class CreateProfileFollowersTable < ActiveRecord::Migration + def up + create_table :profile_followers do |t| + t.column :profile_id, :integer + t.column :follower_id, :integer + t.column :follow_category_id, :integer + t.timestamps + end + + create_table :follow_categories do |t| + t.column :name, :string + t.belongs_to :person + end + + add_foreign_key :profile_followers, :follow_categories, :on_delete => :nullify + + add_index :profile_followers, [:profile_id, :follower_id], :name => "profile_followers_composite_key_index", :unique => true + + #insert one category for each friend group a person has + execute("INSERT INTO follow_categories(name, person_id) SELECT DISTINCT (CASE WHEN (f.group IS NULL OR f.group = '') THEN 'friendships' ELSE f.group END), f.person_id FROM friendships as f") + #insert 'memberships' category if a person is in a community as a member, moderator or profile admin + execute("INSERT INTO follow_categories(name, person_id) SELECT DISTINCT 'memberships', ra.accessor_id FROM role_assignments as ra JOIN roles ON ra.role_id = roles.id WHERE roles.name IN ('Member','Moderator','Profile Administrator')") + #insert 'favorites' category if a person has any favorited enterprise + execute("INSERT INTO follow_categories(name, person_id) SELECT DISTINCT 'favorites', person_id FROM favorite_enterprise_people") + + #insert a follower entry for each friend, with the category the same as the friendship group or equals 'friendships' + execute("INSERT INTO profile_followers(follower_id, profile_id, follow_category_id) SELECT DISTINCT f.person_id, f.friend_id, c.id FROM friendships as f JOIN follow_categories as c ON f.person_id = c.person_id WHERE c.name = f.group OR c.name = 'friendships'") + #insert a follower entry for each favorited enterprise, with the category 'favorites' + execute("INSERT INTO profile_followers(follower_id, profile_id, follow_category_id) SELECT DISTINCT f.person_id, f.enterprise_id, c.id FROM favorite_enterprise_people AS f JOIN follow_categories as c ON f.person_id = c.person_id WHERE c.name = 'favorites' ") + #insert a follower entry for each community a person participates as a member, moderator or admininstrator + execute("INSERT INTO profile_followers(follower_id, profile_id, follow_category_id) SELECT DISTINCT ra.accessor_id, ra.resource_id, c.id FROM role_assignments as ra JOIN roles ON ra.role_id = roles.id JOIN follow_categories as c ON ra.accessor_id = c.person_id WHERE roles.name IN ('Member','Moderator','Profile Administrator') AND c.name = 'memberships'") + + end + + def down + remove_foreign_key :profile_followers, :follow_categories + drop_table :follow_categories + remove_index :profile_followers, :name => "profile_followers_composite_key_index" + drop_table :profile_followers + end +end diff --git a/features/follow_profile.feature b/features/follow_profile.feature new file mode 100644 index 0000000..a129ef6 --- /dev/null +++ b/features/follow_profile.feature @@ -0,0 +1,55 @@ +Feature: follow profile + As a noosfero user + I want to follow a profile + So I can receive notifications from it + + Background: + Given the following community + | identifier | name | + | nightswatch | Nights Watch | + And the following users + | login | + | johnsnow | + And the user "johnsnow" has the following categories to follow + | name | + | Family | + | Work | + + @selenium + Scenario: Common noofero user follow a community with a category + Given I am logged in as "johnsnow" + When I go to nightswatch's homepage + When I follow "Follow" + And I should see "Family" + And I should see "Work" + And I should see "No Category" + Then "johnsnow" should be a follower of "nightswatch" with no category + When I follow "Work" + Then "johnsnow" should be a follower of "nightswatch" with category "Work" + + @selenium + Scenario: Common noofero user follow a community with no category + Given I am logged in as "johnsnow" + When I go to nightswatch's homepage + When I follow "Follow" + When I follow "No Category" + Then "johnsnow" should be a follower of "nightswatch" with no category + + @selenium + Scenario: Common noofero user follow a community with a new category + Given I am logged in as "johnsnow" + When I go to nightswatch's homepage + When I follow "Follow" + And I fill in "category_name" with "Winterfell" + When I click on anything with selector "#new-category-submit-inline" + And I wait 3 second + Then "johnsnow" should be a follower of "nightswatch" with category "Winterfell" + + @selenium + Scenario: Common noofero user unfollow a community + Given "johnsnow" is a follower of "nightswatch" with no category + And I am logged in as "johnsnow" + When I go to nightswatch's homepage + When I follow "Unfollow" + Then "johnsnow" should not be a follower of "nightswatch" + diff --git a/features/step_definitions/followers_steps.rb b/features/step_definitions/followers_steps.rb new file mode 100644 index 0000000..3e24929 --- /dev/null +++ b/features/step_definitions/followers_steps.rb @@ -0,0 +1,39 @@ +Given /^the user "(.+)" has the following categories to follow$/ do |user_name,table| + person = User.find_by(:login => user_name).person + table.hashes.each do |category| + FollowCategory.create!(:person => person, :name => category[:name]) + end +end + +Then /^"(.+)" should be a follower of "(.+)" (?:with no category|with category "(.+)")$/ do |person, profile, category| + profile = Profile.find_by(identifier: profile) + followers = profile.followers + person = Person.find_by(identifier: person) + followers.should include(person) + + if category + ProfileFollower.find_by(:follower => person, :profile => profile).follow_category.name.should == category + else + ProfileFollower.find_by(:follower => person, :profile => profile).follow_category.should == nil + end +end + +Then /^"(.+)" should not be a follower of "(.+)"$/ do |person, profile| + profile = Profile.find_by(identifier: profile) + followers = profile.followers + person = Person.find_by(identifier: person) + followers.should_not include(person) +end + +Given /^"(.+)" is a follower of "(.+)" (?:with no category|with category "(.+)")$/ do |person, profile, category| + profile = Profile.find_by(identifier: profile) + person = Person.find_by(identifier: person) + params = {:follower => person, :profile => profile} + + if category + category = FollowCategory.find_by(:name => category, :person => person) + params.merge!({:follow_category => category}) + end + ProfileFollower.create!(params) +end + diff --git a/features/step_definitions/web_steps.rb b/features/step_definitions/web_steps.rb index a0e0c6c..d12a33a 100644 --- a/features/step_definitions/web_steps.rb +++ b/features/step_definitions/web_steps.rb @@ -298,3 +298,6 @@ When /^(?:|I )wait ([^ ]+) seconds?(?:| .+)$/ do |seconds| sleep seconds.to_f end +Given /^I click on anything with selector "([^"]*)"$/ do |selector| + page.evaluate_script("jQuery('#{selector}').click();") +end diff --git a/public/javascripts/application.js b/public/javascripts/application.js index fff7e9b..778c508 100644 --- a/public/javascripts/application.js +++ b/public/javascripts/application.js @@ -26,6 +26,7 @@ *= require pagination.js * views speficics *= require add-and-join.js +*= require followers.js *= require report-abuse.js *= require autogrow.js *= require require_login.js @@ -550,6 +551,11 @@ function loading_for_button(selector) { jQuery(selector).css('cursor', 'progress'); } +function hide_loading_for_button(selector) { + selector.css("cursor",""); + $(".small-loading").remove(); +} + function new_qualifier_row(selector, select_qualifiers, delete_button) { index = jQuery(selector + ' tr').size() - 1; jQuery(selector).append("" + select_qualifiers + "" + delete_button + ""); diff --git a/public/javascripts/followers.js b/public/javascripts/followers.js new file mode 100644 index 0000000..6d4687b --- /dev/null +++ b/public/javascripts/followers.js @@ -0,0 +1,18 @@ +$(".action-follow").live("click", function() { + var button = $(this); + var url = button.attr("href"); + loading_for_button(button); + + $.post(url, function(data) { + button.fadeOut('fast', function() { + $("#follow-categories-container").html(data); + $("#follow-categories-container").fadeIn(); + }); + }).fail(function(response) { + display_notice(response.responseText); + }).always(function() { + hide_loading_for_button(button); + }); + + return false; +}); diff --git a/public/stylesheets/blocks/profile-info.scss b/public/stylesheets/blocks/profile-info.scss index 80fd7f0..1190e3a 100644 --- a/public/stylesheets/blocks/profile-info.scss +++ b/public/stylesheets/blocks/profile-info.scss @@ -99,3 +99,25 @@ margin: 0px 0px 5px 0px; padding: 2px; } +#follow-categories-container { + background-color: #eee; + padding: 5px; + display: flex; +} +#follow-categories-container p { + font-size: 12px; + margin-bottom: 5px; +} +#no-category-link { + margin-top: 10px; + margin-bottom: 10px; +} +#new-category-field-actions-block { + float: left; + width: 80%; +} +#new-category-submit-actions-block { + float: right; + width: 10%; + padding-right: 10px; +} diff --git a/public/stylesheets/profile-activity.scss b/public/stylesheets/profile-activity.scss index 91848a1..6163fe1 100644 --- a/public/stylesheets/profile-activity.scss +++ b/public/stylesheets/profile-activity.scss @@ -167,7 +167,9 @@ li.profile-activity-item.upload_image .activity-gallery-images-count-1 img { #profile-wall li.profile-activity-item.join_community .profile-activity-text a img, #profile-wall li.profile-activity-item.new_friendship .profile-activity-text a img, +#profile-wall li.profile-activity-item.new_follower .profile-activity-text a img, #profile-network li.profile-activity-item.join_community .profile-activity-text a img, +#profile-network li.profile-activity-item.new_follower .profile-activity-text a img, #profile-network li.profile-activity-item.new_friendship .profile-activity-text a img { margin: 5px 5px 0 0; padding: 1px; diff --git a/public/stylesheets/profile-list.scss b/public/stylesheets/profile-list.scss index cbd9bc1..3458bb9 100644 --- a/public/stylesheets/profile-list.scss +++ b/public/stylesheets/profile-list.scss @@ -23,6 +23,7 @@ } .controller-favorite_enterprises .profile-list a.profile-link, .controller-friends .profile-list a.profile-link, +.controller-followers .profile-list a.profile-link, .list-profile-connections .profile-list a.profile-link, .profiles-suggestions .profile-list a.profile-link { text-decoration: none; @@ -32,11 +33,13 @@ } .controller-favorite_enterprises .profile-list a.profile-link:hover, .controller-friends .profile-list a.profile-link:hover, +.controller-followers .profile-list a.profile-link:hover, .profiles-suggestions .profile-list a.profile-link:hover { color: #FFF; } .controller-favorite_enterprises .profile-list .profile_link span, .controller-friends .profile-list .profile_link span, +.controller-followers .profile-list .profile_link span, .box-1 .profiles-suggestions .profile-list .profile_link span { width: 80px; display: block; @@ -44,12 +47,14 @@ } .controller-favorite_enterprises .profile-list, .controller-friends .profile-list, +.controller-followers .profile-list, .profiles-suggestions .profile-list { position: relative; } .controller-favorite_enterprises .profile-list .controll, .controller-friends .profile-list .controll, +.controller-followers .profile-list .controll, .profiles-suggestions .profile-list .controll { position: absolute; top: 7px; @@ -57,17 +62,20 @@ } .controller-favorite_enterprises .profile-list .controll a, .controller-friends .profile-list .controll a, +.controller-followers .profile-list .controll a, .profiles-suggestions .profile-list .controll a { display: block; margin-bottom: 2px; } .controller-favorite_enterprises .msie6 .profile-list .controll a, .controller-friends .msie6 .profile-list .controll a, +.controller-folloed_people .msie6 .profile-list .controll a, .profiles-suggestions .msie6 .profile-list .controll a { width: 0px; } .controller-favorite_enterprises .button-bar, .controller-friends .button-bar, +.controller-followers .button-bar, .profiles-suggestions .button-bar { clear: both; padding-top: 20px; @@ -208,22 +216,35 @@ font-size: 12px; } .action-profile-members .profile_link{ - position: relative; + position: relative; } .action-profile-members .profile_link span.new-profile:last-child{ - position: absolute; - top: 3px; - right: 2px; - text-transform: uppercase; - color: #FFF; - font-size: 9px; - background: #66CC33; - padding: 2px; - display: block; - width: 35px; - font-weight: 700; + position: absolute; + top: 3px; + right: 2px; + text-transform: uppercase; + color: #FFF; + font-size: 9px; + background: #66CC33; + padding: 2px; + display: block; + width: 35px; + font-weight: 700; } .action-profile-members .profile_link .fn{ - font-style: normal; - color: #000; + font-style: normal; + color: #000; +} +.category-name { + margin-top: 0px; + margin-bottom: 0px; + font-style: italic; + color: #888a85; + text-align: center; +} +.set-category-modal { + width: 250px; +} +.set-category-modal #actions-container { + margin-top: 20px } diff --git a/test/functional/follow_categories_controller_tests.rb b/test/functional/follow_categories_controller_tests.rb new file mode 100644 index 0000000..32848d3 --- /dev/null +++ b/test/functional/follow_categories_controller_tests.rb @@ -0,0 +1,103 @@ +require_relative "../test_helper" +require 'follow_categories_controller' + +class FollowCategoriesControllerTest < ActionController::TestCase + + def setup + @controller = FollowCategoriesController.new + @person = create_user('person').person + login_as(@person.identifier) + end + + should 'return all categories of a profile' do + category1 = FollowCategory.create(:name => "category1", :person => @person) + category2 = FollowCategory.create(:name => "category2", :person => @person) + get :index, :profile => @person.identifier + + assert_equivalent [category1, category2], assigns[:categories] + end + + should 'initialize an empty category for creation' do + get :new, :profile => @person.identifier + assert_nil assigns[:follow_category].id + assert_nil assigns[:follow_category].name + end + + should 'create a new category' do + assert_difference '@person.follow_categories.count' do + post :create, :profile => @person.identifier, + :follow_category => { :name => 'category' } + end + assert_redirected_to :action => :index + end + + should 'not create a category without a name' do + assert_no_difference '@person.follow_categories.count' do + post :create, :profile => @person.identifier, :follow_category => { :name => nil } + end + assert_template :new + end + + should 'retrieve an existing category when editing' do + category = FollowCategory.create(:name => "category", :person => @person) + get :edit, :profile => @person.identifier, :id => category.id + assert_equal category.name, assigns[:follow_category].name + end + + should 'return 404 when editing a category that does not exist' do + get :edit, :profile => @person.identifier, :id => "nope" + assert_response 404 + end + + should 'update an existing category' do + category = FollowCategory.create(:name => "category", :person => @person) + get :update, :profile => @person.identifier, :id => category.id, + :follow_category => { :name => "new name" } + + category.reload + assert_equal "new name", category.name + assert_redirected_to :action => :index + end + + should 'not update an existing category without a name' do + category = FollowCategory.create(:name => "category", :person => @person) + get :update, :profile => @person.identifier, :id => category.id, + :follow_category => { :name => nil } + + category.reload + assert_equal "category", category.name + assert_template :edit + end + + should 'return 404 when updating a category that does not exist' do + get :update, :profile => @person.identifier, :id => "nope", :name => "new name" + assert_response 404 + end + + should 'destroy an existing category and update related profiles' do + category = FollowCategory.create(:name => "category", :person => @person) + follower = fast_create(ProfileFollower, :profile_id => fast_create(Person).id, + :follower_id => @person.id, :follow_category_id => category.id) + + assert_difference "@person.follow_categories.count", -1 do + get :destroy, :profile => @person.identifier, :id => category.id + end + + follower.reload + assert_nil follower.follow_category + end + + should 'return 404 when deleting and category that does not exist' do + get :destroy, :profile => @person.identifier, :id => "nope" + assert_response 404 + end + + should 'display notice when ' do + category = FollowCategory.create(:name => "category", :person => @person) + FollowCategory.any_instance.stubs(:destroy).returns(false) + + get :destroy, :profile => @person.identifier, :id => category.id + assert_not_nil session[:notice] + end + +end diff --git a/test/functional/followers_controller_test.rb b/test/functional/followers_controller_test.rb new file mode 100644 index 0000000..a071cdc --- /dev/null +++ b/test/functional/followers_controller_test.rb @@ -0,0 +1,48 @@ +require_relative "../test_helper" +require 'followers_controller' + +class FollowersControllerTest < ActionController::TestCase + def setup + @profile = create_user('testuser').person + end + + should 'return followed people list' do + login_as(@profile.identifier) + person = fast_create(Person) + fast_create(ProfileFollower, :profile_id => person.id, :follower_id => @profile.id) + + get :index, :profile => @profile.identifier + assert_includes assigns(:followed_people), person + end + + should 'redirect to login page if not logged in' do + person = fast_create(Person) + get :index, :profile => @profile.identifier + assert_redirected_to :controller => 'account', :action => 'login' + end + + should 'render set category modal' do + login_as(@profile.identifier) + get :set_category, :profile => @profile.identifier, :followed_profile_id => 3 + assert_tag :tag => "input", :attributes => { :id => "followed_profile_id", :value => 3 } + end + + should 'update followed person category' do + login_as(@profile.identifier) + person = fast_create(Person) + fast_create(ProfileFollower, :profile_id => person.id, :follower_id => @profile.id) + + post :set_category, :profile => @profile.identifier, :category_name => "category test", :followed_profile_id => person.id + follower = ProfileFollower.find_by(:profile_id => person.id, :follower_id => @profile.id) + assert_equal "WRONG","FIX THIS TEST TO USE CATEGORY INsTEAD OF GROUP" #follower.group, "category test" + end + + should 'not update category of not followed person' do + login_as(@profile.identifier) + person = fast_create(Person) + + post :set_category, :profile => @profile.identifier, :category_name => "category test", :followed_profile_id => person.id + follower = ProfileFollower.find_by(:profile_id => person.id, :follower_id => @profile.id) + ProfileFollower.any_instance.expects(:update_attributes).times(0) + end +end diff --git a/test/functional/profile_controller_test.rb b/test/functional/profile_controller_test.rb index 57da6e0..67ae5e2 100644 --- a/test/functional/profile_controller_test.rb +++ b/test/functional/profile_controller_test.rb @@ -771,12 +771,13 @@ class ProfileControllerTest < ActionController::TestCase assert_equal 15, assigns(:activities).size end - should 'not see the friends activities in the current profile' do + should 'not see the followers activities in the current profile' do p2 = create_user.person - refute profile.is_a_friend?(p2) + refute profile.follows?(p2) p3 = create_user.person - p3.add_friend(profile) - assert p3.is_a_friend?(profile) + profile.follow(p3) + assert profile.follows?(p3) + ActionTracker::Record.destroy_all scrap1 = create(Scrap, defaults_for_scrap(:sender => p2, :receiver => p3)) @@ -964,7 +965,9 @@ class ProfileControllerTest < ActionController::TestCase should 'have activities defined if logged in and is following profile' do login_as(profile.identifier) p1= fast_create(Person) - p1.add_friend(profile) + + profile.follow(p1) + ActionTracker::Record.destroy_all get :index, :profile => p1.identifier assert_equal [], assigns(:activities) @@ -1932,4 +1935,77 @@ class ProfileControllerTest < ActionController::TestCase assert_redirected_to :controller => 'account', :action => 'login' end + should 'follow a user without defining a group' do + login_as(@profile.identifier) + person = fast_create(Person) + get :follow, :profile => person.identifier + + follower = ProfileFollower.find_by(:profile_id => person.id, :follower_id => @profile.id) + assert_not_nil follower + end + + should "not follow user if not logged" do + person = fast_create(Person) + get :follow, :profile => person.identifier + + assert_redirected_to :controller => 'account', :action => 'login' + end + + should 'follow a user with a group' do + login_as(@profile.identifier) + person = fast_create(Person) + get :follow, :profile => person.identifier, :follow => { :category => "A Group" } + + follower = ProfileFollower.find_by(:profile_id => person.id, :follower_id => @profile.id) + assert_not_nil follower + assert_equal "A Group", "FIX THIS TEST TO USE CATEGORY INsTEAD OF GROUP"#follower.category.name + end + + should 'not follow if current_person already follows the person' do + login_as(@profile.identifier) + person = fast_create(Person) + fast_create(ProfileFollower, :profile_id => person.id, :follower_id => @profile.id) + + assert_no_difference 'ProfileFollower.count' do + get :follow, :profile => person.identifier, :follow => { :category => "A Group" } + end + assert_response 400 + end + + should "not unfollow user if not logged" do + person = fast_create(Person) + get :unfollow, :profile => person.identifier + + assert_redirected_to :controller => 'account', :action => 'login' + end + + should "unfollow a followed person" do + login_as(@profile.identifier) + person = fast_create(Person) + follower = fast_create(ProfileFollower, :profile_id => person.id, :follower_id => @profile.id) + assert_not_nil follower + + get :unfollow, :profile => person.identifier + follower = ProfileFollower.find_by(:profile_id => person.id, :follower_id => @profile.id) + assert_nil follower + end + + should "not unfollow a not followed person" do + login_as(@profile.identifier) + person = fast_create(Person) + + assert_no_difference 'ProfileFollower.count' do + get :unfollow, :profile => person.identifier + end + end + + should "redirect to page after unfollow" do + login_as(@profile.identifier) + person = fast_create(Person) + fast_create(ProfileFollower, :profile_id => person.id, :follower_id => @profile.id) + + get :unfollow, :profile => person.identifier, :redirect_to => "/some/url" + assert_redirected_to "/some/url" + end + end diff --git a/test/unit/article_test.rb b/test/unit/article_test.rb index 68cb57f..6226eaa 100644 --- a/test/unit/article_test.rb +++ b/test/unit/article_test.rb @@ -1099,9 +1099,9 @@ class ArticleTest < ActiveSupport::TestCase assert_equal 3, ActionTrackerNotification.where(action_tracker_id: second_activity.id).count end - should 'create notifications to friends when creating an article' do + should 'create notifications to followers when creating an article' do friend = fast_create(Person) - profile.add_friend(friend) + friend.follow(profile) Article.destroy_all ActionTracker::Record.destroy_all ActionTrackerNotification.destroy_all @@ -1112,9 +1112,9 @@ class ArticleTest < ActiveSupport::TestCase assert_equal friend, ActionTrackerNotification.last.profile end - should 'create the notification to the friend when one friend has the notification and the other no' do + should 'create the notification to the follower when one follower has the notification and the other no' do f1 = fast_create(Person) - profile.add_friend(f1) + f1.follow(profile) User.current = profile.user article = create TinyMceArticle, :name => 'Tracked Article 1', :profile_id => profile.id @@ -1123,16 +1123,17 @@ class ArticleTest < ActiveSupport::TestCase assert_equal 2, ActionTrackerNotification.where(action_tracker_id: article.activity.id).count f2 = fast_create(Person) - profile.add_friend(f2) + f2.follow(profile) + article2 = create TinyMceArticle, :name => 'Tracked Article 2', :profile_id => profile.id assert_equal 2, ActionTracker::Record.where(verb: 'create_article').count process_delayed_job_queue assert_equal 3, ActionTrackerNotification.where(action_tracker_id: article2.activity.id).count end - should 'destroy activity and notifications of friends when destroying an article' do + should 'destroy activity and notifications of followers when destroying an article' do friend = fast_create(Person) - profile.add_friend(friend) + friend.follow(profile) Article.destroy_all ActionTracker::Record.destroy_all ActionTrackerNotification.destroy_all diff --git a/test/unit/friendship_test.rb b/test/unit/friendship_test.rb index 8ab5729..4a37a7f 100644 --- a/test/unit/friendship_test.rb +++ b/test/unit/friendship_test.rb @@ -28,14 +28,14 @@ class FriendshipTest < ActiveSupport::TestCase f.person = a f.friend = b f.save! - ta = ActionTracker::Record.last + ta = ActionTracker::Record.where(:target_type => "Friendship").last assert_equal a, ta.user assert_equal 'b', ta.get_friend_name[0] f = Friendship.new f.person = a f.friend = c f.save! - ta = ActionTracker::Record.last + ta = ActionTracker::Record.where(:target_type => "Friendship").last assert_equal a, ta.user assert_equal 'c', ta.get_friend_name[1] end @@ -46,14 +46,14 @@ class FriendshipTest < ActiveSupport::TestCase f.person = a f.friend = b f.save! - ta = ActionTracker::Record.last + ta = ActionTracker::Record.where(:target_type => "Friendship").last assert_equal a, ta.user assert_equal ['b'], ta.get_friend_name f = Friendship.new f.person = b f.friend = a f.save! - ta = ActionTracker::Record.last + ta = ActionTracker::Record.where(:target_type => "Friendship").last assert_equal b, ta.user assert_equal ['a'], ta.get_friend_name end @@ -72,4 +72,53 @@ class FriendshipTest < ActiveSupport::TestCase assert_not_includes p2.friends(true), p1 end + should 'add follower when adding friend' do + p1 = create_user('testuser1').person + p2 = create_user('testuser2').person + + assert_difference 'ProfileFollower.count', 2 do + p1.add_friend(p2, 'friends') + p2.add_friend(p1, 'friends') + end + + assert_includes p1.followers(true), p2 + assert_includes p2.followers(true), p1 + end + + should 'remove follower when a friend removal occurs' do + p1 = create_user('testuser1').person + p2 = create_user('testuser2').person + + p1.add_friend(p2, 'friends') + p2.add_friend(p1, 'friends') + + Friendship.remove_friendship(p1, p2) + + assert_not_includes p1.followers(true), p2 + assert_not_includes p2.followers(true), p1 + end + + should 'keep friendship intact when stop following' do + p1 = create_user('testuser1').person + p2 = create_user('testuser2').person + + p1.add_friend(p2, 'friends') + p2.add_friend(p1, 'friends') + + p1.unfollow(p2) + + assert_includes p1.friends(true), p2 + assert_includes p2.friends(true), p1 + end + + should 'do not add friendship when start following' do + p1 = create_user('testuser1').person + p2 = create_user('testuser2').person + + p1.follow(p2, 'favorites') + p2.follow(p1, 'favorites') + + assert_not_includes p1.friends(true), p2 + assert_not_includes p2.friends(true), p1 + end end diff --git a/test/unit/notify_activity_to_profiles_job_test.rb b/test/unit/notify_activity_to_profiles_job_test.rb index 311113d..afa5972 100644 --- a/test/unit/notify_activity_to_profiles_job_test.rb +++ b/test/unit/notify_activity_to_profiles_job_test.rb @@ -24,15 +24,15 @@ class NotifyActivityToProfilesJobTest < ActiveSupport::TestCase end end - should 'notify just the users and his friends tracking user actions' do + should 'notify just the users and his followers tracking user actions' do person = fast_create(Person) community = fast_create(Community) action_tracker = fast_create(ActionTracker::Record, :user_type => 'Profile', :user_id => person.id, :target_type => 'Profile', :verb => 'create_article') refute NotifyActivityToProfilesJob::NOTIFY_ONLY_COMMUNITY.include?(action_tracker.verb) p1, p2, m1, m2 = fast_create(Person), fast_create(Person), fast_create(Person), fast_create(Person) - fast_create(Friendship, :person_id => person.id, :friend_id => p1.id) - fast_create(Friendship, :person_id => person.id, :friend_id => p2.id) - fast_create(Friendship, :person_id => p1.id, :friend_id => m1.id) + fast_create(ProfileFollower, :profile_id => person.id, :follower_id => p1.id) + fast_create(ProfileFollower, :profile_id => person.id, :follower_id => p2.id) + fast_create(ProfileFollower, :profile_id => m1.id, :follower_id => person.id) fast_create(RoleAssignment, :accessor_id => m2.id, :role_id => 3, :resource_id => community.id) ActionTrackerNotification.delete_all job = NotifyActivityToProfilesJob.new(action_tracker.id) @@ -66,23 +66,21 @@ class NotifyActivityToProfilesJobTest < ActiveSupport::TestCase end end - should 'notify users its friends, the community and its members' do + should 'notify users its followers, the community and its members' do person = fast_create(Person) community = fast_create(Community) action_tracker = fast_create(ActionTracker::Record, :user_type => 'Profile', :user_id => person.id, :target_type => 'Profile', :target_id => community.id, :verb => 'create_article') refute NotifyActivityToProfilesJob::NOTIFY_ONLY_COMMUNITY.include?(action_tracker.verb) p1, p2, m1, m2 = fast_create(Person), fast_create(Person), fast_create(Person), fast_create(Person) - fast_create(Friendship, :person_id => person.id, :friend_id => p1.id) - fast_create(Friendship, :person_id => person.id, :friend_id => p2.id) + fast_create(ProfileFollower, :profile_id => person.id, :follower_id => p1.id) fast_create(RoleAssignment, :accessor_id => m1.id, :role_id => 3, :resource_id => community.id) fast_create(RoleAssignment, :accessor_id => m2.id, :role_id => 3, :resource_id => community.id) ActionTrackerNotification.delete_all job = NotifyActivityToProfilesJob.new(action_tracker.id) job.perform process_delayed_job_queue - - assert_equal 6, ActionTrackerNotification.count - [person, community, p1, p2, m1, m2].each do |profile| + assert_equal 5, ActionTrackerNotification.count + [person, community, p1, m1, m2].each do |profile| notification = ActionTrackerNotification.find_by profile_id: profile.id assert_equal action_tracker, notification.action_tracker end @@ -119,8 +117,8 @@ class NotifyActivityToProfilesJobTest < ActiveSupport::TestCase action_tracker = fast_create(ActionTracker::Record, :user_type => 'Profile', :user_id => person.id, :target_type => 'Profile', :target_id => community.id, :verb => 'join_community') refute NotifyActivityToProfilesJob::NOTIFY_ONLY_COMMUNITY.include?(action_tracker.verb) p1, p2, m1, m2 = fast_create(Person), fast_create(Person), fast_create(Person), fast_create(Person) - fast_create(Friendship, :person_id => person.id, :friend_id => p1.id) - fast_create(Friendship, :person_id => person.id, :friend_id => p2.id) + fast_create(ProfileFollower, :profile_id => person.id, :follower_id => p1.id) + fast_create(ProfileFollower, :profile_id => person.id, :follower_id => p2.id) fast_create(RoleAssignment, :accessor_id => m1.id, :role_id => 3, :resource_id => community.id) fast_create(RoleAssignment, :accessor_id => m2.id, :role_id => 3, :resource_id => community.id) ActionTrackerNotification.delete_all diff --git a/test/unit/person_notifier_test.rb b/test/unit/person_notifier_test.rb index 0a37d1e..6b1c4a1 100644 --- a/test/unit/person_notifier_test.rb +++ b/test/unit/person_notifier_test.rb @@ -178,6 +178,7 @@ class PersonNotifierTest < ActiveSupport::TestCase update_product: -> { create Product, profile: @profile, product_category: create(ProductCategory, environment: Environment.default) }, remove_product: -> { create Product, profile: @profile, product_category: create(ProductCategory, environment: Environment.default) }, favorite_enterprise: -> { create FavoriteEnterprisePerson, enterprise: create(Enterprise), person: @member }, + new_follower: -> { @member } } ActionTrackerConfig.verb_names.each do |verb| @@ -197,6 +198,7 @@ class PersonNotifierTest < ActiveSupport::TestCase 'friend_url' => '/', 'friend_profile_custom_icon' => [], 'friend_name' => ['joe'], 'resource_name' => ['resource'], 'resource_profile_custom_icon' => [], 'resource_url' => ['/'], 'enterprise_name' => 'coop', 'enterprise_url' => '/coop', + 'follower_url' => '/', 'follower_profile_custom_icon' => [], 'follower_name' => ['joe'], 'view_url'=> ['/'], 'thumbnail_path' => ['1'], } a.get_url = '' diff --git a/test/unit/person_test.rb b/test/unit/person_test.rb index 440a8f6..67dffd0 100644 --- a/test/unit/person_test.rb +++ b/test/unit/person_test.rb @@ -728,7 +728,7 @@ class PersonTest < ActiveSupport::TestCase assert_equal [s4], p2.scraps_received.not_replies end - should "the followed_by method be protected and true to the person friends and herself by default" do + should "the followed_by method return true to the person friends and herself by default" do p1 = fast_create(Person) p2 = fast_create(Person) p3 = fast_create(Person) @@ -740,9 +740,9 @@ class PersonTest < ActiveSupport::TestCase assert p1.is_a_friend?(p4) assert_equal true, p1.send(:followed_by?,p1) - assert_equal true, p1.send(:followed_by?,p2) - assert_equal true, p1.send(:followed_by?,p4) - assert_equal false, p1.send(:followed_by?,p3) + assert_equal true, p2.send(:followed_by?,p1) + assert_equal true, p4.send(:followed_by?,p1) + assert_equal false, p3.send(:followed_by?,p1) end should "the person follows her friends and herself by default" do @@ -757,9 +757,9 @@ class PersonTest < ActiveSupport::TestCase assert p4.is_a_friend?(p1) assert_equal true, p1.follows?(p1) - assert_equal true, p1.follows?(p2) - assert_equal true, p1.follows?(p4) - assert_equal false, p1.follows?(p3) + assert_equal true, p2.follows?(p1) + assert_equal true, p4.follows?(p1) + assert_equal false, p3.follows?(p1) end should "a person member of a community follows the community" do @@ -836,18 +836,18 @@ class PersonTest < ActiveSupport::TestCase assert_nil Scrap.find_by(id: scrap.id) end - should "the tracked action be notified to person friends and herself" do + should "the tracked action be notified to person followers and herself" do Person.destroy_all p1 = fast_create(Person) p2 = fast_create(Person) p3 = fast_create(Person) p4 = fast_create(Person) - p1.add_friend(p2) - assert p1.is_a_friend?(p2) - refute p1.is_a_friend?(p3) - p1.add_friend(p4) - assert p1.is_a_friend?(p4) + p2.follow(p1) + assert p2.follows?(p1) + refute p3.follows?(p1) + p4.follow(p1) + assert p4.follows?(p1) action_tracker = fast_create(ActionTracker::Record, :user_id => p1.id) ActionTrackerNotification.delete_all @@ -880,17 +880,17 @@ class PersonTest < ActiveSupport::TestCase end end - should "the tracked action notify friends with one delayed job process" do + should "the tracked action notify followers with one delayed job process" do p1 = fast_create(Person) p2 = fast_create(Person) p3 = fast_create(Person) p4 = fast_create(Person) - p1.add_friend(p2) - assert p1.is_a_friend?(p2) - refute p1.is_a_friend?(p3) - p1.add_friend(p4) - assert p1.is_a_friend?(p4) + p2.follow(p1) + assert p2.follows?(p1) + refute p3.follows?(p1) + p4.follow(p1) + assert p4.follows?(p1) action_tracker = fast_create(ActionTracker::Record, :user_id => p1.id) @@ -1035,11 +1035,13 @@ class PersonTest < ActiveSupport::TestCase p2 = create_user('p2').person p3 = create_user('p3').person c = fast_create(Community, :name => "Foo") + c.add_member(p1) process_delayed_job_queue c.add_member(p3) process_delayed_job_queue - assert_equal 4, ActionTracker::Record.count + + assert_equal 5, ActionTracker::Record.count assert_equal 5, ActionTrackerNotification.count has_add_member_notification = false ActionTrackerNotification.all.map do |notification| diff --git a/test/unit/profile_followers_test.rb b/test/unit/profile_followers_test.rb new file mode 100644 index 0000000..e3fe36c --- /dev/null +++ b/test/unit/profile_followers_test.rb @@ -0,0 +1,68 @@ +require_relative "../test_helper" + +class ProfileFollowersTest < ActiveSupport::TestCase + + should 'a person follow another' do + p1 = create_user('person_test').person + p2 = create_user('person_test_2').person + + assert_difference 'ProfileFollower.count' do + p1.follow(p2) + end + + assert_includes p2.followers(true), p1 + assert_not_includes p1.followers(true), p2 + end + + should 'a person unfollow another person' do + p1 = create_user('person_test').person + p2 = create_user('person_test_2').person + + p1.follow(p2) + + assert_difference 'ProfileFollower.count', -1 do + p1.unfollow(p2) + end + + assert_not_includes p2.followers(true), p1 + end + + should 'get the followed persons for a profile' do + p1 = create_user('person_test').person + p2 = create_user('person_test_2').person + p3 = create_user('person_test_3').person + + p1.follow(p2) + p1.follow(p3) + + assert_equivalent p1.following_profiles, [p2,p3] + assert_equivalent Profile.following_profiles(p1), [p2,p3] + end + + should 'not follow same person twice' do + p1 = create_user('person_test').person + p2 = create_user('person_test_2').person + + assert_difference 'ProfileFollower.count' do + p1.follow(p2) + p1.follow(p2) + end + + assert_equivalent p1.following_profiles, [p2] + assert_equivalent p2.followers, [p1] + end + + should 'show the correct message when a profile is followed by the same person' do + p1 = create_user('person_test').person + p2 = create_user('person_test_2').person + + p1.follow(p2) + profile_follower = ProfileFollower.new + profile_follower.follower = p1 + profile_follower.profile = p2 + profile_follower.valid? + + assert_includes profile_follower.errors.messages[:profile_id], + "can't follow the same profile twice" + end +end diff --git a/test/unit/scrap_test.rb b/test/unit/scrap_test.rb index fb68094..49e5c20 100644 --- a/test/unit/scrap_test.rb +++ b/test/unit/scrap_test.rb @@ -125,11 +125,11 @@ class ScrapTest < ActiveSupport::TestCase assert_equal c, ta.target end - should "notify leave_scrap action tracker verb to friends and itself" do + should "notify leave_scrap action tracker verb to followers and itself" do User.current = create_user p1 = User.current.person p2 = create_user.person - p1.add_friend(p2) + p2.add_friend(p1) process_delayed_job_queue s = Scrap.new s.sender= p1 @@ -180,11 +180,11 @@ class ScrapTest < ActiveSupport::TestCase assert_equal p, ta.user end - should "notify leave_scrap_to_self action tracker verb to friends and itself" do + should "notify leave_scrap_to_self action tracker verb to followers and itself" do User.current = create_user p1 = User.current.person p2 = create_user.person - p1.add_friend(p2) + p2.add_friend(p1) ActionTrackerNotification.delete_all Delayed::Job.delete_all s = Scrap.new -- libgit2 0.21.2