Patch #1333 ยป 1195.diff
test/functional/projects_controller_test.rb (working copy) | ||
---|---|---|
23 | 23 | |
24 | 24 |
class ProjectsControllerTest < Test::Unit::TestCase |
25 | 25 |
fixtures :projects, :versions, :users, :roles, :members, :issues, :journals, :journal_details, |
26 |
:trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages |
|
26 |
:trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages, :membership_activities
|
|
27 | 27 | |
28 | 28 |
def setup |
29 | 29 |
@controller = ProjectsController.new |
... | ... | |
41 | 41 |
assert assigns(:project_tree).has_key?(Project.find(1)) |
42 | 42 |
# Subproject in corresponding value |
43 | 43 |
assert assigns(:project_tree)[Project.find(1)].include?(Project.find(3)) |
44 |
end
|
|
45 |
|
|
46 |
def test_show_by_id
|
|
47 |
get :show, :id => 1
|
|
44 |
end |
|
45 |
|
|
46 |
def test_show_by_id |
|
47 |
get :show, :id => 1 |
|
48 | 48 |
assert_response :success |
49 |
assert_template 'show'
|
|
50 |
assert_not_nil assigns(:project)
|
|
49 |
assert_template 'show' |
|
50 |
assert_not_nil assigns(:project) |
|
51 | 51 |
end |
52 | 52 | |
53 | 53 |
def test_show_by_identifier |
... | ... | |
95 | 95 |
assert_response :success |
96 | 96 |
assert_template 'destroy' |
97 | 97 |
assert_not_nil Project.find_by_id(1) |
98 |
end
|
|
98 |
end |
|
99 | 99 | |
100 | 100 |
def test_post_destroy |
101 | 101 |
@request.session[:user_id] = 1 # admin |
... | ... | |
103 | 103 |
assert_redirected_to 'admin/projects' |
104 | 104 |
assert_nil Project.find_by_id(1) |
105 | 105 |
end |
106 |
|
|
107 |
def test_list_files |
|
108 |
get :list_files, :id => 1 |
|
109 |
assert_response :success |
|
110 |
assert_template 'list_files' |
|
111 |
assert_not_nil assigns(:versions) |
|
112 |
end |
|
113 |
|
|
114 |
def test_changelog |
|
115 |
get :changelog, :id => 1 |
|
116 |
assert_response :success |
|
117 |
assert_template 'changelog' |
|
118 |
assert_not_nil assigns(:versions) |
|
106 |
|
|
107 |
def test_list_files |
|
108 |
get :list_files, :id => 1 |
|
109 |
assert_response :success |
|
110 |
assert_template 'list_files' |
|
111 |
assert_not_nil assigns(:versions) |
|
119 | 112 |
end |
113 | ||
114 |
def test_changelog |
|
115 |
get :changelog, :id => 1 |
|
116 |
assert_response :success |
|
117 |
assert_template 'changelog' |
|
118 |
assert_not_nil assigns(:versions) |
|
119 |
end |
|
120 | 120 |
|
121 | 121 |
def test_roadmap |
122 | 122 |
get :roadmap, :id => 1 |
... | ... | |
178 | 178 |
} |
179 | 179 |
end |
180 | 180 |
|
181 |
def test_membership_activity |
|
182 |
@request.session[:user_id] = 1 # admin |
|
183 |
get :activity, :id => 1, :from => '2006-08-09' |
|
184 |
assert_not_nil assigns(:events_by_day) |
|
185 |
|
|
186 |
['Dave Lopper', 'Dave2 Lopper2', 'John Smith', 'John Smith'].each_with_index do |name, i| |
|
187 |
assert_select "dt:nth-of-type(#{i + 8}) a", name |
|
188 |
end |
|
189 |
|
|
190 |
assert_select "dt.member-destroy a", "Dave2 Lopper2" |
|
191 |
assert_select "dt.member-edit a", "Dave Lopper" |
|
192 | ||
193 |
end |
|
194 |
|
|
181 | 195 |
def test_activity_with_subprojects |
182 | 196 |
get :activity, :id => 1, :with_subprojects => 1 |
183 | 197 |
assert_response :success |
test/functional/users_controller_test.rb (working copy) | ||
---|---|---|
22 | 22 |
class UsersController; def rescue_action(e) raise e end; end |
23 | 23 | |
24 | 24 |
class UsersControllerTest < Test::Unit::TestCase |
25 |
fixtures :users, :projects, :members |
|
25 |
fixtures :users, :projects, :members, :membership_activities
|
|
26 | 26 |
|
27 | 27 |
def setup |
28 | 28 |
@controller = UsersController.new |
... | ... | |
52 | 52 |
:membership => { :role_id => 2} |
53 | 53 |
assert_redirected_to 'users/edit/2' |
54 | 54 |
assert_equal 2, Member.find(1).role_id |
55 |
assert_equal 'edit', MembershipActivity.find(:first, :order => 'created_on DESC').action |
|
55 | 56 |
end |
56 | 57 |
|
57 | 58 |
def test_destroy_membership |
58 | 59 |
post :destroy_membership, :id => 2, :membership_id => 1 |
59 | 60 |
assert_redirected_to 'users/edit/2' |
60 | 61 |
assert_nil Member.find_by_id(1) |
62 |
assert_equal 'destroy', MembershipActivity.find(:first, :order => 'created_on DESC').action |
|
61 | 63 |
end |
62 | 64 |
end |
test/fixtures/membership_activities.yml (revision 0) | ||
---|---|---|
1 |
--- |
|
2 |
membership_activities_001: |
|
3 |
id: 1 |
|
4 |
project_id: 1 |
|
5 |
action: "new" |
|
6 |
user_id: 2 |
|
7 |
creator_id: 1 |
|
8 |
role_id: 1 |
|
9 |
created_on: 2006-07-19 19:35:33 +02:00 |
|
10 | ||
11 |
membership_activities_002: |
|
12 |
id: 2 |
|
13 |
project_id: 1 |
|
14 |
action: "new" |
|
15 |
user_id: 3 |
|
16 |
creator_id: 1 |
|
17 |
role_id: 3 |
|
18 |
created_on: 2006-07-19 19:35:36 +02:00 |
|
19 | ||
20 |
membership_activities_003: |
|
21 |
id: 3 |
|
22 |
project_id: 2 |
|
23 |
action: "new" |
|
24 |
user_id: 2 |
|
25 |
creator_id: 1 |
|
26 |
role_id: 2 |
|
27 |
created_on: 2006-07-19 19:35:36 +02:00 |
|
28 | ||
29 |
membership_activities_004: |
|
30 |
id: 4 |
|
31 |
project_id: 1 |
|
32 |
action: "new" |
|
33 |
user_id: 5 |
|
34 |
creator_id: 1 |
|
35 |
role_id: 2 |
|
36 |
created_on: 2006-07-19 19:35:36 +02:00 |
|
37 | ||
38 |
membership_activities_005: |
|
39 |
id: 5 |
|
40 |
project_id: 5 |
|
41 |
action: "new" |
|
42 |
user_id: 2 |
|
43 |
creator_id: 1 |
|
44 |
role_id: 1 |
|
45 |
created_on: 2006-07-19 19:35:33 +02:00 |
|
46 |
|
|
47 |
membership_activities_006: |
|
48 |
id: 6 |
|
49 |
project_id: 1 |
|
50 |
action: "destroy" |
|
51 |
user_id: 5 |
|
52 |
creator_id: 1 |
|
53 |
role_id: 2 |
|
54 |
created_on: 2006-07-19 20:35:36 +02:00 |
|
55 | ||
56 |
membership_activities_007: |
|
57 |
id: 7 |
|
58 |
project_id: 1 |
|
59 |
action: "edit" |
|
60 |
user_id: 3 |
|
61 |
creator_id: 1 |
|
62 |
role_id: 2 |
|
63 |
created_on: 2006-07-19 20:35:33 +02:00 |
|
64 |
|
|
65 |
|
app/models/project.rb (working copy) | ||
---|---|---|
37 | 37 |
has_one :repository, :dependent => :destroy |
38 | 38 |
has_many :changesets, :through => :repository |
39 | 39 |
has_one :wiki, :dependent => :destroy |
40 |
has_many :membership_activities, :dependent => :destroy |
|
40 | 41 |
# Custom field for the project issues |
41 | 42 |
has_and_belongs_to_many :custom_fields, |
42 | 43 |
:class_name => 'IssueCustomField', |
... | ... | |
180 | 181 |
|
181 | 182 |
# Deletes all project's members |
182 | 183 |
def delete_all_members |
184 |
members.each do |m| |
|
185 |
MembershipActivity.create( |
|
186 |
:project_id => id, |
|
187 |
:action => 'destroy', |
|
188 |
:user_id => m.user_id, |
|
189 |
:creator_id => User.current.id, |
|
190 |
:role_id => m.role_id |
|
191 |
) |
|
192 |
end |
|
193 |
|
|
183 | 194 |
Member.delete_all(['project_id = ?', id]) |
184 | 195 |
end |
185 | 196 |
|
app/models/membership_activity.rb (revision 0) | ||
---|---|---|
1 |
# redMine - project management software |
|
2 |
# Copyright (C) 2006 Jean-Philippe Lang |
|
3 |
# |
|
4 |
# This program is free software; you can redistribute it and/or |
|
5 |
# modify it under the terms of the GNU General Public License |
|
6 |
# as published by the Free Software Foundation; either version 2 |
|
7 |
# of the License, or (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU General Public License |
|
15 |
# along with this program; if not, write to the Free Software |
|
16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
17 | ||
18 |
class MembershipActivity < ActiveRecord::Base |
|
19 |
belongs_to :project |
|
20 |
belongs_to :user |
|
21 |
belongs_to :creator, :class_name => 'User', :foreign_key => 'creator_id' |
|
22 |
belongs_to :role |
|
23 | ||
24 |
acts_as_event :title => Proc.new {|o| "#{o.user.name}" }, |
|
25 |
:description => Proc.new { |o| l(('text_' + o.action + '_member').to_sym, o.role.name) }, |
|
26 |
:author => :creator, |
|
27 |
:type => Proc.new {|o| "member-#{o.action}" }, |
|
28 |
:url => Proc.new {|o| {:controller => 'account', :action => 'show', :id => o.user.id} } |
|
29 |
end |
app/controllers/members_controller.rb (working copy) | ||
---|---|---|
20 | 20 |
before_filter :find_member, :except => :new |
21 | 21 |
before_filter :find_project, :only => :new |
22 | 22 |
before_filter :authorize |
23 |
after_filter :log_activity |
|
23 | 24 | |
24 | 25 |
def new |
25 |
@project.members << Member.new(params[:member]) if request.post? |
|
26 |
if request.post? |
|
27 |
@member = Member.new(params[:member]) |
|
28 |
@project.members << @member |
|
29 |
end |
|
30 |
|
|
26 | 31 |
respond_to do |format| |
27 |
format.html { redirect_to :action => 'settings', :tab => 'members', :id => @project } |
|
32 |
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project }
|
|
28 | 33 |
format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'projects/settings/members'} } |
29 | 34 |
end |
30 | 35 |
end |
31 | 36 |
|
32 | 37 |
def edit |
33 | 38 |
if request.post? and @member.update_attributes(params[:member]) |
34 |
respond_to do |format| |
|
39 |
respond_to do |format|
|
|
35 | 40 |
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project } |
36 | 41 |
format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'projects/settings/members'} } |
37 | 42 |
end |
... | ... | |
40 | 45 | |
41 | 46 |
def destroy |
42 | 47 |
@member.destroy |
43 |
respond_to do |format|
|
|
48 |
respond_to do |format|
|
|
44 | 49 |
format.html { redirect_to :controller => 'projects', :action => 'settings', :tab => 'members', :id => @project } |
45 | 50 |
format.js { render(:update) {|page| page.replace_html "tab-content-members", :partial => 'projects/settings/members'} } |
46 | 51 |
end |
... | ... | |
59 | 64 |
rescue ActiveRecord::RecordNotFound |
60 | 65 |
render_404 |
61 | 66 |
end |
62 |
end |
|
67 |
|
|
68 |
def log_activity |
|
69 |
MembershipActivity.create( |
|
70 |
:project_id => @member.project_id, |
|
71 |
:action => action_name, |
|
72 |
:user_id => @member.user_id, |
|
73 |
:creator_id => User.current.id, |
|
74 |
:role_id => @member.role_id |
|
75 |
) |
|
76 |
end |
|
77 |
|
|
78 |
end |
app/controllers/users_controller.rb (working copy) | ||
---|---|---|
18 | 18 |
class UsersController < ApplicationController |
19 | 19 |
layout 'base' |
20 | 20 |
before_filter :require_admin |
21 | ||
21 |
after_filter :log_activity, :only => [:edit_membership, :destroy_membership] |
|
22 |
|
|
22 | 23 |
helper :sort |
23 | 24 |
include SortHelper |
24 | 25 |
helper :custom_fields |
... | ... | |
104 | 105 |
|
105 | 106 |
def destroy_membership |
106 | 107 |
@user = User.find(params[:id]) |
107 |
Member.find(params[:membership_id]).destroy if request.post? |
|
108 |
if request.post? |
|
109 |
@membership = Member.find(params[:membership_id]) |
|
110 |
@membership.destroy |
|
111 |
end |
|
108 | 112 |
redirect_to :action => 'edit', :id => @user, :tab => 'memberships' |
109 | 113 |
end |
114 |
|
|
115 |
private |
|
116 |
def log_activity |
|
117 |
action = action_name == 'destroy_membership' ? 'destroy' : params[:membership_id] ? 'edit' : 'new' |
|
118 |
|
|
119 |
MembershipActivity.create( |
|
120 |
:project_id => @membership.project_id, |
|
121 |
:action => action, |
|
122 |
:user_id => @membership.user_id, |
|
123 |
:creator_id => User.current.id, |
|
124 |
:role_id => @membership.role_id |
|
125 |
) |
|
126 |
end |
|
127 | ||
110 | 128 |
end |
app/controllers/projects_controller.rb (working copy) | ||
---|---|---|
237 | 237 |
@date_to ||= Date.today + 1 |
238 | 238 |
@date_from = @date_to - @days |
239 | 239 |
|
240 |
@event_types = %w(issues news files documents changesets wiki_pages messages) |
|
240 |
@event_types = %w(issues news files documents changesets wiki_pages messages membership_activity)
|
|
241 | 241 |
if @project |
242 | 242 |
@event_types.delete('wiki_pages') unless @project.wiki |
243 | 243 |
@event_types.delete('changesets') unless @project.repository |
... | ... | |
316 | 316 |
@events += Message.find(:all, :include => [{:board => :project}, :author], :conditions => cond.conditions) |
317 | 317 |
end |
318 | 318 |
|
319 |
if @scope.include?('membership_activity') |
|
320 |
cond = ARCondition.new(Project.allowed_to_condition(User.current, :manage_members, :project => @project, :with_subprojects => @with_subprojects)) |
|
321 |
cond.add(["#{MembershipActivity.table_name}.created_on BETWEEN ? AND ?", @date_from, @date_to]) |
|
322 |
@events += MembershipActivity.find(:all, :include => [:project, :user, :creator], :conditions => cond.conditions) |
|
323 |
end |
|
324 |
|
|
319 | 325 |
@events_by_day = @events.group_by(&:event_date) |
320 | 326 |
|
321 | 327 |
respond_to do |format| |
lang/en.yml (working copy) | ||
---|---|---|
513 | 513 |
label_chronological_order: In chronological order |
514 | 514 |
label_reverse_chronological_order: In reverse chronological order |
515 | 515 |
label_planning: Planning |
516 |
label_membership_activity_plural: Membership activity |
|
516 | 517 | |
517 | 518 |
button_login: Login |
518 | 519 |
button_submit: Submit |
... | ... | |
596 | 597 |
text_destroy_time_entries: Delete reported hours |
597 | 598 |
text_assign_time_entries_to_project: Assign reported hours to the project |
598 | 599 |
text_reassign_time_entries: 'Reassign reported hours to this issue:' |
600 |
text_new_member: "Was added as %s" |
|
601 |
text_edit_member: "Changed roles to %s" |
|
602 |
text_destroy_member: "Was removed from the project" |
|
599 | 603 |
text_user_wrote: '%s wrote:' |
600 | 604 | |
601 | 605 |
default_role_manager: Manager |
db/migrate/095_create_membership_activities.rb (revision 0) | ||
---|---|---|
1 |
class CreateMembershipActivities < ActiveRecord::Migration |
|
2 |
def self.up |
|
3 |
create_table :membership_activities do |t| |
|
4 |
t.column :project_id, :integer |
|
5 |
t.column :action, :string |
|
6 |
t.column :user_id, :integer |
|
7 |
t.column :creator_id, :integer |
|
8 |
t.column :role_id, :integer |
|
9 |
t.column :created_on, :datetime |
|
10 |
end |
|
11 |
end |
|
12 | ||
13 |
def self.down |
|
14 |
drop_table :membership_activities |
|
15 |
end |
|
16 |
end |
vendor/plugins/acts_as_event/lib/acts_as_event.rb (working copy) | ||
---|---|---|
64 | 64 |
|
65 | 65 |
def event_url(options = {}) |
66 | 66 |
option = event_options[:url] |
67 |
(option.is_a?(Proc) ? option.call(self) : send(option)).merge(options) |
|
67 |
if option.is_a?(Proc) |
|
68 |
option.call(self) |
|
69 |
elsif option.is_a?(Symbol) |
|
70 |
send(option) |
|
71 |
else |
|
72 |
option |
|
73 |
end.merge(options) |
|
68 | 74 |
end |
69 | 75 | |
70 | 76 |
module ClassMethods |
lib/redmine.rb (working copy) | ||
---|---|---|
21 | 21 |
map.permission :select_project_modules, {:projects => :modules}, :require => :member |
22 | 22 |
map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy]}, :require => :member |
23 | 23 |
map.permission :manage_versions, {:projects => [:settings, :add_version], :versions => [:edit, :destroy]}, :require => :member |
24 |
map.permission :view_membership_activity, {} |
|
24 | 25 |
|
25 | 26 |
map.project_module :issue_tracking do |map| |
26 | 27 |
# Issue categories |
public/stylesheets/application.css (working copy) | ||
---|---|---|
194 | 194 |
dt.attachment { background-image: url(../images/attachment.png); } |
195 | 195 |
dt.document { background-image: url(../images/document.png); } |
196 | 196 |
dt.project { background-image: url(../images/projects.png); } |
197 |
dt.member-new { background-image: url(../images/user_new.png); } |
|
198 |
dt.member-edit { background-image: url(../images/user.png); } |
|
199 |
dt.member-destroy { background-image: url(../images/false.png); } |
|
197 | 200 | |
201 | ||
198 | 202 |
div#roadmap fieldset.related-issues { margin-bottom: 1em; } |
199 | 203 |
div#roadmap fieldset.related-issues ul { margin-top: 0.3em; margin-bottom: 0.3em; } |
200 | 204 |
div#roadmap .wiki h1:first-child { display: none; } |