diff --git a/fixture/GET/macro.json b/fixture/GET/macro.json new file mode 100644 index 00000000..fe6df285 --- /dev/null +++ b/fixture/GET/macro.json @@ -0,0 +1,39 @@ +{ + "macro":{ + "actions": [ + { + "field": "status", + "value": "solved" + }, + { + "field": "priority", + "value": "normal" + }, + { + "field": "type", + "value": "incident" + }, + { + "field": "assignee_id", + "value": "current_user" + }, + { + "field": "group_id", + "value": "current_groups" + }, + { + "field": "comment_value", + "value": "Thanks for your request. This issue you reported is a known issue. For more information, please visit our forums. " + } + ], + "active": true, + "created_at": "2019-09-16T02:17:38Z", + "description": null, + "id": 360111062754, + "position": 9999, + "restriction": null, + "title": "Close and redirect to topics", + "updated_at": "2019-09-16T02:17:38Z", + "url": "https://subdomain.zendesk.com/api/v2/macros/360111062754.json" + } +} diff --git a/fixture/GET/macros.json b/fixture/GET/macros.json new file mode 100644 index 00000000..a3c55573 --- /dev/null +++ b/fixture/GET/macros.json @@ -0,0 +1,24 @@ +{ + "count": 2, + "macros": [ + { + "actions": [], + "active": true, + "description": "Sets the ticket status to `solved`", + "id": 25, + "position": 42, + "restriction": {}, + "title": "Close and Save" + }, + { + "actions": [], + "active": false, + "description": "Adds a `priority` tag to the ticket", + "id": 26, + "restriction": {}, + "title": "Assign priority tag" + } + ], + "next_page": null, + "previous_page": null +} diff --git a/fixture/POST/macro.json b/fixture/POST/macro.json new file mode 100644 index 00000000..26adcfac --- /dev/null +++ b/fixture/POST/macro.json @@ -0,0 +1,13 @@ +{ + "macro": { + "actions": [ + { + "field": "status", + "value": "solved" + } + ], + "id": 4, + "restriction": {}, + "title": "Roger Wilco" + } +} diff --git a/fixture/PUT/macro.json b/fixture/PUT/macro.json new file mode 100644 index 00000000..6a93bb8a --- /dev/null +++ b/fixture/PUT/macro.json @@ -0,0 +1,16 @@ +{ + "macro": { + "actions": [ + { + "field": "status", + "value": "solved" + } + ], + "active": true, + "description": "Sets the ticket status to `solved`", + "id": 2, + "position": 42, + "restriction": {}, + "title": "Close and Save" + } +} diff --git a/zendesk/api.go b/zendesk/api.go index 40c92a1a..61728c50 100644 --- a/zendesk/api.go +++ b/zendesk/api.go @@ -11,6 +11,7 @@ type API interface { DynamicContentAPI GroupAPI LocaleAPI + MacroAPI TicketAPI TicketFieldAPI TicketFormAPI diff --git a/zendesk/macro.go b/zendesk/macro.go new file mode 100644 index 00000000..89822556 --- /dev/null +++ b/zendesk/macro.go @@ -0,0 +1,165 @@ +package zendesk + +import ( + "context" + "encoding/json" + "fmt" + "time" +) + +// Macro is information about zendesk macro +type Macro struct { + Actions []MacroAction `json:"actions"` + Active bool `json:"active"` + CreatedAt time.Time `json:"created_at,omitempty"` + Description interface{} `json:"description"` + ID int64 `json:"id,omitempty"` + Position int `json:"position,omitempty"` + Restriction interface{} `json:"restriction"` + Title string `json:"title"` + UpdatedAt time.Time `json:"updated_at,omitempty"` + URL string `json:"url,omitempty"` +} + +// MacroAction is definition of what the macro does to the ticket +// +// ref: https://develop.zendesk.com/hc/en-us/articles/360056760874-Support-API-Actions-reference +type MacroAction struct { + Field string `json:"field"` + Value string `json:"value"` +} + +// MacroListOptions is parameters used of GetMacros +type MacroListOptions struct { + Access string `json:"access"` + Active string `json:"active"` + Category int `json:"category"` + GroupID int `json:"group_id"` + Include string `json:"include"` + OnlyViewable bool `json:"only_viewable"` + + PageOptions + + // SortBy can take "created_at", "updated_at", "usage_1h", "usage_24h", + // "usage_7d", "usage_30d", "alphabetical" + SortBy string `url:"sort_by,omitempty"` + + // SortOrder can take "asc" or "desc" + SortOrder string `url:"sort_order,omitempty"` +} + +// MacroAPI an interface containing all macro related methods +type MacroAPI interface { + GetMacros(ctx context.Context, opts *MacroListOptions) ([]Macro, Page, error) + GetMacro(ctx context.Context, macroID int64) (Macro, error) + CreateMacro(ctx context.Context, macro Macro) (Macro, error) + UpdateMacro(ctx context.Context, macroID int64, macro Macro) (Macro, error) + DeleteMacro(ctx context.Context, macroID int64) error +} + +// GetMacros get macro list +// +// ref: https://developer.zendesk.com/rest_api/docs/support/macros#list-macros +func (z *Client) GetMacros(ctx context.Context, opts *MacroListOptions) ([]Macro, Page, error) { + var data struct { + Macros []Macro `json:"macros"` + Page + } + + tmp := opts + if tmp == nil { + tmp = &MacroListOptions{} + } + + u, err := addOptions("/macros.json", tmp) + if err != nil { + return nil, Page{}, err + } + + body, err := z.get(ctx, u) + if err != nil { + return nil, Page{}, err + } + + err = json.Unmarshal(body, &data) + if err != nil { + return nil, Page{}, err + } + return data.Macros, data.Page, nil +} + +// GetMacro gets a specified macro +// +// ref: https://developer.zendesk.com/rest_api/docs/support/macros#show-macro +func (z *Client) GetMacro(ctx context.Context, macroID int64) (Macro, error) { + var result struct { + Macro Macro `json:"macro"` + } + + body, err := z.get(ctx, fmt.Sprintf("/macros/%d.json", macroID)) + if err != nil { + return Macro{}, err + } + + err = json.Unmarshal(body, &result) + if err != nil { + return Macro{}, err + } + + return result.Macro, err +} + +// CreateMacro create a new macro +// +// ref: https://developer.zendesk.com/rest_api/docs/support/macros#create-macro +func (z *Client) CreateMacro(ctx context.Context, macro Macro) (Macro, error) { + var data, result struct { + Macro Macro `json:"macro"` + } + data.Macro = macro + + body, err := z.post(ctx, "/macros.json", data) + if err != nil { + return Macro{}, err + } + + err = json.Unmarshal(body, &result) + if err != nil { + return Macro{}, err + } + return result.Macro, nil +} + +// UpdateMacro update an existing macro +// ref: https://developer.zendesk.com/rest_api/docs/support/macros#update-macro +func (z *Client) UpdateMacro(ctx context.Context, macroID int64, macro Macro) (Macro, error) { + var data, result struct { + Macro Macro `json:"macro"` + } + data.Macro = macro + + path := fmt.Sprintf("/macros/%d.json", macroID) + body, err := z.put(ctx, path, data) + if err != nil { + return Macro{}, err + } + + err = json.Unmarshal(body, &result) + if err != nil { + return Macro{}, err + } + + return result.Macro, nil +} + +// DeleteMacro deletes the specified macro +// ref: https://developer.zendesk.com/rest_api/docs/support/macros#delete-macro +func (z *Client) DeleteMacro(ctx context.Context, macroID int64) error { + err := z.delete(ctx, fmt.Sprintf("/macros/%d.json", macroID)) + + if err != nil { + return err + } + + return nil +} diff --git a/zendesk/macro_test.go b/zendesk/macro_test.go new file mode 100644 index 00000000..ca3477bf --- /dev/null +++ b/zendesk/macro_test.go @@ -0,0 +1,108 @@ +package zendesk + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestGetMacros(t *testing.T) { + mockAPI := newMockAPI(http.MethodGet, "macros.json") + client := newTestClient(mockAPI) + defer mockAPI.Close() + + macros, _, err := client.GetMacros(ctx, &MacroListOptions{ + PageOptions: PageOptions{ + Page: 1, + PerPage: 10, + }, + SortBy: "id", + SortOrder: "asc", + }) + if err != nil { + t.Fatalf("Failed to get macros: %s", err) + } + + expectedLength := 2 + if len(macros) != expectedLength { + t.Fatalf("Returned macros does not have the expected length %d. Macros length is %d", expectedLength, len(macros)) + } +} + +func TestGetMacro(t *testing.T) { + mockAPI := newMockAPI(http.MethodGet, "macro.json") + client := newTestClient(mockAPI) + defer mockAPI.Close() + + macro, err := client.GetMacro(ctx, 2) + if err != nil { + t.Fatalf("Failed to get macro: %s", err) + } + + expectedID := int64(360111062754) + if macro.ID != expectedID { + t.Fatalf("Returned macro does not have the expected ID %d. Macro id is %d", expectedID, macro.ID) + } + +} + +func TestCreateMacro(t *testing.T) { + mockAPI := newMockAPIWithStatus(http.MethodPost, "macro.json", http.StatusCreated) + client := newTestClient(mockAPI) + defer mockAPI.Close() + + macro, err := client.CreateMacro(ctx, Macro{ + Title: "nyanyanyanya", + // Comment: MacroComment{ + // Body: "(●ↀ ω ↀ )", + // }, + }) + if err != nil { + t.Fatalf("Failed to create macro: %s", err) + } + + expectedID := int64(4) + if macro.ID != expectedID { + t.Fatalf("Returned macro does not have the expected ID %d. Macro id is %d", expectedID, macro.ID) + } +} + +func TestUpdateMacro(t *testing.T) { + mockAPI := newMockAPIWithStatus(http.MethodPut, "macro.json", http.StatusOK) + client := newTestClient(mockAPI) + defer mockAPI.Close() + + macro, err := client.UpdateMacro(ctx, 2, Macro{}) + if err != nil { + t.Fatalf("Failed to update macro: %s", err) + } + + expectedID := int64(2) + if macro.ID != expectedID { + t.Fatalf("Returned macro does not have the expected ID %d. Macro id is %d", expectedID, macro.ID) + } +} + +func TestUpdateMacroFailure(t *testing.T) { + mockAPI := newMockAPIWithStatus(http.MethodPut, "macro.json", http.StatusInternalServerError) + client := newTestClient(mockAPI) + defer mockAPI.Close() + + _, err := client.UpdateMacro(ctx, 2, Macro{}) + if err == nil { + t.Fatal("Client did not return error when api failed") + } +} + +func TestDeleteMacro(t *testing.T) { + mockAPI := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNoContent) + w.Write(nil) + })) + + c := newTestClient(mockAPI) + err := c.DeleteMacro(ctx, 437) + if err != nil { + t.Fatalf("Failed to delete macro field: %s", err) + } +} diff --git a/zendesk/mock/client.go b/zendesk/mock/client.go index 665d6fb0..89be69cb 100644 --- a/zendesk/mock/client.go +++ b/zendesk/mock/client.go @@ -6,9 +6,10 @@ package mock import ( context "context" + reflect "reflect" + gomock "github.com/golang/mock/gomock" zendesk "github.com/nukosuke/go-zendesk/zendesk" - reflect "reflect" ) // Client is a mock of API interface. @@ -259,6 +260,21 @@ func (mr *ClientMockRecorder) CreateTrigger(arg0, arg1 interface{}) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateTrigger", reflect.TypeOf((*Client)(nil).CreateTrigger), arg0, arg1) } +// CreateMacro mocks base method. +func (m *Client) CreateMacro(arg0 context.Context, arg1 zendesk.Macro) (zendesk.Macro, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateMacro", arg0, arg1) + ret0, _ := ret[0].(zendesk.Macro) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateMacro indicates an expected call of CreateMacro. +func (mr *ClientMockRecorder) CreateMacro(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateMacro", reflect.TypeOf((*Client)(nil).CreateMacro), arg0, arg1) +} + // CreateUser mocks base method. func (m *Client) CreateUser(arg0 context.Context, arg1 zendesk.User) (zendesk.User, error) { m.ctrl.T.Helper() @@ -414,6 +430,19 @@ func (mr *ClientMockRecorder) DeleteTrigger(arg0, arg1 interface{}) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteTrigger", reflect.TypeOf((*Client)(nil).DeleteTrigger), arg0, arg1) } +func (m *Client) DeleteMacro(arg0 context.Context, arg1 int64) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteMacro", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteMacro indicates an expected call of DeleteMacro. +func (mr *ClientMockRecorder) DeleteMacro(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteMacro", reflect.TypeOf((*Client)(nil).DeleteMacro), arg0, arg1) +} + // DeleteUpload mocks base method. func (m *Client) DeleteUpload(arg0 context.Context, arg1 string) error { m.ctrl.T.Helper() @@ -860,6 +889,37 @@ func (mr *ClientMockRecorder) GetTriggers(arg0, arg1 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTriggers", reflect.TypeOf((*Client)(nil).GetTriggers), arg0, arg1) } +// GetMacro mocks base method. +func (m *Client) GetMacro(arg0 context.Context, arg1 int64) (zendesk.Macro, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMacro", arg0, arg1) + ret0, _ := ret[0].(zendesk.Macro) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMacro indicates an expected call of GetMacro. +func (mr *ClientMockRecorder) GetMacro(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMacro", reflect.TypeOf((*Client)(nil).GetMacro), arg0, arg1) +} + +// GetMacros mocks base method. +func (m *Client) GetMacros(arg0 context.Context, arg1 *zendesk.MacroListOptions) ([]zendesk.Macro, zendesk.Page, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMacros", arg0, arg1) + ret0, _ := ret[0].([]zendesk.Macro) + ret1, _ := ret[1].(zendesk.Page) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetMacros indicates an expected call of GetMacros. +func (mr *ClientMockRecorder) GetMacros(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMacros", reflect.TypeOf((*Client)(nil).GetMacros), arg0, arg1) +} + // GetUser mocks base method. func (m *Client) GetUser(arg0 context.Context, arg1 int64) (zendesk.User, error) { m.ctrl.T.Helper() @@ -1119,6 +1179,21 @@ func (mr *ClientMockRecorder) UpdateTrigger(arg0, arg1, arg2 interface{}) *gomoc return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateTrigger", reflect.TypeOf((*Client)(nil).UpdateTrigger), arg0, arg1, arg2) } +// UpdateMacro mocks base method. +func (m *Client) UpdateMacro(arg0 context.Context, arg1 int64, arg2 zendesk.Macro) (zendesk.Macro, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateMacro", arg0, arg1, arg2) + ret0, _ := ret[0].(zendesk.Macro) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateMacro indicates an expected call of UpdateMacro. +func (mr *ClientMockRecorder) UpdateMacro(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateMacro", reflect.TypeOf((*Client)(nil).UpdateMacro), arg0, arg1, arg2) +} + // UpdateUser mocks base method. func (m *Client) UpdateUser(arg0 context.Context, arg1 int64, arg2 zendesk.User) (zendesk.User, error) { m.ctrl.T.Helper()