ruboty-redmine 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/ruboty/handlers/redmine.rb +193 -15
- data/lib/ruboty/redmine.rb +8 -1
- data/lib/ruboty/redmine/client.rb +49 -0
- data/lib/ruboty/redmine/version.rb +1 -1
- data/ruboty-redmine.gemspec +2 -0
- metadata +30 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bcf104205740a7675b39dc610baaf02a1bd44dbf
|
4
|
+
data.tar.gz: e6904c6198c2673dc4c6e999ccb37c2e02f77527
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f28bc2644829ea3ad70ee3d8594a46ef779e08c5d75d16d0d4f82191b18f02d7e412937e6084a9fb2533abba48ccf3a6455fc7241bc9c5750ef90ba47a96f3a5
|
7
|
+
data.tar.gz: ed581a00f88e7932de5aa9f8ceb7d600b7519e06ac44af64685f2d7ae1494e7af04e840e7bd0b406fd48ebdd668edb279650910f7c86745f8b90cb295ec36d11
|
@@ -1,10 +1,13 @@
|
|
1
1
|
module Ruboty
|
2
2
|
module Handlers
|
3
3
|
class Redmine < Base
|
4
|
+
NAMESPACE = 'redmine'
|
5
|
+
|
4
6
|
env :REDMINE_URL, 'Redmine url (e.g. http://your-redmine)', optional: false
|
5
7
|
env :REDMINE_API_KEY, 'Redmine REST API key', optional: false
|
6
8
|
env :REDMINE_BASIC_AUTH_USER, 'Basic Auth User', optional: true
|
7
9
|
env :REDMINE_BASIC_AUTH_PASSWORD, 'Basic Auth Password', optional: true
|
10
|
+
env :REDMINE_CHECK_INTERVAL, 'Interval to check new issues', optional: true
|
8
11
|
|
9
12
|
on(
|
10
13
|
/create issue (?<rest>.+)/,
|
@@ -12,10 +15,52 @@ module Ruboty
|
|
12
15
|
description: 'Create a new issue'
|
13
16
|
)
|
14
17
|
|
18
|
+
on(
|
19
|
+
/register redmine alias "(?<name>[^"]+)" ("(?<expand_to>[^"]+)"|'(?<expand_to>[^']+)')/,
|
20
|
+
name: 'register_alias',
|
21
|
+
description: 'Register an alias'
|
22
|
+
)
|
23
|
+
|
24
|
+
on(
|
25
|
+
/watch redmine issues in "(?<tracker>[^"]+)" tracker of "(?<project>[^"]+)" project( and assign to (?<assignees>[\d,]+)|)/,
|
26
|
+
name: 'watch_issues',
|
27
|
+
description: 'Watch issues'
|
28
|
+
)
|
29
|
+
|
30
|
+
on(
|
31
|
+
/list watching redmine issues/,
|
32
|
+
name: 'list_watching',
|
33
|
+
description: 'List watching issues'
|
34
|
+
)
|
35
|
+
|
36
|
+
on(
|
37
|
+
/stop watching redmine issues (?<id>\d+)/,
|
38
|
+
name: 'stop_watching',
|
39
|
+
description: 'Stop watching issues',
|
40
|
+
)
|
41
|
+
|
42
|
+
on(
|
43
|
+
/associate redmine user (?<redmine_id>\d+) with "(?<chat_name>[^"]+)"/,
|
44
|
+
name: 'associate_user',
|
45
|
+
description: 'Associate redmine_id with chat_name',
|
46
|
+
)
|
47
|
+
|
48
|
+
def initialize(*args)
|
49
|
+
super
|
50
|
+
|
51
|
+
start_to_watch_issues
|
52
|
+
end
|
53
|
+
|
15
54
|
def create_issue(message)
|
16
|
-
words = message[:rest]
|
55
|
+
words = parse_arg(message[:rest])
|
17
56
|
req = {}
|
18
57
|
req[:subject] = words.shift
|
58
|
+
|
59
|
+
if words.size == 1
|
60
|
+
expand_to = alias_for(words.first)
|
61
|
+
words = parse_arg(expand_to) if expand_to
|
62
|
+
end
|
63
|
+
|
19
64
|
words.each_with_index do |word, i|
|
20
65
|
next if i == 0
|
21
66
|
|
@@ -23,13 +68,7 @@ module Ruboty
|
|
23
68
|
|
24
69
|
case word
|
25
70
|
when 'project'
|
26
|
-
project = redmine.
|
27
|
-
[
|
28
|
-
project.id.to_s,
|
29
|
-
project.name.downcase,
|
30
|
-
project.identifier.downcase,
|
31
|
-
].include?(arg.downcase)
|
32
|
-
end
|
71
|
+
project = redmine.find_project(arg)
|
33
72
|
|
34
73
|
unless project
|
35
74
|
message.reply("Project '#{arg}' is not found.")
|
@@ -38,12 +77,7 @@ module Ruboty
|
|
38
77
|
|
39
78
|
req[:project] = project
|
40
79
|
when 'tracker'
|
41
|
-
tracker = redmine.
|
42
|
-
[
|
43
|
-
tracker.id.to_s,
|
44
|
-
tracker.name.downcase,
|
45
|
-
].include?(arg.downcase)
|
46
|
-
end
|
80
|
+
tracker = redmine.find_tracker(arg)
|
47
81
|
|
48
82
|
unless tracker
|
49
83
|
message.reply("Tracker '#{arg}' is not found.")
|
@@ -63,16 +97,160 @@ module Ruboty
|
|
63
97
|
message.reply("Issue created: #{redmine.url_for_issue(issue)}")
|
64
98
|
end
|
65
99
|
|
100
|
+
def register_alias(message)
|
101
|
+
aliases[message[:name]] = message[:expand_to]
|
102
|
+
message.reply("Registered.")
|
103
|
+
end
|
104
|
+
|
105
|
+
def watch_issues(message)
|
106
|
+
if message.match_data.names.include?('assignees')
|
107
|
+
assignees = message[:assignees].split(',').map(&:to_i)
|
108
|
+
else
|
109
|
+
assignees = []
|
110
|
+
end
|
111
|
+
|
112
|
+
watches << message.original.except(:robot).merge(
|
113
|
+
{id: watches.size + 1, project: message[:project], tracker: message[:tracker], assignees: assignees, assignee_index: 0}
|
114
|
+
).stringify_keys
|
115
|
+
message.reply("Watching.")
|
116
|
+
end
|
117
|
+
|
118
|
+
def list_watching(message)
|
119
|
+
reply = watches.map do |watch|
|
120
|
+
s = "##{watch['id']} #{watch['tracker']} tracker in #{watch['project']} project"
|
121
|
+
if assignees = watch['assignees']
|
122
|
+
s << " and assign to #{assignees}"
|
123
|
+
end
|
124
|
+
end.join("\n")
|
125
|
+
|
126
|
+
message.reply(reply)
|
127
|
+
end
|
128
|
+
|
129
|
+
def stop_watching(message)
|
130
|
+
id = message[:id].to_i
|
131
|
+
watches.reject! do |watch|
|
132
|
+
watch['id'] == id
|
133
|
+
end
|
134
|
+
|
135
|
+
message.reply("Stopped.")
|
136
|
+
end
|
137
|
+
|
138
|
+
def associate_user(message)
|
139
|
+
users << {"redmine_id" => message[:redmine_id].to_i, "chat_name" => message[:chat_name]}
|
140
|
+
|
141
|
+
message.reply("Registered.")
|
142
|
+
end
|
143
|
+
|
66
144
|
private
|
67
145
|
|
68
146
|
def redmine
|
69
|
-
Ruboty::Redmine::Client.new(
|
147
|
+
@redmine ||= Ruboty::Redmine::Client.new(
|
70
148
|
ENV['REDMINE_URL'],
|
71
149
|
ENV['REDMINE_API_KEY'],
|
72
150
|
basic_auth_user: ENV['REDMINE_BASIC_AUTH_USER'],
|
73
151
|
basic_auth_password: ENV['REDMINE_BASIC_AUTH_PASSWORD'],
|
74
152
|
)
|
75
153
|
end
|
154
|
+
|
155
|
+
def alias_for(name)
|
156
|
+
aliases[name]
|
157
|
+
end
|
158
|
+
|
159
|
+
def aliases
|
160
|
+
robot.brain.data["#{NAMESPACE}_aliases"] ||= {}
|
161
|
+
end
|
162
|
+
|
163
|
+
def watches
|
164
|
+
robot.brain.data["#{NAMESPACE}_watches"] ||= []
|
165
|
+
end
|
166
|
+
|
167
|
+
def users
|
168
|
+
robot.brain.data["#{NAMESPACE}_users"] ||= []
|
169
|
+
end
|
170
|
+
|
171
|
+
def find_user_by_id(id)
|
172
|
+
users.find {|user| user['redmine_id'] == id }
|
173
|
+
end
|
174
|
+
|
175
|
+
def parse_arg(text)
|
176
|
+
text.scan(/("([^"]+)"|'([^']+)'|([^ ]+))/).map do |v|
|
177
|
+
v.shift
|
178
|
+
v.find {|itself| itself }
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def start_to_watch_issues
|
183
|
+
thread = Thread.start do
|
184
|
+
last_issues_for_watch = {}
|
185
|
+
|
186
|
+
while true
|
187
|
+
sleep (ENV['REDMINE_CHECK_INTERVAL'] || 30).to_i
|
188
|
+
Ruboty::Redmine.log("Checking new issues...")
|
189
|
+
watches.each do |watch|
|
190
|
+
project = redmine.find_project(watch['project'])
|
191
|
+
tracker = redmine.find_tracker(watch['tracker'])
|
192
|
+
|
193
|
+
issues = redmine.issues(project: project, tracker: tracker, sort: 'id:desc')
|
194
|
+
if last_issues = last_issues_for_watch[watch]
|
195
|
+
new_issues = []
|
196
|
+
issues.each do |issue|
|
197
|
+
found = last_issues.find do |last_issue|
|
198
|
+
last_issue.id == issue.id
|
199
|
+
end
|
200
|
+
|
201
|
+
if found
|
202
|
+
break
|
203
|
+
else
|
204
|
+
new_issues << issue
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
new_issues.each do |new_issue|
|
209
|
+
require 'pry'
|
210
|
+
Pry.config.input = STDIN
|
211
|
+
Pry.config.output = STDOUT
|
212
|
+
binding.pry
|
213
|
+
assignees = watch['assignees']
|
214
|
+
assignee = nil
|
215
|
+
unless assignees.empty?
|
216
|
+
assignee = assignees[watch['assignee_index'] % assignees.size]
|
217
|
+
watch['assignee_index'] += 1
|
218
|
+
|
219
|
+
assignee = find_user_by_id(assignee)
|
220
|
+
end
|
221
|
+
|
222
|
+
if assignee
|
223
|
+
redmine.update_issue(new_issue, assigned_to_id: assignee['redmine_id'])
|
224
|
+
end
|
225
|
+
|
226
|
+
msg = <<-EOC
|
227
|
+
New Issue of #{tracker.name} in #{project.name} project
|
228
|
+
-> #{new_issue.subject}
|
229
|
+
EOC
|
230
|
+
|
231
|
+
if assignee
|
232
|
+
msg += <<-EOC
|
233
|
+
-> Assigned to @#{assignee['chat_name']}
|
234
|
+
EOC
|
235
|
+
end
|
236
|
+
|
237
|
+
msg += <<-EOC
|
238
|
+
-> #{redmine.url_for_issue(new_issue)}
|
239
|
+
EOC
|
240
|
+
|
241
|
+
Message.new(
|
242
|
+
watch.symbolize_keys.merge(robot: robot)
|
243
|
+
).reply(msg)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
last_issues_for_watch[watch] = issues
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
thread.abort_on_exception = true
|
253
|
+
end
|
76
254
|
end
|
77
255
|
end
|
78
256
|
end
|
data/lib/ruboty/redmine.rb
CHANGED
@@ -1,9 +1,16 @@
|
|
1
|
+
require "active_support/core_ext/hash/keys"
|
2
|
+
|
1
3
|
require "ruboty/redmine/version"
|
2
4
|
require "ruboty/redmine/client"
|
3
5
|
require "ruboty/handlers/redmine"
|
4
6
|
|
5
7
|
module Ruboty
|
6
8
|
module Redmine
|
7
|
-
|
9
|
+
def self.log(msg)
|
10
|
+
Ruboty.logger.info "[redmine] #{msg}"
|
11
|
+
|
12
|
+
# FIX: thix dirty hack.
|
13
|
+
Ruboty.logger.instance_variable_get(:@logdev).dev.flush
|
14
|
+
end
|
8
15
|
end
|
9
16
|
end
|
@@ -25,6 +25,37 @@ module Ruboty
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
+
def find_project(query)
|
29
|
+
projects.find do |project|
|
30
|
+
[
|
31
|
+
project.id.to_s,
|
32
|
+
project.name.downcase,
|
33
|
+
project.identifier.downcase,
|
34
|
+
].include?(query.downcase)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def find_tracker(query)
|
39
|
+
trackers.find do |tracker|
|
40
|
+
[
|
41
|
+
tracker.id.to_s,
|
42
|
+
tracker.name.downcase,
|
43
|
+
].include?(query.downcase)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def issues(opts)
|
48
|
+
params = {}
|
49
|
+
params[:project_id] = opts[:project].id if opts[:project]
|
50
|
+
params[:tracker_id] = opts[:tracker].id if opts[:tracker]
|
51
|
+
params[:sort] = opts[:sort] if opts[:sort]
|
52
|
+
|
53
|
+
res = JSON.parse(get('/issues.json', params).body)
|
54
|
+
res['issues'].map do |tracker|
|
55
|
+
OpenStruct.new(tracker)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
28
59
|
def create_issue(opts)
|
29
60
|
req = {
|
30
61
|
issue: {
|
@@ -39,6 +70,15 @@ module Ruboty
|
|
39
70
|
)
|
40
71
|
end
|
41
72
|
|
73
|
+
def update_issue(issue, opts)
|
74
|
+
req = {issue: opts}
|
75
|
+
|
76
|
+
res = put("/issues/#{issue.id}.json", req)
|
77
|
+
unless res.status == 200
|
78
|
+
raise "Updating issue failed. (#{res.body})"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
42
82
|
def url_for_issue(issue)
|
43
83
|
URI.join(@url, "/issues/#{issue.id}")
|
44
84
|
end
|
@@ -78,6 +118,15 @@ module Ruboty
|
|
78
118
|
req.headers['Content-Type'] = 'application/json'
|
79
119
|
end
|
80
120
|
end
|
121
|
+
|
122
|
+
def put(path, params = {})
|
123
|
+
conn.put do |req|
|
124
|
+
req.url path
|
125
|
+
req.body = params.to_json
|
126
|
+
req.headers['X-Redmine-API-Key'] = @api_key
|
127
|
+
req.headers['Content-Type'] = 'application/json'
|
128
|
+
end
|
129
|
+
end
|
81
130
|
end
|
82
131
|
end
|
83
132
|
end
|
data/ruboty-redmine.gemspec
CHANGED
@@ -19,7 +19,9 @@ Gem::Specification.new do |spec|
|
|
19
19
|
|
20
20
|
spec.add_dependency "ruboty"
|
21
21
|
spec.add_dependency "faraday"
|
22
|
+
spec.add_dependency "activesupport"
|
22
23
|
|
24
|
+
spec.add_development_dependency "pry-byebug"
|
23
25
|
spec.add_development_dependency "bundler", "~> 1.7"
|
24
26
|
spec.add_development_dependency "rake", "~> 10.0"
|
25
27
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruboty-redmine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryota Arai
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-02-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ruboty
|
@@ -38,6 +38,34 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activesupport
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry-byebug
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
41
69
|
- !ruby/object:Gem::Dependency
|
42
70
|
name: bundler
|
43
71
|
requirement: !ruby/object:Gem::Requirement
|