openproject-meeting 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +15 -0
  2. data/CHANGELOG.md +46 -0
  3. data/README.md +82 -0
  4. data/app/assets/images/meeting/agenda.png +0 -0
  5. data/app/assets/images/meeting/meeting.png +0 -0
  6. data/app/assets/images/meeting/minutes.png +0 -0
  7. data/app/assets/stylesheets/meeting/meeting.css.erb +39 -0
  8. data/app/controllers/meeting_agendas_controller.rb +33 -0
  9. data/app/controllers/meeting_contents_controller.rb +95 -0
  10. data/app/controllers/meeting_minutes_controller.rb +22 -0
  11. data/app/controllers/meetings_controller.rb +150 -0
  12. data/app/helpers/meeting_contents_helper.rb +68 -0
  13. data/app/helpers/meetings_helper.rb +16 -0
  14. data/app/mailers/meeting_mailer.rb +28 -0
  15. data/app/models/meeting.rb +163 -0
  16. data/app/models/meeting_agenda.rb +95 -0
  17. data/app/models/meeting_content.rb +67 -0
  18. data/app/models/meeting_minutes.rb +81 -0
  19. data/app/models/meeting_participant.rb +45 -0
  20. data/app/views/hooks/_activity_index_head.html.erb +13 -0
  21. data/app/views/meeting_contents/_form.html.erb +33 -0
  22. data/app/views/meeting_contents/_show.html.erb +40 -0
  23. data/app/views/meeting_contents/diff.html.erb +33 -0
  24. data/app/views/meeting_contents/history.html.erb +49 -0
  25. data/app/views/meeting_contents/show.html.erb +13 -0
  26. data/app/views/meeting_mailer/content_for_review.html.erb +23 -0
  27. data/app/views/meeting_mailer/content_for_review.text.erb +9 -0
  28. data/app/views/meetings/_form.html.erb +61 -0
  29. data/app/views/meetings/edit.html.erb +17 -0
  30. data/app/views/meetings/index.html.erb +51 -0
  31. data/app/views/meetings/new.html.erb +17 -0
  32. data/app/views/meetings/show.html.erb +48 -0
  33. data/app/views/shared/_meeting_header.html.erb +3 -0
  34. data/config/locales/de.yml +47 -0
  35. data/config/locales/en.yml +47 -0
  36. data/config/routes.rb +53 -0
  37. data/db/migrate/20110106210555_create_meetings.rb +29 -0
  38. data/db/migrate/20110106221214_create_meeting_contents.rb +29 -0
  39. data/db/migrate/20110106221946_create_meeting_content_versions.rb +20 -0
  40. data/db/migrate/20110108230721_create_meeting_participants.rb +30 -0
  41. data/db/migrate/20110224180804_add_lock_to_meeting_content.rb +24 -0
  42. data/db/migrate/20110819162852_create_initial_meeting_journals.rb +49 -0
  43. data/db/migrate/20111605171815_merge_meeting_content_versions_with_journals.rb +65 -0
  44. data/db/migrate/20130731151542_remove_meeting_role_id_from_meeting_participants.rb +20 -0
  45. data/lib/open_project/meeting.rb +16 -0
  46. data/lib/open_project/meeting/engine.rb +101 -0
  47. data/lib/open_project/meeting/hooks.rb +17 -0
  48. data/lib/open_project/meeting/patches/project_patch.rb +24 -0
  49. data/lib/open_project/meeting/version.rb +16 -0
  50. data/lib/openproject-meeting.rb +12 -0
  51. data/spec/controllers/meetings_controller_spec.rb +90 -0
  52. data/spec/factories/meeting_agenda_factory.rb +16 -0
  53. data/spec/factories/meeting_agenda_journal_factory.rb +18 -0
  54. data/spec/factories/meeting_factory.rb +18 -0
  55. data/spec/factories/meeting_minutes_factory.rb +16 -0
  56. data/spec/factories/meeting_minutes_journal_factory.rb +18 -0
  57. data/spec/factories/meeting_participant_factory.rb +17 -0
  58. data/spec/mailers/meeting_mailer_spec.rb +101 -0
  59. data/spec/models/meeting_agenda_journal_spec.rb +21 -0
  60. data/spec/models/meeting_agenda_spec.rb +52 -0
  61. data/spec/models/meeting_minutes_journal_spec.rb +21 -0
  62. data/spec/models/meeting_minutes_spec.rb +44 -0
  63. data/spec/models/meeting_spec.rb +168 -0
  64. data/spec/models/user_deletion_spec.rb +186 -0
  65. data/spec/spec_helper.rb +14 -0
  66. data/spec/support/plugin_spec_helper.rb +47 -0
  67. metadata +158 -0
@@ -0,0 +1,21 @@
1
+ #-- copyright
2
+ # OpenProject is a project management system.
3
+ #
4
+ # Copyright (C) 2012-2013 the OpenProject Team
5
+ #
6
+ # This program is free software; you can redistribute it and/or
7
+ # modify it under the terms of the GNU General Public License version 3.
8
+ #
9
+ # See doc/COPYRIGHT.rdoc for more details.
10
+ #++
11
+
12
+ require File.dirname(__FILE__) + '/../spec_helper'
13
+ require 'meeting_minutes'
14
+
15
+ describe MeetingAgendaJournal do
16
+ include PluginSpecHelper
17
+
18
+ let(:journal) { FactoryGirl.build(:meeting_agenda_journal) }
19
+
20
+ it_should_behave_like "customized journal class"
21
+ end
@@ -0,0 +1,52 @@
1
+ #-- copyright
2
+ # OpenProject is a project management system.
3
+ #
4
+ # Copyright (C) 2012-2013 the OpenProject Team
5
+ #
6
+ # This program is free software; you can redistribute it and/or
7
+ # modify it under the terms of the GNU General Public License version 3.
8
+ #
9
+ # See doc/COPYRIGHT.rdoc for more details.
10
+ #++
11
+
12
+ require File.dirname(__FILE__) + '/../spec_helper'
13
+
14
+ describe "MeetingAgenda" do
15
+ before(:each) do
16
+ @a = FactoryGirl.build :meeting_agenda, :text => "Some content...\n\nMore content!\n\nExtraordinary content!!"
17
+ end
18
+
19
+ # TODO: Test the right user and messages are set in the history
20
+ describe "#lock!" do
21
+ it "locks the agenda" do
22
+ @a.save
23
+ @a.reload
24
+ @a.lock!
25
+ @a.reload
26
+ @a.locked.should be_true
27
+ end
28
+ end
29
+
30
+ describe "#unlock!" do
31
+ it "unlocks the agenda" do
32
+ @a.locked = true
33
+ @a.save
34
+ @a.reload
35
+ @a.unlock!
36
+ @a.reload
37
+ @a.locked.should be_false
38
+ end
39
+ end
40
+
41
+ # a meeting agenda is editable when it is not locked
42
+ describe "#editable?" do
43
+ it "is editable when not locked" do
44
+ @a.locked = false
45
+ @a.editable?.should be_true
46
+ end
47
+ it "is not editable when locked" do
48
+ @a.locked = true
49
+ @a.editable?.should be_false
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,21 @@
1
+ #-- copyright
2
+ # OpenProject is a project management system.
3
+ #
4
+ # Copyright (C) 2012-2013 the OpenProject Team
5
+ #
6
+ # This program is free software; you can redistribute it and/or
7
+ # modify it under the terms of the GNU General Public License version 3.
8
+ #
9
+ # See doc/COPYRIGHT.rdoc for more details.
10
+ #++
11
+
12
+ require File.dirname(__FILE__) + '/../spec_helper'
13
+ require 'meeting_minutes'
14
+
15
+ describe MeetingMinutesJournal do
16
+ include PluginSpecHelper
17
+
18
+ let(:journal) { FactoryGirl.build(:meeting_minutes_journal) }
19
+
20
+ it_should_behave_like "customized journal class"
21
+ end
@@ -0,0 +1,44 @@
1
+ #-- copyright
2
+ # OpenProject is a project management system.
3
+ #
4
+ # Copyright (C) 2012-2013 the OpenProject Team
5
+ #
6
+ # This program is free software; you can redistribute it and/or
7
+ # modify it under the terms of the GNU General Public License version 3.
8
+ #
9
+ # See doc/COPYRIGHT.rdoc for more details.
10
+ #++
11
+
12
+ require File.dirname(__FILE__) + '/../spec_helper'
13
+
14
+ describe "MeetingMinutes" do
15
+ before do
16
+ @min = FactoryGirl.build :meeting_minutes
17
+ end
18
+
19
+ # meeting minutes are editable when the meeting agenda is locked
20
+ describe "#editable?" do
21
+ before(:each) do
22
+ @mee = FactoryGirl.build :meeting
23
+ @min.meeting = @mee
24
+ end
25
+ describe "with no agenda present" do
26
+ it "is not editable" do
27
+ @min.editable?.should be_false
28
+ end
29
+ end
30
+ describe "with an agenda present" do
31
+ before(:each) do
32
+ @a = FactoryGirl.build :meeting_agenda
33
+ @mee.agenda = @a
34
+ end
35
+ it "is not editable when the agenda is open" do
36
+ @min.editable?.should be_false
37
+ end
38
+ it "is editable when the agenda is closed" do
39
+ @a.lock!
40
+ @min.editable?.should be_true
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,168 @@
1
+ #-- copyright
2
+ # OpenProject is a project management system.
3
+ #
4
+ # Copyright (C) 2012-2013 the OpenProject Team
5
+ #
6
+ # This program is free software; you can redistribute it and/or
7
+ # modify it under the terms of the GNU General Public License version 3.
8
+ #
9
+ # See doc/COPYRIGHT.rdoc for more details.
10
+ #++
11
+
12
+ require File.dirname(__FILE__) + '/../spec_helper'
13
+
14
+ describe Meeting do
15
+ it {should belong_to :project}
16
+ it {should belong_to :author}
17
+ it {should validate_presence_of :title}
18
+ it {should validate_presence_of :start_time}
19
+ it {pending; should accept_nested_attributes_for :participants} # geht das?
20
+
21
+ let(:project) { FactoryGirl.create(:project) }
22
+ let(:user1) { FactoryGirl.create(:user) }
23
+ let(:user2) { FactoryGirl.create(:user) }
24
+ let(:meeting) { FactoryGirl.create(:meeting, :project => project, :author => user1) }
25
+ let(:agenda) do
26
+ meeting.create_agenda :text => "Meeting Agenda text"
27
+ meeting.agenda(true) # avoiding stale object errors
28
+ end
29
+
30
+ let(:role) { FactoryGirl.create(:role, :permissions => [:view_meetings]) }
31
+
32
+ before do
33
+ @m = FactoryGirl.build :meeting, :title => "dingens"
34
+ end
35
+
36
+ describe "to_s" do
37
+ it {@m.to_s.should == "dingens"}
38
+ end
39
+
40
+ describe "start_date" do
41
+ it {@m.start_date.should == Date.tomorrow}
42
+ end
43
+
44
+ describe "start_month" do
45
+ it {@m.start_month.should == Date.tomorrow.month}
46
+ end
47
+
48
+ describe "start_year" do
49
+ it {@m.start_year.should == Date.tomorrow.year}
50
+ end
51
+
52
+ describe "end_time" do
53
+ it {@m.end_time.should == Date.tomorrow + 11.hours}
54
+ end
55
+
56
+ describe "time-sorted finder" do
57
+ it {pending}
58
+ end
59
+
60
+ describe "Journalized Objects" do
61
+ before(:each) do
62
+ @project ||= FactoryGirl.create(:project_with_types)
63
+ @current = FactoryGirl.create(:user, :login => "user1", :mail => "user1@users.com")
64
+ User.stub!(:current).and_return(@current)
65
+ end
66
+
67
+ it 'should work with meeting' do
68
+ @meeting ||= FactoryGirl.create(:meeting, :title => "Test", :project => @project, :author => @current)
69
+
70
+ initial_journal = @meeting.journals.first
71
+ recreated_journal = @meeting.recreate_initial_journal!
72
+ initial_journal.identical?(recreated_journal).should be true
73
+ end
74
+ end
75
+
76
+ describe "all_possible_participants" do
77
+ describe "WITH a user having the view_meetings permission" do
78
+ before do
79
+ project.add_member user1, [role]
80
+ project.save!
81
+ end
82
+
83
+ it "should contain the user" do
84
+ meeting.all_possible_participants.should == [user1]
85
+ end
86
+ end
87
+
88
+ describe "WITH a user not having the view_meetings permission" do
89
+ let(:role2) { FactoryGirl.create(:role, :permissions => []) }
90
+
91
+ before do
92
+ # adding both users so that the author is valid
93
+ project.add_member user1, [role]
94
+ project.add_member user2, [role2]
95
+
96
+ project.save!
97
+ end
98
+
99
+ it "should not contain the user" do
100
+ meeting.all_possible_participants.include?(user2).should be_false
101
+ end
102
+ end
103
+
104
+ end
105
+
106
+ describe "participants and author as watchers" do
107
+ before do
108
+ project.add_member user1, [role]
109
+ project.add_member user2, [role]
110
+
111
+ project.save!
112
+
113
+ meeting.participants.build(:user => user2)
114
+ meeting.save!
115
+ end
116
+
117
+ it { meeting.watchers.collect(&:user).should =~ [user1, user2] }
118
+ end
119
+
120
+ describe :close_agenda_and_copy_to_minutes do
121
+ before do
122
+ agenda #creating it
123
+
124
+ meeting.close_agenda_and_copy_to_minutes!
125
+ end
126
+
127
+ it "should create a meeting with the agenda's text" do
128
+ meeting.minutes.text.should == meeting.agenda.text
129
+ end
130
+
131
+ it "should close the agenda" do
132
+ meeting.agenda.locked?.should be_true
133
+ end
134
+ end
135
+
136
+ describe "Copied meetings" do
137
+ before do
138
+ project.add_member user1, [role]
139
+ project.add_member user2, [role]
140
+
141
+ project.save!
142
+
143
+ meeting.start_time = DateTime.new(2013,3,27,15,35)
144
+ meeting.participants.build(:user => user2)
145
+ meeting.save!
146
+ end
147
+
148
+ it "should have the same start_time as the original meeting" do
149
+ copy = meeting.copy({})
150
+ copy.start_time.should == meeting.start_time
151
+ end
152
+
153
+ it "should delete the copied meeting author if no author is given as parameter" do
154
+ copy = meeting.copy({})
155
+ copy.author.should be_nil
156
+ end
157
+
158
+ it "should set the author to the provided author if one is given" do
159
+ copy = meeting.copy :author => user2
160
+ copy.author.should == user2
161
+ end
162
+
163
+ it "should clear participant ids and attended flags for all copied attendees" do
164
+ copy = meeting.copy({})
165
+ copy.participants.all?{ |p| p.id.nil? && !p.attended }.should be_true
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,186 @@
1
+ #-- copyright
2
+ # OpenProject is a project management system.
3
+ #
4
+ # Copyright (C) 2012-2013 the OpenProject Team
5
+ #
6
+ # This program is free software; you can redistribute it and/or
7
+ # modify it under the terms of the GNU General Public License version 3.
8
+ #
9
+ # See doc/COPYRIGHT.rdoc for more details.
10
+ #++
11
+
12
+ require File.dirname(__FILE__) + '/../spec_helper'
13
+
14
+ describe User, "#destroy" do
15
+ let(:user) { FactoryGirl.create(:user) }
16
+ let(:user2) { FactoryGirl.create(:user) }
17
+ let(:substitute_user) { DeletedUser.first }
18
+ let(:project) do
19
+ project = FactoryGirl.create(:valid_project)
20
+ # FactoryGirl.create(:member, :project => project,
21
+ # :user => user,
22
+ # :roles => [FactoryGirl.build(:role)])
23
+ # FactoryGirl.create(:member, :project => project,
24
+ # :user => user2,
25
+ # :roles => [FactoryGirl.build(:role)])
26
+ project
27
+ end
28
+
29
+ let(:meeting) { FactoryGirl.create(:meeting, :project => project,
30
+ :author => user2) }
31
+ let(:participant) { FactoryGirl.create(:meeting_participant, :user => user,
32
+ :meeting => meeting,
33
+ :invited => true,
34
+ :attended => true) }
35
+
36
+ before do
37
+ user
38
+ user2
39
+ end
40
+
41
+ shared_examples_for "updated journalized associated object" do
42
+ before do
43
+ User.stub(:current).and_return(user2)
44
+ associations.each do |association|
45
+ associated_instance.send(association.to_s + "=", user2)
46
+ end
47
+ associated_instance.save!
48
+
49
+ User.stub(:current).and_return(user) # in order to have the content journal created by the user
50
+ associated_instance.reload
51
+ associations.each do |association|
52
+ associated_instance.send(association.to_s + "=", user)
53
+ end
54
+ associated_instance.save!
55
+
56
+ user.destroy
57
+ associated_instance.reload
58
+ end
59
+
60
+ it { associated_class.find_by_id(associated_instance.id).should == associated_instance }
61
+ it "should replace the user on all associations" do
62
+ associations.each do |association|
63
+ associated_instance.send(association).should == substitute_user
64
+ end
65
+ end
66
+ it { associated_instance.journals.first.user.should == user2 }
67
+ it "should update first journal changes" do
68
+ associations.each do |association|
69
+ associated_instance.journals.first.changed_data[association.to_s + "_id"].last.should == user2.id
70
+ end
71
+ end
72
+ it { associated_instance.journals.last.user.should == substitute_user }
73
+ it "should update second journal changes" do
74
+ associations.each do |association|
75
+ associated_instance.journals.last.changed_data[association.to_s + "_id"].last.should == substitute_user.id
76
+ end
77
+ end
78
+ end
79
+
80
+ shared_examples_for "created journalized associated object" do
81
+ before do
82
+ User.stub(:current).and_return(user) # in order to have the content journal created by the user
83
+ associations.each do |association|
84
+ associated_instance.send(association.to_s + "=", user)
85
+ end
86
+ associated_instance.save!
87
+
88
+ User.stub(:current).and_return(user2)
89
+ associated_instance.reload
90
+ associations.each do |association|
91
+ associated_instance.send(association.to_s + "=", user2)
92
+ end
93
+ associated_instance.save!
94
+
95
+ user.destroy
96
+ associated_instance.reload
97
+ end
98
+
99
+ it { associated_class.find_by_id(associated_instance.id).should == associated_instance }
100
+ it "should keep the current user on all associations" do
101
+ associations.each do |association|
102
+ associated_instance.send(association).should == user2
103
+ end
104
+ end
105
+ it { associated_instance.journals.first.user.should == substitute_user }
106
+ it "should update the first journal" do
107
+ associations.each do |association|
108
+ associated_instance.journals.first.changed_data[association.to_s + "_id"].last.should == substitute_user.id
109
+ end
110
+ end
111
+ it { associated_instance.journals.last.user.should == user2 }
112
+ it "should update the last journal" do
113
+ associations.each do |association|
114
+ associated_instance.journals.last.changed_data[association.to_s + "_id"].first.should == substitute_user.id
115
+ associated_instance.journals.last.changed_data[association.to_s + "_id"].last.should == user2.id
116
+ end
117
+ end
118
+ end
119
+
120
+ describe "WHEN the user created a meeting" do
121
+ let(:associations) { [:author] }
122
+ let(:associated_instance) { FactoryGirl.build(:meeting, :project => project) }
123
+ let(:associated_class) { Meeting }
124
+
125
+ it_should_behave_like "created journalized associated object"
126
+ end
127
+
128
+ describe "WHEN the user updated a meeting" do
129
+ let(:associations) { [:author] }
130
+ let(:associated_instance) { FactoryGirl.build(:meeting, :project => project) }
131
+ let(:associated_class) { Meeting }
132
+
133
+ it_should_behave_like "updated journalized associated object"
134
+ end
135
+
136
+ describe "WHEN the user created a meeting agenda" do
137
+ let(:associations) { [:author] }
138
+ let(:associated_instance) { FactoryGirl.build(:meeting_agenda, :meeting => meeting,
139
+ :text => "lorem")}
140
+ let(:associated_class) { MeetingAgenda }
141
+
142
+ it_should_behave_like "created journalized associated object"
143
+ end
144
+
145
+ describe "WHEN the user updated a meeting agenda" do
146
+ let(:associations) { [:author] }
147
+ let(:associated_instance) { FactoryGirl.build(:meeting_agenda, :meeting => meeting,
148
+ :text => "lorem")}
149
+ let(:associated_class) { MeetingAgenda }
150
+
151
+ it_should_behave_like "updated journalized associated object"
152
+ end
153
+
154
+ describe "WHEN the user created a meeting minutes" do
155
+ let(:associations) { [:author] }
156
+ let(:associated_instance) { FactoryGirl.build(:meeting_minutes, :meeting => meeting,
157
+ :text => "lorem")}
158
+ let(:associated_class) { MeetingMinutes }
159
+
160
+ it_should_behave_like "created journalized associated object"
161
+ end
162
+
163
+ describe "WHEN the user updated a meeting minutes" do
164
+ let(:associations) { [:author] }
165
+ let(:associated_instance) { FactoryGirl.build(:meeting_minutes, :meeting => meeting,
166
+ :text => "lorem")}
167
+ let(:associated_class) { MeetingMinutes }
168
+
169
+ it_should_behave_like "updated journalized associated object"
170
+ end
171
+
172
+ describe "WHEN the user participated in a meeting" do
173
+ before do
174
+ participant
175
+ # user2 added to participants by beeing the author
176
+
177
+ user.destroy
178
+ meeting.reload
179
+ participant.reload
180
+ end
181
+
182
+ it { meeting.participants.map(&:user).should =~ [DeletedUser.first, user2] }
183
+ it { participant.invited.should be_true }
184
+ it { participant.attended.should be_true }
185
+ end
186
+ end