lita-standups 1.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.
@@ -0,0 +1,64 @@
1
+ module Lita
2
+ module Standups
3
+ module Models
4
+ class StandupSession < Ohm::Model
5
+
6
+ include Ohm::Callbacks
7
+ include Ohm::Timestamps
8
+ include Ohm::DataTypes
9
+
10
+ attribute :status
11
+ attribute :room
12
+ attribute :counts, Type::Hash
13
+ attribute :recipients, Type::Array
14
+ attribute :results_sent, Type::Boolean
15
+
16
+ reference :standup, Standup
17
+ reference :standup_schedule, StandupSchedule
18
+ collection :standup_responses, StandupResponse, :standup_session
19
+
20
+ index :status
21
+ index :results_sent
22
+
23
+ def before_create
24
+ self.status = 'pending'
25
+ self.results_sent = false
26
+ end
27
+
28
+ %w(pending running completed).each do |status_name|
29
+ define_method("#{status_name}!") do
30
+ self.status = status_name
31
+ end
32
+ end
33
+
34
+ def update_status
35
+ self.counts = { 'total' => 0, 'finished' => 0 }
36
+ standup_responses.each do |r|
37
+ counts[r.status] = counts[r.status].to_i + 1
38
+ counts['total'] = counts['total'] + 1
39
+ counts['finished'] = counts['finished'] + 1 if r.finished?
40
+ end
41
+ completed! if counts['total'] == counts['finished']
42
+ save
43
+ end
44
+
45
+ def report_message
46
+ standup_responses.map(&:report_message).join("\n")
47
+ end
48
+
49
+ def summary
50
+ "ID: #{id} - standup #{standup.name} ran on #{created_at.strftime('%c')}"
51
+ end
52
+
53
+ def description
54
+ messages = ["ID: #{id}"]
55
+ messages << "Standup: #{standup.name}"
56
+ messages << "Date: #{created_at.strftime('%c')}"
57
+ messages << "Total recipients: #{counts['total']}"
58
+ messages << "Total finished: #{counts['finished']}"
59
+ messages.join("\n")
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,12 @@
1
+ module Lita
2
+ module Standups
3
+ module Models
4
+ extend ActiveSupport::Autoload
5
+
6
+ autoload :Standup
7
+ autoload :StandupResponse
8
+ autoload :StandupSchedule
9
+ autoload :StandupSession
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,31 @@
1
+ module Lita
2
+ module Standups
3
+ module Wizards
4
+ class CreateStandup < Lita::Wizard
5
+
6
+ step :name,
7
+ label: 'Please give it a name:'
8
+
9
+ step :questions,
10
+ label: 'Enter the standup questions below:',
11
+ multiline: true
12
+
13
+ def finish_wizard
14
+ @standup = Models::Standup.create(
15
+ name: value_for(:name),
16
+ questions: value_for(:questions).to_s.split("\n").map(&:strip).map(&:presence).compact
17
+ )
18
+ end
19
+
20
+ def final_message
21
+ [
22
+ "You're done! Below is the summary of your standup:",
23
+ ">>>",
24
+ @standup.description
25
+ ].join("\n")
26
+ end
27
+
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,56 @@
1
+ module Lita
2
+ module Standups
3
+ module Wizards
4
+ class RunStandup < Lita::Wizard
5
+
6
+ def response
7
+ @response ||= Models::StandupResponse[meta['response_id']]
8
+ end
9
+
10
+ def session
11
+ response.standup_session
12
+ end
13
+
14
+ def standup
15
+ session.standup
16
+ end
17
+
18
+ def steps
19
+ @steps ||= standup.questions.map.with_index(1) do |question, index|
20
+ OpenStruct.new(
21
+ name: "q#{index}",
22
+ label: question,
23
+ multiline: true
24
+ )
25
+ end
26
+ end
27
+
28
+ def start_wizard
29
+ response.running!
30
+ response.save
31
+ end
32
+
33
+ def abort_wizard
34
+ response.aborted!
35
+ response.save
36
+ end
37
+
38
+ def finish_wizard
39
+ response.completed!
40
+ response.answers = values
41
+ response.save
42
+ end
43
+
44
+ def initial_message
45
+ "Hey. I'm running the '#{standup.name}' standup. Please answer the following questions " \
46
+ "in the next #{Lita::Standups::Manager::EXPIRATION_TIME} seconds"
47
+ end
48
+
49
+ def final_message
50
+ "You're done. Thanks"
51
+ end
52
+
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,53 @@
1
+ module Lita
2
+ module Standups
3
+ module Wizards
4
+ class ScheduleStandup < Lita::Wizard
5
+
6
+ step :repeat,
7
+ label: 'How often? (daily, weekly)',
8
+ options: %w(daily weekly)
9
+
10
+ step :day_of_week,
11
+ label: 'What day of week? (monday ... sunday)',
12
+ options: %w(monday tuesday wednesday thursday friday saturday sunday),
13
+ if: ->(wizard) { value_for(:repeat) == 'weekly' }
14
+
15
+ step :time,
16
+ label: 'At what time? (ex 9am)',
17
+ validate: /^([1-9]|1[0-2]|0[1-9])?(:[0-5][0-9])?\s?([aApP][mM])?$/
18
+
19
+ step :recipients,
20
+ label: 'Enter the standup members: ',
21
+ multiline: true
22
+
23
+ step :channel,
24
+ label: 'On what channel do you want to post the results to?'
25
+
26
+ def finish_wizard
27
+ @schedule = Models::StandupSchedule.create(
28
+ standup: standup,
29
+ repeat: value_for(:repeat),
30
+ day_of_week: value_for(:day_of_week),
31
+ time: value_for(:time),
32
+ recipients: value_for(:recipients).to_s.gsub("@", "").split(/[\s,\n]/m).map(&:strip).map(&:presence).compact,
33
+ channel: value_for(:channel)
34
+ )
35
+ robot.schedule_standup(@schedule)
36
+ end
37
+
38
+ def final_message
39
+ [
40
+ "You're done! Below is the summary of your scheduled standup:",
41
+ ">>>",
42
+ @schedule.description
43
+ ].join("\n")
44
+ end
45
+
46
+ def standup
47
+ @standup ||= Models::Standup[meta['standup_id']]
48
+ end
49
+
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,11 @@
1
+ module Lita
2
+ module Standups
3
+ module Wizards
4
+ extend ActiveSupport::Autoload
5
+
6
+ autoload :CreateStandup
7
+ autoload :RunStandup
8
+ autoload :ScheduleStandup
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,27 @@
1
+ require "lita"
2
+
3
+ Lita.load_locales Dir[File.expand_path(
4
+ File.join("..", "..", "locales", "*.yml"), __FILE__
5
+ )]
6
+
7
+ module Lita
8
+ module Standups
9
+ end
10
+ end
11
+
12
+ require "lita-wizard"
13
+ require "rufus-scheduler"
14
+ require "ohm"
15
+ require "ohm/contrib"
16
+ require "active_support/all"
17
+
18
+ require "lita/handlers/standups"
19
+ require "lita/standups/wizards"
20
+ require "lita/standups/models"
21
+ require "lita/standups/manager"
22
+ require "lita/standups/mixins/robot"
23
+
24
+ Lita::Handlers::Standups.template_root File.expand_path(
25
+ File.join("..", "..", "templates"),
26
+ __FILE__
27
+ )
@@ -0,0 +1,34 @@
1
+ Gem::Specification.new do |spec|
2
+ spec.name = 'lita-standups'
3
+ spec.version = '1.0.0'
4
+ spec.authors = ['Cristian Bica']
5
+ spec.email = ['cristian.bica@gmail.com']
6
+ spec.description = 'Lita standups'
7
+ spec.summary = 'Lita standups'
8
+ spec.homepage = 'https://github.com/cristianbica/lita-standups'
9
+ spec.license = 'MIT'
10
+ spec.metadata = { 'lita_plugin_type' => 'handler' }
11
+
12
+ spec.files = `git ls-files`.split($/)
13
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
14
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
15
+ spec.require_paths = ['lib']
16
+
17
+ spec.add_runtime_dependency 'lita', '>= 4.7'
18
+ spec.add_runtime_dependency 'lita-wizard'
19
+ spec.add_runtime_dependency 'rufus-scheduler'
20
+ spec.add_runtime_dependency 'ohm'
21
+ spec.add_runtime_dependency 'ohm-contrib'
22
+ spec.add_runtime_dependency 'activesupport'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.3'
25
+ spec.add_development_dependency 'pry'
26
+ spec.add_development_dependency 'guard'
27
+ spec.add_development_dependency 'guard-bundler'
28
+ spec.add_development_dependency 'guard-rspec'
29
+ spec.add_development_dependency 'rake'
30
+ spec.add_development_dependency 'rack-test'
31
+ spec.add_development_dependency 'rspec', '>= 3.0.0'
32
+ spec.add_development_dependency 'simplecov'
33
+ spec.add_development_dependency 'coveralls'
34
+ end
data/locales/en.yml ADDED
@@ -0,0 +1,4 @@
1
+ en:
2
+ lita:
3
+ handlers:
4
+ standups:
@@ -0,0 +1,17 @@
1
+ require "spec_helper"
2
+
3
+ describe Lita::Handlers::Standups, lita_handler: true do
4
+
5
+ it { is_expected.to route_command("list standups").to(:list_standups) }
6
+ it { is_expected.to route_command("create standup").to(:create_standup) }
7
+ it { is_expected.to route_command("show standup 42").to(:show_standup) }
8
+ it { is_expected.to route_command("delete standup 42").to(:delete_standup) }
9
+ it { is_expected.to route_command("schedule standup 42").to(:create_standups_schedule) }
10
+ it { is_expected.to route_command("unschedule standup 42").to(:delete_standups_schedule) }
11
+ it { is_expected.to route_command("list standups schedules").to(:show_standups_schedule) }
12
+ it { is_expected.to route_command("show standup schedule 42").to(:show_standup_schedule) }
13
+ it { is_expected.to route_command("run standup 42 with x,y").to(:run_standup) }
14
+ it { is_expected.to route_command("list standup sessions").to(:list_standup_sessions) }
15
+ it { is_expected.to route_command("show standup session 42").to(:show_standup_session) }
16
+
17
+ end
@@ -0,0 +1,191 @@
1
+ require "spec_helper"
2
+
3
+ describe Lita::Handlers::Standups, lita_handler: true, additional_lita_handlers: Lita::Handlers::Wizard do
4
+ include Lita::Standups::Fixtures
5
+
6
+ before do
7
+ Lita::User.create(2, name: "User2")
8
+ Lita::User.create(3, name: "User3")
9
+ create_standups(3)
10
+ create_standup_schedules(3)
11
+ create_standup_runs(3)
12
+ robot.registry.register_hook(:validate_route, Lita::Extensions::Wizard)
13
+ end
14
+
15
+ after do
16
+ cleanup_data
17
+ end
18
+
19
+ context "managing standups" do
20
+
21
+ it "should list standups" do
22
+ send_command("list standups")
23
+ expect(replies.last).to match(/found: 3/)
24
+ expect(replies.last).to match(/ID: 1/)
25
+ end
26
+
27
+ it "should start the create standup wizard" do
28
+ expect_any_instance_of(described_class).to \
29
+ receive(:start_wizard).with(Lita::Standups::Wizards::CreateStandup, anything)
30
+ send_command("create standup")
31
+ end
32
+
33
+ it "should create a standup" do
34
+ expect do
35
+ send_command("create standup")
36
+ send_message("test-name", privately: true)
37
+ send_message("q1", privately: true)
38
+ send_message("q2", privately: true)
39
+ send_message("done", privately: true)
40
+ end.to change { Lita::Standups::Models::Standup.all.count }.by(1)
41
+ end
42
+
43
+ it "should show the details of a standup" do
44
+ send_command("show standup 1")
45
+ expect(replies.last).to match(/ID: 1/)
46
+ expect(replies.last).to match(/Name:/)
47
+ end
48
+
49
+ it "should show an error message when trying to get the details of an inexisting standup" do
50
+ send_command("show standup 100")
51
+ expect(replies.last).to match(/I couldn't find a standup/)
52
+ end
53
+
54
+ it "should delete a standup" do
55
+ expect do
56
+ send_command("delete standup 1")
57
+ end.to change { Lita::Standups::Models::Standup.all.count }.by(-1)
58
+ expect(replies.last).to match(/Standup with ID 1 has been deleted/)
59
+ end
60
+
61
+ it "should show an error message when trying to delete an inexisting standup" do
62
+ expect do
63
+ send_command("delete standup 100")
64
+ end.not_to change { Lita::Standups::Models::Standup.all.count }
65
+ expect(replies.last).to match(/I couldn't find a standup/)
66
+ end
67
+ end
68
+
69
+ context "managing standups schedules" do
70
+ it "should start the wizard when trying to schedule a standup" do
71
+ expect_any_instance_of(described_class).to \
72
+ receive(:start_wizard).with(Lita::Standups::Wizards::ScheduleStandup, anything, { "standup_id" => "1" })
73
+ send_command("schedule standup 1")
74
+ end
75
+
76
+ it "should show an error message when trying to schedule an inexisting standup" do
77
+ expect do
78
+ send_command("schedule standup 100")
79
+ end.not_to change { Lita::Standups::Models::StandupSchedule.all.count }
80
+ expect(replies.last).to match(/I couldn't find a standup/)
81
+ end
82
+
83
+ it "should schedule a daily standup" do
84
+ expect do
85
+ send_command("schedule standup 1")
86
+ send_message("daily", privately: true)
87
+ send_message("12:42pm", privately: true)
88
+ send_message("user", privately: true)
89
+ send_message("done", privately: true)
90
+ send_message("#a", privately: true)
91
+ end.to change { Lita::Standups::Models::StandupSchedule.all.count }.by(1)
92
+ expect(replies.last).to match(/You're done!/)
93
+ end
94
+
95
+ it "should schedule a weekly standup" do
96
+ expect do
97
+ send_command("schedule standup 1")
98
+ send_message("weekly", privately: true)
99
+ send_message("tuesday", privately: true)
100
+ send_message("12:42pm", privately: true)
101
+ send_message("user", privately: true)
102
+ send_message("done", privately: true)
103
+ send_message("#a", privately: true)
104
+ end.to change { Lita::Standups::Models::StandupSchedule.all.count }.by(1)
105
+ expect(replies.last).to match(/You're done!/)
106
+ end
107
+
108
+ it "should delete a standup schedule" do
109
+ expect do
110
+ send_command("unschedule standup 1")
111
+ end.to change { Lita::Standups::Models::StandupSchedule.all.count }.by(-1)
112
+ expect(replies.last).to match(/Schedule with ID 1 has been deleted/)
113
+ end
114
+
115
+ it "should show an error message when trying to delete an inexisting standup schedule" do
116
+ expect do
117
+ send_command("unschedule standup 100")
118
+ end.not_to change { Lita::Standups::Models::StandupSchedule.all.count }
119
+ expect(replies.last).to match(/I couldn't find a scheduled standup/)
120
+ end
121
+
122
+ it "should list all standup schedules" do
123
+ send_command("list standups schedules")
124
+ expect(replies.last).to match(/Scheduled standups found: 3/)
125
+ end
126
+
127
+ it "should show details of a standup schedule" do
128
+ send_command("show standup schedule 1")
129
+ expect(replies.last).to match(/Here are the details/)
130
+ expect(replies.last).to match(/ID: 1/)
131
+ end
132
+
133
+ it "should show an error message when trying to get the details of an inexisting standup schedule" do
134
+ send_command("show standup schedule 100")
135
+ expect(replies.last).to match(/I couldn't find a scheduled standup/)
136
+ end
137
+ end
138
+
139
+ context "running standups" do
140
+ it "should ask the robot to run a standup" do
141
+ expect(robot).to receive(:run_standup).with("1", %w(user), anything)
142
+ send_command("run standup 1 with user")
143
+ end
144
+
145
+ it "should show an error message when trying to run an inexisting standup schedule" do
146
+ send_command("run standup 100 with user")
147
+ expect(replies.last).to match(/I couldn't find a standup/)
148
+ end
149
+
150
+ it "should run a standup" do
151
+ expect do
152
+ send_command("run standup 1 with 1")
153
+ send_message("a1", privately: true)
154
+ send_message("done", privately: true)
155
+ send_message("a2", privately: true)
156
+ send_message("done", privately: true)
157
+ send_message("a3", privately: true)
158
+ send_message("done", privately: true)
159
+ end.to change { Lita::Standups::Models::StandupSession.all.count }.by(1)
160
+ end
161
+
162
+ it "should be able to abort a running standup" do
163
+ send_command("run standup 1 with 1")
164
+ send_message("a1", privately: true)
165
+ send_message("done", privately: true)
166
+ send_message("abort", privately: true)
167
+ expect(replies.last).to match(/Aborting/)
168
+ end
169
+
170
+ it "should list all standup sessions" do
171
+ send_command("list standup sessions")
172
+ expect(replies.last).to match(/Sessions found: 3. Here they are/)
173
+ end
174
+
175
+ it "should show the details of a standup sessions" do
176
+ send_command("show standup session 1")
177
+ expect(replies.last).to match(/Here are the standup session details/)
178
+ expect(replies.last).to match(/ID: 1/)
179
+ end
180
+
181
+ it "should show an error message when trying to show an inexisting standup session" do
182
+ send_command("show standup session 100")
183
+ expect(replies.last).to match(/I couldn't find a standup session/)
184
+ end
185
+
186
+
187
+ end
188
+
189
+
190
+
191
+ end
@@ -0,0 +1,92 @@
1
+ require "spec_helper"
2
+
3
+ describe Lita::Standups::Manager do
4
+ include Lita::Standups::Fixtures
5
+
6
+ let(:robot) { Lita::Robot.new }
7
+
8
+ before do
9
+ Lita::User.create(2, name: "User2")
10
+ Lita::User.create(3, name: "User3")
11
+ create_standups(3)
12
+ create_standup_schedules(3)
13
+ create_standup_runs(2)
14
+ end
15
+
16
+ after do
17
+ cleanup_data
18
+ end
19
+
20
+ context "class methods" do
21
+ it "should create a session and call run on a new manager instance when running a standup" do
22
+ expect_any_instance_of(described_class).to receive(:run)
23
+ expect do
24
+ described_class.run(robot: robot, standup_id: "1", recipients: %w(user1), room: "#a")
25
+ end.to change { Lita::Standups::Models::StandupSession.all.count }.by(1)
26
+ end
27
+
28
+ it "should create a session and call run on a new manager instance when running a standup schedule" do
29
+ expect_any_instance_of(described_class).to receive(:run)
30
+ expect do
31
+ described_class.run_schedule(robot: robot, schedule_id: "1")
32
+ end.to change { Lita::Standups::Models::StandupSession.all.count }.by(1)
33
+ end
34
+
35
+ it "should mark expired reponses as expired" do
36
+ responses = [Lita::Standups::Models::StandupResponse[1], Lita::Standups::Models::StandupResponse[2]]
37
+ responses[0].created_at = Time.now - 100_000
38
+ allow(Lita::Standups::Models::StandupResponse).to receive_message_chain(:find, :union).and_return(responses)
39
+ expect(responses[0]).to receive(:expired!)
40
+ expect(responses[0]).to receive(:save)
41
+ expect(Lita::Wizard).to receive(:cancel_wizard)
42
+ expect(robot).to receive(:send_message)
43
+ described_class.abort_expired_standups(robot: robot)
44
+ end
45
+
46
+ it "should post results on completed sessions" do
47
+ allow(Lita::Standups::Models::StandupSession).to receive(:find).and_return(["dummy"])
48
+ expect_any_instance_of(described_class).to receive(:post_results)
49
+ described_class.complete_finished_standups(robot: robot)
50
+ end
51
+ end
52
+
53
+ context "instance methods" do
54
+ context "running a session" do
55
+ it "should create a responses for each user" do
56
+ allow(Lita::Standups::Wizards::RunStandup).to receive(:start)
57
+ expect do
58
+ described_class.run(robot: robot, standup_id: "1", recipients: %w(1 2), room: "#a")
59
+ end.to change { Lita::Standups::Models::StandupResponse.all.count }.by(2)
60
+ end
61
+
62
+ it "should start the run wizard" do
63
+ expect(Lita::Standups::Wizards::RunStandup).to receive(:start)
64
+ described_class.run(robot: robot, standup_id: "1", recipients: %w(1), room: "#a")
65
+ end
66
+ end
67
+
68
+ context "posting results" do
69
+ it "should send a message" do
70
+ session = Lita::Standups::Models::StandupSession[1]
71
+ expect(robot).to receive(:send_message)
72
+ described_class.new(robot: robot, session: session).post_results
73
+ end
74
+
75
+ it "should set the results_sent flag on the session" do
76
+ session = Lita::Standups::Models::StandupSession[1]
77
+ allow(robot).to receive(:send_message)
78
+ expect do
79
+ described_class.new(robot: robot, session: session).post_results
80
+ end.to change { Lita::Standups::Models::StandupSession[1].results_sent }.to(true)
81
+ end
82
+
83
+ it "sholdn't post anything if already posted" do
84
+ session = Lita::Standups::Models::StandupSession[1]
85
+ session.results_sent = true
86
+ session.save
87
+ expect(robot).to receive(:send_message).never
88
+ described_class.new(robot: robot, session: session).post_results
89
+ end
90
+ end
91
+ end
92
+ end