diff --git a/plugins/jira-auto-worklog/README.md b/plugins/jira-auto-worklog/README.md new file mode 100644 index 000000000..607690533 --- /dev/null +++ b/plugins/jira-auto-worklog/README.md @@ -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. \ No newline at end of file diff --git a/plugins/jira-auto-worklog/jira-auto-worklog b/plugins/jira-auto-worklog/jira-auto-worklog new file mode 100755 index 000000000..889c11f32 --- /dev/null +++ b/plugins/jira-auto-worklog/jira-auto-worklog @@ -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 \ No newline at end of file diff --git a/plugins/jira-auto-worklog/jira-auto-worklog-check b/plugins/jira-auto-worklog/jira-auto-worklog-check new file mode 100755 index 000000000..db0a72a9c --- /dev/null +++ b/plugins/jira-auto-worklog/jira-auto-worklog-check @@ -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 diff --git a/plugins/jira-auto-worklog/jira-auto-worklog.plugin.zsh b/plugins/jira-auto-worklog/jira-auto-worklog.plugin.zsh new file mode 100644 index 000000000..b313b2d31 --- /dev/null +++ b/plugins/jira-auto-worklog/jira-auto-worklog.plugin.zsh @@ -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