Skip to content
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
129 changes: 129 additions & 0 deletions emailtemplate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package signalfx

import (
"bytes"
"context"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"net/url"
"strconv"

"github.com/signalfx/signalfx-go/emailtemplate"
)

// EmailTemplateAPIURL is the base URL for interacting with email templates.
const EmailTemplateAPIURL = "/v2/alert/emailtemplate"

// CreateEmailTemplate creates an email template.
func (c *Client) CreateEmailTemplate(ctx context.Context, template *emailtemplate.EmailTemplate) (*emailtemplate.EmailTemplate, error) {
payload, err := json.Marshal(template)
if err != nil {
return nil, err
}

resp, err := c.doRequest(ctx, http.MethodPost, EmailTemplateAPIURL, nil, bytes.NewReader(payload))
if err != nil {
return nil, err
}
defer resp.Body.Close()

if err = newResponseError(resp, http.StatusCreated); err != nil {
return nil, err
}

finalTemplate := &emailtemplate.EmailTemplate{}
err = json.NewDecoder(resp.Body).Decode(finalTemplate)
_, _ = io.Copy(ioutil.Discard, resp.Body)

return finalTemplate, err
}

// GetEmailTemplate gets an email template by ID.
func (c *Client) GetEmailTemplate(ctx context.Context, id string) (*emailtemplate.EmailTemplate, error) {
resp, err := c.doRequest(ctx, http.MethodGet, EmailTemplateAPIURL+"/"+id, nil, nil)
if err != nil {
return nil, err
}
defer resp.Body.Close()

if err = newResponseError(resp, http.StatusOK); err != nil {
return nil, err
}

finalTemplate := &emailtemplate.EmailTemplate{}
err = json.NewDecoder(resp.Body).Decode(finalTemplate)
_, _ = io.Copy(ioutil.Discard, resp.Body)

return finalTemplate, err
}

// UpdateEmailTemplate updates an email template.
func (c *Client) UpdateEmailTemplate(ctx context.Context, id string, template *emailtemplate.EmailTemplate) (*emailtemplate.EmailTemplate, error) {
payload, err := json.Marshal(template)
if err != nil {
return nil, err
}

resp, err := c.doRequest(ctx, http.MethodPut, EmailTemplateAPIURL+"/"+id, nil, bytes.NewReader(payload))
if err != nil {
return nil, err
}
defer resp.Body.Close()

if err = newResponseError(resp, http.StatusOK); err != nil {
return nil, err
}

finalTemplate := &emailtemplate.EmailTemplate{}
err = json.NewDecoder(resp.Body).Decode(finalTemplate)
_, _ = io.Copy(ioutil.Discard, resp.Body)

return finalTemplate, err
}

// DeleteEmailTemplate deletes an email template.
func (c *Client) DeleteEmailTemplate(ctx context.Context, id string) error {
resp, err := c.doRequest(ctx, http.MethodDelete, EmailTemplateAPIURL+"/"+id, nil, nil)
if err != nil {
return err
}
defer resp.Body.Close()

if err = newResponseError(resp, http.StatusNoContent); err != nil {
return err
}
_, _ = io.Copy(ioutil.Discard, resp.Body)

return nil
}

// SearchEmailTemplates searches email templates by name with pagination.
func (c *Client) SearchEmailTemplates(ctx context.Context, limit int, name string, offset int, orderBy string) (*emailtemplate.SearchResult, error) {
params := url.Values{}
params.Add("limit", strconv.Itoa(limit))
params.Add("offset", strconv.Itoa(offset))
if name != "" {
params.Add("name", name)
}
if orderBy != "" {
params.Add("orderBy", orderBy)
}

resp, err := c.doRequest(ctx, http.MethodGet, EmailTemplateAPIURL, params, nil)
if err != nil {
return nil, err
}
defer resp.Body.Close()

if err = newResponseError(resp, http.StatusOK); err != nil {
return nil, err
}

finalTemplates := &emailtemplate.SearchResult{}
err = json.NewDecoder(resp.Body).Decode(finalTemplates)
_, _ = io.Copy(ioutil.Discard, resp.Body)

return finalTemplates, err
}
25 changes: 25 additions & 0 deletions emailtemplate/model_email_template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package emailtemplate

// EmailTemplate represents a reusable detector alert email template.
type EmailTemplate struct {
Id string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
TriggerSubject string `json:"triggerSubject,omitempty"`
TriggerBody string `json:"triggerBody,omitempty"`
ResolvedSubject string `json:"resolvedSubject,omitempty"`
ResolvedBody string `json:"resolvedBody,omitempty"`
To []string `json:"to,omitempty"`
Cc []string `json:"cc,omitempty"`
Bcc []string `json:"bcc,omitempty"`
CustomHeaders map[string]string `json:"customHeaders,omitempty"`
CreatedOnMs int64 `json:"createdOnMs,omitempty"`
CreatedBy string `json:"createdBy,omitempty"`
UpdatedOnMs int64 `json:"updatedOnMs,omitempty"`
UpdatedBy string `json:"updatedBy,omitempty"`
}

// SearchResult is the paginated email-template search response.
type SearchResult struct {
Count int `json:"count,omitempty"`
Results []*EmailTemplate `json:"results,omitempty"`
}
113 changes: 113 additions & 0 deletions emailtemplate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package signalfx

import (
"context"
"net/http"
"net/url"
"strconv"
"testing"

"github.com/signalfx/signalfx-go/emailtemplate"
"github.com/stretchr/testify/assert"
)

const emailTemplateRequestBody = `{
"name": "Terraform email template",
"triggerSubject": "{{ruleSeverity}} Alert: {{{ruleName}}}",
"triggerBody": "Triggered at {{timestamp}}",
"resolvedSubject": "{{ruleSeverity}} Resolved: {{{ruleName}}}",
"resolvedBody": "Resolved at {{timestamp}}",
"to": ["alerts@example.com"],
"cc": ["oncall@example.com"],
"bcc": ["audit@example.com"],
"customHeaders": {
"X-Test-Header": "terraform"
}
}`

func testEmailTemplateRequest() *emailtemplate.EmailTemplate {
return &emailtemplate.EmailTemplate{
Name: "Terraform email template",
TriggerSubject: "{{ruleSeverity}} Alert: {{{ruleName}}}",
TriggerBody: "Triggered at {{timestamp}}",
ResolvedSubject: "{{ruleSeverity}} Resolved: {{{ruleName}}}",
ResolvedBody: "Resolved at {{timestamp}}",
To: []string{"alerts@example.com"},
Cc: []string{"oncall@example.com"},
Bcc: []string{"audit@example.com"},
CustomHeaders: map[string]string{
"X-Test-Header": "terraform",
},
}
}

func TestCreateEmailTemplate(t *testing.T) {
teardown := setup()
defer teardown()

mux.HandleFunc("/v2/alert/emailtemplate", verifyRequestWithJsonBody(t, "POST", true, http.StatusCreated, nil, emailTemplateRequestBody, "emailtemplate/create_success.json"))

result, err := client.CreateEmailTemplate(context.Background(), testEmailTemplateRequest())
assert.NoError(t, err, "Unexpected error creating email template")
assert.Equal(t, "template-id", result.Id, "ID does not match")
assert.Equal(t, "Terraform email template", result.Name, "Name does not match")
assert.Equal(t, []string{"alerts@example.com"}, result.To, "To recipients do not match")
assert.Equal(t, "terraform", result.CustomHeaders["X-Test-Header"], "Custom header does not match")
}

func TestGetEmailTemplate(t *testing.T) {
teardown := setup()
defer teardown()

mux.HandleFunc("/v2/alert/emailtemplate/template-id", verifyRequest(t, "GET", true, http.StatusOK, nil, "emailtemplate/create_success.json"))

result, err := client.GetEmailTemplate(context.Background(), "template-id")
assert.NoError(t, err, "Unexpected error getting email template")
assert.Equal(t, "template-id", result.Id, "ID does not match")
assert.Equal(t, "{{ruleSeverity}} Alert: {{{ruleName}}}", result.TriggerSubject, "Trigger subject does not match")
}

func TestUpdateEmailTemplate(t *testing.T) {
teardown := setup()
defer teardown()

mux.HandleFunc("/v2/alert/emailtemplate/template-id", verifyRequestWithJsonBody(t, "PUT", true, http.StatusOK, nil, emailTemplateRequestBody, "emailtemplate/update_success.json"))

result, err := client.UpdateEmailTemplate(context.Background(), "template-id", testEmailTemplateRequest())
assert.NoError(t, err, "Unexpected error updating email template")
assert.Equal(t, "Updated Terraform email template", result.Name, "Name does not match")
assert.Equal(t, int64(1710000100000), result.UpdatedOnMs, "Updated timestamp does not match")
}

func TestDeleteEmailTemplate(t *testing.T) {
teardown := setup()
defer teardown()

mux.HandleFunc("/v2/alert/emailtemplate/template-id", verifyRequest(t, "DELETE", true, http.StatusNoContent, nil, ""))

err := client.DeleteEmailTemplate(context.Background(), "template-id")
assert.NoError(t, err, "Unexpected error deleting email template")
}

func TestSearchEmailTemplates(t *testing.T) {
teardown := setup()
defer teardown()

limit := 10
name := "Terraform"
offset := 2
orderBy := "-updatedOnMs"
params := url.Values{}
params.Add("limit", strconv.Itoa(limit))
params.Add("offset", strconv.Itoa(offset))
params.Add("name", name)
params.Add("orderBy", orderBy)

mux.HandleFunc("/v2/alert/emailtemplate", verifyRequest(t, "GET", true, http.StatusOK, params, "emailtemplate/search_success.json"))

results, err := client.SearchEmailTemplates(context.Background(), limit, name, offset, orderBy)
assert.NoError(t, err, "Unexpected error searching email templates")
assert.Equal(t, 1, results.Count, "Count does not match")
assert.Equal(t, 1, len(results.Results), "Results length does not match")
assert.Equal(t, "template-id", results.Results[0].Id, "Result ID does not match")
}
9 changes: 9 additions & 0 deletions notification/model_email_template_notification.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package notification

// Notification properties for an alert sent through a centralized email template.
type EmailTemplateNotification struct {
// Tells SignalFx which system to use to send the notification. For an email template notification, this is always "EmailTemplate".
Type string `json:"type"`
// The SignalFx ID of the email template to render for this detector notification.
TemplateId string `json:"templateId"`
}
2 changes: 2 additions & 0 deletions notification/model_notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ func (n *Notification) UnmarshalJSON(data []byte) error {
n.Value = &BigPandaNotification{}
case "Email":
n.Value = &EmailNotification{}
case "EmailTemplate":
n.Value = &EmailTemplateNotification{}
case "Jira":
n.Value = &JiraNotification{}
case "Office365":
Expand Down
11 changes: 10 additions & 1 deletion notification/model_notification_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,16 @@ func TestNotificationUnmarshaJSON(t *testing.T) {
errVal: "",
},
{
name: "Email with cc and bcc",
name: "EmailTemplate",
input: `{"type":"EmailTemplate","templateId":"template-id"}`,
expect: &Notification{Type: "EmailTemplate", Value: &EmailTemplateNotification{
Type: "EmailTemplate",
TemplateId: "template-id",
}},
errVal: "",
},
{
name: "Email with cc and bcc",
input: `{"type":"Email","email":"alerts@example.com","cc":["oncall@example.com","ops@example.com"],"bcc":["audit@example.com"]}`,
expect: &Notification{Type: "Email", Value: &EmailNotification{
Type: "Email",
Expand Down
18 changes: 18 additions & 0 deletions testdata/fixtures/emailtemplate/create_success.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"id": "template-id",
"name": "Terraform email template",
"triggerSubject": "{{ruleSeverity}} Alert: {{{ruleName}}}",
"triggerBody": "Triggered at {{timestamp}}",
"resolvedSubject": "{{ruleSeverity}} Resolved: {{{ruleName}}}",
"resolvedBody": "Resolved at {{timestamp}}",
"to": ["alerts@example.com"],
"cc": ["oncall@example.com"],
"bcc": ["audit@example.com"],
"customHeaders": {
"X-Test-Header": "terraform"
},
"createdOnMs": 1710000000000,
"createdBy": "creator-id",
"updatedOnMs": 1710000000000,
"updatedBy": "creator-id"
}
23 changes: 23 additions & 0 deletions testdata/fixtures/emailtemplate/search_success.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"count": 1,
"results": [
{
"id": "template-id",
"name": "Terraform email template",
"triggerSubject": "{{ruleSeverity}} Alert: {{{ruleName}}}",
"triggerBody": "Triggered at {{timestamp}}",
"resolvedSubject": "{{ruleSeverity}} Resolved: {{{ruleName}}}",
"resolvedBody": "Resolved at {{timestamp}}",
"to": ["alerts@example.com"],
"cc": ["oncall@example.com"],
"bcc": ["audit@example.com"],
"customHeaders": {
"X-Test-Header": "terraform"
},
"createdOnMs": 1710000000000,
"createdBy": "creator-id",
"updatedOnMs": 1710000000000,
"updatedBy": "creator-id"
}
]
}
18 changes: 18 additions & 0 deletions testdata/fixtures/emailtemplate/update_success.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"id": "template-id",
"name": "Updated Terraform email template",
"triggerSubject": "{{ruleSeverity}} Alert: {{{ruleName}}}",
"triggerBody": "Triggered at {{timestamp}}",
"resolvedSubject": "{{ruleSeverity}} Resolved: {{{ruleName}}}",
"resolvedBody": "Resolved at {{timestamp}}",
"to": ["alerts@example.com"],
"cc": ["oncall@example.com"],
"bcc": ["audit@example.com"],
"customHeaders": {
"X-Test-Header": "terraform"
},
"createdOnMs": 1710000000000,
"createdBy": "creator-id",
"updatedOnMs": 1710000100000,
"updatedBy": "updater-id"
}