From 1c39fc73c4bf5f17097085732900c254b1572369 Mon Sep 17 00:00:00 2001 From: mehanizm Date: Mon, 6 Apr 2020 13:35:11 +0300 Subject: [PATCH 1/3] feat: add RemoveIDCustomField method --- card.go | 14 +++++++++++++- card_test.go | 15 ++++++++++++++- testdata/customFields/custom-fields-remove.json | 7 +++++++ 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 testdata/customFields/custom-fields-remove.json diff --git a/card.go b/card.go index a6fa0c8..c3caa0e 100644 --- a/card.go +++ b/card.go @@ -91,7 +91,6 @@ func (c *Card) CreatedAt() time.Time { return t } - // CustomFields returns the card's custom fields. func (c *Card) CustomFields(boardCustomFields []*CustomField) map[string]interface{} { @@ -144,6 +143,19 @@ func (c *Card) CustomFields(boardCustomFields []*CustomField) map[string]interfa return *cfm } +// RemoveIDCustomField removes a custom field by ID from card +func (c *Card) RemoveIDCustomField(customFieldID string, customFieldItem *CustomFieldItem) error { + path := fmt.Sprintf("cards/%s/customField/%s/item", c.ID, customFieldID) + return c.client.Put( + path, + Arguments{ + "idValue": "", + "value": "", + }, + customFieldItem, + ) +} + // MoveToList moves a card to a list given by listID. func (c *Card) MoveToList(listID string, args Arguments) error { path := fmt.Sprintf("cards/%s", c.ID) diff --git a/card_test.go b/card_test.go index b8a962f..45a4e8a 100644 --- a/card_test.go +++ b/card_test.go @@ -79,6 +79,19 @@ func TestCardsCustomFields(t *testing.T) { } } +func TestRemoveIDCustomField(t *testing.T) { + card := testCard(t) + card.client.BaseURL = mockResponse("customFields", "custom-fields-remove.json").URL + var customFieldItem CustomFieldItem + err := card.RemoveIDCustomField("customFieldDummyID", &customFieldItem) + if err != nil { + t.Fatal(err) + } + if customFieldItem.Value.val != nil { + t.Fatal("Custom field value should be nil") + } +} + func TestBoardContainsCopyOfCard(t *testing.T) { board := testBoard(t) board.client.BaseURL = mockResponse("actions", "board-actions-copyCard.json").URL @@ -232,7 +245,7 @@ func TestAddURLAttachmentToCard(t *testing.T) { c.client.BaseURL = mockResponse("cards", "url-attachments.json").URL attachment := Attachment{ Name: "Test", - URL: "https://github.com/test", + URL: "https://github.com/test", } err := c.AddURLAttachment(&attachment) if err != nil { diff --git a/testdata/customFields/custom-fields-remove.json b/testdata/customFields/custom-fields-remove.json new file mode 100644 index 0000000..326ff91 --- /dev/null +++ b/testdata/customFields/custom-fields-remove.json @@ -0,0 +1,7 @@ +{ + "id": "dummyID", + "value": null, + "idCustomField": "customFieldDummyID", + "idModel": "dummyIDModel", + "modelType": "card" +} \ No newline at end of file From ae88d81f135e0d42bbdffe2e10a3ee06560c0b18 Mon Sep 17 00:00:00 2001 From: mehanizm Date: Mon, 6 Apr 2020 21:03:48 +0300 Subject: [PATCH 2/3] impr: change custom fields value struct To unmarshal json correctly change the custom fields struct. See TestRemoveIDCustomField for marshalling details. --- card.go | 4 +- card_test.go | 12 ++++-- custom-fields.go | 99 ++++-------------------------------------------- 3 files changed, 18 insertions(+), 97 deletions(-) diff --git a/card.go b/card.go index c3caa0e..7f0885b 100644 --- a/card.go +++ b/card.go @@ -123,8 +123,8 @@ func (c *Card) CustomFields(boardCustomFields []*CustomField) map[string]interfa for _, cf := range c.CustomFieldItems { if name, ok := bcfNames[cf.IDCustomField]; ok { - if cf.Value.Get() != nil { - (*cfm)[name] = cf.Value.Get() + if cf.Value != nil { + (*cfm)[name] = cf.Value } else { // Dropbox // create 2nd level map when not available yet map2, ok := bcfOptionsMap[cf.IDCustomField] diff --git a/card_test.go b/card_test.go index 45a4e8a..690812b 100644 --- a/card_test.go +++ b/card_test.go @@ -82,13 +82,17 @@ func TestCardsCustomFields(t *testing.T) { func TestRemoveIDCustomField(t *testing.T) { card := testCard(t) card.client.BaseURL = mockResponse("customFields", "custom-fields-remove.json").URL - var customFieldItem CustomFieldItem - err := card.RemoveIDCustomField("customFieldDummyID", &customFieldItem) + customFieldItem := &CustomFieldItem{ + Value: &CustomFieldValue{ + Text: "Text that should be deleted", + }, + } + err := card.RemoveIDCustomField("customFieldDummyID", customFieldItem) if err != nil { t.Fatal(err) } - if customFieldItem.Value.val != nil { - t.Fatal("Custom field value should be nil") + if customFieldItem.Value != nil { + t.Fatalf("Custom field value should be nil, but %+v", customFieldItem) } } diff --git a/custom-fields.go b/custom-fields.go index e4971ff..a50a78e 100644 --- a/custom-fields.go +++ b/custom-fields.go @@ -1,110 +1,27 @@ package trello import ( - "database/sql/driver" - "encoding/json" "fmt" - "strconv" - "time" ) // CustomFieldItem represents the custom field items of Trello a trello card. type CustomFieldItem struct { - ID string `json:"id,omitempty"` - Value CustomFieldValue `json:"value,omitempty"` - IDValue string `json:"idValue,omitempty"` - IDCustomField string `json:"idCustomField,omitempty"` - IDModel string `json:"idModel,omitempty"` - IDModelType string `json:"modelType,omitempty"` + ID string `json:"id,omitempty"` + Value *CustomFieldValue `json:"value,omitempty"` + IDValue string `json:"idValue,omitempty"` + IDCustomField string `json:"idCustomField,omitempty"` + IDModel string `json:"idModel,omitempty"` + IDModelType string `json:"modelType,omitempty"` } + +// CustomFieldValue represents value of the custom field type CustomFieldValue struct { - val interface{} -} -type cfval struct { Text string `json:"text,omitempty"` Number string `json:"number,omitempty"` Date string `json:"date,omitempty"` Checked string `json:"checked,omitempty"` } -func NewCustomFieldValue(val interface{}) CustomFieldValue { - return CustomFieldValue{val: val} -} - -const timeFmt = "2006-01-02T15:04:05Z" - -func (v CustomFieldValue) Get() interface{} { - return v.val -} -func (v CustomFieldValue) String() string { - return fmt.Sprintf("%s", v.val) -} -func (v CustomFieldValue) MarshalJSON() ([]byte, error) { - val := v.val - - switchVal: - switch v := val.(type) { - case driver.Valuer: - var err error - val, err = v.Value() - if err != nil { - return nil, err - } - goto switchVal - case string: - return json.Marshal(cfval{Text: v}) - case int, int64: - return json.Marshal(cfval{Number: fmt.Sprintf("%d", v)}) - case float64: - return json.Marshal(cfval{Number: fmt.Sprintf("%f", v)}) - case bool: - if v { - return json.Marshal(cfval{Checked: "true"}) - } else { - return json.Marshal(cfval{Checked: "false"}) - } - case time.Time: - return json.Marshal(cfval{Date: v.Format(timeFmt)}) - default: - return nil, fmt.Errorf("unsupported type") - } -} -func (v *CustomFieldValue) UnmarshalJSON(b []byte) error { - cfval := cfval{} - err := json.Unmarshal(b, &cfval) - if err != nil { - return err - } - if cfval.Text != "" { - v.val = cfval.Text - } - if cfval.Date != "" { - v.val, err = time.Parse(timeFmt, cfval.Date) - if err != nil { - return err - } - } - if cfval.Checked != "" { - v.val = cfval.Checked == "true" - } - if cfval.Number != "" { - v.val, err = strconv.Atoi(cfval.Number) - if err != nil { - v.val, err = strconv.ParseFloat(cfval.Number, 64) - if err != nil { - v.val, err = strconv.ParseFloat(cfval.Number, 32) - if err != nil { - v.val, err = strconv.ParseInt(cfval.Number, 10, 64) - if err != nil { - return fmt.Errorf("cannot convert %s to number", cfval.Number) - } - } - } - } - } - return nil -} - // CustomField represents Trello's custom fields: "extra bits of structured data // attached to cards when our users need a bit more than what Trello provides." // https://developers.trello.com/reference/#custom-fields From 79be5c920f0751d16fe03e5ebb447589e63fc019 Mon Sep 17 00:00:00 2001 From: mehanizm Date: Tue, 7 Apr 2020 09:43:12 +0300 Subject: [PATCH 3/3] impr: refactor CustomFields method Rewrite CustomFields method to use more idiomatic go style code. Use new version of the CustomFieldValue struct. Test actualization. --- card.go | 89 +++++++++++++++++++++++++++++++--------------------- card_test.go | 12 ++++--- 2 files changed, 61 insertions(+), 40 deletions(-) diff --git a/card.go b/card.go index 7f0885b..5e9ed54 100644 --- a/card.go +++ b/card.go @@ -82,7 +82,7 @@ type Card struct { // Custom Fields CustomFieldItems []*CustomFieldItem `json:"customFieldItems,omitempty"` - customFieldMap *map[string]interface{} + customFieldMap map[string]*CustomFieldValue } // CreatedAt returns the receiver card's created-at attribute as time.Time. @@ -92,55 +92,72 @@ func (c *Card) CreatedAt() time.Time { } // CustomFields returns the card's custom fields. -func (c *Card) CustomFields(boardCustomFields []*CustomField) map[string]interface{} { +func (c *Card) CustomFields(boardCustomFields []*CustomField) map[string]*CustomFieldValue { - cfm := c.customFieldMap + // if there is no custom fields in card struct → return nil + if c.CustomFieldItems == nil { + return nil + } - if cfm == nil { - cfm = &(map[string]interface{}{}) + // if customFieldMap already exist → return it + if c.customFieldMap != nil { + return c.customFieldMap + } - // bcfNames[CustomFieldItem ID] = Custom Field Name - bcfNames := map[string]string{} + // customFieldsMap[CustomFieldName] = CustomFieldValue + customFieldsMap := make(map[string]*CustomFieldValue) - // bcfOptionsMap[CustomField ID][ID of the option] = Value of the option - bcfOptionsMap := map[string]map[string]interface{}{} + // boardCustomFieldsNames[CustomFieldItem.IDCustomField] = CustomFieldName + boardCustomFieldsNames := make(map[string]string) - for _, bcf := range boardCustomFields { - bcfNames[bcf.ID] = bcf.Name + // boardCustomFieldsOptionsMap[CustomFieldItem.IDCustomField][CustomFieldOptionID] = + // CustomFieldOptionIDValue + boardCustomFieldsOptions := make(map[string]map[string]*CustomFieldValue) - //Options for Dropbox field - for _, cf := range bcf.Options { - // create 2nd level map when not available yet - map2, ok := bcfOptionsMap[cf.IDCustomField] - if !ok { - map2 = map[string]interface{}{} - bcfOptionsMap[bcf.ID] = map2 - } + for _, boardCustomField := range boardCustomFields { + boardCustomFieldsNames[boardCustomField.ID] = boardCustomField.Name - bcfOptionsMap[bcf.ID][cf.ID] = cf.Value.Text + // collect options for the custom field with dropdown + for _, customField := range boardCustomField.Options { + // make map if there is no one + _, ok := boardCustomFieldsOptions[customField.IDCustomField] + if !ok { + boardCustomFieldsOptions[boardCustomField.ID] = + make(map[string]*CustomFieldValue) } + + boardCustomFieldsOptions[boardCustomField.ID][customField.ID] = + &CustomFieldValue{ + Text: customField.Value.Text, + } } + } - for _, cf := range c.CustomFieldItems { - if name, ok := bcfNames[cf.IDCustomField]; ok { - if cf.Value != nil { - (*cfm)[name] = cf.Value - } else { // Dropbox - // create 2nd level map when not available yet - map2, ok := bcfOptionsMap[cf.IDCustomField] - if !ok { - continue - } - value, ok := map2[cf.IDValue] - - if ok { - (*cfm)[name] = value - } + for _, customField := range c.CustomFieldItems { + // if there is a name in board custom fields names + if customFieldName, customFieldNameExist := + boardCustomFieldsNames[customField.IDCustomField]; customFieldNameExist { + + // if value not nil that it is not a dropdown custom field + if customField.Value != nil { + customFieldsMap[customFieldName] = customField.Value + continue + } + + // else try to find option name + if optionsForCustomField, optionExist := + boardCustomFieldsOptions[customField.IDCustomField]; optionExist { + if optionName, optionNameExist := + optionsForCustomField[customField.IDValue]; optionNameExist { + customFieldsMap[customFieldName] = optionName } } + } } - return *cfm + + return customFieldsMap + } // RemoveIDCustomField removes a custom field by ID from card diff --git a/card_test.go b/card_test.go index 690812b..dd66aac 100644 --- a/card_test.go +++ b/card_test.go @@ -6,6 +6,7 @@ package trello import ( + "reflect" "testing" "time" ) @@ -69,14 +70,17 @@ func TestCardsCustomFields(t *testing.T) { } vf1, ok := fields["Field1"] - if !ok || vf1 != "F1 1st opt" { - t.Errorf("Expected Field1 to be 'F1 1st opt' but it was %v", vf1) + expected1 := &CustomFieldValue{Text: "F1 1st opt"} + if !ok || !reflect.DeepEqual(vf1, expected1) { + t.Errorf("\nExpected Field1:\n%#v\nbut it was:\n%#v", vf1, expected1) } vf2, ok := fields["Field2"] - if !ok || vf2 != "F2 2nd opt" { - t.Errorf("Expected Field1 to be 'F2 2nd opt' but it was %v", vf2) + expected2 := &CustomFieldValue{Text: "F2 2nd opt"} + if !ok || !reflect.DeepEqual(vf2.Text, expected2.Text) { + t.Errorf("\nExpected Field2:\n%#v\nbut it was:\n%#v", vf2, expected2) } + } func TestRemoveIDCustomField(t *testing.T) {