diff --git a/zendesk/client/macro.go b/zendesk/client/macro.go index 207957a2..cf253378 100644 --- a/zendesk/client/macro.go +++ b/zendesk/client/macro.go @@ -17,6 +17,7 @@ type MacroAPI interface { DeleteMacro(ctx context.Context, id int64) error UpdateMacro(ctx context.Context, id int64, form models.Macro) (models.Macro, error) GetMacro(ctx context.Context, id int64) (models.Macro, error) + UpdateMacroPosition(ctx context.Context, id int64, macro models.MacroPosition) error } // GetMacros fetches macros @@ -95,7 +96,13 @@ func (z *Client) UpdateMacro(ctx context.Context, id int64, form models.Macro) ( Macro models.Macro `json:"macro"` } + z.UpdateMacroPosition(ctx, id, models.MacroPosition{ + ID: id, + Position: form.Position, + }) + data.Macro = form + fmt.Printf("\nUpdating Macro position to %d\n", data.Macro.Position) body, err := z.Put(ctx, fmt.Sprintf("/macros/%d.json", id), data) if err != nil { return models.Macro{}, err @@ -109,6 +116,28 @@ func (z *Client) UpdateMacro(ctx context.Context, id int64, form models.Macro) ( return result.Macro, nil } +func (z *Client) UpdateMacroPosition(ctx context.Context, id int64, macro models.MacroPosition) error { + var data, result struct { + Macros []models.MacroPosition `json:"macros"` + } + + data.Macros = append(data.Macros, macro) + + body, err := z.Put(ctx, "/macros/update_many", data) + if err != nil { + return err + } + + err = json.Unmarshal(body, &result) + if err != nil { + return err + } + + fmt.Sprintf("\nUpdated position to %d for macro %d", macro.Position, macro.ID) + + return nil +} + // DeleteMacro deletes the specified macro // ref: https://developer.zendesk.com/rest_api/docs/support/macro#delete-macro func (z *Client) DeleteMacro(ctx context.Context, id int64) error { diff --git a/zendesk/client/view.go b/zendesk/client/view.go new file mode 100644 index 00000000..5751a55b --- /dev/null +++ b/zendesk/client/view.go @@ -0,0 +1,163 @@ +package client + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/nukosuke/terraform-provider-zendesk/zendesk/models" +) + +type ViewAPI interface { + CreateView(ctx context.Context, view models.View) (models.Macro, error) + DeleteView(ctx context.Context, id int64) error + UpdateView(ctx context.Context, id int64, form models.Macro) (models.Macro, error) + GetView(ctx context.Context, id int64) (models.Macro, error) + UpdateViewPosition(ctx context.Context, id int64, view models.ViewPosition) error +} + +func mapViewToViewCreateOrUpdate(view models.View) models.ViewCreateOrUpdate { + var viewCreateOrUpdate models.ViewCreateOrUpdate + + // Map properties from view to viewCreateOrUpdate + viewCreateOrUpdate.ID = view.ID + viewCreateOrUpdate.Active = view.Active + viewCreateOrUpdate.Description = view.Description + viewCreateOrUpdate.Position = view.Position + viewCreateOrUpdate.Title = view.Title + viewCreateOrUpdate.CreatedAt = view.CreatedAt + viewCreateOrUpdate.UpdatedAt = view.UpdatedAt + viewCreateOrUpdate.All = view.Conditions.All + viewCreateOrUpdate.Any = view.Conditions.Any + viewCreateOrUpdate.URL = view.URL + + // Rename "Execution" to "Output" in ViewCreateOrUpdate + viewCreateOrUpdate.Output.GroupBy = view.Execution.GroupBy + viewCreateOrUpdate.Output.SortBy = view.Execution.SortBy + viewCreateOrUpdate.Output.GroupOrder = view.Execution.GroupOrder + viewCreateOrUpdate.Output.SortOrder = view.Execution.SortOrder + + viewCreateOrUpdate.Restriction = view.Restriction + + var columns []interface{} + for _, col := range view.Execution.Columns { + columns = append(columns, col.ID) + } + viewCreateOrUpdate.Output.Columns = columns + + return viewCreateOrUpdate +} + +func (z *Client) CreateView(ctx context.Context, view models.View) (models.View, error) { + var result struct { + View models.View `json:"view"` + } + var data struct { + View models.ViewCreateOrUpdate `json:"view"` + } + data.View = mapViewToViewCreateOrUpdate(view) + + body, err := z.Post(ctx, "/views.json", data) + + if err != nil { + return models.View{}, err + } + + err = json.Unmarshal(body, &result) + if err != nil { + return models.View{}, err + } + return result.View, nil +} + +func (z *Client) GetView(ctx context.Context, id int64) (models.View, error) { + var result struct { + View models.View `json:"view"` + } + + body, err := z.Get(ctx, fmt.Sprintf("/views/%d.json", id)) + fmt.Println("GET bar") + fmt.Println(string(body)) + + if err != nil { + return models.View{}, err + } + + err = json.Unmarshal(body, &result) + if err != nil { + return models.View{}, err + } + + return result.View, err +} + +// UpdateView updates a field with the specified ticket field +// ref: https://developer.zendesk.com/rest_api/docs/support/user_fields#update-ticket-field +func (z *Client) UpdateView(ctx context.Context, id int64, view models.View) (models.View, error) { + var result struct { + View models.View `json:"view"` + } + var data struct { + View models.ViewCreateOrUpdate `json:"view"` + } + + data.View = mapViewToViewCreateOrUpdate(view) + + jsonData, err := json.Marshal(data) + fmt.Println("Update Processed payload: JSON") + fmt.Println(string(jsonData)) + + z.UpdateViewPosition(ctx, id, models.ViewPosition{ + ID: id, + Position: view.Position, + }) + + body, err := z.Put(ctx, fmt.Sprintf("/views/%d.json", id), data) + + if err != nil { + fmt.Println("Printing Error") + fmt.Println(fmt.Sprintf("%+v\n", err)) + return models.View{}, err + } + + err = json.Unmarshal(body, &result) + if err != nil { + return models.View{}, err + } + + return result.View, err +} + +func (z *Client) UpdateViewPosition(ctx context.Context, id int64, view models.ViewPosition) error { + var data, result struct { + Views []models.ViewPosition `json:"views"` + } + + data.Views = append(data.Views, view) + + body, err := z.Put(ctx, "/views/update_many", data) + if err != nil { + return err + } + + err = json.Unmarshal(body, &result) + if err != nil { + return err + } + + fmt.Sprintf("\nUpdated position to %d for view %d", view.Position, view.ID) + + return nil +} + +// DeleteView deletes the specified ticket field +// ref: https://developer.zendesk.com/rest_api/docs/support/user_fields#Delete-ticket-field +func (z *Client) DeleteView(ctx context.Context, viewID int64) error { + err := z.Delete(ctx, fmt.Sprintf("/views/%d.json", viewID)) + + if err != nil { + return err + } + + return nil +} diff --git a/zendesk/models/macro.go b/zendesk/models/macro.go index 1ce03fd1..435e6334 100644 --- a/zendesk/models/macro.go +++ b/zendesk/models/macro.go @@ -2,6 +2,11 @@ package models import "time" +type MacroPosition struct { + ID int64 `json:"id,omitempty"` + Position int `json:"position,omitempty"` +} + type Macro struct { Actions []MacroAction `json:"actions"` Active bool `json:"active"` diff --git a/zendesk/models/view.go b/zendesk/models/view.go new file mode 100644 index 00000000..630531aa --- /dev/null +++ b/zendesk/models/view.go @@ -0,0 +1,75 @@ +package models + +import "time" + +type ( + // View is struct for group membership payload + // https://developer.zendesk.com/api-reference/ticketing/business-rules/views/ + + ViewCondition struct { + Field string `json:"field"` + Operator string `json:"operator"` + Value string `json:"value"` + } + + Column struct { + ID interface{} `json:"id"` // is string for normal fields & number for custom fields + Title string `json:"title"` + } + + Restriction struct { + IDs []int `json:"ids"` + Type string `json:"type"` + } + + // View has a certain structure in Get & Different structure in + // Put/Post + View struct { + ID int64 `json:"id,omitempty"` + Active bool `json:"active"` + Description string `json:"description"` + Position int `json:"position"` + Title string `json:"title"` + CreatedAt time.Time `json:"created_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` + Restriction *Restriction `json:"restriction"` + Conditions struct { + All []ViewCondition `json:"all"` + Any []ViewCondition `json:"any"` + } `json:"conditions"` + URL string `json:"url,omitempty"` + Execution struct { + Columns []Column `json:"columns"` + GroupBy string `json:"group_by,omitempty"` + SortBy string `json:"sort_by,omitempty"` + GroupOrder string `json:"group_order,omitempty"` + SortOrder string `json:"sort_order,omitempty"` + } `json:"execution"` + } + ViewCreateOrUpdate struct { + ID int64 `json:"id,omitempty"` + Active bool `json:"active"` + Description string `json:"description"` + Position int `json:"position"` + Title string `json:"title"` + CreatedAt time.Time `json:"created_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` + All []ViewCondition `json:"all"` + Any []ViewCondition `json:"any"` + URL string `json:"url,omitempty"` + Restriction *Restriction `json:"restriction"` + + Output struct { + Columns []interface{} `json:"columns"` // number for custom fields, string otherwise + GroupBy string `json:"group_by,omitempty"` + SortBy string `json:"sort_by,omitempty"` + GroupOrder string `json:"group_order,omitempty"` + SortOrder string `json:"sort_order,omitempty"` + } `json:"output"` + } + + ViewPosition struct { + ID int64 `json:"id,omitempty"` + Position int `json:"position,omitempty"` + } +) diff --git a/zendesk/resource_zendesk_macro.go b/zendesk/resource_zendesk_macro.go index 5928e397..b03edc77 100644 --- a/zendesk/resource_zendesk_macro.go +++ b/zendesk/resource_zendesk_macro.go @@ -9,7 +9,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" newClient "github.com/nukosuke/terraform-provider-zendesk/zendesk/client" "github.com/nukosuke/terraform-provider-zendesk/zendesk/models" ) @@ -63,12 +62,10 @@ func resourceZendeskMacro() *schema.Resource { Computed: true, }, "position": { - Description: "The relative position of the user field on a ticket. Note that for accounts with ticket forms, positions are controlled by the different forms.", + Description: "IMPORTANT! in order for this to take affect an update on the resource is necessary, since only that triggers a call to update position", Type: schema.TypeInt, Optional: true, - // positions 0 to 7 are reserved for system fields - ValidateFunc: validation.IntAtLeast(8), - Computed: true, + Computed: true, }, "active": { Description: "Whether this field is available.", diff --git a/zendesk/resource_zendesk_ticket_field.go b/zendesk/resource_zendesk_ticket_field.go index 8b843418..73a89300 100644 --- a/zendesk/resource_zendesk_ticket_field.go +++ b/zendesk/resource_zendesk_ticket_field.go @@ -147,7 +147,6 @@ func resourceZendeskTicketField() *schema.Resource { Required: true, }, "value": { - Description: "Custom field option value.", Type: schema.TypeString, Required: true, }, diff --git a/zendesk/resource_zendesk_view.go b/zendesk/resource_zendesk_view.go index 540de245..2f8729a0 100644 --- a/zendesk/resource_zendesk_view.go +++ b/zendesk/resource_zendesk_view.go @@ -2,15 +2,13 @@ package zendesk import ( "context" - "encoding/json" "fmt" "strconv" - "time" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" newClient "github.com/nukosuke/terraform-provider-zendesk/zendesk/client" + "github.com/nukosuke/terraform-provider-zendesk/zendesk/models" ) // https://developer.zendesk.com/api-reference/ticketing/business-rules/views/ @@ -43,12 +41,10 @@ func resourceZendeskView() *schema.Resource { Computed: true, }, "position": { - Description: "The relative position of the user field on a ticket. Note that for accounts with ticket forms, positions are controlled by the different forms.", + Description: "IMPORTANT! in order for this to take affect an update on the resource is necessary, since only that triggers a call to update position", Type: schema.TypeInt, Optional: true, - // positions 0 to 7 are reserved for system fields - ValidateFunc: validation.IntAtLeast(8), - Computed: true, + Computed: true, }, "active": { Description: "Whether this field is available.", @@ -116,7 +112,7 @@ func resourceZendeskView() *schema.Resource { } // marshalViews encodes the provided user field into the provided resource data -func marshalViews(field View, d identifiableGetterSetter) error { +func marshalViews(field models.View, d identifiableGetterSetter) error { fields := map[string]interface{}{ "url": field.URL, "title": field.Title, @@ -183,8 +179,8 @@ func marshalViews(field View, d identifiableGetterSetter) error { } // unmarshalViews parses the provided ResourceData and returns a user field -func unmarshalViews(d identifiableGetterSetter) (View, error) { - tf := View{} +func unmarshalViews(d identifiableGetterSetter) (models.View, error) { + tf := models.View{} if v := d.Id(); v != "" { id, err := strconv.ParseInt(v, 10, 64) @@ -210,7 +206,7 @@ func unmarshalViews(d identifiableGetterSetter) (View, error) { for _, ids := range v.(*schema.Set).List() { restrictions = append(restrictions, ids.(int)) } - tf.Restriction = &Restriction{} + tf.Restriction = &models.Restriction{} tf.Restriction.IDs = restrictions tf.Restriction.Type = "Group" } else { @@ -221,15 +217,15 @@ func unmarshalViews(d identifiableGetterSetter) (View, error) { } if v, ok := d.GetOk("columns"); ok { columns := v.([]interface{}) - c := []Column{} + c := []models.Column{} for _, col := range columns { var _, isFloat = col.(float64) if isFloat { - c = append(c, Column{ + c = append(c, models.Column{ ID: col.(float64), }) } else { - c = append(c, Column{ + c = append(c, models.Column{ ID: col.(string), }) } @@ -238,13 +234,13 @@ func unmarshalViews(d identifiableGetterSetter) (View, error) { } if v, ok := d.GetOk("all"); ok { allConditions := v.(*schema.Set).List() - conditions := []ViewCondition{} + conditions := []models.ViewCondition{} for _, c := range allConditions { condition, ok := c.(map[string]interface{}) if !ok { return tf, fmt.Errorf("could not parse 'all' conditions for view %v", tf) } - conditions = append(conditions, ViewCondition{ + conditions = append(conditions, models.ViewCondition{ Field: condition["field"].(string), Operator: condition["operator"].(string), Value: condition["value"].(string), @@ -254,13 +250,13 @@ func unmarshalViews(d identifiableGetterSetter) (View, error) { } if v, ok := d.GetOk("any"); ok { anyConditions := v.(*schema.Set).List() - conditions := []ViewCondition{} + conditions := []models.ViewCondition{} for _, c := range anyConditions { condition, ok := c.(map[string]interface{}) if !ok { return tf, fmt.Errorf("could not parse 'any' conditions for view %v", tf) } - conditions = append(conditions, ViewCondition{ + conditions = append(conditions, models.ViewCondition{ Field: condition["field"].(string), Operator: condition["operator"].(string), Value: condition["value"].(string), @@ -298,7 +294,7 @@ func createViews(ctx context.Context, d identifiableGetterSetter, zd *newClient. } // Actual API request - tf, err = CreateView(ctx, zd, tf) + tf, err = zd.CreateView(ctx, tf) if err != nil { return diag.FromErr(err) } @@ -326,7 +322,7 @@ func readViews(ctx context.Context, d identifiableGetterSetter, zd *newClient.Cl return diag.FromErr(err) } - field, err := GetView(ctx, zd, id) + field, err := zd.GetView(ctx, id) if err != nil { return diag.FromErr(err) } @@ -358,7 +354,7 @@ func updateViews(ctx context.Context, d identifiableGetterSetter, zd *newClient. } // Actual API request - tf, err = UpdateView(ctx, zd, id, tf) + tf, err = zd.UpdateView(ctx, id, tf) if err != nil { return diag.FromErr(err) } @@ -371,38 +367,6 @@ func updateViews(ctx context.Context, d identifiableGetterSetter, zd *newClient. return diags } -func mapViewToViewCreateOrUpdate(view View) ViewCreateOrUpdate { - var viewCreateOrUpdate ViewCreateOrUpdate - - // Map properties from view to viewCreateOrUpdate - viewCreateOrUpdate.ID = view.ID - viewCreateOrUpdate.Active = view.Active - viewCreateOrUpdate.Description = view.Description - viewCreateOrUpdate.Position = view.Position - viewCreateOrUpdate.Title = view.Title - viewCreateOrUpdate.CreatedAt = view.CreatedAt - viewCreateOrUpdate.UpdatedAt = view.UpdatedAt - viewCreateOrUpdate.All = view.Conditions.All - viewCreateOrUpdate.Any = view.Conditions.Any - viewCreateOrUpdate.URL = view.URL - - // Rename "Execution" to "Output" in ViewCreateOrUpdate - viewCreateOrUpdate.Output.GroupBy = view.Execution.GroupBy - viewCreateOrUpdate.Output.SortBy = view.Execution.SortBy - viewCreateOrUpdate.Output.GroupOrder = view.Execution.GroupOrder - viewCreateOrUpdate.Output.SortOrder = view.Execution.SortOrder - - viewCreateOrUpdate.Restriction = view.Restriction - - var columns []interface{} - for _, col := range view.Execution.Columns { - columns = append(columns, col.ID) - } - viewCreateOrUpdate.Output.Columns = columns - - return viewCreateOrUpdate -} - func resourceZendeskViewsDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { zd := meta.(*newClient.Client) return deleteViews(ctx, d, zd) @@ -416,7 +380,7 @@ func deleteViews(ctx context.Context, d identifiable, zd *newClient.Client) diag return diag.FromErr(err) } - err = DeleteView(ctx, zd, id) + err = zd.DeleteView(ctx, id) if err != nil { return diag.FromErr(err) } @@ -424,73 +388,6 @@ func deleteViews(ctx context.Context, d identifiable, zd *newClient.Client) diag return diags } -type ( - // View is struct for group membership payload - // https://developer.zendesk.com/api-reference/ticketing/business-rules/views/ - - ViewCondition struct { - Field string `json:"field"` - Operator string `json:"operator"` - Value string `json:"value"` - } - - Column struct { - ID interface{} `json:"id"` // is string for normal fields & number for custom fields - Title string `json:"title"` - } - - Restriction struct { - IDs []int `json:"ids"` - Type string `json:"type"` - } - - // View has a certain structure in Get & Different structure in - // Put/Post - View struct { - ID int64 `json:"id,omitempty"` - Active bool `json:"active"` - Description string `json:"description"` - Position int `json:"position"` - Title string `json:"title"` - CreatedAt time.Time `json:"created_at,omitempty"` - UpdatedAt time.Time `json:"updated_at,omitempty"` - Restriction *Restriction `json:"restriction"` - Conditions struct { - All []ViewCondition `json:"all"` - Any []ViewCondition `json:"any"` - } `json:"conditions"` - URL string `json:"url,omitempty"` - Execution struct { - Columns []Column `json:"columns"` - GroupBy string `json:"group_by,omitempty"` - SortBy string `json:"sort_by,omitempty"` - GroupOrder string `json:"group_order,omitempty"` - SortOrder string `json:"sort_order,omitempty"` - } `json:"execution"` - } - ViewCreateOrUpdate struct { - ID int64 `json:"id,omitempty"` - Active bool `json:"active"` - Description string `json:"description"` - Position int `json:"position"` - Title string `json:"title"` - CreatedAt time.Time `json:"created_at,omitempty"` - UpdatedAt time.Time `json:"updated_at,omitempty"` - All []ViewCondition `json:"all"` - Any []ViewCondition `json:"any"` - URL string `json:"url,omitempty"` - Restriction *Restriction `json:"restriction"` - - Output struct { - Columns []interface{} `json:"columns"` // number for custom fields, string otherwise - GroupBy string `json:"group_by,omitempty"` - SortBy string `json:"sort_by,omitempty"` - GroupOrder string `json:"group_order,omitempty"` - SortOrder string `json:"sort_order,omitempty"` - } `json:"output"` - } -) - func viewConditionSchema(desc string) *schema.Schema { return &schema.Schema{ Description: desc, @@ -517,90 +414,3 @@ func viewConditionSchema(desc string) *schema.Schema { Optional: true, } } - -func CreateView(ctx context.Context, z *newClient.Client, field View) (View, error) { - var result struct { - View View `json:"view"` - } - var data struct { - View ViewCreateOrUpdate `json:"view"` - } - data.View = mapViewToViewCreateOrUpdate(field) - - body, err := z.Post(ctx, "/views.json", data) - - if err != nil { - return View{}, err - } - - err = json.Unmarshal(body, &result) - if err != nil { - return View{}, err - } - return result.View, nil -} - -func GetView(ctx context.Context, z *newClient.Client, viewID int64) (View, error) { - var result struct { - View View `json:"view"` - } - - body, err := z.Get(ctx, fmt.Sprintf("/views/%d.json", viewID)) - fmt.Println("GET bar") - fmt.Println(string(body)) - - if err != nil { - return View{}, err - } - - err = json.Unmarshal(body, &result) - if err != nil { - return View{}, err - } - - return result.View, err -} - -// UpdateView updates a field with the specified ticket field -// ref: https://developer.zendesk.com/rest_api/docs/support/user_fields#update-ticket-field -func UpdateView(ctx context.Context, z *newClient.Client, ticketID int64, field View) (View, error) { - var result struct { - View View `json:"view"` - } - var data struct { - View ViewCreateOrUpdate `json:"view"` - } - - data.View = mapViewToViewCreateOrUpdate(field) - - jsonData, err := json.Marshal(data) - fmt.Println("Update Processed payload: JSON") - fmt.Println(string(jsonData)) - - body, err := z.Put(ctx, fmt.Sprintf("/views/%d.json", ticketID), data) - - if err != nil { - fmt.Println("Printing Error") - fmt.Println(fmt.Sprintf("%+v\n", err)) - return View{}, err - } - - err = json.Unmarshal(body, &result) - if err != nil { - return View{}, err - } - - return result.View, err -} - -// DeleteView deletes the specified ticket field -// ref: https://developer.zendesk.com/rest_api/docs/support/user_fields#Delete-ticket-field -func DeleteView(ctx context.Context, z *newClient.Client, viewID int64) error { - err := z.Delete(ctx, fmt.Sprintf("/views/%d.json", viewID)) - - if err != nil { - return err - } - - return nil -}