From 364e187080a2030bb4b3e2eae90a1ba30678368d Mon Sep 17 00:00:00 2001 From: Hariom Balhara Date: Sat, 7 Jan 2023 21:20:28 +0530 Subject: [PATCH] Fix/Next.js-13/Circular Dependency in Routing Forms (#6321) Co-authored-by: Peer Richelsen --- .../components/FormInputFields.tsx | 2 +- .../routing-forms/components/SingleForm.tsx | 2 +- .../ee/routing-forms/lib/InitialConfig.tsx | 3 + .../ee/routing-forms/lib/RoutingPages.ts | 16 ++++ .../lib/getQueryBuilderConfig.ts | 63 +++++++++++++ .../ee/routing-forms/lib/processRoute.tsx | 2 +- .../pages/reporting/[...appPages].tsx | 2 +- .../pages/route-builder/[...appPages].tsx | 88 +------------------ .../playwright/tests/basic.e2e.ts | 5 ++ .../ee/routing-forms/types/types.d.ts | 5 ++ 10 files changed, 100 insertions(+), 88 deletions(-) create mode 100644 packages/app-store/ee/routing-forms/lib/InitialConfig.tsx create mode 100644 packages/app-store/ee/routing-forms/lib/RoutingPages.ts create mode 100644 packages/app-store/ee/routing-forms/lib/getQueryBuilderConfig.ts diff --git a/packages/app-store/ee/routing-forms/components/FormInputFields.tsx b/packages/app-store/ee/routing-forms/components/FormInputFields.tsx index 066720327c..a533c2064c 100644 --- a/packages/app-store/ee/routing-forms/components/FormInputFields.tsx +++ b/packages/app-store/ee/routing-forms/components/FormInputFields.tsx @@ -1,8 +1,8 @@ import { App_RoutingForms_Form } from "@prisma/client"; import { Dispatch, SetStateAction } from "react"; +import { getQueryBuilderConfig } from "../lib/getQueryBuilderConfig"; import isRouterLinkedField from "../lib/isRouterLinkedField"; -import { getQueryBuilderConfig } from "../pages/route-builder/[...appPages]"; import { SerializableForm, Response } from "../types/types"; type Props = { diff --git a/packages/app-store/ee/routing-forms/components/SingleForm.tsx b/packages/app-store/ee/routing-forms/components/SingleForm.tsx index 697a6c5ac5..d0122f6652 100644 --- a/packages/app-store/ee/routing-forms/components/SingleForm.tsx +++ b/packages/app-store/ee/routing-forms/components/SingleForm.tsx @@ -35,9 +35,9 @@ import { VerticalDivider, } from "@calcom/ui"; +import { RoutingPages } from "../lib/RoutingPages"; import { getSerializableForm } from "../lib/getSerializableForm"; import { processRoute } from "../lib/processRoute"; -import { RoutingPages } from "../pages/route-builder/[...appPages]"; import { Response, Route, SerializableForm } from "../types/types"; import { FormAction, FormActionsDropdown, FormActionsProvider } from "./FormActions"; import FormInputFields from "./FormInputFields"; diff --git a/packages/app-store/ee/routing-forms/lib/InitialConfig.tsx b/packages/app-store/ee/routing-forms/lib/InitialConfig.tsx new file mode 100644 index 0000000000..3ef1b62f14 --- /dev/null +++ b/packages/app-store/ee/routing-forms/lib/InitialConfig.tsx @@ -0,0 +1,3 @@ +import QueryBuilderInitialConfig from "../components/react-awesome-query-builder/config/config"; + +export const InitialConfig = QueryBuilderInitialConfig; diff --git a/packages/app-store/ee/routing-forms/lib/RoutingPages.ts b/packages/app-store/ee/routing-forms/lib/RoutingPages.ts new file mode 100644 index 0000000000..ba87e6819e --- /dev/null +++ b/packages/app-store/ee/routing-forms/lib/RoutingPages.ts @@ -0,0 +1,16 @@ +import { LocalRoute } from "../types/types"; + +export const RoutingPages: { label: string; value: NonNullable["type"] }[] = [ + { + label: "Custom Page", + value: "customPageMessage", + }, + { + label: "External Redirect", + value: "externalRedirectUrl", + }, + { + label: "Event Redirect", + value: "eventTypeRedirectUrl", + }, +]; diff --git a/packages/app-store/ee/routing-forms/lib/getQueryBuilderConfig.ts b/packages/app-store/ee/routing-forms/lib/getQueryBuilderConfig.ts new file mode 100644 index 0000000000..f76db80617 --- /dev/null +++ b/packages/app-store/ee/routing-forms/lib/getQueryBuilderConfig.ts @@ -0,0 +1,63 @@ +import { FieldTypes } from "../pages/form-edit/[...appPages]"; +import { QueryBuilderUpdatedConfig, RoutingForm } from "../types/types"; +import { InitialConfig } from "./InitialConfig"; + +export function getQueryBuilderConfig(form: RoutingForm, forReporting = false) { + const fields: Record< + string, + { + label: string; + type: string; + valueSources: ["value"]; + fieldSettings: { + listValues?: { + value: string; + title: string; + }[]; + }; + } + > = {}; + form.fields?.forEach((field) => { + if ("routerField" in field) { + field = field.routerField; + } + if (FieldTypes.map((f) => f.value).includes(field.type)) { + const optionValues = field.selectText?.trim().split("\n"); + const options = optionValues?.map((value) => { + const title = value; + return { + value, + title, + }; + }); + + const widget = InitialConfig.widgets[field.type]; + const widgetType = widget.type; + + fields[field.id] = { + label: field.label, + type: widgetType, + valueSources: ["value"], + fieldSettings: { + listValues: options, + }, + // preferWidgets: field.type === "textarea" ? ["textarea"] : [], + }; + } else { + throw new Error("Unsupported field type:" + field.type); + } + }); + + const initialConfigCopy = { ...InitialConfig, operators: { ...InitialConfig.operators } }; + if (forReporting) { + delete initialConfigCopy.operators.is_empty; + delete initialConfigCopy.operators.is_not_empty; + initialConfigCopy.operators.__calReporting = true; + } + // You need to provide your own config. See below 'Config format' + const config: QueryBuilderUpdatedConfig = { + ...initialConfigCopy, + fields: fields, + }; + return config; +} diff --git a/packages/app-store/ee/routing-forms/lib/processRoute.tsx b/packages/app-store/ee/routing-forms/lib/processRoute.tsx index 129ebd5b61..9bb0e834b5 100644 --- a/packages/app-store/ee/routing-forms/lib/processRoute.tsx +++ b/packages/app-store/ee/routing-forms/lib/processRoute.tsx @@ -2,9 +2,9 @@ import jsonLogic from "json-logic-js"; import { Utils as QbUtils } from "react-awesome-query-builder"; import { z } from "zod"; -import { getQueryBuilderConfig } from "../pages/route-builder/[...appPages]"; import { Response, Route, SerializableForm } from "../types/types"; import { zodNonRouterRoute } from "../zod"; +import { getQueryBuilderConfig } from "./getQueryBuilderConfig"; import { isFallbackRoute } from "./isFallbackRoute"; import isRouter from "./isRouter"; import { App_RoutingForms_Form } from ".prisma/client"; diff --git a/packages/app-store/ee/routing-forms/pages/reporting/[...appPages].tsx b/packages/app-store/ee/routing-forms/pages/reporting/[...appPages].tsx index ae16100660..2570fab771 100644 --- a/packages/app-store/ee/routing-forms/pages/reporting/[...appPages].tsx +++ b/packages/app-store/ee/routing-forms/pages/reporting/[...appPages].tsx @@ -24,7 +24,7 @@ import SingleForm, { import QueryBuilderInitialConfig from "../../components/react-awesome-query-builder/config/config"; import "../../components/react-awesome-query-builder/styles.css"; import { JsonLogicQuery } from "../../jsonLogicToPrisma"; -import { getQueryBuilderConfig } from "../route-builder/[...appPages]"; +import { getQueryBuilderConfig } from "../../lib/getQueryBuilderConfig"; export { getServerSideProps }; diff --git a/packages/app-store/ee/routing-forms/pages/route-builder/[...appPages].tsx b/packages/app-store/ee/routing-forms/pages/route-builder/[...appPages].tsx index 88abacdf58..6eaaf3e9d8 100644 --- a/packages/app-store/ee/routing-forms/pages/route-builder/[...appPages].tsx +++ b/packages/app-store/ee/routing-forms/pages/route-builder/[...appPages].tsx @@ -1,8 +1,7 @@ import { useAutoAnimate } from "@formkit/auto-animate/react"; -import { App_RoutingForms_Form } from "@prisma/client"; import Link from "next/link"; import React, { useCallback, useState } from "react"; -import { Query, Config, Builder, Utils as QbUtils } from "react-awesome-query-builder"; +import { Query, Builder, Utils as QbUtils } from "react-awesome-query-builder"; // types import { JsonTree, ImmutableTree, BuilderProps } from "react-awesome-query-builder"; @@ -21,83 +20,19 @@ import { import SingleForm, { getServerSidePropsForSingleFormView as getServerSideProps, } from "../../components/SingleForm"; -import QueryBuilderInitialConfig from "../../components/react-awesome-query-builder/config/config"; import "../../components/react-awesome-query-builder/styles.css"; +import { RoutingPages } from "../../lib/RoutingPages"; import { createFallbackRoute } from "../../lib/createFallbackRoute"; +import { getQueryBuilderConfig } from "../../lib/getQueryBuilderConfig"; import isRouter from "../../lib/isRouter"; -import { GlobalRoute, LocalRoute, SerializableForm, SerializableRoute } from "../../types/types"; -import { FieldTypes } from "../form-edit/[...appPages]"; +import { GlobalRoute, LocalRoute, QueryBuilderUpdatedConfig, SerializableRoute } from "../../types/types"; export { getServerSideProps }; -type RoutingForm = SerializableForm; - -const InitialConfig = QueryBuilderInitialConfig; const hasRules = (route: Route) => { if (isRouter(route)) return false; route.queryValue.children1 && Object.keys(route.queryValue.children1).length; }; -type QueryBuilderUpdatedConfig = typeof QueryBuilderInitialConfig & { fields: Config["fields"] }; -export function getQueryBuilderConfig(form: RoutingForm, forReporting = false) { - const fields: Record< - string, - { - label: string; - type: string; - valueSources: ["value"]; - fieldSettings: { - listValues?: { - value: string; - title: string; - }[]; - }; - } - > = {}; - form.fields?.forEach((field) => { - if ("routerField" in field) { - field = field.routerField; - } - if (FieldTypes.map((f) => f.value).includes(field.type)) { - const optionValues = field.selectText?.trim().split("\n"); - const options = optionValues?.map((value) => { - const title = value; - return { - value, - title, - }; - }); - - const widget = InitialConfig.widgets[field.type]; - const widgetType = widget.type; - - fields[field.id] = { - label: field.label, - type: widgetType, - valueSources: ["value"], - fieldSettings: { - listValues: options, - }, - // preferWidgets: field.type === "textarea" ? ["textarea"] : [], - }; - } else { - throw new Error("Unsupported field type:" + field.type); - } - }); - - const initialConfigCopy = { ...InitialConfig, operators: { ...InitialConfig.operators } }; - if (forReporting) { - delete initialConfigCopy.operators.is_empty; - delete initialConfigCopy.operators.is_not_empty; - initialConfigCopy.operators.__calReporting = true; - } - // You need to provide your own config. See below 'Config format' - const config: QueryBuilderUpdatedConfig = { - ...initialConfigCopy, - fields: fields, - }; - return config; -} - const getEmptyRoute = (): Exclude => { const uuid = QbUtils.uuid(); return { @@ -122,21 +57,6 @@ type Route = }) | GlobalRoute; -export const RoutingPages: { label: string; value: NonNullable["type"] }[] = [ - { - label: "Custom Page", - value: "customPageMessage", - }, - { - label: "External Redirect", - value: "externalRedirectUrl", - }, - { - label: "Event Redirect", - value: "eventTypeRedirectUrl", - }, -]; - const Route = ({ route, routes, diff --git a/packages/app-store/ee/routing-forms/playwright/tests/basic.e2e.ts b/packages/app-store/ee/routing-forms/playwright/tests/basic.e2e.ts index e808ad6f3c..baa97a29da 100644 --- a/packages/app-store/ee/routing-forms/playwright/tests/basic.e2e.ts +++ b/packages/app-store/ee/routing-forms/playwright/tests/basic.e2e.ts @@ -92,6 +92,9 @@ test.describe("Routing Forms", () => { // Expect F1 fields to be available in F2 await page.goto(`/apps/routing-forms/form-edit/${form2Id}`); + //FIXME: Figure out why this delay is required. Without it field count comes out to be 1 only + await new Promise((resolve) => setTimeout(resolve, 1000)); + expect(await page.locator('[data-testid="field"]').count()).toBe(2); await expectCurrentFormToHaveFields(page, { 1: { label: "F1 Field1", typeIndex: 1 } }, types); // Add 1 more field in F1 @@ -103,6 +106,8 @@ test.describe("Routing Forms", () => { }); await page.goto(`/apps/routing-forms/form-edit/${form2Id}`); + //FIXME: Figure out why this delay is required. Without it field count comes out to be 1 only + await new Promise((resolve) => setTimeout(resolve, 1000)); expect(await page.locator('[data-testid="field"]').count()).toBe(3); await expectCurrentFormToHaveFields(page, { 2: { label: "F1 Field2", typeIndex: 1 } }, types); }); diff --git a/packages/app-store/ee/routing-forms/types/types.d.ts b/packages/app-store/ee/routing-forms/types/types.d.ts index 6b7736b4a9..76e778b291 100644 --- a/packages/app-store/ee/routing-forms/types/types.d.ts +++ b/packages/app-store/ee/routing-forms/types/types.d.ts @@ -3,8 +3,13 @@ import z from "zod"; import { RoutingFormSettings } from "@calcom/prisma/zod-utils"; +import QueryBuilderInitialConfig from "../components/react-awesome-query-builder/config/config"; import { zodRouterRouteView, zodNonRouterRoute, zodFieldsView, zodRoutesView } from "../zod"; +export type RoutingForm = SerializableForm; + +export type QueryBuilderUpdatedConfig = typeof QueryBuilderInitialConfig & { fields: Config["fields"] }; + export type Response = Record< // Field ID string,