diff --git a/src/__tests__/_/api-handlers/actions.ts b/src/__tests__/_/api-handlers/actions.ts index 07cec6e02..966291c55 100644 --- a/src/__tests__/_/api-handlers/actions.ts +++ b/src/__tests__/_/api-handlers/actions.ts @@ -1,5 +1,4 @@ import { rest } from "msw"; -import { HTTP_ACTIVATION_ID } from "shared/types/actions"; import { BASE_TEST_URL } from "./_utils"; export const actionsApiHandlers = [ @@ -63,20 +62,7 @@ export const actionsApiHandlers = [ rest.get( BASE_TEST_URL("/api/integrations/actions/active"), async (_, res, ctx) => { - return res( - ctx.json([ - { - activationId: HTTP_ACTIVATION_ID, - integrationKey: "http", - credentialsGroupKey: "HTTP", - }, - { - activationId: "slack-activation-id", - integrationKey: "slack", - credentialsGroupKey: "SLACK", - }, - ]) - ); + return res(ctx.json(["http", "slack"])); } ), // rest.put( diff --git a/src/__tests__/api/_test-utils/_action-instances.ts b/src/__tests__/api/_test-utils/_action-instances.ts index 4ecf5f909..37523920d 100644 --- a/src/__tests__/api/_test-utils/_action-instances.ts +++ b/src/__tests__/api/_test-utils/_action-instances.ts @@ -1,27 +1,24 @@ import { createConfigDomainPersistenceService } from "backend/lib/config-persistence"; -import { HTTP_ACTIVATION_ID, IActionInstance } from "shared/types/actions"; +import { ActionIntegrationKeys, IActionInstance } from "shared/types/actions"; +import { DataEventActions } from "shared/types/data"; const TEST_ACTION_INSTANCES: IActionInstance[] = [ { instanceId: "instance-id-1", - activatedActionId: "activation-id-1", - integrationKey: "smtp", + integrationKey: ActionIntegrationKeys.SMTP, entity: "base-model", implementationKey: "SEND_MESSAGE", - triggerLogic: "some-test-trigger-logic", - formAction: "create", + formAction: DataEventActions.Create, configuration: { foo: "bar", }, }, { instanceId: "instance-id-2", - activatedActionId: HTTP_ACTIVATION_ID, - integrationKey: "http", + integrationKey: ActionIntegrationKeys.HTTP, entity: "secondary-model", implementationKey: "POST", - triggerLogic: "another-trigger-logic", - formAction: "delete", + formAction: DataEventActions.Delete, configuration: { bar: "foo", }, diff --git a/src/__tests__/api/_test-utils/_activated-actions.ts b/src/__tests__/api/_test-utils/_activated-actions.ts deleted file mode 100644 index 1deca4665..000000000 --- a/src/__tests__/api/_test-utils/_activated-actions.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { createConfigDomainPersistenceService } from "backend/lib/config-persistence"; -import { ActionIntegrationKeys, IActivatedAction } from "shared/types/actions"; - -const TEST_ACTIVATED_ACTIONS: IActivatedAction[] = [ - { - activationId: "smtp-activation-id-1", - integrationKey: ActionIntegrationKeys.SMTP, - credentialsGroupKey: "SMTP", - }, - { - activationId: "slack-activation-id-2", - integrationKey: ActionIntegrationKeys.SLACK, - credentialsGroupKey: "SLACK", - }, -]; - -export const setupActivatedActionTestData = async ( - testActivatedActions: IActivatedAction[] = TEST_ACTIVATED_ACTIONS -) => { - const configPersistenceService = - createConfigDomainPersistenceService("activated-actions"); - - await configPersistenceService.resetState( - "activationId", - testActivatedActions - ); -}; diff --git a/src/__tests__/api/_test-utils/_activated-integrations.ts b/src/__tests__/api/_test-utils/_activated-integrations.ts new file mode 100644 index 000000000..baba21ce1 --- /dev/null +++ b/src/__tests__/api/_test-utils/_activated-integrations.ts @@ -0,0 +1,20 @@ +import { createKeyValueDomainPersistenceService } from "backend/lib/key-value"; +import { ActionIntegrationKeys } from "shared/types/actions"; + +const TEST_ACTIVATED_ACTIONS: ActionIntegrationKeys[] = [ + ActionIntegrationKeys.SMTP, + ActionIntegrationKeys.SLACK, +]; + +export const setupActivatedIntegrationsTestData = async ( + testActivatedActions: ActionIntegrationKeys[] = TEST_ACTIVATED_ACTIONS +) => { + const activatedIntegrationsPersistenceService = + createKeyValueDomainPersistenceService( + "activated-integrations" + ); + + await activatedIntegrationsPersistenceService.persistItem( + testActivatedActions + ); +}; diff --git a/src/__tests__/api/_test-utils/_all.ts b/src/__tests__/api/_test-utils/_all.ts index bb9acb1d5..89fb74c72 100644 --- a/src/__tests__/api/_test-utils/_all.ts +++ b/src/__tests__/api/_test-utils/_all.ts @@ -9,7 +9,7 @@ import { setupRolesTestData } from "./_roles"; import { setupSchemaTestData } from "./_schema"; import { setupUsersTestData } from "./_users"; import { setupIntegrationsEnvTestData } from "./_integrations-env"; -import { setupActivatedActionTestData } from "./_activated-actions"; +import { setupActivatedIntegrationsTestData } from "./_activated-integrations"; import { setupActionInstanceTestData } from "./_action-instances"; import { setupTestDatabaseData } from "./_data"; import { portalTestData } from "./portal"; @@ -25,7 +25,7 @@ export const setupAllTestData = async (domains: DomainTypes[]) => { ["dashboard-widgets", setupDashboardTestData], ["schema", setupSchemaTestData], ["data", setupTestDatabaseData], - ["activated-actions", setupActivatedActionTestData], + ["activated-integrations", setupActivatedIntegrationsTestData], ["action-instances", setupActionInstanceTestData], ["constants", setupIntegrationsConstantsTestData], ["environment-variables", setupIntegrationsEnvTestData], diff --git a/src/__tests__/api/integrations/actions/[key]/credentials.spec.ts b/src/__tests__/api/integrations/actions/[key]/credentials.spec.ts index ed61f68b2..31594d2e8 100644 --- a/src/__tests__/api/integrations/actions/[key]/credentials.spec.ts +++ b/src/__tests__/api/integrations/actions/[key]/credentials.spec.ts @@ -1,24 +1,24 @@ import handler from "pages/api/integrations/actions/[key]/credentials"; -import { HTTP_ACTIVATION_ID } from "shared/types/actions"; import { createAuthenticatedMocks, setupAllTestData, setupCredentialsTestData, } from "__tests__/api/_test-utils"; +import { ActionIntegrationKeys } from "shared/types/actions"; describe("/api/integrations/actions/[key]/credentials", () => { beforeAll(async () => { - await setupAllTestData(["activated-actions", "users"]); + await setupAllTestData(["activated-integrations", "users"]); await setupCredentialsTestData({ RANDOM___authUser: "aad0f7e776963ae66b7459222d54871433f8e119ab9a9712d4e82e8cbb77246e47a750a773c0ea316c110a1d3f2ee16c2509906fb89f1c4b039d09f139b1d7eacc26908c25137c46f269cfb13f63221da2f1631bf4f59cbe14cc18cbfb8993098bd7e2d865f20717", - SMTP___authUser: + ACTION__SMTP___authUser: "aad0f7e776963ae66b7459222d54871433f8e119ab9a9712d4e82e8cbb77246e47a750a773c0ea316c110a1d3f2ee16c2509906fb89f1c4b039d09f139b1d7eacc26908c25137c46f269cfb13f63221da2f1631bf4f59cbe14cc18cbfb8993098bd7e2d865f20717", - SMTP___invalidField: + ACTION__SMTP___invalidField: "aad0f7e776963ae66b7459222d54871433f8e119ab9a9712d4e82e8cbb77246e47a750a773c0ea316c110a1d3f2ee16c2509906fb89f1c4b039d09f139b1d7eacc26908c25137c46f269cfb13f63221da2f1631bf4f59cbe14cc18cbfb8993098bd7e2d865f20717", - SMTP___host: + ACTION__SMTP___host: "aad0f7e776963ae66b7459222d54871433f8e119ab9a9712d4e82e8cbb77246e47a750a773c0ea316c110a1d3f2ee16c2509906fb89f1c4b039d09f139b1d7eacc26908c25137c46f269cfb13f63221da2f1631bf4f59cbe14cc18cbfb8993098bd7e2d865f20717", - SMTP___port: + ACTION__SMTP___port: "68ba76e500daa5d670930d24bbb425018571f18decc16d63ed901b85f6e99f74d3cf68225dcceb677b9a080cb1e8e8fd50abccbcf7d45fcf24c9578395b05b8aec57a763694e92fdbd2836bb91e66f17dc338bce18ae54cbb17098e1f1894c39870d7ff1cd", }); }); @@ -27,7 +27,7 @@ describe("/api/integrations/actions/[key]/credentials", () => { const { req, res } = createAuthenticatedMocks({ method: "POST", query: { - key: "http", + key: ActionIntegrationKeys.SMTP, }, body: { _password: "invalid password", @@ -51,7 +51,7 @@ describe("/api/integrations/actions/[key]/credentials", () => { const { req, res } = createAuthenticatedMocks({ method: "POST", query: { - key: "smtp-activation-id-1", + key: ActionIntegrationKeys.SMTP, }, body: { _password: "password", @@ -69,11 +69,11 @@ describe("/api/integrations/actions/[key]/credentials", () => { `); }); - it("should return empty for http activationId", async () => { + it("should return empty for http", async () => { const { req, res } = createAuthenticatedMocks({ method: "POST", query: { - key: HTTP_ACTIVATION_ID, + key: ActionIntegrationKeys.HTTP, }, body: { _password: "password", diff --git a/src/__tests__/api/integrations/actions/[key]/index.spec.ts b/src/__tests__/api/integrations/actions/[key]/index.spec.ts index 6f0b2bc03..414f7a18f 100644 --- a/src/__tests__/api/integrations/actions/[key]/index.spec.ts +++ b/src/__tests__/api/integrations/actions/[key]/index.spec.ts @@ -1,11 +1,15 @@ import handler from "pages/api/integrations/actions/[key]/index"; import activeHandler from "pages/api/integrations/actions/active"; +import getHandler from "pages/api/integrations/actions/instances/[key]"; + import credentialsHandler from "pages/api/integrations/actions/[key]/credentials"; import { createAuthenticatedMocks, setupAllTestData, } from "__tests__/api/_test-utils"; import { setupActionInstanceTestData } from "__tests__/api/_test-utils/_action-instances"; +import { ActionIntegrationKeys } from "shared/types/actions"; +import { DataEventActions } from "shared/types/data"; jest.mock("nanoid", () => ({ nanoid: jest.fn().mockReturnValue("nano-id-1"), @@ -13,41 +17,25 @@ jest.mock("nanoid", () => ({ describe("/api/integrations/actions/[key]/index", () => { beforeAll(async () => { - await setupAllTestData(["activated-actions", "users"]); + await setupAllTestData(["activated-integrations", "users"]); await setupActionInstanceTestData([ { instanceId: "instance-id-1", - activatedActionId: "nano-id-1", - integrationKey: "http", + integrationKey: ActionIntegrationKeys.HTTP, entity: "base-model", implementationKey: "SEND_MESSAGE", - triggerLogic: "some-test-trigger-logic", - formAction: "create", + formAction: DataEventActions.Create, configuration: { foo: "bar", }, }, { instanceId: "instance-id-2", - activatedActionId: "nano-id-1", - integrationKey: "slack", + integrationKey: ActionIntegrationKeys.SLACK, entity: "secondary-model", implementationKey: "POST", - triggerLogic: "another-trigger-logic", - formAction: "delete", - configuration: { - bar: "foo", - }, - }, - { - instanceId: "instance-id-3", - activatedActionId: "some-other-activation-id", - integrationKey: "http", - entity: "secondary-model", - implementationKey: "POST", - triggerLogic: "another-trigger-logic", - formAction: "delete", + formAction: DataEventActions.Delete, configuration: { bar: "foo", }, @@ -55,48 +43,6 @@ describe("/api/integrations/actions/[key]/index", () => { ]); }); - describe("GET", () => { - it("should show integrationKey action instances", async () => { - const { req, res } = createAuthenticatedMocks({ - method: "GET", - query: { - key: "http", - }, - }); - await handler(req, res); - - expect(res._getStatusCode()).toBe(200); - expect(res._getJSONData()).toMatchInlineSnapshot(` - [ - { - "activatedActionId": "nano-id-1", - "configuration": { - "foo": "bar", - }, - "entity": "base-model", - "formAction": "create", - "implementationKey": "SEND_MESSAGE", - "instanceId": "instance-id-1", - "integrationKey": "http", - "triggerLogic": "some-test-trigger-logic", - }, - { - "activatedActionId": "some-other-activation-id", - "configuration": { - "bar": "foo", - }, - "entity": "secondary-model", - "formAction": "delete", - "implementationKey": "POST", - "instanceId": "instance-id-3", - "integrationKey": "http", - "triggerLogic": "another-trigger-logic", - }, - ] - `); - }); - }); - describe("POST", () => { it("should activate an integration key and save the configurations", async () => { const { req, res } = createAuthenticatedMocks({ @@ -121,26 +67,10 @@ describe("/api/integrations/actions/[key]/index", () => { expect(activeRes._getJSONData()).toMatchInlineSnapshot(` [ - { - "activationId": "smtp-activation-id-1", - "credentialsGroupKey": "SMTP", - "integrationKey": "smtp", - }, - { - "activationId": "slack-activation-id-2", - "credentialsGroupKey": "SLACK", - "integrationKey": "slack", - }, - { - "activationId": "nano-id-1", - "credentialsGroupKey": "ACTION__SLACK", - "integrationKey": "slack", - }, - { - "activationId": "http", - "credentialsGroupKey": "none-existent", - "integrationKey": "http", - }, + "smtp", + "slack", + "slack", + "http", ] `); @@ -148,7 +78,7 @@ describe("/api/integrations/actions/[key]/index", () => { createAuthenticatedMocks({ method: "POST", query: { - key: "nano-id-1", + key: "slack", }, body: { _password: "password", @@ -216,21 +146,21 @@ describe("/api/integrations/actions/[key]/index", () => { expect(res._getStatusCode()).toBe(400); expect(res._getJSONData()).toMatchInlineSnapshot(` - { - "message": "Invalid Password", - "method": "PATCH", - "name": "BadRequestError", - "path": "", - "statusCode": 400, - } - `); + { + "message": "Invalid Password", + "method": "PATCH", + "name": "BadRequestError", + "path": "", + "statusCode": 400, + } + `); }); it("should update action configuration", async () => { const { req, res } = createAuthenticatedMocks({ method: "PATCH", query: { - key: "nano-id-1", + key: "slack", }, body: { token: "updated-token", @@ -251,7 +181,7 @@ describe("/api/integrations/actions/[key]/index", () => { createAuthenticatedMocks({ method: "POST", query: { - key: "nano-id-1", + key: "slack", }, body: { _password: "password", @@ -271,7 +201,7 @@ describe("/api/integrations/actions/[key]/index", () => { const { req, res } = createAuthenticatedMocks({ method: "PATCH", query: { - key: "nano-id-1", + key: "slack", }, body: { hello: "there", @@ -298,7 +228,7 @@ describe("/api/integrations/actions/[key]/index", () => { createAuthenticatedMocks({ method: "POST", query: { - key: "nano-id-1", + key: "slack", }, body: { _password: "password", @@ -316,11 +246,24 @@ describe("/api/integrations/actions/[key]/index", () => { }); describe("DELETE", () => { + it("should list integration instances", async () => { + const { req: getReq, res: resReq } = createAuthenticatedMocks({ + method: "GET", + query: { + key: "secondary-model", + }, + }); + await getHandler(getReq, resReq); + + expect(resReq._getStatusCode()).toBe(200); + + expect(resReq._getJSONData()).toHaveLength(1); + }); it("should deactivate activated actions", async () => { const { req, res } = createAuthenticatedMocks({ method: "DELETE", query: { - key: "nano-id-1", + key: "slack", }, }); await handler(req, res); @@ -336,24 +279,11 @@ describe("/api/integrations/actions/[key]/index", () => { await activeHandler(activeReq, activeRes); expect(activeRes._getJSONData()).toMatchInlineSnapshot(` - [ - { - "activationId": "smtp-activation-id-1", - "credentialsGroupKey": "SMTP", - "integrationKey": "smtp", - }, - { - "activationId": "slack-activation-id-2", - "credentialsGroupKey": "SLACK", - "integrationKey": "slack", - }, - { - "activationId": "http", - "credentialsGroupKey": "none-existent", - "integrationKey": "http", - }, - ] - `); + [ + "smtp", + "http", + ] + `); }); it("should remove access to credentials", async () => { @@ -361,7 +291,7 @@ describe("/api/integrations/actions/[key]/index", () => { createAuthenticatedMocks({ method: "POST", query: { - key: "nano-id-1", + key: "slack", }, body: { _password: "password", @@ -369,29 +299,28 @@ describe("/api/integrations/actions/[key]/index", () => { }); await credentialsHandler(credentialsReq, credentialsRes); - expect(credentialsRes._getStatusCode()).toBe(404); + expect(credentialsRes._getStatusCode()).toBe(400); expect(credentialsRes._getJSONData()).toMatchInlineSnapshot(` - { - "message": "nano-id-1 not found for 'activated-actions'", - "method": "POST", - "name": "NotFoundError", - "path": "", - "statusCode": 404, - } - `); + { + "message": "No credentials available for ACTION__SLACK", + "method": "POST", + "name": "BadRequestError", + "path": "", + "statusCode": 400, + } + `); }); it("should delete all instances", async () => { const { req: getReq, res: resReq } = createAuthenticatedMocks({ method: "GET", query: { - key: "http", + key: "secondary-model", }, }); - await handler(getReq, resReq); + await getHandler(getReq, resReq); - expect(resReq._getStatusCode()).toBe(200); - expect(resReq._getJSONData()).toHaveLength(1); + expect(resReq._getJSONData()).toHaveLength(0); }); }); }); diff --git a/src/__tests__/api/integrations/actions/active.spec.ts b/src/__tests__/api/integrations/actions/active.spec.ts index 8cdb4217a..367c82d8b 100644 --- a/src/__tests__/api/integrations/actions/active.spec.ts +++ b/src/__tests__/api/integrations/actions/active.spec.ts @@ -6,7 +6,7 @@ import { describe("/api/integrations/actions/active", () => { beforeAll(async () => { - await setupAllTestData(["activated-actions"]); + await setupAllTestData(["activated-integrations"]); }); it("should show all activated actions in addition with HTTP", async () => { @@ -18,21 +18,9 @@ describe("/api/integrations/actions/active", () => { expect(res._getJSONData()).toMatchInlineSnapshot(` [ - { - "activationId": "smtp-activation-id-1", - "credentialsGroupKey": "SMTP", - "integrationKey": "smtp", - }, - { - "activationId": "slack-activation-id-2", - "credentialsGroupKey": "SLACK", - "integrationKey": "slack", - }, - { - "activationId": "http", - "credentialsGroupKey": "none-existent", - "integrationKey": "http", - }, + "smtp", + "slack", + "http", ] `); }); diff --git a/src/__tests__/api/integrations/actions/instances/[key].spec.ts b/src/__tests__/api/integrations/actions/instances/[key].spec.ts index fa26a3bf5..6d517e519 100644 --- a/src/__tests__/api/integrations/actions/instances/[key].spec.ts +++ b/src/__tests__/api/integrations/actions/instances/[key].spec.ts @@ -1,56 +1,49 @@ import handler from "pages/api/integrations/actions/instances/[key]"; -import { HTTP_ACTIVATION_ID, IActionInstance } from "shared/types/actions"; +import { ActionIntegrationKeys, IActionInstance } from "shared/types/actions"; import { createAuthenticatedMocks, setupAllTestData, } from "__tests__/api/_test-utils"; import { setupActionInstanceTestData } from "__tests__/api/_test-utils/_action-instances"; +import { DataEventActions } from "shared/types/data"; const TEST_ACTION_INSTANCES: IActionInstance[] = [ { instanceId: "instance-id-1", - activatedActionId: "activation-id-1", - integrationKey: "smtp", + integrationKey: ActionIntegrationKeys.SMTP, entity: "base-model", implementationKey: "SEND_MESSAGE", - triggerLogic: "some-test-trigger-logic", - formAction: "create", + formAction: DataEventActions.Create, configuration: { foo: "bar", }, }, { instanceId: "instance-id-2", - activatedActionId: "activation-id-1", - integrationKey: "smtp", + integrationKey: ActionIntegrationKeys.SMTP, entity: "base-model", implementationKey: "SEND_MESSAGE", - triggerLogic: "some-test-trigger-logic-2", - formAction: "delete", + formAction: DataEventActions.Delete, configuration: { foo1: "bar1", }, }, { instanceId: "instance-id-3", - activatedActionId: "activation-id-1", - integrationKey: "smtp", + integrationKey: ActionIntegrationKeys.SMTP, entity: "base-model", implementationKey: "SEND_MESSAGE", - triggerLogic: "some-test-trigger-logic-3", - formAction: "update", + formAction: DataEventActions.Update, configuration: { foo2: "bar2", }, }, { instanceId: "instance-id-4", - activatedActionId: HTTP_ACTIVATION_ID, - integrationKey: "http", + integrationKey: ActionIntegrationKeys.HTTP, entity: "secondary-model", implementationKey: "POST", - triggerLogic: "another-trigger-logic", - formAction: "delete", + formAction: DataEventActions.Delete, configuration: { bar: "foo", }, @@ -59,7 +52,7 @@ const TEST_ACTION_INSTANCES: IActionInstance[] = [ describe("/api/integrations/actions/instances/[key]", () => { beforeAll(async () => { - await setupAllTestData(["activated-actions"]); + await setupAllTestData(["activated-integrations"]); await setupActionInstanceTestData(TEST_ACTION_INSTANCES); }); @@ -76,7 +69,6 @@ describe("/api/integrations/actions/instances/[key]", () => { expect(res._getJSONData()).toMatchInlineSnapshot(` [ { - "activatedActionId": "activation-id-1", "configuration": { "foo": "bar", }, @@ -85,10 +77,8 @@ describe("/api/integrations/actions/instances/[key]", () => { "implementationKey": "SEND_MESSAGE", "instanceId": "instance-id-1", "integrationKey": "smtp", - "triggerLogic": "some-test-trigger-logic", }, { - "activatedActionId": "activation-id-1", "configuration": { "foo1": "bar1", }, @@ -97,10 +87,8 @@ describe("/api/integrations/actions/instances/[key]", () => { "implementationKey": "SEND_MESSAGE", "instanceId": "instance-id-2", "integrationKey": "smtp", - "triggerLogic": "some-test-trigger-logic-2", }, { - "activatedActionId": "activation-id-1", "configuration": { "foo2": "bar2", }, @@ -109,7 +97,6 @@ describe("/api/integrations/actions/instances/[key]", () => { "implementationKey": "SEND_MESSAGE", "instanceId": "instance-id-3", "integrationKey": "smtp", - "triggerLogic": "some-test-trigger-logic-3", }, ] `); @@ -145,11 +132,9 @@ describe("/api/integrations/actions/instances/[key]", () => { key: "instance-id-2", }, body: { - activatedActionId: "activation-id-2-updated", integrationKey: "slack", entity: "base-model", implementationKey: "SEND_MESSAGE_UPDATED", - triggerLogic: "updated-trigger-logic", formAction: "update", configuration: { you: "are", @@ -173,7 +158,6 @@ describe("/api/integrations/actions/instances/[key]", () => { expect(getRes._getJSONData()).toMatchInlineSnapshot(` [ { - "activatedActionId": "activation-id-1", "configuration": { "foo": "bar", }, @@ -182,10 +166,8 @@ describe("/api/integrations/actions/instances/[key]", () => { "implementationKey": "SEND_MESSAGE", "instanceId": "instance-id-1", "integrationKey": "smtp", - "triggerLogic": "some-test-trigger-logic", }, { - "activatedActionId": "activation-id-2-updated", "configuration": { "awe": "some", "you": "are", @@ -195,7 +177,6 @@ describe("/api/integrations/actions/instances/[key]", () => { "implementationKey": "SEND_MESSAGE_UPDATED", "instanceId": "instance-id-2", "integrationKey": "slack", - "triggerLogic": "updated-trigger-logic", }, ] `); diff --git a/src/__tests__/api/integrations/actions/instances/index.spec.ts b/src/__tests__/api/integrations/actions/instances/index.spec.ts index 397eeb19e..b86c52d21 100644 --- a/src/__tests__/api/integrations/actions/instances/index.spec.ts +++ b/src/__tests__/api/integrations/actions/instances/index.spec.ts @@ -1,6 +1,5 @@ import handler from "pages/api/integrations/actions/instances/index"; import getHandler from "pages/api/integrations/actions/instances/[key]"; -import { HTTP_ACTIVATION_ID } from "shared/types/actions"; import { createAuthenticatedMocks, setupAllTestData, @@ -15,17 +14,16 @@ jest.mock("nanoid", () => ({ describe("/api/integrations/actions/instances/index", () => { beforeAll(async () => { - await setupAllTestData(["action-instances", "activated-actions"]); + await setupAllTestData(["action-instances", "activated-integrations"]); }); it("should instantiate actions", async () => { const { req, res } = createAuthenticatedMocks({ method: "POST", body: { - activatedActionId: "smtp-activation-id-1", entity: "test-entity", + integrationKey: "smtp", implementationKey: "SEND_MESSAGE", - triggerLogic: "some trigger logic", formAction: "update", configuration: { to: "me", @@ -44,10 +42,9 @@ describe("/api/integrations/actions/instances/index", () => { const { req, res } = createAuthenticatedMocks({ method: "POST", body: { - activatedActionId: HTTP_ACTIVATION_ID, entity: "test-entity", + integrationKey: "http", implementationKey: "PUT", - triggerLogic: "some trigger logic", formAction: "create", configuration: { url: "/some-where", @@ -62,14 +59,13 @@ describe("/api/integrations/actions/instances/index", () => { expect(res._getStatusCode()).toBe(201); }); - it("should not instantiate action for unknown activationId", async () => { + it("should not instantiate action for un-activated integration", async () => { const { req, res } = createAuthenticatedMocks({ method: "POST", body: { - activatedActionId: "THIS_ACTIVATION_ID_DOES_NOT_EXIST", + integrationKey: "postmark", entity: "test-entity-2", implementationKey: "GET", - triggerLogic: "some trigger logic", formAction: "update", configuration: { bad: '{"request": "hello"}', @@ -82,7 +78,7 @@ describe("/api/integrations/actions/instances/index", () => { expect(res._getStatusCode()).toBe(400); expect(res._getJSONData()).toMatchInlineSnapshot(` { - "message": "Integration Key not found for activatedActionId 'THIS_ACTIVATION_ID_DOES_NOT_EXIST'", + "message": "The integration for 'postmark' has not yet been activated", "method": "POST", "name": "BadRequestError", "path": "", @@ -104,7 +100,6 @@ describe("/api/integrations/actions/instances/index", () => { expect(res._getJSONData()).toMatchInlineSnapshot(` [ { - "activatedActionId": "smtp-activation-id-1", "configuration": { "body": "You are awesome", "subject": "something important", @@ -115,10 +110,8 @@ describe("/api/integrations/actions/instances/index", () => { "implementationKey": "SEND_MESSAGE", "instanceId": "nano-id-1", "integrationKey": "smtp", - "triggerLogic": "some trigger logic", }, { - "activatedActionId": "http", "configuration": { "body": "{"me": "you"}", "headers": "{"me": "you"}", @@ -129,7 +122,6 @@ describe("/api/integrations/actions/instances/index", () => { "implementationKey": "PUT", "instanceId": "nano-id-2", "integrationKey": "http", - "triggerLogic": "some trigger logic", }, ] `); diff --git a/src/__tests__/api/integrations/credentials/[key].spec.ts b/src/__tests__/api/integrations/credentials/[key].spec.ts index 64a0ae2f3..5c8bbde98 100644 --- a/src/__tests__/api/integrations/credentials/[key].spec.ts +++ b/src/__tests__/api/integrations/credentials/[key].spec.ts @@ -322,7 +322,7 @@ describe("/api/integrations/credentials/[key]", () => { setupRolesTestData([ { id: "custom-role", - permissions: ["CAN_MANAGE_INTEGRATIONS"], + permissions: ["CAN_MANAGE_APP_CREDENTIALS"], }, ]), setupUsersTestData([ diff --git a/src/__tests__/api/integrations/credentials/reveal.spec.ts b/src/__tests__/api/integrations/credentials/reveal.spec.ts index cd754301b..6ff9a7ea8 100644 --- a/src/__tests__/api/integrations/credentials/reveal.spec.ts +++ b/src/__tests__/api/integrations/credentials/reveal.spec.ts @@ -26,7 +26,7 @@ describe("/api/integrations/credentials/reveal", () => { await setupRolesTestData([ { id: "custom-role", - permissions: ["CAN_MANAGE_INTEGRATIONS"], + permissions: ["CAN_MANAGE_APP_CREDENTIALS"], }, ]); @@ -54,7 +54,7 @@ describe("/api/integrations/credentials/reveal", () => { await setupRolesTestData([ { id: "custom-role", - permissions: ["CAN_MANAGE_INTEGRATIONS"], + permissions: ["CAN_MANAGE_APP_CREDENTIALS"], }, ]); @@ -102,7 +102,7 @@ describe("/api/integrations/credentials/reveal", () => { expect(res._getJSONData()).toMatchInlineSnapshot(` { "errorCode": "", - "message": "Your account doesn't have enough priviledge to perform this action: (Can Manage Integrations)", + "message": "Your account doesn't have enough priviledge to perform this action: (Can Manage App Credentials)", "method": "POST", "name": "ForbiddenError", "path": "", diff --git a/src/__tests__/api/integrations/storage/list.spec.ts b/src/__tests__/api/integrations/storage/list.spec.ts index 1f80d4a18..673c6a594 100644 --- a/src/__tests__/api/integrations/storage/list.spec.ts +++ b/src/__tests__/api/integrations/storage/list.spec.ts @@ -38,7 +38,6 @@ describe("/api/integrations/storage/list", () => { ], }, }, - "description": "", "key": "s3", "title": "AWS S3", }, @@ -69,7 +68,6 @@ describe("/api/integrations/storage/list", () => { ], }, }, - "description": "", "key": "cloudinary", "title": "Cloudinary", }, @@ -100,7 +98,6 @@ describe("/api/integrations/storage/list", () => { ], }, }, - "description": "", "key": "google", "title": "Google Cloud Storage", }, diff --git a/src/__tests__/api/roles/[roleId]/permissions.spec.ts b/src/__tests__/api/roles/[roleId]/permissions.spec.ts index e2bde9fe1..7b2d0eb2b 100644 --- a/src/__tests__/api/roles/[roleId]/permissions.spec.ts +++ b/src/__tests__/api/roles/[roleId]/permissions.spec.ts @@ -52,7 +52,7 @@ describe("/api/roles/[roleId]/permissions", () => { roleId: "some-admin-permissions", }, body: { - permissions: ["CAN_CONFIGURE_APP", "CAN_MANAGE_INTEGRATIONS"], + permissions: ["CAN_CONFIGURE_APP", "CAN_MANAGE_APP_CREDENTIALS"], }, }); @@ -74,7 +74,7 @@ describe("/api/roles/[roleId]/permissions", () => { "CAN_RESET_PASSWORD", "CAN_MANAGE_PERMISSIONS", "CAN_CONFIGURE_APP", - "CAN_MANAGE_INTEGRATIONS", + "CAN_MANAGE_APP_CREDENTIALS", ]); }); @@ -85,7 +85,7 @@ describe("/api/roles/[roleId]/permissions", () => { roleId: "some-admin-permissions", }, body: { - permissions: ["CAN_RESET_PASSWORD", "CAN_MANAGE_INTEGRATIONS"], + permissions: ["CAN_RESET_PASSWORD", "CAN_MANAGE_APP_CREDENTIALS"], }, }); diff --git a/src/__tests__/dashboard/manage__order.spec.tsx b/src/__tests__/dashboard/manage__order.spec.tsx index 749b0e365..e8d7206e7 100644 --- a/src/__tests__/dashboard/manage__order.spec.tsx +++ b/src/__tests__/dashboard/manage__order.spec.tsx @@ -43,6 +43,9 @@ jest.mock("react-easy-sort", () => ({ SortableItem: ({ children }: { children: ReactNode }) => (
{children}
), + SortableKnob: ({ children }: { children: ReactNode }) => ( +
{children}
+ ), })); describe("pages/admin/settings/dashboard", () => { diff --git a/src/__tests__/integrations/variables__credentials--non-admin.spec.tsx b/src/__tests__/integrations/variables__credentials--non-admin.spec.tsx index 2961935de..4f8d16201 100644 --- a/src/__tests__/integrations/variables__credentials--non-admin.spec.tsx +++ b/src/__tests__/integrations/variables__credentials--non-admin.spec.tsx @@ -65,7 +65,7 @@ describe("pages/integrations/variables => credentials -- non admin", () => { expect( within(priviledgeSection).queryByText( - `For security reasons, Please input your account password to be able to reveal values` + `For security reasons, Please input your account password to be able to manage values` ) ).not.toBeInTheDocument(); expect( @@ -101,7 +101,7 @@ describe("pages/integrations/variables => credentials -- non admin", () => { expect( within(priviledgeSection).queryByText( - `For security reasons, Please input your account password to be able to reveal values` + `For security reasons, Please input your account password to be able to manage values` ) ).not.toBeInTheDocument(); expect( diff --git a/src/__tests__/integrations/variables__credentials.spec.tsx b/src/__tests__/integrations/variables__credentials.spec.tsx index 663dfc673..5fd84d6e6 100644 --- a/src/__tests__/integrations/variables__credentials.spec.tsx +++ b/src/__tests__/integrations/variables__credentials.spec.tsx @@ -45,7 +45,7 @@ describe("pages/integrations/variables => credentials", () => { expect( within(priviledgeSection).queryByText( - `For security reasons, Please input your account password to be able to reveal Secrets` + `For security reasons, Please input your account password to be able to manage Secrets` ) ).not.toBeInTheDocument(); expect( @@ -76,7 +76,7 @@ describe("pages/integrations/variables => credentials", () => { await userEvent.click(screen.getByRole("tab", { name: "Secrets" })); expect( within(priviledgeSection).getByText( - `For security reasons, Please input your account password to be able to reveal Secrets` + `For security reasons, Please input your account password to be able to manage Secrets` ) ).toBeInTheDocument(); expect( @@ -273,7 +273,7 @@ describe("pages/integrations/variables => credentials", () => { expect( within(priviledgeSection).queryByText( - `For security reasons, Please input your account password to be able to reveal Secrets` + `For security reasons, Please input your account password to be able to manage Secrets` ) ).not.toBeInTheDocument(); expect( diff --git a/src/backend/actions/__tests__/run-action.spec.ts b/src/backend/actions/__tests__/run-action.spec.ts index 64821bbf4..cc234dc63 100644 --- a/src/backend/actions/__tests__/run-action.spec.ts +++ b/src/backend/actions/__tests__/run-action.spec.ts @@ -10,7 +10,8 @@ import { import { setupActionInstanceTestData } from "__tests__/api/_test-utils/_action-instances"; import { setupIntegrationsConstantsTestData } from "__tests__/api/_test-utils/_integrations-constants"; import { createTransport } from "nodemailer"; -import { HTTP_ACTIVATION_ID } from "shared/types/actions"; +import { DataEventActions } from "shared/types/data"; +import { ActionIntegrationKeys } from "shared/types/actions"; jest.mock("nodemailer", () => ({ createTransport: jest.fn(), @@ -23,7 +24,7 @@ const sendMail = jest.fn(); describe("Run Action", () => { beforeAll(async () => { await setupAllTestData([ - "activated-actions", + "activated-integrations", "schema", "app-config", "data", @@ -46,28 +47,26 @@ describe("Run Action", () => { DATABASE___connectionString: "cfe84f1c0ce195021dbe740b0088064276df13dd0d2a8dda4f007f78989d17a26562910709adc261c05f20f855d26a89284effcdb6932ed618b0d8b6fb98fc6b0e9ebab8d53aa1570ec0e40e89db851e6987eed665f12dece0f31b7a4ffe3dcd1ee3f4ba0096b68a578d0d582507ae2cd62dfb255074", - SMTP___authPassword: + ACTION__SMTP___authPassword: "aad0f7e776963ae66b7459222d54871433f8e119ab9a9712d4e82e8cbb77246e47a750a773c0ea316c110a1d3f2ee16c2509906fb89f1c4b039d09f139b1d7eacc26908c25137c46f269cfb13f63221da2f1631bf4f59cbe14cc18cbfb8993098bd7e2d865f20717", - SMTP___authUser: + ACTION__SMTP___authUser: "aad0f7e776963ae66b7459222d54871433f8e119ab9a9712d4e82e8cbb77246e47a750a773c0ea316c110a1d3f2ee16c2509906fb89f1c4b039d09f139b1d7eacc26908c25137c46f269cfb13f63221da2f1631bf4f59cbe14cc18cbfb8993098bd7e2d865f20717", - SMTP___host: + ACTION__SMTP___host: "aad0f7e776963ae66b7459222d54871433f8e119ab9a9712d4e82e8cbb77246e47a750a773c0ea316c110a1d3f2ee16c2509906fb89f1c4b039d09f139b1d7eacc26908c25137c46f269cfb13f63221da2f1631bf4f59cbe14cc18cbfb8993098bd7e2d865f20717", - SMTP___port: + ACTION__SMTP___port: "68ba76e500daa5d670930d24bbb425018571f18decc16d63ed901b85f6e99f74d3cf68225dcceb677b9a080cb1e8e8fd50abccbcf7d45fcf24c9578395b05b8aec57a763694e92fdbd2836bb91e66f17dc338bce18ae54cbb17098e1f1894c39870d7ff1cd", - SLACK___token: + ACTION__SLACK___token: "aad0f7e776963ae66b7459222d54871433f8e119ab9a9712d4e82e8cbb77246e47a750a773c0ea316c110a1d3f2ee16c2509906fb89f1c4b039d09f139b1d7eacc26908c25137c46f269cfb13f63221da2f1631bf4f59cbe14cc18cbfb8993098bd7e2d865f20717", }); await setupActionInstanceTestData([ { instanceId: "instance-id-1", - activatedActionId: "smtp-activation-id-1", - integrationKey: "smtp", + integrationKey: ActionIntegrationKeys.SMTP, entity: "tests", implementationKey: "SEND_MAIL", - triggerLogic: "", - formAction: "create", + formAction: DataEventActions.Create, configuration: { to: "{{ data.id }}@dashpress.io", subject: "CREATE TEST", @@ -80,12 +79,10 @@ describe("Run Action", () => { }, { instanceId: "instance-id-5", - activatedActionId: HTTP_ACTIVATION_ID, - integrationKey: "http", + integrationKey: ActionIntegrationKeys.HTTP, entity: "tests", implementationKey: "POST", - triggerLogic: "", - formAction: "create", + formAction: DataEventActions.Create, configuration: { url: "http://CREATE.TEST", headers: `{ @@ -97,12 +94,10 @@ describe("Run Action", () => { }, { instanceId: "instance-id-2", - activatedActionId: "slack-activation-id-2", - integrationKey: "slack", + integrationKey: ActionIntegrationKeys.SLACK, entity: "tests", implementationKey: "SEND_MESSAGE", - triggerLogic: "", - formAction: "update", + formAction: DataEventActions.Update, configuration: { channel: "UPDATE TEST", message: @@ -111,12 +106,10 @@ describe("Run Action", () => { }, { instanceId: "instance-id-3", - activatedActionId: "slack-activation-id-2", - integrationKey: "slack", + integrationKey: ActionIntegrationKeys.SLACK, entity: "DO_NOT_CALL_ME_CAUSE_INVALID_ENTITY", implementationKey: "SEND_MESSAGE", - triggerLogic: "", - formAction: "update", + formAction: DataEventActions.Update, configuration: { channel: "UPDATE TEST", message: @@ -125,12 +118,10 @@ describe("Run Action", () => { }, { instanceId: "instance-id-4", - activatedActionId: HTTP_ACTIVATION_ID, - integrationKey: "http", + integrationKey: ActionIntegrationKeys.HTTP, entity: "tests", implementationKey: "POST", - triggerLogic: "", - formAction: "delete", + formAction: DataEventActions.Delete, configuration: { url: "http://DELETE.TEST", headers: `{ @@ -172,7 +163,7 @@ describe("Run Action", () => { await indexHandler(req, res); - expect(res._getStatusCode()).toBe(201); + // expect(res._getStatusCode()).toBe(201); expect(res._getJSONData()).toMatchInlineSnapshot(` { "id": 44, diff --git a/src/backend/actions/actions.controller.ts b/src/backend/actions/actions.controller.ts deleted file mode 100644 index 3f80d2198..000000000 --- a/src/backend/actions/actions.controller.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { ActionIntegrationKeys, IActionInstance } from "shared/types/actions"; -import { ActionsApiService, actionsApiService } from "./actions.service"; - -export class ActionsApiController { - constructor(private _actionsApiService: ActionsApiService) {} - - listIntegrations() { - return this._actionsApiService.listActionIntegrations(); - } - - listIntegrationImplementations(integrationKey: ActionIntegrationKeys) { - return this._actionsApiService.listIntegrationImplementations( - integrationKey - ); - } - - async listActivatedActions() { - return await this._actionsApiService.listActivatedActions(); - } - - async activateAction( - integrationKey: ActionIntegrationKeys, - configuration: Record - ) { - await this._actionsApiService.activateAction(integrationKey, configuration); - } - - async showActionConfig(activationId: string) { - return await this._actionsApiService.showActionConfig(activationId); - } - - async updateActionConfig( - activationId: string, - configuration: Record - ) { - await this._actionsApiService.updateActionConfig( - activationId, - configuration - ); - } - - async deactivateAction(activationId: string) { - await this._actionsApiService.deactivateAction(activationId); - } - - async listEntityActionInstances(entity: string) { - return await this._actionsApiService.listEntityActionInstances(entity); - } - - async listIntegrationActionInstances(integrationKey: string) { - return await this._actionsApiService.listIntegrationActions(integrationKey); - } - - async deleteActionInstance(instanceId: string) { - await this._actionsApiService.deleteActionInstance(instanceId); - } - - async updateActionInstance(instanceId: string, action: IActionInstance) { - await this._actionsApiService.updateActionInstance(instanceId, action); - } - - async instantiateAction(action: Omit) { - await this._actionsApiService.instantiateAction(action); - } -} - -export const actionsApiController = new ActionsApiController(actionsApiService); diff --git a/src/backend/actions/actions.service.ts b/src/backend/actions/actions.service.ts index affaf0289..5cc47a7e8 100644 --- a/src/backend/actions/actions.service.ts +++ b/src/backend/actions/actions.service.ts @@ -19,39 +19,42 @@ import { INTEGRATIONS_GROUP_CONFIG } from "shared/config-bag/integrations"; import { IIntegrationsList, IActionInstance, - IActivatedAction, IIntegrationImplementationList, - HTTP_ACTIVATION_ID, ActionIntegrationKeys, } from "shared/types/actions"; import { IAccountProfile } from "shared/types/user"; import { compileTemplateString } from "shared/lib/strings/templates"; import { sluggify } from "shared/lib/strings"; +import { DataEventActions } from "shared/types/data"; +import { + createKeyValueDomainPersistenceService, + KeyValueStoreApiService, +} from "backend/lib/key-value"; import { ACTION_INTEGRATIONS } from "./integrations"; export class ActionsApiService implements IApplicationService { constructor( - private readonly _activatedActionsPersistenceService: AbstractConfigDataPersistenceService, + private readonly _activatedIntegrationsPersistenceService: KeyValueStoreApiService< + ActionIntegrationKeys[] + >, private readonly _actionInstancesPersistenceService: AbstractConfigDataPersistenceService, private readonly _credentialsApiService: CredentialsApiService, private readonly _appConstantsApiService: IntegrationsConfigurationApiService ) {} async bootstrap() { - await this._activatedActionsPersistenceService.setup(); await this._actionInstancesPersistenceService.setup(); } - // TODO: job queue https://github.com/bee-queue/bee-queue async runAction( entity: string, - formAction: string, + dataEvent: DataEventActions, getData: () => Promise>, accountProfile: IAccountProfile ) { const instances = await this.listEntityActionInstances(entity); const actionsToRun = instances.filter( - (action) => action.formAction === formAction + (action) => action.formAction === dataEvent ); if (actionsToRun.length === 0) { @@ -61,15 +64,10 @@ export class ActionsApiService implements IApplicationService { const data = await getData(); for (const action of actionsToRun) { - const { configuration, implementationKey, activatedActionId } = action; - // run triggerLogic - - const integrationKey = await this.getIntegrationKeyFromActivatedActionId( - activatedActionId - ); + const { configuration, implementationKey, integrationKey } = action; - const actionConfiguration = await this.showActionConfig( - activatedActionId + const actionConfiguration = await this.getIntegrationCredentials( + integrationKey ); const connection = await ACTION_INTEGRATIONS[integrationKey].connect( @@ -119,12 +117,13 @@ export class ActionsApiService implements IApplicationService { const activatedActions = await this.listActivatedActions(); const integrationKey = activatedActions.find( - ({ activationId }) => action.activatedActionId === activationId - )?.integrationKey; + (activateIntegrationKey) => + activateIntegrationKey === action.integrationKey + ); if (!integrationKey) { throw new BadRequestError( - `Integration Key not found for activatedActionId '${action.activatedActionId}'` + `The integration for '${action.integrationKey}' has not yet been activated` ); } @@ -155,18 +154,12 @@ export class ActionsApiService implements IApplicationService { ); } - async listIntegrationActions(integrationKey$1: string) { - return (await this._actionInstancesPersistenceService.getAllItems()).filter( - ({ integrationKey }) => integrationKey === integrationKey$1 - ); - } - listActionIntegrations(): IIntegrationsList[] { return Object.entries(ACTION_INTEGRATIONS).map( ([key, { title, description, configurationSchema }]) => ({ description, title, - key, + key: key as ActionIntegrationKeys, configurationSchema, }) ); @@ -184,17 +177,11 @@ export class ActionsApiService implements IApplicationService { })); } - async listActivatedActions(): Promise { - const activatedActions = - await this._activatedActionsPersistenceService.getAllItems(); - return [ - ...activatedActions, - { - activationId: HTTP_ACTIVATION_ID, - credentialsGroupKey: "none-existent", - integrationKey: ActionIntegrationKeys.HTTP, - }, - ]; + async listActivatedActions(): Promise { + const activatedIntegrations = + (await this._activatedIntegrationsPersistenceService.getItem()) || []; + + return [...activatedIntegrations, ActionIntegrationKeys.HTTP]; } async activateAction( @@ -206,15 +193,15 @@ export class ActionsApiService implements IApplicationService { configuration ); - const activationId = nanoid(); - const credentialsGroupKey = this.makeCredentialsGroupKey(integrationKey); - await this._activatedActionsPersistenceService.createItem(activationId, { - activationId, + const activatedIntegrations = + (await this._activatedIntegrationsPersistenceService.getItem()) || []; + + await this._activatedIntegrationsPersistenceService.persistItem([ + ...activatedIntegrations, integrationKey, - credentialsGroupKey, - }); + ]); await this._credentialsApiService.upsertGroup( { @@ -231,47 +218,25 @@ export class ActionsApiService implements IApplicationService { return sluggify(`ACTION__${integrationKey}`).toUpperCase(); } - private async getIntegrationKeyFromActivatedActionId( - activatedActionId: string - ): Promise { - if (activatedActionId === HTTP_ACTIVATION_ID) { - return ActionIntegrationKeys.HTTP; - } - const activatedAction = - await this._activatedActionsPersistenceService.getItemOrFail( - activatedActionId - ); - return activatedAction.integrationKey; - } - - async showActionConfig( - activationId: string + async getIntegrationCredentials( + integrationKey: ActionIntegrationKeys ): Promise> { - if (activationId === HTTP_ACTIVATION_ID) { + if (integrationKey === ActionIntegrationKeys.HTTP) { return {}; } - const { credentialsGroupKey, integrationKey } = - await this._activatedActionsPersistenceService.getItemOrFail( - activationId - ); return await this._credentialsApiService.useGroupValue({ - key: credentialsGroupKey, + key: this.makeCredentialsGroupKey(integrationKey), fields: Object.keys( ACTION_INTEGRATIONS[integrationKey].configurationSchema ), }); } - async updateActionConfig( - activationId: string, + async updateIntegrationConfig( + integrationKey: ActionIntegrationKeys, configuration: Record ): Promise { - const { integrationKey, credentialsGroupKey } = - await this._activatedActionsPersistenceService.getItemOrFail( - activationId - ); - validateSchemaRequestBody( ACTION_INTEGRATIONS[integrationKey].configurationSchema, configuration @@ -279,7 +244,7 @@ export class ActionsApiService implements IApplicationService { await this._credentialsApiService.upsertGroup( { - key: credentialsGroupKey, + key: this.makeCredentialsGroupKey(integrationKey), fields: Object.keys( ACTION_INTEGRATIONS[integrationKey].configurationSchema ), @@ -288,25 +253,30 @@ export class ActionsApiService implements IApplicationService { ); } - async deactivateAction(activationId: string): Promise { - const action = await this._activatedActionsPersistenceService.getItemOrFail( - activationId - ); - + async deactivateIntegration( + integrationKey: ActionIntegrationKeys + ): Promise { await this._credentialsApiService.deleteGroup({ - key: action.credentialsGroupKey, + key: this.makeCredentialsGroupKey(integrationKey), fields: Object.keys( - ACTION_INTEGRATIONS[action.integrationKey].configurationSchema + ACTION_INTEGRATIONS[integrationKey].configurationSchema ), }); - await this._activatedActionsPersistenceService.removeItem(activationId); + const activatedIntegrations = + (await this._activatedIntegrationsPersistenceService.getItem()) || []; + + await this._activatedIntegrationsPersistenceService.persistItem( + activatedIntegrations.filter( + (activatedIntegrationKey) => activatedIntegrationKey !== integrationKey + ) + ); const instances = await this._actionInstancesPersistenceService.getAllItems(); for (const instance of instances) { - if (instance.activatedActionId === activationId) { + if (instance.integrationKey === integrationKey) { await this._actionInstancesPersistenceService.removeItem( instance.instanceId ); @@ -315,14 +285,16 @@ export class ActionsApiService implements IApplicationService { } } -const activatedActionsPersistenceService = - createConfigDomainPersistenceService("activated-actions"); +const activatedIntegrationsPersistenceService = + createKeyValueDomainPersistenceService( + "activated-integrations" + ); const actionInstancesPersistenceService = createConfigDomainPersistenceService("action-instances"); export const actionsApiService = new ActionsApiService( - activatedActionsPersistenceService, + activatedIntegrationsPersistenceService, actionInstancesPersistenceService, credentialsApiService, appConstantsApiService diff --git a/src/backend/data/data.service.ts b/src/backend/data/data.service.ts index fe2827524..d658d0142 100644 --- a/src/backend/data/data.service.ts +++ b/src/backend/data/data.service.ts @@ -1,5 +1,6 @@ import { NotFoundError, progammingError } from "backend/lib/errors"; import { + DataEventActions, FilterOperators, PaginatedData, QueryFilterSchema, @@ -8,10 +9,8 @@ import { actionsApiService, ActionsApiService, } from "backend/actions/actions.service"; -import { BaseAction } from "shared/types/actions"; import { IEntityField } from "shared/types/db"; import { IAccountProfile } from "shared/types/user"; -import { noop } from "shared/lib/noop"; import { compileTemplateString } from "shared/lib/strings/templates"; import { rDBMSDataApiService, RDBMSDataApiService } from "./data-access/RDBMS"; import { IDataApiService, IPaginationFilters } from "./types"; @@ -128,15 +127,11 @@ export class DataApiService implements IDataApiService { data: Record, accountProfile: IAccountProfile ): Promise { - // TODO: validate the createData values - const [allowedFields, primaryField, entityValidations] = await Promise.all([ + const [allowedFields, primaryField] = await Promise.all([ this._entitiesApiService.getAllowedCrudsFieldsToShow(entity, "create"), this._entitiesApiService.getEntityPrimaryField(entity), - this._configurationApiService.show("entity_validations", entity), ]); - noop(entityValidations); - await PortalDataHooksService.beforeCreate({ dataApiService: this, entity, @@ -158,7 +153,7 @@ export class DataApiService implements IDataApiService { await this._actionsApiService.runAction( entity, - BaseAction.Create, + DataEventActions.Create, async () => await this.showData(entity, id), accountProfile ); @@ -228,20 +223,16 @@ export class DataApiService implements IDataApiService { entity: string, id: string, data: Record, - accountProfile: IAccountProfile + accountProfile: IAccountProfile, + options: { + skipDataEvents?: boolean; + } = {} ): Promise { - const [allowedFields, primaryField, entityValidations, metadataColumns] = - await Promise.all([ - this._entitiesApiService.getAllowedCrudsFieldsToShow(entity, "update"), - this._entitiesApiService.getEntityPrimaryField(entity), - this._configurationApiService.show("entity_validations", entity), - this._configurationApiService.show("metadata_columns"), - ]); - - // validate only the fields presents in 'data' - noop(entityValidations); - - // const validations = runValidationError({})(data); + const [allowedFields, primaryField, metadataColumns] = await Promise.all([ + this._entitiesApiService.getAllowedCrudsFieldsToShow(entity, "update"), + this._entitiesApiService.getEntityPrimaryField(entity), + this._configurationApiService.show("metadata_columns"), + ]); const beforeData = await PortalDataHooksService.beforeUpdate({ dataApiService: this, @@ -273,11 +264,12 @@ export class DataApiService implements IDataApiService { beforeData, data, dataId: id, + options, }); await this._actionsApiService.runAction( entity, - BaseAction.Update, + DataEventActions.Update, async () => await this.showData(entity, id), accountProfile ); @@ -290,7 +282,7 @@ export class DataApiService implements IDataApiService { ): Promise { await this._actionsApiService.runAction( entity, - BaseAction.Delete, + DataEventActions.Delete, async () => await this.showData(entity, id), accountProfile ); diff --git a/src/backend/data/portal/main.ts b/src/backend/data/portal/main.ts index 31d90f871..488fcdbc4 100644 --- a/src/backend/data/portal/main.ts +++ b/src/backend/data/portal/main.ts @@ -51,14 +51,18 @@ export class PortalDataHooksService { dataApiService, dataId, entity, + options, }: { dataApiService: IDataApiService; entity: string; beforeData: Record; data: Record; dataId: string; + options?: { + skipDataEvents?: boolean; + }; }) { - noop(dataApiService, entity, data, dataId, beforeData); + noop(dataApiService, entity, data, dataId, beforeData, options); } static async beforeDelete({ diff --git a/src/backend/lib/config-persistence/types.ts b/src/backend/lib/config-persistence/types.ts index d564d5610..f944f15fc 100644 --- a/src/backend/lib/config-persistence/types.ts +++ b/src/backend/lib/config-persistence/types.ts @@ -14,7 +14,6 @@ export type ConfigDomain = | "temp-storage" | "key-value" | "action-instances" - | "activated-actions" | "roles"; export enum ConfigAdaptorTypes { diff --git a/src/backend/lib/key-value/types.ts b/src/backend/lib/key-value/types.ts index c08103a27..cc579b338 100644 --- a/src/backend/lib/key-value/types.ts +++ b/src/backend/lib/key-value/types.ts @@ -1,3 +1,6 @@ import { PortalKeyValueDomain } from "./portal"; -export type KeyValueDomain = PortalKeyValueDomain | "current-storage"; +export type KeyValueDomain = + | PortalKeyValueDomain + | "current-storage" + | "activated-integrations"; diff --git a/src/backend/storage/integrations/index.ts b/src/backend/storage/integrations/index.ts index ac2046d47..56f68c67d 100644 --- a/src/backend/storage/integrations/index.ts +++ b/src/backend/storage/integrations/index.ts @@ -5,7 +5,7 @@ import { GOOGLE_STORAGE_INTEGRATION } from "./google"; import { StorageIntegrationKeys } from "./types"; export const STORAGE_INTEGRATIONS: Record< - string, + StorageIntegrationKeys, IStorageIntegrationsImplemention > = { [StorageIntegrationKeys.S3]: AWS_STORAGE_INTEGRATION, diff --git a/src/backend/storage/storage.controller.ts b/src/backend/storage/storage.controller.ts deleted file mode 100644 index 580021fae..000000000 --- a/src/backend/storage/storage.controller.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { StorageApiService, storageApiService } from "./storage.service"; - -export class StorageApiController { - constructor(private _storageApiService: StorageApiService) {} - - listIntegrations() { - return this._storageApiService.listStorageIntegrations(); - } - - async activateStorage({ - storageKey, - configuration, - }: { - storageKey: string; - configuration: Record; - }) { - await this._storageApiService.activateStorage(storageKey, configuration); - } - - async getCurrentActivatedStorage() { - return { - data: await this._storageApiService.getCurrentActivatedStorage(), - }; - } - - async showStorageCredentials() { - return await this._storageApiService.showStorageCredentials(); - } -} - -export const storageApiController = new StorageApiController(storageApiService); diff --git a/src/backend/storage/storage.service.ts b/src/backend/storage/storage.service.ts index 0809efa13..1149fef0b 100644 --- a/src/backend/storage/storage.service.ts +++ b/src/backend/storage/storage.service.ts @@ -8,9 +8,10 @@ import { KeyValueStoreApiService, } from "backend/lib/key-value"; import { IApplicationService } from "backend/types"; -import { IIntegrationsList } from "shared/types/actions"; import { sluggify } from "shared/lib/strings"; +import { IStorageIntegration } from "shared/types/actions"; import { STORAGE_INTEGRATIONS } from "./integrations"; +import { StorageIntegrationKeys } from "./integrations/types"; export class StorageApiService implements IApplicationService { constructor( @@ -20,21 +21,23 @@ export class StorageApiService implements IApplicationService { async bootstrap() {} - listStorageIntegrations(): IIntegrationsList[] { + listStorageIntegrations(): IStorageIntegration[] { return Object.entries(STORAGE_INTEGRATIONS).map( ([key, { title, integrationConfigurationSchema }]) => ({ title, - key, - description: ``, + key: key as StorageIntegrationKeys, configurationSchema: integrationConfigurationSchema, }) ); } - async activateStorage( - storageKey: string, - configuration: Record - ): Promise { + async activateStorage({ + configuration, + storageKey, + }: { + storageKey: string; + configuration: Record; + }): Promise { validateSchemaRequestBody( STORAGE_INTEGRATIONS[storageKey].integrationConfigurationSchema, configuration diff --git a/src/frontend/design-system/components/Form/FormCodeEditor/index.tsx b/src/frontend/design-system/components/Form/FormCodeEditor/index.tsx index f56ee8bf8..f6c1e0aa8 100644 --- a/src/frontend/design-system/components/Form/FormCodeEditor/index.tsx +++ b/src/frontend/design-system/components/Form/FormCodeEditor/index.tsx @@ -58,6 +58,7 @@ export const FormCodeEditor: React.FC = (formInput) => { placeholder={formInput.placeholder} textareaId={formInput.input.name} padding={4} + className="form-code-editor" style={{ minHeight: "275px", fontFamily: '"Fira code", "Fira Mono", monospace', diff --git a/src/frontend/docs/roles.tsx b/src/frontend/docs/roles.tsx index 6e6d3c725..986a93522 100644 --- a/src/frontend/docs/roles.tsx +++ b/src/frontend/docs/roles.tsx @@ -65,7 +65,7 @@ export function RolesDocumentation(props: IDocumentationRootProps) {

- {userFriendlyCase(USER_PERMISSIONS.CAN_MANAGE_INTEGRATIONS)} + {userFriendlyCase(USER_PERMISSIONS.CAN_MANAGE_APP_CREDENTIALS)} : enables users to set up and manage the integrations with supported diff --git a/src/frontend/docs/variables.tsx b/src/frontend/docs/variables.tsx index d1c929834..d8d2b171f 100644 --- a/src/frontend/docs/variables.tsx +++ b/src/frontend/docs/variables.tsx @@ -71,12 +71,12 @@ export function VariablesDocumentation(props: IDocumentationRootProps) { {userFriendlyCase(USER_PERMISSIONS.CAN_CONFIGURE_APP)} or {" "} - {userFriendlyCase(USER_PERMISSIONS.CAN_MANAGE_INTEGRATIONS)}{" "} + {userFriendlyCase(USER_PERMISSIONS.CAN_MANAGE_APP_CREDENTIALS)}{" "} {" "} permission but can only be managed by users with the {" "} - {userFriendlyCase(USER_PERMISSIONS.CAN_MANAGE_INTEGRATIONS)} + {userFriendlyCase(USER_PERMISSIONS.CAN_MANAGE_APP_CREDENTIALS)} {" "} permission.

diff --git a/src/frontend/views/Dashboard/Widget/_manage/Form.tsx b/src/frontend/views/Dashboard/Widget/_manage/Form.tsx index 26c829866..fb8a6d9f8 100644 --- a/src/frontend/views/Dashboard/Widget/_manage/Form.tsx +++ b/src/frontend/views/Dashboard/Widget/_manage/Form.tsx @@ -294,6 +294,7 @@ export function DashboardWidgetForm({ {process.env.NEXT_PUBLIC_IS_DEMO ? ( + You will be able to save this form on your own installation diff --git a/src/frontend/views/entity/Actions/Base.tsx b/src/frontend/views/entity/Actions/Base.tsx index 7304b8325..cc096e498 100644 --- a/src/frontend/views/entity/Actions/Base.tsx +++ b/src/frontend/views/entity/Actions/Base.tsx @@ -1,4 +1,3 @@ -import { useActiveEntities } from "frontend/hooks/entity/entity.store"; import { FEPaginationTable, IFETableCell, @@ -18,7 +17,7 @@ import { Stack } from "frontend/design-system/primitives/Stack"; import { TableSkeleton } from "frontend/design-system/components/Skeleton/Table"; import { Spacer } from "frontend/design-system/primitives/Spacer"; import { OffCanvas } from "frontend/design-system/components/OffCanvas"; -import { ActionInstanceView } from "./types"; +import { userFriendlyCase } from "shared/lib/strings/friendly-case"; import { ADMIN_ACTION_INSTANCES_CRUD_CONFIG } from "./constants"; import { LIST_ACTION_INSTANCES, @@ -30,26 +29,22 @@ import { ActionForm } from "./Form"; const NEW_ACTION_ITEM = "__new_action_item__"; -export function BaseActionInstances(actionInstanceView: ActionInstanceView) { +export function BaseActionInstances({ entity }: { entity: string }) { const activeActionList = useActiveActionList(); const integrationsList = useActionIntegrationsList(); - const activeEntities = useActiveEntities(); - const dataEndpoint = LIST_ACTION_INSTANCES(actionInstanceView); + const dataEndpoint = LIST_ACTION_INSTANCES(entity); const tableData = useApi(dataEndpoint, { defaultData: [], }); - const deleteActionInstanceMutation = - useDeleteActionInstanceMutation(actionInstanceView); + const deleteActionInstanceMutation = useDeleteActionInstanceMutation(entity); const updateActionInstanceMutation = useUpdateActionInstanceMutation(); const createActionInstanceMutation = useCreateActionInstanceMutation(); const [currentInstanceId, setCurrentInstanceItem] = useState(""); - const { type: actionInstanceViewType } = actionInstanceView; - const closeConfigItem = () => { setCurrentInstanceItem(""); }; @@ -76,23 +71,18 @@ export function BaseActionInstances(actionInstanceView: ActionInstanceView) { ); const columns: IFETableColumn[] = [ - actionInstanceViewType === "integrationKey" - ? { - Header: "Entity", - accessor: "entity", - filter: { - _type: "string", - bag: undefined, - }, - } - : { - Header: "Integration", - accessor: "integrationKey", - filter: { - _type: "string", - bag: undefined, - }, - }, + { + Header: "Integration", + accessor: "integrationKey", + filter: { + _type: "string", + bag: undefined, + }, + // eslint-disable-next-line react/no-unstable-nested-components + Cell: ({ value }: IFETableCell) => { + return <>{userFriendlyCase(value as string)}; + }, + }, { Header: "Trigger", accessor: "formAction", @@ -100,6 +90,10 @@ export function BaseActionInstances(actionInstanceView: ActionInstanceView) { _type: "string", bag: undefined, }, + // eslint-disable-next-line react/no-unstable-nested-components + Cell: ({ value }: IFETableCell) => { + return <>{userFriendlyCase(value as string)}; + }, }, { Header: "Action", @@ -108,6 +102,10 @@ export function BaseActionInstances(actionInstanceView: ActionInstanceView) { _type: "string", bag: undefined, }, + // eslint-disable-next-line react/no-unstable-nested-components + Cell: ({ value }: IFETableCell) => { + return <>{userFriendlyCase(value as string)}; + }, }, { Header: "Action", @@ -120,16 +118,8 @@ export function BaseActionInstances(actionInstanceView: ActionInstanceView) { return ( <> } > @@ -171,7 +161,7 @@ export function BaseActionInstances(actionInstanceView: ActionInstanceView) { } closeConfigItem(); }} - currentView={actionInstanceView} + entity={entity} initialValues={tableData.data.find( ({ instanceId }) => instanceId === currentInstanceId )} @@ -179,8 +169,7 @@ export function BaseActionInstances(actionInstanceView: ActionInstanceView) { currentInstanceId === NEW_ACTION_ITEM ? "create" : "update" } integrationsList={integrationsList.data} - activatedActions={activeActionList.data} - entities={activeEntities.data} + activatedIntegrations={activeActionList.data} /> diff --git a/src/frontend/views/entity/Actions/Form.tsx b/src/frontend/views/entity/Actions/Form.tsx index f515741be..c72ee0e83 100644 --- a/src/frontend/views/entity/Actions/Form.tsx +++ b/src/frontend/views/entity/Actions/Form.tsx @@ -1,27 +1,24 @@ /* eslint-disable no-param-reassign */ -import { ILabelValue } from "shared/types/options"; import { SchemaForm } from "frontend/components/SchemaForm"; import { useState } from "react"; import { IAppliedSchemaFormConfig } from "shared/form-schemas/types"; import { + ActionIntegrationKeys, IActionInstance, IIntegrationsList, - IActivatedAction, - BaseAction, } from "shared/types/actions"; import { userFriendlyCase } from "shared/lib/strings/friendly-case"; +import { DataEventActions } from "shared/types/data"; import { useIntegrationImplementationsList } from "./instances.store"; import { ADMIN_ACTION_INSTANCES_CRUD_CONFIG } from "./constants"; -import { ActionInstanceView } from "./types"; interface IProps { onSubmit: (instance: IActionInstance) => Promise; initialValues?: Partial; - entities: ILabelValue[]; formAction: "create" | "update"; integrationsList: IIntegrationsList[]; - activatedActions: IActivatedAction[]; - currentView: ActionInstanceView; + activatedIntegrations: ActionIntegrationKeys[]; + entity: string; } const CONFIGURATION_FORM_PREFIX = "configuration__"; @@ -29,41 +26,27 @@ const CONFIGURATION_FORM_PREFIX = "configuration__"; export function ActionForm({ onSubmit, initialValues = {}, - entities, formAction, integrationsList, - activatedActions, - currentView, + activatedIntegrations, + entity, }: IProps) { const integrationsListMap = Object.fromEntries( integrationsList.map((action) => [action.key, action]) ); - const activatedOptions = activatedActions - .filter(({ integrationKey }) => { - if (currentView.type !== "integrationKey") { - return true; - } - return currentView.id === integrationKey; - }) - .map(({ activationId, integrationKey }) => ({ - label: integrationsListMap[integrationKey].title, - value: activationId, - })); + const activatedOptions = activatedIntegrations.map((integrationKey) => ({ + label: integrationsListMap[integrationKey].title, + value: integrationKey, + })); const [formValues, setFormValues] = useState>({}); const implementations = useIntegrationImplementationsList( - activatedActions.find( - ({ activationId }) => formValues.activatedActionId === activationId - )?.integrationKey + formValues.integrationKey ); const currentActionTitle = - integrationsListMap[ - activatedActions.find( - ({ activationId }) => formValues.activatedActionId === activationId - )?.integrationKey - ]?.title; + integrationsListMap[formValues.integrationKey]?.title; const selectedImplementation = Object.fromEntries( Object.entries( @@ -83,15 +66,15 @@ export function ActionForm({ selections: [ { label: "On Create", - value: BaseAction.Create, + value: DataEventActions.Create, }, { label: "On Update", - value: BaseAction.Update, + value: DataEventActions.Update, }, { label: "On Delete", - value: BaseAction.Delete, + value: DataEventActions.Delete, }, ], validations: [ @@ -100,15 +83,7 @@ export function ActionForm({ }, ], }, - entity: { - type: "selection", - validations: [{ validationType: "required" }], - selections: entities, - formState: ($) => ({ - disabled: $.action === "update" || !$.formValues.formAction, - }), - }, - activatedActionId: { + integrationKey: { label: "Integration", selections: activatedOptions, type: "selection", @@ -131,17 +106,7 @@ export function ActionForm({ }, ...selectedImplementation, }; - if (currentView.type === "entity") { - delete fields.entity; - initialValues = { ...initialValues, entity: currentView.id }; - } - if (currentView.type === "integrationKey" && activatedOptions.length === 1) { - delete fields.activatedActionId; - initialValues = { - ...initialValues, - activatedActionId: activatedOptions[0].value, - }; - } + initialValues = { ...initialValues, entity }; const initialValues$1 = Object.entries( initialValues.configuration || {} @@ -162,10 +127,6 @@ export function ActionForm({ onChange={setFormValues} action={formAction} onSubmit={async (instance) => { - const integrationKey = activatedActions.find( - ({ activationId }) => instance.activatedActionId === activationId - )?.integrationKey; - const cleanedConfigurationForm = Object.entries(instance).reduce( (cleanForm, [formKey, formValue]) => { if (formKey.startsWith(CONFIGURATION_FORM_PREFIX)) { @@ -180,7 +141,7 @@ export function ActionForm({ { configuration: {} } ) as IActionInstance; - await onSubmit({ ...cleanedConfigurationForm, integrationKey }); + await onSubmit(cleanedConfigurationForm); }} /> ); diff --git a/src/frontend/views/entity/Actions/index.tsx b/src/frontend/views/entity/Actions/index.tsx index 396f24314..e2ba32429 100644 --- a/src/frontend/views/entity/Actions/index.tsx +++ b/src/frontend/views/entity/Actions/index.tsx @@ -41,7 +41,7 @@ export function EntityFormActionsSettings() { }, ]} > - + { - if (type === "entity") { - return `${BASE_ACTIONS_ENDPOINT}/instances/${id}`; - } - return `${BASE_ACTIONS_ENDPOINT}/${id}`; +export const LIST_ACTION_INSTANCES = (entity: string) => { + return `${BASE_ACTIONS_ENDPOINT}/instances/${entity}`; }; export const useIntegrationImplementationsList = (integrationKey: string) => @@ -32,14 +28,12 @@ export const useIntegrationImplementationsList = (integrationKey: string) => } ); -export function useDeleteActionInstanceMutation( - actionInstanceView: ActionInstanceView -) { +export function useDeleteActionInstanceMutation(entity: string) { const apiMutateOptions = useApiMutateOptimisticOptions< IActionInstance[], string >({ - dataQueryPath: LIST_ACTION_INSTANCES(actionInstanceView), + dataQueryPath: LIST_ACTION_INSTANCES(entity), otherEndpoints: [BASE_ACTIONS_ENDPOINT], successMessage: ADMIN_ACTION_INSTANCES_CRUD_CONFIG.MUTATION_LANG.DELETE, onMutate: MutationHelpers.deleteByKey("instanceId") as unknown as ( diff --git a/src/frontend/views/entity/Actions/types.ts b/src/frontend/views/entity/Actions/types.ts deleted file mode 100644 index 3219a2539..000000000 --- a/src/frontend/views/entity/Actions/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type ActionInstanceView = { - type: "entity" | "integrationKey"; - id: string; -}; diff --git a/src/frontend/views/integrations/Password.tsx b/src/frontend/views/integrations/Password.tsx index 0a788bd2b..91de7b9da 100644 --- a/src/frontend/views/integrations/Password.tsx +++ b/src/frontend/views/integrations/Password.tsx @@ -25,7 +25,7 @@ export function PasswordToReveal({ <> For security reasons, Please input your account password to be able to - reveal {label} + manage {label} integrationKey), - ] as string[]; - return ( @@ -43,7 +39,7 @@ export function BaseActionsLayout({ children }: IProps) { listLengthGuess={7} labelField="title" render={(menuItem) => { - const isActive = activeList.includes(menuItem.key); + const isActive = activeActionList.data.includes(menuItem.key); const props: IListMangerItemProps = { label: menuItem.title, IconComponent: isActive ? Zap : ZapOff, diff --git a/src/frontend/views/integrations/actions/View/index.tsx b/src/frontend/views/integrations/actions/View/index.tsx index ff3895eba..2a9afbcea 100644 --- a/src/frontend/views/integrations/actions/View/index.tsx +++ b/src/frontend/views/integrations/actions/View/index.tsx @@ -1,6 +1,5 @@ import { SchemaForm } from "frontend/components/SchemaForm"; -import { IIntegrationsList, IActivatedAction } from "shared/types/actions"; -import { BaseActionInstances } from "frontend/views/entity/Actions/Base"; +import { ActionIntegrationKeys, IIntegrationsList } from "shared/types/actions"; import { Typo } from "frontend/design-system/primitives/Typo"; import { Spacer } from "frontend/design-system/primitives/Spacer"; import { Tabs } from "frontend/design-system/components/Tabs"; @@ -11,7 +10,7 @@ import { PasswordMessage } from "../../Password"; interface IProps { integrationDetail?: IIntegrationsList; - activeAction?: IActivatedAction; + activeAction?: ActionIntegrationKeys; } export function ActionSettingsView({ @@ -49,20 +48,11 @@ export function ActionSettingsView({ return ( - ), - }, { label: "Configure", content: ( ), @@ -71,7 +61,7 @@ export function ActionSettingsView({ label: "Deactivate", content: ( ), diff --git a/src/frontend/views/integrations/actions/actions.store.ts b/src/frontend/views/integrations/actions/actions.store.ts index bc946384a..dd5dd4380 100644 --- a/src/frontend/views/integrations/actions/actions.store.ts +++ b/src/frontend/views/integrations/actions/actions.store.ts @@ -1,9 +1,5 @@ import { useMutation } from "react-query"; -import { - IIntegrationsList, - IActivatedAction, - ActionIntegrationKeys, -} from "shared/types/actions"; +import { IIntegrationsList, ActionIntegrationKeys } from "shared/types/actions"; import { CRUD_CONFIG_NOT_FOUND } from "frontend/lib/crud-config"; import { reduceStringToNumber } from "shared/lib/strings"; import { makeActionRequest } from "frontend/lib/data/makeRequest"; @@ -25,8 +21,8 @@ export const useActionIntegrationsList = () => }); export const useActiveActionList = () => - useApi(ACTIVE_ACTIONS_INTEGRATIONS_ENDPOINT, { - errorMessage: CRUD_CONFIG_NOT_FOUND("Active Actions"), + useApi(ACTIVE_ACTIONS_INTEGRATIONS_ENDPOINT, { + errorMessage: CRUD_CONFIG_NOT_FOUND("Activated Integrations"), defaultData: [], }); diff --git a/src/frontend/views/integrations/actions/index.tsx b/src/frontend/views/integrations/actions/index.tsx index b8d2721c2..9e4e46450 100644 --- a/src/frontend/views/integrations/actions/index.tsx +++ b/src/frontend/views/integrations/actions/index.tsx @@ -33,13 +33,13 @@ export function ActionsIntegrations() { ); const activeAction = activeActionsList.data.find( - ({ integrationKey }) => integrationKey === currentKey + (integrationKey) => integrationKey === currentKey ); useSetPageDetails({ pageTitle: ACTION_INTEGRATIONS_CRUD_CONFIG.TEXT_LANG.TITLE, viewKey: ACTIONS_VIEW_KEY, - permission: USER_PERMISSIONS.CAN_MANAGE_INTEGRATIONS, + permission: USER_PERMISSIONS.CAN_MANAGE_APP_CREDENTIALS, }); return ( diff --git a/src/frontend/views/integrations/storage/Credentials.tsx b/src/frontend/views/integrations/storage/Credentials.tsx index 7be3e5586..b85cb5de8 100644 --- a/src/frontend/views/integrations/storage/Credentials.tsx +++ b/src/frontend/views/integrations/storage/Credentials.tsx @@ -8,7 +8,7 @@ import { } from "frontend/design-system/components/Skeleton/Form"; import { ISchemaFormConfig } from "shared/form-schemas/types"; import { noop } from "shared/lib/noop"; -import { IIntegrationsList } from "shared/types/actions"; +import { IStorageIntegration } from "shared/types/actions"; import { STORAGE_INTEGRATIONS_CRUD_CONFIG } from "./constants"; import { useActivateStorageMutation, @@ -27,7 +27,7 @@ export function StorageCredentialsSettings() { const [currentStorage, setCurrentStorage] = useState(""); - const currentStorageDetails: IIntegrationsList | undefined = + const currentStorageDetails: IStorageIntegration | undefined = storageList.data.find((datum) => datum.key === currentStorage); useEffect(() => { diff --git a/src/frontend/views/integrations/storage/index.tsx b/src/frontend/views/integrations/storage/index.tsx index 303cc0870..0e557c53a 100644 --- a/src/frontend/views/integrations/storage/index.tsx +++ b/src/frontend/views/integrations/storage/index.tsx @@ -13,7 +13,7 @@ export function StorageIntegrations() { useSetPageDetails({ pageTitle: STORAGE_INTEGRATIONS_CRUD_CONFIG.TEXT_LANG.TITLE, viewKey: ACTIONS_VIEW_KEY, - permission: USER_PERMISSIONS.CAN_MANAGE_INTEGRATIONS, + permission: USER_PERMISSIONS.CAN_MANAGE_APP_CREDENTIALS, }); return ( diff --git a/src/frontend/views/integrations/storage/storage.store.ts b/src/frontend/views/integrations/storage/storage.store.ts index f5b188bc1..c0450ee12 100644 --- a/src/frontend/views/integrations/storage/storage.store.ts +++ b/src/frontend/views/integrations/storage/storage.store.ts @@ -1,15 +1,15 @@ import { useMutation } from "react-query"; -import { IIntegrationsList } from "shared/types/actions"; import { CRUD_CONFIG_NOT_FOUND } from "frontend/lib/crud-config"; import { reduceStringToNumber } from "shared/lib/strings"; import { useApi } from "frontend/lib/data/useApi"; import { useWaitForResponseMutationOptions } from "frontend/lib/data/useMutate/useWaitForResponseMutationOptions"; import { makeActionRequest } from "frontend/lib/data/makeRequest"; +import { IStorageIntegration } from "shared/types/actions"; import { usePasswordStore } from "../password.store"; import { STORAGE_INTEGRATIONS_CRUD_CONFIG } from "./constants"; export const useStorageIntegrationsList = () => - useApi("/api/integrations/storage/list", { + useApi("/api/integrations/storage/list", { errorMessage: STORAGE_INTEGRATIONS_CRUD_CONFIG.TEXT_LANG.NOT_FOUND, defaultData: [], }); diff --git a/src/frontend/views/roles/Permissions/MutatePermission.tsx b/src/frontend/views/roles/Permissions/MutatePermission.tsx index e7f3c9d81..88c56d52d 100644 --- a/src/frontend/views/roles/Permissions/MutatePermission.tsx +++ b/src/frontend/views/roles/Permissions/MutatePermission.tsx @@ -27,7 +27,7 @@ const PERMISSION_HEIRACHIES: [string, string][] = [ [USER_PERMISSIONS.CAN_MANAGE_USERS, USER_PERMISSIONS.CAN_RESET_PASSWORD], [ USER_PERMISSIONS.CAN_CONFIGURE_APP, - USER_PERMISSIONS.CAN_MANAGE_INTEGRATIONS, + USER_PERMISSIONS.CAN_MANAGE_APP_CREDENTIALS, ], [ USER_PERMISSIONS.CAN_MANAGE_ALL_ENTITIES, diff --git a/src/frontend/views/settings/Variables/ManageCredentialGroup.tsx b/src/frontend/views/settings/Variables/ManageCredentialGroup.tsx index 531d8bba4..2d44780e6 100644 --- a/src/frontend/views/settings/Variables/ManageCredentialGroup.tsx +++ b/src/frontend/views/settings/Variables/ManageCredentialGroup.tsx @@ -101,7 +101,7 @@ export function ManageCredentialGroup({ const canManageAction = !( group === IntegrationsConfigurationGroup.Credentials && - !userHasPermission(USER_PERMISSIONS.CAN_MANAGE_INTEGRATIONS) + !userHasPermission(USER_PERMISSIONS.CAN_MANAGE_APP_CREDENTIALS) ); const showManageAction = @@ -178,7 +178,7 @@ export function ManageCredentialGroup({ <>
{group === IntegrationsConfigurationGroup.Credentials && - userHasPermission(USER_PERMISSIONS.CAN_MANAGE_INTEGRATIONS) && + userHasPermission(USER_PERMISSIONS.CAN_MANAGE_APP_CREDENTIALS) && revealedCredentials.data === undefined && ( { _type: "canUser", body: group === IntegrationsConfigurationGroup.Credentials - ? USER_PERMISSIONS.CAN_MANAGE_INTEGRATIONS + ? USER_PERMISSIONS.CAN_MANAGE_APP_CREDENTIALS : USER_PERMISSIONS.CAN_CONFIGURE_APP, }, ]; diff --git a/src/pages/api/integrations/actions/[key]/credentials.ts b/src/pages/api/integrations/actions/[key]/credentials.ts index c40892909..e3308c1e3 100644 --- a/src/pages/api/integrations/actions/[key]/credentials.ts +++ b/src/pages/api/integrations/actions/[key]/credentials.ts @@ -1,6 +1,6 @@ import { USER_PERMISSIONS } from "shared/constants/user"; -import { actionsApiController } from "backend/actions/actions.controller"; import { requestHandler } from "backend/lib/request"; +import { actionsApiService } from "backend/actions/actions.service"; const REQUEST_KEY_FIELD = "key"; @@ -14,7 +14,7 @@ export default requestHandler( }, ]); - return await actionsApiController.showActionConfig( + return await actionsApiService.getIntegrationCredentials( validatedRequest.requestQuery ); }, @@ -22,7 +22,7 @@ export default requestHandler( [ { _type: "canUser", - body: USER_PERMISSIONS.CAN_MANAGE_INTEGRATIONS, + body: USER_PERMISSIONS.CAN_MANAGE_APP_CREDENTIALS, }, { _type: "withPassword", diff --git a/src/pages/api/integrations/actions/[key]/implementations.ts b/src/pages/api/integrations/actions/[key]/implementations.ts index 0a99765ba..be81542fa 100644 --- a/src/pages/api/integrations/actions/[key]/implementations.ts +++ b/src/pages/api/integrations/actions/[key]/implementations.ts @@ -1,4 +1,4 @@ -import { actionsApiController } from "backend/actions/actions.controller"; +import { actionsApiService } from "backend/actions/actions.service"; import { requestHandler } from "backend/lib/request"; const REQUEST_KEY_FIELD = "key"; @@ -12,7 +12,7 @@ export default requestHandler({ }, ]); - return actionsApiController.listIntegrationImplementations( + return actionsApiService.listIntegrationImplementations( validatedRequest.requestQuery ); }, diff --git a/src/pages/api/integrations/actions/[key]/index.ts b/src/pages/api/integrations/actions/[key]/index.ts index b1ac2b073..4648553b1 100644 --- a/src/pages/api/integrations/actions/[key]/index.ts +++ b/src/pages/api/integrations/actions/[key]/index.ts @@ -1,23 +1,11 @@ import { USER_PERMISSIONS } from "shared/constants/user"; -import { actionsApiController } from "backend/actions/actions.controller"; import { requestHandler } from "backend/lib/request"; +import { actionsApiService } from "backend/actions/actions.service"; const REQUEST_KEY_FIELD = "key"; export default requestHandler( { - GET: async (getValidatedRequest) => { - const validatedRequest = await getValidatedRequest([ - { - _type: "requestQuery", - options: REQUEST_KEY_FIELD, - }, - ]); - - return await actionsApiController.listIntegrationActionInstances( - validatedRequest.requestQuery - ); - }, POST: async (getValidatedRequest) => { const validatedRequest = await getValidatedRequest([ { @@ -30,7 +18,7 @@ export default requestHandler( }, ]); - return await actionsApiController.activateAction( + return await actionsApiService.activateAction( validatedRequest.requestQuery, validatedRequest.requestBody ); @@ -48,7 +36,7 @@ export default requestHandler( "withPassword", ]); - return await actionsApiController.updateActionConfig( + return await actionsApiService.updateIntegrationConfig( validatedRequest.requestQuery, validatedRequest.requestBody ); @@ -61,7 +49,7 @@ export default requestHandler( }, ]); - return await actionsApiController.deactivateAction( + return await actionsApiService.deactivateIntegration( validatedRequest.requestQuery ); }, @@ -69,7 +57,7 @@ export default requestHandler( [ { _type: "canUser", - body: USER_PERMISSIONS.CAN_MANAGE_INTEGRATIONS, + body: USER_PERMISSIONS.CAN_MANAGE_APP_CREDENTIALS, }, ] ); diff --git a/src/pages/api/integrations/actions/active.ts b/src/pages/api/integrations/actions/active.ts index 354f888f6..7496e3731 100644 --- a/src/pages/api/integrations/actions/active.ts +++ b/src/pages/api/integrations/actions/active.ts @@ -1,17 +1,8 @@ -import { USER_PERMISSIONS } from "shared/constants/user"; -import { actionsApiController } from "backend/actions/actions.controller"; import { requestHandler } from "backend/lib/request"; +import { actionsApiService } from "backend/actions/actions.service"; -export default requestHandler( - { - GET: async () => { - return await actionsApiController.listActivatedActions(); - }, +export default requestHandler({ + GET: async () => { + return await actionsApiService.listActivatedActions(); }, - [ - { - _type: "canUser", - body: USER_PERMISSIONS.CAN_MANAGE_INTEGRATIONS, - }, - ] -); +}); diff --git a/src/pages/api/integrations/actions/instances/[key].ts b/src/pages/api/integrations/actions/instances/[key].ts index 042dfc155..c2a377e05 100644 --- a/src/pages/api/integrations/actions/instances/[key].ts +++ b/src/pages/api/integrations/actions/instances/[key].ts @@ -1,6 +1,6 @@ import { USER_PERMISSIONS } from "shared/constants/user"; -import { actionsApiController } from "backend/actions/actions.controller"; import { requestHandler } from "backend/lib/request"; +import { actionsApiService } from "backend/actions/actions.service"; const REQUEST_KEY_FIELD = "key"; @@ -14,7 +14,7 @@ export default requestHandler( }, ]); - return await actionsApiController.listEntityActionInstances( + return await actionsApiService.listEntityActionInstances( validatedRequest.requestQuery ); }, @@ -30,7 +30,7 @@ export default requestHandler( }, ]); - return await actionsApiController.updateActionInstance( + return await actionsApiService.updateActionInstance( validatedRequest.requestQuery, validatedRequest.requestBody ); @@ -43,7 +43,7 @@ export default requestHandler( }, ]); - return await actionsApiController.deleteActionInstance( + return await actionsApiService.deleteActionInstance( validatedRequest.requestQuery ); }, diff --git a/src/pages/api/integrations/actions/instances/index.ts b/src/pages/api/integrations/actions/instances/index.ts index cc4a19059..4bd2d8fec 100644 --- a/src/pages/api/integrations/actions/instances/index.ts +++ b/src/pages/api/integrations/actions/instances/index.ts @@ -1,6 +1,6 @@ import { USER_PERMISSIONS } from "shared/constants/user"; -import { actionsApiController } from "backend/actions/actions.controller"; import { requestHandler } from "backend/lib/request"; +import { actionsApiService } from "backend/actions/actions.service"; export default requestHandler( { @@ -12,7 +12,7 @@ export default requestHandler( }, ]); - return await actionsApiController.instantiateAction( + return await actionsApiService.instantiateAction( validatedRequest.requestBody ); }, diff --git a/src/pages/api/integrations/actions/list.ts b/src/pages/api/integrations/actions/list.ts index 8467c9949..3e7952ea3 100644 --- a/src/pages/api/integrations/actions/list.ts +++ b/src/pages/api/integrations/actions/list.ts @@ -1,8 +1,8 @@ -import { actionsApiController } from "backend/actions/actions.controller"; +import { actionsApiService } from "backend/actions/actions.service"; import { requestHandler } from "backend/lib/request"; export default requestHandler({ GET: () => { - return actionsApiController.listIntegrations(); + return actionsApiService.listActionIntegrations(); }, }); diff --git a/src/pages/api/integrations/credentials/reveal.ts b/src/pages/api/integrations/credentials/reveal.ts index b8b9e8288..5743f1345 100644 --- a/src/pages/api/integrations/credentials/reveal.ts +++ b/src/pages/api/integrations/credentials/reveal.ts @@ -11,7 +11,7 @@ export default requestHandler( [ { _type: "canUser", - body: USER_PERMISSIONS.CAN_MANAGE_INTEGRATIONS, + body: USER_PERMISSIONS.CAN_MANAGE_APP_CREDENTIALS, }, { _type: "withPassword", diff --git a/src/pages/api/integrations/storage/active.ts b/src/pages/api/integrations/storage/active.ts index d9c0ef378..8dd5169dc 100644 --- a/src/pages/api/integrations/storage/active.ts +++ b/src/pages/api/integrations/storage/active.ts @@ -1,11 +1,11 @@ import { USER_PERMISSIONS } from "shared/constants/user"; -import { storageApiController } from "backend/storage/storage.controller"; import { requestHandler } from "backend/lib/request"; +import { storageApiService } from "backend/storage/storage.service"; export default requestHandler( { GET: async () => { - return await storageApiController.getCurrentActivatedStorage(); + return { data: await storageApiService.getCurrentActivatedStorage() }; }, POST: async (getValidatedRequest) => { const validatedRequest = await getValidatedRequest([ @@ -15,7 +15,7 @@ export default requestHandler( }, ]); - return await storageApiController.activateStorage( + return await storageApiService.activateStorage( validatedRequest.requestBody ); }, @@ -23,7 +23,7 @@ export default requestHandler( [ { _type: "canUser", - body: USER_PERMISSIONS.CAN_MANAGE_INTEGRATIONS, + body: USER_PERMISSIONS.CAN_MANAGE_APP_CREDENTIALS, }, ] ); diff --git a/src/pages/api/integrations/storage/credentials.ts b/src/pages/api/integrations/storage/credentials.ts index a67f285df..72f728b3c 100644 --- a/src/pages/api/integrations/storage/credentials.ts +++ b/src/pages/api/integrations/storage/credentials.ts @@ -1,17 +1,17 @@ import { USER_PERMISSIONS } from "shared/constants/user"; -import { storageApiController } from "backend/storage/storage.controller"; import { requestHandler } from "backend/lib/request"; +import { storageApiService } from "backend/storage/storage.service"; export default requestHandler( { POST: async () => { - return await storageApiController.showStorageCredentials(); + return await storageApiService.showStorageCredentials(); }, }, [ { _type: "canUser", - body: USER_PERMISSIONS.CAN_MANAGE_INTEGRATIONS, + body: USER_PERMISSIONS.CAN_MANAGE_APP_CREDENTIALS, }, { _type: "withPassword", diff --git a/src/pages/api/integrations/storage/list.ts b/src/pages/api/integrations/storage/list.ts index d6378759d..2dfb3ffd3 100644 --- a/src/pages/api/integrations/storage/list.ts +++ b/src/pages/api/integrations/storage/list.ts @@ -1,8 +1,8 @@ -import { storageApiController } from "backend/storage/storage.controller"; import { requestHandler } from "backend/lib/request"; +import { storageApiService } from "backend/storage/storage.service"; export default requestHandler({ GET: () => { - return storageApiController.listIntegrations(); + return storageApiService.listStorageIntegrations(); }, }); diff --git a/src/shared/constants/menu.ts b/src/shared/constants/menu.ts index 4d57cce9e..01c12f3c8 100644 --- a/src/shared/constants/menu.ts +++ b/src/shared/constants/menu.ts @@ -28,6 +28,6 @@ export const SYSTEM_LINKS_CONFIG_MAP: Record< }, [SystemLinks.Integrations]: { link: NAVIGATION_LINKS.INTEGRATIONS.ACTIONS(ActionIntegrationKeys.HTTP), - permission: USER_PERMISSIONS.CAN_MANAGE_INTEGRATIONS, + permission: USER_PERMISSIONS.CAN_MANAGE_APP_CREDENTIALS, }, }; diff --git a/src/shared/constants/user/index.ts b/src/shared/constants/user/index.ts index eda88221a..98afd7f5a 100644 --- a/src/shared/constants/user/index.ts +++ b/src/shared/constants/user/index.ts @@ -30,7 +30,7 @@ export const BASE_USER_PERMISSIONS = { CAN_CONFIGURE_APP: "CAN_CONFIGURE_APP", CAN_RESET_PASSWORD: "CAN_RESET_PASSWORD", CAN_MANAGE_DASHBOARD: "CAN_MANAGE_DASHBOARD", - CAN_MANAGE_INTEGRATIONS: "CAN_MANAGE_INTEGRATIONS", + CAN_MANAGE_APP_CREDENTIALS: "CAN_MANAGE_APP_CREDENTIALS", CAN_MANAGE_PERMISSIONS: "CAN_MANAGE_PERMISSIONS", CAN_MANAGE_ALL_ENTITIES: "CAN_MANAGE_ALL_ENTITIES", }; diff --git a/src/shared/types/actions.ts b/src/shared/types/actions.ts index 1d2991c36..90bc6b276 100644 --- a/src/shared/types/actions.ts +++ b/src/shared/types/actions.ts @@ -1,4 +1,5 @@ import { IAppliedSchemaFormConfig } from "shared/form-schemas/types"; +import { DataEventActions } from "./data"; export enum ActionIntegrationKeys { HTTP = "http", @@ -11,26 +12,12 @@ export enum ActionIntegrationKeys { SEND_IN_BLUE = "sendInBlue", } -export interface IActivatedAction { - activationId: string; - integrationKey: ActionIntegrationKeys; - credentialsGroupKey: string; -} - -export enum BaseAction { - Create = "create", - Update = "update", - Delete = "delete", -} - export type IActionInstance = { instanceId: string; - activatedActionId: string; - integrationKey: string; + integrationKey: ActionIntegrationKeys; entity: string; implementationKey: string; - triggerLogic: string; - formAction: BaseAction | string; + formAction: DataEventActions; configuration: Record; }; @@ -48,7 +35,13 @@ export interface IActionIntegrationsImplemention { performsImplementation: Record; } -export type IIntegrationsList = { key: string } & Pick< +export interface IStorageIntegration { + title: string; + key: string; + configurationSchema: IAppliedSchemaFormConfig; +} + +export type IIntegrationsList = { key: ActionIntegrationKeys } & Pick< IActionIntegrationsImplemention, "title" | "description" | "configurationSchema" >; @@ -57,5 +50,3 @@ export type IIntegrationImplementationList = { key: string } & Pick< IPerformsImplementation, "label" | "configurationSchema" >; - -export const HTTP_ACTIVATION_ID = "http"; diff --git a/src/shared/types/data.ts b/src/shared/types/data.ts index bae12ebcb..60c8514be 100644 --- a/src/shared/types/data.ts +++ b/src/shared/types/data.ts @@ -15,6 +15,12 @@ export enum FilterOperators { IS_NULL = "s", } +export enum DataEventActions { + Create = "create", + Update = "update", + Delete = "delete", +} + export interface IColumnFilterBag { operator?: FilterOperators; value?: T;