From fc592a17ac6df2a7b486935b6eaae8164df228b7 Mon Sep 17 00:00:00 2001 From: Rajesh Jonnalagadda <38752904+rajeshj11@users.noreply.github.com> Date: Sun, 18 Aug 2024 23:04:59 +0530 Subject: [PATCH] feat: Add 'Disable on Prefill' option for fields (#15715) * feat:add prefilcheck check box in the field creation * chore:formatted the li8 translations * feat:add e2e test case for this feature * refactor:reanmed the key from prefilledCheck to disableOnPrefill and moved the readOnly logic to formbuilderField * chore:fixed the type check * fix:if prefilled has error. do not disabble it * chore:removed the e2e tests for prefill in e2e * chore:revert back to old changes * chore:remove unwanted code frome2e * Support name field as well * refactor:handled the feedback changes. still unit test cases pending * feat: add unit tests for disable field on prefill logic * chore: resolved the type check and typos * chore:renamed the files and type clean up * Apply suggestions from code review * chore:renaming test files and moving the disableonprefill hook to formbuilderFeild. * refactor: add unit test for serach params and form values check and minor code formatting and file name changes * fix: validating the input value partially at the intialising phase. so that form response values and from ui values are same and making sure invalid data not sent to backend on submit. * chore:fix the type check issue * fix: field value valdiation at initilisation and add the unit test cases for that change * chore:add comments to the tests * chore:resolve type check * chore: moved the tests to the formBuilder * Simplify codebase by ensuring select and multiselect changes the value when no option is found * Relocate and refactor tests * handle special cases * Dont disable for dirty fields * Update schema.prisma --------- Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com> Co-authored-by: Hariom --- .../inputs/update-event-type.input.ts | 4 + .../outputs/get-event-type-public.output.ts | 4 + apps/web/public/static/locales/en/common.json | 1 + .../components/FormInputFields.tsx | 2 +- .../widgets.test.tsx | 103 ++++ .../react-awesome-query-builder/widgets.tsx | 17 +- .../embed-core/playwright/lib/testUtils.ts | 2 +- packages/features/form-builder/Components.tsx | 4 +- .../features/form-builder/FormBuilder.tsx | 10 + .../form-builder/FormBuilderField.tsx | 9 +- .../lib/formbuilder/test/FormBuilder.test.tsx | 77 +++ .../lib/formbuilder/test/testUtils.ts | 184 ++++++ packages/features/form-builder/schema.ts | 5 +- .../useShouldBeDisabledDueToPrefill.test.ts | 554 ++++++++++++++++++ .../useShouldBeDisabledDueToPrefill.tsx | 118 ++++ packages/prisma/schema.prisma | 2 +- vitest.workspace.ts | 13 +- 17 files changed, 1098 insertions(+), 11 deletions(-) create mode 100644 packages/app-store/routing-forms/components/react-awesome-query-builder/widgets.test.tsx create mode 100644 packages/features/form-builder/lib/formbuilder/test/FormBuilder.test.tsx create mode 100644 packages/features/form-builder/lib/formbuilder/test/testUtils.ts create mode 100644 packages/features/form-builder/useShouldBeDisabledDueToPrefill.test.ts create mode 100644 packages/features/form-builder/useShouldBeDisabledDueToPrefill.tsx diff --git a/apps/api/v2/src/ee/event-types/event-types_2024_04_15/inputs/update-event-type.input.ts b/apps/api/v2/src/ee/event-types/event-types_2024_04_15/inputs/update-event-type.input.ts index cf9c3c901c..1f1f5c50a7 100644 --- a/apps/api/v2/src/ee/event-types/event-types_2024_04_15/inputs/update-event-type.input.ts +++ b/apps/api/v2/src/ee/event-types/event-types_2024_04_15/inputs/update-event-type.input.ts @@ -177,6 +177,10 @@ export class BookingField_2024_04_15 { @ValidateNested({ each: true }) @Type(() => Source) sources?: Source[]; + + @IsOptional() + @IsBoolean() + disableOnPrefill?: boolean; } export class RecurringEvent_2024_04_15 { diff --git a/apps/api/v2/src/ee/event-types/event-types_2024_04_15/outputs/get-event-type-public.output.ts b/apps/api/v2/src/ee/event-types/event-types_2024_04_15/outputs/get-event-type-public.output.ts index 887e1f6bef..7d71d04965 100644 --- a/apps/api/v2/src/ee/event-types/event-types_2024_04_15/outputs/get-event-type-public.output.ts +++ b/apps/api/v2/src/ee/event-types/event-types_2024_04_15/outputs/get-event-type-public.output.ts @@ -87,6 +87,10 @@ class BookingField { @Type(() => Source) @IsOptional() sources?: Source[]; + + @IsOptional() + @IsBoolean() + disableOnPrefill?: boolean; } class Organization { diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index b04c5ea5ea..b30e514580 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -2561,5 +2561,6 @@ "number_of_options":"{{count}} options", "reschedule_with_same_round_robin_host_title": "Reschedule with same Round-Robin host", "reschedule_with_same_round_robin_host_description": "Rescheduled events will be assigned to the same host as initially scheduled", + "disable_input_if_prefilled": "Disable input if the URL identifier is prefilled", "ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑" } diff --git a/packages/app-store/routing-forms/components/FormInputFields.tsx b/packages/app-store/routing-forms/components/FormInputFields.tsx index 4a15f6550f..ad87020c33 100644 --- a/packages/app-store/routing-forms/components/FormInputFields.tsx +++ b/packages/app-store/routing-forms/components/FormInputFields.tsx @@ -47,7 +47,7 @@ export default function FormInputFields(props: Props) { ({ + __esModule: true, + default: () => { + return function MockSelect({ + options, + onChange, + value, + isMulti, + }: { + options: { value: string; label: string }[]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + onChange: (value: any) => void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + value: any; + isMulti: boolean; + }) { + return ( + + ); + }; + }, +})); + +describe("Select Widgets", () => { + describe("SelectWidget", () => { + const listValues = [ + { title: "Option 1", value: "1" }, + { title: "Option 2", value: "2" }, + { title: "Option 3", value: "3" }, + ]; + + it("should handle render the value in option correctly", () => { + const setValue = vi.fn(); + render(); + + const select = screen.getByTestId("mock-select"); + expect(select).toBeInTheDocument(); + expect(screen.getAllByRole("option")).toHaveLength(3); + expect(setValue).not.toHaveBeenCalled(); + }); + + it("should handle a value that is not in the list and reset the value to empty string", () => { + const setValue = vi.fn(); + render(); + + const select = screen.getByTestId("mock-select"); + expect(select).toBeInTheDocument(); + expect(screen.getAllByRole("option")).toHaveLength(3); + expect(setValue).toHaveBeenCalledWith(""); + }); + }); + + describe("MultiSelectWidget", () => { + const listValues = [ + { title: "Option 1", value: "1" }, + { title: "Option 2", value: "2" }, + { title: "Option 3", value: "3" }, + ]; + + it("renders options correctly", () => { + const setValue = vi.fn(); + + render(); + + const select = screen.getByTestId("mock-select"); + expect(select).toBeInTheDocument(); + expect(screen.getAllByRole("option")).toHaveLength(3); + expect(setValue).not.toHaveBeenCalled(); + }); + + it("sets value to empty array when no options match", () => { + const setValue = vi.fn(); + render(); + + expect(setValue).toHaveBeenCalledWith([]); + }); + }); +}); diff --git a/packages/app-store/routing-forms/components/react-awesome-query-builder/widgets.tsx b/packages/app-store/routing-forms/components/react-awesome-query-builder/widgets.tsx index 5065df70c7..0d7220b3b2 100644 --- a/packages/app-store/routing-forms/components/react-awesome-query-builder/widgets.tsx +++ b/packages/app-store/routing-forms/components/react-awesome-query-builder/widgets.tsx @@ -150,8 +150,6 @@ const MultiSelectWidget = ({ value, ...remainingProps }: SelectLikeComponentPropsRAQB) => { - //TODO: Use Select here. - //TODO: Let's set listValue itself as label and value instead of using title. if (!listValues) { return null; } @@ -164,6 +162,14 @@ const MultiSelectWidget = ({ const optionsFromList = selectItems.filter((item) => value?.includes(item.value)); + // If no value could be found in the list, then we set the value to undefined. + // This is to update the value back to the source that we couldn't set it. This is important otherwise the outside party thinks that the value is set but it is not. + // Do it only when it is not already empty, this is to avoid infinite state updates + // NOTE: value is some times sent as undefined even though the type will tell you that it can't be + if (optionsFromList.length === 0 && value?.length) { + setValue([]); + } + return ( = { props.setValue(val); }} {...props} + disabled={props.readOnly} /> ); }, @@ -364,9 +365,10 @@ export const Components: Record = { }, radio: { propsType: propsTypes.radio, - factory: ({ setValue, name, value, options }) => { + factory: ({ setValue, name, value, options, readOnly }) => { return ( { setValue(e); diff --git a/packages/features/form-builder/FormBuilder.tsx b/packages/features/form-builder/FormBuilder.tsx index 8031cb107f..eb138b7f65 100644 --- a/packages/features/form-builder/FormBuilder.tsx +++ b/packages/features/form-builder/FormBuilder.tsx @@ -11,6 +11,7 @@ import { Badge, BooleanToggleGroupField, Button, + CheckboxField, Dialog, DialogClose, DialogContent, @@ -491,6 +492,10 @@ function FieldEditDialog({ } label={t("identifier")} /> + + +