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

feat: allow options to assign many flags #3820

Merged
merged 20 commits into from
Nov 1, 2024
Merged

Conversation

jessicamcinchak
Copy link
Member

@jessicamcinchak jessicamcinchak commented Oct 16, 2024

Following on from #3764

Gist:

  • Question & Checklist "Options" currently support a single data field val and single flag each - as we think about the future of automations & filtering, there's actually no reason a single Option shouldn't be able to support multiple flags
    • For example, answering "No" might flag up "Planning permission needed" AND "Listed building consent required" and it's ideal for editors to be able to set both in a single go (each Options still supports a single data field val)
  • Editors should ideally only select up to one flag per category
    • In a followup PR we could build nice validation error handling for this
    • But the cool thing is that it's actually harmless if they select more than one in the same category, it will simply be treated like flags within the same category are always/already treated - only the left-most, highest order flag matters!

Changes:

  • Update Option node data type flag from string to Array<Flag["value"]>
  • Implement MultipleSelect with grouping by category for editor "Option" modals within Questions & Checklists
  • Update graph node styling with color band per flag
  • Update preview store methods & associated tests
    • autoAnswerableFlag for Filter node navigation
    • getResultData/resultData for Result node navigation and analytics tracking
    • collectedFlags (no component or navigation dependencies)
  • Expose collectedFlags in editor Console side panel

Open question:
** All of the above work with existing Option's flag (string) and new/updated Option's flag (string[]) without requiring a content migration of existing flow content (or all existing mock test data!).

  • But are we happy with slightly semantically incorrect type "flag" for a list of values?
  • Do we still want to consider a content migration script across ALL flow's to rename flag to flags and format legacy values as nice-to-have? (Might be good practice for clones?)

Copy link

github-actions bot commented Oct 17, 2024

Removed vultr server and associated DNS entries

@jessicamcinchak
Copy link
Member Author

@@ -382,7 +374,74 @@ export const previewStore: StateCreator<

resultData(flagSet, overrides) {
const { breadcrumbs, flow } = get();
return getResultData(breadcrumbs, flow, flagSet, overrides);
Copy link
Member Author

@jessicamcinchak jessicamcinchak Oct 30, 2024

Choose a reason for hiding this comment

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

Decided to simplify this into a single function rather than this previous pattern of resultData calling getResultData - cleaning up another reduce along the way 🧹 (we aren't even dealing with an array of anything in this method because a single flagset category is passed in, so reduce was especially irrelevant in this case!! 🙈)

@@ -885,3 +871,43 @@ export const removeNodesDependentOnPassport = (
}, [] as string[]);
return { removedNodeIds, breadcrumbsWithoutPassportData: newBreadcrumbs };
};

const collectedFlagValuesByCategory = (
Copy link
Member Author

Choose a reason for hiding this comment

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

No logical change here - this has simply been extracted out of autoAnswerableFlag so we can re-use it in collectedFlags and resultData too

@jessicamcinchak jessicamcinchak marked this pull request as ready for review October 31, 2024 15:33
@jessicamcinchak jessicamcinchak requested a review from a team October 31, 2024 15:33
@jessicamcinchak
Copy link
Member Author

Logic followups

count: gr.children.length,
})),
}
categories: groupedOptions.map((gr) => ({
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: what do you think about changing these acronyms (gr) to something a bit more meaningful?

I just find it slightly increases the mental load unnecessarily to read something that isn't immediately obvious (like I understood it was a group on second reading, but I think making code as frictionless and easy to read as possible is something to strive for!)

Copy link
Contributor

Choose a reason for hiding this comment

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

Yep, agree here 👍

Another nice pattern can be to just destructure the keys we need.

I appreciate though that this is just a linting change and not something you've specifically touched so it can come as a follow up if needed.

Suggested change
categories: groupedOptions.map((gr) => ({
categories: groupedOptions.map(({ title, children }) => ({

Copy link
Member Author

Choose a reason for hiding this comment

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

Fully agree - updated now !

@@ -36,7 +46,7 @@ const Option: React.FC<any> = (props) => {
imageAltText={props.data.text}
/>
)}
<div className="band" style={{ background, color }}></div>
{flags ? flags.map((flag) => <FlagBand key={`${props.id}-${flag.value}`} flag={flag} />) : <NoFlagBand />}
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice clear component names 😍

Copy link
Contributor

@jamdelion jamdelion left a comment

Choose a reason for hiding this comment

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

I know Daf said he would review this too but I felt nosy 😆 Looks good to me but obviously feel free to wait for his review as he probably has more context!

Copy link
Contributor

@DafyddLlyr DafyddLlyr left a comment

Choose a reason for hiding this comment

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

Working as expected, nice and clear to follow 👍

A few minor suggestions, no showstoppers.

On the whole flag/flags thing I think we can live with this as is for now (flag pointing to an array) but it would be nice to handle a migration here for sure - it's nice simple use case as well.

One other comment would be on the validation - I think it's worth making the effort to enforce one per category just from the point of view of explaining this to users. I think it could be as simple as disabling the renderOption if flag from the same category is already in the group. We could also handle this in the validation schema. I'm aware that August flagged this as a blocker so happy for this to be a follow up here for sure 👍

count: gr.children.length,
})),
}
categories: groupedOptions.map((gr) => ({
Copy link
Contributor

Choose a reason for hiding this comment

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

Yep, agree here 👍

Another nice pattern can be to just destructure the keys we need.

I appreciate though that this is just a linting change and not something you've specifically touched so it can come as a follow up if needed.

Suggested change
categories: groupedOptions.map((gr) => ({
categories: groupedOptions.map(({ title, children }) => ({

import { CustomCheckbox, SelectMultiple } from "ui/shared/SelectMultiple";

interface Props {
value?: Array<Flag["value"]>;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this actually an optional prop?

Copy link
Member Author

Choose a reason for hiding this comment

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

Annoying yes because Flag["value"] is optional, despite it always be populated/required in practice. Will try to do a more thorough revisit of types as a followup 👍

const value: Flag[] | undefined = useMemo(
() =>
initialFlagValues?.flatMap((initialFlagValue) => flatFlags.filter((flag) => flag.value === initialFlagValue)),
[initialFlagValues, flatFlags],
Copy link
Contributor

Choose a reason for hiding this comment

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

I think that flatFlags is a totally static value which won't change, so it can be removed from here.

@@ -28,7 +28,7 @@ export interface Option {
id: string;
data: {
description?: string;
flag?: string;
flag?: Array<Flag["value"]>;
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Maybe a FlagValues type could be nice?

@jessicamcinchak jessicamcinchak merged commit 8c5f127 into main Nov 1, 2024
11 of 12 checks passed
@jessicamcinchak jessicamcinchak deleted the jess/select-many-flags branch November 1, 2024 11:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants