Commit 9701ad866bdf8dd8646d3d45e9525f89035d9155

Authored by Daniela Feitosa
1 parent d8b660b2

profile-suggestions: job to generate suggestions

  * Added rules for suggestions
  * Send e-mail to users with the suggestions

(ActionItem3234)
app/mailers/user_mailer.rb
... ... @@ -41,6 +41,23 @@ class UserMailer < ActionMailer::Base
41 41 )
42 42 end
43 43  
  44 + def profiles_suggestions_email(user)
  45 + @recipient = user.name
  46 + @environment = user.environment.name
  47 + @url = user.environment.top_url
  48 + @people_suggestions_url = user.people_suggestions_url
  49 + @people_suggestions = user.suggested_people.sample(3)
  50 + @communities_suggestions_url = user.communities_suggestions_url
  51 + @communities_suggestions = user.suggested_communities.sample(3)
  52 +
  53 + mail(
  54 + content_type: 'text/html',
  55 + to: user.email,
  56 + from: "#{user.environment.name} <#{user.environment.contact_email}>",
  57 + subject: _("[%s] We have suggestions for your network") % user.environment.name
  58 + )
  59 + end
  60 +
44 61 class Job < Struct.new(:user, :method)
45 62 def perform
46 63 UserMailer.send(method, user).deliver
... ...
app/models/profile.rb
... ... @@ -513,6 +513,14 @@ class Profile &lt; ActiveRecord::Base
513 513 generate_url(:profile => identifier, :controller => 'profile', :action => 'index')
514 514 end
515 515  
  516 + def people_suggestions_url
  517 + generate_url(:profile => identifier, :controller => 'friends', :action => 'suggest')
  518 + end
  519 +
  520 + def communities_suggestions_url
  521 + generate_url(:profile => identifier, :controller => 'memberships', :action => 'suggest')
  522 + end
  523 +
516 524 def generate_url(options)
517 525 url_options.merge(options)
518 526 end
... ...
app/models/profile_suggestion.rb
... ... @@ -29,6 +29,119 @@ class ProfileSuggestion &lt; ActiveRecord::Base
29 29 settings_items category.to_sym
30 30 attr_accessible category.to_sym
31 31 end
  32 +
  33 + RULES = %w[
  34 + friends_of_friends_with_common_friends
  35 + people_with_common_communities
  36 + people_with_common_tags
  37 + communities_with_common_friends
  38 + communities_with_common_tags
  39 + ]
  40 +
  41 + # Number of suggestions
  42 + N_SUGGESTIONS = 30
  43 +
  44 + # Number max of attempts
  45 + MAX_ATTEMPTS = N_SUGGESTIONS * 2
  46 +
  47 + # Number of friends in common
  48 + COMMON_FRIENDS = 2
  49 +
  50 + # Number of communities in common
  51 + COMMON_COMMUNITIES = 1
  52 +
  53 + # Number of friends in common
  54 + COMMON_TAGS = 2
  55 +
  56 + def self.friends_of_friends_with_common_friends(person)
  57 + person_attempts = 0
  58 + person_friends = person.friends
  59 + person_friends.each do |friend|
  60 + friend.friends.includes.each do |friend_of_friend|
  61 + person_attempts += 1
  62 + return unless person.profile_suggestions.count < N_SUGGESTIONS && person_attempts < MAX_ATTEMPTS
  63 + unless friend_of_friend == person || friend_of_friend.is_a_friend?(person) || person.already_request_friendship?(friend_of_friend)
  64 + common_friends = friend_of_friend.friends & person_friends
  65 + if common_friends.size >= COMMON_FRIENDS
  66 + person.profile_suggestions.create(:suggestion => friend_of_friend, :common_friends => common_friends.size)
  67 + end
  68 + end
  69 + end
  70 + end
  71 + end
  72 +
  73 + def self.people_with_common_communities(person)
  74 + person_attempts = 0
  75 + person_communities = person.communities
  76 + person_communities.each do |community|
  77 + community.members.each do |member|
  78 + person_attempts += 1
  79 + return unless person.profile_suggestions.count < N_SUGGESTIONS && person_attempts < MAX_ATTEMPTS
  80 + unless member == person || member.is_a_friend?(person) || person.already_request_friendship?(member)
  81 + common_communities = person_communities & member.communities
  82 + if common_communities.size >= COMMON_COMMUNITIES
  83 + person.profile_suggestions.create(:suggestion => member, :common_communities => common_communities.size)
  84 + end
  85 + end
  86 + end
  87 + end
  88 + end
  89 +
  90 + def self.people_with_common_tags(person)
  91 + person_attempts = 0
  92 + tags = person.article_tags.keys
  93 + tags.each do |tag|
  94 + person_attempts += 1
  95 + return unless person.profile_suggestions.count < N_SUGGESTIONS && person_attempts < MAX_ATTEMPTS
  96 + tagged_content = ActsAsTaggableOn::Tagging.includes(:taggable).where(:tag_id => ActsAsTaggableOn::Tag.find_by_name(tag))
  97 + tagged_content.each do |tg|
  98 + author = tg.taggable.created_by
  99 + unless author.nil? || author == person || author.is_a_friend?(person) || person.already_request_friendship?(author)
  100 + common_tags = tags & author.article_tags.keys
  101 + if common_tags.size >= COMMON_TAGS
  102 + person.profile_suggestions.create(:suggestion => author, :common_tags => common_tags.size)
  103 + end
  104 + end
  105 + end
  106 + end
  107 + end
  108 +
  109 + def self.communities_with_common_friends(person)
  110 + community_attempts = 0
  111 + person_friends = person.friends
  112 + person_friends.each do |friend|
  113 + friend.communities.each do |community|
  114 + community_attempts += 1
  115 + return unless person.profile_suggestions.count < N_SUGGESTIONS && community_attempts < MAX_ATTEMPTS
  116 + unless person.is_member_of?(community) || community.already_request_membership?(person)
  117 + common_friends = community.members & person.friends
  118 + if common_friends.size >= COMMON_FRIENDS
  119 + person.profile_suggestions.create(:suggestion => community, :common_friends => common_friends.size)
  120 + end
  121 + end
  122 + end
  123 + end
  124 + end
  125 +
  126 + def self.communities_with_common_tags(person)
  127 + community_attempts = 0
  128 + tags = person.article_tags.keys
  129 + tags.each do |tag|
  130 + community_attempts += 1
  131 + return unless person.profile_suggestions.count < N_SUGGESTIONS && community_attempts < MAX_ATTEMPTS
  132 + tagged_content = ActsAsTaggableOn::Tagging.includes(:taggable).where(:tag_id => ActsAsTaggableOn::Tag.find_by_name(tag))
  133 + tagged_content.each do |tg|
  134 + profile = tg.taggable.profile
  135 + unless !profile.community? || person.is_member_of?(profile) || profile.already_request_membership?(person)
  136 + common_tags = tags & profile.article_tags.keys
  137 + if common_tags.size >= COMMON_TAGS
  138 + person.profile_suggestions.create(:suggestion => profile, :common_tags => common_tags.size)
  139 + end
  140 + end
  141 + end
  142 + end
  143 + end
  144 +
32 145 def disable
33 146 self.enabled = false
34 147 self.save
... ...
app/views/user_mailer/profiles_suggestions_email.html.erb 0 → 100644
... ... @@ -0,0 +1,32 @@
  1 +<%= _('Hi, %{recipient}!') % { :recipient => @recipient } %>
  2 +
  3 +<p><%= _('We want to give you some suggestions to grow up your network. Check it out!') %></p>
  4 +
  5 +<% unless @people_suggestions.empty? %>
  6 + <p><%= _('Friends suggestions:') %></p>
  7 +
  8 + <ul>
  9 + <% @people_suggestions.each do |person| %>
  10 + <li><a href="<%= url_for(person.public_profile_url) %>"><%= person.name %></a></li>
  11 + <% end %>
  12 + <ul>
  13 + <%= _("To see the full list of friends suggestions, follow the link: %s") % @people_suggestions_url %>
  14 +<% end %>
  15 +
  16 +<% unless @communities_suggestions.empty? %>
  17 + <p><%= _('Communities suggestions:') %></p>
  18 +
  19 + <ul>
  20 + <% @communities_suggestions.each do |community| %>
  21 + <li><a href="<%= url_for(community.public_profile_url) %>"><%= community.name %></a></li>
  22 + <% end %>
  23 + <ul>
  24 + <%= _("To see the full list of communities suggestions, follow the link: %s") % @communities_suggestions_url %>
  25 +<% end %>
  26 +
  27 +
  28 +<%= _("Greetings,") %>
  29 +
  30 +--
  31 +<%= _('%s team.') % @environment %>
  32 +<%= @url %>
... ...
lib/profile_suggestions_job.rb 0 → 100644
... ... @@ -0,0 +1,17 @@
  1 +class ProfileSuggestionsJob < Struct.new(:person_id)
  2 +
  3 + def perform
  4 + begin
  5 + person = Person.find(person_id)
  6 +
  7 + ProfileSuggestion::RULES.each do |rule|
  8 + ProfileSuggestion.send(rule, person)
  9 + end
  10 +
  11 + UserMailer.profiles_suggestions_email(person).deliver
  12 + rescue Exception => exception
  13 + Rails.logger.warn("Error with suggestions for person ID %d\n%s" % [person_id, exception.to_s])
  14 + end
  15 + end
  16 +
  17 +end
... ...
test/unit/person_test.rb
... ... @@ -1521,4 +1521,16 @@ class PersonTest &lt; ActiveSupport::TestCase
1521 1521 assert_equal [suggested_community], person.suggested_communities
1522 1522 end
1523 1523  
  1524 + should 'return url to people suggestions for a person' do
  1525 + environment = create_environment('mycolivre.net')
  1526 + profile = build(Person, :identifier => 'testprofile', :environment_id => create_environment('mycolivre.net').id)
  1527 + assert_equal({ :host => "mycolivre.net", :profile => 'testprofile', :controller => 'friends', :action => 'suggest' }, profile.people_suggestions_url)
  1528 + end
  1529 +
  1530 + should 'return url to communities suggestions for a person' do
  1531 + environment = create_environment('mycolivre.net')
  1532 + profile = build(Person, :identifier => 'testprofile', :environment_id => create_environment('mycolivre.net').id)
  1533 + assert_equal({ :host => "mycolivre.net", :profile => 'testprofile', :controller => 'memberships', :action => 'suggest' }, profile.communities_suggestions_url)
  1534 + end
  1535 +
1524 1536 end
... ...
test/unit/profile_suggestions_job_test.rb 0 → 100644
... ... @@ -0,0 +1,108 @@
  1 +require File.dirname(__FILE__) + '/../test_helper'
  2 +
  3 +class ProfileSuggestionsJobTest < ActiveSupport::TestCase
  4 +
  5 + should 'suggest friends from friends' do
  6 + person = create_user('person').person
  7 + friend = create_user('friend').person
  8 + friend2 = create_user('friend2').person
  9 + friend3 = create_user('friend3').person
  10 +
  11 + person.add_friend friend
  12 + friend.add_friend person
  13 +
  14 + person.add_friend friend2
  15 + friend2.add_friend person
  16 +
  17 + person.add_friend friend3
  18 + friend3.add_friend person
  19 +
  20 + friend_of_friend = create_user('friend_of_friend').person
  21 + friend.add_friend friend_of_friend
  22 + friend_of_friend.add_friend friend
  23 +
  24 + friend_of_friend.add_friend friend2
  25 + friend2.add_friend friend_of_friend
  26 +
  27 + friend_of_friend.add_friend friend3
  28 + friend3.add_friend friend_of_friend
  29 +
  30 + job = ProfileSuggestionsJob.new(person.id)
  31 + assert_difference 'ProfileSuggestion.count', 1 do
  32 + job.perform
  33 + end
  34 + assert_equal [friend_of_friend], person.suggested_people
  35 + end
  36 +
  37 + should 'suggest friends from communities' do
  38 + person = create_user('person').person
  39 + community = fast_create(Community)
  40 +
  41 + member1 = create_user('member1').person
  42 + member2 = create_user('member2').person
  43 +
  44 + community.add_member person
  45 + community.add_member member1
  46 + community.add_member member2
  47 +
  48 + job = ProfileSuggestionsJob.new(person.id)
  49 + assert_difference 'ProfileSuggestion.count', 2 do
  50 + job.perform
  51 + end
  52 + assert_equivalent [member1, member2], person.suggested_people
  53 + end
  54 +
  55 + should 'suggest friends from tags' do
  56 + person = create_user('person').person
  57 + person2 = create_user('person2').person
  58 + person3 = create_user('person3').person
  59 +
  60 + create(Article, :created_by => person, :profile => person, :tag_list => 'first-tag, second-tag')
  61 + create(Article, :created_by => person2, :profile => person2, :tag_list => 'first-tag, second-tag, third-tag')
  62 + create(Article, :created_by => person3, :profile => person3, :tag_list => 'first-tag')
  63 +
  64 + job = ProfileSuggestionsJob.new(person.id)
  65 + assert_difference 'ProfileSuggestion.count', 1 do
  66 + job.perform
  67 + end
  68 + assert_equivalent [person2], person.suggested_people
  69 + end
  70 +
  71 + should 'suggest from communities friends' do
  72 + person = create_user('person').person
  73 +
  74 + member1 = create_user('member1').person
  75 + member2 = create_user('member2').person
  76 +
  77 + person.add_friend member1
  78 + person.add_friend member2
  79 +
  80 + community = fast_create(Community)
  81 + community.add_member member1
  82 + community.add_member member2
  83 +
  84 + job = ProfileSuggestionsJob.new(person.id)
  85 + assert_difference 'ProfileSuggestion.count', 1 do
  86 + job.perform
  87 + end
  88 + assert_equivalent [community], person.suggested_communities
  89 + end
  90 +
  91 + should 'suggest communities from tags' do
  92 + person = create_user('person').person
  93 + person2 = create_user('person2').person
  94 +
  95 + community = fast_create(Community)
  96 + community.add_admin person2
  97 +
  98 + create(Article, :created_by => person, :profile => person, :tag_list => 'first-tag, second-tag')
  99 + create(Article, :created_by => person2, :profile => community, :tag_list => 'first-tag, second-tag, third-tag')
  100 +
  101 + job = ProfileSuggestionsJob.new(person.id)
  102 + assert_difference 'ProfileSuggestion.count', 1 do
  103 + job.perform
  104 + end
  105 + assert_equivalent [community], person.suggested_communities
  106 + end
  107 +
  108 +end
... ...
test/unit/profile_test.rb
... ... @@ -1955,4 +1955,14 @@ class ProfileTest &lt; ActiveSupport::TestCase
1955 1955 p = fast_create(Profile)
1956 1956 assert p.folder_types.include?('ProfileTest::Folder1')
1957 1957 end
  1958 +
  1959 + should 'disable suggestion if profile requested membership' do
  1960 + person = fast_create(Person)
  1961 + community = fast_create(Community)
  1962 + suggestion = ProfileSuggestion.create(:person => person, :suggestion => community, :enabled => true)
  1963 +
  1964 + community.add_member person
  1965 + assert_equal false, ProfileSuggestion.find(suggestion.id).enabled
  1966 + end
  1967 +
1958 1968 end
... ...