From 9e70267766289879809f4ae143df49795b84bb6c Mon Sep 17 00:00:00 2001 From: Evgen Kostenko Date: Wed, 1 Jun 2016 21:23:27 +0300 Subject: [PATCH 1/8] add boards service --- board.go | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/board.go b/board.go index 8fa05fc..36a85a1 100644 --- a/board.go +++ b/board.go @@ -1 +1,59 @@ package jira + +import ( + //"fmt" + "net/http" +) + +type BoardService struct { + client *Client +} + +//Type for boards list +type BoardsList struct { + MaxResults int `json:"maxResults"` + StartAt int `json:"startAt"` + Total int `json:"total"` + IsLast bool `json:"isLast"` + Values []struct { + ID int `json:"id"` + Self string `json:"self"` + Name string `json:"name"` + Type string `json:"type"` + } `json:"values"` +} + +type BoardListSettings struct { + startAt int + maxResults int + boardType string + name string + projectKeyOrId string +} + + +// Get all boards form jira +// +// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects +func (s *BoardService) GetList(bs *BoardListSettings) (*BoardsList, *http.Response, error) { + + + apiEndpoint := "/rest/agile/1.0/board" + req, err := s.client.NewRequest("GET", apiEndpoint, nil) + + if bs != nil { + values := req.URL.Query() + values.Add(name, value) + } + + if err != nil { + return nil, nil, err + } + + boards := new(BoardsList) + resp, err := s.client.Do(req, boards) + if err != nil { + return nil, resp, err + } + return boards, resp, nil +} From 065bb9db447d96ff3b51218c5d98634aa26fb0e6 Mon Sep 17 00:00:00 2001 From: Evgen Kostenko Date: Wed, 15 Jun 2016 12:20:37 +0300 Subject: [PATCH 2/8] Add boards and fix some bugs in project --- board.go | 48 ++++++++++++++++++++++++------------------------ board_test.go | 1 + jira.go | 40 ++++++++++++++++++++++++++++++++++++++++ project_test.go | 2 +- 4 files changed, 66 insertions(+), 25 deletions(-) create mode 100644 board_test.go diff --git a/board.go b/board.go index 36a85a1..a52c703 100644 --- a/board.go +++ b/board.go @@ -11,41 +11,40 @@ type BoardService struct { //Type for boards list type BoardsList struct { - MaxResults int `json:"maxResults"` - StartAt int `json:"startAt"` - Total int `json:"total"` - IsLast bool `json:"isLast"` - Values []struct { - ID int `json:"id"` + MaxResults int `json:"maxResults"` + StartAt int `json:"startAt"` + Total int `json:"total"` + IsLast bool `json:"isLast"` + Values []struct { + ID int `json:"id"` Self string `json:"self"` Name string `json:"name"` Type string `json:"type"` } `json:"values"` } -type BoardListSettings struct { - startAt int - maxResults int - boardType string - name string - projectKeyOrId string -} +// BoardListOptions specifies the optional parameters to the BoardService.GetList +type BoardListOptions struct { + // Filters results to boards of the specified type. + // Valid values: scrum, kanban. + BoardType string `url:"boardType,omitempty"` + // Filters results to boards that match or partially match the specified name. + Name string `url:"name,omitempty"` + // Filters results to boards that are relevant to a project. + // Relevance meaning that the jql filter defined in board contains a reference to a project. + ProjectKeyOrId string `url:"projectKeyOrId,omitempty"` + // Default Pagination options + ListOptions +} // Get all boards form jira // // JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects -func (s *BoardService) GetList(bs *BoardListSettings) (*BoardsList, *http.Response, error) { - - +func (s *BoardService) GetList(opt *BoardListOptions) (*BoardsList, *http.Response, error) { apiEndpoint := "/rest/agile/1.0/board" - req, err := s.client.NewRequest("GET", apiEndpoint, nil) - - if bs != nil { - values := req.URL.Query() - values.Add(name, value) - } - + url, err := addOptions(apiEndpoint, opt) + req, err := s.client.NewRequest("GET", url, nil) if err != nil { return nil, nil, err } @@ -55,5 +54,6 @@ func (s *BoardService) GetList(bs *BoardListSettings) (*BoardsList, *http.Respon if err != nil { return nil, resp, err } - return boards, resp, nil + + return boards, resp, err } diff --git a/board_test.go b/board_test.go new file mode 100644 index 0000000..8fa05fc --- /dev/null +++ b/board_test.go @@ -0,0 +1 @@ +package jira diff --git a/jira.go b/jira.go index 09c5498..b8461be 100644 --- a/jira.go +++ b/jira.go @@ -7,6 +7,9 @@ import ( "io" "net/http" "net/url" + "reflect" + + "github.com/google/go-querystring/query" ) // A Client manages communication with the JIRA API. @@ -92,6 +95,41 @@ func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Requ return req, nil } +// ListOptions specifies the optional parameters to various List methods that +// support pagination. +// Pagination is used for the JIRA REST APIs to conserve server resources and limit +// response size for resources that return potentially large collection of items. +// A request to a pages API will result in a values array wrapped in a JSON object with some paging metadata +type ListOptions struct { + // The starting index of the returned projects. Base index: 0. + StartAt int `url:"startAt,omitempty"` + + // The maximum number of projects to return per page. Default: 50. + MaxResults int `url:"maxResults,omitempty"` +} + +// addOptions adds the parameters in opt as URL query parameters to s. opt +// must be a struct whose fields may contain "url" tags. +func addOptions(s string, opt interface{}) (string, error) { + v := reflect.ValueOf(opt) + if v.Kind() == reflect.Ptr && v.IsNil() { + return s, nil + } + + u, err := url.Parse(s) + if err != nil { + return s, err + } + + qs, err := query.Values(opt) + if err != nil { + return s, err + } + + u.RawQuery = qs.Encode() + return u.String(), nil +} + // NewMultiPartRequest creates an API request including a multi-part file. // A relative URL can be provided in urlStr, in which case it is resolved relative to the baseURL of the Client. // Relative URLs should always be specified without a preceding slash. @@ -144,6 +182,8 @@ func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) { return resp, err } + + // CheckResponse checks the API response for errors, and returns them if present. // A response is considered an error if it has a status code outside the 200 range. // The caller is responsible to analyze the response body. diff --git a/project_test.go b/project_test.go index e0ea0bd..2984269 100644 --- a/project_test.go +++ b/project_test.go @@ -68,7 +68,7 @@ func TestProjectGet_NoProject(t *testing.T) { projects, resp, err := testClient.Project.Get("99999999") if projects != nil { - t.Errorf("Expected nil. Got %s", projects) + t.Errorf("Expected nil. Got %s", err) } if resp.Status == "404" { From 00c9de8eaf83d27bf32fc92d5f3f645cc9d6ca6d Mon Sep 17 00:00:00 2001 From: Evgen Kostenko Date: Wed, 15 Jun 2016 13:29:30 +0300 Subject: [PATCH 3/8] remove old project --- progect.go | 171 ----------------------------------------------------- 1 file changed, 171 deletions(-) delete mode 100644 progect.go diff --git a/progect.go b/progect.go deleted file mode 100644 index 0cb9c4f..0000000 --- a/progect.go +++ /dev/null @@ -1,171 +0,0 @@ -package jira - -import ( - "fmt" - "net/http" -) - -type ProjectService struct { - client *Client -} - -// Project list type -type ProjectList []struct { - Expand string `json:"expand"` - Self string `json:"self"` - ID string `json:"id"` - Key string `json:"key"` - Name string `json:"name"` - AvatarUrls struct { - Four8X48 string `json:"48x48"` - Two4X24 string `json:"24x24"` - One6X16 string `json:"16x16"` - Three2X32 string `json:"32x32"` - } `json:"avatarUrls"` - ProjectTypeKey string `json:"projectTypeKey"` - ProjectCategory struct { - Self string `json:"self"` - ID string `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - } `json:"projectCategory,omitempty"` -} - -// Full project description -// Can't name it project because it exist in issue -type FullProject struct { - Expand string `json:"expand"` - Self string `json:"self"` - ID string `json:"id"` - Key string `json:"key"` - Description string `json:"description"` - Lead struct { - Self string `json:"self"` - Name string `json:"name"` - AvatarUrls struct { - Four8X48 string `json:"48x48"` - Two4X24 string `json:"24x24"` - One6X16 string `json:"16x16"` - Three2X32 string `json:"32x32"` - } `json:"avatarUrls"` - DisplayName string `json:"displayName"` - Active bool `json:"active"` - } `json:"lead"` - Components []struct { - Self string `json:"self"` - ID string `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Lead struct { - Self string `json:"self"` - Name string `json:"name"` - AvatarUrls struct { - Four8X48 string `json:"48x48"` - Two4X24 string `json:"24x24"` - One6X16 string `json:"16x16"` - Three2X32 string `json:"32x32"` - } `json:"avatarUrls"` - DisplayName string `json:"displayName"` - Active bool `json:"active"` - } `json:"lead"` - AssigneeType string `json:"assigneeType"` - Assignee struct { - Self string `json:"self"` - Name string `json:"name"` - AvatarUrls struct { - Four8X48 string `json:"48x48"` - Two4X24 string `json:"24x24"` - One6X16 string `json:"16x16"` - Three2X32 string `json:"32x32"` - } `json:"avatarUrls"` - DisplayName string `json:"displayName"` - Active bool `json:"active"` - } `json:"assignee"` - RealAssigneeType string `json:"realAssigneeType"` - RealAssignee struct { - Self string `json:"self"` - Name string `json:"name"` - AvatarUrls struct { - Four8X48 string `json:"48x48"` - Two4X24 string `json:"24x24"` - One6X16 string `json:"16x16"` - Three2X32 string `json:"32x32"` - } `json:"avatarUrls"` - DisplayName string `json:"displayName"` - Active bool `json:"active"` - } `json:"realAssignee"` - IsAssigneeTypeValid bool `json:"isAssigneeTypeValid"` - Project string `json:"project"` - ProjectID int `json:"projectId"` - } `json:"components"` - IssueTypes []struct { - Self string `json:"self"` - ID string `json:"id"` - Description string `json:"description"` - IconURL string `json:"iconUrl"` - Name string `json:"name"` - Subtask bool `json:"subtask"` - AvatarID int `json:"avatarId"` - } `json:"issueTypes"` - URL string `json:"url"` - Email string `json:"email"` - AssigneeType string `json:"assigneeType"` - Versions []interface{} `json:"versions"` - Name string `json:"name"` - Roles struct { - Developers string `json:"Developers"` - } `json:"roles"` - AvatarUrls struct { - Four8X48 string `json:"48x48"` - Two4X24 string `json:"24x24"` - One6X16 string `json:"16x16"` - Three2X32 string `json:"32x32"` - } `json:"avatarUrls"` - ProjectCategory struct { - Self string `json:"self"` - ID string `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - } `json:"projectCategory"` -} - -// Get all projects form jira -// -// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects -func (s *ProjectService) GetList() (*ProjectList, *http.Response, error) { - - apiEndpoint := "rest/api/2/project" - req, err := s.client.NewRequest("GET", apiEndpoint, nil) - - if err != nil { - return nil, nil, err - } - - projectList := new(ProjectList) - resp, err := s.client.Do(req, projectList) - if err != nil { - return nil, resp, err - } - return projectList, resp, nil -} - -// Get returns a full representation of the project for the given issue key. -// JIRA will attempt to identify the project by the projectIdOrKey path parameter. -// This can be an project id, or an project key. -// -// JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getProject -func (s *ProjectService) Get(projectID string) (*FullProject, *http.Response, error) { - apiEndpoint := fmt.Sprintf("/rest/api/2/project/%s", projectID) - req, err := s.client.NewRequest("GET", apiEndpoint, nil) - - if err != nil { - return nil, nil, err - } - - project := new(FullProject) - resp, err := s.client.Do(req, project) - if err != nil { - return nil, resp, err - } - return project, resp, nil -} From 81b91847b3eac1ca3440a5e8ba151d65da114919 Mon Sep 17 00:00:00 2001 From: Evgen Kostenko Date: Wed, 15 Jun 2016 20:08:15 +0300 Subject: [PATCH 4/8] Implement BoardService and get boards list with parameters --- board.go | 37 ++++++++++++------- board_test.go | 66 ++++++++++++++++++++++++++++++++++ jira.go | 14 ++------ jira_test.go | 3 +- mocks/all_boards.json | 43 ++++++++++++++++++++++ mocks/all_boards_filtered.json | 25 +++++++++++++ 6 files changed, 162 insertions(+), 26 deletions(-) create mode 100644 mocks/all_boards.json create mode 100644 mocks/all_boards_filtered.json diff --git a/board.go b/board.go index a52c703..965704e 100644 --- a/board.go +++ b/board.go @@ -11,33 +11,44 @@ type BoardService struct { //Type for boards list type BoardsList struct { - MaxResults int `json:"maxResults"` - StartAt int `json:"startAt"` - Total int `json:"total"` - IsLast bool `json:"isLast"` - Values []struct { - ID int `json:"id"` - Self string `json:"self"` - Name string `json:"name"` - Type string `json:"type"` - } `json:"values"` + MaxResults int `json:"maxResults"` + StartAt int `json:"startAt"` + Total int `json:"total"` + IsLast bool `json:"isLast"` + Values []Value `json:"values"` } +type Value struct { + ID int `json:"id"` + Self string `json:"self"` + Name string `json:"name"` + Type string `json:"type"` +} // BoardListOptions specifies the optional parameters to the BoardService.GetList type BoardListOptions struct { // Filters results to boards of the specified type. // Valid values: scrum, kanban. - BoardType string `url:"boardType,omitempty"` + BoardType string `url:"boardType,omitempty"` // Filters results to boards that match or partially match the specified name. - Name string `url:"name,omitempty"` + Name string `url:"name,omitempty"` // Filters results to boards that are relevant to a project. // Relevance meaning that the jql filter defined in board contains a reference to a project. ProjectKeyOrId string `url:"projectKeyOrId,omitempty"` + // ListOptions specifies the optional parameters to various List methods that + // support pagination. + // Pagination is used for the JIRA REST APIs to conserve server resources and limit + // response size for resources that return potentially large collection of items. + // A request to a pages API will result in a values array wrapped in a JSON object with some paging metadata + // Works only for 1.0 endpoints // Default Pagination options - ListOptions + // The starting index of the returned projects. Base index: 0. + StartAt int `url:"startAt,omitempty"` + // The maximum number of projects to return per page. Default: 50. + MaxResults int `url:"maxResults,omitempty"` } + // Get all boards form jira // // JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects diff --git a/board_test.go b/board_test.go index 8fa05fc..93dee4b 100644 --- a/board_test.go +++ b/board_test.go @@ -1 +1,67 @@ package jira + + +import ( + "fmt" + "io/ioutil" + "net/http" + "testing" +) + +func TestBoardsGetAll(t *testing.T) { + setup() + defer teardown() + testAPIEdpoint := "/rest/agile/1.0/board" + + raw, err := ioutil.ReadFile("./mocks/all_boards.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEdpoint) + fmt.Fprint(w, string(raw)) + }) + + projects, _, err := testClient.Board.GetList(nil) + if projects == nil { + t.Error("Expected boards list. Boards list is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +// Test with params +func TestBoardsGetFiltered(t *testing.T) { + setup() + defer teardown() + testAPIEdpoint := "/rest/agile/1.0/board" + + raw, err := ioutil.ReadFile("./mocks/all_boards_filtered.json") + if err != nil { + t.Error(err.Error()) + } + testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEdpoint) + fmt.Fprint(w, string(raw)) + }) + + boardsListOptions := BoardListOptions { + BoardType: "scrum", + Name: "Test", + ProjectKeyOrId: "TE", + StartAt: 1, + MaxResults: 10, + } + + projects, _, err := testClient.Board.GetList(&boardsListOptions) + if projects == nil { + t.Error("Expected boards list. Boards list is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} + diff --git a/jira.go b/jira.go index 729f4d2..bdc1724 100644 --- a/jira.go +++ b/jira.go @@ -27,6 +27,7 @@ type Client struct { Authentication *AuthenticationService Issue *IssueService Project *ProjectService + Board *BoardService } // NewClient returns a new JIRA API client. @@ -53,6 +54,7 @@ func NewClient(httpClient *http.Client, baseURL string) (*Client, error) { c.Authentication = &AuthenticationService{client: c} c.Issue = &IssueService{client: c} c.Project = &ProjectService{client: c} + c.Board = &BoardService{client: c} return c, nil } @@ -95,18 +97,6 @@ func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Requ return req, nil } -// ListOptions specifies the optional parameters to various List methods that -// support pagination. -// Pagination is used for the JIRA REST APIs to conserve server resources and limit -// response size for resources that return potentially large collection of items. -// A request to a pages API will result in a values array wrapped in a JSON object with some paging metadata -type ListOptions struct { - // The starting index of the returned projects. Base index: 0. - StartAt int `url:"startAt,omitempty"` - - // The maximum number of projects to return per page. Default: 50. - MaxResults int `url:"maxResults,omitempty"` -} // addOptions adds the parameters in opt as URL query parameters to s. opt // must be a struct whose fields may contain "url" tags. diff --git a/jira_test.go b/jira_test.go index b9f72d1..f87182b 100644 --- a/jira_test.go +++ b/jira_test.go @@ -10,6 +10,7 @@ import ( "reflect" "testing" "time" + "strings" ) const ( @@ -52,7 +53,7 @@ func testMethod(t *testing.T, r *http.Request, want string) { } func testRequestURL(t *testing.T, r *http.Request, want string) { - if got := r.URL.String(); got != want { + if got := r.URL.String(); !strings.HasPrefix(got, want) { t.Errorf("Request URL: %v, want %v", got, want) } } diff --git a/mocks/all_boards.json b/mocks/all_boards.json new file mode 100644 index 0000000..2065cb6 --- /dev/null +++ b/mocks/all_boards.json @@ -0,0 +1,43 @@ +{ + "maxResults": 50, + "startAt": 0, + "isLast": true, + "values": [ + { + "id": 4, + "self": "https://test.jira.org/rest/agile/1.0/board/4", + "name": "Test Weekly", + "type": "scrum" + }, + { + "id": 5, + "self": "https://test.jira.org/rest/agile/1.0/board/5", + "name": "Test Production Support", + "type": "kanban" + }, + { + "id": 6, + "self": "https://test.jira.org/rest/agile/1.0/board/6", + "name": "Test To Give", + "type": "kanban" + }, + { + "id": 7, + "self": "https://test.jira.org/rest/agile/1.0/board/7", + "name": "Test Journey App", + "type": "kanban" + }, + { + "id": 9, + "self": "https://test.jira.org/rest/agile/1.0/board/9", + "name": "Testix", + "type": "scrum" + }, + { + "id": 1, + "self": "https://test.jira.org/rest/agile/1.0/board/1", + "name": "Test Mobile", + "type": "scrum" + } + ] +} \ No newline at end of file diff --git a/mocks/all_boards_filtered.json b/mocks/all_boards_filtered.json new file mode 100644 index 0000000..545f8a8 --- /dev/null +++ b/mocks/all_boards_filtered.json @@ -0,0 +1,25 @@ +{ + "maxResults": 10, + "startAt": 1, + "isLast": true, + "values": [ + { + "id": 4, + "self": "https://test.jira.org/rest/agile/1.0/board/4", + "name": "Test Weekly", + "type": "scrum" + }, + { + "id": 9, + "self": "https://test.jira.org/rest/agile/1.0/board/9", + "name": "Testix", + "type": "scrum" + }, + { + "id": 1, + "self": "https://test.jira.org/rest/agile/1.0/board/1", + "name": "Test Mobile", + "type": "scrum" + } + ] +} \ No newline at end of file From 9f8d06db415e46a9acb6d992000842fcf1994f02 Mon Sep 17 00:00:00 2001 From: Evgen Kostenko Date: Wed, 15 Jun 2016 20:15:12 +0300 Subject: [PATCH 5/8] go fmt boards --- board.go | 1 - board_test.go | 12 +++++------- jira.go | 5 +---- jira_test.go | 2 +- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/board.go b/board.go index 965704e..4e9ff1c 100644 --- a/board.go +++ b/board.go @@ -48,7 +48,6 @@ type BoardListOptions struct { MaxResults int `url:"maxResults,omitempty"` } - // Get all boards form jira // // JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects diff --git a/board_test.go b/board_test.go index 93dee4b..5c67845 100644 --- a/board_test.go +++ b/board_test.go @@ -1,6 +1,5 @@ package jira - import ( "fmt" "io/ioutil" @@ -48,12 +47,12 @@ func TestBoardsGetFiltered(t *testing.T) { fmt.Fprint(w, string(raw)) }) - boardsListOptions := BoardListOptions { - BoardType: "scrum", - Name: "Test", + boardsListOptions := BoardListOptions{ + BoardType: "scrum", + Name: "Test", ProjectKeyOrId: "TE", - StartAt: 1, - MaxResults: 10, + StartAt: 1, + MaxResults: 10, } projects, _, err := testClient.Board.GetList(&boardsListOptions) @@ -64,4 +63,3 @@ func TestBoardsGetFiltered(t *testing.T) { t.Errorf("Error given: %s", err) } } - diff --git a/jira.go b/jira.go index bdc1724..83d84ea 100644 --- a/jira.go +++ b/jira.go @@ -27,7 +27,7 @@ type Client struct { Authentication *AuthenticationService Issue *IssueService Project *ProjectService - Board *BoardService + Board *BoardService } // NewClient returns a new JIRA API client. @@ -97,7 +97,6 @@ func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Requ return req, nil } - // addOptions adds the parameters in opt as URL query parameters to s. opt // must be a struct whose fields may contain "url" tags. func addOptions(s string, opt interface{}) (string, error) { @@ -172,8 +171,6 @@ func (c *Client) Do(req *http.Request, v interface{}) (*http.Response, error) { return resp, err } - - // CheckResponse checks the API response for errors, and returns them if present. // A response is considered an error if it has a status code outside the 200 range. // The caller is responsible to analyze the response body. diff --git a/jira_test.go b/jira_test.go index f87182b..e752494 100644 --- a/jira_test.go +++ b/jira_test.go @@ -8,9 +8,9 @@ import ( "net/http/httptest" "net/url" "reflect" + "strings" "testing" "time" - "strings" ) const ( From 7d8addc17970e94ca7b67d57af8f9727820d6cbc Mon Sep 17 00:00:00 2001 From: Evgen Kostenko Date: Thu, 16 Jun 2016 10:52:16 +0300 Subject: [PATCH 6/8] add board create with tests --- board.go | 58 ++++++++++++++++++++++++++++++++++------ board_test.go | 73 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 121 insertions(+), 10 deletions(-) diff --git a/board.go b/board.go index 4e9ff1c..5c29246 100644 --- a/board.go +++ b/board.go @@ -2,6 +2,7 @@ package jira import ( //"fmt" + "fmt" "net/http" ) @@ -15,14 +16,16 @@ type BoardsList struct { StartAt int `json:"startAt"` Total int `json:"total"` IsLast bool `json:"isLast"` - Values []Value `json:"values"` + Values []Board `json:"values"` } -type Value struct { - ID int `json:"id"` - Self string `json:"self"` - Name string `json:"name"` - Type string `json:"type"` +// Board represents a JIRA board +type Board struct { + ID int `json:"id",omitempty"` + Self string `json:"self",omitempty"` + Name string `json:"name",omitempty"` + Type string `json:"type",omitempty"` + FilterId int `omitempty` } // BoardListOptions specifies the optional parameters to the BoardService.GetList @@ -40,7 +43,6 @@ type BoardListOptions struct { // Pagination is used for the JIRA REST APIs to conserve server resources and limit // response size for resources that return potentially large collection of items. // A request to a pages API will result in a values array wrapped in a JSON object with some paging metadata - // Works only for 1.0 endpoints // Default Pagination options // The starting index of the returned projects. Base index: 0. StartAt int `url:"startAt,omitempty"` @@ -52,7 +54,7 @@ type BoardListOptions struct { // // JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/project-getAllProjects func (s *BoardService) GetList(opt *BoardListOptions) (*BoardsList, *http.Response, error) { - apiEndpoint := "/rest/agile/1.0/board" + apiEndpoint := "rest/agile/1.0/board" url, err := addOptions(apiEndpoint, opt) req, err := s.client.NewRequest("GET", url, nil) if err != nil { @@ -67,3 +69,43 @@ func (s *BoardService) GetList(opt *BoardListOptions) (*BoardsList, *http.Respon return boards, resp, err } + +// Returns the board for the given board Id. This board will only be returned if the user has permission to view it. +func (s *BoardService) Get(boardID string) (*Board, *http.Response, error) { + apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%s", boardID) + req, err := s.client.NewRequest("GET", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + board := new(Board) + resp, err := s.client.Do(req, board) + if err != nil { + return nil, resp, err + } + return board, resp, nil +} + +// Creates a new board. Board name, type and filter Id is required. +// name - Must be less than 255 characters. +// type - Valid values: scrum, kanban +// filterId - Id of a filter that the user has permissions to view. +// Note, if the user does not have the 'Create shared objects' permission and tries to create a shared board, a private +// board will be created instead (remember that board sharing depends on the filter sharing). + +// JIRA API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-createBoard +func (s *BoardService) Create(board *Board) (*Board, *http.Response, error) { + + apiEndpoint := "rest/agile/1.0/board" + req, err := s.client.NewRequest("POST", apiEndpoint, board) + if err != nil { + return nil, nil, err + } + + responseBoard := new(Board) + resp, err := s.client.Do(req, responseBoard) + if err != nil { + return nil, resp, err + } + return responseBoard, resp, nil +} diff --git a/board_test.go b/board_test.go index 5c67845..2dc7f9e 100644 --- a/board_test.go +++ b/board_test.go @@ -47,7 +47,7 @@ func TestBoardsGetFiltered(t *testing.T) { fmt.Fprint(w, string(raw)) }) - boardsListOptions := BoardListOptions{ + boardsListOptions := &BoardListOptions{ BoardType: "scrum", Name: "Test", ProjectKeyOrId: "TE", @@ -55,7 +55,7 @@ func TestBoardsGetFiltered(t *testing.T) { MaxResults: 10, } - projects, _, err := testClient.Board.GetList(&boardsListOptions) + projects, _, err := testClient.Board.GetList(boardsListOptions) if projects == nil { t.Error("Expected boards list. Boards list is nil") } @@ -63,3 +63,72 @@ func TestBoardsGetFiltered(t *testing.T) { t.Errorf("Error given: %s", err) } } + +func TestBoardGet(t *testing.T) { + setup() + defer teardown() + testAPIEdpoint := "/rest/agile/1.0/board/1" + + testMux.HandleFunc(testAPIEdpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEdpoint) + fmt.Fprint(w, `{"id":4,"self":"https://test.jira.org/rest/agile/1.0/board/1","name":"Test Weekly","type":"scrum"}`) + }) + + board, _, err := testClient.Board.Get("1") + if board == nil { + t.Error("Expected board list. Board list is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} + +func TestBoardGet_NoBoard(t *testing.T) { + setup() + defer teardown() + testAPIEndpoint := "/rest/api/2/board/99999999" + + testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testRequestURL(t, r, testAPIEndpoint) + fmt.Fprint(w, nil) + }) + + board, resp, err := testClient.Board.Get("99999999") + if board != nil { + t.Errorf("Expected nil. Got %s", err) + } + + if resp.Status == "404" { + t.Errorf("Expected status 404. Got %s", resp.Status) + } + if err == nil { + t.Errorf("Error given: %s", err) + } +} + +func TestBoardCreate(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/agile/1.0/board", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testRequestURL(t, r, "/rest/agile/1.0/board") + + w.WriteHeader(http.StatusCreated) + fmt.Fprint(w, `{"id":17,"self":"https://test.jira.org/rest/agile/1.0/board/17","name":"Test","type":"kanban"}`) + }) + + b := &Board{ + Name: "Test", + Type: "kanban", + FilterId: 17, + } + issue, _, err := testClient.Board.Create(b) + if issue == nil { + t.Error("Expected board. Board is nil") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} From c66a9e0e77f8586dae36938064c15e75fa18c4ef Mon Sep 17 00:00:00 2001 From: Evgen Kostenko Date: Thu, 16 Jun 2016 12:20:47 +0300 Subject: [PATCH 7/8] add delete board with tests + go fmt --- board.go | 30 ++++++++++++++++++++++-------- board_test.go | 24 ++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/board.go b/board.go index 5c29246..e413f3e 100644 --- a/board.go +++ b/board.go @@ -21,11 +21,11 @@ type BoardsList struct { // Board represents a JIRA board type Board struct { - ID int `json:"id",omitempty"` - Self string `json:"self",omitempty"` - Name string `json:"name",omitempty"` - Type string `json:"type",omitempty"` - FilterId int `omitempty` + ID int `json:"id",omitempty"` + Self string `json:"self",omitempty"` + Name string `json:"name",omitempty"` + Type string `json:"type",omitempty"` + FilterId int `omitempty` } // BoardListOptions specifies the optional parameters to the BoardService.GetList @@ -71,8 +71,8 @@ func (s *BoardService) GetList(opt *BoardListOptions) (*BoardsList, *http.Respon } // Returns the board for the given board Id. This board will only be returned if the user has permission to view it. -func (s *BoardService) Get(boardID string) (*Board, *http.Response, error) { - apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%s", boardID) +func (s *BoardService) Get(boardID int) (*Board, *http.Response, error) { + apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%v", boardID) req, err := s.client.NewRequest("GET", apiEndpoint, nil) if err != nil { return nil, nil, err @@ -92,7 +92,7 @@ func (s *BoardService) Get(boardID string) (*Board, *http.Response, error) { // filterId - Id of a filter that the user has permissions to view. // Note, if the user does not have the 'Create shared objects' permission and tries to create a shared board, a private // board will be created instead (remember that board sharing depends on the filter sharing). - +// // JIRA API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-createBoard func (s *BoardService) Create(board *Board) (*Board, *http.Response, error) { @@ -109,3 +109,17 @@ func (s *BoardService) Create(board *Board) (*Board, *http.Response, error) { } return responseBoard, resp, nil } + +// Deletes the board. +// +// https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-deleteBoard +func (s *BoardService) Delete(boardID int) (*Board, *http.Response, error) { + apiEndpoint := fmt.Sprintf("rest/agile/1.0/board/%v", boardID) + req, err := s.client.NewRequest("DELETE", apiEndpoint, nil) + if err != nil { + return nil, nil, err + } + + resp, err := s.client.Do(req, nil) + return nil, resp, err +} diff --git a/board_test.go b/board_test.go index 2dc7f9e..727d928 100644 --- a/board_test.go +++ b/board_test.go @@ -75,7 +75,7 @@ func TestBoardGet(t *testing.T) { fmt.Fprint(w, `{"id":4,"self":"https://test.jira.org/rest/agile/1.0/board/1","name":"Test Weekly","type":"scrum"}`) }) - board, _, err := testClient.Board.Get("1") + board, _, err := testClient.Board.Get(1) if board == nil { t.Error("Expected board list. Board list is nil") } @@ -95,7 +95,7 @@ func TestBoardGet_NoBoard(t *testing.T) { fmt.Fprint(w, nil) }) - board, resp, err := testClient.Board.Get("99999999") + board, resp, err := testClient.Board.Get(99999999) if board != nil { t.Errorf("Expected nil. Got %s", err) } @@ -132,3 +132,23 @@ func TestBoardCreate(t *testing.T) { t.Errorf("Error given: %s", err) } } + +func TestBoardDelete(t *testing.T) { + setup() + defer teardown() + testMux.HandleFunc("/rest/agile/1.0/board/1", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + testRequestURL(t, r, "/rest/agile/1.0/board/1") + + w.WriteHeader(http.StatusNoContent) + fmt.Fprint(w, `{}`) + }) + + _, resp, err := testClient.Board.Delete(1) + if resp.StatusCode != 204 { + t.Error("Expected board not deleted.") + } + if err != nil { + t.Errorf("Error given: %s", err) + } +} From e3b9aad702d8e5ed3d25d859da267cc3b12893ea Mon Sep 17 00:00:00 2001 From: Evgen Kostenko Date: Thu, 16 Jun 2016 12:25:47 +0300 Subject: [PATCH 8/8] cosmetic fix in boards imports, tests in projects --- board.go | 1 - project_test.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/board.go b/board.go index e413f3e..d7266dc 100644 --- a/board.go +++ b/board.go @@ -1,7 +1,6 @@ package jira import ( - //"fmt" "fmt" "net/http" ) diff --git a/project_test.go b/project_test.go index c8efe8d..79c6ad4 100644 --- a/project_test.go +++ b/project_test.go @@ -68,7 +68,7 @@ func TestProjectGet_NoProject(t *testing.T) { projects, resp, err := testClient.Project.Get("99999999") if projects != nil { - t.Errorf("Expected nil. Got %s", err) + t.Errorf("Expected nil. Got %+v", projects) } if resp.Status == "404" {