From 27e604b8d8d6e6017173dd403fc37e364a337056 Mon Sep 17 00:00:00 2001 From: Joey Orlando Date: Fri, 5 Jul 2024 14:24:04 -0400 Subject: [PATCH] add CRUD handlers for user notification rules + remove custom actions endpoint handlers (#19) --- .github/CODEOWNERS | 1 + client.go | 28 +++--- custom_action.go | 170 --------------------------------- custom_action_test.go | 119 ----------------------- user_notification_rule.go | 158 ++++++++++++++++++++++++++++++ user_notification_rule_test.go | 158 ++++++++++++++++++++++++++++++ 6 files changed, 331 insertions(+), 303 deletions(-) create mode 100644 .github/CODEOWNERS delete mode 100644 custom_action.go delete mode 100644 custom_action_test.go create mode 100644 user_notification_rule.go create mode 100644 user_notification_rule_test.go diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..492d540 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @grafana/grafana-oncall-backend diff --git a/client.go b/client.go index 7d308e8..b6af886 100644 --- a/client.go +++ b/client.go @@ -44,19 +44,19 @@ type Client struct { limiter *rate.Limiter UserAgent string // List of Services. Keep in sync with func newClient - Alerts *AlertService - Integrations *IntegrationService - EscalationChains *EscalationChainService - Escalations *EscalationService - Users *UserService - Schedules *ScheduleService - Routes *RouteService - SlackChannels *SlackChannelService - UserGroups *UserGroupService - CustomActions *CustomActionService - OnCallShifts *OnCallShiftService - Teams *TeamService - Webhooks *WebhookService + Alerts *AlertService + Integrations *IntegrationService + EscalationChains *EscalationChainService + Escalations *EscalationService + Users *UserService + Schedules *ScheduleService + Routes *RouteService + SlackChannels *SlackChannelService + UserGroups *UserGroupService + OnCallShifts *OnCallShiftService + Teams *TeamService + Webhooks *WebhookService + UserNotificationRules *UserNotificationRuleService } func New(base_url, token string) (*Client, error) { @@ -108,10 +108,10 @@ func newClient(url string) (*Client, error) { c.Routes = NewRouteService(c) c.SlackChannels = NewSlackChannelService(c) c.UserGroups = NewUserGroupService(c) - c.CustomActions = NewCustomActionService(c) c.OnCallShifts = NewOnCallShiftService(c) c.Teams = NewTeamService(c) c.Webhooks = NewWebhookService(c) + c.UserNotificationRules = NewUserNotificationRuleService(c) return c, nil } diff --git a/custom_action.go b/custom_action.go deleted file mode 100644 index 0d029ae..0000000 --- a/custom_action.go +++ /dev/null @@ -1,170 +0,0 @@ -package aapi - -import ( - "fmt" - "net/http" -) - -// CustomActionService handles requests to outgoing webhook endpoint -// -// https://grafana.com/docs/grafana-cloud/oncall/oncall-api-reference/outgoing_webhooks/ -type CustomActionService struct { - client *Client - url string -} - -// NewCustomActionService creates CustomActionService with defined url -func NewCustomActionService(client *Client) *CustomActionService { - customActionService := CustomActionService{} - customActionService.client = client - customActionService.url = "actions" - return &customActionService -} - -type PaginatedCustomActionsResponse struct { - PaginatedResponse - CustomActions []*CustomAction `json:"results"` -} - -type CustomAction struct { - ID string `json:"id"` - Name string `json:"name"` - TeamId string `json:"team_id"` - Url string `json:"url"` - Data *string `json:"data"` - User *string `json:"user"` - Password *string `json:"password"` - AuthorizationHeader *string `json:"authorization_header"` - ForwardWholePayload bool `json:"forward_whole_payload"` -} - -type ListCustomActionOptions struct { - ListOptions - Name string `url:"name,omitempty" json:"name,omitempty"` -} - -// ListCustomActions fetches all customActions for authorized organization -// -// https://grafana.com/docs/grafana-cloud/oncall/oncall-api-reference/outgoing_webhooks/#list-actions -func (service *CustomActionService) ListCustomActions(opt *ListCustomActionOptions) (*PaginatedCustomActionsResponse, *http.Response, error) { - u := fmt.Sprintf("%s", service.url) - - req, err := service.client.NewRequest("GET", u, opt) - if err != nil { - return nil, nil, err - } - - var customActions *PaginatedCustomActionsResponse - resp, err := service.client.Do(req, &customActions) - if err != nil { - return nil, resp, err - } - - return customActions, resp, err -} - -type GetCustomActionOptions struct { -} - -// GetCustomAction fetches custom action by given id. -// -// https://grafana.com/docs/grafana-cloud/oncall/oncall-api-reference/outgoing_webhooks/ -func (service *CustomActionService) GetCustomAction(id string, opt *GetCustomActionOptions) (*CustomAction, *http.Response, error) { - u := fmt.Sprintf("%s/%s/", service.url, id) - - req, err := service.client.NewRequest("GET", u, opt) - if err != nil { - return nil, nil, err - } - - customAction := new(CustomAction) - resp, err := service.client.Do(req, customAction) - if err != nil { - return nil, resp, err - } - - return customAction, resp, err -} - -type CreateCustomActionOptions struct { - Name string `json:"name,omitempty"` - TeamId string `json:"team_id"` - Url string `json:"url,omitempty"` - Data *string `json:"data"` - User *string `json:"user"` - Password *string `json:"password"` - AuthorizationHeader *string `json:"authorization_header"` - ForwardWholePayload bool `json:"forward_whole_payload"` -} - -// CreateCustomAction creates custom action -// -// https://grafana.com/docs/grafana-cloud/oncall/oncall-api-reference/outgoing_webhooks/ -func (service *CustomActionService) CreateCustomAction(opt *CreateCustomActionOptions) (*CustomAction, *http.Response, error) { - u := fmt.Sprintf("%s/", service.url) - - req, err := service.client.NewRequest("POST", u, opt) - if err != nil { - return nil, nil, err - } - - customAction := new(CustomAction) - - resp, err := service.client.Do(req, customAction) - - if err != nil { - return nil, resp, err - } - - return customAction, resp, err -} - -type UpdateCustomActionOptions struct { - Name string `json:"name,omitempty"` - Url string `json:"url"` - Data *string `json:"data"` - User *string `json:"user"` - Password *string `json:"password"` - AuthorizationHeader *string `json:"authorization_header"` - ForwardWholePayload bool `json:"forward_whole_payload"` - TeamId string `json:"team_id"` -} - -// UpdateCustomAction updates custom action -// -// https://grafana.com/docs/grafana-cloud/oncall/oncall-api-reference/outgoing_webhooks/ -func (service *CustomActionService) UpdateCustomAction(id string, opt *UpdateCustomActionOptions) (*CustomAction, *http.Response, error) { - u := fmt.Sprintf("%s/%s/", service.url, id) - - req, err := service.client.NewRequest("PUT", u, opt) - if err != nil { - return nil, nil, err - } - - CustomAction := new(CustomAction) - resp, err := service.client.Do(req, CustomAction) - if err != nil { - return nil, resp, err - } - - return CustomAction, resp, err -} - -type DeleteCustomActionOptions struct { -} - -// DeleteCustomAction deletes custom action. -// -// https://grafana.com/docs/grafana-cloud/oncall/oncall-api-reference/outgoing_webhooks/ -func (service *CustomActionService) DeleteCustomAction(id string, opt *DeleteCustomActionOptions) (*http.Response, error) { - - u := fmt.Sprintf("%s/%s/", service.url, id) - - req, err := service.client.NewRequest("DELETE", u, opt) - if err != nil { - return nil, err - } - - resp, err := service.client.Do(req, nil) - return resp, err -} diff --git a/custom_action_test.go b/custom_action_test.go deleted file mode 100644 index cc0c8a6..0000000 --- a/custom_action_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package aapi - -import ( - "fmt" - "net/http" - "reflect" - "testing" -) - -var testCustomAction = &CustomAction{ - ID: "KGEFG74LU1D8L", - Name: "Test action", - TeamId: "T3HRAP3K3IKOP", -} - -var testCustomActionBody = `{ - "id": "KGEFG74LU1D8L", - "name": "Test action", - "team_id": "T3HRAP3K3IKOP" -}` - -func TestListCustomActions(t *testing.T) { - mux, server, client := setup(t) - defer teardown(server) - - mux.HandleFunc("/api/v1/actions/", func(w http.ResponseWriter, r *http.Request) { - testRequestMethod(t, r, "GET") - fmt.Fprint(w, fmt.Sprintf(`{"count": 1, "next": null, "previous": null, "results": [%s]}`, testCustomActionBody)) - }) - - options := &ListCustomActionOptions{ - Name: "Test action", - } - - customActions, _, err := client.CustomActions.ListCustomActions(options) - if err != nil { - t.Fatal(err) - } - - want := &PaginatedCustomActionsResponse{ - PaginatedResponse: PaginatedResponse{ - Count: 1, - Next: nil, - Previous: nil, - }, - CustomActions: []*CustomAction{ - testCustomAction, - }, - } - if !reflect.DeepEqual(want, customActions) { - t.Errorf("returned\n %+v, \nwant\n %+v", customActions, want) - } -} - -func TestCreateCustomAction(t *testing.T) { - mux, server, client := setup(t) - defer teardown(server) - - mux.HandleFunc("/api/v1/actions/", func(w http.ResponseWriter, r *http.Request) { - testRequestMethod(t, r, "POST") - fmt.Fprint(w, testCustomActionBody) - }) - - createOptions := &CreateCustomActionOptions{ - Name: "Test CustomAction", - Url: "https://example.com", - } - customAction, _, err := client.CustomActions.CreateCustomAction(createOptions) - - if err != nil { - t.Fatal(err) - } - - want := testCustomAction - - if !reflect.DeepEqual(want, customAction) { - t.Errorf("returned\n %+v\n want\n %+v\n", customAction, want) - } -} - -func TestDeleteCustomAction(t *testing.T) { - mux, server, client := setup(t) - defer teardown(server) - - mux.HandleFunc("/api/v1/actions/KGEFG74LU1D8L/", func(w http.ResponseWriter, r *http.Request) { - testRequestMethod(t, r, "DELETE") - }) - - options := &DeleteCustomActionOptions{} - - _, err := client.CustomActions.DeleteCustomAction("KGEFG74LU1D8L", options) - if err != nil { - t.Fatal(err) - } -} - -func TestGetCustomAction(t *testing.T) { - mux, server, client := setup(t) - defer teardown(server) - - mux.HandleFunc("/api/v1/actions/KGEFG74LU1D8L/", func(w http.ResponseWriter, r *http.Request) { - testRequestMethod(t, r, "GET") - fmt.Fprint(w, testCustomActionBody) - }) - - options := &GetCustomActionOptions{} - - customAction, _, err := client.CustomActions.GetCustomAction("KGEFG74LU1D8L", options) - - if err != nil { - t.Fatal(err) - } - - want := testCustomAction - - if !reflect.DeepEqual(want, customAction) { - t.Errorf("returned\n %+v\n want\n %+v\n", customAction, want) - } -} diff --git a/user_notification_rule.go b/user_notification_rule.go new file mode 100644 index 0000000..4359f51 --- /dev/null +++ b/user_notification_rule.go @@ -0,0 +1,158 @@ +package aapi + +import ( + "fmt" + "log" + "net/http" +) + +// UserNotificationRuleService handles requests to user notification rule endpoints +// +// https://grafana.com/docs/oncall/latest/oncall-api-reference/personal_notification_rules/ +type UserNotificationRuleService struct { + client *Client + url string +} + +// NewUserNotificationRuleService creates UserNotificationRuleService with defined url +func NewUserNotificationRuleService(client *Client) *UserNotificationRuleService { + userNotificationRuleService := UserNotificationRuleService{client: client, url: "personal_notification_rules"} + return &userNotificationRuleService +} + +type PaginatedUserNotificationRulesResponse struct { + PaginatedResponse + UserNotificationRules []*UserNotificationRule `json:"results"` +} + +type UserNotificationRule struct { + ID string `json:"id"` + UserId string `json:"user_id"` + Position int `json:"position"` + Duration int `json:"duration"` + Important bool `json:"important"` + Type string `json:"type"` +} + +type ListUserNotificationRuleOptions struct { + ListOptions + UserId string `url:"user_id,omitempty" json:"user_id,omitempty"` + Important string `url:"important,omitempty" json:"important,omitempty"` +} + +// ListUserNotificationRules fetches all user notification rules for authorized organization +// +// https://grafana.com/docs/oncall/latest/oncall-api-reference/personal_notification_rules/#list-personal-notification-rules +func (service *UserNotificationRuleService) ListUserNotificationRules(opt *ListUserNotificationRuleOptions) (*PaginatedUserNotificationRulesResponse, *http.Response, error) { + req, err := service.client.NewRequest("GET", service.url, opt) + if err != nil { + return nil, nil, err + } + + var userNotificationRules *PaginatedUserNotificationRulesResponse + resp, err := service.client.Do(req, &userNotificationRules) + if err != nil { + return nil, resp, err + } + + return userNotificationRules, resp, err +} + +type GetUserNotificationRuleOptions struct { +} + +// GetUserNotificationRule fetches a user notification rule by given id +// +// https://grafana.com/docs/oncall/latest/oncall-api-reference/personal_notification_rules/#get-personal-notification-rule +func (service *UserNotificationRuleService) GetUserNotificationRule(id string, opt *GetUserNotificationRuleOptions) (*UserNotificationRule, *http.Response, error) { + u := fmt.Sprintf("%s/%s/", service.url, id) + + req, err := service.client.NewRequest("GET", u, opt) + if err != nil { + return nil, nil, err + } + + userNotificationRule := new(UserNotificationRule) + resp, err := service.client.Do(req, userNotificationRule) + if err != nil { + return nil, resp, err + } + + return userNotificationRule, resp, err +} + +type CreateUserNotificationRuleOptions struct { + UserId string `json:"user_id,omitempty"` + Position *int `json:"position,omitempty"` + Duration *int `json:"duration,omitempty"` + Important bool `json:"important,omitempty"` + Type string `json:"type,omitempty"` + ManualOrder bool `json:"manual_order,omitempty"` +} + +// CreateUserNotificationRule creates a user notification rule for the given user, type, and position +// +// https://grafana.com/docs/oncall/latest/oncall-api-reference/personal_notification_rules/#post-a-personal-notification-rule +func (service *UserNotificationRuleService) CreateUserNotificationRule(opt *CreateUserNotificationRuleOptions) (*UserNotificationRule, *http.Response, error) { + u := fmt.Sprintf("%s/", service.url) + req, err := service.client.NewRequest("POST", u, opt) + if err != nil { + return nil, nil, err + } + + userNotificationRule := new(UserNotificationRule) + + resp, err := service.client.Do(req, userNotificationRule) + log.Printf("[DEBUG] request success") + + if err != nil { + return nil, resp, err + } + + return userNotificationRule, resp, err +} + +type UpdateUserNotificationRuleOptions struct { + Position *int `json:"position,omitempty"` + Duration *int `json:"duration,omitempty"` + Type string `json:"type,omitempty"` + ManualOrder bool `json:"manual_order,omitempty"` +} + +// UpdateUserNotificationRule updates user notification rule with new position, duration, and type +// +// NOTE: this endpoint is not currently publicly documented, but it does exist +func (service *UserNotificationRuleService) UpdateUserNotificationRule(id string, opt *UpdateUserNotificationRuleOptions) (*UserNotificationRule, *http.Response, error) { + u := fmt.Sprintf("%s/%s/", service.url, id) + + req, err := service.client.NewRequest("PUT", u, opt) + if err != nil { + return nil, nil, err + } + + userNotificationRule := new(UserNotificationRule) + resp, err := service.client.Do(req, userNotificationRule) + if err != nil { + return nil, resp, err + } + + return userNotificationRule, resp, err +} + +type DeleteUserNotificationRuleOptions struct { +} + +// DeleteUserNotificationRule deletes user notification rule +// +// https://grafana.com/docs/oncall/latest/oncall-api-reference/personal_notification_rules/#delete-a-personal-notification-rule +func (service *UserNotificationRuleService) DeleteUserNotificationRule(id string, opt *DeleteUserNotificationRuleOptions) (*http.Response, error) { + u := fmt.Sprintf("%s/%s/", service.url, id) + + req, err := service.client.NewRequest("DELETE", u, opt) + if err != nil { + return nil, err + } + + resp, err := service.client.Do(req, nil) + return resp, err +} diff --git a/user_notification_rule_test.go b/user_notification_rule_test.go new file mode 100644 index 0000000..a2ef302 --- /dev/null +++ b/user_notification_rule_test.go @@ -0,0 +1,158 @@ +package aapi + +import ( + "fmt" + "net/http" + "reflect" + "testing" +) + +var baseUrl = "personal_notification_rules" +var testId = "NT79GA9I7E4DJ" +var testUserId = "U4DNY931HHJS5" + +var testUserNotificationRule = &UserNotificationRule{ + ID: testId, + UserId: testUserId, + Position: 0, + Type: "notify_by_sms", +} + +var testUserNotificationRuleBody = fmt.Sprintf(`{ + "id": "%s", + "user_id": "%s", + "position": 0, + "type": "notify_by_sms" +}`, testId, testUserId) + +func TestCreateUserNotificationRule(t *testing.T) { + mux, server, client := setup(t) + defer teardown(server) + + url := fmt.Sprintf("/api/v1/%s/", baseUrl) + mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { + testRequestMethod(t, r, "POST") + fmt.Fprint(w, testUserNotificationRuleBody) + }) + + createOptions := &CreateUserNotificationRuleOptions{ + UserId: testUserId, + Important: true, + Type: "notify_by_sms", + ManualOrder: true, + } + userNotificationRule, _, err := client.UserNotificationRules.CreateUserNotificationRule(createOptions) + + if err != nil { + t.Fatal(err) + } + + want := testUserNotificationRule + + if !reflect.DeepEqual(want, userNotificationRule) { + t.Errorf("returned\n %+v\n want\n %+v\n", userNotificationRule, want) + } +} + +func TestUpdateUserNotificationRule(t *testing.T) { + mux, server, client := setup(t) + defer teardown(server) + + url := fmt.Sprintf("/api/v1/%s/%s/", baseUrl, testId) + mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { + testRequestMethod(t, r, "PUT") + fmt.Fprint(w, testUserNotificationRuleBody) + }) + + updateOptions := &UpdateUserNotificationRuleOptions{ + Type: "notify_by_sms", + ManualOrder: true, + } + userNotificationRule, _, err := client.UserNotificationRules.UpdateUserNotificationRule(testId, updateOptions) + + if err != nil { + t.Fatal(err) + } + + want := testUserNotificationRule + + if !reflect.DeepEqual(want, userNotificationRule) { + t.Errorf("returned\n %+v\n want\n %+v\n", userNotificationRule, want) + } +} + +func TestDeleteUserNotificationRule(t *testing.T) { + mux, server, client := setup(t) + defer teardown(server) + + url := fmt.Sprintf("/api/v1/%s/%s/", baseUrl, testId) + mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { + testRequestMethod(t, r, "DELETE") + }) + + options := &DeleteUserNotificationRuleOptions{} + + _, err := client.UserNotificationRules.DeleteUserNotificationRule(testId, options) + if err != nil { + t.Fatal(err) + } +} + +func TestListUserNotificationRules(t *testing.T) { + mux, server, client := setup(t) + defer teardown(server) + + url := fmt.Sprintf("/api/v1/%s/", baseUrl) + mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { + testRequestMethod(t, r, "GET") + fmt.Fprint(w, fmt.Sprintf(`{"count": 1, "next": null, "previous": null, "results": [%s]}`, testUserNotificationRuleBody)) + }) + + options := &ListUserNotificationRuleOptions{ + UserId: testUserId, + } + + userNotificationRules, _, err := client.UserNotificationRules.ListUserNotificationRules(options) + if err != nil { + t.Fatal(err) + } + + want := &PaginatedUserNotificationRulesResponse{ + PaginatedResponse: PaginatedResponse{ + Count: 1, + Next: nil, + Previous: nil, + }, + UserNotificationRules: []*UserNotificationRule{ + testUserNotificationRule, + }, + } + if !reflect.DeepEqual(want, userNotificationRules) { + t.Errorf(" returned\n %+v, \nwant\n %+v", userNotificationRules, want) + } +} + +func TestGetUserNotificationRule(t *testing.T) { + mux, server, client := setup(t) + defer teardown(server) + + url := fmt.Sprintf("/api/v1/%s/%s/", baseUrl, testId) + mux.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { + testRequestMethod(t, r, "GET") + fmt.Fprint(w, testUserNotificationRuleBody) + }) + + options := &GetUserNotificationRuleOptions{} + + userNotificationRule, _, err := client.UserNotificationRules.GetUserNotificationRule(testId, options) + + if err != nil { + t.Fatal(err) + } + + want := userNotificationRule + + if !reflect.DeepEqual(want, userNotificationRule) { + t.Errorf("returned\n %+v\n want\n %+v\n", userNotificationRule, want) + } +}