Compare commits

...

4 Commits

Author SHA1 Message Date
DylanEtris 395ddb38c7
Merge 692ae9a5dc into 30e516a3aa 2025-03-03 11:51:08 -05:00
nasso 30e516a3aa
feat(jj): add jujutsu plugin (#12292) 2025-03-03 17:15:43 +01:00
Dylan Etris 692ae9a5dc fix(jira-auto-worklog): Fix typo in jira-auto-worklog-check 2025-01-14 14:50:57 -05:00
Dylan Etris 75483693fb feat(jira-auto-worklog): Add jira-auto-worklog plugin 2025-01-14 13:58:22 -05:00
6 changed files with 378 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 "Jira auto-worklog is configured correctly. Time will be logged."
else
puts "Jira auto-worklog 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

89
plugins/jj/README.md Normal file
View File

@ -0,0 +1,89 @@
# jj - Jujutsu CLI
This plugin provides autocompletion for [jj](https://martinvonz.github.io/jj).
To use it, add `jj` to the plugins array of your zshrc file:
```zsh
plugins=(... jj)
```
## Aliases
| Alias | Command |
| ------ | ----------------------------- |
| jjc | `jj commit` |
| jjcmsg | `jj commit --message` |
| jjd | `jj diff` |
| jjdmsg | `jj desc --message` |
| jjds | `jj desc` |
| jje | `jj edit` |
| jjgcl | `jj git clone` |
| jjgf | `jj git fetch` |
| jjgp | `jj git push` |
| jjl | `jj log` |
| jjla | `jj log -r "all()"` |
| jjn | `jj new` |
| jjrb | `jj rebase` |
| jjrs | `jj restore` |
| jjrt | `cd "$(jj root \|\| echo .)"` |
| jjsp | `jj split` |
| jjsq | `jj squash` |
## Prompt usage
Because `jj` has a very powerful [template syntax](https://martinvonz.github.io/jj/latest/templates/), this
plugin only exposes a convenience function `jj_prompt_template` to read information from the current change.
It is basically the same as `jj log --no-graph -r @ -T $1`:
```sh
_my_theme_jj_info() {
jj_prompt_template 'self.change_id().shortest(3)'
}
PROMPT='$(_my_theme_jj_info) $'
```
`jj_prompt_template` escapes `%` signs in the output. Use `jj_prompt_template_raw` if you don't want that
(e.g. to colorize the output).
However, because `jj` can be used inside a Git repository, some themes might clash with it. Generally, you can
fix it with a wrapper function that tries `jj` first and then falls back to `git` if it didn't work:
```sh
_my_theme_vcs_info() {
jj_prompt_template 'self.change_id().shortest(3)' \
|| git_prompt_info
}
PROMPT='$(_my_theme_vcs_info) $'
```
You can find an example
[here](https://github.com/nasso/omzsh/blob/e439e494f22f4fd4ef1b6cb64626255f4b341c1b/themes/sunakayu.zsh-theme).
### Performance
Sometimes `jj` can be slower than `git`.
If you feel slowdowns, consider using the following:
```
zstyle :omz:plugins:jj ignore-working-copy yes
```
This will add `--ignore-working-copy` to all `jj` commands executed by your prompt. The downside here is that
your prompt might be out-of-sync until the next time `jj` gets a chance to _not_ ignore the working copy (i.e.
you manually run a `jj` command).
If you prefer to keep your prompt always up-to-date but still don't want to _feel_ the slowdown, you can make
your prompt asynchronous. This plugin doesn't do this automatically so you'd have to hack your theme a bit for
that.
## See Also
- [martinvonz/jj](https://github.com/martinvonz/jj)
## Contributors
- [nasso](https://github.com/nasso) - Plugin Author

53
plugins/jj/jj.plugin.zsh Normal file
View File

@ -0,0 +1,53 @@
# if jj is not found, don't do the rest of the script
if (( ! $+commands[jj] )); then
return
fi
# If the completion file doesn't exist yet, we need to autoload it and
# bind it to `jj`. Otherwise, compinit will have already done that.
if [[ ! -f "$ZSH_CACHE_DIR/completions/_jj" ]]; then
typeset -g -A _comps
autoload -Uz _jj
_comps[jj]=_jj
fi
jj util completion zsh >| "$ZSH_CACHE_DIR/completions/_jj" &|
function __jj_prompt_jj() {
local -a flags
flags=("--no-pager")
if zstyle -t ':omz:plugins:jj' ignore-working-copy; then
flags+=("--ignore-working-copy")
fi
command jj $flags "$@"
}
# convenience functions for themes
function jj_prompt_template_raw() {
__jj_prompt_jj log --no-graph -r @ -T "$@" 2> /dev/null
}
function jj_prompt_template() {
local out
out=$(jj_prompt_template_raw "$@") || return 1
echo "${out:gs/%/%%}"
}
# Aliases (sorted alphabetically)
alias jjc='jj commit'
alias jjcmsg='jj commit --message'
alias jjd='jj diff'
alias jjdmsg='jj desc --message'
alias jjds='jj desc'
alias jje='jj edit'
alias jjgcl='jj git clone'
alias jjgf='jj git fetch'
alias jjgp='jj git push'
alias jjl='jj log'
alias jjla='jj log -r "all()"'
alias jjn='jj new'
alias jjrb='jj rebase'
alias jjrs='jj restore'
alias jjrt='cd "$(jj root || echo .)"'
alias jjsp='jj split'
alias jjsq='jj squash'