1
0
mirror of https://github.com/interviewstreet/go-jira.git synced 2025-02-03 13:11:49 +02:00

Merge branch 'develop' of https://github.com/EvgenKostenko/go-jira into EvgenKostenko-develop

* 'develop' of https://github.com/EvgenKostenko/go-jira:
  cosmetic fix in boards imports, tests in projects
  add delete board with tests + go fmt
  add board create with tests
  go fmt boards
  Implement BoardService and get boards list with parameters
  remove old project
  Add boards and fix some bugs in project
  add boards service
This commit is contained in:
Andy Grunwald 2016-06-19 14:40:09 +02:00
commit ce47602482
6 changed files with 375 additions and 1 deletions

124
board.go Normal file
View File

@ -0,0 +1,124 @@
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 []Board `json:"values"`
}
// 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
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"`
// 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
// Default Pagination options
// 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
func (s *BoardService) GetList(opt *BoardListOptions) (*BoardsList, *http.Response, error) {
apiEndpoint := "rest/agile/1.0/board"
url, err := addOptions(apiEndpoint, opt)
req, err := s.client.NewRequest("GET", url, nil)
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, 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 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
}
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
}
// 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
}

154
board_test.go Normal file
View File

@ -0,0 +1,154 @@
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)
}
}
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)
}
}
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)
}
}

27
jira.go
View File

@ -7,6 +7,9 @@ import (
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
"reflect"
"github.com/google/go-querystring/query"
) )
// A Client manages communication with the JIRA API. // A Client manages communication with the JIRA API.
@ -24,6 +27,7 @@ type Client struct {
Authentication *AuthenticationService Authentication *AuthenticationService
Issue *IssueService Issue *IssueService
Project *ProjectService Project *ProjectService
Board *BoardService
} }
// NewClient returns a new JIRA API client. // NewClient returns a new JIRA API client.
@ -50,6 +54,7 @@ func NewClient(httpClient *http.Client, baseURL string) (*Client, error) {
c.Authentication = &AuthenticationService{client: c} c.Authentication = &AuthenticationService{client: c}
c.Issue = &IssueService{client: c} c.Issue = &IssueService{client: c}
c.Project = &ProjectService{client: c} c.Project = &ProjectService{client: c}
c.Board = &BoardService{client: c}
return c, nil return c, nil
} }
@ -92,6 +97,28 @@ func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Requ
return req, nil 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) {
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. // 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. // 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. // Relative URLs should always be specified without a preceding slash.

View File

@ -8,6 +8,7 @@ import (
"net/http/httptest" "net/http/httptest"
"net/url" "net/url"
"reflect" "reflect"
"strings"
"testing" "testing"
"time" "time"
) )
@ -52,7 +53,7 @@ func testMethod(t *testing.T, r *http.Request, want string) {
} }
func testRequestURL(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) t.Errorf("Request URL: %v, want %v", got, want)
} }
} }

43
mocks/all_boards.json Normal file
View File

@ -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"
}
]
}

View File

@ -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"
}
]
}