diff --git a/src/__tests__/_/api-handlers/form-actions.ts b/src/__tests__/_/api-handlers/form-actions.ts new file mode 100644 index 000000000..65d510466 --- /dev/null +++ b/src/__tests__/_/api-handlers/form-actions.ts @@ -0,0 +1,81 @@ +import { rest } from "msw"; +import { BASE_TEST_URL } from "./_utils"; + +const FORM_ACTIONS = [ + { + id: "1", + integration: "http", + entity: "test-entity-1", + trigger: "create", + action: "POST", + configuration: { + url: "http://localhost:3000", + method: "GET", + }, + }, + { + id: "2", + integration: "smtp", + entity: "test-entity-1", + trigger: "update", + action: "SEND_MAIL", + configuration: { + url: "http://localhost:3000", + method: "GET", + }, + }, + { + id: "3", + integration: "slack", + entity: "test-entity-1", + trigger: "delete", + action: "SEND_MESSAGE", + configuration: { + url: "http://localhost:3000", + method: "GET", + }, + }, +]; + +export const formActionsApiHandlers = [ + rest.get(BASE_TEST_URL("/api/form-actions/:entity"), async (_, res, ctx) => { + return res(ctx.json(FORM_ACTIONS)); + }), + rest.post(BASE_TEST_URL("/api/form-actions"), async (req, res, ctx) => { + const newFormAction = await req.json(); + FORM_ACTIONS.push(newFormAction); + if ( + JSON.stringify(newFormAction) === + '{"configuration":{"channel":"{ CONSTANTS.SLACK_CHANNEL }}","message":"Hello how are youHello how are you","shouldNotify":true},"entity":"test-entity","trigger":"create","integration":"slack","action":"send_message"}' + ) { + return res(ctx.status(204)); + } + return res(ctx.status(400)); + }), + rest.patch( + BASE_TEST_URL("/api/form-actions/:formActionId"), + async (req, res, ctx) => { + const formActionId = req.params.formActionId as string; + const formAction = await req.json(); + + const index = FORM_ACTIONS.findIndex(({ id }) => id === formActionId); + + FORM_ACTIONS[index] = formAction; + + return res(ctx.status(204)); + } + ), + rest.delete( + BASE_TEST_URL("/api/form-actions/:formActionId"), + async (req, res, ctx) => { + const formActionId = req.params.formActionId as string; + + FORM_ACTIONS.splice( + FORM_ACTIONS.findIndex(({ id }) => id === formActionId), + 1 + ); + + return res(ctx.status(204)); + } + ), +]; diff --git a/src/__tests__/_/api-handlers/index.ts b/src/__tests__/_/api-handlers/index.ts index 4e65c728c..e19c74113 100644 --- a/src/__tests__/_/api-handlers/index.ts +++ b/src/__tests__/_/api-handlers/index.ts @@ -11,6 +11,8 @@ import { versionApiHandlers } from "./versions"; import { portalApiHandlers } from "./portal"; import { menuApiHandlers } from "./menu"; import { userPreferencesApiHandlers } from "./user-preferences"; +import { formActionsApiHandlers } from "./form-actions"; +import { integrationsListApiHandlers } from "./integrations-list"; export const apiHandlers = [ ...setupApiHandlers, @@ -21,9 +23,11 @@ export const apiHandlers = [ ...integrationsApiHandlers, ...rolesApiHandlers, ...configApiHandlers, + ...integrationsListApiHandlers, ...dashboardApiHandlers, ...versionApiHandlers, ...portalApiHandlers, ...menuApiHandlers, + ...formActionsApiHandlers, ...userPreferencesApiHandlers, ]; diff --git a/src/__tests__/_/api-handlers/actions.ts b/src/__tests__/_/api-handlers/integrations-list.ts similarity index 56% rename from src/__tests__/_/api-handlers/actions.ts rename to src/__tests__/_/api-handlers/integrations-list.ts index 966291c55..90938bbcd 100644 --- a/src/__tests__/_/api-handlers/actions.ts +++ b/src/__tests__/_/api-handlers/integrations-list.ts @@ -1,7 +1,7 @@ import { rest } from "msw"; import { BASE_TEST_URL } from "./_utils"; -export const actionsApiHandlers = [ +export const integrationsListApiHandlers = [ rest.get( BASE_TEST_URL("/api/integrations/actions/list"), async (_, res, ctx) => { @@ -65,30 +65,53 @@ export const actionsApiHandlers = [ return res(ctx.json(["http", "slack"])); } ), - // rest.put( - // BASE_TEST_URL("/api/integrations/constants/:key"), - // async (req, res, ctx) => { - // const key = req.params.key as string; - // const { value } = await req.json(); - - // const index = CONSTANTS.findIndex((constant) => constant.key === key); - - // if (index > -1) { - // CONSTANTS[index] = { key, value }; - // } else { - // CONSTANTS.push({ key, value }); - // } - - // return res(ctx.status(204)); - // } - // ), - // rest.delete( - // BASE_TEST_URL("/api/integrations/constants/:key"), - // async (req, res, ctx) => { - // const key = req.params.key as string; - - // CONSTANTS = CONSTANTS.filter((permission$1) => permission$1.key !== key); - // return res(ctx.status(204)); - // } - // ), + rest.get( + BASE_TEST_URL("/api/integrations/actions/:integration/implementations"), + async (_, res, ctx) => { + return res( + ctx.json([ + { + key: "send_message", + label: "Send Message", + configurationSchema: { + channel: { + type: "text", + validations: [ + { + validationType: "required", + }, + ], + }, + message: { + type: "text", + validations: [ + { + validationType: "required", + }, + ], + }, + shouldNotify: { + type: "boolean", + validations: [], + }, + }, + }, + { + key: "send_mail", + label: "Send Mail", + configurationSchema: { + message: { + type: "text", + validations: [ + { + validationType: "required", + }, + ], + }, + }, + }, + ]) + ); + } + ), ]; diff --git a/src/__tests__/_/forCodeCoverage.spec.ts b/src/__tests__/_/forCodeCoverage.spec.ts index d1e2913b0..79608363c 100644 --- a/src/__tests__/_/forCodeCoverage.spec.ts +++ b/src/__tests__/_/forCodeCoverage.spec.ts @@ -35,6 +35,7 @@ import { FOR_CODE_COV as $41 } from "frontend/design-system/components/Form/_typ import { FOR_CODE_COV as $42 } from "backend/menu/types"; import { FOR_CODE_COV as $43 } from "frontend/lib/form/types"; import { FOR_CODE_COV as $44 } from "frontend/design-system/components/Table/filters/types"; +import { FOR_CODE_COV as $45 } from "shared/form-schemas/users"; import { noop } from "shared/lib/noop"; @@ -75,7 +76,8 @@ noop( $41, $42, $43, - $44 + $44, + $45 ); describe("Code coverage ignores plain types file", () => { diff --git a/src/__tests__/admin/[entity]/config/actions.spec.tsx b/src/__tests__/admin/[entity]/config/actions.spec.tsx new file mode 100644 index 000000000..48635bec8 --- /dev/null +++ b/src/__tests__/admin/[entity]/config/actions.spec.tsx @@ -0,0 +1,125 @@ +import React from "react"; +import { render, screen, within } from "@testing-library/react"; +import { ApplicationRoot } from "frontend/components/ApplicationRoot"; +import userEvent from "@testing-library/user-event"; +import EntityFormActionsSettings from "pages/admin/[entity]/config/actions"; + +import { setupApiHandlers } from "__tests__/_/setupApihandlers"; +import { getTableRows } from "__tests__/_/utiis/getTableRows"; + +setupApiHandlers(); + +describe("pages/admin/[entity]/config/actions", () => { + beforeAll(() => { + const useRouter = jest.spyOn(require("next/router"), "useRouter"); + useRouter.mockImplementation(() => ({ + asPath: "/", + query: { + entity: "test-entity", + }, + isReady: true, + })); + }); + + it.skip("should list entity form actions", async () => { + render( + + x + + ); + + expect(await screen.findByRole("table")).toBeInTheDocument(); + + expect(await getTableRows(screen.getByRole("table"))) + .toMatchInlineSnapshot(` + [ + "Integration + + Trigger + + Action + + Action", + "HttpCreatePost", + "SmtpUpdateSend Mail", + "SlackDeleteSend Message", + ] + `); + }); + + it("should create new form action successfully", async () => { + render( + + + + ); + + await userEvent.click( + await screen.findByRole("button", { name: "Add New Form Action" }) + ); + + const dialog = screen.getByRole("dialog"); + + await userEvent.type(within(dialog).getByLabelText("Trigger"), "On Create"); + await userEvent.keyboard("{Enter}"); + + await userEvent.click( + within(dialog).getByRole("option", { name: "Slack" }) + ); + + expect( + within(dialog).queryByRole("option", { name: "SMTP" }) + ).not.toBeInTheDocument(); + + await userEvent.type( + within(dialog).getByLabelText("Action"), + "Send Message" + ); + await userEvent.keyboard("{Enter}"); + + await userEvent.type( + await within(dialog).findByLabelText("Slack: Channel"), + "{{ CONSTANTS.SLACK_CHANNEL }}" + ); + + await userEvent.type( + within(dialog).getByLabelText("Slack: Message"), + "Hello how are you" + ); + + await userEvent.type( + within(dialog).getByLabelText("Slack: Message"), + "Hello how are you" + ); + + await userEvent.click(screen.getByLabelText("Slack: Should Notify")); + + await userEvent.click( + within(dialog).getByRole("button", { name: "Create Form Action" }) + ); + + expect(await screen.findByRole("status")).toHaveTextContent( + "Form Action Created Successfully" + ); + + expect( + screen.queryByRole("button", { name: "Create Form Action" }) + ).not.toBeInTheDocument(); + }); + + // it("should display updated diction values", async () => { + // render( + // + // + // + // ); + // await waitFor(() => { + // expect(screen.getByLabelText("Plural")).toHaveValue( + // "Plural entity-1Updated" + // ); + // }); + // expect(screen.getByLabelText("Singular")).toHaveValue( + // "Singular entity-1Updated" + // ); + // }); +}); diff --git a/src/bin/index.ts b/src/bin/index.ts index b05435c3b..fee1fa3f9 100644 --- a/src/bin/index.ts +++ b/src/bin/index.ts @@ -57,12 +57,12 @@ const replaceRandomCharaters = (envContent: string) => { console.log(` /$$ /$$ | $$ | $$ - /$$$$$$$ /$$$$$$ /$$$$$$$| $$$$$$$ /$$$$$$ /$$$$$$ / $$$$$$$$ /$$$$$$$ /$$$$$$$ - /$$__ $$ |____ $$ /$$_____/| $$__ $$ / $$__ $$/ $$__ $$ / $$__ $$ /$$_____//$$_____/ -| $$ | $$ /$$$$$$$| $$$$$$ | $$ \\ $$ | $$ \\ $$| $$ \\__/| $$$$$$$$| $$$$$$| $$$$$$ -| $$ | $$ / $$__ $$ \\____$$ | $$ | $$ | $$ | $$| $$ | $$_____/\\____ $$\\____ $$ -| $$$$$$$ | $$$$$$$ /$$$$$$$/| $$ | $$ | $$$$$$$/| $$ | $$$$$$$ /$$$$$$$//$$$$$$$/ -\\_______/ \\_______/|_______/ |__/ |__/| $$____/ |__/ \\_______/|_______/|_______/ + /$$$$$$$ /$$$$$$ /$$$$$$ | $$$$$$$ /$$$$$$ /$$$$$$ / $$$$$$$$ /$$$$$$$ /$$$$$$$ + /$$__ $$ |____ $$ /$$____/ | $$__ $$ / $$__ $$ / $$__ $$ | $$__ $$ /$$_____//$$____/ +| $$ | $$ /$$$$$$$ | $$$$$$ | $$ \\ $$ | $$ \\ $$| $$ \\__ | $$$$$$$$ | $$$$$$| $$$$$$ +| $$ | $$ / $$__ $$ \\_____$$ | $$ | $$ | $$ | $$| $$ | $$_____/ \\____ $$ \\____ $$ +| $$$$$$$ | $$$$$$$ /$$$$$$$/| $$ | $$ | $$$$$$$/| $$ | $$$$$$$ /$$$$$$$//$$$$$$$/ +\\_______/ \\_______/|_______/ |__/ |__/ | $$____/ |__/ \\_______/|_______/|_______/ | $$ | $$ |__/ diff --git a/src/frontend/views/entity/Actions/Base.tsx b/src/frontend/views/entity/Actions/Base.tsx index e95a39c22..259f2cd5c 100644 --- a/src/frontend/views/entity/Actions/Base.tsx +++ b/src/frontend/views/entity/Actions/Base.tsx @@ -41,8 +41,8 @@ export function FormActions({ entity }: { entity: string }) { }); const deleteFormActionMutation = useDeleteFormActionMutation(entity); - const updateFormActionMutation = useUpdateFormActionMutation(); - const createFormActionMutation = useCreateFormActionMutation(); + const updateFormActionMutation = useUpdateFormActionMutation(entity); + const createFormActionMutation = useCreateFormActionMutation(entity); const [currentFormActionId, setCurrentFormActionId] = useState(""); diff --git a/src/frontend/views/entity/Actions/form-actions.store.ts b/src/frontend/views/entity/Actions/form-actions.store.ts index 39070ab95..99e984b25 100644 --- a/src/frontend/views/entity/Actions/form-actions.store.ts +++ b/src/frontend/views/entity/Actions/form-actions.store.ts @@ -31,7 +31,6 @@ export function useDeleteFormActionMutation(entity: string) { const apiMutateOptions = useApiMutateOptimisticOptions( { dataQueryPath: LIST_ENTITY_FORM_ACTIONS(entity), - otherEndpoints: [LIST_ENTITY_FORM_ACTIONS(entity)], successMessage: FORM_ACTION_CRUD_CONFIG.MUTATION_LANG.DELETE, onMutate: MutationHelpers.deleteByKey("id") as unknown as ( oldData: IFormAction[], @@ -50,30 +49,28 @@ export function useDeleteFormActionMutation(entity: string) { ); } -export function useCreateFormActionMutation() { +export function useCreateFormActionMutation(entity: string) { const apiMutateOptions = useWaitForResponseMutationOptions< Record >({ - endpoints: [FORM_ACTION_CRUD_CONFIG.ENDPOINTS.LIST], + endpoints: [LIST_ENTITY_FORM_ACTIONS(entity)], successMessage: FORM_ACTION_CRUD_CONFIG.MUTATION_LANG.CREATE, }); - return useMutation( - async (configuration: IFormAction) => - await makeActionRequest( - "POST", - FORM_ACTION_CRUD_CONFIG.ENDPOINTS.CREATE, - configuration - ), - apiMutateOptions - ); + return useMutation(async (configuration: IFormAction) => { + return await makeActionRequest( + "POST", + FORM_ACTION_CRUD_CONFIG.ENDPOINTS.CREATE, + configuration + ); + }, apiMutateOptions); } -export function useUpdateFormActionMutation() { +export function useUpdateFormActionMutation(entity: string) { const apiMutateOptions = useWaitForResponseMutationOptions< Record >({ - endpoints: [FORM_ACTION_CRUD_CONFIG.ENDPOINTS.LIST], + endpoints: [LIST_ENTITY_FORM_ACTIONS(entity)], successMessage: FORM_ACTION_CRUD_CONFIG.MUTATION_LANG.EDIT, }); diff --git a/src/frontend/views/users/Create/index.tsx b/src/frontend/views/users/Create/index.tsx index f68acd124..a91580b67 100644 --- a/src/frontend/views/users/Create/index.tsx +++ b/src/frontend/views/users/Create/index.tsx @@ -6,7 +6,7 @@ import { ContentLayout } from "frontend/design-system/components/Section/Section import { SectionBox } from "frontend/design-system/components/Section/SectionBox"; import { AppLayout } from "frontend/_layouts/app"; import { SchemaForm } from "frontend/components/SchemaForm"; -import { ICreateUserForm } from "shared/form-schemas/users/create"; +import { ICreateUserForm } from "shared/form-schemas/users"; import { useDocumentationActionButton } from "frontend/docs/constants"; import { IActionButton } from "frontend/design-system/components/Button/types"; import { IAppliedSchemaFormConfig } from "shared/form-schemas/types"; diff --git a/src/frontend/views/users/Update/index.tsx b/src/frontend/views/users/Update/index.tsx index aad0bd893..aed3b70fc 100644 --- a/src/frontend/views/users/Update/index.tsx +++ b/src/frontend/views/users/Update/index.tsx @@ -23,7 +23,7 @@ import { import { useDocumentationActionButton } from "frontend/docs/constants"; import { IActionButton } from "frontend/design-system/components/Button/types"; import { IAppliedSchemaFormConfig } from "shared/form-schemas/types"; -import { IUpdateUserForm } from "shared/form-schemas/users/update"; +import { IUpdateUserForm } from "shared/form-schemas/users"; import { useUsernameFromRouteParam } from "../hooks"; import { useUpdateUserMutation, diff --git a/src/frontend/views/users/users.store.ts b/src/frontend/views/users/users.store.ts index 528bf7cfd..c17ecd82c 100644 --- a/src/frontend/views/users/users.store.ts +++ b/src/frontend/views/users/users.store.ts @@ -1,7 +1,7 @@ import { NAVIGATION_LINKS } from "frontend/lib/routing/links"; import { useRouter } from "next/router"; import { useMutation } from "react-query"; -import { ICreateUserForm } from "shared/form-schemas/users/create"; +import { ICreateUserForm } from "shared/form-schemas/users"; import { IResetPasswordForm } from "shared/form-schemas/users/reset-password"; import { IAccountProfile } from "shared/types/user"; import { MAKE_CRUD_CONFIG } from "frontend/lib/crud-config"; diff --git a/src/shared/form-schemas/users/create.ts b/src/shared/form-schemas/users/index.ts similarity index 51% rename from src/shared/form-schemas/users/create.ts rename to src/shared/form-schemas/users/index.ts index 49edff6c9..09fc1deca 100644 --- a/src/shared/form-schemas/users/create.ts +++ b/src/shared/form-schemas/users/index.ts @@ -5,3 +5,11 @@ export type ICreateUserForm = { password: string; systemProfile: string; }; + +export type IUpdateUserForm = { + name: string; + role: string; + systemProfile: string; +}; + +export const FOR_CODE_COV = 1; diff --git a/src/shared/form-schemas/users/update.ts b/src/shared/form-schemas/users/update.ts deleted file mode 100644 index c861a8506..000000000 --- a/src/shared/form-schemas/users/update.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type IUpdateUserForm = { - name: string; - role: string; - systemProfile: string; -};