Report Synopsis
Given a JIRA project name, a start date and and end date, find total counts of issues completed before, on or after the due date, per priority:
Total | Unfinished | Finished On Due | Finished Before Due | Finished After Due | |
Major, with due date | |||||
Major, without due date | - | - | - | ||
Critical, with due date | |||||
Critical, without due date | - | - | - |
Implementation
In Ruby, using the jira-ruby
gem.
First we set up a $client
object, using HTTP Basic authentication;
require 'jira' require 'parallel' HOST='https://REDACTED.atlassian.net' $options = { :site => HOST, :context_path => '', :username => 'myusername', :password => %q{REDACTED}, :auth_type => :basic } $client = JIRA::Client.new($options)
Next, we fetch the issues we're interested in:
issues = $client.Issue.jql("project=UX and updated>='2015-10-01' AND updated<='2015-10-27'", max_results:1000) { |i| i.fetch; i }
Now for the interesting part. Issues with a due date will have a resolutiondate
field, which we can parse wtih strptime
:
rdate = issues.find { |i| i.resolutiondate }.resolutiondate => "2015-10-26T09:23:07.000-0700" rdate = DateTime.strptime(rdate, '%Y-%m-%dT%H:%M:%S.%L%z') => #<DateTime: 2015-10-26T09:23:07-07:00 ((2457322j,58987s,0n),-25200s,2299161j)> rdate = rdate.to_date # Discard time portion => #<Date: 2015-10-26 ((2457322j,0s,0n),+0s,2299161j)>
We will also have a duedate
, which we can parse similarly:
ddate = issues.find { |i| i.duedate }.duedate => "2015-11-05" Date.strptime(ddate, "%Y-%m-%d") => #<Date: 2015-11-05 ((2457332j,0s,0n),+0s,2299161j)>
and a priority
, which is actually an object, so we'll just use the name
part of it:
[14] pry(main)> ddate = issues.find { |i| i.priority }.priority.name => "Critical"
Now we need to:
- group issues by priority
- for each priority's group, group again by classification:
- if there is no resolution date, classify as "Unfinished"
- if there is a resolution date, but no due date, classify "Finished, no due date"
- If the resolution date and due date match, classify as "On Due"
- If the resolution date is earlier than due date, classify as "Before Due"
- If the resolution date is after the due date, classify as "After Due"
- for each priority's group, group again by classification:
The Ruby Enumerable
module's group_by
method does the group-into-buckets job nicely, giving us a hash-of-hashes data structure.
data = issues.group_by { |i| i.priority.name + ", " + (i.duedate ? "with" : "without") + " due date" } .inject({}) { |h, (k, v)| h[k] = v.group_by { |i| resdate = i.resolutiondate && DateTime.strptime(i.resolutiondate, '%Y-%m-%dT%H:%M:%S.%L%z').to_date duedate = i.duedate && Date.strptime(i.duedate, "%Y-%m-%d") if !resdate then "Unfinished" elsif !duedate then "Finished, no due date" elsif resdate == duedate then "On Due" elsif resdate < duedate then "Before Due" else "After Due" end } h } data.keys # Show our top-level groupings (this will be rows) => ["Critical, without due date", "Major, without due date", "Minor, without due date", "Major, with due date", "Blocker, without due date"] cols = data.collect { |(k,v)| v.keys }.flatten.uniq # Identify unique columns. => ["Unfinished", "Finished, no due date"]
Then for reporting. I have yet to find a nice easy way of formatting a 2d array of numbers with arbitrary column numbers. The pretty print pp module might be good enough:
result = [[nil] + cols] # First row is a list of columns, starting with a nil # Add rows, consisting of an array beginning with 'rowname', followed by the number of issues, or zero result += data.collect { |(rowname,v)| [rowname] + cols.collect { |col| v[col] ? v[col].size : 0 } } require 'pp' pp result [[nil, "Unfinished", "Finished, no due date"], ["Critical, without due date", 1, 3], ["Major, without due date", 133, 143], ["Minor, without due date", 27, 10], ["Major, with due date", 3, 0], ["Blocker, without due date", 0, 4]]
or some primitive HTML output:
puts "<table border=1>\n" + result.map.with_index { |r, i| "<tr>" + r.map.with_index { |c, j| el = (i==0 || j==0 ? "th" : "td") "<#{el}>" + (c ? c.to_s : "\t") + "</#{el}>" }.join + "</tr>" }.join("\n") + "</table>" => <table border=1> <tr><th> </th><th>Unfinished</th><th>Finished, no due date</th></tr> <tr><th>Critical, without due date</th><td>1</td><td>3</td></tr> <tr><th>Major, without due date</th><td>133</td><td>143</td></tr> <tr><th>Minor, without due date</th><td>27</td><td>10</td></tr> <tr><th>Major, with due date</th><td>3</td><td>0</td></tr> <tr><th>Blocker, without due date</th><td>0</td><td>4</td></tr></table>
which renders as:
Unfinished | Finished, no due date | |
---|---|---|
Critical, without due date | 1 | 3 |
Major, without due date | 133 | 143 |
Minor, without due date | 27 | 10 |
Major, with due date | 3 | 0 |
Blocker, without due date | 0 | 4 |