feat(jira-auto-worklog): Add jira-auto-worklog plugin

This commit is contained in:
Dylan Etris 2025-01-14 13:47:57 -05:00
parent 7a3695aadf
commit 75483693fb
4 changed files with 236 additions and 0 deletions

View File

@ -0,0 +1,43 @@
# JIRA auto-worklog
This plugin automatically logs time to Atlassian's [JIRA](https://www.atlassian.com/software/jira) bug tracking software when on a git branch with a JIRA issue ID in the branch name.
## Installation
Add the plugin to the list of plugins for Oh My Zsh to load (inside `~/.zshrc`):
```sh
plugins=(
# other plugins...
jira-auto-worklog
)
```
## Usage
Every time the command prompt is generated, the time spent on a JIRA branch is accumulated. Once the accumulated time exceeds a threshold, the time is logged in the background. By default, the time is logged every minute.
To prevent AFK time from being counted, no time is logged if there is no activity on a JIRA branch for a certain amount of time. By default the AFK threshold is 30m.
## Setup
The URL for your JIRA instance is located in the following places, in order of precedence:
1. `$JIRA_URL` environment variable
2. `./.jira-url` file in the current directory
3. `~/.jira-url` file in your home directory
The PAT for your JIRA instance is located in the following places, in order of precedence:
1. `./.jira-pat` file in the current directory
2. `~/.jira-pat` file in your home directory
Run `jira-auto-worklog-check` in this directory to check if the plugin is correctly configured.
### Variables
* `$JIRA_URL` - Your JIRA instance's URL
* `$JIRA_AUTO_WORKLOG_AFK_THRESHOLD` - The number of minutes of AFK time before no time is logged. Defaults to 30
* `$JIRA_AUTO_WORKLOG_COMMENT` - The comment to add to the work log. Defaults to an empty string
* `$JIRA_AUTO_WORKLOG_PRECISION` - The size of the chunks of time that are logged. Must be greater than zero. Defaults to 1. For example, `JIRA_AUTO_WORKLOG_PRECISION=15` will log 15 minutes after being active for 15 minutes a JIRA branch.

View File

@ -0,0 +1,108 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'net/http'
require 'json'
require 'yaml'
require 'date'
JIRA_REGEX = /([A-Z]+-\d+)/.freeze
CONFIG_PATH = "#{Dir.home}/.jira-auto-worklog"
def jira_url
ENV.fetch('JIRA_URL', nil) ||
(File.read('./.jira-url').chomp if File.exist?('./.jira-url')) ||
(File.read("#{Dir.home}/.jira-url").chomp if File.exist?("#{Dir.home}/.jira-url")) ||
nil
end
def jira_pat
(File.read('./.jira-pat').chomp if File.exist?('./.jira-pat')) ||
(File.read("#{Dir.home}/.jira-pat").chomp if File.exist?("#{Dir.home}/.jira-pat")) ||
nil
end
def afk_threshold
ENV.fetch('JIRA_AUTO_WORKLOG_AFK_THRESHOLD', 30).to_i
end
def comment
ENV.fetch('JIRA_AUTO_WORKLOG_COMMENT', '')
end
def precision
ENV.fetch('JIRA_AUTO_WORKLOG_PRECISION', 1).to_i
end
unless jira_url
puts 'JIRA URL not found.'
puts
puts 'Valid Locations are, in order of precedence:'
puts ' 1. JIRA_URL environment variable'
puts ' 2. ./.jira-url'
puts ' 3. ~/.jira-url'
exit(1)
end
unless jira_pat
puts 'JIRA Personal Access Token not found.'
puts
puts 'Valid Locations are, in order of precedence:'
puts ' 1. ./.jira-pat'
puts ' 2. ~/.jira-pat'
exit(1)
end
unless precision > 0
puts 'Precision must be a positive integer.'
exit(1)
end
branch = `command git rev-parse --abbrev-ref --symbolic-full-name HEAD 2> /dev/null`
exit(0) unless (match = branch.match(JIRA_REGEX))
ticket = match[1]
# load the config
data = {}
if File.exist?(CONFIG_PATH)
data = YAML.safe_load(File.read(CONFIG_PATH), permitted_classes: [Time])
end
data['overflow'] ||= 0
data['last_updated'] ||= Time.now
# Calculate the delta
delta_s = Time.now.utc.to_i - data['last_updated'].to_i
data['last_updated'] = Time.now.utc
if (delta_s / 60) > afk_threshold
File.write(CONFIG_PATH, YAML.dump(data))
exit(0)
end
time_spent = delta_s + data['overflow']
precision_s = precision * 60
minutes = (time_spent / precision_s)
data['overflow'] = (time_spent % precision_s)
File.write(CONFIG_PATH, YAML.dump(data))
exit(0) if minutes.zero?
uri = URI("#{jira_url}/rest/api/2/issue/#{ticket}/worklog")
request = Net::HTTP::Post.new(uri)
request['Authorization'] = format('Bearer %s', jira_pat)
request['Content-Type'] = 'application/json'
request.body = JSON.generate({
'timeSpent' => minutes,
'comment' => comment
})
Net::HTTP.start(uri.host, uri.port, use_ssl: true, verify_mode: OpenSSL::SSL::VERIFY_PEER) do |http|
res = http.request(request)
if res.is_a?(Net::HTTPSuccess)
puts "Logged #{minutes} minutes to #{ticket}"
else
puts "Failed to log time to #{ticket}"
end
end

View File

@ -0,0 +1,65 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'net/http'
require 'json'
require 'yaml'
require 'date'
JIRA_REGEX = /([A-Z]+-\d+)/.freeze
def jira_url
ENV.fetch('JIRA_URL', nil) ||
(File.read('./.jira-url').chomp if File.exist?('./.jira-url')) ||
(File.read("#{Dir.home}/.jira-url").chomp if File.exist?("#{Dir.home}/.jira-url")) ||
nil
end
def jira_pat
(File.read('./.jira-pat').chomp if File.exist?('./.jira-pat')) ||
(File.read("#{Dir.home}/.jira-pat").chomp if File.exist?("#{Dir.home}/.jira-pat")) ||
nil
end
def precision
ENV.fetch('JIRA_AUTO_WORKLOG_PRECISION', 1).to_i
end
unless jira_url
puts 'JIRA URL not found.'
puts
puts 'Valid Locations are, in order of precedence:'
puts ' 1. JIRA_URL environment variable'
puts ' 2. ./.jira-url'
puts ' 3. ~/.jira-url'
exit(1)
end
unless jira_pat
puts 'JIRA Personal Access Token not found.'
puts
puts 'Valid Locations are, in order of precedence:'
puts ' 1. ./.jira-pat'
puts ' 2. ~/.jira-pat'
exit(1)
end
unless precision > 0
puts 'Precision must be a positive integer.'
exit(1)
end
uri = URI("#{jira_url}/rest/api/2/myself")
request = Net::HTTP::Get.new(uri)
request['Authorization'] = format('Bearer %s', jira_pat)
request['Content-Type'] = 'application/json'
Net::HTTP.start(uri.host, uri.port, use_ssl: true, verify_mode: OpenSSL::SSL::VERIFY_PEER) do |http|
res = http.request(request)
if res.is_a?(Net::HTTPSuccess)
puts "Counttime is configured correctly. Time will be logged."
else
puts "Counttime is not configured correctly. Please check your JIRA URL and Personal Access Token."
end
end

View File

@ -0,0 +1,20 @@
# zle-line-init widget (don't redefine if already defined)
(( ! ${+functions[_jira-auto-worklog_zle-line-init]} )) || return 0
# Get the directory of this file
dir="$(dirname "$0")"
case "$widgets[zle-line-init]" in
# Simply define the function if zle-line-init doesn't yet exist
builtin|"") function _jira-auto-worklog_zle-line-init() {
($dir/jira-auto-worklog &> /dev/null)
} ;;
# Override the current zle-line-init widget, calling the old one
user:*) zle -N _jira-auto-worklog_orig_zle-line-init "${widgets[zle-line-init]#user:}"
function _jira-auto-worklog_zle-line-init() {
($dir/jira-auto-worklog &> /dev/null)
zle _jira-auto-worklog_orig_zle-line-init -- "$@"
} ;;
esac
zle -N zle-line-init _jira-auto-worklog_zle-line-init