Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add reason field to notification output #79

Merged
merged 6 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 79 additions & 40 deletions gh-notify
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,17 @@ ${WHITE_BOLD}Key Bindings fzf${NC}
${GREEN}ctrl+x ${NC} write a comment with the editor and quit
${GREEN}esc ${NC} quit

${WHITE_BOLD}Table Format${NC}
${GREEN}unread symbol${NC} indicates unread status
${GREEN}time ${NC} last time the notification was read
${GREEN}repo ${NC} related repository
${GREEN}type ${NC} notification type
${GREEN}number ${NC} associated number
${GREEN}reason ${NC} trigger reason
${GREEN}title ${NC} notification title

${WHITE_BOLD}Example${NC}
# Display the last 20 notifications
${DARK_GRAY}# Display the last 20 notifications${NC}
gh notify -an 20
EOF
)
Expand Down Expand Up @@ -129,40 +138,67 @@ get_notifs() {
local_page_size=$num_notifications
fi
printf >&2 "." # "marching ants" because sometimes this takes a bit.
# Use '-F/--field' to pass a variable that is a number, Boolean, or null. Use '-f/--raw-field' for other variables.
# Use '-F/--field' to pass a variable that is a number, Boolean, or null. Use '-f/--raw-field'
# for other variables.
# Playground to test jq: https://jqplay.org/
gh api --header "$GH_REST_API_VERSION" --method GET notifications --cache=0s \
--field per_page="$local_page_size" --field page="$page_num" \
--field participating="$only_participating_flag" --field all="$include_all_flag" \
--jq \
'def colors:
$'def colors:
{
"cyan": "\u001b[36m",
"cyan_bold": "\u001b[1;36m",
"gray": "\u001b[90m",
"magenta": "\u001b[35m",
"white_bold": "\u001b[1;37m",
"reset": "\u001b[0m"
};
def colored(text; color):
colors[color] + text + colors.reset;
.[] | {
updated_short: .updated_at | fromdateiso8601 | strftime("%Y-%m"),
full_name: .repository.full_name,
# UTC time ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ
# https://docs.github.com/en/rest/overview/resources-in-the-rest-api#timezones
iso8601: now | strftime("%Y-%m-%dT%H:%M:%SZ"),
thread_id: .id,
thread_state: (if .unread then "UNREAD" else "READ" end),
comment_url: .subject.latest_comment_url | tostring | split("/") | last,
timefmt: colored(.updated_at | fromdateiso8601 | strflocaltime("%d/%b %H:%M"); "gray"),
owner: colored(.repository.owner.login; "cyan"),
name: colored(.repository.name; "cyan_bold"),
type: .subject.type,
repo_full_name: .repository.full_name,
unread_symbol: colored((if .unread then "\u25cf" else "\u00a0" end); "magenta"),
# make sure each outcome has an equal number of fields separated by spaces
timefmt: colored((.last_read_at | fromdateiso8601) as $time_sec |
# difference is less than one hour
if ((now - $time_sec) / 3600) < 1 then
(now - $time_sec) / 60 | floor | tostring + "min ago"
# difference is less than 24 hours
elif ((now - $time_sec) / 3600) < 24 then
(now - $time_sec) / 3600 | floor | tostring + "h ago"
else
$time_sec | strflocaltime("%d/%b %H:%M")
end; "gray"),
owner_abbreviated: colored(
if (.repository.owner.login | length) > 11 then
.repository.owner.login | .[0:10] | tostring + "…"
else
.repository.owner.login
end; "cyan"),
name_abbreviated: colored(
if (.repository.name | length) > 16 then
.repository.name | .[0:15] | tostring + "…"
else
.repository.name
end; "cyan_bold"),
type: colored(.subject.type; "white_bold"),
# Some infos have to be pulled from this URL in later steps, so no string modifications.
url: .subject.url | tostring,
unread_symbol: colored((if .unread then "\u25cf" else "\u00a0" end);"magenta"),
reason: colored(.reason; "gray"),
title: .subject.title
} | ["updated:>=\(.updated_short) repo:\(.full_name)", .iso8601, .thread_id, .thread_state, .comment_url, .timefmt, "\(.owner)/\(.name)", .type, .url, .unread_symbol, .title ] | @tsv'
} | [
.updated_short, .iso8601, .thread_id, .thread_state, .comment_url, .repo_full_name,
.unread_symbol, .timefmt, "\(.owner_abbreviated)/\(.name_abbreviated)", .type, .url,
.reason, .title
] | @tsv'
}

print_notifs() {
Expand All @@ -178,11 +214,13 @@ print_notifs() {
page_num=$((page_num + 1))
fi
new_notifs=$(
echo "$page" | while IFS=$'\t' read -r qualifier iso8601 thread_id thread_state comment_url timefmt repo type url unread_symbol title number; do
echo "$page" | while IFS=$'\t' read -r updated_short iso8601 thread_id thread_state \
comment_url repo_full_name unread_symbol timefmt repo_abbreviated type url reason \
title number; do
if grep -q "Discussion" <<<"$type"; then
# https://docs.github.com/en/search-github/searching-on-github/searching-discussions
number=$(gh api graphql --cache=100h --raw-field filter="$title in:title $qualifier" \
--raw-field query="$graphql_query_discussion" --jq '.data.search.nodes | .[].number') ||
number="#$(gh api graphql --cache=100h --raw-field filter="$title in:title updated:>=$updated_short repo:$repo_full_name" \
--raw-field query="$graphql_query_discussion" --jq '.data.search.nodes | .[].number')" ||
die "Failed GraphQL discussion query."
elif ! grep -q "^null" <<<"$url"; then
if grep -q "Commit" <<<"$type"; then
Expand All @@ -201,9 +239,10 @@ print_notifs() {
number=${url/*\//#}
fi
fi
printf "\n%s\t%s\t%s\t%s\t%s\t%s\t%s %b%s%b %s\t%s\n" \
"$iso8601" "$thread_id" "$thread_state" "$comment_url" "$timefmt" \
"$repo" "$type" "$GREEN" "$number" "$NC" "$unread_symbol" "$title"
printf "\n%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%b%s%b\t%s \t%s\n" \
"$iso8601" "$thread_id" "$thread_state" "$comment_url" "$repo_full_name" \
"$unread_symbol" "$timefmt" "$repo_abbreviated" "$type" "$GREEN" "$number" \
"$NC" "$reason" "$title"
done
) || die "Something went wrong"
all_notifs="$all_notifs$new_notifs"
Expand All @@ -220,9 +259,9 @@ print_notifs() {
if [[ -z $result && $SHLVL -gt $NESTED_START_LVL ]]; then
# TODO: exit fzf automatically if the list is empty after a reload
# it does work with '--bind "zero:become:"', but this only came with version '0.40.0'
# workaround, since fzf hides the first elements with '--with-nth 5..'
# workaround, since fzf hides the first elements with '--with-nth 6..'
# if the list is empty on a reload, the message would be hidden, so ' \b' (backspace) is added
echo -e " \b \b \b \b$FINAL_MSG"
echo -e " \b \b \b \b \b$FINAL_MSG"
else
echo "$result"
fi
Expand All @@ -242,57 +281,57 @@ highlight_output() {
}

open_in_browser() {
local comment_number date time repo type number unhashed_num
IFS=' ' read -r _ _ _ comment_number date time repo type number _ <<<"$1"
local comment_number date time repo_full_name type number unhashed_num
IFS=' ' read -r _ _ _ comment_number repo_full_name _ date time _ type number _ <<<"$1"
unhashed_num=$(tr -d "#" <<<"$number")
case "$type" in
CheckSuite)
"$python_executable" -m webbrowser "https://github.com/${repo}/actions"
"$python_executable" -m webbrowser "https://github.com/${repo_full_name}/actions"
;;
Commit)
gh browse "$number" --repo "$repo"
gh browse "$number" --repo "$repo_full_name"
;;
Discussion)
"$python_executable" -m webbrowser "https://github.com/${repo}/discussions/${number}"
"$python_executable" -m webbrowser "https://github.com/${repo_full_name}/discussions/${unhashed_num}"
;;
Issue | PullRequest)
if [[ $comment_number == "$unhashed_num" || $comment_number == null ]]; then
gh issue view "$number" --web --repo "$repo"
gh issue view "$number" --web --repo "$repo_full_name"
else
"$python_executable" -m webbrowser "https://github.com/${repo}/issues/${unhashed_num}#issuecomment-${comment_number}"
"$python_executable" -m webbrowser "https://github.com/${repo_full_name}/issues/${unhashed_num}#issuecomment-${comment_number}"
fi
;;
Pre-release | Release)
gh release view "$number" --web --repo "$repo"
gh release view "$number" --web --repo "$repo_full_name"
;;
*)
gh repo view --web "$repo"
gh repo view --web "$repo_full_name"
;;
esac
}

view_notification() {
local all_comments date time repo type number
local all_comments date time repo_full_name type number
if [ "$1" = "--all_comments" ]; then
shift
all_comments="1"
fi
IFS=' ' read -r _ _ _ _ date time repo type number _ <<<"$1"
IFS=' ' read -r _ _ _ _ repo_full_name _ date time _ type number _ <<<"$1"
printf "[%s %s - %s]\n" "$date" "$time" "$type"
case "$type" in
Commit)
gh api --header "$GH_REST_API_VERSION" --cache=24h \
--method GET "repos/$repo/commits/$number" --jq '.files[].patch' | highlight_output
--method GET "repos/$repo_full_name/commits/$number" --jq '.files[].patch' | highlight_output
;;
Issue)
# use the '--comments' flag only if 'all_comments' exists and is not null
gh issue view "$number" --repo "$repo" ${all_comments:+"--comments"}
gh issue view "$number" --repo "$repo_full_name" ${all_comments:+"--comments"}
;;
PullRequest)
gh pr view "$number" --repo "$repo" ${all_comments:+"--comments"}
gh pr view "$number" --repo "$repo_full_name" ${all_comments:+"--comments"}
;;
Pre-release | Release)
gh release view "$number" --repo "$repo"
gh release view "$number" --repo "$repo_full_name"
;;
*)
printf "Seeing the preview of a %b%s%b is not supported.\n" "$WHITE_BOLD" "$type" "$NC"
Expand Down Expand Up @@ -328,7 +367,7 @@ select_notif() {
"-+X" # reset screen clearing prevention
)

local output expected_key selected_line repo type num
local output expected_key selected_line repo_full_name type num
# make functions available in child processes
# 'SHELL="$(which bash)"' is needed to use exported functions when the default shell
# is not bash
Expand All @@ -349,8 +388,8 @@ select_notif() {
--bind "change:first" \
--bind "ctrl-a:execute-silent(mark_all_read {})+reload:print_notifs || true" \
--bind "ctrl-b:execute-silent:open_in_browser {}" \
--bind "ctrl-d:toggle-preview+change-preview:if grep -q PullRequest <<<{8}; then gh pr diff {9} --repo {7} | highlight_output; else view_notification {}; fi" \
--bind "ctrl-p:toggle-preview+change-preview:if grep -q PullRequest <<<{8}; then gh pr diff {9} --patch --repo {7} | highlight_output; else view_notification {}; fi" \
--bind "ctrl-d:toggle-preview+change-preview:if grep -q PullRequest <<<{10}; then gh pr diff {11} --repo {5} | highlight_output; else view_notification {}; fi" \
--bind "ctrl-p:toggle-preview+change-preview:if grep -q PullRequest <<<{10}; then gh pr diff {11} --patch --repo {5} | highlight_output; else view_notification {}; fi" \
--bind "ctrl-r:reload:print_notifs || true" \
--bind "ctrl-t:execute-silent(mark_individual_read {})+reload:print_notifs || true" \
--bind "enter:execute:view_notification --all_comments {} | less ${less_args[*]} >/dev/tty" \
Expand All @@ -371,15 +410,15 @@ select_notif() {
--print-query \
--prompt "GitHub Notifications > " \
--reverse \
--with-nth 5..
--with-nth 6..
)
# actions that close fzf are defined below
# 1st line ('--print-query'): the input query string
# 2nd line ('--expect'): the actual key
# 3rd line: the selected line when the user pressed the key
expected_key="$(sed '1d;3d' <<<"$output")"
selected_line="$(sed '1d;2d' <<<"$output")"
IFS=' ' read -r _ thread_id thread_state _ _ _ repo type num _ <<<"$selected_line"
IFS=' ' read -r _ thread_id thread_state _ repo_full_name _ _ _ _ type num _ <<<"$selected_line"
[[ -z $type ]] && exit 0
case "$expected_key" in
esc)
Expand All @@ -389,7 +428,7 @@ select_notif() {
;;
ctrl-x)
if grep -qE "Issue|PullRequest" <<<"$type"; then
gh issue comment "$num" --repo "$repo"
gh issue comment "$num" --repo "$repo_full_name"
else
printf "Writing comments is only supported for %bIssues%b and %bPullRequests%b.\n" "$WHITE_BOLD" "$NC" "$WHITE_BOLD" "$NC"
fi
Expand Down Expand Up @@ -506,7 +545,7 @@ gh_notify() {
else
# remove unimportant elements from the static display
# '[[:blank:]]' matches horizontal whitespace characters (spaces/ tabs)
echo "$notifs" | sed -E 's/^([^[:blank:]]+[[:blank:]]+){4}//'
echo "$notifs" | sed -E 's/^([^[:blank:]]+[[:blank:]]+){5}//'
fi
}

Expand Down
13 changes: 13 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,20 @@ gh notify [Flags]
| <kbd>ctrl</kbd><kbd>x</kbd> | write a comment with the editor and quit |
| <kbd>esc</kbd> | quit |

### Table Format

| Field | Description |
| ------------- | ----------------------------------- |
| unread symbol | indicates unread status |
| time | last time the notification was read |
| repo | related repository |
| type | notification type |
| number | associated number |
| reason | trigger reason |
| title | notification title |

---

## Customizations

### Fuzzy Finder (fzf)
Expand Down
Loading