Commit be66917b09dd234c35e2ed493bcf4bc63089627b

Authored by Rodrigo Souto
2 parents f2aaabd6 3cdf7126

Merge branch 'master_profile_members_filter' into 'master'

Added filter section on profile members list

- Adds use of filtered members to mailing queue executed by send_mail action

Signed-off-by: Gustavo Jaruga <darksshades@gmail.com>
Signed-off-by: Marcos Ronaldo <marcos.rpj2@gmail.com>
Signed-off-by: Michel Felipe de Oliveira Ferreira <michel.ferreira@serpro.gov.br>

See merge request !800
app/controllers/my_profile/profile_members_controller.rb
... ... @@ -2,8 +2,26 @@ class ProfileMembersController &lt; MyProfileController
2 2 protect 'manage_memberships', :profile
3 3  
4 4 def index
5   - @members = profile.members_by_name
6   - @member_role = environment.roles.find_by_name('member')
  5 + @filters = params[:filters] || {:roles => []}
  6 + @filters[:roles] = [] unless @filters[:roles]
  7 + @data = {}
  8 + field = 'name'
  9 + field = 'email' if @filters[:name] =~ /\@/
  10 +
  11 + @data[:members] = profile.members_by(field,@filters[:name]).by_role(@filters[:roles])
  12 + session[:members_filtered] = @data[:members].map{|m|m.id} if request.post?
  13 + @data[:roles] = Profile::Roles.organization_member_roles(environment.id) | Profile::Roles.organization_custom_roles(environment.id, profile.id)
  14 +
  15 + end
  16 +
  17 + def send_mail
  18 + session[:members_filtered] = params[:members_filtered].select{|value| value!="0"}
  19 + if session[:members_filtered].present?
  20 + redirect_to :controller => :profile, :action => :send_mail
  21 + else
  22 + session[:notice] = _("Select at least one member.")
  23 + redirect_to :action => :index
  24 + end
7 25 end
8 26  
9 27 def update_roles
... ... @@ -156,4 +174,13 @@ class ProfileMembersController &lt; MyProfileController
156 174 end
157 175 end
158 176  
  177 + def search_members
  178 + field = 'name'
  179 + field = 'email' if params[:filter_name] =~ /\@/
  180 +
  181 + result = profile.members_like field, params[:filter_name]
  182 + result = result.select{|member| member.can_view_field?(current_person, "email") } if field=="email"
  183 + render :json => result.map { |member| {:label => "#{member.name}#{member.can_view_field?(current_person, "email") ? " <#{member.email}>" : ""}", :value => member.name }}
  184 + end
  185 +
159 186 end
... ...
app/controllers/public/profile_controller.rb
... ... @@ -370,6 +370,7 @@ class ProfileController &lt; PublicController
370 370  
371 371 def send_mail
372 372 @mailing = profile.mailings.build(params[:mailing])
  373 + @mailing.data = session[:members_filtered] ? {:members_filtered => session[:members_filtered]} : {}
373 374 if request.post?
374 375 @mailing.locale = locale
375 376 @mailing.person = user
... ...
app/helpers/forms_helper.rb
... ... @@ -7,9 +7,10 @@ module FormsHelper
7 7  
8 8 def labelled_check_box( human_name, name, value = "1", checked = false, options = {} )
9 9 options[:id] ||= 'checkbox-' + FormsHelper.next_id_number
10   - hidden_field_tag(name, '0') +
11   - check_box_tag( name, value, checked, options ) +
12   - content_tag( 'label', human_name, :for => options[:id] )
  10 + html = options[:add_hidden] == false ? "" : hidden_field_tag(name, '0')
  11 +
  12 + html += check_box_tag( name, value, checked, options ) +
  13 + content_tag( 'label', human_name, :for => options[:id] )
13 14 end
14 15  
15 16 def labelled_text_field( human_name, name, value=nil, options={} )
... ...
app/mailers/mailing.rb
... ... @@ -2,7 +2,10 @@ require_dependency &#39;mailing_job&#39;
2 2  
3 3 class Mailing < ActiveRecord::Base
4 4  
5   - attr_accessible :subject, :body
  5 + acts_as_having_settings :field => :data
  6 +
  7 + attr_accessible :subject, :body, :data
  8 +
6 9 validates_presence_of :source_id, :subject, :body
7 10 belongs_to :source, :foreign_key => :source_id, :polymorphic => true
8 11 belongs_to :person
... ...
app/mailers/organization_mailing.rb
... ... @@ -5,9 +5,17 @@ class OrganizationMailing &lt; Mailing
5 5 end
6 6  
7 7 def recipients(offset=0, limit=100)
8   - source.members.order(:id).offset(offset).limit(limit)
9   - .joins("LEFT OUTER JOIN mailing_sents m ON (m.mailing_id = #{id} AND m.person_id = profiles.id)")
  8 + result = source.members.order(:id).offset(offset).limit(limit)
  9 +
  10 + if data.present? and data.is_a?(Hash) and data[:members_filtered]
  11 + result = result.where('profiles.id IN (?)', data[:members_filtered])
  12 + end
  13 +
  14 + if result.blank?
  15 + result = result.joins("LEFT OUTER JOIN mailing_sents m ON (m.mailing_id = #{id} AND m.person_id = profiles.id)")
10 16 .where("m.person_id" => nil)
  17 + end
  18 + result
11 19 end
12 20  
13 21 def each_recipient
... ...
app/models/person.rb
... ... @@ -16,10 +16,13 @@ class Person &lt; Profile
16 16 acts_as_trackable :after_add => Proc.new {|p,t| notify_activity(t)}
17 17 acts_as_accessor
18 18  
19   - scope :members_of, -> resources {
  19 + scope :members_of, lambda { |resources, field = ''|
20 20 resources = Array(resources)
  21 + joins = [:role_assignments]
  22 + joins << :user if User.attribute_names.include? field
  23 +
21 24 conditions = resources.map {|resource| "role_assignments.resource_type = '#{resource.class.base_class.name}' AND role_assignments.resource_id = #{resource.id || -1}"}.join(' OR ')
22   - distinct.select('profiles.*').joins(:role_assignments).where([conditions])
  25 + select('DISTINCT profiles.*').joins(joins).where([conditions])
23 26 }
24 27  
25 28 scope :not_members_of, -> resources {
... ... @@ -48,6 +51,14 @@ class Person &lt; Profile
48 51 ['( roles.key = ? AND role_assignments.accessor_type = ? AND role_assignments.accessor_id = ? ) OR (
49 52 ( ( friendships.person_id = ? ) OR (profiles.public_profile = ?)) AND (profiles.visible = ?) )', 'environment_administrator', Profile.name, person.id, person.id, true, true]
50 53 ).uniq
  54 + }
  55 + scope :by_role, lambda { |roles|
  56 +
  57 + roles = [roles] unless roles.kind_of?(Array)
  58 +
  59 + if roles.length > 0
  60 + {:select => 'DISTINCT profiles.*', :joins => :role_assignments, :conditions => ['role_assignments.role_id IN (?)', roles] }
  61 + end
51 62 }
52 63  
53 64  
... ...
app/models/profile.rb
... ... @@ -49,6 +49,9 @@ class Profile &lt; ActiveRecord::Base
49 49 def self.organization_member_roles(env_id)
50 50 all_roles(env_id).select{ |r| r.key.match(/^profile_/) unless r.key.blank? || !r.profile_id.nil?}
51 51 end
  52 + def self.organization_custom_roles(env_id, profile_id)
  53 + all_roles(env_id).where('profile_id = ?', profile_id)
  54 + end
52 55 def self.all_roles(env_id)
53 56 Role.where(environment_id: env_id)
54 57 end
... ... @@ -155,15 +158,23 @@ class Profile &lt; ActiveRecord::Base
155 158  
156 159 include TimeScopes
157 160  
158   - def members
  161 + def members(by_field = '')
159 162 scopes = plugins.dispatch_scopes(:organization_members, self)
160   - scopes << Person.members_of(self)
  163 + scopes << Person.members_of(self,by_field)
161 164 return scopes.first if scopes.size == 1
162 165 ScopeTool.union *scopes
163 166 end
164 167  
165   - def members_by_name
166   - members.order('profiles.name')
  168 + def members_by(field,value = nil)
  169 + if value and !value.blank?
  170 + members_like(field,value).order('profiles.name')
  171 + else
  172 + members.order('profiles.name')
  173 + end
  174 + end
  175 +
  176 + def members_like(field,value)
  177 + members(field).where("LOWER(#{field}) LIKE ?", "%#{value.downcase}%") if value
167 178 end
168 179  
169 180 class << self
... ... @@ -1098,6 +1109,10 @@ private :generate_url, :url_options
1098 1109 end
1099 1110 end
1100 1111  
  1112 + def can_view_field? current_person, field
  1113 + display_private_info_to?(current_person) || (public_fields.include?(field) && public?)
  1114 + end
  1115 +
1101 1116 validates_inclusion_of :redirection_after_login, :in => Environment.login_redirection_options.keys, :allow_nil => true
1102 1117 def preferred_login_redirection
1103 1118 redirection_after_login.blank? ? environment.redirection_after_login : redirection_after_login
... ...
app/views/profile/send_mail.html.erb
... ... @@ -4,6 +4,9 @@
4 4  
5 5 <%= error_messages_for :mailing %>
6 6  
  7 + <% to = @mailing.data[:members_filtered].present? ? @mailing.recipients.map{|r| r.name}.join(', ') : _('All members')%>
  8 + <%= labelled_form_field(_('To:'), text_area(:data, 'members_filtered', :value => to, :rows => 4, :disabled => 'disabled', :class => 'send-mail-recipients')) %>
  9 +
7 10 <%= form_for :mailing, :url => {:action => 'send_mail'}, :html => {:id => 'mailing-form'} do |f| %>
8 11  
9 12 <%= labelled_form_field(_('Subject:'), f.text_field(:subject)) %>
... ...
app/views/profile_members/_index_buttons.html.erb
... ... @@ -5,7 +5,7 @@
5 5 <%= button :person, _('Invite people to join'), :controller => 'invite', :action => 'invite_friends' %>
6 6 <% end %>
7 7 <% if profile.community? and user.has_permission?(:send_mail_to_members, profile) %>
8   - <%= button :send, _('Send e-mail to members'), :controller => 'profile', :action => 'send_mail' %>
  8 + <%= submit_button(:send, _('Send e-mail to members')) %>
9 9 <% end %>
10 10 <% @plugins.dispatch(:manage_members_extra_buttons).each do |plugin_button| %>
11 11 <%= button plugin_button[:icon], plugin_button[:title], plugin_button[:url] %>
... ...
app/views/profile_members/_members_filter.erb 0 → 100644
... ... @@ -0,0 +1,18 @@
  1 +<%= form_tag '#', :method => 'post' do %>
  2 +
  3 + <%= field_set_tag _('Filter'), :class => 'filter_fields' do %>
  4 + <p>
  5 + <%= labelled_text_field(_('Name or Email')+': ', "filters[name]", @filters[:name], {:id => 'filter-name-autocomplete',:size => 30}) %>
  6 + </p>
  7 +
  8 + <p><%= _('Roles:') %> </p>
  9 + <% @data[:roles].each do |r| %>
  10 + <%= labelled_check_box(r.name, 'filters[roles][]', r.id, @filters[:roles].include?(r.id.to_s), :add_hidden => false) %><br/>
  11 + <% end %>
  12 + <p>
  13 + <%= submit_button(:search, _('Search')) %>
  14 + </p>
  15 + <% end %>
  16 +<% end %>
  17 +
  18 +<%= javascript_include_tag params[:controller] %>
0 19 \ No newline at end of file
... ...
app/views/profile_members/_members_list.html.erb
1   -<% collection = @collection == :profile_admins ? profile.admins : profile.members_by_name %>
  1 +<% members = @data ? @data[:members] : profile.members_by('name') %>
  2 +<% collection = @collection == :profile_admins ? profile.admins : members %>
2 3 <% title = @title ? @title : _('Current members') %>
3 4 <% remove_action = @remove_action ? @remove_action : {:action => 'unassociate'} %>
  5 +<%= javascript_include_tag params[:controller] %>
4 6  
5 7 <h3><%= title %></h3>
6 8  
7 9 <table>
  10 + <col width="1">
8 11 <tr>
  12 + <th><%= check_box_tag 'checkbox-all', 1, false, :onClick => "toggle(this)" %></th>
9 13 <th><%= _('Member') %></th>
10 14 <th><%= _('Actions') %></th>
11 15 </tr>
  16 +
12 17 <% collection.each do |m| %>
13 18 <tr title="<%= m.name %>">
  19 + <td><%= labelled_check_box('', 'members_filtered[]', m.id.to_s, false, :id => 'checkbox-'+m.identifier) %></td>
14 20 <td><%= link_to_profile m.short_name, m.identifier, :title => m.name %> </td>
15 21 <td>
16 22 <div class="members-buttons-cell">
... ... @@ -26,3 +32,8 @@
26 32 </tr>
27 33 <% end %>
28 34 </table>
  35 +<% if collection.empty? %>
  36 + <p>
  37 + <em><%= _('No members found to: %s') % profile.name %></em>
  38 + </p>
  39 +<% end %>
... ...
app/views/profile_members/index.html.erb
1 1 <h1><%= h profile.short_name(50) %></h1>
2 2  
3   -<%= render :partial => 'index_buttons' %>
  3 +<%= render :partial => 'members_filter' %>
4 4  
5   -<div id="members-list">
6   - <%= render :partial => 'members_list' %>
7   -</div>
  5 +<%= form_tag 'profile_members/send_mail', :method => 'post' do %>
  6 + <div id="members-list">
  7 + <%= render :partial => 'members_list' %>
  8 + </div>
8 9  
9   -<%= render :partial => 'index_buttons' %>
  10 + <%= render :partial => 'index_buttons' %>
  11 +
  12 +<% end %>
... ...
db/migrate/20160224132937_add_data_to_mailing.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +class AddDataToMailing < ActiveRecord::Migration
  2 + def change
  3 + add_column :mailings, :data, :text
  4 + end
  5 +end
... ...
db/schema.rb
... ... @@ -11,7 +11,7 @@
11 11 #
12 12 # It's strongly recommended that you check this file into your version control system.
13 13  
14   -ActiveRecord::Schema.define(version: 20160202142247) do
  14 +ActiveRecord::Schema.define(version: 20160224132937) do
15 15  
16 16 # These are extensions that must be enabled in order to support this database
17 17 enable_extension "plpgsql"
... ... @@ -471,6 +471,7 @@ ActiveRecord::Schema.define(version: 20160202142247) do
471 471 t.string "locale"
472 472 t.datetime "created_at"
473 473 t.datetime "updated_at"
  474 + t.text "data"
474 475 end
475 476  
476 477 create_table "national_region_types", force: :cascade do |t|
... ...
features/send_email_to_organization_members.feature
... ... @@ -31,7 +31,8 @@ Feature: send emails to organization members
31 31 Scenario: Send e-mail to members
32 32 Given I am logged in as "joaosilva"
33 33 And I go to Sample Community's members management
34   - And I follow "Send e-mail to members"
  34 + And I check "checkbox-manoel"
  35 + And I press "Send e-mail to members"
35 36 And I fill in "Subject" with "Hello, member!"
36 37 And I fill in "Body" with "We have some news"
37 38 When I press "Send"
... ... @@ -40,7 +41,8 @@ Feature: send emails to organization members
40 41 Scenario: Not send e-mail to members if subject is blank
41 42 Given I am logged in as "joaosilva"
42 43 And I go to Sample Community's members management
43   - And I follow "Send e-mail to members"
  44 + And I check "checkbox-manoel"
  45 + And I press "Send e-mail to members"
44 46 And I fill in "Body" with "We have some news"
45 47 When I press "Send"
46 48 Then I should be on /profile/sample-community/send_mail
... ... @@ -48,7 +50,8 @@ Feature: send emails to organization members
48 50 Scenario: Not send e-mail to members if body is blank
49 51 Given I am logged in as "joaosilva"
50 52 And I go to Sample Community's members management
51   - And I follow "Send e-mail to members"
  53 + And I check "checkbox-manoel"
  54 + And I press "Send e-mail to members"
52 55 And I fill in "Subject" with "Hello, user!"
53 56 When I press "Send"
54 57 Then I should be on /profile/sample-community/send_mail
... ... @@ -56,7 +59,8 @@ Feature: send emails to organization members
56 59 Scenario: Cancel creation of mailing
57 60 Given I am logged in as "joaosilva"
58 61 And I go to Sample Community's members management
59   - And I follow "Send e-mail to members"
  62 + And I check "checkbox-manoel"
  63 + And I press "Send e-mail to members"
60 64 When I follow "Cancel e-mail"
61 65 Then I should be on Sample Community's members management
62 66  
... ...
public/designs/themes/noosfero/style.css
... ... @@ -48,3 +48,9 @@
48 48 width: 80px;
49 49 }
50 50  
  51 +.action-profile-send_mail .send-mail-recipients {
  52 + color: #888888;
  53 + padding: 10px;
  54 + width: 475px;
  55 + line-height: 15px;
  56 +}
... ...
public/javascripts/profile_members.js 0 → 100644
... ... @@ -0,0 +1,25 @@
  1 +(function($) {
  2 +
  3 + //Autocomplete to list members
  4 + $('#filter-name-autocomplete').autocomplete({
  5 + minLength:2,
  6 + source:function(request,response){
  7 + $.ajax({
  8 + url:document.location.pathname+'/search_members',
  9 + dataType:'json',
  10 + data:{
  11 + filter_name:request.term
  12 + },
  13 + success:response
  14 + });
  15 + }
  16 + });
  17 +})(jQuery);
  18 +
  19 +
  20 +function toggle(source) {
  21 + checkboxes = document.getElementsByName('members_filtered[]');
  22 + for(var i=0, n=checkboxes.length;i<n;i++) {
  23 + checkboxes[i].checked = source.checked;
  24 + }
  25 +}
... ...
test/functional/profile_controller_test.rb
... ... @@ -1465,11 +1465,41 @@ class ProfileControllerTest &lt; ActionController::TestCase
1465 1465 create_user_with_permission('profile_moderator_user', 'send_mail_to_members', community)
1466 1466 login_as('profile_moderator_user')
1467 1467 @controller.stubs(:locale).returns('pt')
  1468 +
1468 1469 assert_difference 'Delayed::Job.count', 1 do
1469 1470 post :send_mail, :profile => community.identifier, :mailing => {:subject => 'Hello', :body => 'We have some news'}
1470 1471 end
1471 1472 end
1472 1473  
  1474 + should 'send to members_filtered if available' do
  1475 + community = fast_create(Community)
  1476 + create_user_with_permission('profile_moderator_user', 'send_mail_to_members', community)
  1477 + person = create_user('Any').person
  1478 + community.add_member(person)
  1479 + community.save!
  1480 + login_as('profile_moderator_user')
  1481 +
  1482 + post :send_mail, :profile => community.identifier, :mailing => {:subject => 'Hello', :body => 'We have some news'}
  1483 + assert_equivalent community.members, OrganizationMailing.last.recipients
  1484 +
  1485 + @request.session[:members_filtered] = [person.id]
  1486 + post :send_mail, :profile => community.identifier, :mailing => {:subject => 'RUN!!', :body => 'Run to the hills!!'}
  1487 + assert_equal [person], OrganizationMailing.last.recipients
  1488 + end
  1489 +
  1490 + should 'send email to all members if there is no valid member in members_filtered' do
  1491 + community = fast_create(Community)
  1492 + create_user_with_permission('profile_moderator_user', 'send_mail_to_members', community)
  1493 + person = create_user('Any').person
  1494 + community.add_member(person)
  1495 + community.save!
  1496 + login_as('profile_moderator_user')
  1497 +
  1498 + @request.session[:members_filtered] = [Profile.last.id+1]
  1499 + post :send_mail, :profile => community.identifier, :mailing => {:subject => 'RUN!!', :body => 'Run to the hills!!'}
  1500 + assert_empty OrganizationMailing.last.recipients
  1501 + end
  1502 +
1473 1503 should 'save mailing' do
1474 1504 community = fast_create(Community)
1475 1505 create_user_with_permission('profile_moderator_user', 'send_mail_to_members', community)
... ...
test/functional/profile_members_controller_test.rb
... ... @@ -31,6 +31,31 @@ class ProfileMembersControllerTest &lt; ActionController::TestCase
31 31 assert_template 'index'
32 32 end
33 33  
  34 + should 'access index and filter members by name and roles' do
  35 +
  36 + ent = fast_create(Enterprise, :identifier => 'test_enterprise', :name => 'test enterprise')
  37 + roles = {
  38 + :admin => Profile::Roles.admin(Environment.default),
  39 + :member => Profile::Roles.member(Environment.default)
  40 + }
  41 +
  42 + member = create_user('test_member', :email => 'testmember@test.com.br').person
  43 + member.add_role(roles[:member], ent)
  44 +
  45 + admin = create_user('test_admin').person
  46 + admin.add_role roles[:admin], ent
  47 +
  48 + user = create_user_with_permission('test_user', 'manage_memberships', ent)
  49 + login_as :test_user
  50 +
  51 + post :index, :profile => 'test_enterprise' , :filters => {:name => 'testmember@test.com.br', :roles => [roles[:member].id]}
  52 +
  53 + assert_response :success
  54 + assert_template 'index'
  55 +
  56 + assert_includes assigns(:data)[:members], member
  57 + end
  58 +
34 59 should 'show form to change role' do
35 60 ent = fast_create(Enterprise, :identifier => 'test_enterprise', :name => 'test enterprise')
36 61 role = Profile::Roles.member(Environment.default)
... ... @@ -171,7 +196,7 @@ class ProfileMembersControllerTest &lt; ActionController::TestCase
171 196 login_as :test_user
172 197  
173 198 get :index, :profile => community.identifier
174   - assert_tag :tag => 'a', :attributes => {:href => /send_mail/}
  199 + assert_tag :tag => 'input', :attributes => {:value => 'Send e-mail to members'}
175 200 end
176 201  
177 202 should 'not display send email to members if doesn\'t have the permission' do
... ...
test/unit/organization_mailing_test.rb
... ... @@ -98,6 +98,11 @@ class OrganizationMailingTest &lt; ActiveSupport::TestCase
98 98 assert_equal [Person['user_one'], Person['user_two']], mailing.recipients
99 99 end
100 100  
  101 + should 'return recipients previously filtered' do
  102 + mailing = create(OrganizationMailing, :source => community, :subject => 'Hello', :body => 'We have some news', :person => person, :data => {:members_filtered => [Person['user_one'].id,Person['user_two'].id]})
  103 + assert_equivalent [Person['user_one'], Person['user_two']], mailing.recipients
  104 + end
  105 +
101 106 should 'return recipients according to limit' do
102 107 mailing = create(OrganizationMailing, :source => community, :subject => 'Hello', :body => 'We have some news', :person => person)
103 108 assert_equal [Person['user_one']], mailing.recipients(0, 1)
... ...
test/unit/profile_test.rb
... ... @@ -1816,6 +1816,21 @@ class ProfileTest &lt; ActiveSupport::TestCase
1816 1816 assert_equal [person], community.members
1817 1817 end
1818 1818  
  1819 + should 'return a list members by email of a community' do
  1820 + someone = create_user('Someone', email:'someone@test.com.br')
  1821 + someperson = create_user('Someperson',email:'someperson@test.com.br')
  1822 +
  1823 + community = fast_create(Community)
  1824 + community.add_member(someone.person)
  1825 + community.add_member(someperson.person)
  1826 +
  1827 + result = community.members_like 'email', '@test.com.br'
  1828 +
  1829 + assert_includes result, someone.person
  1830 + assert_includes result, someperson.person
  1831 +
  1832 + end
  1833 +
1819 1834 should 'count unique members of a community' do
1820 1835 person = fast_create(Person)
1821 1836 community = fast_create(Community)
... ...