Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: refactor public component wrappers for auto-answering into preview Node #4108

Merged
merged 3 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions editor.planx.uk/src/@planx/components/Filter/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
ComponentType as TYPES,
DEFAULT_FLAG_CATEGORY,
flatFlags,
Node,
} from "@opensystemslab/planx-core/types";
import { useFormik } from "formik";
import React from "react";
Expand All @@ -15,6 +16,7 @@ export interface Props {
id?: string;
handleSubmit?: (data: any, children?: any) => void;
node?: any;
autoAnswer?: Node["id"];
}

const Filter: React.FC<Props> = (props) => {
Expand Down
10 changes: 2 additions & 8 deletions editor.planx.uk/src/@planx/components/Filter/Public.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useStore } from "pages/FlowEditor/lib/store";
import { useEffect } from "react";

import { PublicProps } from "../shared/types";
Expand All @@ -8,17 +7,12 @@ export type Props = PublicProps<Filter>;

// Filters are always auto-answered and never seen by a user, but should still leave a breadcrumb
export default function Component(props: Props) {
const autoAnswerableFlag = useStore((state) => state.autoAnswerableFlag);

let idThatCanBeAutoAnswered: string | undefined;
if (props.id) idThatCanBeAutoAnswered = autoAnswerableFlag(props.id);

Comment on lines -11 to -14
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! 💪

useEffect(() => {
props.handleSubmit?.({
answers: [idThatCanBeAutoAnswered],
answers: [props.autoAnswer],
auto: true,
});
}, []);
}, [props.autoAnswer]);

return null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import FormLabel from "@mui/material/FormLabel";
import Grid from "@mui/material/Grid";
import RadioGroup from "@mui/material/RadioGroup";
import { visuallyHidden } from "@mui/utils";
import { Edges } from "@opensystemslab/planx-core/types";
import Card from "@planx/components/shared/Preview/Card";
import { CardHeader } from "@planx/components/shared/Preview/CardHeader/CardHeader";
import BasicRadio from "@planx/components/shared/Radio/BasicRadio";
Expand All @@ -17,7 +16,7 @@ import FullWidthWrapper from "ui/public/FullWidthWrapper";
import ErrorWrapper from "ui/shared/ErrorWrapper";
import { mixed, object, string } from "yup";

import { Question } from "../model";
import { Question } from "./model";

export enum QuestionLayout {
Basic,
Expand All @@ -26,48 +25,11 @@ export enum QuestionLayout {
}

const QuestionComponent: React.FC<Question> = (props) => {
const [flow, autoAnswerableOptions] = useStore((state) => [
state.flow,
state.autoAnswerableOptions,
]);
// Questions without edges act like "sticky notes" in the graph for editors only & are auto-answered
const flow = useStore().flow;
const edges = props.id ? flow[props.id]?.edges : undefined;
const isStickyNote = !edges || edges.length === 0;

if (props.neverAutoAnswer) {
return <VisibleQuestion {...props} />;
}

// Questions without edges act like "sticky notes" in the graph for editors only & can be immediately auto-answered
let edges: Edges | undefined;
if (props.id) edges = flow[props.id]?.edges;
if (!edges || edges.length === 0) {
return <AutoAnsweredQuestion {...props} answerIds={undefined} />;
}

let idsThatCanBeAutoAnswered: string[] | undefined;
if (props.id) idsThatCanBeAutoAnswered = autoAnswerableOptions(props.id);
if (idsThatCanBeAutoAnswered) {
return (
<AutoAnsweredQuestion {...props} answerIds={idsThatCanBeAutoAnswered} />
);
}

return <VisibleQuestion {...props} />;
};

// An auto-answered Question won't be seen by the user, but still leaves a breadcrumb
const AutoAnsweredQuestion: React.FC<
Question & { answerIds: string[] | undefined }
> = (props) => {
useEffect(() => {
props.handleSubmit?.({
answers: props.answerIds,
auto: true,
});
}, []);

return null;
};

const VisibleQuestion: React.FC<Question> = (props) => {
const previousResponseId = props?.previouslySubmittedData?.answers?.[0];
const previousResponseKey = props.responses.find(
(response) => response.id === previousResponseId,
Expand Down Expand Up @@ -104,6 +66,20 @@ const VisibleQuestion: React.FC<Question> = (props) => {
layout = QuestionLayout.Descriptions;
}

useEffect(() => {
if (isStickyNote || props.autoAnswers) {
props.handleSubmit?.({
answers: props.autoAnswers,
auto: true,
});
}
}, [isStickyNote, props.autoAnswers]);

// Auto-answered questions are not publicly visible
if (isStickyNote || props.autoAnswers) {
return null;
}

return (
<Card handleSubmit={formik.handleSubmit}>
<CardHeader
Expand Down

This file was deleted.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for spotting this - this looks like just a weird artifact of somebody calling pnpm i in this folder that we've missed at some point?

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Meta, StoryObj } from "@storybook/react";

import Question from "./Question";
import Question from "./Public";

const meta = {
title: "PlanX Components/Question",
Expand Down
1 change: 1 addition & 0 deletions editor.planx.uk/src/@planx/components/Question/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ export interface Question extends BaseNodeData {
}[];
previouslySubmittedData?: Store.UserData;
handleSubmit: HandleSubmit;
autoAnswers?: string[] | undefined;
}
97 changes: 58 additions & 39 deletions editor.planx.uk/src/pages/Preview/Node.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,18 @@ interface Props {
}

const Node: React.FC<Props> = (props) => {
const [childNodesOf, resetPreview, cachedBreadcrumbs] = useStore((state) => [
const [
childNodesOf,
resetPreview,
cachedBreadcrumbs,
autoAnswerableFlag,
autoAnswerableOptions,
] = useStore((state) => [
state.childNodesOf,
state.resetPreview,
state.cachedBreadcrumbs,
state.autoAnswerableFlag,
state.autoAnswerableOptions,
]);

const handleSubmit = props.handleSubmit;
Expand All @@ -96,13 +104,16 @@ const Node: React.FC<Props> = (props) => {
});

switch (props.node.type) {
case TYPES.AddressInput:
return <AddressInputComponent {...getComponentProps<AddressInput>()} />;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is a bit extra noisy because I've alphabetized our component types within the switch block - only Checklist, Filter, Question types have functional changes!

case TYPES.Calculate:
return <CalculateComponent {...getComponentProps<Calculate>()} />;

case TYPES.Checklist: {
const checklistProps = getComponentProps<Checklist>();
const childNodes = childNodesOf(
props.node.id,
nodeId,
) as (typeof checklistProps)["options"];

return (
Expand Down Expand Up @@ -135,6 +146,9 @@ const Node: React.FC<Props> = (props) => {
case TYPES.Confirmation:
return <ConfirmationComponent {...getComponentProps<Confirmation>()} />;

case TYPES.ContactInput:
return <ContactInputComponent {...getComponentProps<ContactInput>()} />;

case TYPES.Content:
return <ContentComponent {...getComponentProps<Content>()} />;

Expand All @@ -143,8 +157,10 @@ const Node: React.FC<Props> = (props) => {

case TYPES.DrawBoundary:
return <DrawBoundaryComponent {...getComponentProps<DrawBoundary>()} />;

case TYPES.Feedback:
return <FeedbackComponent {...getComponentProps<Feedback>()} />;

case TYPES.FileUpload:
return <FileUploadComponent {...getComponentProps<FileUpload>()} />;

Expand All @@ -155,6 +171,13 @@ const Node: React.FC<Props> = (props) => {
/>
);

case TYPES.Filter: {
const filterProps = getComponentProps<Filter>();
const autoAnswer = nodeId ? autoAnswerableFlag(nodeId) : undefined;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: We could calculate this at the top of the file and add it to both getComponentProps() and PublicProps<T>.

Slightly cleaner / more consistent, and we could put a check for component type within autoAnswerableFlag() if it's an expensive / slow function.


return <FilterComponent {...filterProps} autoAnswer={autoAnswer} />;
}

case TYPES.FindProperty:
return <FindPropertyComponent {...getComponentProps<FindProperty>()} />;

Expand All @@ -179,6 +202,38 @@ const Node: React.FC<Props> = (props) => {
case TYPES.Pay:
return <PayComponent {...getComponentProps<Pay>()} />;

case TYPES.PlanningConstraints:
return (
<PlanningConstraintsComponent
{...getComponentProps<PlanningConstraints>()}
/>
);

case TYPES.PropertyInformation:
return (
<PropertyInformationComponent
{...getComponentProps<PropertyInformation>()}
/>
);

case TYPES.Question: {
const questionProps = getComponentProps<Question>();
const autoAnswers = nodeId ? autoAnswerableOptions(nodeId) : undefined;

return (
<QuestionComponent
{...questionProps}
responses={childNodesOf(nodeId).map((n, i) => ({
id: n.id,
responseKey: i + 1,
title: n.data?.text,
...n.data,
}))}
autoAnswers={autoAnswers}
/>
);
}

case TYPES.Result:
return <ResultComponent {...getComponentProps<Result>()} />;

Expand All @@ -194,19 +249,6 @@ const Node: React.FC<Props> = (props) => {
case TYPES.SetValue:
return <SetValueComponent {...getComponentProps<SetValue>()} />;

case TYPES.Question:
return (
<QuestionComponent
{...getComponentProps<Question>()}
responses={childNodesOf(props.node.id).map((n, i) => ({
id: n.id,
responseKey: i + 1,
title: n.data?.text,
...n.data,
}))}
/>
);

case TYPES.TaskList: {
const taskListProps = getComponentProps<TaskList>();

Expand All @@ -221,34 +263,11 @@ const Node: React.FC<Props> = (props) => {
case TYPES.TextInput:
return <TextInputComponent {...getComponentProps<TextInput>()} />;

case TYPES.AddressInput:
return <AddressInputComponent {...getComponentProps<AddressInput>()} />;

case TYPES.ContactInput:
return <ContactInputComponent {...getComponentProps<ContactInput>()} />;

case TYPES.PlanningConstraints:
return (
<PlanningConstraintsComponent
{...getComponentProps<PlanningConstraints>()}
/>
);

case TYPES.PropertyInformation:
return (
<PropertyInformationComponent
{...getComponentProps<PropertyInformation>()}
/>
);

case TYPES.Filter:
return <FilterComponent {...getComponentProps<Filter>()} />;

// These types are never seen by users, nor do they leave their own breadcrumbs entry
case TYPES.Answer:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always appreciate seeing some alphabetising!

case TYPES.ExternalPortal:
case TYPES.Flow:
case TYPES.InternalPortal:
case TYPES.Answer:
case undefined:
return null;

Expand Down
Loading