jiraMule 0.1.1
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 +7 -0
- data/.gitignore +23 -0
- data/Gemfile +17 -0
- data/LICENSE +22 -0
- data/README.md +37 -0
- data/Rakefile +95 -0
- data/TODO.taskpaper +6 -0
- data/bin/jm +87 -0
- data/jiraMule.gemspec +43 -0
- data/lib/JiraMule/Tempo.rb +75 -0
- data/lib/JiraMule/commands/assign.rb +38 -0
- data/lib/JiraMule/commands/githubImport.rb +46 -0
- data/lib/JiraMule/commands/link.rb +41 -0
- data/lib/JiraMule/commands/timesheet.rb +113 -0
- data/lib/JiraMule/gb.rb +33 -0
- data/lib/JiraMule.rb +7 -0
- data/lib/jiraMule/Config.rb +224 -0
- data/lib/jiraMule/Passwords.rb +81 -0
- data/lib/jiraMule/commands/attach.rb +69 -0
- data/lib/jiraMule/commands/config.rb +68 -0
- data/lib/jiraMule/commands/goto.rb +166 -0
- data/lib/jiraMule/commands/kanban.rb +243 -0
- data/lib/jiraMule/commands/logWork.rb +39 -0
- data/lib/jiraMule/commands/next.rb +89 -0
- data/lib/jiraMule/commands/progress.rb +68 -0
- data/lib/jiraMule/commands/query.rb +80 -0
- data/lib/jiraMule/commands/release.rb +44 -0
- data/lib/jiraMule/commands/testReady.rb +68 -0
- data/lib/jiraMule/commands.rb +20 -0
- data/lib/jiraMule/gitUtils.rb +11 -0
- data/lib/jiraMule/http.rb +147 -0
- data/lib/jiraMule/jiraUtils.rb +288 -0
- data/lib/jiraMule/verbosing.rb +28 -0
- data/lib/jiraMule/version.rb +3 -0
- metadata +268 -0
@@ -0,0 +1,243 @@
|
|
1
|
+
require 'terminal-table'
|
2
|
+
require 'mustache'
|
3
|
+
require 'yaml'
|
4
|
+
require 'JiraMule/jiraUtils'
|
5
|
+
|
6
|
+
command :kanban do |c|
|
7
|
+
extra_columns = []
|
8
|
+
c.syntax = 'jm query [options] kanban'
|
9
|
+
c.summary = 'Show a kanban table'
|
10
|
+
c.description = %{
|
11
|
+
Display issues grouped by a query. Each group is a column in a table or a section in a list.
|
12
|
+
|
13
|
+
Original intent was to build kanban tables, hence the name. However, the output is quite
|
14
|
+
configurable can can be used for many other styles.
|
15
|
+
|
16
|
+
Use the --dump option to see how things have been styled.
|
17
|
+
|
18
|
+
Formatting is done with Mustash.
|
19
|
+
}
|
20
|
+
c.example 'Show a kanban table', 'jm kanban'
|
21
|
+
c.example 'Show a status list', 'jm status'
|
22
|
+
c.example 'Another way to show a status list', 'jm --style status'
|
23
|
+
c.example 'Show a list to use with Taskpaper', 'jm --style taskpaper'
|
24
|
+
c.example 'Show status list, with differnt styling', %{jm --style status --header '# {{column}}' --item '** {{key}} {{summary}}'}
|
25
|
+
c.example 'Showoff', %{jm kanban --style empty --heading '<h1>{{column}}</h1>' \\
|
26
|
+
--column 'Working=status="In Progress"' \\
|
27
|
+
--column 'Done=status="Pending Release"' \\
|
28
|
+
--fields key,summary,assignee \\
|
29
|
+
--item '<h2>{{key}}</h2><b>Who:{{assignee.name}}</b><p>{{summary}}</p>'}
|
30
|
+
|
31
|
+
c.option '--[no-]raw', 'Do not prefix queries with project and assignee'
|
32
|
+
c.option '-w', '--width WIDTH', Integer, 'Width of the terminal'
|
33
|
+
c.option '-s', '--style STYLE', String, 'Which style to use'
|
34
|
+
c.option '--heading STYLE', String, 'Format for heading'
|
35
|
+
c.option '--item STYLE', String, 'Format for items'
|
36
|
+
c.option('-c', '--column NAME=QUERY', '') {|ec| extra_columns << ec}
|
37
|
+
c.option '-f', '--fields FIELDS', Array, 'Which fields to return'
|
38
|
+
c.option '-d', '--dump', 'Dump the style to STDOUT as yaml'
|
39
|
+
c.option '--file FILE', String, %{Style definition file to load}
|
40
|
+
|
41
|
+
c.action do |args, options|
|
42
|
+
options.default :width=>HighLine::SystemExtensions.terminal_size[0],
|
43
|
+
:style => 'kanban'
|
44
|
+
|
45
|
+
# Table of Styles. Appendable via config file. ??and command line??
|
46
|
+
allOfThem = {
|
47
|
+
:empty => {
|
48
|
+
:fields => [:key, :summary],
|
49
|
+
:format => {
|
50
|
+
:heading => "{{column}}",
|
51
|
+
:item => "{{key}} {{summary}}",
|
52
|
+
},
|
53
|
+
:columns => {}
|
54
|
+
},
|
55
|
+
:status => {
|
56
|
+
:fields => [:key, :summary],
|
57
|
+
:format => {
|
58
|
+
:heading => "#### {{column}}",
|
59
|
+
:item => "- {{key}} {{summary}}",
|
60
|
+
:order => [:Done, :Testing, :InProgress, :Todo],
|
61
|
+
},
|
62
|
+
:columns => {
|
63
|
+
:Done => [%{status = 'Pending Release'}],
|
64
|
+
:Testing => [%{status = Testing}],
|
65
|
+
:InProgress => [%{status = "In Progress"}],
|
66
|
+
:Todo => [%{(status = Open OR},
|
67
|
+
%{status = Reopened OR},
|
68
|
+
%{status = "On Deck" OR},
|
69
|
+
%{status = "Waiting Estimation Approval" OR},
|
70
|
+
%{status = "Reopened" OR},
|
71
|
+
%{status = "Testing (Signoff)" OR},
|
72
|
+
%{status = "Testing (Review)" OR},
|
73
|
+
%{status = "Testing - Bug Found")}],
|
74
|
+
},
|
75
|
+
|
76
|
+
},
|
77
|
+
:kanban => {
|
78
|
+
:fields => [:key, :summary],
|
79
|
+
:format => {
|
80
|
+
:heading => "{{column}}",
|
81
|
+
:item => "{{key}}\n {{summary}}",
|
82
|
+
:order => [:Todo, :InProgress, :Done],
|
83
|
+
:usetable => true
|
84
|
+
},
|
85
|
+
:columns => {
|
86
|
+
:Done => [%{(status = Released OR status = "Not Needed - Closed")}],
|
87
|
+
:InProgress => [%{(status = "In Progress" OR},
|
88
|
+
%{status = "In Dev" OR},
|
89
|
+
%{status = "Pending Release" OR},
|
90
|
+
%{status = "In QA" OR},
|
91
|
+
%{status = "Integration QA" OR},
|
92
|
+
%{status = "In Design")},
|
93
|
+
],
|
94
|
+
:Todo => [%{(status = Open OR},
|
95
|
+
%{status = Reopened OR},
|
96
|
+
%{status = "On Deck" OR},
|
97
|
+
%{status = "Waiting Estimation Approval" OR},
|
98
|
+
%{status = "Reopened" OR},
|
99
|
+
%{status = "Testing (Signoff)" OR},
|
100
|
+
%{status = "Testing (Review)" OR},
|
101
|
+
%{status = "Testing - Bug Found" OR},
|
102
|
+
%{status = "Backlog" OR},
|
103
|
+
%{status = "Ready For Dev" OR},
|
104
|
+
%{status = "Ready For QA" OR},
|
105
|
+
%{status = "To Do" OR},
|
106
|
+
%{status = "Release Package")},
|
107
|
+
],
|
108
|
+
},
|
109
|
+
},
|
110
|
+
:taskpaper => {
|
111
|
+
:fields => [:key, :summary, :duedate],
|
112
|
+
:format => {
|
113
|
+
:heading => "{{column}}:",
|
114
|
+
:item => "- {{summary}} @jira({{key}}) {{#duedate}}@due({{duedate}}){{/duedate}}",
|
115
|
+
},
|
116
|
+
:columns => {
|
117
|
+
:InProgress => %{status = "In Progress"},
|
118
|
+
:Todo => [%{(status = Open OR},
|
119
|
+
%{status = "Testing - Bug Found")}],
|
120
|
+
}
|
121
|
+
},
|
122
|
+
}
|
123
|
+
# TODO: Load styles from project dir
|
124
|
+
|
125
|
+
if options.file.nil? then
|
126
|
+
theStyle = allOfThem[options.style.to_sym]
|
127
|
+
else
|
128
|
+
theStyle = allOfThem[:empty]
|
129
|
+
File.open(options.file) {|io|
|
130
|
+
theStyle = YAML.load(io)
|
131
|
+
# make sure the required keys are symbols.
|
132
|
+
%w{fields format columns}.each do |key|
|
133
|
+
if theStyle.has_key? key then
|
134
|
+
theStyle[key.to_sym] = theStyle[key]
|
135
|
+
theStyle.delete(key)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
%w{heading item}.each do |key|
|
139
|
+
if theStyle[:format].has_key? key then
|
140
|
+
theStyle[:format][key.to_sym] = theStyle[:format][key]
|
141
|
+
theStyle[:format].delete(key)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
}
|
145
|
+
end
|
146
|
+
|
147
|
+
#### look for command line overrides
|
148
|
+
extra_columns.each do |cm|
|
149
|
+
name, query = cm.split(/=/, 2)
|
150
|
+
theStyle[:columns][name.to_sym] = [query]
|
151
|
+
end
|
152
|
+
|
153
|
+
theStyle[:fields] = options.fields if options.fields
|
154
|
+
theStyle[:format][:heading] = options.heading if options.heading
|
155
|
+
theStyle[:format][:item] = options.item if options.item
|
156
|
+
|
157
|
+
|
158
|
+
# All loading and computing of the style is complete, now fetch and print
|
159
|
+
|
160
|
+
if options.dump then
|
161
|
+
puts theStyle.to_yaml
|
162
|
+
else
|
163
|
+
|
164
|
+
### Fetch the issues for each column
|
165
|
+
columns = theStyle[:columns]
|
166
|
+
|
167
|
+
jira = JiraMule::JiraUtils.new(args, options)
|
168
|
+
|
169
|
+
#### Fetch these fields
|
170
|
+
fields = theStyle[:fields]
|
171
|
+
|
172
|
+
#### Now fetch
|
173
|
+
qBase = []
|
174
|
+
qBase.unshift("assignee = #{jira.username} AND") unless options.raw
|
175
|
+
qBase.unshift("project = #{jira.project} AND") unless options.raw
|
176
|
+
qBase.unshift('(' + args.join(' ') + ') AND') unless args.empty?
|
177
|
+
|
178
|
+
results = {}
|
179
|
+
columns.each_pair do |name, query|
|
180
|
+
query = [query] unless query.is_a? Array
|
181
|
+
q = qBase + query + [%{ORDER BY Rank}]
|
182
|
+
issues = jira.getIssues(q.join(' '), fields)
|
183
|
+
results[name] = issues
|
184
|
+
end
|
185
|
+
|
186
|
+
### Now format the output
|
187
|
+
format = theStyle[:format]
|
188
|
+
|
189
|
+
#### Setup ordering
|
190
|
+
format[:order] = columns.keys.sort unless format.has_key? :order
|
191
|
+
|
192
|
+
#### setup column widths
|
193
|
+
cW = options.width.to_i
|
194
|
+
cW = -1 if cW == 0
|
195
|
+
cWR = cW
|
196
|
+
if format[:usetable] and cW > 0 then
|
197
|
+
borders = 4 + (columns.count * 3); # 2 on left, 2 on right, 3 for each internal
|
198
|
+
cW = (cW - borders) / columns.count
|
199
|
+
cWR = cW + ((cW - borders) % columns.count)
|
200
|
+
end
|
201
|
+
|
202
|
+
#### Format Items
|
203
|
+
formatted={}
|
204
|
+
results.each_pair do |name, issues|
|
205
|
+
formatted[name] = issues.map do |issue|
|
206
|
+
line = Mustache.render(format[:item], issue.merge(issue[:fields]))
|
207
|
+
#### Trim length?
|
208
|
+
if format[:order].last == name
|
209
|
+
line[0..cWR]
|
210
|
+
else
|
211
|
+
line[0..cW]
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
#### Print
|
217
|
+
if format.has_key?(:usetable) and format[:usetable] then
|
218
|
+
# Table type
|
219
|
+
#### Pad
|
220
|
+
longest = formatted.values.map{|l| l.length}.max
|
221
|
+
formatted.each_pair do |name, issues|
|
222
|
+
if issues.length <= longest then
|
223
|
+
issues.fill(' ', issues.length .. longest)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
#### Transpose
|
228
|
+
rows = format[:order].map{|n| formatted[n]}.transpose
|
229
|
+
puts Terminal::Table.new :headings => format[:order], :rows=>rows
|
230
|
+
|
231
|
+
else
|
232
|
+
# List type
|
233
|
+
format[:order].each do |columnName|
|
234
|
+
puts Mustache.render(format[:heading], :column => columnName.to_s)
|
235
|
+
formatted[columnName].each {|issue| puts issue}
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
alias_command :status, :kanban, '--style', 'status'
|
242
|
+
|
243
|
+
# vim: set sw=2 ts=2 :
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'chronic_duration'
|
2
|
+
require 'date'
|
3
|
+
require 'JiraMule/jiraUtils'
|
4
|
+
|
5
|
+
command :logwork do |c|
|
6
|
+
c.syntax = 'jm logwork [options] <key> <time spent...>'
|
7
|
+
c.summary = 'Log work spent'
|
8
|
+
c.description = %{Log time spent on a issue}
|
9
|
+
c.option '-m', '--message MSG', String, 'Message to add to work log'
|
10
|
+
c.option '--date DATE', String, 'When this work was done'
|
11
|
+
c.example 'Log some work done on an issue', %{jm logwork BUG-42 1h 12m}
|
12
|
+
|
13
|
+
c.action do |args, options|
|
14
|
+
options.default :message => ''
|
15
|
+
jira = JiraMule::JiraUtils.new(args, options)
|
16
|
+
|
17
|
+
key = jira.expandKeys(args).shift
|
18
|
+
ts = ChronicDuration.parse(args.join(' '))
|
19
|
+
|
20
|
+
unless options.date.nil? then
|
21
|
+
options.date = DateTime.parse(options.date)
|
22
|
+
if options.date.day_fraction.numerator == 0 then
|
23
|
+
# No time component. Need to add timezone...
|
24
|
+
offset = -(Time.now.gmt_offset/3600)
|
25
|
+
options.date += Rational(offset,24)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
begin
|
30
|
+
jira.logWork(key, ts, options.message, options.date)
|
31
|
+
rescue JiraUtilsException => e
|
32
|
+
pp e.response.body
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
alias_command :lw, :logwork
|
38
|
+
|
39
|
+
# vim: set et sw=2 ts=2 :
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'vine'
|
2
|
+
require 'pp'
|
3
|
+
require 'JiraMule/jiraUtils'
|
4
|
+
|
5
|
+
command :next do |c|
|
6
|
+
c.syntax = 'jm next [options] [keys]'
|
7
|
+
c.summary = 'Move issue to the next state'
|
8
|
+
c.description = %{
|
9
|
+
Move to the next state. For states with multiple exits, use the 'preferred' one.
|
10
|
+
}
|
11
|
+
c.example 'Move BUG-4 into the next state.', %{jm next BUG-4}
|
12
|
+
c.option '--[no-]save-next', %{Save transition as preferred}
|
13
|
+
c.option '--force-choice', %{Always show menu of transitions (if more than one)}
|
14
|
+
|
15
|
+
c.action do |args, options|
|
16
|
+
jira = JiraMule::JiraUtils.new(args, options)
|
17
|
+
|
18
|
+
# keys can be with or without the project prefix.
|
19
|
+
keys = jira.expandKeys(args)
|
20
|
+
jira.printVars(:keys=>keys)
|
21
|
+
return if keys.empty?
|
22
|
+
|
23
|
+
keys.each do |key|
|
24
|
+
# First see if there is a single exit. If so, just do that.
|
25
|
+
trans = jira.transitionsFor(key)
|
26
|
+
if trans.length == 1 then
|
27
|
+
id = trans.first[:id]
|
28
|
+
jira.verbose "Taking single exit: '#{trans.first[:name]}'"
|
29
|
+
jira.transition(key, id)
|
30
|
+
|
31
|
+
else
|
32
|
+
# If more than one:
|
33
|
+
|
34
|
+
# Need to know the name of the state we are currently in
|
35
|
+
#query = "assignee = #{jira.username} AND project = #{jira.project} AND "
|
36
|
+
query = "key = #{key}"
|
37
|
+
issues = jira.getIssues(query, ["status"])
|
38
|
+
raise "Cannot find #{key}" if issues.empty?
|
39
|
+
at = issues.first.access('fields.status.name')
|
40
|
+
|
41
|
+
unless options.force_choice then
|
42
|
+
# If a preferred transition is set, use that
|
43
|
+
nxt = $cfg["next-preferred.#{at}"]
|
44
|
+
unless nxt.nil? then
|
45
|
+
direct = trans.select {|item| jira.fuzzyMatchStatus(item, nxt) }
|
46
|
+
unless direct.empty? then
|
47
|
+
id = direct.first[:id]
|
48
|
+
jira.verbose "Taking preferred exit #{key} to #{direct.first['name']} (#{id})"
|
49
|
+
jira.transition(key, id)
|
50
|
+
exit
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Filter ignored transitions; If only one left, goto it.
|
55
|
+
skiplist = $cfg['next.ignore'] || []
|
56
|
+
check = trans.reject{|t| skiplist.include? t[:name] }
|
57
|
+
if check.length == 1 then
|
58
|
+
id = check.first[:id]
|
59
|
+
jira.verbose "Taking filtered single exit: '#{check.first[:name]}'"
|
60
|
+
jira.transition(key, id)
|
61
|
+
exit
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Otherwise, ask which transition to use.
|
66
|
+
# - save that as preferred
|
67
|
+
# - goto it.
|
68
|
+
choose do |menu|
|
69
|
+
menu.prompt = "Follow which transition?"
|
70
|
+
trans.each do |tr|
|
71
|
+
menu.choice(tr[:name]) do
|
72
|
+
if options.save_next then
|
73
|
+
$cfg.set("next-preferred.#{at}", tr[:name], :project)
|
74
|
+
end
|
75
|
+
jira.verbose "Transitioning #{key} to #{tr[:name]} (#{tr[:id]})"
|
76
|
+
jira.transition(key, tr[:id])
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end #keys.each
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
# vim: set sw=2 ts=2 :
|
89
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'terminal-table'
|
3
|
+
require 'vine'
|
4
|
+
require 'JiraMule/jiraUtils'
|
5
|
+
|
6
|
+
command :progress do |c|
|
7
|
+
c.syntax = 'jm progress [options] [<key>]'
|
8
|
+
c.summary = 'Show progress on issues'
|
9
|
+
c.description = %{}
|
10
|
+
# Show only overdue (today > duedate)
|
11
|
+
# Show only unstarted (timespent == 0)
|
12
|
+
# Show only Started (timespent > 0)
|
13
|
+
c.option '-s', '--status STATUSES', Array, %{Which status to limit to. (default is "In Progress")}
|
14
|
+
|
15
|
+
c.example 'Show how current project is going', %{jm progress}
|
16
|
+
c.example 'Show how work on task 5 is going', %{jm progress 5}
|
17
|
+
|
18
|
+
c.action do |args, options|
|
19
|
+
options.default :status=>[]
|
20
|
+
|
21
|
+
jira = JiraMule::JiraUtils.new(args, options)
|
22
|
+
keys = jira.expandKeys(args)
|
23
|
+
if keys.empty? and options.status.empty? then
|
24
|
+
options.status << 'In Progress'
|
25
|
+
end
|
26
|
+
|
27
|
+
query = []
|
28
|
+
query << %{assignee = #{jira.username}}
|
29
|
+
query << %{project = #{jira.project}} if keys.empty?
|
30
|
+
unless keys.empty? then
|
31
|
+
query << '(' + keys.map{|k| "key=#{k}"}.join(' OR ') + ')'
|
32
|
+
end
|
33
|
+
unless options.status.empty? then
|
34
|
+
query << '(' + options.status.map{|s| %{status="#{s}"}}.join(' OR ') + ')'
|
35
|
+
end
|
36
|
+
query = query.join(' AND ')
|
37
|
+
|
38
|
+
#jira.printVars(:q=>query)
|
39
|
+
progresses = jira.getIssues(query, ['key', 'workratio', 'aggregatetimespent',
|
40
|
+
'duedate', 'aggregatetimeoriginalestimate'])
|
41
|
+
|
42
|
+
rows = progresses.map do |issue|
|
43
|
+
estimate = (issue.access('fields.aggregatetimeoriginalestimate') or 0)/3600.0
|
44
|
+
progress = (issue.access('fields.aggregatetimespent') or 0)/3600.0
|
45
|
+
due = issue.access('fields.duedate')
|
46
|
+
percent = issue.access('fields.workratio')
|
47
|
+
if percent < 0 then
|
48
|
+
estimate = progress if estimate == 0
|
49
|
+
percent = (progress / estimate * 100).floor
|
50
|
+
end
|
51
|
+
ret = [ issue[:key], "%.2f"%[estimate], "%.2f"%[progress],
|
52
|
+
%{#{"%.1f"%[percent]}%}, due ]
|
53
|
+
if progress > estimate or (not due.nil? and Date.new >= Date.parse(due)) then
|
54
|
+
ret.map!{|v| HighLine.color((v or ''), :bold)}
|
55
|
+
end
|
56
|
+
ret
|
57
|
+
end.sort{|a,b| a[0].sub(/^\D+(\d+)$/,'\1').to_i <=> b[0].sub(/^\D+(\d+)$/,'\1').to_i }
|
58
|
+
|
59
|
+
tbl = Terminal::Table.new :headings=>[:key, :estimated, :progress, :percent, :due], :rows=>rows
|
60
|
+
tbl.align_column(1, :right)
|
61
|
+
tbl.align_column(2, :right)
|
62
|
+
tbl.align_column(3, :right)
|
63
|
+
puts tbl
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# vim: set sw=2 ts=2 :
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'terminal-table'
|
2
|
+
require 'mustache'
|
3
|
+
|
4
|
+
command :query do |c|
|
5
|
+
c.syntax = 'jm query [options] query'
|
6
|
+
c.summary = 'Get the keys from a jira query'
|
7
|
+
c.description = 'Run a query. '
|
8
|
+
c.example 'Get Open issues and dump everything', %{jm query status=Open --fields "" --json}
|
9
|
+
c.option '--[no-]raw', 'Do not prefix query with project and assignee'
|
10
|
+
c.option '--[no-]json', 'Output json reply instead of summary'
|
11
|
+
c.option '--fields FIELDS', Array, 'Which fields to return.'
|
12
|
+
c.option '--all_fields', 'Return all fields'
|
13
|
+
c.option '--format STYLE', String, 'Format for keys'
|
14
|
+
c.option '--style STYLE', String, 'Which style to use'
|
15
|
+
|
16
|
+
c.action do |args, options|
|
17
|
+
options.default :json => false, :all_fields => false, :style => 'basic'
|
18
|
+
|
19
|
+
allOfThem = {
|
20
|
+
:basic => {
|
21
|
+
:fields => [:key, :summary],
|
22
|
+
:format => %{{{key}} {{summary}}},
|
23
|
+
},
|
24
|
+
:info => {
|
25
|
+
:fields => [:key, :description, :assignee, :reporter, :priority, :issuetype,
|
26
|
+
:status, :resolution, :votes, :watches],
|
27
|
+
:format => %{{{key}}
|
28
|
+
Reporter: {{reporter.displayName}}
|
29
|
+
Assignee: {{assignee.displayName}}
|
30
|
+
Type: {{issuetype.name}} ({{priority.name}})
|
31
|
+
Status: {{status.name}} (Resolution: {{resolution.name}})
|
32
|
+
Watches: {{watches.watchCount}} Votes: {{votes.votes}}
|
33
|
+
Description: {{description}}
|
34
|
+
}
|
35
|
+
},
|
36
|
+
}
|
37
|
+
|
38
|
+
theStyle = allOfThem[options.style.to_sym]
|
39
|
+
theStyle[:fields] = options.fields if options.fields
|
40
|
+
|
41
|
+
jira = JiraMule::JiraUtils.new(args, options)
|
42
|
+
args.unshift("assignee = #{jira.username} AND") unless options.raw
|
43
|
+
args.unshift("project = #{jira.project} AND") unless options.raw
|
44
|
+
q = args.join(' ')
|
45
|
+
if options.all_fields then
|
46
|
+
issues = jira.getIssues(q, [])
|
47
|
+
else
|
48
|
+
issues = jira.getIssues(q, theStyle[:fields])
|
49
|
+
end
|
50
|
+
if options.json then
|
51
|
+
puts JSON.dump(issues)
|
52
|
+
else
|
53
|
+
|
54
|
+
format = theStyle[:format]
|
55
|
+
keys = issues.map do |issue|
|
56
|
+
Mustache.render(format, issue.merge(issue[:fields]))
|
57
|
+
end
|
58
|
+
keys.each {|k| puts k}
|
59
|
+
|
60
|
+
|
61
|
+
|
62
|
+
|
63
|
+
# headers = [:key]
|
64
|
+
# rows = issues.map do |item|
|
65
|
+
# rw = [item[:key]]
|
66
|
+
# item[:fields].each_pair do |fname, fvalue|
|
67
|
+
# headers << fname unless headers.include? fname
|
68
|
+
# rw << fvalue
|
69
|
+
# end
|
70
|
+
# rw
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# puts Terminal::Table.new :headings => headers, :rows=>rows
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
alias_command :q, :query
|
79
|
+
|
80
|
+
# vim: set sw=2 ts=2 :
|
@@ -0,0 +1,44 @@
|
|
1
|
+
|
2
|
+
command :release do |c|
|
3
|
+
c.syntax = 'jm release [options]'
|
4
|
+
c.summary = 'Little tool for releasing a version in Jira'
|
5
|
+
c.description = ''
|
6
|
+
c.example 'description', 'command example'
|
7
|
+
c.option '--some-switch', 'Some switch that does something'
|
8
|
+
c.action do |args, options|
|
9
|
+
# Do something or c.when_called Jira::Commands::Release
|
10
|
+
|
11
|
+
version = GitUtils.getVersion
|
12
|
+
newver = ask("\033[1m=?\033[0m Enter the version you want to release (#{version}) ")
|
13
|
+
version = newver unless newver == ''
|
14
|
+
|
15
|
+
project = $cfg['jira.project']
|
16
|
+
jira = JiraUtils.new(args, options)
|
17
|
+
|
18
|
+
jira.createVersion(project, version)
|
19
|
+
|
20
|
+
### Find all unreleased issues
|
21
|
+
query ="assignee = #{jira.username} AND project = #{project} AND (status = Resolved OR status = Closed) AND fixVersion = EMPTY"
|
22
|
+
keys = jira.getIssues(query).map {|item| item['key'] }
|
23
|
+
printVars({:keys=>keys})
|
24
|
+
|
25
|
+
### Mark issues as fixed by version
|
26
|
+
updt = { 'fixVersions'=>[{'add'=>{'name'=>version}}] }
|
27
|
+
jira.updateKeys(keys, updt)
|
28
|
+
|
29
|
+
### This is old process residue. So should consider removing
|
30
|
+
if $cfg['jira.alsoClose'] == true
|
31
|
+
puts "Also closing." if options.verbose
|
32
|
+
query = "assignee = #{jira.username} AND project = #{project} AND status = Resolved AND fixVersion != EMPTY"
|
33
|
+
keys = jira.getIssues(query).map {|item| item['key'] }
|
34
|
+
printVars({:Rkeys=>keys})
|
35
|
+
|
36
|
+
if !keys.empty?
|
37
|
+
jira.transition(keys, 'Closed')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# vim: set sw=2 ts=2 :
|
@@ -0,0 +1,68 @@
|
|
1
|
+
|
2
|
+
def printErr(msg)
|
3
|
+
$stdout.print("\033[1m=!\033[0m ")
|
4
|
+
$stdout.print(msg)
|
5
|
+
$stdout.print("\n")
|
6
|
+
end
|
7
|
+
|
8
|
+
command :testReady do |c|
|
9
|
+
c.syntax = 'jm testReady [options] [version]'
|
10
|
+
c.summary = 'Little tool for setting the fix version on testable issues'
|
11
|
+
c.description = %{On all issues in the Testing state, set the fix version and optionally reassign.
|
12
|
+
}
|
13
|
+
c.example 'Set the release version to the latest git tag', %{jm testReady}
|
14
|
+
c.example 'Set the release version to "v2.0"', %{jm testReady v2.0}
|
15
|
+
c.example 'Also reassign to the default', %{jm testReady -r v2.0}
|
16
|
+
c.example 'Also reassign to BOB', %{jm testReady v2.0 --assign BOB}
|
17
|
+
c.option '-r', '--[no-]reassign', 'Also reassign to Default'
|
18
|
+
c.option '-a', '--assign USER', 'Assign to USER'
|
19
|
+
c.action do |args, options|
|
20
|
+
options.default :reassign => false
|
21
|
+
|
22
|
+
if args[0].nil? then
|
23
|
+
version = GitUtils.getVersion
|
24
|
+
newver = ask("\033[1m=?\033[0m Enter the version you want to release (#{version}) ")
|
25
|
+
version = newver unless newver == ''
|
26
|
+
else
|
27
|
+
version = args[0]
|
28
|
+
end
|
29
|
+
|
30
|
+
jira = JiraUtils.new(args, options)
|
31
|
+
project = jira.project
|
32
|
+
|
33
|
+
if !options.assign.nil? then
|
34
|
+
users = jira.checkUser(options.assign)
|
35
|
+
if users.length > 1 then
|
36
|
+
printErr "User name '#{options.assign}' is ambigious."
|
37
|
+
printVars('Could be'=>users)
|
38
|
+
exit 4
|
39
|
+
end
|
40
|
+
if users.length <= 0 then
|
41
|
+
printErr "No users match #{options.assign}"
|
42
|
+
exit 4
|
43
|
+
end
|
44
|
+
options.assign = users[0]
|
45
|
+
printVars(:assign=>options.assign)
|
46
|
+
end
|
47
|
+
|
48
|
+
jira.createVersion(project, version)
|
49
|
+
|
50
|
+
### Find all unreleased issues
|
51
|
+
query ="assignee = #{jira.username} AND project = #{project} AND status = Testing"
|
52
|
+
keys = jira.getIssues(query).map {|item| item['key'] }
|
53
|
+
printVars({:keys=>keys})
|
54
|
+
|
55
|
+
### Mark issues as fixed by version
|
56
|
+
updt = { 'fixVersions'=>[{'add'=>{'name'=>version}}] }
|
57
|
+
## assign to '-1' to have Jira automatically assign it
|
58
|
+
updt['assignee'] = [{'set'=>{'name'=>'-1'}}] if options.reassign
|
59
|
+
updt['assignee'] = [{'set'=>{'name'=>options.assign}}] if options.assign
|
60
|
+
|
61
|
+
printVars(:update=>updt) if options.verbose
|
62
|
+
|
63
|
+
jira.updateKeys(keys, updt)
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# vim: set sw=2 ts=2 :
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'JiraMule/commands/assign'
|
2
|
+
require 'JiraMule/commands/attach'
|
3
|
+
require 'JiraMule/commands/config'
|
4
|
+
require 'JiraMule/commands/githubImport'
|
5
|
+
require 'JiraMule/commands/goto'
|
6
|
+
require 'JiraMule/commands/kanban'
|
7
|
+
require 'JiraMule/commands/link'
|
8
|
+
require 'JiraMule/commands/logWork'
|
9
|
+
require 'JiraMule/commands/next'
|
10
|
+
require 'JiraMule/commands/progress'
|
11
|
+
require 'JiraMule/commands/query'
|
12
|
+
require 'JiraMule/commands/timesheet'
|
13
|
+
|
14
|
+
#require 'JiraMule/commands/release'
|
15
|
+
#require 'JiraMule/commands/testReady'
|
16
|
+
# difference between testReady and release is two things:
|
17
|
+
# 1. testReady also assigns
|
18
|
+
# 2. release also transitions
|
19
|
+
#
|
20
|
+
# So, merge all that into one command and have the parts be --options; maybe
|