Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Swap targets for webhooks in Zendesk #89

Merged
merged 6 commits into from
Nov 4, 2022
Merged
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
Binary file modified mailroom_test.dump
Binary file not shown.
50 changes: 28 additions & 22 deletions services/tickets/zendesk/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,39 +98,45 @@ func NewRESTClient(httpClient *http.Client, httpRetries *httpx.RetryConfig, subd
return &RESTClient{baseClient: newBaseClient(httpClient, httpRetries, subdomain, token)}
}

// Target see https://developer.zendesk.com/rest_api/docs/support/targets
type Target struct {
ID int64 `json:"id"`
Title string `json:"title"`
Type string `json:"type"`
TargetURL string `json:"target_url"`
Method string `json:"method"`
Username string `json:"username"`
Password string `json:"password"`
ContentType string `json:"content_type"`
}

// CreateTarget see https://developer.zendesk.com/rest_api/docs/support/targets#create-target
func (c *RESTClient) CreateTarget(target *Target) (*Target, *httpx.Trace, error) {
type Webhook struct {
ID string `json:"id"`
Authentication struct {
AddPosition string `json:"add_position"`
Data struct {
Password string `json:"password"`
Username string `json:"username"`
} `json:"data"`
Type string `json:"type"`
} `json:"authentication"`
Endpoint string `json:"endpoint"`
HttpMethod string `json:"http_method"`
Name string `json:"name"`
RequestFormat string `json:"request_format"`
Status string `json:"status"`
Subscriptions []string `json:"subscriptions"`
}

// CreateWebhook see https://developer.zendesk.com/api-reference/event-connectors/webhooks/webhooks/#create-or-clone-webhook
func (c *RESTClient) CreateWebhook(webhook *Webhook) (*Webhook, *httpx.Trace, error) {
payload := struct {
Target *Target `json:"target"`
}{Target: target}
Webhook *Webhook `json:"webhook"`
}{Webhook: webhook}

response := &struct {
Target *Target `json:"target"`
Webhook *Webhook `json:"webhook"`
}{}

trace, err := c.post("targets.json", payload, response)
trace, err := c.post("webhooks", payload, response)
if err != nil {
return nil, trace, err
}

return response.Target, trace, nil
return response.Webhook, trace, nil
}

// DeleteTarget see https://developer.zendesk.com/rest_api/docs/support/targets#delete-target
func (c *RESTClient) DeleteTarget(id int64) (*httpx.Trace, error) {
return c.delete(fmt.Sprintf("targets/%d.json", id))
// DeleteWebhook see https://developer.zendesk.com/api-reference/event-connectors/webhooks/webhooks/#delete-webhook
func (c *RESTClient) DeleteWebhook(id int64) (*httpx.Trace, error) {
return c.delete(fmt.Sprintf("webhooks/%d", id))
}

// Condition see https://developer.zendesk.com/rest_api/docs/support/triggers#conditions
Expand Down
77 changes: 51 additions & 26 deletions services/tickets/zendesk/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,62 +11,87 @@ import (
"github.com/stretchr/testify/assert"
)

func TestCreateTarget(t *testing.T) {
func TestCreateWebhook(t *testing.T) {
defer httpx.SetRequestor(httpx.DefaultRequestor)
httpx.SetRequestor(httpx.NewMockRequestor(map[string][]httpx.MockResponse{
"https://nyaruka.zendesk.com/api/v2/targets.json": {
"https://nyaruka.zendesk.com/api/v2/webhooks": {
httpx.MockConnectionError,
httpx.NewMockResponse(400, nil, `{"description": "Something went wrong", "error": "Unknown"}`), // non-200 response
httpx.NewMockResponse(200, nil, `xx`), // non-JSON response
httpx.NewMockResponse(201, nil, `{
"target": {
"id": 1234567,
"title": "Temba",
"target_url": "http://temba.io/updates",
"method": "POST",
"content_type": "application/json"
"webhook": {
"id": "1234567",
"name": "Temba",
"status": "active",
"subscriptions": [
"conditional_ticket_events"
],
"endpoint": "http://temba.io/updates",
"http_method": "POST",
"request_format": "json",
"authentication": {
"type": "basic_auth",
"data": {
"username": "zendesk",
"password":"cffc2ed5-ab65-4bd3-93ed-b4430a20984c"
},
"add_position": "header"
}
}
}`),
},
}))

client := zendesk.NewRESTClient(http.DefaultClient, nil, "nyaruka", "123456789")
target := &zendesk.Target{
Title: "Temba",
TargetURL: "http://temba.io/updates",
Method: "POST",
ContentType: "application/json",
webhook := &zendesk.Webhook{
Authentication: struct {
AddPosition string "json:\"add_position\""
Data struct {
Password string "json:\"password\""
Username string "json:\"username\""
} "json:\"data\""
Type string "json:\"type\""
}{AddPosition: "header", Type: "basic_auth", Data: struct {
Password string "json:\"password\""
Username string "json:\"username\""
}{Password: "cffc2ed5-ab65-4bd3-93ed-b4430a20984c", Username: "zendesk"}},
Endpoint: "http://temba.io/updates",
HttpMethod: "POST",
Name: "Temba",
RequestFormat: "json",
Status: "active",
Subscriptions: []string{"conditional_ticket_events"},
}

_, _, err := client.CreateTarget(target)
_, _, err := client.CreateWebhook(webhook)
assert.EqualError(t, err, "unable to connect to server")

_, _, err = client.CreateTarget(target)
_, _, err = client.CreateWebhook(webhook)
assert.EqualError(t, err, "Something went wrong")

_, _, err = client.CreateTarget(target)
_, _, err = client.CreateWebhook(webhook)
assert.EqualError(t, err, "invalid character 'x' looking for beginning of value")

target, trace, err := client.CreateTarget(target)
webhook, trace, err := client.CreateWebhook(webhook)
assert.NoError(t, err)
assert.Equal(t, int64(1234567), target.ID)
assert.Equal(t, "HTTP/1.0 201 Created\r\nContent-Length: 180\r\n\r\n", string(trace.ResponseTrace))
assert.Equal(t, "1234567", webhook.ID)
assert.Equal(t, "HTTP/1.0 201 Created\r\nContent-Length: 470\r\n\r\n", string(trace.ResponseTrace))
}

func TestDeleteTarget(t *testing.T) {
func TestDeleteWebhook(t *testing.T) {
defer httpx.SetRequestor(httpx.DefaultRequestor)
httpx.SetRequestor(httpx.NewMockRequestor(map[string][]httpx.MockResponse{
"https://nyaruka.zendesk.com/api/v2/targets/123.json": {
"https://nyaruka.zendesk.com/api/v2/webhooks/123": {
httpx.NewMockResponse(200, nil, ``),
},
}))

client := zendesk.NewRESTClient(http.DefaultClient, nil, "nyaruka", "123456789")

trace, err := client.DeleteTarget(123)
trace, err := client.DeleteWebhook(123)

assert.NoError(t, err)
assert.Equal(t, "DELETE /api/v2/targets/123.json HTTP/1.1\r\nHost: nyaruka.zendesk.com\r\nUser-Agent: Go-http-client/1.1\r\nAuthorization: Bearer 123456789\r\nAccept-Encoding: gzip\r\n\r\n", string(trace.RequestTrace))
assert.Equal(t, "DELETE /api/v2/webhooks/123 HTTP/1.1\r\nHost: nyaruka.zendesk.com\r\nUser-Agent: Go-http-client/1.1\r\nAuthorization: Bearer 123456789\r\nAccept-Encoding: gzip\r\n\r\n", string(trace.RequestTrace))
}

func TestCreateTrigger(t *testing.T) {
Expand All @@ -90,7 +115,7 @@ func TestCreateTrigger(t *testing.T) {
},
"actions": [
{
"field": "notification_target",
"field": "notification_webhook",
"value": ["123", "{}"]
}
]
Expand All @@ -108,7 +133,7 @@ func TestCreateTrigger(t *testing.T) {
},
},
Actions: []zendesk.Action{
{"notification_target", []string{"123", "{}"}},
{"notification_webhook", []string{"123", "{}"}},
},
}

Expand All @@ -124,7 +149,7 @@ func TestCreateTrigger(t *testing.T) {
trigger, trace, err := client.CreateTrigger(trigger)
assert.NoError(t, err)
assert.Equal(t, int64(1234567), trigger.ID)
assert.Equal(t, "HTTP/1.0 201 Created\r\nContent-Length: 317\r\n\r\n", string(trace.ResponseTrace))
assert.Equal(t, "HTTP/1.0 201 Created\r\nContent-Length: 318\r\n\r\n", string(trace.ResponseTrace))
}

func TestDeleteTrigger(t *testing.T) {
Expand Down
52 changes: 31 additions & 21 deletions services/tickets/zendesk/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const (
configOAuthToken = "oauth_token"
configPushID = "push_id"
configPushToken = "push_token"
configTargetID = "target_id"
configWebhookID = "webhook_id"
configTriggerID = "trigger_id"

statusOpen = "open"
Expand All @@ -46,7 +46,7 @@ type service struct {
redactor utils.Redactor
secret string
instancePushID string
targetID string
webhookID string
triggerID string
}

Expand All @@ -57,7 +57,7 @@ func NewService(rtCfg *runtime.Config, httpClient *http.Client, httpRetries *htt
oAuthToken := config[configOAuthToken]
instancePushID := config[configPushID]
pushToken := config[configPushToken]
targetID := config[configTargetID]
webhookID := config[configWebhookID]
triggerID := config[configTriggerID]

if subdomain != "" && secret != "" && oAuthToken != "" && instancePushID != "" && pushToken != "" {
Expand All @@ -69,7 +69,7 @@ func NewService(rtCfg *runtime.Config, httpClient *http.Client, httpRetries *htt
redactor: utils.NewRedactor(flows.RedactionMask, oAuthToken, pushToken),
secret: secret,
instancePushID: instancePushID,
targetID: targetID,
webhookID: webhookID,
triggerID: triggerID,
}, nil
}
Expand Down Expand Up @@ -213,21 +213,31 @@ func (s *service) Reopen(tickets []*models.Ticket, logHTTP flows.HTTPLogCallback
return err
}

// AddStatusCallback adds a target and trigger to callback to us when ticket status is changed
// AddStatusCallback adds a webhook and trigger to callback to us when ticket status is changed
func (s *service) AddStatusCallback(name, domain string, logHTTP flows.HTTPLogCallback) (map[string]string, error) {
targetURL := fmt.Sprintf("https://%s/mr/tickets/types/zendesk/target/%s", domain, s.ticketer.UUID())

target := &Target{
Type: "http_target",
Title: fmt.Sprintf("%s Tickets", name),
TargetURL: targetURL,
Method: "POST",
Username: "zendesk",
Password: s.secret,
ContentType: "application/json",
webhookURL := fmt.Sprintf("https://%s/mr/tickets/types/zendesk/webhook/%s", domain, s.ticketer.UUID())

webhook := &Webhook{
Authentication: struct {
AddPosition string "json:\"add_position\""
Data struct {
Password string "json:\"password\""
Username string "json:\"username\""
} "json:\"data\""
Type string "json:\"type\""
}{AddPosition: "header", Data: struct {
Password string "json:\"password\""
Username string "json:\"username\""
}{Password: s.secret, Username: "zendesk"}, Type: "basic_auth"},
Endpoint: webhookURL,
HttpMethod: "POST",
Name: fmt.Sprintf("%s Tickets", name),
RequestFormat: "json",
Status: "active",
Subscriptions: []string{"conditional_ticket_events"},
}

target, trace, err := s.restClient.CreateTarget(target)
webhook, trace, err := s.restClient.CreateWebhook(webhook)
if trace != nil {
logHTTP(flows.NewHTTPLog(trace, flows.HTTPStatusFromCode, s.redactor))
}
Expand All @@ -250,7 +260,7 @@ func (s *service) AddStatusCallback(name, domain string, logHTTP flows.HTTPLogCa
},
},
Actions: []Action{
{Field: "notification_target", Value: []string{fmt.Sprintf("%d", target.ID), string(payload)}},
{Field: "notification_webhook", Value: []string{fmt.Sprintf("%s", webhook.ID), string(payload)}},
},
}

Expand All @@ -263,7 +273,7 @@ func (s *service) AddStatusCallback(name, domain string, logHTTP flows.HTTPLogCa
}

return map[string]string{
configTargetID: NumericIDToString(target.ID),
configWebhookID: webhook.ID,
configTriggerID: NumericIDToString(trigger.ID),
}, nil
}
Expand All @@ -279,9 +289,9 @@ func (s *service) RemoveStatusCallback(logHTTP flows.HTTPLogCallback) error {
return err
}
}
if s.targetID != "" {
id, _ := ParseNumericID(s.targetID)
trace, err := s.restClient.DeleteTarget(id)
if s.webhookID != "" {
id, _ := ParseNumericID(s.webhookID)
trace, err := s.restClient.DeleteWebhook(id)
if trace != nil {
logHTTP(flows.NewHTTPLog(trace, flows.HTTPStatusFromCode, s.redactor))
}
Expand Down
14 changes: 7 additions & 7 deletions services/tickets/zendesk/testdata/event_callback.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,12 @@
}
},
{
"label": "target and trigger created for create_integration_instance event",
"label": "webhook and trigger created for create_integration_instance event",
"http_mocks": {
"https://nyaruka.zendesk.com/api/v2/targets.json": [
"https://nyaruka.zendesk.com/api/v2/webhooks": [
{
"status": 200,
"body": "{\"target\":{\"id\":15}}"
"body": "{\"webhook\":{\"id\":\"15\"}}"
}
],
"https://nyaruka.zendesk.com/api/v2/triggers.json": [
Expand Down Expand Up @@ -139,7 +139,7 @@
},
"db_assertions": [
{
"query": "select count(*) from tickets_ticketer where config @> '{\"target_id\": \"15\", \"trigger_id\": \"23\"}'",
"query": "select count(*) from tickets_ticketer where config @> '{\"webhook_id\": \"15\", \"trigger_id\": \"23\"}'",
"count": 1
}
]
Expand Down Expand Up @@ -206,9 +206,9 @@
]
},
{
"label": "target and trigger deleted for destroy_integration_instance event",
"label": "webhook and trigger deleted for destroy_integration_instance event",
"http_mocks": {
"https://nyaruka.zendesk.com/api/v2/targets/15.json": [
"https://nyaruka.zendesk.com/api/v2/webhooks/15": [
{
"status": 200,
"body": ""
Expand Down Expand Up @@ -243,7 +243,7 @@
},
"db_assertions": [
{
"query": "select count(*) from tickets_ticketer where config @> '{\"target_id\": \"15\", \"trigger_id\": \"23\"}'",
"query": "select count(*) from tickets_ticketer where config @> '{\"webhook_id\": \"15\", \"trigger_id\": \"23\"}'",
"count": 0
},
{
Expand Down
Loading