Commit 9701ad866bdf8dd8646d3d45e9525f89035d9155
1 parent
d8b660b2
Exists in
master
and in
27 other branches
profile-suggestions: job to generate suggestions
* Added rules for suggestions * Send e-mail to users with the suggestions (ActionItem3234)
Showing
8 changed files
with
317 additions
and
0 deletions
Show diff stats
app/mailers/user_mailer.rb
@@ -41,6 +41,23 @@ class UserMailer < ActionMailer::Base | @@ -41,6 +41,23 @@ class UserMailer < ActionMailer::Base | ||
41 | ) | 41 | ) |
42 | end | 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 | class Job < Struct.new(:user, :method) | 61 | class Job < Struct.new(:user, :method) |
45 | def perform | 62 | def perform |
46 | UserMailer.send(method, user).deliver | 63 | UserMailer.send(method, user).deliver |
app/models/profile.rb
@@ -513,6 +513,14 @@ class Profile < ActiveRecord::Base | @@ -513,6 +513,14 @@ class Profile < ActiveRecord::Base | ||
513 | generate_url(:profile => identifier, :controller => 'profile', :action => 'index') | 513 | generate_url(:profile => identifier, :controller => 'profile', :action => 'index') |
514 | end | 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 | def generate_url(options) | 524 | def generate_url(options) |
517 | url_options.merge(options) | 525 | url_options.merge(options) |
518 | end | 526 | end |
app/models/profile_suggestion.rb
@@ -29,6 +29,119 @@ class ProfileSuggestion < ActiveRecord::Base | @@ -29,6 +29,119 @@ class ProfileSuggestion < ActiveRecord::Base | ||
29 | settings_items category.to_sym | 29 | settings_items category.to_sym |
30 | attr_accessible category.to_sym | 30 | attr_accessible category.to_sym |
31 | end | 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 | def disable | 145 | def disable |
33 | self.enabled = false | 146 | self.enabled = false |
34 | self.save | 147 | self.save |
app/views/user_mailer/profiles_suggestions_email.html.erb
0 → 100644
@@ -0,0 +1,32 @@ | @@ -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 %> |
@@ -0,0 +1,17 @@ | @@ -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 < ActiveSupport::TestCase | @@ -1521,4 +1521,16 @@ class PersonTest < ActiveSupport::TestCase | ||
1521 | assert_equal [suggested_community], person.suggested_communities | 1521 | assert_equal [suggested_community], person.suggested_communities |
1522 | end | 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 | end | 1536 | end |
@@ -0,0 +1,108 @@ | @@ -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 < ActiveSupport::TestCase | @@ -1955,4 +1955,14 @@ class ProfileTest < ActiveSupport::TestCase | ||
1955 | p = fast_create(Profile) | 1955 | p = fast_create(Profile) |
1956 | assert p.folder_types.include?('ProfileTest::Folder1') | 1956 | assert p.folder_types.include?('ProfileTest::Folder1') |
1957 | end | 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 | end | 1968 | end |