openproject-meeting 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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