diff --git a/cloud/issue.go b/cloud/issue.go index 56d868ef..a1849529 100644 --- a/cloud/issue.go +++ b/cloud/issue.go @@ -780,6 +780,32 @@ func (s *IssueService) GetWorklogs(ctx context.Context, issueID string, options return v, resp, err } +// GetWorklogRecord gets a worklog for an issue. +// +// https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issue-worklogs/#api-rest-api-2-issue-issueidorkey-worklog-id-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. +func (s *IssueService) GetWorklogRecord(ctx context.Context, issueID, worklogID string, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) { + apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog/%s", issueID, worklogID) + + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + for _, option := range options { + err = option(req) + if err != nil { + return nil, nil, err + } + } + + v := new(WorklogRecord) + resp, err := s.client.Do(req, v) + return v, resp, err +} + // Applies query options to http request. // This helper is meant to be used with all "QueryOptions" structs. // diff --git a/cloud/issue_test.go b/cloud/issue_test.go index a8ad6487..0c5f2acd 100644 --- a/cloud/issue_test.go +++ b/cloud/issue_test.go @@ -1583,6 +1583,114 @@ func TestIssueService_GetWorklogs(t *testing.T) { } } +func TestIssueService_GetWorklogRecord(t *testing.T) { + setup() + defer teardown() + + tt := []struct { + name string + response string + issueId string + worklogId string + uri string + worklog *WorklogRecord + err error + option *AddWorklogQueryOptions + }{ + { + name: "simple worklog", + response: `{"id": "3","self": "http://kelpie9:8081/rest/api/2/issue/10002/worklog/3","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","comment":"","started":"2016-03-16T04:22:37.356+0000","timeSpent": "1h","timeSpentSeconds": 3600,"issueId":"10002"}`, + issueId: "10002", + worklogId: "3", + uri: "/rest/api/2/issue/%s/worklog/%s", + worklog: &WorklogRecord{ + Self: "http://kelpie9:8081/rest/api/2/issue/10002/worklog/3", + Author: &User{ + Self: "http://www.example.com/jira/rest/api/2/user?username=fred", + Name: "fred", + DisplayName: "Fred F. User", + }, + UpdateAuthor: &User{ + Self: "http://www.example.com/jira/rest/api/2/user?username=fred", + Name: "fred", + DisplayName: "Fred F. User", + }, + Created: getTime(time.Date(2016, time.March, 16, 4, 22, 37, 356000000, time.UTC)), + Started: getTime(time.Date(2016, time.March, 16, 4, 22, 37, 356000000, time.UTC)), + Updated: getTime(time.Date(2016, time.March, 16, 4, 22, 37, 356000000, time.UTC)), + TimeSpent: "1h", + TimeSpentSeconds: 3600, + ID: "3", + IssueID: "10002", + }, + }, + { + name: "expanded worklog", + response: `{"id":"3","self":"http://kelpie9:8081/rest/api/2/issue/10002/worklog/3","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","comment":"","started":"2016-03-16T04:22:37.356+0000","timeSpent":"1h","timeSpentSeconds":3600,"issueId":"10002","properties":[{"key":"foo","value":{"bar":"baz"}}]}`, + issueId: "10002", + worklogId: "3", + uri: "/rest/api/2/issue/%s/worklog/%s?expand=properties", + worklog: &WorklogRecord{ + Self: "http://kelpie9:8081/rest/api/2/issue/10002/worklog/3", + Author: &User{ + Self: "http://www.example.com/jira/rest/api/2/user?username=fred", + Name: "fred", + DisplayName: "Fred F. User", + }, + UpdateAuthor: &User{ + Self: "http://www.example.com/jira/rest/api/2/user?username=fred", + Name: "fred", + DisplayName: "Fred F. User", + }, + Created: getTime(time.Date(2016, time.March, 16, 4, 22, 37, 356000000, time.UTC)), + Started: getTime(time.Date(2016, time.March, 16, 4, 22, 37, 356000000, time.UTC)), + Updated: getTime(time.Date(2016, time.March, 16, 4, 22, 37, 356000000, time.UTC)), + TimeSpent: "1h", + TimeSpentSeconds: 3600, + ID: "3", + IssueID: "10002", + Properties: []EntityProperty{ + { + Key: "foo", + Value: map[string]interface{}{ + "bar": "baz", + }, + }, + }, + }, + option: &AddWorklogQueryOptions{Expand: "properties"}, + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + uri := fmt.Sprintf(tc.uri, tc.issueId, tc.worklogId) + testMux.HandleFunc(uri, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + testRequestURL(t, r, uri) + _, _ = fmt.Fprint(w, tc.response) + }) + + var record *WorklogRecord + var err error + + if tc.option != nil { + record, _, err = testClient.Issue.GetWorklogRecord(context.Background(), tc.issueId, tc.worklogId, WithQueryOptions(tc.option)) + } else { + record, _, err = testClient.Issue.GetWorklogRecord(context.Background(), tc.issueId, tc.worklogId) + } + + if err != nil && !cmp.Equal(err, tc.err) { + t.Errorf("unexpected error: %v", err) + } + + if !cmp.Equal(record, tc.worklog) { + t.Errorf("unexpected worklog structure: %s", cmp.Diff(record, tc.worklog)) + } + }) + } +} + func TestIssueService_GetWatchers(t *testing.T) { setup() defer teardown() diff --git a/onpremise/issue.go b/onpremise/issue.go index d1bca7aa..7ed35aaa 100644 --- a/onpremise/issue.go +++ b/onpremise/issue.go @@ -752,6 +752,32 @@ func (s *IssueService) DeleteLink(ctx context.Context, linkID string) (*Response return resp, nil } +// GetWorklogRecord gets a worklog for an issue. +// +// https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issue-worklogs/#api-rest-api-2-issue-issueidorkey-worklog-id-get +// +// TODO Double check this method if this works as expected, is using the latest API and the response is complete +// This double check effort is done for v2 - Remove this two lines if this is completed. +func (s *IssueService) GetWorklogRecord(ctx context.Context, issueID, worklogID string, options ...func(*http.Request) error) (*WorklogRecord, *Response, error) { + apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/worklog/%s", issueID, worklogID) + + req, err := s.client.NewRequest(ctx, http.MethodGet, apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + for _, option := range options { + err = option(req) + if err != nil { + return nil, nil, err + } + } + + v := new(WorklogRecord) + resp, err := s.client.Do(req, v) + return v, resp, err +} + // GetWorklogs gets all the worklogs for an issue. // This method is especially important if you need to read all the worklogs, not just the first page. // diff --git a/onpremise/issue_test.go b/onpremise/issue_test.go index 36b6aa31..7b9c448b 100644 --- a/onpremise/issue_test.go +++ b/onpremise/issue_test.go @@ -1583,6 +1583,114 @@ func TestIssueService_GetWorklogs(t *testing.T) { } } +func TestIssueService_GetWorklogRecord(t *testing.T) { + setup() + defer teardown() + + tt := []struct { + name string + response string + issueId string + worklogId string + uri string + worklog *WorklogRecord + err error + option *AddWorklogQueryOptions + }{ + { + name: "simple worklog", + response: `{"id": "3","self": "http://kelpie9:8081/rest/api/2/issue/10002/worklog/3","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","comment":"","started":"2016-03-16T04:22:37.356+0000","timeSpent": "1h","timeSpentSeconds": 3600,"issueId":"10002"}`, + issueId: "10002", + worklogId: "3", + uri: "/rest/api/2/issue/%s/worklog/%s", + worklog: &WorklogRecord{ + Self: "http://kelpie9:8081/rest/api/2/issue/10002/worklog/3", + Author: &User{ + Self: "http://www.example.com/jira/rest/api/2/user?username=fred", + Name: "fred", + DisplayName: "Fred F. User", + }, + UpdateAuthor: &User{ + Self: "http://www.example.com/jira/rest/api/2/user?username=fred", + Name: "fred", + DisplayName: "Fred F. User", + }, + Created: getTime(time.Date(2016, time.March, 16, 4, 22, 37, 356000000, time.UTC)), + Started: getTime(time.Date(2016, time.March, 16, 4, 22, 37, 356000000, time.UTC)), + Updated: getTime(time.Date(2016, time.March, 16, 4, 22, 37, 356000000, time.UTC)), + TimeSpent: "1h", + TimeSpentSeconds: 3600, + ID: "3", + IssueID: "10002", + }, + }, + { + name: "expanded worklog", + response: `{"id":"3","self":"http://kelpie9:8081/rest/api/2/issue/10002/worklog/3","author":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"updateAuthor":{"self":"http://www.example.com/jira/rest/api/2/user?username=fred","name":"fred","displayName":"Fred F. User","active":false},"created":"2016-03-16T04:22:37.356+0000","updated":"2016-03-16T04:22:37.356+0000","comment":"","started":"2016-03-16T04:22:37.356+0000","timeSpent":"1h","timeSpentSeconds":3600,"issueId":"10002","properties":[{"key":"foo","value":{"bar":"baz"}}]}`, + issueId: "10002", + worklogId: "3", + uri: "/rest/api/2/issue/%s/worklog/%s?expand=properties", + worklog: &WorklogRecord{ + Self: "http://kelpie9:8081/rest/api/2/issue/10002/worklog/3", + Author: &User{ + Self: "http://www.example.com/jira/rest/api/2/user?username=fred", + Name: "fred", + DisplayName: "Fred F. User", + }, + UpdateAuthor: &User{ + Self: "http://www.example.com/jira/rest/api/2/user?username=fred", + Name: "fred", + DisplayName: "Fred F. User", + }, + Created: getTime(time.Date(2016, time.March, 16, 4, 22, 37, 356000000, time.UTC)), + Started: getTime(time.Date(2016, time.March, 16, 4, 22, 37, 356000000, time.UTC)), + Updated: getTime(time.Date(2016, time.March, 16, 4, 22, 37, 356000000, time.UTC)), + TimeSpent: "1h", + TimeSpentSeconds: 3600, + ID: "3", + IssueID: "10002", + Properties: []EntityProperty{ + { + Key: "foo", + Value: map[string]interface{}{ + "bar": "baz", + }, + }, + }, + }, + option: &AddWorklogQueryOptions{Expand: "properties"}, + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + uri := fmt.Sprintf(tc.uri, tc.issueId, tc.worklogId) + testMux.HandleFunc(uri, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + testRequestURL(t, r, uri) + _, _ = fmt.Fprint(w, tc.response) + }) + + var record *WorklogRecord + var err error + + if tc.option != nil { + record, _, err = testClient.Issue.GetWorklogRecord(context.Background(), tc.issueId, tc.worklogId, WithQueryOptions(tc.option)) + } else { + record, _, err = testClient.Issue.GetWorklogRecord(context.Background(), tc.issueId, tc.worklogId) + } + + if err != nil && !cmp.Equal(err, tc.err) { + t.Errorf("unexpected error: %v", err) + } + + if !cmp.Equal(record, tc.worklog) { + t.Errorf("unexpected worklog structure: %s", cmp.Diff(record, tc.worklog)) + } + }) + } +} + func TestIssueService_GetWatchers(t *testing.T) { setup() defer teardown()