-
-
Save GregPK/b59be02977b16ec4c4073065f870b6a0 to your computer and use it in GitHub Desktop.
JIRA script for everyday use
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env /home/gregpk/.rbenv/shims/ruby | |
# This script handles personal check for JIRA tickets. | |
# Currently it supports the following commands: | |
# - daily - output and open HTML report about the current tickets | |
# - breaching - check for breaching issues and notify if there are any | |
require 'bundler/inline' | |
require 'json' | |
require 'optparse' | |
require 'date' | |
require 'csv' | |
gemfile do | |
source 'https://rubygems.org' | |
gem 'jira-ruby' | |
gem 'pry' | |
end | |
# Basic objects | |
Issue = Struct.new(:key, :assignee, :status, :title, :points) | |
class MyJiraStuff | |
COMMANDS = %w[daily assess breaching].freeze | |
attr_accessor :command, :options | |
def initialize(args) | |
parse_options(args) | |
end | |
def run | |
case command | |
when 'daily' | |
daily | |
when 'breaching' | |
breaching | |
else | |
raise "Command [#{command}] not implemented yet" | |
end | |
end | |
def breaching | |
breach_days = 3 | |
resolution_timestamp = (Date.today + breach_days).to_s | |
q = <<~Q | |
(project = "BILL" OR project = "ER") | |
AND assignee IN (currentUser()) AND statusCategory in ("To Do", "In Progress") | |
AND issuetype != "Epic" AND "Resolution Target[Time stamp]" <= "#{resolution_timestamp}" | |
ORDER BY created DESC | |
Q | |
issues = JiraQuery.new(q, 'Breaching issues').issues | |
if issues.empty? | |
puts "No breaching issues found <= [#{resolution_timestamp}]" | |
return | |
end | |
issue_keys = issues.map(&:key).join(', ') | |
title = 'There are Jira issues assigned to you that are breaching soon' | |
body = "Issues: \n#{issue_keys}\n" | |
`notify-send '#{title}' "#{body}"` | |
puts title | |
puts messages | |
end | |
def daily | |
stories_query = '(project = "BILL" OR project = "ER") AND assignee IN (currentUser()) AND statusCategory in ("To Do", "In Progress") AND issuetype != "Epic"' | |
epics_query = '(project = "BILL" OR project = "ER") AND assignee IN (currentUser()) AND statusCategory in ("To Do", "In Progress") AND issuetype = "Epic"' | |
query_and_report([ | |
JiraQuery.new(stories_query, 'Issues'), | |
JiraQuery.new(epics_query, 'Epics'), | |
]) | |
end | |
Result = Struct.new(:who, :total_points, :bill_points, :er_points, :bill_issues, :er_issues) | |
private | |
def parse_options(args) | |
options = {} | |
self.command = args.first | |
opt_parser = OptionParser.new do |opts| | |
opts.banner = 'Usage: ,j [command] [options]' | |
opts.on('-sDATE', '--date-start=DATE', 'Start date for comparison') do |n| | |
options[:date_start] = Date.parse(n) | |
end | |
opts.on('-eDATE', '--date-end=DATE', 'End date for comparison') do |n| | |
options[:date_end] = Date.parse(n) | |
end | |
end | |
opt_parser.parse!(args) | |
unless options[:date_start] | |
options[:date_start] = Date.today - (Date.today.day - 1) # first_day_of_month | |
end | |
unless options[:date_end] | |
options[:date_end] = Date.today - (Date.today.day - 1) - 1 >> 1 # last day of month | |
end | |
unless COMMANDS.include?(command) | |
puts <<~HLP | |
Invalid command: | |
Available commands: #{COMMANDS.join(', ')} | |
HLP | |
end | |
self.options = options | |
end | |
def query_and_report(title_queries) | |
issue_lists = [] | |
Array(title_queries).each do |tq| | |
data_stories = `jira list -q '#{tq.query}'` | |
issue_lists << JiraQuery.new(tq.query, tq.title) | |
end | |
report = HTMLReport.new(issue_lists) | |
report.write_report | |
report.open_report | |
report.print_txt_version | |
end | |
end | |
# Responsible for generating HTML reports | |
class HTMLReport | |
attr_accessor :str, :file, :story_lists | |
def initialize(story_lists) | |
self.story_lists = story_lists | |
self.str = '' | |
end | |
def write_report | |
str = '' | |
story_lists.each do |story_list| | |
str += "<h2>#{story_list.title}</h2>\n" | |
str += "<ul>\n" | |
story_list.issues.each do |issue| | |
verb = case issue.status | |
when 'To Do' then 'Left' | |
when 'In Progress' then 'Started on' | |
when 'In Review' then 'Finished' | |
else 'Also working on' | |
end | |
str += %|<li>#{verb} <a href="https://toptal-core.atlassian.net/browse/#{issue.key}">#{issue.key} (#{issue.title})</a></li>| | |
end | |
str += "</ul>\n" | |
end | |
self.str = str | |
write_to_tmp_file | |
end | |
def open_report | |
`google-chrome-stable #{file}` | |
end | |
def print_txt_version | |
story_lists.each do |story_list| | |
puts "# #{story_list.title}:" | |
story_list.issues.each do |issue| | |
puts "[#{issue.key}] #{issue.title}" | |
end | |
end | |
end | |
private | |
def write_to_tmp_file | |
f = File.new('/tmp/jdaily'+Random.new.rand.to_s+'.html', 'w+') | |
f.write(str) | |
f.rewind | |
f.close | |
self.file = f.path | |
end | |
end | |
class JiraQuery | |
attr_accessor :query, :title, :client | |
def initialize(query, title=nil) | |
@title = title | |
@query = query | |
@client = JIRA::Client.new(jira_options) | |
end | |
def jira_options | |
{ | |
site: 'https://toptal-core.atlassian.net/', | |
context_path: '', | |
username: '[email protected]', | |
password: ENV.fetch('JIRA_GEM_AUTH_TOKEN'), | |
auth_type: :basic | |
} | |
end | |
def issues | |
hashes = client.Issue.jql(query, max_results: 500) | |
@issues ||= hashes.map{ |h| data_hash_to_issue(h) }.compact | |
end | |
def data_hash_to_issue(issue) | |
return unless issue.fields['assignee'] | |
Issue.new( | |
key: issue.key, | |
title: issue.fields['summary'], | |
assignee: issue.fields['assignee']['displayName'], | |
points: issue.fields['customfield_10024'].to_f, | |
status: issue.fields['status']['name'] | |
) | |
rescue => e | |
raise StandardError.new("Problem with parsing #{issue.key}: error: #{e.inspect}") | |
end | |
end | |
MyJiraStuff.new(ARGV).run |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment