From db8ac64f031513fef922cddf90b20d4269a34e46 Mon Sep 17 00:00:00 2001 From: srikanth597 Date: Tue, 31 Oct 2023 22:50:19 +0530 Subject: [PATCH] feat: workflow support --- lib/model.go | 49 ++++- lib/novu.go | 4 +- lib/workflow.go | 138 ++++++++++++ lib/workflow_test.go | 196 ++++++++++++++++++ testdata/create_workflow_response.json | 91 ++++++++ testdata/get_workflow_response.json | 91 ++++++++ testdata/update_workflow_response.json | 91 ++++++++ testdata/update_workflow_status_response.json | 91 ++++++++ 8 files changed, 746 insertions(+), 5 deletions(-) create mode 100644 lib/workflow.go create mode 100644 lib/workflow_test.go create mode 100644 testdata/create_workflow_response.json create mode 100644 testdata/get_workflow_response.json create mode 100644 testdata/update_workflow_response.json create mode 100644 testdata/update_workflow_status_response.json diff --git a/lib/model.go b/lib/model.go index cf0f22f..bf9bad6 100644 --- a/lib/model.go +++ b/lib/model.go @@ -546,9 +546,50 @@ type ChangesApplyResponse struct { Data []ChangesGetResponseData `json:"data,omitempty"` } - type UpdateTenantRequest struct { - Name string `json:"name"` - Data map[string]interface{} `json:"data"` - Identifier string `json:"identifier"` + Name string `json:"name"` + Data map[string]interface{} `json:"data"` + Identifier string `json:"identifier"` +} + +type WorkflowGetRequestOptions struct { + Page int `json:"page,omitempty"` + Limit int `json:"limit,omitempty"` +} + +type WorkflowData struct { + Id string `json:"_id,omitempty"` + Name string `json:"name"` + Description string `json:"description"` + Active bool `json:"active,omitempty"` + Draft bool `json:"draft,omitempty"` + PreferenceSettings Channel `json:"preferenceSettings"` + Critical bool `json:"critical"` + Tags []string `json:"tags"` + Steps []interface{} `json:"steps"` + OrganizationId string `json:"_organizationId,omitempty"` + CreatorId string `json:"_creatorId,omitempty"` + EnvironmentId string `json:"_environmentId,omitempty"` + Triggers []interface{} `json:"triggers,omitempty"` + NotificationGroupID string `json:"_notificationGroupId,omitempty"` + NotificationGroupId string `json:"notificationGroupId,omitempty"` + ParentId string `json:"_parentId,omitempty"` + Deleted bool `json:"deleted,omitempty"` + DeletedAt string `json:"deletedAt,omitempty"` + DeletedBy string `json:"deletedBy,omitempty"` + NotificationGroup interface{} `json:"notificationGroup,omitempty"` + Data interface{} `json:"data,omitempty"` + WorkflowIntegrationStatus interface{} `json:"workflowIntegrationStatus,omitempty"` + BlueprintId string `json:"blueprintId,omitempty"` +} +type WorkflowGetResponse struct { + Data WorkflowData `json:"data"` +} + +type WorkflowDeleteResponse struct { + Data bool `json:"data"` +} + +type WorkflowUpdateStatus struct { + Active bool `json:"active"` } diff --git a/lib/novu.go b/lib/novu.go index 5f5925c..0f35397 100644 --- a/lib/novu.go +++ b/lib/novu.go @@ -51,7 +51,8 @@ type APIClient struct { IntegrationsApi *IntegrationService InboundParserApi *InboundParserService LayoutApi *LayoutService - TenantApi *TenantService + TenantApi *TenantService + WorkflowApi *WorkflowService } type service struct { @@ -112,6 +113,7 @@ func NewAPIClient(apiKey string, cfg *Config) *APIClient { c.LayoutApi = (*LayoutService)(&c.common) c.BlueprintApi = (*BlueprintService)(&c.common) c.TenantApi = (*TenantService)(&c.common) + c.WorkflowApi = (*WorkflowService)(&c.common) return c } diff --git a/lib/workflow.go b/lib/workflow.go new file mode 100644 index 0000000..470cb94 --- /dev/null +++ b/lib/workflow.go @@ -0,0 +1,138 @@ +package lib + +import ( + "bytes" + "context" + "encoding/json" + "net/http" +) + +type WorkflowService service + +func (w *WorkflowService) List(ctx context.Context, options *WorkflowGetRequestOptions) (*WorkflowGetResponse, error) { + var resp WorkflowGetResponse + URL := w.client.config.BackendURL.JoinPath("workflows") + if options == nil { + options = &WorkflowGetRequestOptions{} + } + queryParams, _ := json.Marshal(options) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, URL.String(), bytes.NewBuffer(queryParams)) + if err != nil { + return nil, err + } + + _, err = w.client.sendRequest(req, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +func (w *WorkflowService) Create(ctx context.Context, request WorkflowData) (*WorkflowData, error) { + var resp WorkflowData + URL := w.client.config.BackendURL.JoinPath("workflows") + + requestBody := WorkflowData{ + Name: request.Name, + NotificationGroupId: request.NotificationGroupId, + Tags: request.Tags, + Description: request.Description, + Steps: request.Steps, + Active: request.Active, + Critical: request.Critical, + PreferenceSettings: request.PreferenceSettings, + BlueprintId: request.BlueprintId, + Data: request.Data, + } + + jsonBody, _ := json.Marshal(requestBody) + b := bytes.NewBuffer(jsonBody) + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, URL.String(), b) + if err != nil { + return nil, err + } + _, err = w.client.sendRequest(req, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +func (w *WorkflowService) Update(ctx context.Context, key string, request WorkflowData) (*WorkflowData, error) { + + var resp WorkflowData + URL := w.client.config.BackendURL.JoinPath("workflows", key) + + requestBody := WorkflowData{ + Name: request.Name, + Tags: request.Tags, + Description: request.Description, + Steps: request.Steps, + NotificationGroupId: request.NotificationGroupId, + Critical: request.Critical, + PreferenceSettings: request.PreferenceSettings, + Data: request.Data, + } + + jsonBody, _ := json.Marshal(requestBody) + b := bytes.NewBuffer(jsonBody) + + req, err := http.NewRequestWithContext(ctx, http.MethodPut, URL.String(), b) + if err != nil { + return nil, err + } + _, err = w.client.sendRequest(req, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +func (w *WorkflowService) Delete(ctx context.Context, key string) (*WorkflowDeleteResponse, error) { + var resp WorkflowDeleteResponse + URL := w.client.config.BackendURL.JoinPath("workflows", key) + + req, err := http.NewRequestWithContext(ctx, http.MethodDelete, URL.String(), http.NoBody) + if err != nil { + return nil, err + } + _, err = w.client.sendRequest(req, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +func (w *WorkflowService) Get(ctx context.Context, key string) (*WorkflowData, error) { + var resp WorkflowData + URL := w.client.config.BackendURL.JoinPath("workflows", key) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, URL.String(), bytes.NewBuffer([]byte{})) + if err != nil { + return nil, err + } + + _, err = w.client.sendRequest(req, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +func (w *WorkflowService) UpdateStatus(ctx context.Context, key string) (*WorkflowData, error) { + var resp WorkflowData + URL := w.client.config.BackendURL.JoinPath("workflows", key, "status") + + req, err := http.NewRequestWithContext(ctx, http.MethodPut, URL.String(), http.NoBody) + if err != nil { + return nil, err + } + _, err = w.client.sendRequest(req, &resp) + if err != nil { + return nil, err + } + return &resp, nil + +} diff --git a/lib/workflow_test.go b/lib/workflow_test.go new file mode 100644 index 0000000..ee8e94f --- /dev/null +++ b/lib/workflow_test.go @@ -0,0 +1,196 @@ +package lib_test + +import ( + "context" + "fmt" + "net/http" + "path/filepath" + "testing" + + "github.com/novuhq/go-novu/lib" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const workflowId = "workflowId" +const WORKFLOW_PATH = "/v1/workflows" + +func TestWorkflowService_List_Workflows_Success(t *testing.T) { + body := map[string]string{} + var expectedResponse *lib.WorkflowGetResponse = &lib.WorkflowGetResponse{ + Data: lib.WorkflowData{ + Id: workflowId, + Name: "workflowName", + NotificationGroupId: "notificationGroupId", + Tags: []string{"tag1", "tag2"}, + Description: "workflowDescription", + Steps: []interface{}(nil), + Active: true, + Critical: true, + PreferenceSettings: lib.Channel{ + Email: true, + Sms: true, + Chat: true, + InApp: true, + Push: true, + }, + BlueprintId: "blueprintId", + Data: nil, + }, + } + + httpServer := createTestServer(t, TestServerOptions[map[string]string, lib.WorkflowGetResponse]{ + expectedURLPath: WORKFLOW_PATH, + expectedSentMethod: http.MethodGet, + expectedSentBody: body, + responseStatusCode: http.StatusOK, + responseBody: *expectedResponse, + }) + + ctx := context.Background() + c := lib.NewAPIClient(novuApiKey, &lib.Config{BackendURL: lib.MustParseURL(httpServer.URL)}) + resp, err := c.WorkflowApi.List(ctx, nil) + + require.NoError(t, err) + require.Equal(t, expectedResponse, resp) +} + +func TestWorkflowService_Create_Workflow_Success(t *testing.T) { + + var createWorkflowRequest *lib.WorkflowData = &lib.WorkflowData{ + Name: "workflowName", + NotificationGroupId: "notificationGroupId", + Tags: []string{"tag1", "tag2"}, + Description: "workflowDescription", + Steps: []interface{}(nil), + Active: true, + Critical: true, + PreferenceSettings: lib.Channel{ + Email: true, + Sms: true, + Chat: true, + InApp: true, + Push: true, + }, + BlueprintId: "blueprintId", + Data: nil, + } + + var response *lib.WorkflowData + fileToStruct(filepath.Join("../testdata", "create_workflow_response.json"), &response) + + httpServer := createTestServer(t, TestServerOptions[lib.WorkflowData, lib.WorkflowData]{ + expectedURLPath: WORKFLOW_PATH, + expectedSentMethod: http.MethodPost, + expectedSentBody: *createWorkflowRequest, + responseStatusCode: http.StatusCreated, + responseBody: *response, + }) + + ctx := context.Background() + c := lib.NewAPIClient(novuApiKey, &lib.Config{BackendURL: lib.MustParseURL(httpServer.URL)}) + res, err := c.WorkflowApi.Create(ctx, *createWorkflowRequest) + + require.Equal(t, response, res) + require.NoError(t, err) + +} + +func TestWorkflowService_Update_Success(t *testing.T) { + + var updateWorkflowRequest *lib.WorkflowData = &lib.WorkflowData{ + Name: "workflowName", + NotificationGroupId: "notificationGroupId", + Tags: []string{"tag1", "tag2"}, + Description: "workflowDescription", + Steps: []interface{}(nil), + Active: false, + Critical: true, + PreferenceSettings: lib.Channel{ + Email: true, + Sms: true, + Chat: true, + InApp: true, + Push: true, + }, + Data: nil, + } + var response *lib.WorkflowData + fileToStruct(filepath.Join("../testdata", "update_workflow_response.json"), &response) + + httpServer := createTestServer(t, TestServerOptions[lib.WorkflowData, lib.WorkflowData]{ + expectedURLPath: fmt.Sprintf("/v1/workflows/%s", workflowId), + expectedSentMethod: http.MethodPut, + expectedSentBody: *updateWorkflowRequest, + responseStatusCode: http.StatusOK, + responseBody: *response, + }) + + ctx := context.Background() + c := lib.NewAPIClient(novuApiKey, &lib.Config{BackendURL: lib.MustParseURL(httpServer.URL)}) + res, err := c.WorkflowApi.Update(ctx, workflowId, *updateWorkflowRequest) + + assert.Equal(t, response, res) + require.NoError(t, err) + +} + +func TestWorkflowService_Delete_Success(t *testing.T) { + + var DeleteResponse *lib.WorkflowDeleteResponse = &lib.WorkflowDeleteResponse{ + Data: true, + } + + httpServer := createTestServer(t, TestServerOptions[map[string]string, lib.WorkflowDeleteResponse]{ + expectedURLPath: fmt.Sprintf("/v1/workflows/%s", workflowId), + expectedSentMethod: http.MethodDelete, + responseStatusCode: http.StatusOK, + responseBody: *DeleteResponse, + }) + + ctx := context.Background() + c := lib.NewAPIClient(novuApiKey, &lib.Config{BackendURL: lib.MustParseURL(httpServer.URL)}) + res, err := c.WorkflowApi.Delete(ctx, workflowId) + + require.Equal(t, DeleteResponse, res) + require.NoError(t, err) +} + +func TestWorkflowService_Get_Success(t *testing.T) { + + var response *lib.WorkflowData + fileToStruct(filepath.Join("../testdata", "get_workflow_response.json"), &response) + + httpServer := createTestServer(t, TestServerOptions[map[string]string, lib.WorkflowData]{ + expectedURLPath: fmt.Sprintf("/v1/workflows/%s", workflowId), + expectedSentMethod: http.MethodGet, + responseStatusCode: http.StatusOK, + responseBody: *response, + }) + + ctx := context.Background() + c := lib.NewAPIClient(novuApiKey, &lib.Config{BackendURL: lib.MustParseURL(httpServer.URL)}) + res, err := c.WorkflowApi.Get(ctx, workflowId) + + require.Equal(t, response, res) + require.NoError(t, err) +} + +func TestWorkflowService_UpdateStatus_Success(t *testing.T) { + + var response *lib.WorkflowData + fileToStruct(filepath.Join("../testdata", "update_workflow_status_response.json"), &response) + httpServer := createTestServer(t, TestServerOptions[map[string]string, lib.WorkflowData]{ + expectedURLPath: fmt.Sprintf("/v1/workflows/%s/status", workflowId), + expectedSentMethod: http.MethodPut, + responseStatusCode: http.StatusOK, + responseBody: *response, + }) + + ctx := context.Background() + c := lib.NewAPIClient(novuApiKey, &lib.Config{BackendURL: lib.MustParseURL(httpServer.URL)}) + res, err := c.WorkflowApi.UpdateStatus(ctx, workflowId) + + require.Equal(t, response, res) + require.NoError(t, err) +} diff --git a/testdata/create_workflow_response.json b/testdata/create_workflow_response.json new file mode 100644 index 0000000..a6c3259 --- /dev/null +++ b/testdata/create_workflow_response.json @@ -0,0 +1,91 @@ +{ + "data": { + "_id": "string", + "name": "string", + "description": "string", + "active": true, + "draft": true, + "preferenceSettings": { + "email": true, + "sms": true, + "in_app": true, + "chat": true, + "push": true + }, + "critical": true, + "tags": [ + "string" + ], + "steps": [ + { + "_id": "string", + "uuid": "string", + "name": "string", + "_templateId": "string", + "active": true, + "shouldStopOnFail": true, + "template": {}, + "filters": [ + { + "isNegated": true, + "type": "BOOLEAN", + "value": "AND", + "children": [ + { + "field": "string", + "value": "string", + "operator": "LARGER", + "on": "subscriber" + } + ] + } + ], + "_parentId": {}, + "metadata": { + "amount": 0, + "unit": "seconds", + "digestKey": "string", + "type": "regular", + "backoff": true, + "backoffAmount": 0, + "backoffUnit": "seconds", + "updateMode": true + }, + "replyCallback": {} + } + ], + "_organizationId": "string", + "_creatorId": "string", + "_environmentId": "string", + "triggers": [ + { + "type": "string", + "identifier": "string", + "variables": [ + { + "name": "string" + } + ], + "subscriberVariables": [ + { + "name": "string" + } + ] + } + ], + "_notificationGroupId": "string", + "_parentId": "string", + "deleted": true, + "deletedAt": "string", + "deletedBy": "string", + "notificationGroup": { + "_id": "string", + "name": "string", + "_environmentId": "string", + "_organizationId": "string", + "_parentId": "string" + }, + "data": {}, + "workflowIntegrationStatus": {} + } + } \ No newline at end of file diff --git a/testdata/get_workflow_response.json b/testdata/get_workflow_response.json new file mode 100644 index 0000000..a6c3259 --- /dev/null +++ b/testdata/get_workflow_response.json @@ -0,0 +1,91 @@ +{ + "data": { + "_id": "string", + "name": "string", + "description": "string", + "active": true, + "draft": true, + "preferenceSettings": { + "email": true, + "sms": true, + "in_app": true, + "chat": true, + "push": true + }, + "critical": true, + "tags": [ + "string" + ], + "steps": [ + { + "_id": "string", + "uuid": "string", + "name": "string", + "_templateId": "string", + "active": true, + "shouldStopOnFail": true, + "template": {}, + "filters": [ + { + "isNegated": true, + "type": "BOOLEAN", + "value": "AND", + "children": [ + { + "field": "string", + "value": "string", + "operator": "LARGER", + "on": "subscriber" + } + ] + } + ], + "_parentId": {}, + "metadata": { + "amount": 0, + "unit": "seconds", + "digestKey": "string", + "type": "regular", + "backoff": true, + "backoffAmount": 0, + "backoffUnit": "seconds", + "updateMode": true + }, + "replyCallback": {} + } + ], + "_organizationId": "string", + "_creatorId": "string", + "_environmentId": "string", + "triggers": [ + { + "type": "string", + "identifier": "string", + "variables": [ + { + "name": "string" + } + ], + "subscriberVariables": [ + { + "name": "string" + } + ] + } + ], + "_notificationGroupId": "string", + "_parentId": "string", + "deleted": true, + "deletedAt": "string", + "deletedBy": "string", + "notificationGroup": { + "_id": "string", + "name": "string", + "_environmentId": "string", + "_organizationId": "string", + "_parentId": "string" + }, + "data": {}, + "workflowIntegrationStatus": {} + } + } \ No newline at end of file diff --git a/testdata/update_workflow_response.json b/testdata/update_workflow_response.json new file mode 100644 index 0000000..a6c3259 --- /dev/null +++ b/testdata/update_workflow_response.json @@ -0,0 +1,91 @@ +{ + "data": { + "_id": "string", + "name": "string", + "description": "string", + "active": true, + "draft": true, + "preferenceSettings": { + "email": true, + "sms": true, + "in_app": true, + "chat": true, + "push": true + }, + "critical": true, + "tags": [ + "string" + ], + "steps": [ + { + "_id": "string", + "uuid": "string", + "name": "string", + "_templateId": "string", + "active": true, + "shouldStopOnFail": true, + "template": {}, + "filters": [ + { + "isNegated": true, + "type": "BOOLEAN", + "value": "AND", + "children": [ + { + "field": "string", + "value": "string", + "operator": "LARGER", + "on": "subscriber" + } + ] + } + ], + "_parentId": {}, + "metadata": { + "amount": 0, + "unit": "seconds", + "digestKey": "string", + "type": "regular", + "backoff": true, + "backoffAmount": 0, + "backoffUnit": "seconds", + "updateMode": true + }, + "replyCallback": {} + } + ], + "_organizationId": "string", + "_creatorId": "string", + "_environmentId": "string", + "triggers": [ + { + "type": "string", + "identifier": "string", + "variables": [ + { + "name": "string" + } + ], + "subscriberVariables": [ + { + "name": "string" + } + ] + } + ], + "_notificationGroupId": "string", + "_parentId": "string", + "deleted": true, + "deletedAt": "string", + "deletedBy": "string", + "notificationGroup": { + "_id": "string", + "name": "string", + "_environmentId": "string", + "_organizationId": "string", + "_parentId": "string" + }, + "data": {}, + "workflowIntegrationStatus": {} + } + } \ No newline at end of file diff --git a/testdata/update_workflow_status_response.json b/testdata/update_workflow_status_response.json new file mode 100644 index 0000000..a6c3259 --- /dev/null +++ b/testdata/update_workflow_status_response.json @@ -0,0 +1,91 @@ +{ + "data": { + "_id": "string", + "name": "string", + "description": "string", + "active": true, + "draft": true, + "preferenceSettings": { + "email": true, + "sms": true, + "in_app": true, + "chat": true, + "push": true + }, + "critical": true, + "tags": [ + "string" + ], + "steps": [ + { + "_id": "string", + "uuid": "string", + "name": "string", + "_templateId": "string", + "active": true, + "shouldStopOnFail": true, + "template": {}, + "filters": [ + { + "isNegated": true, + "type": "BOOLEAN", + "value": "AND", + "children": [ + { + "field": "string", + "value": "string", + "operator": "LARGER", + "on": "subscriber" + } + ] + } + ], + "_parentId": {}, + "metadata": { + "amount": 0, + "unit": "seconds", + "digestKey": "string", + "type": "regular", + "backoff": true, + "backoffAmount": 0, + "backoffUnit": "seconds", + "updateMode": true + }, + "replyCallback": {} + } + ], + "_organizationId": "string", + "_creatorId": "string", + "_environmentId": "string", + "triggers": [ + { + "type": "string", + "identifier": "string", + "variables": [ + { + "name": "string" + } + ], + "subscriberVariables": [ + { + "name": "string" + } + ] + } + ], + "_notificationGroupId": "string", + "_parentId": "string", + "deleted": true, + "deletedAt": "string", + "deletedBy": "string", + "notificationGroup": { + "_id": "string", + "name": "string", + "_environmentId": "string", + "_organizationId": "string", + "_parentId": "string" + }, + "data": {}, + "workflowIntegrationStatus": {} + } + } \ No newline at end of file