From 41ea3595ba7cf6785a27a78756b3bebc066cbd95 Mon Sep 17 00:00:00 2001 From: Yannick Briffa Date: Tue, 23 Apr 2024 17:41:42 +0200 Subject: [PATCH] implements board.GetIssuesForBacklog --- cloud/board.go | 37 ++++++++ cloud/board_test.go | 28 ++++++ onpremise/board.go | 37 ++++++++ onpremise/board_test.go | 28 ++++++ testing/mock-data/backlog_in_board.json | 115 ++++++++++++++++++++++++ 5 files changed, 245 insertions(+) create mode 100644 testing/mock-data/backlog_in_board.json diff --git a/cloud/board.go b/cloud/board.go index 1505831d..51a5cfac 100644 --- a/cloud/board.go +++ b/cloud/board.go @@ -139,6 +139,19 @@ type BoardConfigurationColumnStatus struct { Self string `json:"self"` } +// GetBoardIssuesOptions specifies the optional parameters to the BoardService.GetIssuesForBacklog +type GetBoardIssuesOptions struct { + // Jql filters results to sprints in the specified jql query + Jql string `json:"jql"` + + SearchOptions +} + +// IssuesInBoardResult represents a wrapper struct for search result +type IssuesInBoardResult struct { + Issues []Issue `json:"issues"` +} + // GetAllBoards will returns all boards. This only includes boards that the user has permission to view. // // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getAllBoards @@ -261,6 +274,30 @@ func (s *BoardService) GetAllSprints(ctx context.Context, boardID int64, options return result, resp, err } +// GetIssuesForBacklog returns all issues from a backlog, for a given board ID. +// This only includes issues that the user has permission to view. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/software/rest/api-group-board/#api-rest-agile-1-0-board-boardid-backlog-get +func (s *BoardService) GetIssuesForBacklog(ctx context.Context, boardID int64, options *GetBoardIssuesOptions) ([]Issue, *Response, error) { + apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%d/backlog", boardID) + url, err := addOptions(apiEndpoint, options) + if err != nil { + return nil, nil, err + } + req, err := s.client.NewRequest(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, nil, err + } + + result := new(IssuesInBoardResult) + resp, err := s.client.Do(req, result) + if err != nil { + err = NewJiraError(resp, err) + } + + return result.Issues, resp, err +} + // GetBoardConfiguration will return a board configuration for a given board Id // Jira API docs:https://developer.atlassian.com/cloud/jira/software/rest/#api-rest-agile-1-0-board-boardId-configuration-get // diff --git a/cloud/board_test.go b/cloud/board_test.go index c37978a8..be46acd9 100644 --- a/cloud/board_test.go +++ b/cloud/board_test.go @@ -233,3 +233,31 @@ func TestBoardService_GetBoardConfigoration(t *testing.T) { t.Errorf("Expected a max of 0 issues in progress. Got %d", inProgressColumn.Max) } } + +func TestBoardService_GetIssuesForBacklog(t *testing.T) { + setup() + defer teardown() + testapiEndpoint := "/rest/agile/1.0/board/123/backlog" + + raw, err := os.ReadFile("../testing/mock-data/backlog_in_board.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testapiEndpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + testRequestURL(t, r, testapiEndpoint) + fmt.Fprint(w, string(raw)) + }) + + issues, _, err := testClient.Board.GetIssuesForBacklog(context.Background(), 123, nil) + if err != nil { + t.Errorf("Error given: %v", err) + } + if issues == nil { + t.Error("Expected issues in sprint list. Issues list is nil") + } + if len(issues) != 1 { + t.Errorf("Expect there to be 1 issue in the sprint, found %v", len(issues)) + } + +} diff --git a/onpremise/board.go b/onpremise/board.go index 3fdd1f2e..a5c009a1 100644 --- a/onpremise/board.go +++ b/onpremise/board.go @@ -125,6 +125,19 @@ type BoardConfigurationColumnStatus struct { Self string `json:"self"` } +// GetBoardIssuesOptions specifies the optional parameters to the BoardService.GetIssuesForBacklog +type GetBoardIssuesOptions struct { + // Jql filters results to sprints in the specified jql query + Jql string `json:"jql"` + + SearchOptions +} + +// IssuesInBoardResult represents a wrapper struct for search result +type IssuesInBoardResult struct { + Issues []Issue `json:"issues"` +} + // GetAllBoards will returns all boards. This only includes boards that the user has permission to view. // // Jira API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getAllBoards @@ -252,6 +265,30 @@ func (s *BoardService) GetAllSprints(ctx context.Context, boardID int, options * return result, resp, err } +// GetIssuesForBacklog returns all issues from a backlog, for a given board ID. +// This only includes issues that the user has permission to view. +// +// Jira API docs: https://developer.atlassian.com/cloud/jira/software/rest/api-group-board/#api-rest-agile-1-0-board-boardid-backlog-get +func (s *BoardService) GetIssuesForBacklog(ctx context.Context, boardID int64, options *GetBoardIssuesOptions) ([]Issue, *Response, error) { + apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%d/backlog", boardID) + url, err := addOptions(apiEndpoint, options) + if err != nil { + return nil, nil, err + } + req, err := s.client.NewRequest(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, nil, err + } + + result := new(IssuesInBoardResult) + resp, err := s.client.Do(req, result) + if err != nil { + err = NewJiraError(resp, err) + } + + return result.Issues, resp, err +} + // GetBoardConfiguration will return a board configuration for a given board Id // Jira API docs:https://developer.atlassian.com/cloud/jira/software/rest/#api-rest-agile-1-0-board-boardId-configuration-get // diff --git a/onpremise/board_test.go b/onpremise/board_test.go index 44e3e122..53aa3568 100644 --- a/onpremise/board_test.go +++ b/onpremise/board_test.go @@ -233,3 +233,31 @@ func TestBoardService_GetBoardConfigoration(t *testing.T) { t.Errorf("Expected a max of 0 issues in progress. Got %d", inProgressColumn.Max) } } + +func TestBoardService_GetIssuesForBacklog(t *testing.T) { + setup() + defer teardown() + testapiEndpoint := "/rest/agile/1.0/board/123/backlog" + + raw, err := os.ReadFile("../testing/mock-data/backlog_in_board.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testapiEndpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, http.MethodGet) + testRequestURL(t, r, testapiEndpoint) + fmt.Fprint(w, string(raw)) + }) + + issues, _, err := testClient.Board.GetIssuesForBacklog(context.Background(), 123, nil) + if err != nil { + t.Errorf("Error given: %v", err) + } + if issues == nil { + t.Error("Expected issues in sprint list. Issues list is nil") + } + if len(issues) != 1 { + t.Errorf("Expect there to be 1 issue in the sprint, found %v", len(issues)) + } + +} diff --git a/testing/mock-data/backlog_in_board.json b/testing/mock-data/backlog_in_board.json new file mode 100644 index 00000000..adc42bcf --- /dev/null +++ b/testing/mock-data/backlog_in_board.json @@ -0,0 +1,115 @@ +{ + "expand": "schema,names", + "startAt": 0, + "maxResults": 50, + "total": 10, + "issues": [ + { + "expand": "operations,versionedRepresentations,editmeta,changelog,renderedFields", + "id": "12338", + "self": "https://example.atlassian.net/rest/agile/1.0/issue/12338", + "key": "AR-86", + "fields": { + "issuetype": { + "self": "https://example.atlassian.net/rest/api/2/issuetype/3", + "id": "3", + "description": "A task that needs to be done.", + "iconUrl": "https://example.atlassian.net/secure/viewavatar?size=xsmall&avatarId=10418&avatarType=issuetype", + "name": "Task", + "subtask": false, + "avatarId": 10418 + }, + "timespent": null, + "project": { + "self": "https://example.atlassian.net/rest/api/2/project/10302", + "id": "10302", + "key": "AR", + "name": "Team Argon", + "avatarUrls": { + "48x48": "https://example.atlassian.net/secure/projectavatar?pid=10302&avatarId=10610", + "24x24": "https://example.atlassian.net/secure/projectavatar?size=small&pid=10302&avatarId=10610", + "16x16": "https://example.atlassian.net/secure/projectavatar?size=xsmall&pid=10302&avatarId=10610", + "32x32": "https://example.atlassian.net/secure/projectavatar?size=medium&pid=10302&avatarId=10610" + } + }, + "fixVersions": [], + "customfield_11200": "0|0zzzzd:vi", + "aggregatetimespent": null, + "resolution": { + "self": "https://example.atlassian.net/rest/api/2/resolution/6", + "id": "6", + "description": "", + "name": "Done" + }, + "customfield_11401": null, + "customfield_11400": null, + "customfield_10105": 13.0, + "customfield_10700": "AR-37", + "resolutiondate": "2015-12-07T14:19:13.000-0800", + "workratio": -1, + "lastViewed": null, + "watches": { + "self": "https://example.atlassian.net/rest/api/2/issue/AR-86/watchers", + "watchCount": 2, + "isWatching": true + }, + "created": "2015-12-02T07:39:15.000-0800", + "epic": { + "id": 11900, + "key": "AR-37", + "self": "https://example.atlassian.net/rest/agile/1.0/epic/11900", + "name": "Moderation: Design", + "summary": "Moderation design", + "color": { + "key": "color_8" + }, + "done": true + }, + "priority": { + "self": "https://example.atlassian.net/rest/api/2/priority/3", + "iconUrl": "https://example.atlassian.net/images/icons/priorities/major.svg", + "name": "Major", + "id": "3" + }, + "customfield_10102": null, + "customfield_10103": null, + "labels": [], + "customfield_11700": null, + "timeestimate": null, + "aggregatetimeoriginalestimate": null, + "versions": [], + "issuelinks": [], + "assignee": { + "self": "https://example.atlassian.net/rest/api/2/user?username=mister.morris", + "name": "mister.morris", + "key": "mister.morris", + "emailAddress": "mister.morris@uservoice.com", + "avatarUrls": { + "48x48": "https://example.atlassian.net/secure/useravatar?ownerId=mister.morris&avatarId=10604", + "24x24": "https://example.atlassian.net/secure/useravatar?size=small&ownerId=mister.morris&avatarId=10604", + "16x16": "https://example.atlassian.net/secure/useravatar?size=xsmall&ownerId=mister.morris&avatarId=10604", + "32x32": "https://example.atlassian.net/secure/useravatar?size=medium&ownerId=mister.morris&avatarId=10604" + }, + "displayName": "mister Morris", + "active": true, + "timeZone": "America/New_York" + }, + "updated": "2016-02-01T08:17:04.000-0800", + "status": { + "self": "https://example.atlassian.net/rest/api/2/status/10000", + "description": "Ready to move to dev team for grooming", + "iconUrl": "https://example.atlassian.net/images/icons/statuses/closed.png", + "name": "Ready", + "id": "10000", + "statusCategory": { + "self": "https://example.atlassian.net/rest/api/2/statuscategory/2", + "id": 2, + "key": "new", + "colorName": "blue-gray", + "name": "To Do" + } + } + } + } + ] +} \ No newline at end of file