lita-slack-standup 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +674 -0
- data/README.md +35 -0
- data/Rakefile +6 -0
- data/lib/lita-slack-standup.rb +13 -0
- data/lib/lita/handlers/slack_standup.rb +166 -0
- data/lib/slack_client.rb +17 -0
- data/lita-slack-standup.gemspec +29 -0
- data/locales/en.yml +4 -0
- data/spec/lita/handlers/standup_spec.rb +168 -0
- data/spec/spec_helper.rb +37 -0
- data/templates/.gitkeep +0 -0
- metadata +215 -0
data/README.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# lita-standup
|
2
|
+
|
3
|
+
- lita-standup is a gem for Lita (https://www.lita.io/), a chat bot written in ruby.
|
4
|
+
- It handles standup meetings on slack (the sentences are in french for now).
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add lita-standup to your Lita instance's Gemfile:
|
9
|
+
|
10
|
+
``` ruby
|
11
|
+
gem "lita-standup"
|
12
|
+
```
|
13
|
+
|
14
|
+
## Configuration
|
15
|
+
|
16
|
+
In your lita configuration file (lita_config.rb), add the lines :
|
17
|
+
``` ruby
|
18
|
+
Lita.congifure do |config|
|
19
|
+
## standup
|
20
|
+
config.handlers.standup.channel = ENV['STANDUP_CHANNEL']
|
21
|
+
end
|
22
|
+
```
|
23
|
+
|
24
|
+
And set the environment variable STANDUP_CHANNEL, with the name of the channel where you want to held the standup.
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
!start standup : launches the standup, prints the standups already filled and asks for someone else to report
|
29
|
+
!next standup : skips the current user and asks the next user to do his standup report
|
30
|
+
!standup <some standup report> : saves your standup. If you do it before the start of the standup, you won't be asked to report. The bot will displays your standup in your stead
|
31
|
+
!ignore <some user> : ignores an user for the standups
|
32
|
+
!unignore <some user> : unignores an user
|
33
|
+
!list ignore : list all ignored users
|
34
|
+
|
35
|
+
The standup stops when everyone has done his report or has been skipped.
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "lita"
|
2
|
+
require "slack_client"
|
3
|
+
|
4
|
+
Lita.load_locales Dir[File.expand_path(
|
5
|
+
File.join("..", "..", "locales", "*.yml"), __FILE__
|
6
|
+
)]
|
7
|
+
|
8
|
+
require "lita/handlers/slack_standup"
|
9
|
+
|
10
|
+
Lita::Handlers::SlackStandup.template_root File.expand_path(
|
11
|
+
File.join("..", "..", "templates"),
|
12
|
+
__FILE__
|
13
|
+
)
|
@@ -0,0 +1,166 @@
|
|
1
|
+
module Lita
|
2
|
+
module Handlers
|
3
|
+
class SlackStandup < Handler
|
4
|
+
include SlackClient
|
5
|
+
|
6
|
+
config :channel
|
7
|
+
|
8
|
+
route(/^start\s*standup?/, :start_standup, command: true, help: {"start standup" => "Lance le standup." })
|
9
|
+
|
10
|
+
def start_standup(message=nil)
|
11
|
+
in_standup.value = 'true'
|
12
|
+
setup_redis_objects
|
13
|
+
send_message(config.channel, 'Hello <!channel> ! Le standup va commencer : )')
|
14
|
+
prewritten_standups_summary
|
15
|
+
next_standup
|
16
|
+
end
|
17
|
+
|
18
|
+
route(/^standup\s*(.*)$/, :standup, command: true, help: {"standup texte" => "Permet d'écrire son standup à l'avance ou pendant le tour d'un autre."})
|
19
|
+
|
20
|
+
def standup(message)
|
21
|
+
setup_redis_objects
|
22
|
+
save_standup message
|
23
|
+
message.reply("Ton standup est enregistré. Merci :)")
|
24
|
+
end
|
25
|
+
|
26
|
+
route(/^next\s*standup?/, :next_standup, command: true, help: {"next standup" => "Passe à l'utilisateur suivant et considère le standup précédent comme fait."})
|
27
|
+
|
28
|
+
def next_standup(message=nil)
|
29
|
+
if in_standup.value == 'true'
|
30
|
+
if standup_members.none? { |user, standup| standup.empty? }
|
31
|
+
end_standup
|
32
|
+
else
|
33
|
+
next_attendee = select_next_standup
|
34
|
+
send_message(config.channel,"Bonjour <@#{next_attendee}> ! C'est à ton tour de parler.")
|
35
|
+
fill_standup(next_attendee)
|
36
|
+
end
|
37
|
+
else
|
38
|
+
send_message(config.channel,"La commande n'est pas disponible en dehors d'un standup.")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def reminder
|
43
|
+
standup_members.each do |user, standup|
|
44
|
+
send_message("@#{user}","Bonsoir <@#{user}> ! Tu peux donner ton standup pour demain. !standup 3615mavie") if standup.empty?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
route(/^ignore\s*(.*)$/, :ignore, command: true, help: {"ignore nom" => "Retire un utilisateur de la liste des personnes ayant à faire le standup"})
|
49
|
+
|
50
|
+
def ignore(message)
|
51
|
+
user = message.matches[0][0].gsub('@','')
|
52
|
+
unless ignored_members.include? user
|
53
|
+
ignored_members << user
|
54
|
+
standup_members.delete(user)
|
55
|
+
end
|
56
|
+
message.reply("<@#{user}> est désormais ignoré jusqu'à nouvel ordre.")
|
57
|
+
end
|
58
|
+
|
59
|
+
route(/^unignore\s*(.*)$/, :unignore, command: true, help: {"unignore nom" => "Remet l'utilisateur dans la liste des personnes participant aux standups."})
|
60
|
+
|
61
|
+
def unignore(message)
|
62
|
+
user = message.matches[0][0].gsub('@','')
|
63
|
+
if ignored_members.include? user
|
64
|
+
ignored_members.delete(user)
|
65
|
+
standup_members[user] = ''
|
66
|
+
end
|
67
|
+
message.reply("<@#{user}> est à nouveau inclus dans les standups.")
|
68
|
+
end
|
69
|
+
|
70
|
+
route(/^list\s*ignore?/, :list_ignore, command: true, help: {"list ignore" => "Liste les utilisateurs ignorés."})
|
71
|
+
|
72
|
+
def list_ignore(message)
|
73
|
+
reply = "Utilisateurs ignorés : "
|
74
|
+
ignored_members.each do |user|
|
75
|
+
reply += "#{user} "
|
76
|
+
end
|
77
|
+
message.reply(reply)
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def in_standup
|
83
|
+
@in_standup ||= Redis::Value.new('in_standup', redis, marshal: true)
|
84
|
+
end
|
85
|
+
|
86
|
+
def ignored_members
|
87
|
+
@ignored_members ||= Redis::List.new('ignored_members', redis)
|
88
|
+
end
|
89
|
+
|
90
|
+
def setup_redis_objects
|
91
|
+
update_ids_to_members if ids_to_members.empty?
|
92
|
+
update_standup_members if standup_members.empty?
|
93
|
+
end
|
94
|
+
|
95
|
+
def standup_members
|
96
|
+
@standup_members ||= Redis::HashKey.new('standup_members', redis, marshal: true)
|
97
|
+
end
|
98
|
+
|
99
|
+
def retrieve_channel
|
100
|
+
slack_client.channels_list['channels'].
|
101
|
+
find do |channel|
|
102
|
+
"##{ channel['name'] }" == config.channel
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def fill_standup_members(members_list)
|
107
|
+
members_list.each do |user_id|
|
108
|
+
name = ids_to_members[user_id]
|
109
|
+
standup_members[name] = '' unless ignored_members.include?(name)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def update_standup_members
|
114
|
+
standup_members.clear
|
115
|
+
members_list = retrieve_channel['members']
|
116
|
+
fill_standup_members(members_list)
|
117
|
+
end
|
118
|
+
|
119
|
+
def ids_to_members
|
120
|
+
@ids_to_members ||= Redis::HashKey.new('ids_to_members', redis, marshal: true)
|
121
|
+
end
|
122
|
+
|
123
|
+
def update_ids_to_members
|
124
|
+
ids_to_members.clear
|
125
|
+
slack_client.users_list['members'].each do |user|
|
126
|
+
@ids_to_members[user['id']] = user['name']
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def prewritten_standups_summary
|
131
|
+
standup_members.each do |user, standup|
|
132
|
+
display_standup(user, standup) unless standup.empty?
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def display_standup(user, standup)
|
137
|
+
send_message(config.channel, "#{user} a déjà renseigné son standup : \n #{standup}")
|
138
|
+
end
|
139
|
+
|
140
|
+
def save_standup(message)
|
141
|
+
member = message.user.mention_name
|
142
|
+
standup_report = message.matches[0][0]
|
143
|
+
standup_members[member] = standup_report
|
144
|
+
end
|
145
|
+
|
146
|
+
def fill_standup(member)
|
147
|
+
standup_members[member] = 'Standup fait en live.'
|
148
|
+
end
|
149
|
+
|
150
|
+
def select_next_standup
|
151
|
+
standup_members.select{ |key,value| value.empty? }.first.first
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
def end_standup
|
156
|
+
in_standup.value = 'false'
|
157
|
+
|
158
|
+
send_message(config.channel, "Et voilà ! C'est bon pour aujourd'hui. Merci tout le monde :parrot:")
|
159
|
+
update_standup_members
|
160
|
+
update_ids_to_members
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
Lita.register_handler(SlackStandup)
|
165
|
+
end
|
166
|
+
end
|
data/lib/slack_client.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'slack-ruby-client'
|
2
|
+
|
3
|
+
module SlackClient
|
4
|
+
def slack_client
|
5
|
+
@slack_client ||= Slack::Web::Client.new
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def send_message(channel, message)
|
11
|
+
slack_client.chat_postMessage(
|
12
|
+
channel: channel,
|
13
|
+
text: message,
|
14
|
+
as_user: true
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
Gem::Specification.new do |spec|
|
2
|
+
spec.name = "lita-slack-standup"
|
3
|
+
spec.version = "0.1.0"
|
4
|
+
spec.authors = ["Sybil Deboin"]
|
5
|
+
spec.email = ["sybil.deboin@gmail.com"]
|
6
|
+
spec.description = "Standup feature for slack"
|
7
|
+
spec.summary = ""
|
8
|
+
spec.homepage = "https://github.com/blackbirdco/lita-slack-standup"
|
9
|
+
spec.license = "GNU v3.0"
|
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-slack"
|
19
|
+
spec.add_runtime_dependency "slack-ruby-client"
|
20
|
+
spec.add_runtime_dependency "redis-objects"
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
23
|
+
spec.add_development_dependency "pry-byebug"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "rack-test"
|
26
|
+
spec.add_development_dependency "rspec", ">= 3.0.0"
|
27
|
+
spec.add_development_dependency "webmock"
|
28
|
+
spec.add_development_dependency "vcr"
|
29
|
+
end
|
data/locales/en.yml
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Lita::Handlers::Standup, lita_handler: true do
|
4
|
+
|
5
|
+
let(:channel) { "#jambot-test" }
|
6
|
+
let(:robot_name) { "jambot-test" }
|
7
|
+
let(:sybil) { Lita::User.create(1000, name: "sybil") }
|
8
|
+
let(:zaratan) { Lita::User.create(1001, name: "zaratan") }
|
9
|
+
|
10
|
+
before(:all) do
|
11
|
+
Slack.configure do |config|
|
12
|
+
config.token = ENV['SLACK_LITA_TOKEN']
|
13
|
+
end
|
14
|
+
Lita.configure do |config|
|
15
|
+
config.handlers.standup.channel = '#jambot-test'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
let(:registry) do
|
20
|
+
reg = Lita::Registry.new
|
21
|
+
reg.register_handler(Lita::Handlers::Standup)
|
22
|
+
reg.configure do |config|
|
23
|
+
config.robot.name = robot_name
|
24
|
+
config.robot.alias = "!"
|
25
|
+
config.robot.adapter = :slack
|
26
|
+
config.handlers.standup.channel = channel
|
27
|
+
end
|
28
|
+
|
29
|
+
reg
|
30
|
+
end
|
31
|
+
|
32
|
+
before do
|
33
|
+
standup = Lita::Handlers::Standup.new(robot)
|
34
|
+
standup.send(:standup_members).clear
|
35
|
+
standup.send(:ids_to_members).clear
|
36
|
+
standup.send(:ignored_members).clear
|
37
|
+
['jambot','slackbot','sybil_test'].each do |ignored_member|
|
38
|
+
standup.send(:ignored_members) << ignored_member
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "when standup starts" do
|
43
|
+
|
44
|
+
subject do
|
45
|
+
send_message("!start standup", as: sybil, from: channel)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "starts standup", vcr: {cassette_name: 'start_standup'} do
|
49
|
+
[ "Hello <!channel> ! Le standup va commencer : )",
|
50
|
+
"Bonjour <@zaratan> ! C'est à ton tour de parler."
|
51
|
+
].each do |text|
|
52
|
+
expect_any_instance_of(Slack::Web::Client).to receive(:chat_postMessage).with({:channel=>channel, :text=>text, :as_user=>true})
|
53
|
+
end
|
54
|
+
|
55
|
+
subject
|
56
|
+
end
|
57
|
+
|
58
|
+
it "annoys people only on working days" do
|
59
|
+
#standup test that it's called on weekday
|
60
|
+
end
|
61
|
+
|
62
|
+
context "with pre-filled reports" do
|
63
|
+
it "displays known reports", vcr: {cassette_name: 'standup_prefilled'} do
|
64
|
+
send_message("!standup My standup report for testing purposes", as: sybil, from: channel)
|
65
|
+
|
66
|
+
[ "Hello <!channel> ! Le standup va commencer : )",
|
67
|
+
"sybil a déjà renseigné son standup : \n My standup report for testing purposes",
|
68
|
+
"Bonjour <@zaratan> ! C'est à ton tour de parler."
|
69
|
+
].each do |text|
|
70
|
+
expect_any_instance_of(Slack::Web::Client).to receive(:chat_postMessage).with({:channel=>channel, :text=>text, :as_user=>true})
|
71
|
+
end
|
72
|
+
|
73
|
+
subject
|
74
|
+
end
|
75
|
+
context "with all reports pre-filled", vcr: {cassette_name: 'standups_all_prefilled'} do
|
76
|
+
it "displays the standups and ends the meeting" do
|
77
|
+
send_message("!standup My standup report for testing purposes", as: sybil, from: channel)
|
78
|
+
send_message("!standup My standup report for testing purposes", as: zaratan, from: channel)
|
79
|
+
|
80
|
+
[ "Hello <!channel> ! Le standup va commencer : )",
|
81
|
+
"zaratan a déjà renseigné son standup : \n My standup report for testing purposes",
|
82
|
+
"sybil a déjà renseigné son standup : \n My standup report for testing purposes",
|
83
|
+
"Et voilà ! C'est bon pour aujourd'hui. Merci tout le monde :parrot:"
|
84
|
+
].each do |text|
|
85
|
+
expect_any_instance_of(Slack::Web::Client).to receive(:chat_postMessage).with({:channel=>channel, :text=>text, :as_user=>true})
|
86
|
+
end
|
87
|
+
|
88
|
+
subject
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "!standup command", vcr: {cassette_name: 'standup'} do
|
95
|
+
subject do
|
96
|
+
send_message("!standup My standup report for testing purposes", as: sybil, from: channel)
|
97
|
+
end
|
98
|
+
|
99
|
+
context "when the user reports" do
|
100
|
+
it "saves the report" do
|
101
|
+
subject
|
102
|
+
expect(replies.last).to eq("Ton standup est enregistré. Merci :)")
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "!next standup command" do
|
108
|
+
subject do
|
109
|
+
send_message("!next standup", as: sybil, from: channel)
|
110
|
+
end
|
111
|
+
|
112
|
+
context "when its called oustide of standups", vcr: {cassette_name: 'next_standup_refused'} do
|
113
|
+
it "is not available" do
|
114
|
+
expect_any_instance_of(Slack::Web::Client).to receive(:chat_postMessage).with({:channel=>channel, :text=>"La commande n'est pas disponible en dehors d'un standup.", :as_user=>true})
|
115
|
+
|
116
|
+
subject
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context "when it's called during standup", vcr: {cassette_name: 'next_standup'} do
|
121
|
+
it "asks the next user to report" do
|
122
|
+
send_message("!start standup", as: sybil, from: channel)
|
123
|
+
|
124
|
+
expect_any_instance_of(Slack::Web::Client).to receive(:chat_postMessage).with({:channel=>channel, :text=>"Bonjour <@sybil> ! C'est à ton tour de parler.", :as_user=>true})
|
125
|
+
|
126
|
+
subject
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe "!ignore user command", vcr: {cassette_name: 'ignore_user'} do
|
132
|
+
|
133
|
+
subject do
|
134
|
+
send_message("!ignore sybil", as: sybil, from: channel)
|
135
|
+
send_message("!next standup", as: sybil, from: channel)
|
136
|
+
end
|
137
|
+
|
138
|
+
it "ignores an user" do
|
139
|
+
send_message("!start standup", as: sybil, from: channel)
|
140
|
+
|
141
|
+
expect_any_instance_of(Slack::Web::Client).to receive(:chat_postMessage).with({:channel=>channel, :text=>"Et voilà ! C'est bon pour aujourd'hui. Merci tout le monde :parrot:", :as_user=>true})
|
142
|
+
|
143
|
+
subject
|
144
|
+
expect(replies.last).to eq("<@sybil> est désormais ignoré jusqu'à nouvel ordre.")
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
describe "!unignore user command", vcr: {cassette_name: 'unignore_user'} do
|
150
|
+
|
151
|
+
subject do
|
152
|
+
send_message("!unignore sybil", as: sybil, from: channel)
|
153
|
+
send_message("!next standup", as: sybil, from: channel)
|
154
|
+
end
|
155
|
+
|
156
|
+
it "unignores an user" do
|
157
|
+
send_message("!start standup", as: sybil, from: channel)
|
158
|
+
send_message("!ignore sybil", as: sybil, from: channel)
|
159
|
+
|
160
|
+
expect_any_instance_of(Slack::Web::Client).to receive(:chat_postMessage).with({:channel=>channel, :text=>"Bonjour <@sybil> ! C'est à ton tour de parler.", :as_user=>true})
|
161
|
+
|
162
|
+
subject
|
163
|
+
expect(replies.last).to eq("<@sybil> est à nouveau inclus dans les standups.")
|
164
|
+
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|