Skip to content

Commit

Permalink
feat: autocomplete data fields in the editor (#4062)
Browse files Browse the repository at this point in the history
Co-authored-by: Dafydd Llŷr Pearson <[email protected]>
  • Loading branch information
jessicamcinchak and DafyddLlyr authored Jan 3, 2025
1 parent 0d5e368 commit 4392053
Show file tree
Hide file tree
Showing 27 changed files with 733 additions and 189 deletions.
4 changes: 4 additions & 0 deletions e2e/tests/ui-driven/src/create-flow-with-geospatial.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ test.describe("Flow creation, publish and preview", () => {
});

test("Create a flow", async ({ browser }) => {
test.setTimeout(60_000);

const page = await getTeamPage({
browser,
userId: context.user!.id!,
Expand Down Expand Up @@ -107,6 +109,8 @@ test.describe("Flow creation, publish and preview", () => {
test("Publish and preview flow with geospatial components", async ({
browser,
}) => {
test.setTimeout(60_000);

const page = await createAuthenticatedSession({
browser,
userId: context.user!.id!,
Expand Down
12 changes: 10 additions & 2 deletions e2e/tests/ui-driven/src/create-flow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ test.describe("Flow creation, publish and preview", () => {
});

test("Create a flow", async ({ browser }) => {
test.setTimeout(60_000);

const page = await getTeamPage({
browser,
userId: context.user!.id!,
Expand Down Expand Up @@ -231,7 +233,7 @@ test.describe("Flow creation, publish and preview", () => {
await publishService(page);
});

test("Can preview a published flow", async ({
test("Can preview a published flow with an external portal", async ({
browser,
}: {
browser: Browser;
Expand All @@ -241,12 +243,17 @@ test.describe("Flow creation, publish and preview", () => {
userId: context.user!.id!,
});

await page.goto(`/${context.team.slug}/${serviceProps.slug}`);
await navigateToService(page, serviceProps.slug);

await expect(
page.getByRole("link", { name: "E2E/an-external-portal-service" }),
).toBeVisible();

const previewLink = page.getByRole("link", {
name: "Open published service",
});
await expect(previewLink).toBeVisible();

await page.goto(
`/${context.team.slug}/${serviceProps.slug}/published?analytics=false`,
);
Expand All @@ -273,6 +280,7 @@ test.describe("Flow creation, publish and preview", () => {
});
await clickContinue({ page });

// The external portal question has been flattened into the overall flow data structure and can be successfully navigated through
await answerQuestion({
page,
title: externalPortalFlowData.title,
Expand Down
43 changes: 33 additions & 10 deletions e2e/tests/ui-driven/src/helpers/addComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,19 @@ const createBaseComponent = async (
break;
case ComponentType.AddressInput:
await page.getByPlaceholder("Title").fill(title || "");
await page.getByPlaceholder("Data Field").fill(options?.[0] || "");
await page.getByRole("combobox", { name: "Data field" }).click();
await page
.getByRole("combobox", { name: "Data field" })
.fill(options?.[0] || "proposal.address");
await page.getByRole("combobox", { name: "Data field" }).press("Enter");
break;
case ComponentType.ContactInput:
await page.getByPlaceholder("Title").fill(title || "");
await page.getByPlaceholder("Data Field").fill(options?.[0] || "");
await page.getByRole("combobox", { name: "Data field" }).click();
await page
.getByRole("combobox", { name: "Data field" })
.fill(options?.[0] || "proposal.contact");
await page.getByRole("combobox", { name: "Data field" }).press("Enter");
break;
case ComponentType.TaskList:
await page.getByPlaceholder("Main Title").fill(title || "");
Expand Down Expand Up @@ -112,15 +120,27 @@ const createBaseComponent = async (
}
break;
case ComponentType.FileUpload:
await page.getByPlaceholder("Data Field").fill(options?.[0] || "");
await page.getByRole("combobox", { name: "Data field" }).click();
await page
.getByRole("combobox", { name: "Data field" })
.fill(options?.[0] || "otherDocument");
await page.getByRole("combobox", { name: "Data field" }).press("Enter");
break;
case ComponentType.FileUploadAndLabel:
await page.getByPlaceholder("File type").fill(options?.[0] || "");
await page.getByPlaceholder("Data Field").fill(options?.[1] || "");
await page.getByRole("combobox", { name: "Data field" }).click();
await page
.getByRole("combobox", { name: "Data field" })
.fill(options?.[1] || "otherDocument");
await page.getByRole("combobox", { name: "Data field" }).press("Enter");
break;
case ComponentType.List:
await page.getByPlaceholder("Title").fill(title || "");
await page.getByPlaceholder("Data Field").fill(options?.[0] || "");
await page.getByRole("combobox", { name: "Data field" }).click();
await page
.getByRole("combobox", { name: "Data field" })
.fill(options?.[0] || "proposal.list");
await page.getByRole("combobox", { name: "Data field" }).press("Enter");
break;
case ComponentType.Content:
await page
Expand Down Expand Up @@ -176,7 +196,9 @@ export const createQuestionWithDataFieldOptions = async (
await locatingNode.click();
await page.getByRole("dialog").waitFor();
await page.getByPlaceholder("Text").fill(questionText);
await page.getByPlaceholder("Data Field").fill(dataField);
await page.getByRole("combobox", { name: "Data field" }).click();
await page.getByRole("combobox", { name: "Data field" }).fill(dataField);
await page.getByRole("combobox", { name: "Data field" }).press("Enter");
await createComponentOptionsWithDataValues(page, options);
await page.locator('button[form="modal"][type="submit"]').click();
};
Expand Down Expand Up @@ -377,12 +399,13 @@ async function createComponentOptionsWithDataValues(
page: Page,
options: OptionWithDataValues[],
) {
let index = 0;
for (const option of options) {
await page.locator("button").filter({ hasText: "add new" }).click();
await page.getByPlaceholder("Option").nth(index).fill(option.optionText);
await page.getByPlaceholder("Data Value").nth(index).fill(option.dataValue);
index++;
await page.getByPlaceholder("Option").last().fill(option.optionText);
await page.getByRole("combobox", { name: "Data field" }).last().click();
await page
.getByRole("option", { name: option.dataValue, exact: true })
.click();
}
}

Expand Down
4 changes: 2 additions & 2 deletions e2e/tests/ui-driven/src/helpers/userActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,14 +328,14 @@ export async function answerListInput(
await page
.getByRole("combobox", { name: "What best describes this unit?" })
.click();
await page.getByRole("option", { name: unitType }).click();
await page.getByRole("option", { name: unitType, exact: true }).click();

await page
.getByRole("combobox", {
name: "What best describes the tenure of this unit?",
})
.click();
await page.getByRole("option", { name: tenure }).click();
await page.getByRole("option", { name: tenure, exact: true }).click();

await page
.getByLabel("How many bedrooms does this unit have?")
Expand Down
19 changes: 8 additions & 11 deletions editor.planx.uk/src/@planx/components/AddressInput/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import RichTextInput from "ui/editor/RichTextInput/RichTextInput";
import Input from "ui/shared/Input/Input";
import InputRow from "ui/shared/InputRow";

import { DataFieldAutocomplete } from "../shared/DataFieldAutocomplete";
import { ICONS } from "../shared/icons";
import { AddressInput, parseAddressInput } from "./model";

Expand All @@ -25,8 +26,9 @@ export default function AddressInputComponent(props: Props): FCReturn {
});
}
},
validate: () => {},
validate: () => { },
});

return (
<form onSubmit={formik.handleSubmit} id="modal">
<ModalSection>
Expand All @@ -51,16 +53,11 @@ export default function AddressInputComponent(props: Props): FCReturn {
onChange={formik.handleChange}
/>
</InputRow>
<InputRow>
<Input
required
format="data"
name="fn"
value={formik.values.fn}
placeholder="Data Field"
onChange={formik.handleChange}
/>
</InputRow>
<DataFieldAutocomplete
required
value={formik.values.fn}
onChange={(value) => formik.setFieldValue("fn", value)}
/>
</ModalSectionContent>
</ModalSection>
<ModalFooter formik={formik} />
Expand Down
17 changes: 7 additions & 10 deletions editor.planx.uk/src/@planx/components/Calculate/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import Input from "ui/shared/Input/Input";
import InputRow from "ui/shared/InputRow";
import { Switch } from "ui/shared/Switch";

import { DataFieldAutocomplete } from "../shared/DataFieldAutocomplete";
import { ICONS } from "../shared/icons";
import type { Calculate } from "./model";
import { evaluate, getVariables, parseCalculate } from "./model";
Expand All @@ -25,6 +26,7 @@ const ConditionLabel = styled("span")(() => ({
}));

const UNKNOWN = "unknown";

export default function Component(props: Props) {
const formik = useFormik({
initialValues: parseCalculate(props.node?.data),
Expand Down Expand Up @@ -123,16 +125,11 @@ export default function Component(props: Props) {
</InputRow>
</ModalSectionContent>
<ModalSectionContent title="Output">
<InputRow>
<Input
required
placeholder="output data field"
name="output"
format="data"
value={formik.values.output}
onChange={formik.handleChange}
/>
</InputRow>
<DataFieldAutocomplete
required
value={formik.values.output}
onChange={(value) => formik.setFieldValue("output", value)}
/>
<InputRow>
<Switch
checked={formik.values.formatOutputForAutomations}
Expand Down
20 changes: 5 additions & 15 deletions editor.planx.uk/src/@planx/components/Checklist/Editor/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import Input from "ui/shared/Input/Input";
import InputRow from "ui/shared/InputRow";
import { Switch } from "ui/shared/Switch";

import { DataFieldAutocomplete } from "@planx/components/shared/DataFieldAutocomplete";
import { Option, parseBaseNodeData } from "../../shared";
import { ICONS } from "../../shared/icons";
import type { Checklist } from "../model";
Expand Down Expand Up @@ -123,15 +124,13 @@ export const ChecklistEditor: React.FC<ChecklistProps> = (props) => {
inputRef={focusRef}
required
/>

<ImgInput
img={formik.values.img}
onChange={(newUrl) => {
formik.setFieldValue("img", newUrl);
}}
/>
</InputRow>

<InputRow>
<RichTextInput
name="description"
Expand All @@ -140,18 +139,10 @@ export const ChecklistEditor: React.FC<ChecklistProps> = (props) => {
onChange={formik.handleChange}
/>
</InputRow>

<InputRow>
<Input
format="data"
name="fn"
value={formik.values.fn}
placeholder="Data Field"
onChange={formik.handleChange}
error={Boolean(formik.errors?.fn)}
errorMessage={formik.errors?.fn}
/>
</InputRow>
<DataFieldAutocomplete
value={formik.values.fn}
onChange={(value) => formik.setFieldValue("fn", value)}
/>
<InputRow>
<Switch
checked={!!formik.values.groupedOptions}
Expand Down Expand Up @@ -198,7 +189,6 @@ export const ChecklistEditor: React.FC<ChecklistProps> = (props) => {
<Options formik={formik} />
</ErrorWrapper>
</ModalSection>

<ModalFooter formik={formik} />
</form>
);
Expand Down
17 changes: 15 additions & 2 deletions editor.planx.uk/src/@planx/components/Checklist/Editor/Options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import ErrorWrapper from "ui/shared/ErrorWrapper";
import Input from "ui/shared/Input/Input";
import InputRow from "ui/shared/InputRow";

import { getOptionsSchemaByFn } from "@planx/components/shared/utils";
import { useStore } from "pages/FlowEditor/lib/store";
import { Option } from "../../shared";
import type { Group } from "../model";
import ChecklistOptionsEditor from "./OptionsEditor";
Expand All @@ -28,6 +30,10 @@ export const Options: React.FC<{ formik: FormikHookReturn }> = ({ formik }) => {

const exclusiveOrOptionManagerShouldRender =
hasFeatureFlag("EXCLUSIVE_OR") && nonExclusiveOptions.length;

const schema = useStore().getFlowSchema()?.options;
const initialOptions: Option[] | undefined = formik.initialValues.options || formik.initialValues.groupedOptions?.map((group: Group<Option>) => group.children)?.flat();
const initialOptionVals = initialOptions?.map((option) => option.data?.val);

return (
<ModalSectionContent subtitle="Options">
Expand Down Expand Up @@ -115,6 +121,7 @@ export const Options: React.FC<{ formik: FormikHookReturn }> = ({ formik }) => {
groups: formik.values.groupedOptions.map(
(opt: Group<Option>) => opt.title
),
schema: getOptionsSchemaByFn(formik.values.fn, schema, initialOptionVals),
}}
/>
</Box>
Expand Down Expand Up @@ -160,7 +167,10 @@ export const Options: React.FC<{ formik: FormikHookReturn }> = ({ formik }) => {
}) as Option
}
Editor={ChecklistOptionsEditor}
editorExtraProps={{ showValueField: !!formik.values.fn }}
editorExtraProps={{
showValueField: !!formik.values.fn,
schema: getOptionsSchemaByFn(formik.values.fn, schema, initialOptionVals),
}}
/>
)}
{exclusiveOrOptionManagerShouldRender ? (
Expand Down Expand Up @@ -189,7 +199,10 @@ export const Options: React.FC<{ formik: FormikHookReturn }> = ({ formik }) => {
}) as Option
}
Editor={BaseOptionsEditor}
editorExtraProps={{ showValueField: !!formik.values.fn }}
editorExtraProps={{
showValueField: !!formik.values.fn,
schema: getOptionsSchemaByFn(formik.values.fn, schema, initialOptionVals),
}}
/>
</ErrorWrapper>
</Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type ChecklistOptionsEditorProps = BaseOptionsEditorProps & {

const ChecklistOptionsEditor: React.FC<ChecklistOptionsEditorProps> = ({
value,
schema,
onChange,
showValueField = false,
groups,
Expand All @@ -24,6 +25,7 @@ const ChecklistOptionsEditor: React.FC<ChecklistOptionsEditorProps> = ({
return (
<BaseOptionsEditor
value={value}
schema={schema}
onChange={onChange}
showValueField={showValueField}
>
Expand Down
Loading

0 comments on commit 4392053

Please sign in to comment.