Commit b8ade54001d3480693016e52353b96a1bc79f9a2

Authored by Daniela Feitosa
1 parent c0cb6076

Procedure after last admin leaves organization

* if he is also last member, the next user to join organization will become admin
* if has other members, admin must choose some user to become admin
* Generalized enterprise add_member interface.
* Using json to manage action after join/leave/add/remove
community/friend through ajax.
* Person.members_of now returns unique profiles

(ActionItem1400)
app/controllers/my_profile/profile_members_controller.rb
... ... @@ -15,26 +15,29 @@ class ProfileMembersController < MyProfileController
15 15 rescue ActiveRecord::RecordNotFound
16 16 @person = nil
17 17 end
18   - if @person && @person.define_roles(@roles, profile)
19   - session[:notice] = _('Roles successfuly updated')
  18 + if !params[:confirmation] && @person && @person.is_last_admin_leaving?(profile, @roles)
  19 + redirect_to :action => :last_admin, :roles => params[:roles], :person => @person
20 20 else
21   - session[:notice] = _('Couldn\'t change the roles')
  21 + if @person && @person.define_roles(@roles, profile)
  22 + session[:notice] = _('Roles successfuly updated')
  23 + else
  24 + session[:notice] = _('Couldn\'t change the roles')
  25 + end
  26 + if params[:confirmation]
  27 + redirect_to profile.url
  28 + else
  29 + redirect_to :action => :index
  30 + end
22 31 end
23   - redirect_to :action => :index
24 32 end
25 33  
26   - def change_role
27   - @roles = Profile::Roles.organization_member_roles(environment.id)
28   - begin
29   - @member = profile.members.find(params[:id])
30   - rescue ActiveRecord::RecordNotFound
31   - @member = nil
32   - end
33   - if @member
34   - @associations = @member.find_roles(@profile)
35   - else
36   - redirect_to :action => :index
37   - end
  34 + def last_admin
  35 + @person = params[:person]
  36 + @roles = params[:roles] || []
  37 + @members = profile.members.select {|member| !profile.admins.include?(member)}
  38 + @title = _('Current admins')
  39 + @collection = :profile_admins
  40 + @remove_action = {:action => 'remove_admin'}
38 41 end
39 42  
40 43 def add_role
... ... @@ -59,14 +62,28 @@ class ProfileMembersController < MyProfileController
59 62 render :layout => false
60 63 end
61 64  
  65 + def change_role
  66 + @roles = Profile::Roles.organization_member_roles(environment.id)
  67 + begin
  68 + @member = profile.members.find(params[:id])
  69 + rescue ActiveRecord::RecordNotFound
  70 + @member = nil
  71 + end
  72 + if @member
  73 + @associations = @member.find_roles(@profile)
  74 + else
  75 + redirect_to :action => :index
  76 + end
  77 + end
  78 +
62 79 def unassociate
63 80 member = Person.find(params[:id])
64 81 associations = member.find_roles(profile)
65 82 RoleAssignment.transaction do
66 83 if associations.map(&:destroy)
67   - session[:notice] = 'Member succefully unassociated'
  84 + session[:notice] = _('Member succesfully unassociated')
68 85 else
69   - session[:notice] = 'Failed to unassociate member'
  86 + session[:notice] = _('Failed to unassociate member')
70 87 end
71 88 end
72 89 render :layout => false
... ... @@ -83,11 +100,39 @@ class ProfileMembersController < MyProfileController
83 100 render :layout => false
84 101 end
85 102  
  103 + def add_admin
  104 + @title = _('Current admins')
  105 + @collection = :profile_admins
  106 +
  107 + if profile.community?
  108 + member = profile.members.find_by_identifier(params[:id])
  109 + profile.add_admin(member)
  110 + end
  111 + render :layout => false
  112 + end
  113 +
  114 + def remove_admin
  115 + @title = _('Current admins')
  116 + @collection = :profile_admins
  117 +
  118 + if profile.community?
  119 + member = profile.members.find_by_identifier(params[:id])
  120 + profile.remove_admin(member)
  121 + end
  122 + render :layout => false
  123 + end
  124 +
86 125 def find_users
87 126 if !params[:query] || params[:query].length <= 2
88 127 @users_found = []
89   - else
90   - @users_found = Person.find_by_contents(params[:query] + '*')
  128 + elsif params[:scope] == 'all_users'
  129 + @users_found = Person.find_by_contents(params[:query] + '*').select {|user| !profile.members.include?(user)}
  130 + @button_alt = _('Add member')
  131 + @add_action = {:action => 'add_member'}
  132 + elsif params[:scope] == 'new_admins'
  133 + @users_found = Person.find_by_contents(params[:query] + '*').select {|user| profile.members.include?(user) && !profile.admins.include?(user)}
  134 + @button_alt = _('Add member')
  135 + @add_action = {:action => 'add_admin'}
91 136 end
92 137 render :layout => false
93 138 end
... ...
app/controllers/public/profile_controller.rb
... ... @@ -82,13 +82,13 @@ class ProfileController &lt; PublicController
82 82 def join
83 83 if !user.memberships.include?(profile)
84 84 profile.add_member(user)
85   - if profile.closed?
86   - render :text => _('%s administrator still needs to accept you as member.') % profile.name
  85 + if !profile.members.include?(user)
  86 + render :text => {:message => _('%s administrator still needs to accept you as member.') % profile.name}.to_json
87 87 else
88   - render :text => _('You just became a member of %s.') % profile.name
  88 + render :text => {:message => _('You just became a member of %s.') % profile.name}.to_json
89 89 end
90 90 else
91   - render :text => _('You are already a member of %s.') % profile.name
  91 + render :text => {:message => _('You are already a member of %s.') % profile.name}.to_json
92 92 end
93 93 end
94 94  
... ... @@ -110,11 +110,14 @@ class ProfileController &lt; PublicController
110 110 end
111 111  
112 112 def leave
113   - if user.memberships.include?(profile)
114   - profile.remove_member(current_user.person)
115   - render :text => _('You just left %s.') % profile.name
  113 + if current_person.memberships.include?(profile)
  114 + if current_person.is_last_admin?(profile)
  115 + render :text => {:redirect_to => url_for({:controller => 'profile_members', :action => 'last_admin', :person => current_person.id})}.to_json
  116 + else
  117 + render :text => current_person.leave(profile, params[:reload])
  118 + end
116 119 else
117   - render :text => _('You are already a member of %s.') % profile.name
  120 + render :text => {:message => _('You are not a member of %s.') % profile.name}.to_json
118 121 end
119 122 end
120 123  
... ...
app/models/environment_mailing.rb
1 1 class EnvironmentMailing < Mailing
2 2  
3 3 def recipients(offset=0, limit=100)
4   - source.people.all(:order => :id, :offset => offset, :limit => limit, :joins => "LEFT OUTER JOIN mailing_sents m ON (m.mailing_id = #{id} AND m.person_id = profiles.id)", :order => :id, :conditions => { "m.person_id" => nil })
  4 + source.people.all(:order => :id, :offset => offset, :limit => limit, :joins => "LEFT OUTER JOIN mailing_sents m ON (m.mailing_id = #{id} AND m.person_id = profiles.id)", :conditions => { "m.person_id" => nil })
5 5 end
6 6  
7 7 def each_recipient
... ...
app/models/organization_mailing.rb
... ... @@ -5,7 +5,7 @@ class OrganizationMailing &lt; Mailing
5 5 end
6 6  
7 7 def recipients(offset=0, limit=100)
8   - source.members.all(:order => :id, :offset => offset, :limit => limit, :joins => "LEFT OUTER JOIN mailing_sents m ON (m.mailing_id = #{id} AND m.person_id = profiles.id)", :order => :id, :conditions => { "m.person_id" => nil })
  8 + source.members.all(:order => self.id, :offset => offset, :limit => limit, :joins => "LEFT OUTER JOIN mailing_sents m ON (m.mailing_id = #{id} AND m.person_id = profiles.id)", :conditions => { "m.person_id" => nil })
9 9 end
10 10  
11 11 def each_recipient
... ...
app/models/person.rb
... ... @@ -4,7 +4,7 @@ class Person &lt; Profile
4 4 acts_as_trackable :after_add => Proc.new {|p,t| notify_activity(t)}
5 5 acts_as_accessor
6 6  
7   - named_scope :members_of, lambda { |resource| { :select => 'DISTINCT profiles.*', :joins => :role_assignments, :conditions => ['role_assignments.resource_type = ? AND role_assignments.resource_id = ?', resource.class.base_class.name, resource.id ] } }
  7 + named_scope :members_of, lambda { |resource| { :select => 'DISTINCT profiles.*', :include => :role_assignments, :group => 'profiles.id', :conditions => ['role_assignments.resource_type = ? AND role_assignments.resource_id = ?', resource.class.base_class.name, resource.id ] } }
8 8  
9 9 def memberships
10 10 Profile.memberships_of(self)
... ... @@ -363,10 +363,26 @@ class Person &lt; Profile
363 363 generate_url(:profile => identifier, :controller => 'profile', :action => 'index', :anchor => 'profile-wall')
364 364 end
365 365  
  366 + def is_last_admin?(organization)
  367 + organization.admins == [self]
  368 + end
  369 +
  370 + def is_last_admin_leaving?(organization, roles)
  371 + is_last_admin?(organization) && roles.select {|role| role.key == "profile_admin"}.blank?
  372 + end
  373 +
  374 + def leave(profile, reload = false)
  375 + leave_hash = {:message => _('You just left %s.') % profile.name}
  376 + if reload
  377 + leave_hash.merge!({:reload => true})
  378 + end
  379 + profile.remove_member(self)
  380 + leave_hash.to_json
  381 + end
  382 +
366 383 protected
367 384  
368 385 def followed_by?(profile)
369 386 self == profile || self.is_a_friend?(profile)
370 387 end
371   -
372 388 end
... ...
app/models/profile.rb
... ... @@ -424,8 +424,8 @@ class Profile &lt; ActiveRecord::Base
424 424 { :profile => identifier, :controller => 'profile_editor', :action => 'index' }
425 425 end
426 426  
427   - def leave_url
428   - { :profile => identifier, :controller => 'profile', :action => 'leave' }
  427 + def leave_url(reload = false)
  428 + { :profile => identifier, :controller => 'profile', :action => 'leave', :reload => reload }
429 429 end
430 430  
431 431 def join_url
... ... @@ -563,13 +563,16 @@ private :generate_url, :url_options
563 563 # Adds a person as member of this Profile.
564 564 def add_member(person)
565 565 if self.has_members?
566   - if self.closed?
  566 + if self.closed? && members.count > 0
567 567 AddMember.create!(:person => person, :organization => self) unless self.already_request_membership?(person)
568 568 else
  569 + if members.count == 0
  570 + self.affiliate(person, Profile::Roles.admin(environment.id))
  571 + end
569 572 self.affiliate(person, Profile::Roles.member(environment.id))
570 573 end
571 574 else
572   - raise _("%s can't has members") % self.class.name
  575 + raise _("%s can't have members") % self.class.name
573 576 end
574 577 end
575 578  
... ... @@ -582,6 +585,10 @@ private :generate_url, :url_options
582 585 self.affiliate(person, Profile::Roles.admin(environment.id))
583 586 end
584 587  
  588 + def remove_admin(person)
  589 + self.disaffiliate(person, Profile::Roles.admin(environment.id))
  590 + end
  591 +
585 592 def add_moderator(person)
586 593 if self.has_members?
587 594 self.affiliate(person, Profile::Roles.moderator(environment.id))
... ...
app/views/memberships/index.rhtml
... ... @@ -23,7 +23,7 @@
23 23 <%= _('Created at: %s') % show_date(membership.created_at) unless membership.enterprise? %> <br/>
24 24 <% button_bar do %>
25 25 <%= button 'menu-ctrl-panel', _('Control panel of this group'), membership.admin_url %>
26   - <%= lightbox_button 'menu-logout', _('Leave'), membership.leave_url %>
  26 + <%= button 'menu-logout', _('Leave'), membership.leave_url(true), :class => 'leave-community' %>
27 27 <% if (membership.community? && user.has_permission?(:destroy_profile, membership)) %>
28 28 <%= button 'delete', _('Remove'), { :controller => 'profile_editor', :action => 'destroy_profile', :profile => membership.identifier } %>
29 29 <% end %>
... ...
app/views/profile_members/_add_admins.rhtml 0 → 100644
... ... @@ -0,0 +1,30 @@
  1 +<h2><%= _('Add admins to %s') % profile.name %></h2>
  2 +
  3 +<% form_remote_tag :url => {:action => 'find_users', :profile => profile.identifier, :scope => 'new_admins'}, :update => 'users-list', :loading => '$("users-list").addClassName("loading")', :complete => '$("users-list").removeClassName("loading")' do %>
  4 + <%= text_field_tag('query', '', :autocomplete => 'off') %>
  5 + <%= submit_tag(_('Search')) %>
  6 +<% end %>
  7 +
  8 +<%= observe_field('query', :url => {:action => 'find_users', :profile => profile.identifier, :scope => 'new_admins'}, :update => 'users-list', :frequency => 1, :with => 'query', :condition => '$("query").value.length > 2', :loading => '$("users-list").addClassName("loading")', :complete => '$("users-list").removeClassName("loading")') %>
  9 +<%= observe_field('query', :frequency => 1, :condition => '$("query").value.length <= 2', :function => '$("users-list").update($("empty-query").innerHTML)') %>
  10 +
  11 +<div id="users-list">
  12 + <%= render :partial => 'find_users' %>
  13 +</div>
  14 +
  15 +<div id='empty-query' style='display: none'>
  16 + <%= render :partial => 'find_users' %>
  17 +</div>
  18 +
  19 +<div id="members-list" class="add-members">
  20 + <%= render :partial => 'members_list' %>
  21 +</div>
  22 +<%= drop_receiving_element('members-list',
  23 + :url => {:action => 'add_admin', :profile => profile.identifier, :leaving_admin => @person},
  24 + :before => '$("tr-" + element.id).hide()',
  25 + :loading => '$("members-list").addClassName("loading")',
  26 + :update => 'members-list',
  27 + :success => '$("tr-" + element.id).hide(); $(element.id).show();',
  28 + :complete => '$("members-list").removeClassName("loading")') %>
  29 +
  30 +<br style="clear:both" />
... ...
app/views/profile_members/_members_list.rhtml
1   -<h3><%= _('Current Members') %></h3>
  1 +<% collection = @collection == :profile_admins ? profile.admins : profile.members %>
  2 +<% title = @title ? @title : "Current members" %>
  3 +<% remove_action = @remove_action ? @remove_action : {:action => 'unassociate'} %>
  4 +
  5 +<h3><%= _(title) %></h3>
2 6  
3 7 <table>
4 8 <tr>
5 9 <th><%= _('Member') %></th>
6 10 <th><%= _('Actions') %></th>
7 11 </tr>
8   - <% profile.members.each do |m| %>
  12 + <% collection.each do |m| %>
9 13 <tr>
10 14 <td><%= link_to_profile m.short_name, m.identifier, :title => m.name %> </td>
11 15 <td>
... ... @@ -16,8 +20,7 @@
16 20 :loading => '$("members-list").addClassName("loading")',
17 21 :success => "$('tr-#{m.identifier}').show()",
18 22 :complete => '$("members-list").removeClassName("loading")',
19   - :confirm => _('Are you sure that you want to remove this member?'),
20   - :url => {:action => 'unassociate', :id => m}) if m != user %>
  23 + :url => { :id => m }.merge(remove_action)) if m != user %>
21 24 </div>
22 25 </td>
23 26 </tr>
... ...
app/views/profile_members/add_admin.rhtml 0 → 120000
... ... @@ -0,0 +1 @@
  1 +_members_list.rhtml
0 2 \ No newline at end of file
... ...
app/views/profile_members/add_members.rhtml
1 1 <h2><%= _('Add members to %s') % profile.name %></h2>
2 2  
3   -<% form_remote_tag :url => {:action => 'find_users', :profile => profile.identifier}, :update => 'users-list', :loading => '$("users-list").addClassName("loading")', :complete => '$("users-list").removeClassName("loading")' do %>
  3 +<% form_remote_tag :url => {:action => 'find_users', :profile => profile.identifier, :scope => 'all_users'}, :update => 'users-list', :loading => '$("users-list").addClassName("loading")', :complete => '$("users-list").removeClassName("loading")' do %>
4 4 <%= text_field_tag('query', '', :autocomplete => 'off') %>
5 5 <%= submit_tag(_('Search')) %>
6 6 <% end %>
7 7  
8   -<%= observe_field('query', :url => {:action => 'find_users', :profile => profile.identifier}, :update => 'users-list', :frequency => 1, :with => 'query', :condition => '$("query").value.length > 2', :loading => '$("users-list").addClassName("loading")', :complete => '$("users-list").removeClassName("loading")') %>
  8 +<%= observe_field('query', :url => {:action => 'find_users', :profile => profile.identifier, :scope => 'all_users'}, :update => 'users-list', :frequency => 1, :with => 'query', :condition => '$("query").value.length > 2', :loading => '$("users-list").addClassName("loading")', :complete => '$("users-list").removeClassName("loading")') %>
9 9 <%= observe_field('query', :frequency => 1, :condition => '$("query").value.length <= 2', :function => '$("users-list").update($("empty-query").innerHTML)') %>
10 10  
11 11 <div id="users-list">
... ...
app/views/profile_members/find_users.rhtml
... ... @@ -2,7 +2,7 @@
2 2 <table>
3 3 <tr><th><%= _('Name') %></th><th></th></tr>
4 4 <% @users_found.each do |user| %>
5   - <tr id="tr-<%= user.identifier %>"<%= profile.members.include?(user) ? 'style="display:none"' : '' %>>
  5 + <tr id="tr-<%= user.identifier %>">
6 6 <td>
7 7 <div id="<%= user.identifier %>" class="draggable-user">
8 8 <%= image_tag('/images/grip-clue.png') %>
... ... @@ -14,10 +14,10 @@
14 14 <%= draggable_element(user.identifier, :revert => true) %>
15 15 </td>
16 16 <td>
17   - <%= button_to_remote_without_text(:add, _('Add member'),
  17 + <%= button_to_remote_without_text(:add, @button_alt,
18 18 { :loading => '$("members-list").addClassName("loading")',
19 19 :update => 'members-list',
20   - :url => {:action => 'add_member', :profile => profile.identifier, :id => user.id},
  20 + :url => {:id => user.id, :profile => profile.identifier}.merge(@add_action),
21 21 :success => "$('tr-#{user.identifier}').hide()",
22 22 :complete => '$("members-list").removeClassName("loading")'}) %>
23 23  
... ...
app/views/profile_members/last_admin.rhtml 0 → 100644
... ... @@ -0,0 +1,27 @@
  1 +<h1><%= _('Last administrator leaving %s') % profile.name %></h1>
  2 +
  3 +<% if profile.members.count > 1 %>
  4 + <div id='last-admin-message'>
  5 + <%= _('Since you are the last administrator, you must choose at least one member to administer this community.') % profile.name %>
  6 + </div>
  7 +
  8 + <%= render :partial => 'add_admins' %>
  9 +
  10 + <% form_tag :action => 'update_roles', :roles => @roles, :person => @person, :confirmation => profile.admins.count > 1 do %>
  11 + <% button_bar do %>
  12 + <%= submit_button(:save, _("Leave administration and save"), :cancel => {:action => 'index'}) %>
  13 + <% end %>
  14 + <% end %>
  15 +
  16 +<% else %>
  17 +
  18 + <div id='last-admin-message'>
  19 + <%= _('Since you are the last administrator and there is no other member in this community, the next member to join this community will assume the administrator role.') % profile.name %>
  20 + </div>
  21 +
  22 + <% form_tag :action => 'update_roles', :roles => @roles, :person => @person, :confirmation => true do %>
  23 + <% button_bar do %>
  24 + <%= submit_button(:ok, _("Ok, I want to leave"), :cancel => {:action => 'index'}) %>
  25 + <% end %>
  26 + <% end %>
  27 +<% end %>
... ...
app/views/profile_members/remove_admin.rhtml 0 → 120000
... ... @@ -0,0 +1 @@
  1 +_members_list.rhtml
0 2 \ No newline at end of file
... ...
features/delete_profile.feature
... ... @@ -7,6 +7,11 @@ Feature: delete profile
7 7 Given the following users
8 8 | login | name |
9 9 | joaosilva | Joao Silva |
  10 + | mariasilva | Maria Silva |
  11 + And the following community
  12 + | identifier | name |
  13 + | sample-community | Sample Community |
  14 + And "Maria Silva" is a member of "Sample Community"
10 15  
11 16 Scenario: deleting profile
12 17 Given I am logged in as "joaosilva"
... ... @@ -20,10 +25,7 @@ Feature: delete profile
20 25 Then I should see "There is no such page"
21 26  
22 27 Scenario: deleting other profile
23   - Given the following users
24   - | login | name |
25   - | mariasilva | Maria Silva |
26   - And I am logged in as "mariasilva"
  28 + Given I am logged in as "mariasilva"
27 29 And I go to /myprofile/joaosilva/profile_editor/destroy_profile
28 30 Then I should see "Access denied"
29 31  
... ... @@ -37,20 +39,14 @@ Feature: delete profile
37 39 Then I should be on Joao Silva's profile
38 40  
39 41 Scenario: community admin can see link to delete profile
40   - Given the following community
41   - | identifier | name |
42   - | sample-community | Sample Community |
43   - And "Joao Silva" is admin of "Sample Community"
  42 + Given "Joao Silva" is admin of "Sample Community"
44 43 And I am logged in as "joaosilva"
45 44 And I am on Sample Community's control panel
46 45 When I follow "Community Info and settings"
47 46 Then I should see "Delete profile"
48 47  
49 48 Scenario: community admin deletes the community
50   - Given the following community
51   - | identifier | name |
52   - | sample-community | Sample Community |
53   - And "Joao Silva" is admin of "Sample Community"
  49 + Given "Joao Silva" is admin of "Sample Community"
54 50 And I am logged in as "joaosilva"
55 51 And I am on Sample Community's control panel
56 52 And I follow "Community Info and settings"
... ... @@ -62,10 +58,7 @@ Feature: delete profile
62 58 Then I should see "There is no such page"
63 59  
64 60 Scenario: community regular member tries to delete the community
65   - Given the following community
66   - | identifier | name |
67   - | sample-community | Sample Community |
68   - And "Joao Silva" is a member of "Sample Community"
  61 + Given "Joao Silva" is a member of "Sample Community"
69 62 And I am logged in as "joaosilva"
70 63 And I go to /myprofile/sample-community/profile_editor/destroy_profile
71 64 Then I should see "Access denied"
... ... @@ -96,19 +89,17 @@ Feature: delete profile
96 89 Then I should see "There is no such page"
97 90  
98 91 Scenario: enterprise regular member tries to delete the enterprise
99   - Given the following community
  92 + Given the following enterprise
100 93 | identifier | name |
101 94 | sample-enterprise | Sample Enterprise |
  95 + And "Maria Silva" is a member of "Sample Enterprise"
102 96 And "Joao Silva" is a member of "Sample Enterprise"
103 97 And I am logged in as "joaosilva"
104 98 And I go to /myprofile/sample-enterprise/profile_editor/destroy_profile
105 99 Then I should see "Access denied"
106 100  
107 101 Scenario: community regular member cannot see link to delete profile
108   - Given the following community
109   - | identifier | name |
110   - | sample-community | Sample Community |
111   - And "Joao Silva" is a member of "Sample Community"
  102 + Given "Joao Silva" is a member of "Sample Community"
112 103 And I am logged in as "joaosilva"
113 104 And I am on Sample Community's control panel
114 105 When I follow "Community Info and settings"
... ...
features/last_administrator_leaving.feature 0 → 100644
... ... @@ -0,0 +1,38 @@
  1 +Feature: remove administrator role
  2 + As an organization administrator
  3 + I want to remove my administrator role
  4 + In order to stop administrating the organization
  5 +
  6 + Background:
  7 + Given the following users
  8 + | login | name |
  9 + | joaosilva | Joao Silva |
  10 + | mariasouza | Maria Souza |
  11 + And the following community
  12 + | name | identifier |
  13 + | Nice people | nice-people |
  14 + And "Joao Silva" is admin of "Nice people"
  15 + And I am logged in as "joaosilva"
  16 +
  17 + Scenario: the last administrator and member removes his administrator role and the next member to join becomes the new administrator
  18 + Given I am on Nice people's members management
  19 + And I follow "Edit"
  20 + And I uncheck "Profile Administrator"
  21 + When I press "Save changes"
  22 + Then I should see "Since you are the last administrator and there is no other member in this community"
  23 + And I press "Ok, I want to leave"
  24 + And I am logged in as "mariasouza"
  25 + When I go to Nice people's join page
  26 + Then "Maria Souza" should be admin of "Nice people"
  27 +
  28 + Scenario: the last administrator and member removes his administrator role and the next member to join becomes the new administrator even if the organization is closed.
  29 + Given the community "Nice people" is closed
  30 + And I am on Nice people's members management
  31 + And I follow "Edit"
  32 + And I uncheck "Profile Administrator"
  33 + When I press "Save changes"
  34 + Then I should see "Since you are the last administrator and there is no other member in this community"
  35 + And I press "Ok, I want to leave"
  36 + And I am logged in as "mariasouza"
  37 + When I go to Nice people's join page
  38 + Then "Maria Souza" should be admin of "Nice people"
... ...
features/register_enterprise.feature
... ... @@ -93,7 +93,7 @@ Feature: register enterprise
93 93 Then I should see "Enterprise registration completed"
94 94 And I am logged in as admin
95 95 And I go to the Control panel
96   - When I follow "Tasks"
  96 + When I follow "Tasks" within ".control-panel"
97 97 Then I should see "Joao Silva wants to create enterprise My Enterprise."
98 98 And the first mail is to admin_user@example.com
99 99 And I choose "Accept"
... ... @@ -120,7 +120,7 @@ Feature: register enterprise
120 120 Then I should see "Enterprise registration completed"
121 121 And I am logged in as admin
122 122 And I go to the Control panel
123   - When I follow "Tasks"
  123 + When I follow "Tasks" within ".control-panel"
124 124 Then I should see "Joao Silva wants to create enterprise My Enterprise."
125 125 And the first mail is to admin_user@example.com
126 126 And I choose "Reject"
... ...
features/step_definitions/noosfero_steps.rb
... ... @@ -16,13 +16,13 @@ Given /^&quot;(.+)&quot; is (online|offline|busy) in chat$/ do |user, status|
16 16 User.find_by_login(user).update_attributes(:chat_status => status, :chat_status_at => DateTime.now)
17 17 end
18 18  
19   -Given /^the following (community|communities|enterprises?)$/ do |kind,table|
  19 +Given /^the following (community|communities|enterprises?|organizations?)$/ do |kind,table|
20 20 klass = kind.singularize.camelize.constantize
21 21 table.hashes.each do |row|
22 22 owner = row.delete("owner")
23   - community = klass.create!(row)
  23 + organization = klass.create!(row)
24 24 if owner
25   - community.add_admin(Profile[owner])
  25 + organization.add_admin(Profile[owner])
26 26 end
27 27 end
28 28 end
... ... @@ -210,6 +210,12 @@ Given /^&quot;(.+)&quot; is admin of &quot;(.+)&quot;$/ do |person, organization|
210 210 org.add_admin(user)
211 211 end
212 212  
  213 +Then /^"(.+)" should be admin of "(.+)"$/ do |person, organization|
  214 + org = Organization.find_by_name(organization)
  215 + user = Person.find_by_name(person)
  216 + org.admins.should include(user)
  217 +end
  218 +
213 219 Given /^"([^\"]*)" has no articles$/ do |profile|
214 220 (Profile[profile] || Profile.find_by_name(profile)).articles.delete_all
215 221 end
... ... @@ -317,3 +323,9 @@ Given /^the following comments?$/ do |table|
317 323 comment.save!
318 324 end
319 325 end
  326 +
  327 +Given /^the community "(.+)" is closed$/ do |community|
  328 + community = Community.find_by_name(community)
  329 + community.closed = true
  330 + community.save
  331 +end
... ...
features/support/paths.rb
... ... @@ -36,6 +36,12 @@ module NavigationHelpers
36 36 when /^the profile$/
37 37 '/profile/%s' % User.find_by_id(session[:user]).login
38 38  
  39 + when /^(.*)'s join page/
  40 + '/profile/%s/join' % Profile.find_by_name($1).identifier
  41 +
  42 + when /^(.*)'s leave page/
  43 + '/profile/%s/leave' % Profile.find_by_name($1).identifier
  44 +
39 45 when /^login page$/
40 46 '/account/login'
41 47  
... ...
public/javascripts/add-and-join.js
... ... @@ -23,8 +23,8 @@ jQuery(function($) {
23 23 });
24 24 clicked.css("cursor","");
25 25 $(".small-loading").remove();
26   - display_notice(data);
27   - });
  26 + display_notice(data.message);
  27 + }, "json");
28 28 return false;
29 29 })
30 30  
... ... @@ -33,15 +33,24 @@ jQuery(function($) {
33 33 url = clicked.attr("href");
34 34 loading_for_button(this);
35 35 $.post(url, function(data){
36   - clicked.fadeOut(function(){
37   - clicked.css("display","none");
38   - clicked.parent().parent().find(".join-community").fadeIn();
39   - clicked.parent().parent().find(".join-community").css("display", "");
40   - });
41   - clicked.css("cursor","");
42   - $(".small-loading").remove();
43   - display_notice(data);
44   - });
  36 + if(data.redirect_to){
  37 + document.location.href = data.redirect_to;
  38 + }
  39 + else if(data.reload){
  40 + document.location.reload(true);
  41 + }
  42 + else{
  43 + clicked.fadeOut(function(){
  44 + clicked.css("display","none");
  45 + clicked.parent().parent().find(".join-community").fadeIn();
  46 + clicked.parent().parent().find(".join-community").css("display", "");
  47 + });
  48 + clicked.css("cursor","");
  49 + $(".small-loading").remove();
  50 +
  51 + display_notice(data.message);
  52 + }
  53 + }, "json");
45 54 return false;
46 55 })
47 56  
... ...
public/stylesheets/application.css
... ... @@ -1566,7 +1566,7 @@ a.comment-picture {
1566 1566 a.button, a.button:visited,
1567 1567 body.noosfero a.button, body.noosfero a.button:visited,
1568 1568 input.button {
1569   - margin: 0px 2px;
  1569 + margin: 0px 2px 0px 0px;
1570 1570 background-repeat: no-repeat;
1571 1571 background-position: 50% 50%;
1572 1572 padding: 3px 0px 3px 20px;
... ... @@ -4094,6 +4094,15 @@ h1#agenda-title {
4094 4094 width: 100%;
4095 4095 }
4096 4096  
  4097 +#last-admin-message {
  4098 + background-color: #CCC;
  4099 + color: #000;
  4100 + font-size: 14px;
  4101 + padding: 20px 15px;
  4102 + -moz-border-radius:10px;
  4103 + -webkit-border-radius:10px;
  4104 +}
  4105 +
4097 4106 /* ==> public/stylesheets/controller_search.css <== */
4098 4107 /* @import url(pagination.css); ALREADY INCLUDED ABOVE */
4099 4108  
... ...
test/functional/content_viewer_controller_test.rb
... ... @@ -308,14 +308,16 @@ class ContentViewerControllerTest &lt; Test::Unit::TestCase
308 308 end
309 309  
310 310 should 'not show private content to members' do
311   - community = Community.create!(:name => 'testcomm')
312   - Folder.create!(:name => 'test', :profile => community, :published => false)
313   - community.add_member(profile)
  311 + community = fast_create(Community)
  312 + admin = fast_create(Person)
  313 + community.add_member(admin)
314 314  
  315 + folder = fast_create(Folder, :profile_id => community.id, :published => false)
  316 + community.add_member(profile)
315 317 login_as(profile.identifier)
316 318  
317 319 @request.stubs(:ssl?).returns(true)
318   - get :view_page, :profile => community.identifier, :page => [ 'test' ]
  320 + get :view_page, :profile => community.identifier, :page => [ folder.path ]
319 321  
320 322 assert_template 'access_denied.rhtml'
321 323 end
... ...
test/functional/memberships_controller_test.rb
... ... @@ -95,11 +95,11 @@ class MembershipsControllerTest &lt; Test::Unit::TestCase
95 95 assert_no_tag :tag => 'li', :content => /Description:/
96 96 end
97 97  
98   - should 'show link to leave from community' do
  98 + should 'show link to leave from community with reload' do
99 99 community = Community.create!(:name => 'my test community', :description => 'description test')
100 100 community.add_member(profile)
101 101 get :index, :profile => profile.identifier
102   - assert_tag :tag => 'a', :attributes => { :href => "/profile/#{community.identifier}/leave" }, :content => 'Leave'
  102 + assert_tag :tag => 'a', :attributes => { :href => "/profile/#{community.identifier}/leave?reload=true" }, :content => 'Leave'
103 103 end
104 104  
105 105 should 'current user is added as admin after create new community' do
... ... @@ -127,7 +127,9 @@ class MembershipsControllerTest &lt; Test::Unit::TestCase
127 127 end
128 128  
129 129 should 'not display destroy link to normal members' do
130   - community = Community.create!(:name => 'A community to destroy')
  130 + community = fast_create(Community)
  131 + admin = fast_create(Person)
  132 + community.add_member(admin)
131 133  
132 134 person = Person['testuser']
133 135 community.add_member(person)
... ...
test/functional/profile_controller_test.rb
... ... @@ -383,14 +383,28 @@ class ProfileControllerTest &lt; Test::Unit::TestCase
383 383 assert profile.memberships.include?(community), 'profile should be actually added to the community'
384 384 end
385 385  
386   - should 'create task when join to closed organization' do
387   - community = Community.create!(:name => 'my test community', :closed => true)
388   - login_as @profile.identifier
  386 + should 'create task when join to closed organization with members' do
  387 + community = fast_create(Community)
  388 + community.update_attribute(:closed, true)
  389 + admin = fast_create(Person)
  390 + community.add_member(admin)
  391 +
  392 + login_as profile.identifier
389 393 assert_difference AddMember, :count do
390 394 post :join, :profile => community.identifier
391 395 end
392 396 end
393 397  
  398 + should 'not create task when join to closed and empty organization' do
  399 + community = fast_create(Community)
  400 + community.update_attribute(:closed, true)
  401 +
  402 + login_as profile.identifier
  403 + assert_no_difference AddMember, :count do
  404 + post :join, :profile => community.identifier
  405 + end
  406 + end
  407 +
394 408 should 'require login to join community' do
395 409 community = Community.create!(:name => 'my test community', :closed => true)
396 410 get :join, :profile => community.identifier
... ... @@ -399,7 +413,10 @@ class ProfileControllerTest &lt; Test::Unit::TestCase
399 413 end
400 414  
401 415 should 'actually leave profile' do
402   - community = Community.create!(:name => 'my test community')
  416 + community = fast_create(Community)
  417 + admin = fast_create(Person)
  418 + community.add_member(admin)
  419 +
403 420 community.add_member(profile)
404 421 assert_includes profile.memberships, community
405 422  
... ... @@ -417,6 +434,21 @@ class ProfileControllerTest &lt; Test::Unit::TestCase
417 434 assert_redirected_to :controller => 'account', :action => 'login'
418 435 end
419 436  
  437 + should 'not leave if is last admin' do
  438 + community = fast_create(Community)
  439 +
  440 + community.add_admin(profile)
  441 + assert_includes profile.memberships, community
  442 +
  443 + login_as(profile.identifier)
  444 + post :leave, :profile => community.identifier
  445 +
  446 + profile.reload
  447 + assert_response :success
  448 + assert_match(/last_admin/, @response.body)
  449 + assert_includes profile.memberships, community
  450 + end
  451 +
420 452 should 'store location before login when request join via get not logged' do
421 453 community = Community.create!(:name => 'my test community')
422 454  
... ...
test/functional/profile_members_controller_test.rb
... ... @@ -236,22 +236,42 @@ class ProfileMembersControllerTest &lt; Test::Unit::TestCase
236 236 should 'find users' do
237 237 ent = fast_create(Enterprise, :name => 'Test Ent', :identifier => 'test_ent')
238 238 user = create_user_full('test_user').person
239   - u = create_user_with_permission('ent_user', 'manage_memberships', ent)
  239 + person = create_user_with_permission('ent_user', 'manage_memberships', ent)
240 240 login_as :ent_user
241 241  
242   - get :find_users, :profile => ent.identifier, :query => 'test*'
  242 + get :find_users, :profile => ent.identifier, :query => 'test*', :scope => 'all_users'
243 243  
244 244 assert_includes assigns(:users_found), user
245 245 end
246 246  
247   - should 'not appear add button for member in add members page' do
  247 + should 'not display members when finding users in all_users scope' do
248 248 ent = fast_create(Enterprise, :name => 'Test Ent', :identifier => 'test_ent')
249   - p = create_user_with_permission('test_user', 'manage_memberships', ent)
250   - login_as :test_user
  249 + user = create_user_full('test_user').person
  250 +
  251 + person = create_user_with_permission('ent_user', 'manage_memberships', ent)
  252 + login_as :ent_user
  253 +
  254 + get :find_users, :profile => ent.identifier, :query => '*user', :scope => 'all_users'
  255 +
  256 + assert_tag :tag => 'a', :content => /#{user.name}/
  257 + assert_no_tag :tag => 'a', :content => /#{person.name}/
  258 + end
251 259  
252   - get :find_users, :profile => ent.identifier, :query => 'test*'
  260 + should 'not display admins when finding users in new_admins scope' do
  261 + ent = fast_create(Enterprise, :name => 'Test Ent', :identifier => 'test_ent')
  262 +
  263 + person = create_user('admin_user').person
  264 + ent.add_admin(person)
  265 +
  266 + user = create_user_full('test_user').person
  267 + ent.add_member(user).finish
253 268  
254   - assert_tag :tag => 'tr', :attributes => {:id => 'tr-test_user', :style => 'display:none'}
  269 + login_as :admin_user
  270 +
  271 + get :find_users, :profile => ent.identifier, :query => '*user', :scope => 'new_admins'
  272 +
  273 + assert_tag :tag => 'a', :content => /#{user.name}/
  274 + assert_no_tag :tag => 'a', :content => /#{person.name}/
255 275 end
256 276  
257 277 should 'return users with <query> as a prefix' do
... ... @@ -259,10 +279,10 @@ class ProfileMembersControllerTest &lt; Test::Unit::TestCase
259 279 daniela = create_user_full('daniela').person
260 280  
261 281 ent = fast_create(Enterprise, :name => 'Test Ent', :identifier => 'test_ent')
262   - p = create_user_with_permission('test_user', 'manage_memberships', ent)
  282 + person = create_user_with_permission('test_user', 'manage_memberships', ent)
263 283 login_as :test_user
264 284  
265   - get :find_users, :profile => ent.identifier, :query => 'daniel'
  285 + get :find_users, :profile => ent.identifier, :query => 'daniel', :scope => 'all_users'
266 286  
267 287 assert_includes assigns(:users_found), daniel
268 288 assert_includes assigns(:users_found), daniela
... ... @@ -307,4 +327,32 @@ class ProfileMembersControllerTest &lt; Test::Unit::TestCase
307 327 assert_equal Profile['profile_admin_user'], assigns(:mailing).person
308 328 end
309 329  
  330 + should 'set a community member as admin' do
  331 + community = fast_create(Community)
  332 + admin = create_user_with_permission('admin_user', 'manage_memberships', community)
  333 + member = create_user('test_member').person
  334 + community.add_member(member)
  335 +
  336 + assert_not_includes community.admins, member
  337 +
  338 + login_as :admin_user
  339 + get :add_admin, :profile => community.identifier, :id => member.identifier
  340 +
  341 + assert_includes community.admins, member
  342 + end
  343 +
  344 + should 'remove a community admin' do
  345 + community = fast_create(Community)
  346 + admin = create_user_with_permission('admin_user', 'manage_memberships', community)
  347 + member = create_user('test_member').person
  348 + community.add_admin(member)
  349 +
  350 + assert_includes community.admins, member
  351 +
  352 + login_as :admin_user
  353 + get :remove_admin, :profile => community.identifier, :id => member.identifier
  354 +
  355 + assert_not_includes community.admins, member
  356 + end
  357 +
310 358 end
... ...
test/unit/application_helper_test.rb
... ... @@ -96,11 +96,20 @@ class ApplicationHelperTest &lt; Test::Unit::TestCase
96 96 assert_equal 'black', role_color('none', Environment.default.id)
97 97 end
98 98  
99   - should 'rolename for' do
  99 + should 'rolename for first organization member' do
100 100 person = create_user('usertest').person
101 101 community = fast_create(Community, :name => 'new community', :identifier => 'new-community', :environment_id => Environment.default.id)
102 102 community.add_member(person)
103   - assert_equal 'Profile Member', rolename_for(person, community)
  103 + assert_equal 'Profile Administrator', rolename_for(person, community)
  104 + end
  105 +
  106 + should 'rolename for a member' do
  107 + member1 = create_user('usertest1').person
  108 + member2 = create_user('usertest2').person
  109 + community = fast_create(Community, :name => 'new community', :identifier => 'new-community', :environment_id => Environment.default.id)
  110 + community.add_member(member1)
  111 + community.add_member(member2)
  112 + assert_equal 'Profile Member', rolename_for(member2, community)
104 113 end
105 114  
106 115 should 'get theme from environment by default' do
... ...
test/unit/community_test.rb
... ... @@ -3,7 +3,7 @@ require File.dirname(__FILE__) + &#39;/../test_helper&#39;
3 3 class CommunityTest < Test::Unit::TestCase
4 4  
5 5 def setup
6   - @person = create_user('testuser').person
  6 + @person = fast_create(Person)
7 7 end
8 8  
9 9 attr_reader :person
... ... @@ -183,10 +183,33 @@ class CommunityTest &lt; Test::Unit::TestCase
183 183 end
184 184 end
185 185  
  186 + should 'set as member without task if organization is closed and has no members' do
  187 + community = fast_create(Community)
  188 + community.closed = true
  189 + community.save
  190 +
  191 + assert_no_difference AddMember, :count do
  192 + community.add_member(person)
  193 + end
  194 + assert person.is_member_of?(community)
  195 + end
  196 +
  197 + should 'set as member without task if organization is not closed and has no members' do
  198 + community = fast_create(Community)
  199 +
  200 + assert_no_difference AddMember, :count do
  201 + community.add_member(person)
  202 + end
  203 + assert person.is_member_of?(community)
  204 + end
  205 +
186 206 should 'not create new request membership if it already exists' do
187 207 community = fast_create(Community)
188 208 community.closed = true
189 209 community.save
  210 +
  211 + community.add_member(fast_create(Person))
  212 +
190 213 assert_difference AddMember, :count do
191 214 community.add_member(person)
192 215 end
... ...
test/unit/enterprise_test.rb
... ... @@ -102,14 +102,24 @@ class EnterpriseTest &lt; Test::Unit::TestCase
102 102 assert_not_includes result, ent2
103 103 end
104 104  
  105 + should 'allow to add new members if has no members' do
  106 + enterprise = fast_create(Enterprise)
  107 +
  108 + person = fast_create(Person)
  109 + enterprise.add_member(person)
  110 +
  111 + assert person.is_member_of?(enterprise)
  112 + end
  113 +
105 114 should 'not allow to add new members' do
106   - o = fast_create(Enterprise, :name => 'my test profile', :identifier => 'mytestprofile')
107   - p = create_user('mytestuser').person
  115 + enterprise = fast_create(Enterprise)
  116 + member = fast_create(Person)
  117 + enterprise.add_member(member)
108 118  
109   - o.add_member(p)
110   - o.reload
  119 + person = fast_create(Person)
  120 + enterprise.add_member(person)
111 121  
112   - assert_not_includes o.members, p
  122 + assert_equal false, person.is_member_of?(enterprise)
113 123 end
114 124  
115 125 should 'allow to remove members' do
... ...
test/unit/person_test.rb
... ... @@ -394,12 +394,16 @@ class PersonTest &lt; Test::Unit::TestCase
394 394 end
395 395  
396 396 should 'not allow simple member to view group pending tasks' do
397   - c = fast_create(Community)
398   - c.tasks << Task.new
399   - p = create_user('user_without_tasks').person
400   - c.add_member(p)
  397 + community = fast_create(Community)
  398 + member = fast_create(Person)
  399 + community.add_member(member)
  400 +
  401 + community.tasks << Task.new
401 402  
402   - assert_not_includes Person.with_pending_tasks, p
  403 + person = fast_create(Person)
  404 + community.add_member(person)
  405 +
  406 + assert_not_includes Person.with_pending_tasks, person
403 407 end
404 408  
405 409 should 'person has organization pending tasks' do
... ... @@ -1116,4 +1120,29 @@ class PersonTest &lt; Test::Unit::TestCase
1116 1120 assert person.receives_scrap_notification?
1117 1121 end
1118 1122  
  1123 + should 'check if person is the only admin' do
  1124 + person = fast_create(Person)
  1125 + organization = fast_create(Organization)
  1126 + organization.add_admin(person)
  1127 +
  1128 + assert person.is_last_admin?(organization)
  1129 + end
  1130 +
  1131 + should 'check if person is the last admin leaving the community' do
  1132 + person = fast_create(Person)
  1133 + organization = fast_create(Organization)
  1134 + organization.add_admin(person)
  1135 +
  1136 + assert person.is_last_admin_leaving?(organization, [])
  1137 + assert !person.is_last_admin_leaving?(organization, [Role.find_by_key('profile_admin')])
  1138 + end
  1139 +
  1140 + should 'return unique members of a community' do
  1141 + person = fast_create(Person)
  1142 + community = fast_create(Community)
  1143 + community.add_member(person)
  1144 +
  1145 + assert_equal [person], Person.members_of(community)
  1146 + assert_equal 1, Person.members_of(community).count
  1147 + end
1119 1148 end
... ...
test/unit/profile_test.rb
... ... @@ -1406,7 +1406,12 @@ class ProfileTest &lt; Test::Unit::TestCase
1406 1406  
1407 1407 should 'provide URL to leave' do
1408 1408 profile = build(Profile, :identifier => 'testprofile')
1409   - assert_equal({ :profile => 'testprofile', :controller => 'profile', :action => 'leave'}, profile.leave_url)
  1409 + assert_equal({ :profile => 'testprofile', :controller => 'profile', :action => 'leave', :reload => false}, profile.leave_url)
  1410 + end
  1411 +
  1412 + should 'provide URL to leave with reload' do
  1413 + profile = build(Profile, :identifier => 'testprofile')
  1414 + assert_equal({ :profile => 'testprofile', :controller => 'profile', :action => 'leave', :reload => true}, profile.leave_url(true))
1410 1415 end
1411 1416  
1412 1417 should 'provide URL to join' do
... ... @@ -1743,26 +1748,25 @@ class ProfileTest &lt; Test::Unit::TestCase
1743 1748 end
1744 1749  
1745 1750 should "return one member on label if the profile has one member" do
1746   - p = fast_create(Person)
1747   - c = fast_create(Community)
1748   - c.add_member(p)
1749   - assert_equal 1, c.members.count
1750   - assert_equal "one member", c.more_popular_label
  1751 + person = fast_create(Person)
  1752 + community = fast_create(Community)
  1753 + community.add_member(person)
  1754 +
  1755 + assert_equal "one member", community.more_popular_label
1751 1756 end
1752 1757  
1753 1758 should "return the number of members on label if the profile has more than one member" do
1754   - p1 = fast_create(Person)
1755   - p2 = fast_create(Person)
1756   - c = fast_create(Community)
1757   - c.add_member(p1)
1758   - c.add_member(p2)
1759   - assert_equal 2, c.members.count
1760   - assert_equal "2 members", c.more_popular_label
  1759 + person1 = fast_create(Person)
  1760 + person2 = fast_create(Person)
  1761 + community = fast_create(Community)
1761 1762  
1762   - p3 = fast_create(Person)
1763   - c.add_member(p3)
1764   - assert_equal 3, c.members.count
1765   - assert_equal "3 members", c.more_popular_label
  1763 + community.add_member(person1)
  1764 + community.add_member(person2)
  1765 + assert_equal "2 members", community.more_popular_label
  1766 +
  1767 + person3 = fast_create(Person)
  1768 + community.add_member(person3)
  1769 + assert_equal "3 members", community.more_popular_label
1766 1770 end
1767 1771  
1768 1772 should 'provide list of galleries' do
... ...