mirror of
				https://github.com/interviewstreet/go-jira.git
				synced 2025-10-30 23:47:46 +02:00 
			
		
		
		
	Merge branch 'EvgenKostenko-develop'
* EvgenKostenko-develop: Adjusted PR #19 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:
		
							
								
								
									
										119
									
								
								board.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								board.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,119 @@ | ||||
| package jira | ||||
|  | ||||
| import "fmt" | ||||
|  | ||||
| // BoardService handles Agile Boards for the JIRA instance / API. | ||||
| // | ||||
| // JIRA API docs: https://docs.atlassian.com/jira-software/REST/server/ | ||||
| type BoardService struct { | ||||
| 	client *Client | ||||
| } | ||||
|  | ||||
| // BoardsList reflects a list of agile boards | ||||
| 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 agile 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    `json:"filterId,omitempty"` | ||||
| } | ||||
|  | ||||
| // BoardListOptions specifies the optional parameters to the BoardService.GetList | ||||
| type BoardListOptions struct { | ||||
| 	// BoardType filters results to boards of the specified type. | ||||
| 	// Valid values: scrum, kanban. | ||||
| 	BoardType string `url:"boardType,omitempty"` | ||||
| 	// Name filters results to boards that match or partially match the specified name. | ||||
| 	Name string `url:"name,omitempty"` | ||||
| 	// ProjectKeyOrID 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"` | ||||
|  | ||||
| 	SearchOptions | ||||
| } | ||||
|  | ||||
| // GetList will return all boards from JIRA | ||||
| // | ||||
| // JIRA API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getAllBoards | ||||
| func (s *BoardService) GetList(opt *BoardListOptions) (*BoardsList, *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 | ||||
| } | ||||
|  | ||||
| // Get will return the board for the given boardID. | ||||
| // This board will only be returned if the user has permission to view it. | ||||
| // | ||||
| // JIRA API docs: https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-getBoard | ||||
| func (s *BoardService) Get(boardID int) (*Board, *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 | ||||
| } | ||||
|  | ||||
| // Create 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, *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 | ||||
| } | ||||
|  | ||||
| // Delete will delete an agile board. | ||||
| // | ||||
| // https://docs.atlassian.com/jira-software/REST/cloud/#agile/1.0/board-deleteBoard | ||||
| func (s *BoardService) Delete(boardID int) (*Board, *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
									
								
							
							
						
						
									
										154
									
								
								board_test.go
									
									
									
									
									
										Normal 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", | ||||
| 	} | ||||
| 	boardsListOptions.StartAt = 1 | ||||
| 	boardsListOptions.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) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										14
									
								
								issue.go
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								issue.go
									
									
									
									
									
								
							| @@ -277,11 +277,17 @@ type CommentVisibility struct { | ||||
| 	Value string `json:"value,omitempty"` | ||||
| } | ||||
|  | ||||
| // SearchOptions represents options you can apply | ||||
| // at a Search functionality (JQL in JIRA). | ||||
| // SearchOptions 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 | ||||
| type SearchOptions struct { | ||||
| 	StartAt    int | ||||
| 	MaxResults int | ||||
| 	// StartAt: The starting index of the returned projects. Base index: 0. | ||||
| 	StartAt int `url:"startAt,omitempty"` | ||||
| 	// MaxResults: The maximum number of projects to return per page. Default: 50. | ||||
| 	MaxResults int `url:"maxResults,omitempty"` | ||||
| } | ||||
|  | ||||
| // searchResult is only a small wrapper arround the Search (with JQL) method | ||||
|   | ||||
							
								
								
									
										27
									
								
								jira.go
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								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. | ||||
| @@ -24,6 +27,7 @@ type Client struct { | ||||
| 	Authentication *AuthenticationService | ||||
| 	Issue          *IssueService | ||||
| 	Project        *ProjectService | ||||
| 	Board          *BoardService | ||||
| } | ||||
|  | ||||
| // 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.Issue = &IssueService{client: c} | ||||
| 	c.Project = &ProjectService{client: c} | ||||
| 	c.Board = &BoardService{client: c} | ||||
|  | ||||
| 	return c, nil | ||||
| } | ||||
| @@ -92,6 +97,28 @@ 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) { | ||||
| 	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. | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import ( | ||||
| 	"net/http/httptest" | ||||
| 	"net/url" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| 	"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) { | ||||
| 	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) | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										43
									
								
								mocks/all_boards.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								mocks/all_boards.json
									
									
									
									
									
										Normal 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" | ||||
|     } | ||||
|   ] | ||||
| } | ||||
							
								
								
									
										25
									
								
								mocks/all_boards_filtered.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								mocks/all_boards_filtered.json
									
									
									
									
									
										Normal 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" | ||||
|     } | ||||
|   ] | ||||
| } | ||||
		Reference in New Issue
	
	Block a user