Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions internal/cmd/issue/edit/edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ func edit(cmd *cobra.Command, args []string) {
cmdcommon.ValidateCustomFields(edr.CustomFields, configuredCustomFields)
edr.WithCustomFields(configuredCustomFields)
}
edr.ForInstallationType(viper.GetString("installation"))

return client.Edit(params.issueKey, &edr)
}()
Expand Down
19 changes: 14 additions & 5 deletions pkg/jira/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func (cr *CreateRequest) ForProjectType(pt string) {
cr.projectType = pt
}

// ForInstallationType sets jira project type.
// ForInstallationType sets jira installation type for the create request.
func (cr *CreateRequest) ForInstallationType(it string) {
cr.installationType = it
}
Expand Down Expand Up @@ -222,12 +222,12 @@ func (*Client) getRequestData(req *CreateRequest) *createRequest {
}{OriginalEstimate: req.OriginalEstimate}
}

constructCustomFields(req.CustomFields, req.configuredCustomFields, &data)
constructCustomFields(req.CustomFields, req.configuredCustomFields, req.installationType, &data)

return &data
}

func constructCustomFields(fields map[string]string, configuredFields []IssueTypeField, data *createRequest) {
func constructCustomFields(fields map[string]string, configuredFields []IssueTypeField, installationType string, data *createRequest) {
if len(fields) == 0 || len(configuredFields) == 0 {
return
}
Expand All @@ -248,13 +248,20 @@ func constructCustomFields(fields map[string]string, configuredFields []IssueTyp
data.Fields.M.customFields[configured.Key] = customFieldTypeProject{Value: val}
case customFieldFormatArray:
pieces := strings.Split(strings.TrimSpace(val), ",")
if configured.Schema.Items == customFieldFormatOption {
switch configured.Schema.Items {
case customFieldFormatOption:
items := make([]customFieldTypeOption, 0)
for _, p := range pieces {
items = append(items, customFieldTypeOption{Value: p})
}
data.Fields.M.customFields[configured.Key] = items
} else {
case customFieldFormatUser:
items := make([]customFieldTypeUser, 0, len(pieces))
for _, p := range pieces {
items = append(items, newCustomFieldTypeUser(strings.TrimSpace(p), installationType))
}
data.Fields.M.customFields[configured.Key] = items
default:
data.Fields.M.customFields[configured.Key] = pieces
}
case customFieldFormatNumber:
Expand All @@ -265,6 +272,8 @@ func constructCustomFields(fields map[string]string, configuredFields []IssueTyp
} else {
data.Fields.M.customFields[configured.Key] = customFieldTypeNumber(num)
}
case customFieldFormatUser:
data.Fields.M.customFields[configured.Key] = newCustomFieldTypeUser(val, installationType)
default:
data.Fields.M.customFields[configured.Key] = val
}
Expand Down
158 changes: 158 additions & 0 deletions pkg/jira/create_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package jira

import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
Expand Down Expand Up @@ -178,3 +179,160 @@ func TestCreateEpicNextGen(t *testing.T) {
_, err = client.CreateV2(&requestData)
assert.Error(t, &ErrUnexpectedResponse{}, err)
}

func TestCreateWithUserCustomField(t *testing.T) {
expectedBody := `{"update":{},"fields":{"project":{"key":"TEST"},"issuetype":{"name":"Bug"},` +
`"summary":"Test bug","customfield_12574":{"accountId":"5f7e1b2c"}}}`
testServer := createTestServer{code: 201}
server := testServer.serve(t, expectedBody)
defer server.Close()

client := NewClient(Config{Server: server.URL}, WithTimeout(3*time.Second))
requestData := CreateRequest{
Project: "TEST",
IssueType: "Bug",
Summary: "Test bug",
CustomFields: map[string]string{
"pm-owner": "5f7e1b2c",
},
}
requestData.ForInstallationType(InstallationTypeCloud)
requestData.WithCustomFields([]IssueTypeField{
{
Name: "PM owner",
Key: "customfield_12574",
Schema: struct {
DataType string `json:"type"`
Items string `json:"items,omitempty"`
}{DataType: "user"},
},
})

actual, err := client.CreateV2(&requestData)
assert.NoError(t, err)

expected := &CreateResponse{
ID: "10057",
Key: "TEST-3",
}
assert.Equal(t, expected, actual)
}

func TestCreateWithMultiUserCustomField(t *testing.T) {
expectedBody := `{"update":{},"fields":{"project":{"key":"TEST"},"issuetype":{"name":"Bug"},` +
`"summary":"Test bug","customfield_10100":[{"accountId":"user1"},{"accountId":"user2"}]}}`
testServer := createTestServer{code: 201}
server := testServer.serve(t, expectedBody)
defer server.Close()

client := NewClient(Config{Server: server.URL}, WithTimeout(3*time.Second))
requestData := CreateRequest{
Project: "TEST",
IssueType: "Bug",
Summary: "Test bug",
CustomFields: map[string]string{
"reviewers": "user1,user2",
},
}
requestData.ForInstallationType(InstallationTypeCloud)
requestData.WithCustomFields([]IssueTypeField{
{
Name: "Reviewers",
Key: "customfield_10100",
Schema: struct {
DataType string `json:"type"`
Items string `json:"items,omitempty"`
}{DataType: "array", Items: "user"},
},
})

actual, err := client.CreateV2(&requestData)
assert.NoError(t, err)

expected := &CreateResponse{
ID: "10057",
Key: "TEST-3",
}
assert.Equal(t, expected, actual)
}

func TestConstructCustomFieldsUser(t *testing.T) {
data := &createRequest{}

fields := map[string]string{
"pm-owner": "5f7e1b2c",
}
configuredFields := []IssueTypeField{
{
Name: "PM owner",
Key: "customfield_12574",
Schema: struct {
DataType string `json:"type"`
Items string `json:"items,omitempty"`
}{DataType: "user"},
},
}

constructCustomFields(fields, configuredFields, InstallationTypeCloud, data)

result, ok := data.Fields.M.customFields["customfield_12574"]
assert.True(t, ok)

b, err := json.Marshal(result)
assert.NoError(t, err)
assert.JSONEq(t, `{"accountId":"5f7e1b2c"}`, string(b))
}

func TestConstructCustomFieldsMultiUser(t *testing.T) {
data := &createRequest{}

fields := map[string]string{
"reviewers": "user1,user2",
}
configuredFields := []IssueTypeField{
{
Name: "Reviewers",
Key: "customfield_10100",
Schema: struct {
DataType string `json:"type"`
Items string `json:"items,omitempty"`
}{DataType: "array", Items: "user"},
},
}

constructCustomFields(fields, configuredFields, InstallationTypeCloud, data)

result, ok := data.Fields.M.customFields["customfield_10100"]
assert.True(t, ok)

b, err := json.Marshal(result)
assert.NoError(t, err)
assert.JSONEq(t, `[{"accountId":"user1"},{"accountId":"user2"}]`, string(b))
}

func TestConstructCustomFieldsUserLocal(t *testing.T) {
data := &createRequest{}

fields := map[string]string{
"pm-owner": "john.doe",
}
configuredFields := []IssueTypeField{
{
Name: "PM owner",
Key: "customfield_12574",
Schema: struct {
DataType string `json:"type"`
Items string `json:"items,omitempty"`
}{DataType: "user"},
},
}

constructCustomFields(fields, configuredFields, InstallationTypeLocal, data)

result, ok := data.Fields.M.customFields["customfield_12574"]
assert.True(t, ok)

b, err := json.Marshal(result)
assert.NoError(t, err)
assert.JSONEq(t, `{"name":"john.doe"}`, string(b))
}
18 changes: 18 additions & 0 deletions pkg/jira/customfield.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const (
customFieldFormatArray = "array"
customFieldFormatNumber = "number"
customFieldFormatProject = "project"
customFieldFormatUser = "user"
)

type customField map[string]interface{}
Expand Down Expand Up @@ -39,3 +40,20 @@ type customFieldTypeProject struct {
type customFieldTypeProjectSet struct {
Set customFieldTypeProject `json:"set"`
}

type customFieldTypeUser struct {
Name *string `json:"name,omitempty"` // For local (Server/DC) installation.
AccountID *string `json:"accountId,omitempty"` // For cloud installation.
}

type customFieldTypeUserSet struct {
Set customFieldTypeUser `json:"set"`
}

// newCustomFieldTypeUser creates a user field value appropriate for the installation type.
func newCustomFieldTypeUser(val, installationType string) customFieldTypeUser {
if installationType == InstallationTypeLocal {
return customFieldTypeUser{Name: &val}
}
return customFieldTypeUser{AccountID: &val}
}
34 changes: 34 additions & 0 deletions pkg/jira/customfield_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package jira

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestNewCustomFieldTypeUserCloud(t *testing.T) {
accountID := "5f7e1b2c3d4e5f6a7b8c9d0e"
user := newCustomFieldTypeUser(accountID, InstallationTypeCloud)

assert.Nil(t, user.Name)
assert.NotNil(t, user.AccountID)
assert.Equal(t, accountID, *user.AccountID)
}

func TestNewCustomFieldTypeUserLocal(t *testing.T) {
username := "john.doe"
user := newCustomFieldTypeUser(username, InstallationTypeLocal)

assert.NotNil(t, user.Name)
assert.Nil(t, user.AccountID)
assert.Equal(t, username, *user.Name)
}

func TestNewCustomFieldTypeUserDefaultIsCloud(t *testing.T) {
accountID := "5f7e1b2c3d4e5f6a7b8c9d0e"
user := newCustomFieldTypeUser(accountID, "")

assert.Nil(t, user.Name)
assert.NotNil(t, user.AccountID)
assert.Equal(t, accountID, *user.AccountID)
}
23 changes: 19 additions & 4 deletions pkg/jira/edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,19 @@ type EditRequest struct {
SkipNotify bool

configuredCustomFields []IssueTypeField
installationType string
}

// WithCustomFields sets valid custom fields for the issue.
func (er *EditRequest) WithCustomFields(cf []IssueTypeField) {
er.configuredCustomFields = cf
}

// ForInstallationType sets jira installation type for the edit request.
func (er *EditRequest) ForInstallationType(it string) {
er.installationType = it
}

// Edit updates an issue using POST /issue endpoint.
func (c *Client) Edit(key string, req *EditRequest) error {
data := getRequestDataForEdit(req)
Expand Down Expand Up @@ -353,12 +359,12 @@ func getRequestDataForEdit(req *EditRequest) *editRequest {
Update: update,
Fields: fields,
}
constructCustomFieldsForEdit(req.CustomFields, req.configuredCustomFields, &data)
constructCustomFieldsForEdit(req.CustomFields, req.configuredCustomFields, req.installationType, &data)

return &data
}

func constructCustomFieldsForEdit(fields map[string]string, configuredFields []IssueTypeField, data *editRequest) {
func constructCustomFieldsForEdit(fields map[string]string, configuredFields []IssueTypeField, installationType string, data *editRequest) {
if len(fields) == 0 || len(configuredFields) == 0 {
return
}
Expand All @@ -379,7 +385,8 @@ func constructCustomFieldsForEdit(fields map[string]string, configuredFields []I
data.Update.M.customFields[configured.Key] = []customFieldTypeProjectSet{{Set: customFieldTypeProject{Value: val}}}
case customFieldFormatArray:
pieces := strings.Split(strings.TrimSpace(val), ",")
if configured.Schema.Items == customFieldFormatOption {
switch configured.Schema.Items {
case customFieldFormatOption:
items := make([]customFieldTypeOptionAddRemove, 0)
for _, p := range pieces {
if strings.HasPrefix(p, separatorMinus) {
Expand All @@ -389,7 +396,13 @@ func constructCustomFieldsForEdit(fields map[string]string, configuredFields []I
}
}
data.Update.M.customFields[configured.Key] = items
} else {
case customFieldFormatUser:
items := make([]customFieldTypeUserSet, 0, len(pieces))
for _, p := range pieces {
items = append(items, customFieldTypeUserSet{Set: newCustomFieldTypeUser(strings.TrimSpace(p), installationType)})
}
data.Update.M.customFields[configured.Key] = items
default:
data.Update.M.customFields[configured.Key] = pieces
}
case customFieldFormatNumber:
Expand All @@ -400,6 +413,8 @@ func constructCustomFieldsForEdit(fields map[string]string, configuredFields []I
} else {
data.Update.M.customFields[configured.Key] = []customFieldTypeNumberSet{{Set: customFieldTypeNumber(num)}}
}
case customFieldFormatUser:
data.Update.M.customFields[configured.Key] = []customFieldTypeUserSet{{Set: newCustomFieldTypeUser(val, installationType)}}
default:
data.Update.M.customFields[configured.Key] = []customFieldTypeStringSet{{Set: val}}
}
Expand Down
Loading