From 7a0b01c5de4d94c375de254bf7f464e82e344224 Mon Sep 17 00:00:00 2001 From: Steve Brudz Date: Fri, 14 Apr 2023 12:40:12 -0500 Subject: [PATCH] Add methods to get and update organization integration configData (#80) * Extend OrganizationIntegrationsService to support GET org integration details endpoint * Support variable shape configData for org integrations * Implement OrganizationIntegrationsService UpdateConfig --- sentry/organization_integrations.go | 49 ++++++-- sentry/organization_integrations_test.go | 142 ++++++++++++++++++++++- 2 files changed, 182 insertions(+), 9 deletions(-) diff --git a/sentry/organization_integrations.go b/sentry/organization_integrations.go index 6ddfbe5..687296e 100644 --- a/sentry/organization_integrations.go +++ b/sentry/organization_integrations.go @@ -16,29 +16,34 @@ type OrganizationIntegrationProvider struct { Features []string `json:"features"` } +// IntegrationConfigData for defining integration-specific configuration data. +type IntegrationConfigData map[string]interface{} + // OrganizationIntegration represents an integration added for the organization. // https://github.com/getsentry/sentry/blob/22.7.0/src/sentry/api/serializers/models/integration.py#L93 type OrganizationIntegration struct { // https://github.com/getsentry/sentry/blob/22.7.0/src/sentry/api/serializers/models/integration.py#L35 ID string `json:"id"` Name string `json:"name"` - Icon string `json:"icon"` + Icon *string `json:"icon"` DomainName string `json:"domainName"` - AccountType string `json:"accountType"` + AccountType *string `json:"accountType"` Scopes []string `json:"scopes"` Status string `json:"status"` Provider OrganizationIntegrationProvider `json:"provider"` // https://github.com/getsentry/sentry/blob/22.7.0/src/sentry/api/serializers/models/integration.py#L138 - ExternalId string `json:"externalId"` - OrganizationId int `json:"organizationId"` - OrganizationIntegrationStatus string `json:"organizationIntegrationStatus"` - GracePeriodEnd *time.Time `json:"gracePeriodEnd"` + ConfigData *IntegrationConfigData `json:"configData"` + ExternalId string `json:"externalId"` + OrganizationId int `json:"organizationId"` + OrganizationIntegrationStatus string `json:"organizationIntegrationStatus"` + GracePeriodEnd *time.Time `json:"gracePeriodEnd"` } // OrganizationIntegrationsService provides methods for accessing Sentry organization integrations API endpoints. -// Paths: https://github.com/getsentry/sentry/blob/22.7.0/src/sentry/api/urls.py#L1236-L1240 +// Paths: https://github.com/getsentry/sentry/blob/22.7.0/src/sentry/api/urls.py#L1236-L1245 // Endpoints: https://github.com/getsentry/sentry/blob/22.7.0/src/sentry/api/endpoints/integrations/organization_integrations/index.py +// Endpoints: https://github.com/getsentry/sentry/blob/22.7.0/src/sentry/api/endpoints/integrations/organization_integrations/details.py type OrganizationIntegrationsService service type ListOrganizationIntegrationsParams struct { @@ -66,3 +71,33 @@ func (s *OrganizationIntegrationsService) List(ctx context.Context, organization } return integrations, resp, nil } + +// Get organization integration details. +func (s *OrganizationIntegrationsService) Get(ctx context.Context, organizationSlug string, integrationID string) (*OrganizationIntegration, *Response, error) { + u := fmt.Sprintf("0/organizations/%v/integrations/%v/", organizationSlug, integrationID) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + integration := new(OrganizationIntegration) + resp, err := s.client.Do(ctx, req, integration) + if err != nil { + return nil, resp, err + } + return integration, resp, nil +} + +type UpdateConfigOrganizationIntegrationsParams = IntegrationConfigData + +// UpdateConfig - update configData for organization integration. +// https://github.com/getsentry/sentry/blob/22.7.0/src/sentry/api/endpoints/integrations/organization_integrations/details.py#L94-L102 +func (s *OrganizationIntegrationsService) UpdateConfig(ctx context.Context, organizationSlug string, integrationID string, params *UpdateConfigOrganizationIntegrationsParams) (*Response, error) { + u := fmt.Sprintf("0/organizations/%v/integrations/%v/", organizationSlug, integrationID) + req, err := s.client.NewRequest("POST", u, params) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} diff --git a/sentry/organization_integrations_test.go b/sentry/organization_integrations_test.go index 028afcd..63ab695 100644 --- a/sentry/organization_integrations_test.go +++ b/sentry/organization_integrations_test.go @@ -2,6 +2,7 @@ package sentry import ( "context" + "encoding/json" "fmt" "net/http" "testing" @@ -62,9 +63,9 @@ func TestOrganizationIntegrationsService_List(t *testing.T) { { ID: "123456", Name: "octocat", - Icon: "https://avatars.githubusercontent.com/u/583231?v=4", + Icon: String("https://avatars.githubusercontent.com/u/583231?v=4"), DomainName: "github.com/octocat", - AccountType: "Organization", + AccountType: String("Organization"), Scopes: []string{"read", "write"}, Status: "active", Provider: OrganizationIntegrationProvider{ @@ -80,6 +81,7 @@ func TestOrganizationIntegrationsService_List(t *testing.T) { "stacktrace-link", }, }, + ConfigData: &IntegrationConfigData{}, ExternalId: "87654321", OrganizationId: 2, OrganizationIntegrationStatus: "active", @@ -88,3 +90,139 @@ func TestOrganizationIntegrationsService_List(t *testing.T) { } assert.Equal(t, expected, integrations) } + +func TestOrganizationIntegrationsService_Get(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/api/0/organizations/the-interstellar-jurisdiction/integrations/456789/", func(w http.ResponseWriter, r *http.Request) { + assertMethod(t, "GET", r) + w.Header().Set("Content-Type", "application/json") + fmt.Fprint(w, `{ + "id": "456789", + "name": "Interstellar PagerDuty", + "icon": null, + "domainName": "the-interstellar-jurisdiction", + "accountType": null, + "scopes": null, + "status": "active", + "provider": { + "key": "pagerduty", + "slug": "pagerduty", + "name": "PagerDuty", + "canAdd": true, + "canDisable": false, + "features": [ + "alert-rule", + "incident-management" + ], + "aspects": { + "alerts": [ + { + "type": "info", + "text": "The PagerDuty integration adds a new Alert Rule action to all projects. To enable automatic notifications sent to PagerDuty you must create a rule using the PagerDuty action in your project settings." + } + ] + } + }, + "configOrganization": [ + { + "name": "service_table", + "type": "table", + "label": "PagerDuty services with the Sentry integration enabled", + "help": "If services need to be updated, deleted, or added manually please do so here. Alert rules will need to be individually updated for any additions or deletions of services.", + "addButtonText": "", + "columnLabels": { + "service": "Service", + "integration_key": "Integration Key" + }, + "columnKeys": [ + "service", + "integration_key" + ], + "confirmDeleteMessage": "Any alert rules associated with this service will stop working. The rules will still exist but will show a removed service." + } + ], + "configData": { + "service_table": [ + { + "service": "testing123", + "integration_key": "abc123xyz", + "id": 22222 + } + ] + }, + "externalId": "999999", + "organizationId": 2, + "organizationIntegrationStatus": "active", + "gracePeriodEnd": null + }`) + }) + + ctx := context.Background() + integration, _, err := client.OrganizationIntegrations.Get(ctx, "the-interstellar-jurisdiction", "456789") + assert.NoError(t, err) + expected := OrganizationIntegration{ + ID: "456789", + Name: "Interstellar PagerDuty", + Icon: nil, + DomainName: "the-interstellar-jurisdiction", + AccountType: nil, + Scopes: nil, + Status: "active", + Provider: OrganizationIntegrationProvider{ + Key: "pagerduty", + Slug: "pagerduty", + Name: "PagerDuty", + CanAdd: true, + CanDisable: false, + Features: []string{ + "alert-rule", + "incident-management", + }, + }, + ConfigData: &IntegrationConfigData{ + "service_table": []interface{}{ + map[string]interface{}{ + "service": "testing123", + "integration_key": "abc123xyz", + "id": json.Number("22222"), + }, + }, + }, + ExternalId: "999999", + OrganizationId: 2, + OrganizationIntegrationStatus: "active", + GracePeriodEnd: nil, + } + assert.Equal(t, &expected, integration) +} + +func TestOrganizationIntegrationsService_UpdateConfig(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/api/0/organizations/the-interstellar-jurisdiction/integrations/456789/", func(w http.ResponseWriter, r *http.Request) { + assertMethod(t, "POST", r) + w.Header().Set("Content-Type", "application/json") + }) + + updateConfigOrganizationIntegrationsParams := UpdateConfigOrganizationIntegrationsParams{ + "service_table": []interface{}{ + map[string]interface{}{ + "service": "testing123", + "integration_key": "abc123xyz", + "id": json.Number("22222"), + }, + map[string]interface{}{ + "service": "testing456", + "integration_key": "efg456lmn", + "id": "", + }, + }, + } + ctx := context.Background() + resp, err := client.OrganizationIntegrations.UpdateConfig(ctx, "the-interstellar-jurisdiction", "456789", &updateConfigOrganizationIntegrationsParams) + assert.NoError(t, err) + assert.Equal(t, int64(0), resp.ContentLength) +}