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;
-};