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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 80d38692ef9bfa4c14e4641be38f067786234bb8
4
- data.tar.gz: a6be7d580cf0b3ebcdf9780583e7f39181b176b1
3
+ metadata.gz: bcf104205740a7675b39dc610baaf02a1bd44dbf
4
+ data.tar.gz: e6904c6198c2673dc4c6e999ccb37c2e02f77527
5
5
  SHA512:
6
- metadata.gz: c45f767065539bd4f8457a93e6853a7c7fdef682915b35b5981e403e640c1de0aaada64ce838e4d517a6751ccd85168337132a251db9d58d9816b5a5f6e286ba
7
- data.tar.gz: 1d8f01c07cf5ba3317fc1345ee269fdfe1fb2eff811ebd46ab73050361ef05c91d480b6372b502b5fefa5bac73cad749e10b2a5646badde03216bba06370b100
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].scan(/("([^"]+)"|([^ ]+))/).map {|v| v[1] || v[2] }
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.projects.find do |project|
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.trackers.find do |tracker|
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
@@ -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
- # Your code goes here...
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
@@ -1,5 +1,5 @@
1
1
  module Ruboty
2
2
  module Redmine
3
- VERSION = "0.0.3"
3
+ VERSION = "0.0.4"
4
4
  end
5
5
  end
@@ -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.3
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-01-30 00:00:00.000000000 Z
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