lita-slack-standup 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -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
@@ -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,4 @@
1
+ en:
2
+ lita:
3
+ handlers:
4
+ standup:
@@ -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