1
0
mirror of https://github.com/interviewstreet/go-jira.git synced 2025-02-09 13:36:58 +02:00

Adds unknown map for arbitrary fields in IssueFields. Adds Custom Marshall,Unmarshall. Adds structs tag where necessary

This commit is contained in:
Bidesh Thapaliya 2016-09-23 16:19:07 +02:00
parent fe6b129172
commit 5ce765977f
6 changed files with 508 additions and 239 deletions

View File

@ -14,20 +14,20 @@ type BoardService struct {
// 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"`
MaxResults int `json:"maxResults" structs:"maxResults"`
StartAt int `json:"startAt" structs:"startAt"`
Total int `json:"total" structs:"total"`
IsLast bool `json:"isLast" structs:"isLast"`
Values []Board `json:"values" structs:"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"`
ID int `json:"id,omitempty" structs:"id,omitempty"`
Self string `json:"self,omitempty" structs:"self,omitempty"`
Name string `json:"name,omitempty" structs:"name,omitemtpy"`
Type string `json:"type,omitempty" structs:"type,omitempty"`
FilterID int `json:"filterId,omitempty" structs:"filterId,omitempty"`
}
// BoardListOptions specifies the optional parameters to the BoardService.GetList
@ -46,19 +46,19 @@ type BoardListOptions struct {
// Wrapper struct for search result
type sprintsResult struct {
Sprints []Sprint `json:"values"`
Sprints []Sprint `json:"values" structs:"values"`
}
// Sprint represents a sprint on JIRA agile board
type Sprint struct {
ID int `json:"id"`
Name string `json:"name"`
CompleteDate *time.Time `json:"completeDate"`
EndDate *time.Time `json:"endDate"`
StartDate *time.Time `json:"startDate"`
OriginBoardID int `json:"originBoardId"`
Self string `json:"self"`
State string `json:"state"`
ID int `json:"id" structs:"id"`
Name string `json:"name" structs:"name"`
CompleteDate *time.Time `json:"completeDate" structs:"completeDate"`
EndDate *time.Time `json:"endDate" structs:"endDate"`
StartDate *time.Time `json:"startDate" structs:"startDate"`
OriginBoardID int `json:"originBoardId" structs:"originBoardId"`
Self string `json:"self" structs:"self"`
State string `json:"state" structs:"state"`
}
// GetAllBoards will returns all boards. This only includes boards that the user has permission to view.

369
issue.go
View File

@ -2,10 +2,14 @@ package jira
import (
"bytes"
"encoding/json"
"fmt"
"github.com/fatih/structs"
"github.com/trivago/tgo/tcontainer"
"io"
"mime/multipart"
"net/url"
"reflect"
"strings"
"time"
)
@ -24,35 +28,35 @@ type IssueService struct {
// Issue represents a JIRA issue.
type Issue struct {
Expand string `json:"expand,omitempty"`
ID string `json:"id,omitempty"`
Self string `json:"self,omitempty"`
Key string `json:"key,omitempty"`
Fields *IssueFields `json:"fields,omitempty"`
Expand string `json:"expand,omitempty" structs:"expand,omitempty"`
ID string `json:"id,omitempty" structs:"id,omitempty"`
Self string `json:"self,omitempty" structs:"self,omitempty"`
Key string `json:"key,omitempty" structs:"key,omitempty"`
Fields *IssueFields `json:"fields,omitempty" structs:"fields,omitempty"`
}
// Attachment represents a JIRA attachment
type Attachment struct {
Self string `json:"self,omitempty"`
ID string `json:"id,omitempty"`
Filename string `json:"filename,omitempty"`
Author *User `json:"author,omitempty"`
Created string `json:"created,omitempty"`
Size int `json:"size,omitempty"`
MimeType string `json:"mimeType,omitempty"`
Content string `json:"content,omitempty"`
Thumbnail string `json:"thumbnail,omitempty"`
Self string `json:"self,omitempty" structs:"self,omitempty"`
ID string `json:"id,omitempty" structs:"id,omitempty"`
Filename string `json:"filename,omitempty" structs:"filename,omitempty"`
Author *User `json:"author,omitempty" structs:"author,omitempty"`
Created string `json:"created,omitempty" structs:"created,omitempty"`
Size int `json:"size,omitempty" structs:"size,omitempty"`
MimeType string `json:"mimeType,omitempty" structs:"mimeType,omitempty"`
Content string `json:"content,omitempty" structs:"content,omitempty"`
Thumbnail string `json:"thumbnail,omitempty" structs:"thumbnail,omitempty"`
}
// Epic represents the epic to which an issue is associated
// Not that this struct does not process the returned "color" value
type Epic struct {
ID int `json:"id"`
Key string `json:"key"`
Self string `json:"self"`
Name string `json:"name"`
Summary string `json:"summary"`
Done bool `json:"done"`
ID int `json:"id" structs:"id"`
Key string `json:"key" structs:"key"`
Self string `json:"self" structs:"self"`
Name string `json:"name" structs:"name"`
Summary string `json:"summary" structs:"summary"`
Done bool `json:"done" structs:"done"`
}
// IssueFields represents single fields of a JIRA issue.
@ -70,124 +74,185 @@ type IssueFields struct {
// * "aggregatetimeestimate": null,
// * "environment": null,
// * "duedate": null,
Type IssueType `json:"issuetype"`
Project Project `json:"project,omitempty"`
Resolution *Resolution `json:"resolution,omitempty"`
Priority *Priority `json:"priority,omitempty"`
Resolutiondate string `json:"resolutiondate,omitempty"`
Created string `json:"created,omitempty"`
Watches *Watches `json:"watches,omitempty"`
Assignee *User `json:"assignee,omitempty"`
Updated string `json:"updated,omitempty"`
Description string `json:"description,omitempty"`
Summary string `json:"summary"`
Creator *User `json:"Creator,omitempty"`
Reporter *User `json:"reporter,omitempty"`
Components []*Component `json:"components,omitempty"`
Status *Status `json:"status,omitempty"`
Progress *Progress `json:"progress,omitempty"`
AggregateProgress *Progress `json:"aggregateprogress,omitempty"`
Worklog *Worklog `json:"worklog,omitempty"`
IssueLinks []*IssueLink `json:"issuelinks,omitempty"`
Comments *Comments `json:"comment,omitempty"`
FixVersions []*FixVersion `json:"fixVersions,omitempty"`
Labels []string `json:"labels,omitempty"`
Subtasks []*Subtasks `json:"subtasks,omitempty"`
Attachments []*Attachment `json:"attachment,omitempty"`
Epic *Epic `json:"epic,omitempty"`
Type IssueType `json:"issuetype" structs:"issuetype"`
Project Project `json:"project,omitempty" structs:"project,omitempty"`
Resolution *Resolution `json:"resolution,omitempty" structs:"resolution,omitempty"`
Priority *Priority `json:"priority,omitempty" structs:"priority,omitempty"`
Resolutiondate string `json:"resolutiondate,omitempty" structs:"resolutiondate,omitempty"`
Created string `json:"created,omitempty" structs:"created,omitempty"`
Watches *Watches `json:"watches,omitempty" structs:"watches,omitempty"`
Assignee *User `json:"assignee,omitempty" structs:"assignee,omitempty"`
Updated string `json:"updated,omitempty" structs:"updated,omitempty"`
Description string `json:"description,omitempty" structs:"description,omitempty"`
Summary string `json:"summary" structs:"summary"`
Creator *User `json:"Creator,omitempty" structs:"Creator,omitempty"`
Reporter *User `json:"reporter,omitempty" structs:"reporter,omitempty"`
Components []*Component `json:"components,omitempty" structs:"components,omitempty"`
Status *Status `json:"status,omitempty" structs:"status,omitempty"`
Progress *Progress `json:"progress,omitempty" structs:"progress,omitempty"`
AggregateProgress *Progress `json:"aggregateprogress,omitempty" structs:"aggregateprogress,omitempty"`
Worklog *Worklog `json:"worklog,omitempty" structs:"worklog,omitempty"`
IssueLinks []*IssueLink `json:"issuelinks,omitempty" structs:"issuelinks,omitempty"`
Comments *Comments `json:"comment,omitempty" structs:"comment,omitempty"`
FixVersions []*FixVersion `json:"fixVersions,omitempty" structs:"fixVersions,omitempty"`
Labels []string `json:"labels,omitempty" structs:"labels,omitempty"`
Subtasks []*Subtasks `json:"subtasks,omitempty" structs:"subtasks,omitempty"`
Attachments []*Attachment `json:"attachment,omitempty" structs:"attachment,omitempty"`
Epic *Epic `json:"epic,omitempty" structs:"epic,omitempty"`
Unknowns tcontainer.MarshalMap
}
func (i *IssueFields) MarshalJSON() ([]byte, error) {
m := structs.Map(i)
unknowns, okay := m["Unknowns"]
if okay {
// if unknowns present, shift all key value from unkown to a level up
for key, value := range unknowns.(tcontainer.MarshalMap) {
m[key] = value
}
delete(m, "Unknowns")
}
return json.Marshal(m)
}
func (i *IssueFields) UnmarshalJSON(data []byte) error {
// Do the normal unmarshalling first
// Details for this way: http://choly.ca/post/go-json-marshalling/
type Alias IssueFields
aux := &struct {
*Alias
}{
Alias: (*Alias)(i),
}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
totalMap := tcontainer.NewMarshalMap()
err := json.Unmarshal(data, &totalMap)
if err != nil {
return err
}
t := reflect.TypeOf(*i)
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
tagDetail := field.Tag.Get("json")
if tagDetail == "" {
// ignore if there are no tags
continue
}
options := strings.Split(tagDetail, ",")
if len(options) == 0 {
return fmt.Errorf("No tags options found for %s", field.Name)
}
// the first one is the json tag
key := options[0]
if _, okay := totalMap.Value(key); okay {
delete(totalMap, key)
}
}
i = (*IssueFields)(aux.Alias)
// all the tags found in the struct were removed. Whatever is left are unknowns to struct
i.Unknowns = totalMap
return nil
}
// IssueType represents a type of a JIRA issue.
// Typical types are "Request", "Bug", "Story", ...
type IssueType struct {
Self string `json:"self,omitempty"`
ID string `json:"id,omitempty"`
Description string `json:"description,omitempty"`
IconURL string `json:"iconUrl,omitempty"`
Name string `json:"name,omitempty"`
Subtask bool `json:"subtask,omitempty"`
AvatarID int `json:"avatarId,omitempty"`
Self string `json:"self,omitempty" structs:"self,omitempty"`
ID string `json:"id,omitempty" struct:"id,omitempty"`
Description string `json:"description,omitempty" struct:"description,omitempty"`
IconURL string `json:"iconUrl,omitempty" struct:"iconUrl,omitempty"`
Name string `json:"name,omitempty" struct:"name,omitempty"`
Subtask bool `json:"subtask,omitempty" struct:"subtask,omitempty"`
AvatarID int `json:"avatarId,omitempty" struct:"avatarId,omitempty"`
}
// Resolution represents a resolution of a JIRA issue.
// Typical types are "Fixed", "Suspended", "Won't Fix", ...
type Resolution struct {
Self string `json:"self"`
ID string `json:"id"`
Description string `json:"description"`
Name string `json:"name"`
Self string `json:"self" structs:"self"`
ID string `json:"id" structs:"id"`
Description string `json:"description" structs:"description"`
Name string `json:"name" structs:"name"`
}
// Priority represents a priority of a JIRA issue.
// Typical types are "Normal", "Moderate", "Urgent", ...
type Priority struct {
Self string `json:"self,omitempty"`
IconURL string `json:"iconUrl,omitempty"`
Name string `json:"name,omitempty"`
ID string `json:"id,omitempty"`
Self string `json:"self,omitempty" structs:"self,omitempty"`
IconURL string `json:"iconUrl,omitempty" structs:"iconUrl,omitempty"`
Name string `json:"name,omitempty" structs:"name,omitempty"`
ID string `json:"id,omitempty" structs:"id,omitempty"`
}
// Watches represents a type of how many user are "observing" a JIRA issue to track the status / updates.
type Watches struct {
Self string `json:"self,omitempty"`
WatchCount int `json:"watchCount,omitempty"`
IsWatching bool `json:"isWatching,omitempty"`
Self string `json:"self,omitempty" structs:"self,omitempty"`
WatchCount int `json:"watchCount,omitempty" structs:"watchCount,omitempty"`
IsWatching bool `json:"isWatching,omitempty" structs:"isWatching,omitempty"`
}
// User represents a user who is this JIRA issue assigned to.
type User struct {
Self string `json:"self,omitempty"`
Name string `json:"name,omitempty"`
Key string `json:"key,omitempty"`
EmailAddress string `json:"emailAddress,omitempty"`
AvatarUrls AvatarUrls `json:"avatarUrls,omitempty"`
DisplayName string `json:"displayName,omitempty"`
Active bool `json:"active,omitempty"`
TimeZone string `json:"timeZone,omitempty"`
Self string `json:"self,omitempty" structs:"self,omitempty"`
Name string `json:"name,omitempty" structs:"name,omitempty"`
Key string `json:"key,omitempty" structs:"key,omitempty"`
EmailAddress string `json:"emailAddress,omitempty" structs:"emailAddress,omitempty"`
AvatarUrls AvatarUrls `json:"avatarUrls,omitempty" structs:"avatarUrls,omitempty"`
DisplayName string `json:"displayName,omitempty" structs:"displayName,omitempty"`
Active bool `json:"active,omitempty" structs:"active,omitempty"`
TimeZone string `json:"timeZone,omitempty" structs:"timeZone,omitempty"`
}
// AvatarUrls represents different dimensions of avatars / images
type AvatarUrls struct {
Four8X48 string `json:"48x48,omitempty"`
Two4X24 string `json:"24x24,omitempty"`
One6X16 string `json:"16x16,omitempty"`
Three2X32 string `json:"32x32,omitempty"`
Four8X48 string `json:"48x48,omitempty" structs:"48x48,omitempty"`
Two4X24 string `json:"24x24,omitempty" structs:"24x24,omitempty"`
One6X16 string `json:"16x16,omitempty" structs:"16x16,omitempty"`
Three2X32 string `json:"32x32,omitempty" structs:"32x32,omitempty"`
}
// Component represents a "component" of a JIRA issue.
// Components can be user defined in every JIRA instance.
type Component struct {
Self string `json:"self,omitempty"`
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Self string `json:"self,omitempty" structs:"self,omitempty"`
ID string `json:"id,omitempty" structs:"id,omitempty"`
Name string `json:"name,omitempty" structs:"name,omitempty"`
}
// Status represents the current status of a JIRA issue.
// Typical status are "Open", "In Progress", "Closed", ...
// Status can be user defined in every JIRA instance.
type Status struct {
Self string `json:"self"`
Description string `json:"description"`
IconURL string `json:"iconUrl"`
Name string `json:"name"`
ID string `json:"id"`
StatusCategory StatusCategory `json:"statusCategory"`
Self string `json:"self" structs:"self"`
Description string `json:"description" structs:"description"`
IconURL string `json:"iconUrl" structs:"iconUrl"`
Name string `json:"name" structs:"name"`
ID string `json:"id" structs:"id"`
StatusCategory StatusCategory `json:"statusCategory" structs:"statusCategory"`
}
// StatusCategory represents the category a status belongs to.
// Those categories can be user defined in every JIRA instance.
type StatusCategory struct {
Self string `json:"self"`
ID int `json:"id"`
Name string `json:"name"`
Key string `json:"key"`
ColorName string `json:"colorName"`
Self string `json:"self" structs:"self"`
ID int `json:"id" structs:"id"`
Name string `json:"name" structs:"name"`
Key string `json:"key" structs:"key"`
ColorName string `json:"colorName" structs:"colorName"`
}
// Progress represents the progress of a JIRA issue.
type Progress struct {
Progress int `json:"progress"`
Total int `json:"total"`
Progress int `json:"progress" structs:"progress"`
Total int `json:"total" structs:"total"`
}
// Time represents the Time definition of JIRA as a time.Time of go
@ -195,29 +260,29 @@ type Time time.Time
// Wrapper struct for search result
type transitionResult struct {
Transitions []Transition `json:"transitions"`
Transitions []Transition `json:"transitions" structs:"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"`
ID string `json:"id" structs:"id"`
Name string `json:"name" structs:"name"`
Fields map[string]TransitionField `json:"fields" structs:"fields"`
}
// TransitionField represents the value of one Transistion
type TransitionField struct {
Required bool `json:"required"`
Required bool `json:"required" structs:"required"`
}
// CreateTransitionPayload is used for creating new issue transitions
type CreateTransitionPayload struct {
Transition TransitionPayload `json:"transition"`
Transition TransitionPayload `json:"transition" structs:"transition"`
}
// TransitionPayload represents the request payload of Transistion calls like DoTransition
type TransitionPayload struct {
ID string `json:"id"`
ID string `json:"id" structs:"id"`
}
// UnmarshalJSON will transform the JIRA time into a time.Time
@ -235,90 +300,90 @@ func (t *Time) UnmarshalJSON(b []byte) error {
// One Worklog contains zero or n WorklogRecords
// JIRA Wiki: https://confluence.atlassian.com/jira/logging-work-on-an-issue-185729605.html
type Worklog struct {
StartAt int `json:"startAt"`
MaxResults int `json:"maxResults"`
Total int `json:"total"`
Worklogs []WorklogRecord `json:"worklogs"`
StartAt int `json:"startAt" structs:"startAt"`
MaxResults int `json:"maxResults" structs:"maxResults"`
Total int `json:"total" structs:"total"`
Worklogs []WorklogRecord `json:"worklogs" structs:"worklogs"`
}
// WorklogRecord represents one entry of a Worklog
type WorklogRecord struct {
Self string `json:"self"`
Author User `json:"author"`
UpdateAuthor User `json:"updateAuthor"`
Comment string `json:"comment"`
Created Time `json:"created"`
Updated Time `json:"updated"`
Started Time `json:"started"`
TimeSpent string `json:"timeSpent"`
TimeSpentSeconds int `json:"timeSpentSeconds"`
ID string `json:"id"`
IssueID string `json:"issueId"`
Self string `json:"self" structs:"self"`
Author User `json:"author" structs:"author"`
UpdateAuthor User `json:"updateAuthor" structs:"updateAuthor"`
Comment string `json:"comment" structs:"comment"`
Created Time `json:"created" structs:"created"`
Updated Time `json:"updated" structs:"updated"`
Started Time `json:"started" structs:"started"`
TimeSpent string `json:"timeSpent" structs:"timeSpent"`
TimeSpentSeconds int `json:"timeSpentSeconds" structs:"timeSpentSeconds"`
ID string `json:"id" structs:"id"`
IssueID string `json:"issueId" structs:"issueId"`
}
// Subtasks represents all issues of a parent issue.
type Subtasks struct {
ID string `json:"id"`
Key string `json:"key"`
Self string `json:"self"`
Fields IssueFields `json:"fields"`
ID string `json:"id" structs:"id"`
Key string `json:"key" structs:"key"`
Self string `json:"self" structs:"self"`
Fields IssueFields `json:"fields" structs:"fields"`
}
// IssueLink represents a link between two issues in JIRA.
type IssueLink struct {
ID string `json:"id,omitempty"`
Self string `json:"self,omitempty"`
Type IssueLinkType `json:"type"`
OutwardIssue *Issue `json:"outwardIssue"`
InwardIssue *Issue `json:"inwardIssue"`
Comment *Comment `json:"comment,omitempty"`
ID string `json:"id,omitempty" structs:"id,omitempty"`
Self string `json:"self,omitempty" structs:"self,omitempty"`
Type IssueLinkType `json:"type" structs:"type"`
OutwardIssue *Issue `json:"outwardIssue" structs:"outwardIssue"`
InwardIssue *Issue `json:"inwardIssue" structs:"inwardIssue"`
Comment *Comment `json:"comment,omitempty" structs:"comment,omitempty"`
}
// IssueLinkType represents a type of a link between to issues in JIRA.
// Typical issue link types are "Related to", "Duplicate", "Is blocked by", etc.
type IssueLinkType struct {
ID string `json:"id,omitempty"`
Self string `json:"self,omitempty"`
Name string `json:"name"`
Inward string `json:"inward"`
Outward string `json:"outward"`
ID string `json:"id,omitempty" structs:"id,omitempty"`
Self string `json:"self,omitempty" structs:"self,omitempty"`
Name string `json:"name" structs:"name"`
Inward string `json:"inward" structs:"inward"`
Outward string `json:"outward" structs:"outward"`
}
// Comments represents a list of Comment.
type Comments struct {
Comments []*Comment `json:"comments,omitempty"`
Comments []*Comment `json:"comments,omitempty" structs:"comments,omitempty"`
}
// Comment represents a comment by a person to an issue in JIRA.
type Comment struct {
ID string `json:"id,omitempty"`
Self string `json:"self,omitempty"`
Name string `json:"name,omitempty"`
Author User `json:"author,omitempty"`
Body string `json:"body,omitempty"`
UpdateAuthor User `json:"updateAuthor,omitempty"`
Updated string `json:"updated,omitempty"`
Created string `json:"created,omitempty"`
Visibility CommentVisibility `json:"visibility,omitempty"`
ID string `json:"id,omitempty" structs:"id,omitempty"`
Self string `json:"self,omitempty" structs:"self,omitempty"`
Name string `json:"name,omitempty" structs:name,omitempty"`
Author User `json:"author,omitempty" structs:"author,omitempty"`
Body string `json:"body,omitempty" structs:"body,omitempty"`
UpdateAuthor User `json:"updateAuthor,omitempty" structs:"updateAuthor,omitempty"`
Updated string `json:"updated,omitempty" structs:"updated,omitempty"`
Created string `json:"created,omitempty" structs:"created,omitempty"`
Visibility CommentVisibility `json:"visibility,omitempty" structs:"visibility,omitempty"`
}
// FixVersion represents a software release in which an issue is fixed.
type FixVersion struct {
Archived *bool `json:"archived,omitempty"`
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
ProjectID int `json:"projectId,omitempty"`
ReleaseDate string `json:"releaseDate,omitempty"`
Released *bool `json:"released,omitempty"`
Self string `json:"self,omitempty"`
UserReleaseDate string `json:"userReleaseDate,omitempty"`
Archived *bool `json:"archived,omitempty" structs:"archived,omitempty"`
ID string `json:"id,omitempty" structs:"id,omitempty"`
Name string `json:"name,omitempty" structs:"name,omitempty"`
ProjectID int `json:"projectId,omitempty" structs:"projectId,omitempty"`
ReleaseDate string `json:"releaseDate,omitempty" structs:"releaseDate,omitempty"`
Released *bool `json:"released,omitempty" structs:"released,omitempty"`
Self string `json:"self,omitempty" structs:"self,omitempty"`
UserReleaseDate string `json:"userReleaseDate,omitempty" structs:"userReleaseDate,omitempty"`
}
// CommentVisibility represents he visibility of a comment.
// E.g. Type could be "role" and Value "Administrators"
type CommentVisibility struct {
Type string `json:"type,omitempty"`
Value string `json:"value,omitempty"`
Type string `json:"type,omitempty" structs:"type,omitempty"`
Value string `json:"value,omitempty" structs:"value,omitempty"`
}
// SearchOptions specifies the optional parameters to various List methods that
@ -337,10 +402,10 @@ type SearchOptions struct {
// searchResult is only a small wrapper arround the Search (with JQL) method
// to be able to parse the results
type searchResult struct {
Issues []Issue `json:"issues"`
StartAt int `json:"startAt"`
MaxResults int `json:"maxResults"`
Total int `json:"total"`
Issues []Issue `json:"issues" structs:"issues"`
StartAt int `json:"startAt" structs:"startAt"`
MaxResults int `json:"maxResults" structs:"maxResults"`
Total int `json:"total" structs:"total"`
}
// CustomFields represents custom fields of JIRA

View File

@ -8,6 +8,8 @@ import (
"reflect"
"strings"
"testing"
"github.com/trivago/tgo/tcontainer"
)
func TestIssueService_Get_Success(t *testing.T) {
@ -465,3 +467,128 @@ func TestIssueService_DoTransition(t *testing.T) {
t.Errorf("Got error: %v", err)
}
}
func TestIssueFields_TestMarshalJSON_PopulateUnknownsSuccess(t *testing.T) {
data := `{
"customfield_123":"test",
"description":"example bug report",
"project":{
"self":"http://www.example.com/jira/rest/api/2/project/EX",
"id":"10000",
"key":"EX",
"name":"Example",
"avatarUrls":{
"48x48":"http://www.example.com/jira/secure/projectavatar?size=large&pid=10000",
"24x24":"http://www.example.com/jira/secure/projectavatar?size=small&pid=10000",
"16x16":"http://www.example.com/jira/secure/projectavatar?size=xsmall&pid=10000",
"32x32":"http://www.example.com/jira/secure/projectavatar?size=medium&pid=10000"
},
"projectCategory":{
"self":"http://www.example.com/jira/rest/api/2/projectCategory/10000",
"id":"10000",
"name":"FIRST",
"description":"First Project Category"
}
},
"issuelinks":[
{
"id":"10001",
"type":{
"id":"10000",
"name":"Dependent",
"inward":"depends on",
"outward":"is depended by"
},
"outwardIssue":{
"id":"10004L",
"key":"PRJ-2",
"self":"http://www.example.com/jira/rest/api/2/issue/PRJ-2",
"fields":{
"status":{
"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png",
"name":"Open"
}
}
}
},
{
"id":"10002",
"type":{
"id":"10000",
"name":"Dependent",
"inward":"depends on",
"outward":"is depended by"
},
"inwardIssue":{
"id":"10004",
"key":"PRJ-3",
"self":"http://www.example.com/jira/rest/api/2/issue/PRJ-3",
"fields":{
"status":{
"iconUrl":"http://www.example.com/jira//images/icons/statuses/open.png",
"name":"Open"
}
}
}
}
]
}`
i := new(IssueFields)
err := json.Unmarshal([]byte(data), i)
if err != nil {
t.Errorf("Expected nil error, recieved %s", err)
}
if len(i.Unknowns) != 1 {
t.Errorf("Expected 1 unknown field to be present, recieved %d", len(i.Unknowns))
}
if i.Description != "example bug report" {
t.Errorf("Expected description to be \"%s\", recieved \"%s\"", "example bug report", i.Description)
}
}
func TestIssueFields_MarshalJSON_Success(t *testing.T) {
/*
{
"customfield_123":"test",
"description":"example bug report",
"project":{
"self":"http://www.example.com/jira/rest/api/2/project/EX",
"id":"10000",
"key":"EX"
}
}
*/
i := &IssueFields{
Description: "example bug report",
Unknowns: tcontainer.MarshalMap{
"customfield_123": "test",
},
Project: Project{
Self: "http://www.example.com/jira/rest/api/2/project/EX",
ID: "10000",
Key: "EX",
},
}
bytes, err := json.Marshal(i)
if err != nil {
t.Errorf("Expected nil err, recieved %s", err)
}
recieved := new(IssueFields)
// the order of json might be different. so unmarshal it again and comapre objects
err = json.Unmarshal(bytes, recieved)
if err != nil {
t.Errorf("Expected nil err, recieved %s", err)
}
if !reflect.DeepEqual(i, recieved) {
t.Errorf("Recieved object different from expected")
}
}

View File

@ -2,6 +2,7 @@ package jira
import (
"fmt"
"github.com/trivago/tgo/tcontainer"
"strings"
)
@ -29,14 +30,14 @@ type MetaProject struct {
// expect these for a general way. This will be returning a map.
// Further processing must be done depending on what is required.
type MetaIssueTypes struct {
Self string `json:"expand,omitempty"`
Id string `json:"id,omitempty"`
Description string `json:"description,omitempty"`
IconUrl string `json:"iconurl,omitempty"`
Name string `json:"name,omitempty"`
Subtasks bool `json:"subtask,omitempty"`
Expand string `json:"expand,omitempty"`
Fields map[string]interface{} `json:"fields,omitempty"`
Self string `json:"expand,omitempty"`
Id string `json:"id,omitempty"`
Description string `json:"description,omitempty"`
IconUrl string `json:"iconurl,omitempty"`
Name string `json:"name,omitempty"`
Subtasks bool `json:"subtask,omitempty"`
Expand string `json:"expand,omitempty"`
Fields tcontainer.MarshalMap `json:"fields,omitempty"`
}
// GetCreateMeta makes the api call to get the meta information required to create a ticket
@ -98,24 +99,35 @@ func (p *MetaProject) GetIssueTypeWithName(name string) *MetaIssueTypes {
// }
// the returned map would have "Epic Link" as the key and "customfield_10806" as value.
// This choice has been made so that the it is easier to generate the create api request later.
func (t *MetaIssueTypes) GetMandatoryFields() map[string]string {
func (t *MetaIssueTypes) GetMandatoryFields() (map[string]string, error) {
ret := make(map[string]string)
for key, obj := range t.Fields {
details := obj.(map[string]interface{})
if details["required"] == true {
ret[details["name"].(string)] = key
for key, _ := range t.Fields {
required, err := t.Fields.Bool(key + "/required")
if err != nil {
return nil, err
}
if required {
name, err := t.Fields.String(key + "/name")
if err != nil {
return nil, err
}
ret[name] = key
}
}
return nil
return ret, nil
}
// GetAllFields returns a map of all the fields for an IssueType. This includes all required and not required.
// The key of the returned map is what you see in the form and the value is how it is representated in the jira schema.
func (t *MetaIssueTypes) GetAllFields() map[string]string {
func (t *MetaIssueTypes) GetAllFields() (map[string]string, error) {
ret := make(map[string]string)
for key, obj := range t.Fields {
details := obj.(map[string]interface{})
ret[details["name"].(string)] = key
for key, _ := range t.Fields {
name, err := t.Fields.String(key + "/name")
if err != nil {
return nil, err
}
ret[name] = key
}
return ret
return ret, nil
}

View File

@ -377,3 +377,68 @@ func TestIssueService_GetCreateMeta_Success(t *testing.T) {
}
}
func TestMetaIssueTypes_GetMandatoryFields(t *testing.T) {
data := make(map[string]interface{})
data["summary"] = map[string]interface{}{
"required": true,
"name": "Summary",
}
data["components"] = map[string]interface{}{
"required": true,
"name": "Components",
}
data["epicLink"] = map[string]interface{}{
"required": false,
"name": "Epic Link",
}
m := new(MetaIssueTypes)
m.Fields = data
mandatory, err := m.GetMandatoryFields()
if err != nil {
t.Errorf("Expected nil error, recieved %s", err)
}
if len(mandatory) != 2 {
t.Errorf("Expected 2 recieved %d", mandatory)
}
}
func TestMetaIssueTypes_GetAllFields(t *testing.T) {
data := make(map[string]interface{})
data["summary"] = map[string]interface{}{
"required": true,
"name": "Summary",
}
data["components"] = map[string]interface{}{
"required": true,
"name": "Components",
}
data["epicLink"] = map[string]interface{}{
"required": false,
"name": "Epic Link",
}
m := new(MetaIssueTypes)
m.Fields = data
mandatory, err := m.GetAllFields()
if err != nil {
t.Errorf("Expected nil err, recieved %s", err)
}
if len(mandatory) != 3 {
t.Errorf("Expected 3 recieved %d", mandatory)
}
}

View File

@ -13,72 +13,72 @@ type ProjectService struct {
// ProjectList represent a list of Projects
type ProjectList []struct {
Expand string `json:"expand"`
Self string `json:"self"`
ID string `json:"id"`
Key string `json:"key"`
Name string `json:"name"`
AvatarUrls AvatarUrls `json:"avatarUrls"`
ProjectTypeKey string `json:"projectTypeKey"`
ProjectCategory ProjectCategory `json:"projectCategory,omitempty"`
Expand string `json:"expand" structs:"expand"`
Self string `json:"self" structs:"self"`
ID string `json:"id" structs:"id"`
Key string `json:"key" structs:"key"`
Name string `json:"name" structs:"name"`
AvatarUrls AvatarUrls `json:"avatarUrls" structs:"avatarUrls"`
ProjectTypeKey string `json:"projectTypeKey" structs:"projectTypeKey"`
ProjectCategory ProjectCategory `json:"projectCategory,omitempty" structs:"projectsCategory,omitempty"`
}
// ProjectCategory represents a single project category
type ProjectCategory struct {
Self string `json:"self"`
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Self string `json:"self" structs:"self"`
ID string `json:"id" structs:"id"`
Name string `json:"name" structs:"name"`
Description string `json:"description" structs:"description"`
}
// Project represents a JIRA Project.
type Project struct {
Expand string `json:"expand,omitempty"`
Self string `json:"self,omitempty"`
ID string `json:"id,omitempty"`
Key string `json:"key,omitempty"`
Description string `json:"description,omitempty"`
Lead User `json:"lead,omitempty"`
Components []ProjectComponent `json:"components,omitempty"`
IssueTypes []IssueType `json:"issueTypes,omitempty"`
URL string `json:"url,omitempty"`
Email string `json:"email,omitempty"`
AssigneeType string `json:"assigneeType,omitempty"`
Versions []Version `json:"versions,omitempty"`
Name string `json:"name,omitempty"`
Expand string `json:"expand,omitempty" structs:"expand,omitempty"`
Self string `json:"self,omitempty" structs:"self,omitempty"`
ID string `json:"id,omitempty" structs:"id,omitempty"`
Key string `json:"key,omitempty" structs:"key,omitempty"`
Description string `json:"description,omitempty" structs:"description,omitempty"`
Lead User `json:"lead,omitempty" structs:"lead,omitempty"`
Components []ProjectComponent `json:"components,omitempty" structs:"components,omitempty"`
IssueTypes []IssueType `json:"issueTypes,omitempty" structs:"issueTypes,omitempty"`
URL string `json:"url,omitempty" structs:"url,omitempty"`
Email string `json:"email,omitempty" structs:"email,omitempty"`
AssigneeType string `json:"assigneeType,omitempty" structs:"assigneeType,omitempty"`
Versions []Version `json:"versions,omitempty" structs:"versions,omitempty"`
Name string `json:"name,omitempty" structs:"name,omitempty"`
Roles struct {
Developers string `json:"Developers,omitempty"`
} `json:"roles,omitempty"`
AvatarUrls AvatarUrls `json:"avatarUrls,omitempty"`
ProjectCategory ProjectCategory `json:"projectCategory,omitempty"`
Developers string `json:"Developers,omitempty" structs:"Developers,omitempty"`
} `json:"roles,omitempty" structs:"roles,omitempty"`
AvatarUrls AvatarUrls `json:"avatarUrls,omitempty" structs:"avatarUrls,omitempty"`
ProjectCategory ProjectCategory `json:"projectCategory,omitempty" structs:"projectCategory,omitempty"`
}
// Version represents a single release version of a project
type Version struct {
Self string `json:"self"`
ID string `json:"id"`
Name string `json:"name"`
Archived bool `json:"archived"`
Released bool `json:"released"`
ReleaseDate string `json:"releaseDate"`
UserReleaseDate string `json:"userReleaseDate"`
ProjectID int `json:"projectId"` // Unlike other IDs, this is returned as a number
Self string `json:"self" structs:"self"`
ID string `json:"id" structs:"id"`
Name string `json:"name" structs:"name"`
Archived bool `json:"archived" structs:"archived"`
Released bool `json:"released" structs:"released"`
ReleaseDate string `json:"releaseDate" structs:"releaseDate"`
UserReleaseDate string `json:"userReleaseDate" structs:"userReleaseDate"`
ProjectID int `json:"projectId" structs:"projectId"` // Unlike other IDs, this is returned as a number
}
// ProjectComponent represents a single component of a project
type ProjectComponent struct {
Self string `json:"self"`
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Lead User `json:"lead"`
AssigneeType string `json:"assigneeType"`
Assignee User `json:"assignee"`
RealAssigneeType string `json:"realAssigneeType"`
RealAssignee User `json:"realAssignee"`
IsAssigneeTypeValid bool `json:"isAssigneeTypeValid"`
Project string `json:"project"`
ProjectID int `json:"projectId"`
Self string `json:"self" structs:"self"`
ID string `json:"id" structs:"id"`
Name string `json:"name" structs:"name"`
Description string `json:"description" structs:"description"`
Lead User `json:"lead" structs:"lead"`
AssigneeType string `json:"assigneeType" structs:"assigneeType"`
Assignee User `json:"assignee" structs:"assignee"`
RealAssigneeType string `json:"realAssigneeType" structs:"realAssigneeType"`
RealAssignee User `json:"realAssignee" structs:"realAssignee"`
IsAssigneeTypeValid bool `json:"isAssigneeTypeValid" structs:"isAssigneeTypeValid"`
Project string `json:"project" structs:"project"`
ProjectID int `json:"projectId" structs:"projectId"`
}
// GetList gets all projects form JIRA