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

replace promptui with huh + improve gojira issues #9

Merged
merged 19 commits into from
Mar 30, 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
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

## [0.6.0] - 2024-03-30
### Changed
- Replace [manifoldco/promptui](https://github.com/charmbracelet/huh) with [charmbracelet/huh](https://github.com/charmbracelet/huh) due to lack of maintainer
- code cleanup pass, simplified structs, improved messaging, aligned variable naming
- Include already logged issues in recent issues list - allow editing existing time
- Add issues with worklogs for current day while launching `gojira issues`

## [0.5.4] - 2024-03-29
### Fixed
- MonthRange function returned first day of next month which causes invalid summaries
Expand Down Expand Up @@ -89,7 +96,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
### Added
- Initial release of gojira

[Unreleased]: https://github.com/jzyinq/gojira/compare/0.5.4...master
[Unreleased]: https://github.com/jzyinq/gojira/compare/0.6.0...master
[0.6.0]: https://github.com/jzyinq/gojira/compare/0.5.4...0.6.0
[0.5.4]: https://github.com/jzyinq/gojira/compare/0.5.3...0.5.4
[0.5.3]: https://github.com/jzyinq/gojira/compare/0.5.2...0.5.3
[0.5.2]: https://github.com/jzyinq/gojira/compare/0.5.1...0.5.2
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# gojira

Small cli helper for adding/updating work logs in Jira / Tempo.
Based on [urfave/cli](https://github.com/urfave/cli/), [manifoldco/promptui](https://github.com/manifoldco/promptui)
Based on [urfave/cli](https://github.com/urfave/cli/), [charmbracelet/huh](https://github.com/charmbracelet/huh)
and [rivo/tview](https://github.com/rivo/tview)

## Features
Expand Down Expand Up @@ -70,6 +70,7 @@ Just remember to urldecode it. Save it and you should ready to go!

## Todo list

- [ ] delete worklog through simple cli version for today
- [ ] ticket status change prompt after logging time
- [ ] tests
- [ ] unify workLogs and worklogsIssues structs - use one for both
Expand All @@ -79,7 +80,9 @@ Just remember to urldecode it. Save it and you should ready to go!
- `gojira log -i TICKET -t 1h30m`
- `gojira` -> `gojira recent`
- `gojira` -> `gojira --help`
- [ ] trigger ui updates after worklog change more efficiently
- [ ] trigger ui updates after worklog change more efficiently
- [x] cli version does not update worklogs if they exist already
- [x] fetch worklogs from current day and propose them for selection
- [x] Add worklogs from ui
- [x] gojira worklog delete option
- [x] recent jira task list for easy time logging
Expand Down
22 changes: 15 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,38 @@ module gojira
go 1.22

require (
github.com/gdamore/tcell v1.4.0
github.com/charmbracelet/huh v0.3.0
github.com/charmbracelet/huh/spinner v0.0.0-20240328185852-590ecabc34b9
github.com/gdamore/tcell/v2 v2.7.4
github.com/manifoldco/promptui v0.9.0
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
github.com/rivo/tview v0.0.0-20240307173318-e804876934a1
github.com/sirupsen/logrus v1.9.3
github.com/urfave/cli/v2 v2.27.1
)

require (
github.com/chzyer/readline v1.5.1 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/catppuccin/go v0.2.0 // indirect
github.com/charmbracelet/bubbles v0.18.0 // indirect
github.com/charmbracelet/bubbletea v0.25.0 // indirect
github.com/charmbracelet/lipgloss v0.10.0 // indirect
github.com/containerd/console v1.0.4 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/gdamore/encoding v1.0.1 // indirect
github.com/juju/ansiterm v1.0.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/term v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/yaml.v2 v2.2.8 // indirect
)
133 changes: 39 additions & 94 deletions go.sum

Large diffs are not rendered by default.

145 changes: 91 additions & 54 deletions gojira/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package gojira

import (
"fmt"
"github.com/manifoldco/promptui"
"github.com/charmbracelet/huh/spinner"
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
"log"
Expand Down Expand Up @@ -45,7 +45,7 @@ func NewWorklogIssues() error {
}
if app.workLogsIssues.startDate != startDate || app.workLogsIssues.endDate != endDate {
app.ui.loaderView.Show("Fetching worklogs...")
app.workLogs, err = GetWorklogs()
app.workLogs, err = GetWorklogs(MonthRange(app.time))
app.ui.loaderView.Hide()
if err != nil {
return err
Expand Down Expand Up @@ -85,24 +85,69 @@ func NewWorklogIssues() error {

var IssuesCommand = &cli.Command{
Name: "issues",
Usage: "Show currently assigned issues",
Usage: "Show recent issues",
Action: func(context *cli.Context) error {
lastTickets, err := NewJiraClient().GetLatestIssues()
if err != nil {
return err
}
issue, err := PromptForIssueSelection(lastTickets.Issues)
var recentIssues []Issue
var err error
_ = spinner.New().Title("Fetching issues...").Action(func() {
var funcErr error
var issuesWithWorkLogs []Issue
var lastIssues []Issue
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
app.workLogs, err = GetWorklogs(DayRange(app.time))
if err != nil {
err = funcErr
return
}
issuesWithWorkLogs, funcErr = GetIssuesWithWorklogs(app.workLogs.logs)
if funcErr != nil {
err = funcErr
return
}
}()
go func() {
defer wg.Done()
lastTickets, funcErr := NewJiraClient().GetLatestIssues()
lastIssues = lastTickets.Issues
logrus.Infof("Last tickets: %v", lastIssues)
if funcErr != nil {
err = funcErr
return
}
}()
wg.Wait()
combinedIssues := append(issuesWithWorkLogs, lastIssues...)
uniqueIssueKeys := map[string]bool{}
for _, issue := range combinedIssues {
if _, value := uniqueIssueKeys[issue.Key]; !value {
uniqueIssueKeys[issue.Key] = true
recentIssues = append(recentIssues, issue)
}
}
}).Run()
if err != nil {
return err
}
timeSpent, err := PromptForTimeSpent("Add work log")
issue, timeSpent, err := IssueWorklogForm(recentIssues)
if err != nil {
return err
}
err = issue.LogWork(app.time, timeSpent)
err = spinner.New().Title("Logging work...").Action(func() {
worklog := findWorklogByIssueKey(app.workLogs.logs, issue.Key)
if worklog != nil {
err = worklog.Update(timeSpent)
return
}
err = issue.LogWork(app.time, timeSpent)
}).Run()
if err != nil {
return err
}
fmt.Printf("Successfully logged %s to ticket %s\n", timeSpent, issue.Key)
fmt.Printf("Time logged for today: %s\n", FormatTimeSpent(CalculateTimeSpent(app.workLogs.logs)))
return nil
},
}
Expand All @@ -127,19 +172,19 @@ var LogWorkCommand = &cli.Command{
if err != nil {
return err
}
fmt.Printf("%s %s\n", issue.Key, issue.Fields.Summary)
fmt.Printf("Status: %s\n", issue.Fields.Status.Name)
if timeSpent == "" {
timeSpent, err = PromptForTimeSpent("Add work log")
timeSpent, err = InputTimeSpentForm(issue, "")
if err != nil {
return err
}
}

err = issue.LogWork(app.time, timeSpent)
err = spinner.New().Title("Logging work...").Action(func() {
err = issue.LogWork(app.time, timeSpent)
}).Run()
if err != nil {
return err
}
fmt.Printf("Successfully logged %s to ticket %s ", timeSpent, issue.Key)
return nil
},
}
Expand All @@ -153,12 +198,7 @@ var DefaultAction = func(c *cli.Context) error {
if ticketFromBranch != "" {
c.App.Metadata["JiraIssue"] = ticketFromBranch
fmt.Printf("Detected possible ticket in git branch name - %s\n", ticketFromBranch)
prompt := promptui.Select{
Label: "Select Action",
Items: []string{"Log Work", "View Issue"},
}
_, action, err := prompt.Run()

action, err := SelectActionForm([]string{"Log Work", "View Issue"})
if err != nil {
fmt.Printf("Prompt failed %v\n", err)
return nil
Expand All @@ -182,16 +222,17 @@ var GitOrIssueListAction = func(c *cli.Context) error {
if err != nil {
return err
}
fmt.Printf("Status: %s\nSummary: %s\n", issue.Fields.Status.Name, issue.Fields.Summary)
// log time or view issue
timeSpent, err := PromptForTimeSpent("Add work log")
timeSpent, err := InputTimeSpentForm(issue, "")
if err != nil {
return nil
}
err = issue.LogWork(app.time, timeSpent)
err = spinner.New().Title("Logging work...").Action(func() {
err = issue.LogWork(app.time, timeSpent)
}).Run()
if err != nil {
return err
}
fmt.Printf("Successfully logged %s to ticket %s ", timeSpent, issue.Key)
return nil
}

Expand All @@ -210,31 +251,6 @@ var ViewIssueInBrowserAction = func(c *cli.Context) error {
return nil
}

var ConfigCommand = &cli.Command{
Name: "config",
Usage: "configuration help",
Action: func(context *cli.Context) error {
//nolint:lll
fmt.Print(`gojira needs a couple of env variables right now that you have to configure:
#1 Export below values in your .bashrc / .zshrc / .profile file:

export GOJIRA_JIRA_INSTANCE_URL="https://<INSTANCE>.atlassian.net"
export GOJIRA_JIRA_LOGIN="your@email.com"
export GOJIRA_JIRA_TOKEN= generate it at https://id.atlassian.com/manage-profile/security/api-tokens
export GOJIRA_TEMPO_TOKEN= generate it at https://<INSTANCE>.atlassian.net/plugins/servlet/ac/io.tempo.jira/tempo-app#!/configuration/api-integration

#2 Now we need to fetch one last env variable using previously saved values:
export GOJIRA_JIRA_ACCOUNT_ID= fetch it using this curl:
curl --request GET \
--url "$GOJIRA_JIRA_INSTANCE_URL/rest/api/3/user/bulk/migration?username=$GOJIRA_JIRA_LOGIN" \
--header "Authorization: Basic $(echo -n $GOJIRA_JIRA_LOGIN:$GOJIRA_JIRA_TOKEN | base64)"

Save it and you should ready to go!
`)
return nil
},
}

func (issue Issue) LogWork(logTime *time.Time, timeSpent string) error {
logrus.Infof("Logging %s of time to ticket %s at %s", timeSpent, issue.Key, logTime)
todayWorklog, err := app.workLogs.LogsOnDate(logTime)
Expand All @@ -244,15 +260,11 @@ func (issue Issue) LogWork(logTime *time.Time, timeSpent string) error {
if Config.UpdateExistingWorklog {
for index, workLog := range todayWorklog {
if workLog.Issue.Key == issue.Key {
//fmt.Println("Updating existing worklog...")
timeSpentSum := FormatTimeSpent(TimeSpentToSeconds(timeSpent) + workLog.TimeSpentSeconds)
err := todayWorklog[index].Update(timeSpentSum)
if err != nil {
return err
}
// FIXME - this should be only in cli mode
//fmt.Printf("Successfully logged %s of time to ticket %s\n", timeSpent, workLog.Issue.Key)
//fmt.Printf("Currently logged time: %s\n", FormatTimeSpent(CalculateTimeSpent(todayWorklog)))
return nil
}
}
Expand All @@ -266,3 +278,28 @@ func (issue Issue) LogWork(logTime *time.Time, timeSpent string) error {
app.workLogsIssues.issues = append(app.workLogsIssues.issues, WorklogIssue{Issue: issue, Worklog: &worklog})
return nil
}

var ConfigCommand = &cli.Command{
Name: "config",
Usage: "configuration help",
Action: func(context *cli.Context) error {
//nolint:lll
fmt.Print(`gojira needs a couple of env variables right now that you have to configure:
#1 Export below values in your .bashrc / .zshrc / .profile file:

export GOJIRA_JIRA_INSTANCE_URL="https://<INSTANCE>.atlassian.net"
export GOJIRA_JIRA_LOGIN="your@email.com"
export GOJIRA_JIRA_TOKEN= generate it at https://id.atlassian.com/manage-profile/security/api-tokens
export GOJIRA_TEMPO_TOKEN= generate it at https://<INSTANCE>.atlassian.net/plugins/servlet/ac/io.tempo.jira/tempo-app#!/configuration/api-integration

#2 Now we need to fetch one last env variable using previously saved values:
export GOJIRA_JIRA_ACCOUNT_ID= fetch it using this curl:
curl --request GET \
--url "$GOJIRA_JIRA_INSTANCE_URL/rest/api/3/user/bulk/migration?username=$GOJIRA_JIRA_LOGIN" \
--header "Authorization: Basic $(echo -n $GOJIRA_JIRA_LOGIN:$GOJIRA_JIRA_TOKEN | base64)"

Save it and you should ready to go!
`)
return nil
},
}
1 change: 1 addition & 0 deletions gojira/gojira.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ func Run() {
}
err = app.cli.Run(os.Args)
if err != nil {
logrus.Error(err)
log.Fatal(err)
}
}
Expand Down
16 changes: 13 additions & 3 deletions gojira/jira.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"strings"
"time"
)

Expand Down Expand Up @@ -79,11 +80,11 @@ type JiraWorklogUpdate struct {
TimeSpentSeconds int `json:"timeSpentSeconds"`
}

func (jc *JiraClient) GetLatestIssues() (JQLResponse, error) {
func (jc *JiraClient) GetIssuesByJQL(jql string, maxResults int) (JQLResponse, error) {
payload := &JQLSearch{
Expand: []string{"names"},
Jql: "assignee in (currentUser()) ORDER BY updated DESC, created DESC",
MaxResults: 10,
Jql: jql,
MaxResults: maxResults,
FieldsByKeys: false,
Fields: []string{"summary", "status"},
StartAt: 0,
Expand All @@ -106,6 +107,15 @@ func (jc *JiraClient) GetLatestIssues() (JQLResponse, error) {
return jqlResponse, nil
}

func (jc *JiraClient) GetLatestIssues() (JQLResponse, error) {
return jc.GetIssuesByJQL("assignee in (currentUser()) ORDER BY updated DESC, created DESC", 10)
}

func (jc *JiraClient) GetIssuesByKeys(issueKeys []string) (JQLResponse, error) {
issueKeysJQL := fmt.Sprintf("key in (%s) ORDER BY updated DESC, created DESC", strings.Join(issueKeys, ","))
return jc.GetIssuesByJQL(issueKeysJQL, len(issueKeys))
}

func (jc *JiraClient) GetIssue(issueKey string) (Issue, error) {
requestUrl := fmt.Sprintf("%s/rest/api/2/issue/%s?fields=summary,status", Config.JiraUrl, issueKey)
response, err := SendHttpRequest("GET", requestUrl, nil, jc.getHttpHeaders(), 200)
Expand Down
Loading
Loading