From 86fa9a00e199e875c97b048db76c772bd5b16781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaros=C5=82aw=20Zywert?= Date: Mon, 20 Jan 2025 12:42:48 +0100 Subject: [PATCH 1/3] not so trivial --- README.md | 2 ++ gojira/config.go | 2 +- gojira/tempo.go | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a566012..ae129be 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,8 @@ Just remember to urldecode it. Save it and you should ready to go! ## [Changelog](./CHANGELOG.md) ## Todo list +- [ ] Color legend for calendar +- [ ] Fetch holidays for other years than current - [ ] Refactor ShowError focus return - [ ] Open issue in modal if it's the only result? - [ ] Prompt `are you sure want to exit` on escape key diff --git a/gojira/config.go b/gojira/config.go index 4ab97f7..1676d3e 100644 --- a/gojira/config.go +++ b/gojira/config.go @@ -27,7 +27,7 @@ func PrepareConfig() { JiraLogin: GetEnv("GOJIRA_JIRA_LOGIN"), JiraToken: GetEnv("GOJIRA_JIRA_TOKEN"), JiraAccountId: GetEnv("GOJIRA_JIRA_ACCOUNT_ID"), - TempoUrl: "https://api.tempo.io/core/3", + TempoUrl: "https://api.tempo.io/4", TempoToken: GetEnv("GOJIRA_TEMPO_TOKEN"), UpdateExistingWorklog: true, } diff --git a/gojira/tempo.go b/gojira/tempo.go index 5924db1..76d1fe9 100644 --- a/gojira/tempo.go +++ b/gojira/tempo.go @@ -36,7 +36,7 @@ type WorklogUpdateRequest struct { func (tc *TempoClient) GetWorklogs(fromDate, toDate time.Time) (WorklogsResponse, error) { // tempo is required only because of fetching worklogs by date range - requestUrl := fmt.Sprintf("%s/worklogs/user/%s?from=%s&to=%s&limit=1000", + requestUrl := fmt.Sprintf("%s/worklogs/account/%s?from=%s&to=%s&limit=1000", tc.Url, tc.JiraAccountId, fromDate.Format(dateLayout), toDate.Format(dateLayout)) headers := map[string]string{ "Authorization": fmt.Sprintf("Bearer %s", tc.Token), From 2d45eae202ac691f9a70b9d13e22f25603da6164 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaros=C5=82aw=20Zywert?= Date: Mon, 20 Jan 2025 22:16:26 +0100 Subject: [PATCH 2/3] Change Tempo API version from 3 to 4 --- gojira/cli.go | 7 ++++--- gojira/jira.go | 38 +++++++++++++++++++++++++++----------- gojira/tempo.go | 7 ++++--- gojira/worklog.go | 24 ++++++++++++------------ 4 files changed, 47 insertions(+), 29 deletions(-) diff --git a/gojira/cli.go b/gojira/cli.go index 7b1176d..aa3f383 100644 --- a/gojira/cli.go +++ b/gojira/cli.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "os" + "strconv" "sync" "time" @@ -81,7 +82,7 @@ func NewWorklogIssues() error { for i := range app.workLogs.logs { waitGroup.Add(1) go func(workLog *Worklog) { - issue, err := NewJiraClient().GetIssue(workLog.Issue.Key) + issue, err := NewJiraClient().GetIssue(strconv.Itoa(workLog.Issue.Id)) if err != nil { errCh <- err // Send the error to the channel. return @@ -278,7 +279,7 @@ func (issue Issue) LogWork(logTime *time.Time, timeSpent string) error { } if Config.UpdateExistingWorklog { for index, workLog := range todayWorklog { - if workLog.Issue.Key == issue.Key { + if strconv.Itoa(workLog.Issue.Id) == issue.Id { timeSpentSum := FormatTimeSpent(TimeSpentToSeconds(timeSpent) + workLog.TimeSpentSeconds) err := todayWorklog[index].Update(timeSpentSum) if err != nil { @@ -288,7 +289,7 @@ func (issue Issue) LogWork(logTime *time.Time, timeSpent string) error { } } } - worklog, err := NewWorklog(issue.Key, logTime, timeSpent) + worklog, err := NewWorklog(issue.GetIdAsInt(), logTime, timeSpent) if err != nil { return err } diff --git a/gojira/jira.go b/gojira/jira.go index 6b4d918..f641cc1 100644 --- a/gojira/jira.go +++ b/gojira/jira.go @@ -5,6 +5,7 @@ import ( "encoding/base64" "encoding/json" "fmt" + "strconv" "strings" "time" ) @@ -57,6 +58,7 @@ type JQLResponse struct { type Issue struct { Key string `json:"key"` + Id string `json:"id"` Fields struct { Summary string `json:"summary"` Status struct { @@ -65,6 +67,14 @@ type Issue struct { } `json:"fields"` } +func (issue Issue) GetIdAsInt() int { + value, err := strconv.ParseInt(issue.Id, 10, 64) + if err != nil { + return 0 + } + return int(value) +} + type WorklogResponse struct { Self string `json:"self"` Author struct { @@ -111,13 +121,19 @@ 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, ",")) +func (jc *JiraClient) GetIssuesByKeys(issueKeys []int) (JQLResponse, error) { + // Convert []int to []string + issueKeysStr := make([]string, len(issueKeys)) + for i, key := range issueKeys { + issueKeysStr[i] = fmt.Sprintf("%d", key) + } + issueKeysJQL := fmt.Sprintf("key in (%s) ORDER BY updated DESC, created DESC", strings.Join(issueKeysStr, ",")) 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) + // issueKey could be JIRA-123 (key) or just 234235 (id) + requestUrl := fmt.Sprintf("%s/rest/api/2/issue/%s?fields=summary,status,id", Config.JiraUrl, issueKey) response, err := SendHttpRequest("GET", requestUrl, nil, jc.getHttpHeaders(), 200) if err != nil { return Issue{}, err @@ -130,7 +146,7 @@ func (jc *JiraClient) GetIssue(issueKey string) (Issue, error) { return jiraIssue, nil } -func (jc *JiraClient) CreateWorklog(issueKey string, logTime *time.Time, timeSpent string) (WorklogResponse, error) { +func (jc *JiraClient) CreateWorklog(issueId int, logTime *time.Time, timeSpent string) (WorklogResponse, error) { payload := map[string]string{ "timeSpent": FormatTimeSpent(TimeSpentToSeconds(timeSpent)), "adjustEstimate": "leave", @@ -138,7 +154,7 @@ func (jc *JiraClient) CreateWorklog(issueKey string, logTime *time.Time, timeSpe } payloadJson, _ := json.Marshal(payload) requestBody := bytes.NewBuffer(payloadJson) - requestUrl := fmt.Sprintf("%s/rest/api/2/issue/%s/worklog?notifyUsers=false", Config.JiraUrl, issueKey) + requestUrl := fmt.Sprintf("%s/rest/api/2/issue/%d/worklog?notifyUsers=false", Config.JiraUrl, issueId) response, err := SendHttpRequest("POST", requestUrl, requestBody, jc.getHttpHeaders(), 201) if err != nil { return WorklogResponse{}, err @@ -152,21 +168,21 @@ func (jc *JiraClient) CreateWorklog(issueKey string, logTime *time.Time, timeSpe return workLogRequest, nil } -func (jc *JiraClient) UpdateWorklog(issueKey string, jiraWorklogId int, timeSpentInSeconds int) error { +func (jc *JiraClient) UpdateWorklog(issueId int, jiraWorklogId int, timeSpentInSeconds int) error { payload := JiraWorklogUpdate{ TimeSpentSeconds: timeSpentInSeconds, } payloadJson, _ := json.Marshal(payload) requestBody := bytes.NewBuffer(payloadJson) - requestUrl := fmt.Sprintf("%s/rest/api/2/issue/%s/worklog/%d?notifyUsers=false", - Config.JiraUrl, issueKey, jiraWorklogId) + requestUrl := fmt.Sprintf("%s/rest/api/2/issue/%d/worklog/%d?notifyUsers=false", + Config.JiraUrl, issueId, jiraWorklogId) _, err := SendHttpRequest("PUT", requestUrl, requestBody, jc.getHttpHeaders(), 200) return err } -func (jc *JiraClient) DeleteWorklog(issueKey string, jiraWorklogId int) error { - requestUrl := fmt.Sprintf("%s/rest/api/2/issue/%s/worklog/%d?notifyUsers=false", - Config.JiraUrl, issueKey, jiraWorklogId) +func (jc *JiraClient) DeleteWorklog(issueId int, jiraWorklogId int) error { + requestUrl := fmt.Sprintf("%s/rest/api/2/issue/%d/worklog/%d?notifyUsers=false", + Config.JiraUrl, issueId, jiraWorklogId) _, err := SendHttpRequest("DELETE", requestUrl, nil, jc.getHttpHeaders(), 204) return err } diff --git a/gojira/tempo.go b/gojira/tempo.go index 76d1fe9..bbd8e4e 100644 --- a/gojira/tempo.go +++ b/gojira/tempo.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "fmt" + "strconv" "time" ) @@ -26,7 +27,7 @@ type WorklogsResponse struct { } type WorklogUpdateRequest struct { - IssueKey string `json:"issueKey"` + Id string `json:"id"` StartDate string `json:"startDate"` StartTime string `json:"startTime"` Description string `json:"description"` @@ -36,7 +37,7 @@ type WorklogUpdateRequest struct { func (tc *TempoClient) GetWorklogs(fromDate, toDate time.Time) (WorklogsResponse, error) { // tempo is required only because of fetching worklogs by date range - requestUrl := fmt.Sprintf("%s/worklogs/account/%s?from=%s&to=%s&limit=1000", + requestUrl := fmt.Sprintf("%s/worklogs/user/%s?from=%s&to=%s&limit=1000", tc.Url, tc.JiraAccountId, fromDate.Format(dateLayout), toDate.Format(dateLayout)) headers := map[string]string{ "Authorization": fmt.Sprintf("Bearer %s", tc.Token), @@ -58,7 +59,7 @@ func (tc *TempoClient) UpdateWorklog(worklog *Worklog, timeSpent string) error { timeSpentInSeconds := TimeSpentToSeconds(timeSpent) payload := WorklogUpdateRequest{ - IssueKey: worklog.Issue.Key, + Id: strconv.Itoa(worklog.Issue.Id), StartDate: worklog.StartDate, StartTime: worklog.StartTime, Description: worklog.Description, diff --git a/gojira/worklog.go b/gojira/worklog.go index 48837d4..98e2418 100644 --- a/gojira/worklog.go +++ b/gojira/worklog.go @@ -9,8 +9,8 @@ import ( "time" ) -func NewWorklog(issueKey string, logTime *time.Time, timeSpent string) (Worklog, error) { - workLogResponse, err := NewJiraClient().CreateWorklog(issueKey, logTime, timeSpent) +func NewWorklog(issueId int, logTime *time.Time, timeSpent string) (Worklog, error) { + workLogResponse, err := NewJiraClient().CreateWorklog(issueId, logTime, timeSpent) if err != nil { return Worklog{}, err } @@ -26,8 +26,8 @@ func NewWorklog(issueKey string, logTime *time.Time, timeSpent string) (Worklog, StartTime: logTime.Format("15:04:05"), TimeSpentSeconds: workLogResponse.Timespentseconds, Issue: struct { - Key string `json:"key"` - }{Key: issueKey}, + Id int `json:"id"` + }{Id: issueId}, } return workLog, nil } @@ -36,7 +36,7 @@ type Worklog struct { TempoWorklogid int `json:"tempoWorklogId"` JiraWorklogID int `json:"jiraWorklogId"` Issue struct { - Key string `json:"key"` + Id int `json:"id"` } `json:"issue"` TimeSpentSeconds int `json:"timeSpentSeconds"` StartDate string `json:"startDate"` @@ -85,7 +85,7 @@ func (wl *Worklogs) LogsOnDate(date *time.Time) ([]*Worklog, error) { func findWorklogByIssueKey(worklogs []*Worklog, issueKey string) *Worklog { for _, log := range worklogs { - if log.Issue.Key == issueKey { + if strconv.Itoa(log.Issue.Id) == issueKey { return log } } @@ -94,14 +94,14 @@ func findWorklogByIssueKey(worklogs []*Worklog, issueKey string) *Worklog { func GetIssuesWithWorklogs(worklogs []*Worklog) ([]Issue, error) { var err error - var worklogIssuesKeys []string + var worklogIssueIds []int for _, worklog := range worklogs { - worklogIssuesKeys = append(worklogIssuesKeys, worklog.Issue.Key) + worklogIssueIds = append(worklogIssueIds, worklog.Issue.Id) } - if len(worklogIssuesKeys) == 0 { + if len(worklogIssueIds) == 0 { return []Issue{}, err } - todaysIssues, err := NewJiraClient().GetIssuesByKeys(worklogIssuesKeys) + todaysIssues, err := NewJiraClient().GetIssuesByKeys(worklogIssueIds) if err != nil { return []Issue{}, err } @@ -187,7 +187,7 @@ func (wl *Worklog) Update(timeSpent string) error { err = NewTempoClient().UpdateWorklog(wl, timeSpent) } else { // make update request to jira if tempoWorklogId is not set - err = NewJiraClient().UpdateWorklog(wl.Issue.Key, wl.JiraWorklogID, timeSpentInSeconds) + err = NewJiraClient().UpdateWorklog(wl.Issue.Id, wl.JiraWorklogID, timeSpentInSeconds) } if err != nil { return err @@ -203,7 +203,7 @@ func (wl *Worklogs) Delete(w *Worklog) error { if w.TempoWorklogid != 0 { err = NewTempoClient().DeleteWorklog(w.TempoWorklogid) } else { - err = NewJiraClient().DeleteWorklog(w.Issue.Key, w.JiraWorklogID) + err = NewJiraClient().DeleteWorklog(w.Issue.Id, w.JiraWorklogID) } if err != nil { logrus.Debug(w) From cb647867bdd772b9c462366114eb3a314e13996e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaros=C5=82aw=20Zywert?= Date: Mon, 20 Jan 2025 22:18:18 +0100 Subject: [PATCH 3/3] CHANGELOG --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd11839..77eecfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +## [0.11.0] - 2025-01-20 +### Changed +- Tempo API version from 3 to 4 due to incoming shutdown of the old version + ## [0.10.2] - 2025-01-08 ### Fixed - hardcoded `2024` while fetching holidays for given year @@ -129,7 +133,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.10.2...master +[Unreleased]: https://github.com/jzyinq/gojira/compare/0.11.0...master +[0.11.0]: https://github.com/jzyinq/gojira/compare/0.10.2...0.11.0 [0.10.2]: https://github.com/jzyinq/gojira/compare/0.10.1...0.10.2 [0.10.1]: https://github.com/jzyinq/gojira/compare/0.10.0...0.10.1 [0.10.0]: https://github.com/jzyinq/gojira/compare/0.9.0...0.10.0