Skip to content

Instantly share code, notes, and snippets.

@phallstrom
Created November 9, 2022 16:37
Show Gist options
  • Save phallstrom/40c4667d8d1d314a2132cffb2e03f080 to your computer and use it in GitHub Desktop.
Save phallstrom/40c4667d8d1d314a2132cffb2e03f080 to your computer and use it in GitHub Desktop.
sailfish
### sailfish
Sailfish is a ruby script that is meant to turn a git commit message into a
JIRA ticket and then update that commit message to reference the newly created
JIRA ticket.
To use it you will need Ruby installed and a few gems (see top of script file
itself for details).
You will also need to create `~/.sailfish` with contents similar to the following:
```
---
username: youremail@example.com
password: xxxxxxxxxxxxxxxxxxxxxxxx
project_key: ABC
board_name: App Dev Team
sprint_prefix: Dev Sprint
```
You can also set defaults for issue type, points, priority, and epic. See the
script file for details.
Once configured, to use it write a normal git commit message to a file and then run:
% path/to/devfu/sailfish path/to/commit/msg
Ideally you'd configure your git tooling to do this for you. If you use the Git
CLI and VIM, checkout [vim-sailfish](https://github.com/phallstrom/vim-sailfish).
#!/usr/bin/env ruby
# NOTE: You will almost certainly have to tweak some of the parameters sent to JIRA
# depending on how your JIRA has been setup and what fields are required, etc. Pay close
# attention to any "custom_field_12345" sections of the code.
# https://developer.atlassian.com/cloud/jira/platform/rest/v2/
# https://developer.atlassian.com/cloud/jira/platform/rest/v3/
# https://docs.atlassian.com/jira-software/REST/7.3.1/
require "rubygems"
require "yaml"
require "pry"
require "http"
################################################################################
config = {
host: "example.atlassian.net",
issue_type: "Story", # Story, Bug, Task
points: 2,
priority: 3,
epic: nil, # ex: RKT-602
}
################################################################################
[ ENV["HOME"], Dir.pwd ].each do |dir|
config_file = File.join(dir, ".sailfish")
next unless File.exist?(config_file)
config.merge!(YAML.load(File.read(config_file), symbolize_names: true))
end
raise "You must set 'username' in the configuration" unless config[:username]
raise "You must set 'password' in the configuration" unless config[:password]
raise "You must set 'project_key' in the configuration" unless config[:project_key]
raise "You must set 'board_name' in the configuration" unless config[:board_name]
raise "You must set 'sprint_prefix' in the configuration" unless config[:sprint_prefix]
################################################################################
project_key = config[:project_key]
board_name = config[:board_name]
origin = config[:origin]
issue_type = config[:issue_type]
points = config[:points]
priority = config[:priority]
epic = config[:epic]
################################################################################
commit_file = ARGV[0]
raise "Commit file '#{commit_file}' does not exist" unless File.exist?(commit_file)
commit_msg = File.readlines(commit_file)
summary = commit_msg.first.strip
description = commit_msg.drop(1).reject { |l| l.start_with?("#") }.join.strip
raise "Commit file does not have a summary" if summary.to_s.empty?
raise "Commit file does not have a description" if description.to_s.empty?
raise "Commit file already references a ticket" if description.match?(/^(fixes|refs|closes) #{project_key}-\d/)
################################################################################
http = HTTP.basic_auth(user: config[:username], pass: config[:password])
################################################################################
res = http.get("https://#{config[:host]}/rest/agile/1.0/board")
board = JSON.parse(res.body.to_s)["values"].detect { |e| e["location"]["projectKey"] == project_key && e["name"] == board_name}
board_id = board["id"]
project_id = board["location"]["projectId"]
raise("Unable to find board id for project key '#{project_key}'") if board_id.nil?
res = http.get("https://#{config[:host]}/rest/api/3/issuetype/project?projectId=#{project_id}")
issue_type_id = JSON.parse(res.body.to_s).detect { |e| e["name"] == issue_type }["id"]
raise("Unable to find issue type id") if issue_type_id.nil?
res = http.get("https://#{config[:host]}/rest/api/3/myself")
assignee = JSON.parse(res.body.to_s)["accountId"]
raise("Unable to find assignee") if assignee.nil?
res = http.get("https://#{config[:host]}/rest/agile/1.0/board/#{board_id}/sprint?state=active")
sprint_id = JSON.parse(res.body.to_s)["values"].select { |e| e["name"].match?(/^#{config[:sprint_prefix]}/i) }.sort_by { |e| e["endDate"] }.last["id"]
raise("Unable to find active sprint") if sprint_id.nil?
res = http.get("https://#{config[:host]}/rest/api/3/field")
fields = JSON.parse(res.body.to_s)
story_points_field = fields.detect { |e| e["name"] == "Story Points" }["key"].to_sym
sprint_field = fields.detect { |e| e["name"] == "Sprint" }["key"].to_sym
epic_field = fields.detect { |e| e["name"] == "Epic Link" }["key"].to_sym
devteam_field = fields.detect { |e| e["name"] == "YourDev Team" && e["scope"].nil? }["key"].to_sym
################################################################################
begin
res = http.post("https://#{config[:host]}/rest/api/2/issue", json: {
fields: {
summary: summary,
description: description,
project: { id: project_id.to_s },
issuetype: { id: issue_type_id },
priority: { id: priority.to_s },
assignee: { id: assignee },
sprint_field => sprint_id.to_i,
epic_field => epic,
devteam_field => [{ id: "10116"}] # "Your App Dev Team"
}
})
if res.code == 201
ticket = JSON.parse(res.body.to_s)
File.write(commit_file, commit_msg.insert(1, "\n", "fixes [#{ticket["key"]}]", "\n").join)
begin
res = http.get("https://#{config[:host]}/rest/api/2/issue/#{ticket["key"]}/transitions")
transition_id = JSON.parse(res.body.to_s)["transitions"].detect { |e| e["name"] == "In Progress" }["id"]
http.post("https://#{config[:host]}/rest/api/2/issue/#{ticket["key"]}/transitions", json: {
transition: { id: transition_id.to_i }
})
# fields: { story_points_field => points.to_i }
rescue
end
else
puts
puts "!!! ERROR !!!"
puts
begin
puts JSON.pretty_generate(JSON.parse(res.body.to_s))
rescue
puts res.body.to_s
end
puts
end
rescue StandardError => e
puts
puts "!!! ERROR !!!"
puts
puts e.to_s
puts
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment