ruboty-redmine 0.0.3 → 0.0.4
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.
- 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
|