From 73f375f6ddee98584d1ca26cb12548c9c504b340 Mon Sep 17 00:00:00 2001 From: Scott Dickerson Date: Fri, 6 Oct 2023 07:43:56 -0400 Subject: [PATCH] :bug: Update archetype model, form and detail drawer to current hub spec (#1438) ### Summary Resolves: #1436 With hub updates to tag handling and api field name changes related to archetypes, adjustments needed to be made to the UI. ### Archetype model - Update `models.ts` to the current state of `TagRef` and `Archetype` in terms of field names and data types. - MSW stub data updated to use api model changes. ### Archetype Table - **Note**: The tags column on the archetype table is currently displaying ALL tags returned with no source differentiation. ### Archetype Add/EditForm - Update `ArchetypeForm` to use `criteria` instead of `criteriaTags` as per the api model changes. - With hub changes to combine the archetype and non-archetype tags into a single field, the form processing now ignored non-archetype fields but still sends them on an update. - Note: This change mirrors the behavior of manual / non-manual tags on applications and the application form. - Stakeholder and stakeholder groups handling is updated to use the `*ToRef()` helper function style introduced in the application form change #1408. ### Archetype Detail Drawer - Since all tags for an archetype are provided in the same field `tags`, filter them into "archetype tags" (i.e. an empty source) and "assessment tags" (i.e. have a non-empty source) to display them separately. - Note: The "archetype tags" are the tags manually attached to the archetype in the new/edit form. --------- Signed-off-by: Scott J Dickerson --- client/src/app/api/models.ts | 7 +- client/src/app/components/Autocomplete.tsx | 1 - .../app/pages/archetypes/archetypes-page.tsx | 4 +- .../components/archetype-detail-drawer.tsx | 33 +++++++-- .../archetype-form/archetype-form.tsx | 71 +++++++++++-------- .../components/archetype-tags-column.tsx | 1 + client/src/mocks/stub-new-work/archetypes.ts | 12 ++-- 7 files changed, 79 insertions(+), 50 deletions(-) diff --git a/client/src/app/api/models.ts b/client/src/app/api/models.ts index ea6ac8c5b6..8d8215a190 100644 --- a/client/src/app/api/models.ts +++ b/client/src/app/api/models.ts @@ -461,6 +461,7 @@ export interface IReadFile { export interface TagRef extends Ref { source?: string; + virtual?: boolean; } export interface MigrationWave { @@ -744,12 +745,12 @@ export interface Archetype { name: string; description: string; comments: string; - criteriaTags: Tag[]; - tags: Tag[]; - assessmentTags?: Tag[]; + tags: TagRef[]; + criteria: TagRef[]; stakeholders?: Ref[]; stakeholderGroups?: Ref[]; applications?: Ref[]; assessments?: Ref[]; + assessed?: boolean; review?: Ref; } diff --git a/client/src/app/components/Autocomplete.tsx b/client/src/app/components/Autocomplete.tsx index 3c45d3c57f..42576429e0 100644 --- a/client/src/app/components/Autocomplete.tsx +++ b/client/src/app/components/Autocomplete.tsx @@ -130,7 +130,6 @@ export const Autocomplete: React.FC = ({ const matchingOption = options.find( (o) => o.toLowerCase() === (hint || newSelectionText).toLowerCase() ); - console.log({ matchingOption, newSelectionText, options }); if (!matchingOption || selections.includes(matchingOption)) { return; } diff --git a/client/src/app/pages/archetypes/archetypes-page.tsx b/client/src/app/pages/archetypes/archetypes-page.tsx index 557a62bf8f..47b3141d1a 100644 --- a/client/src/app/pages/archetypes/archetypes-page.tsx +++ b/client/src/app/pages/archetypes/archetypes-page.tsx @@ -375,8 +375,6 @@ const Archetypes: React.FC = () => { /> - {/* TODO: Add duplicate confirm modal */} - {/* Delete confirm modal */} { } }} /> + + {/* Override existing assessment confirm modal */} = ({ }) => { const { t } = useTranslation(); + const { tagCategories } = useFetchTagCategories(); + const tags = useMemo( + () => tagCategories.flatMap((tc) => tc.tags).filter(Boolean), + [tagCategories] + ); + + const archetypeTags = + archetype?.tags + ?.filter((t) => t?.source ?? "" === "") + .map((ref) => tags.find((tag) => ref.id === tag.id)) + .filter(Boolean) ?? []; + + const assessmentTags = + archetype?.tags + ?.filter((t) => t?.source ?? "" !== "") + .map((ref) => tags.find((tag) => ref.id === tag.id)) + .filter(Boolean) ?? []; + return ( = ({ {t("terms.tagsCriteria")} - {archetype?.criteriaTags?.length ?? 0 > 0 ? ( - + {archetype?.criteria?.length ?? 0 > 0 ? ( + ) : ( )} @@ -74,8 +93,8 @@ const ArchetypeDetailDrawer: React.FC = ({ {t("terms.tagsArchetype")} - {archetype?.tags?.length ?? 0 > 0 ? ( - + {archetypeTags.length > 0 ? ( + ) : ( )} @@ -85,8 +104,8 @@ const ArchetypeDetailDrawer: React.FC = ({ {t("terms.tagsAssessment")} - {archetype?.assessmentTags?.length ?? 0 > 0 ? ( - + {assessmentTags.length > 0 ? ( + ) : ( )} diff --git a/client/src/app/pages/archetypes/components/archetype-form/archetype-form.tsx b/client/src/app/pages/archetypes/components/archetype-form/archetype-form.tsx index cf0c5d2e3d..f4c4e114bc 100644 --- a/client/src/app/pages/archetypes/components/archetype-form/archetype-form.tsx +++ b/client/src/app/pages/archetypes/components/archetype-form/archetype-form.tsx @@ -15,7 +15,6 @@ import { import type { Archetype, New, - Ref, Stakeholder, StakeholderGroup, Tag, @@ -37,6 +36,7 @@ import { useFetchTagCategories } from "@app/queries/tags"; import { useFetchStakeholderGroups } from "@app/queries/stakeholdergoups"; import { useFetchStakeholders } from "@app/queries/stakeholders"; import ItemsSelect from "@app/components/items-select/items-select"; +import { matchItemsToRefs } from "@app/utils/model-utils"; export interface ArchetypeFormValues { name: string; @@ -44,7 +44,7 @@ export interface ArchetypeFormValues { comments?: string; // TODO: a string[] only works here with `Autocomplete` if the entities have globally unique names - criteriaTags: string[]; + criteria: string[]; tags: string[]; stakeholders?: string[]; stakeholderGroups?: string[]; @@ -66,8 +66,11 @@ export const ArchetypeForm: React.FC = ({ const { existingArchetypes, tags, + tagsToRefs, stakeholders, + stakeholdersToRefs, stakeholderGroups, + stakeholderGroupsToRefs, createArchetype, updateArchetype, } = useArchetypeFormData({ @@ -75,6 +78,12 @@ export const ArchetypeForm: React.FC = ({ onActionSuccess: onClose, }); + const archetypeTags = + archetype?.tags?.filter((t) => t?.source ?? "" === "") ?? []; + + const assessmentTags = + archetype?.tags?.filter((t) => t?.source ?? "" !== "") ?? []; + const validationSchema = yup.object().shape({ name: yup .string() @@ -104,7 +113,7 @@ export const ArchetypeForm: React.FC = ({ .max(250, t("validation.maxLength", { length: 250 })), // for complex data fields - criteriaTags: yup + criteria: yup .array() .of(yup.string()) .min(1) @@ -144,9 +153,8 @@ export const ArchetypeForm: React.FC = ({ description: archetype?.description || "", comments: archetype?.comments || "", - criteriaTags: - archetype?.criteriaTags?.map((tag) => tag.name).sort() ?? [], - tags: archetype?.tags?.map((tag) => tag.name).sort() ?? [], + criteria: archetype?.criteria?.map((tag) => tag.name).sort() ?? [], + tags: archetypeTags.map((tag) => tag.name).sort() ?? [], stakeholders: archetype?.stakeholders?.map((sh) => sh.name).sort() ?? [], stakeholderGroups: @@ -157,38 +165,24 @@ export const ArchetypeForm: React.FC = ({ }); const onValidSubmit = (values: ArchetypeFormValues) => { + // Note: We need to manually retain the tags with source != "" in the payload + const tags = [...(tagsToRefs(values.tags) ?? []), ...assessmentTags]; + const payload: New = { name: values.name.trim(), description: values.description?.trim() ?? "", comments: values.comments?.trim() ?? "", - criteriaTags: values.criteriaTags + criteria: values.criteria .map((tagName) => tags.find((tag) => tag.name === tagName)) - .filter(Boolean) as Tag[], + .filter(Boolean), tags: values.tags .map((tagName) => tags.find((tag) => tag.name === tagName)) - .filter(Boolean) as Tag[], - - stakeholders: - values.stakeholders === undefined - ? undefined - : (values.stakeholders - .map((name) => stakeholders.find((s) => s.name === name)) - .map((sh) => - !sh ? undefined : { id: sh.id, name: sh.name } - ) - .filter(Boolean) as Ref[]), + .filter(Boolean), - stakeholderGroups: - values.stakeholderGroups === undefined - ? undefined - : (values.stakeholderGroups - .map((name) => stakeholderGroups.find((s) => s.name === name)) - .map((sg) => - !sg ? undefined : { id: sg.id, name: sg.name } - ) - .filter(Boolean) as Ref[]), + stakeholders: stakeholdersToRefs(values.stakeholders), + stakeholderGroups: stakeholderGroupsToRefs(values.stakeholderGroups), }; if (archetype && !isDuplicating) { @@ -218,9 +212,9 @@ export const ArchetypeForm: React.FC = ({ items={tags} control={control} - name="criteriaTags" + name="criteria" label="Criteria Tags" - fieldId="criteriaTags" + fieldId="criteria" isRequired noResultsMessage={t("message.noResultsFoundTitle")} placeholderText={t("composed.selectMany", { @@ -234,7 +228,7 @@ export const ArchetypeForm: React.FC = ({ control={control} name="tags" label="Archetype Tags" - fieldId="archetypeTags" + fieldId="tags" isRequired noResultsMessage={t("message.noResultsFoundTitle")} placeholderText={t("composed.selectMany", { @@ -323,6 +317,7 @@ const useArchetypeFormData = ({ const { t } = useTranslation(); const { pushNotification } = React.useContext(NotificationsContext); + // Fetch data const { archetypes: existingArchetypes } = useFetchArchetypes(); const { archetype } = useFetchArchetypeById(id); @@ -335,6 +330,17 @@ const useArchetypeFormData = ({ const { stakeholderGroups } = useFetchStakeholderGroups(); const { stakeholders } = useFetchStakeholders(); + // Helpers + const tagsToRefs = (names: string[] | undefined | null) => + matchItemsToRefs(tags, (i) => i.name, names); + + const stakeholdersToRefs = (names: string[] | undefined | null) => + matchItemsToRefs(stakeholders, (i) => i.name, names); + + const stakeholderGroupsToRefs = (names: string[] | undefined | null) => + matchItemsToRefs(stakeholderGroups, (i) => i.name, names); + + // Mutation notification handlers const onCreateSuccess = (archetype: Archetype) => { pushNotification({ title: t("toastr.success.createWhat", { @@ -381,7 +387,10 @@ const useArchetypeFormData = ({ updateArchetype, tagCategories, tags, + tagsToRefs, stakeholders, + stakeholdersToRefs, stakeholderGroups, + stakeholderGroupsToRefs, }; }; diff --git a/client/src/app/pages/archetypes/components/archetype-tags-column.tsx b/client/src/app/pages/archetypes/components/archetype-tags-column.tsx index 1afd671cff..4d65bc8239 100644 --- a/client/src/app/pages/archetypes/components/archetype-tags-column.tsx +++ b/client/src/app/pages/archetypes/components/archetype-tags-column.tsx @@ -27,6 +27,7 @@ const TagLabel: React.FC<{ // TODO: Refactor the application-tags-label.tsx so applications and archetypes can share `TagLabel` // TODO: Sort tags? // TODO: Group tags by categories? +// TODO: Display ONLY manual tags (source==="") or display tags from ALL sources? const ArchetypeTagsColumn: React.FC<{ archetype: Archetype }> = ({ archetype, }) => ( diff --git a/client/src/mocks/stub-new-work/archetypes.ts b/client/src/mocks/stub-new-work/archetypes.ts index e72645e391..f7b6fa0d20 100644 --- a/client/src/mocks/stub-new-work/archetypes.ts +++ b/client/src/mocks/stub-new-work/archetypes.ts @@ -140,11 +140,11 @@ const data: Record = { name: "Wayne", description: "Wayne does the bare minimum", comments: "This one needs coffee", - criteriaTags: [tagData["1"]], + criteria: [tagData["1"]], tags: [tagData["81"]], - assessmentTags: [], stakeholders: [], stakeholderGroups: [], + assessed: false, }, 2: { @@ -152,11 +152,11 @@ const data: Record = { name: "Garth", description: "Garth has some extra tags", comments: "This one needs tea", - criteriaTags: [tagData["2"]], + criteria: [tagData["2"]], tags: [tagData["81"], tagData["82"]], - assessmentTags: [], stakeholders: [], stakeholderGroups: [], + assessed: false, }, 3: { @@ -164,13 +164,13 @@ const data: Record = { name: "Cassandra", description: "Cassandra is the most complex", comments: "This one needs cakes", - criteriaTags: [tagData["3"]], + criteria: [tagData["3"]], tags: [tagData["81"], tagData["82"], tagData["83"]], - assessmentTags: [], stakeholders: [], stakeholderGroups: [], // assessments: [{ id: 1, name: "test" }], assessments: [], + assessed: false, }, };