Commit 9701ad866bdf8dd8646d3d45e9525f89035d9155
1 parent
d8b660b2
Exists in
master
and in
29 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 | 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 < 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 < 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 %> | ... | ... |
... | ... | @@ -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 | 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 | ... | ... |
... | ... | @@ -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 | 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 | ... | ... |