mirror of
				https://github.com/interviewstreet/go-jira.git
				synced 2025-10-30 23:47:46 +02:00 
			
		
		
		
	Add Transition API handling
Added TransitionService with 2 methods: * GetList for retrieving possible transitions for an issue * Create for creating transition and changing issue status in the process
This commit is contained in:
		| @@ -13,6 +13,7 @@ | |||||||
|  |  | ||||||
| * Authentication (HTTP Basic, OAuth, Session Cookie) | * Authentication (HTTP Basic, OAuth, Session Cookie) | ||||||
| * Create and receive issues | * Create and receive issues | ||||||
|  | * Create and retrieve issue transitions (status updates) | ||||||
| * Call every API endpoint of the JIRA, even it is not directly implemented in this library | * Call every API endpoint of the JIRA, even it is not directly implemented in this library | ||||||
|  |  | ||||||
| This package is not JIRA API complete (yet), but you can call every API endpoint you want. See [Call a not implemented API endpoint](#call-a-not-implemented-api-endpoint) how to do this. For all possible API endpoints of JRIA have a look at [latest JIRA REST API documentation](https://docs.atlassian.com/jira/REST/latest/). | This package is not JIRA API complete (yet), but you can call every API endpoint you want. See [Call a not implemented API endpoint](#call-a-not-implemented-api-endpoint) how to do this. For all possible API endpoints of JRIA have a look at [latest JIRA REST API documentation](https://docs.atlassian.com/jira/REST/latest/). | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								jira.go
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								jira.go
									
									
									
									
									
								
							| @@ -28,6 +28,7 @@ type Client struct { | |||||||
| 	Issue          *IssueService | 	Issue          *IssueService | ||||||
| 	Project        *ProjectService | 	Project        *ProjectService | ||||||
| 	Board          *BoardService | 	Board          *BoardService | ||||||
|  | 	Transition     *TransitionService | ||||||
| } | } | ||||||
|  |  | ||||||
| // NewClient returns a new JIRA API client. | // NewClient returns a new JIRA API client. | ||||||
| @@ -55,6 +56,7 @@ func NewClient(httpClient *http.Client, baseURL string) (*Client, error) { | |||||||
| 	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} | 	c.Board = &BoardService{client: c} | ||||||
|  | 	c.Transition = &TransitionService{client: c} | ||||||
|  |  | ||||||
| 	return c, nil | 	return c, nil | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										101
									
								
								mocks/transitions.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								mocks/transitions.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | |||||||
|  | { | ||||||
|  |     "expand": "transitions", | ||||||
|  |     "transitions": [ | ||||||
|  |         { | ||||||
|  |             "id": "2", | ||||||
|  |             "name": "Close Issue", | ||||||
|  |             "to": { | ||||||
|  |                 "self": "http://localhost:8090/jira/rest/api/2.0/status/10000", | ||||||
|  |                 "description": "The issue is currently being worked on.", | ||||||
|  |                 "iconUrl": "http://localhost:8090/jira/images/icons/progress.gif", | ||||||
|  |                 "name": "In Progress", | ||||||
|  |                 "id": "10000", | ||||||
|  |                 "statusCategory": { | ||||||
|  |                     "self": "http://localhost:8090/jira/rest/api/2.0/statuscategory/1", | ||||||
|  |                     "id": 1, | ||||||
|  |                     "key": "in-flight", | ||||||
|  |                     "colorName": "yellow", | ||||||
|  |                     "name": "In Progress" | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             "fields": { | ||||||
|  |                 "summary": { | ||||||
|  |                     "required": false, | ||||||
|  |                     "schema": { | ||||||
|  |                         "type": "array", | ||||||
|  |                         "items": "option", | ||||||
|  |                         "custom": "com.atlassian.jira.plugin.system.customfieldtypes:multiselect", | ||||||
|  |                         "customId": 10001 | ||||||
|  |                     }, | ||||||
|  |                     "name": "My Multi Select", | ||||||
|  |                     "hasDefaultValue": false, | ||||||
|  |                     "operations": [ | ||||||
|  |                         "set", | ||||||
|  |                         "add" | ||||||
|  |                     ], | ||||||
|  |                     "allowedValues": [ | ||||||
|  |                         "red", | ||||||
|  |                         "blue" | ||||||
|  |                     ] | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "id": "711", | ||||||
|  |             "name": "QA Review", | ||||||
|  |             "to": { | ||||||
|  |                 "self": "http://localhost:8090/jira/rest/api/2.0/status/5", | ||||||
|  |                 "description": "The issue is closed.", | ||||||
|  |                 "iconUrl": "http://localhost:8090/jira/images/icons/closed.gif", | ||||||
|  |                 "name": "Closed", | ||||||
|  |                 "id": "5", | ||||||
|  |                 "statusCategory": { | ||||||
|  |                     "self": "http://localhost:8090/jira/rest/api/2.0/statuscategory/9", | ||||||
|  |                     "id": 9, | ||||||
|  |                     "key": "completed", | ||||||
|  |                     "colorName": "green" | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             "fields": { | ||||||
|  |                 "summary": { | ||||||
|  |                     "required": false, | ||||||
|  |                     "schema": { | ||||||
|  |                         "type": "array", | ||||||
|  |                         "items": "option", | ||||||
|  |                         "custom": "com.atlassian.jira.plugin.system.customfieldtypes:multiselect", | ||||||
|  |                         "customId": 10001 | ||||||
|  |                     }, | ||||||
|  |                     "name": "My Multi Select", | ||||||
|  |                     "hasDefaultValue": false, | ||||||
|  |                     "operations": [ | ||||||
|  |                         "set", | ||||||
|  |                         "add" | ||||||
|  |                     ], | ||||||
|  |                     "allowedValues": [ | ||||||
|  |                         "red", | ||||||
|  |                         "blue" | ||||||
|  |                     ] | ||||||
|  |                 }, | ||||||
|  |                 "colour": { | ||||||
|  |                     "required": false, | ||||||
|  |                     "schema": { | ||||||
|  |                         "type": "array", | ||||||
|  |                         "items": "option", | ||||||
|  |                         "custom": "com.atlassian.jira.plugin.system.customfieldtypes:multiselect", | ||||||
|  |                         "customId": 10001 | ||||||
|  |                     }, | ||||||
|  |                     "name": "My Multi Select", | ||||||
|  |                     "hasDefaultValue": false, | ||||||
|  |                     "operations": [ | ||||||
|  |                         "set", | ||||||
|  |                         "add" | ||||||
|  |                     ], | ||||||
|  |                     "allowedValues": [ | ||||||
|  |                         "red", | ||||||
|  |                         "blue" | ||||||
|  |                     ] | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     ] | ||||||
|  | } | ||||||
							
								
								
									
										75
									
								
								transition.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								transition.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | |||||||
|  | package jira | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // TransitionService handles transitions for JIRA issue. | ||||||
|  | type TransitionService struct { | ||||||
|  | 	client *Client | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Wrapper struct for search result | ||||||
|  | type transitionResult struct { | ||||||
|  | 	Transitions []Transition `json:transitions` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Transition represents an issue transition in JIRA | ||||||
|  | type Transition struct { | ||||||
|  | 	ID     string                     `json:"id"` | ||||||
|  | 	Name   string                     `json:"name"` | ||||||
|  | 	Fields map[string]TransitionField `json:"fields"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type TransitionField struct { | ||||||
|  | 	Required bool `json:"required"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // CreatePayload is used for creating new issue transitions | ||||||
|  | type CreateTransitionPayload struct { | ||||||
|  | 	Transition TransitionPayload `json:"transition"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type TransitionPayload struct { | ||||||
|  | 	ID string `json:"id"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // GetList gets transitions available for given issue | ||||||
|  | // | ||||||
|  | // JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-getTransitions | ||||||
|  | func (s *TransitionService) GetList(id string) ([]Transition, *Response, error) { | ||||||
|  | 	apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/transitions?expand=transitions.fields", id) | ||||||
|  | 	req, err := s.client.NewRequest("GET", apiEndpoint, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	result := new(transitionResult) | ||||||
|  | 	resp, err := s.client.Do(req, result) | ||||||
|  | 	return result.Transitions, resp, err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Basic transition creation. This simply creates transition with given ID for issue | ||||||
|  | // with given ID. It doesn't yet support anything else. | ||||||
|  | // | ||||||
|  | // JIRA API docs: https://docs.atlassian.com/jira/REST/latest/#api/2/issue-doTransition | ||||||
|  | func (s *TransitionService) Create(ticketID, transitionID string) (*Response, error) { | ||||||
|  | 	apiEndpoint := fmt.Sprintf("rest/api/2/issue/%s/transitions", ticketID) | ||||||
|  |  | ||||||
|  | 	payload := CreateTransitionPayload{ | ||||||
|  | 		Transition: TransitionPayload{ | ||||||
|  | 			ID: transitionID, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	req, err := s.client.NewRequest("POST", apiEndpoint, payload) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	resp, err := s.client.Do(req, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return resp, nil | ||||||
|  | } | ||||||
							
								
								
									
										76
									
								
								transition_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								transition_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | |||||||
|  | package jira | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"net/http" | ||||||
|  | 	"strings" | ||||||
|  | 	"testing" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestTransitionGetList(t *testing.T) { | ||||||
|  | 	setup() | ||||||
|  | 	defer teardown() | ||||||
|  |  | ||||||
|  | 	testAPIEndpoint := "/rest/api/2/issue/123/transitions" | ||||||
|  |  | ||||||
|  | 	raw, err := ioutil.ReadFile("./mocks/transitions.json") | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Error(err.Error()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 		testMethod(t, r, "GET") | ||||||
|  | 		testRequestURL(t, r, testAPIEndpoint) | ||||||
|  | 		fmt.Fprint(w, string(raw)) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	transitions, _, err := testClient.Transition.GetList("123") | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Errorf("Got error: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if transitions == nil { | ||||||
|  | 		t.Error("Expected transition list. Got nil.") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(transitions) != 2 { | ||||||
|  | 		t.Errorf("Expected 2 transitions. Got %d", len(transitions)) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if transitions[0].Fields["summary"].Required != false { | ||||||
|  | 		t.Errorf("First transition summary field should not be required") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestTransitionCreate(t *testing.T) { | ||||||
|  | 	setup() | ||||||
|  | 	defer teardown() | ||||||
|  |  | ||||||
|  | 	testAPIEndpoint := "/rest/api/2/issue/123/transitions" | ||||||
|  |  | ||||||
|  | 	transitionID := "22" | ||||||
|  |  | ||||||
|  | 	testMux.HandleFunc(testAPIEndpoint, func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 		testMethod(t, r, "POST") | ||||||
|  | 		testRequestURL(t, r, testAPIEndpoint) | ||||||
|  |  | ||||||
|  | 		decoder := json.NewDecoder(r.Body) | ||||||
|  | 		var payload CreateTransitionPayload | ||||||
|  | 		err := decoder.Decode(&payload) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Error("Got error: %v", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if strings.Compare(payload.Transition.ID, transitionID) != 0 { | ||||||
|  | 			t.Errorf("Expected %s to be in payload, got %s instead", transitionID, payload.Transition.ID) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  | 	_, err := testClient.Transition.Create("123", transitionID) | ||||||
|  |  | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Error("Got error: %v", err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user