diff --git a/client/src/app/api/models.ts b/client/src/app/api/models.ts index e3107eff72..a3a3fea085 100644 --- a/client/src/app/api/models.ts +++ b/client/src/app/api/models.ts @@ -735,6 +735,7 @@ export interface Assessment confidence?: number; stakeholders?: Ref[]; stakeholderGroups?: Ref[]; + required?: boolean; } export interface CategorizedTag { category: TagCategory; @@ -794,3 +795,7 @@ export interface AssessmentWithArchetypeApplications extends AssessmentWithSectionOrder { archetypeApplications: Ref[]; } +export interface AssessmentsWithArchetype { + archetype: Archetype; + assessments: Assessment[]; +} diff --git a/client/src/app/pages/applications/components/application-detail-drawer/application-detail-drawer.tsx b/client/src/app/pages/applications/components/application-detail-drawer/application-detail-drawer.tsx index f3b393e913..6219a3e0f4 100644 --- a/client/src/app/pages/applications/components/application-detail-drawer/application-detail-drawer.tsx +++ b/client/src/app/pages/applications/components/application-detail-drawer/application-detail-drawer.tsx @@ -57,6 +57,7 @@ import { LabelsFromItems } from "@app/components/labels/labels-from-items/labels import { RiskLabel } from "@app/components/RiskLabel"; import { ApplicationDetailFields } from "./application-detail-fields"; import { useFetchArchetypes } from "@app/queries/archetypes"; +import { AssessedArchetypes } from "./components/assessed-archetypes"; export interface IApplicationDetailDrawerProps extends Pick { @@ -101,14 +102,6 @@ export const ApplicationDetailDrawer: React.FC< const enableDownloadSetting = useSetting("download.html.enabled"); - const assessedArchetypes = - application?.archetypes - ?.map((archetypeRef) => - archetypes.find((archetype) => archetype.id === archetypeRef.id) - ) - .filter((fullArchetype) => fullArchetype?.assessed) - .filter(Boolean) || []; - const reviewedArchetypes = application?.archetypes ?.map((archetypeRef) => @@ -211,18 +204,7 @@ export const ApplicationDetailDrawer: React.FC< {t("terms.archetypesAssessed")} - - {assessedArchetypes?.length ? ( - assessedArchetypes.map((assessedArchetype) => ( - - )) - ) : ( - - )} - + diff --git a/client/src/app/pages/applications/components/application-detail-drawer/components/assessed-archetypes.tsx b/client/src/app/pages/applications/components/application-detail-drawer/components/assessed-archetypes.tsx new file mode 100644 index 0000000000..89503af756 --- /dev/null +++ b/client/src/app/pages/applications/components/application-detail-drawer/components/assessed-archetypes.tsx @@ -0,0 +1,52 @@ +import React from "react"; +import { Application } from "@app/api/models"; +import { Label, LabelGroup, Spinner } from "@patternfly/react-core"; +import { EmptyTextMessage } from "@app/components/EmptyTextMessage"; +import { useTranslation } from "react-i18next"; +import { useFetchArchetypes } from "@app/queries/archetypes"; +import { useFetchAllAssessmentsWithArchetypes } from "@app/queries/assessments"; + +interface IAssessedArchetypesProps { + application: Application | null; +} + +export const AssessedArchetypes: React.FC = ({ + application, +}) => { + const { t } = useTranslation(); + const { + archetypes: applicationArchetypes, + isFetching: isFetchingArchetypes, + } = useFetchArchetypes(application); + + const { + assessmentsWithArchetypes, + isLoading: isFetchingAllAssessmentsWithArchetypesLoading, + } = useFetchAllAssessmentsWithArchetypes(applicationArchetypes); + + const assessedArchetypesWithARequiredAssessment = assessmentsWithArchetypes + ?.filter((assessmentsWithArchetype) => { + return ( + assessmentsWithArchetype.archetype.assessed && + assessmentsWithArchetype.assessments.some( + (assessment) => assessment?.required === true + ) + ); + }) + .map((assessmentsWithArchetype) => assessmentsWithArchetype.archetype); + + if (isFetchingArchetypes || isFetchingAllAssessmentsWithArchetypesLoading) { + return ; + } + return ( + + {assessedArchetypesWithARequiredAssessment?.length ? ( + assessedArchetypesWithARequiredAssessment?.map((archetype) => ( + + )) + ) : ( + + )} + + ); +}; diff --git a/client/src/app/queries/archetypes.ts b/client/src/app/queries/archetypes.ts index 7b0de34b64..e2d6efc470 100644 --- a/client/src/app/queries/archetypes.ts +++ b/client/src/app/queries/archetypes.ts @@ -1,7 +1,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { AxiosError } from "axios"; -import { Archetype } from "@app/api/models"; +import { Application, Archetype } from "@app/api/models"; import { createArchetype, deleteArchetype, @@ -14,18 +14,33 @@ import { assessmentsByItemIdQueryKey, } from "./assessments"; import { reviewsQueryKey } from "./reviews"; +import { useState } from "react"; export const ARCHETYPES_QUERY_KEY = "archetypes"; export const ARCHETYPE_QUERY_KEY = "archetype"; -export const useFetchArchetypes = () => { +export const useFetchArchetypes = (forApplication?: Application | null) => { + const [filteredArchetypes, setFilteredArchetypes] = useState([]); + const queryClient = useQueryClient(); const { isLoading, isSuccess, error, refetch, data } = useQuery({ initialData: [], - queryKey: [ARCHETYPES_QUERY_KEY], + queryKey: [ARCHETYPES_QUERY_KEY, forApplication?.id], + queryFn: getArchetypes, refetchInterval: 5000, - onSuccess: () => { + onSuccess: (fetchedArchetypes) => { + if (forApplication && forApplication.archetypes) { + const archetypeIds = forApplication.archetypes.map( + (archetype) => archetype.id + ); + const filtered = fetchedArchetypes.filter((archetype) => + archetypeIds.includes(archetype.id) + ); + setFilteredArchetypes(filtered); + } else { + setFilteredArchetypes(fetchedArchetypes); + } queryClient.invalidateQueries([reviewsQueryKey]); queryClient.invalidateQueries([assessmentsQueryKey]); queryClient.invalidateQueries([assessmentsByItemIdQueryKey]); @@ -35,7 +50,7 @@ export const useFetchArchetypes = () => { }); return { - archetypes: data || [], + archetypes: filteredArchetypes || [], isFetching: isLoading, isSuccess, error, diff --git a/client/src/app/queries/assessments.ts b/client/src/app/queries/assessments.ts index 8128ba75b4..77cd04d579 100644 --- a/client/src/app/queries/assessments.ts +++ b/client/src/app/queries/assessments.ts @@ -17,9 +17,11 @@ import { } from "@app/api/rest"; import { AxiosError } from "axios"; import { + Archetype, Assessment, AssessmentWithArchetypeApplications, AssessmentWithSectionOrder, + AssessmentsWithArchetype, InitialAssessment, } from "@app/api/models"; import { QuestionnairesQueryKey } from "./questionnaires"; @@ -214,7 +216,7 @@ const removeSectionOrderFromQuestions = ( ...assessmentWithOrder, sections: assessmentWithOrder.sections.map((section) => ({ ...section, - questions: section.questions.map(({ sectionOrder, ...rest }) => rest), // Destructure out sectionOrder + questions: section.questions.map(({ sectionOrder, ...rest }) => rest), })), }; }; @@ -262,3 +264,33 @@ export const useFetchAssessmentsWithArchetypeApplications = () => { isLoading: assessmentsLoading || isArchetypesLoading, }; }; + +export const useFetchAllAssessmentsWithArchetypes = ( + archetypes: Archetype[] = [] +) => { + const assessmentQueries = useQueries({ + queries: archetypes.map((archetype) => ({ + queryKey: ["assessmentsForArchetype", archetype.id], + queryFn: () => getAssessmentsByItemId(true, archetype.id), + })), + }); + + const assessmentsWithArchetypes: AssessmentsWithArchetype[] = + assessmentQueries + .map((query, index) => { + if (query.isSuccess) { + return { + archetype: archetypes[index], + assessments: query.data, + }; + } + return null; + }) + .filter(Boolean); + + return { + assessmentsWithArchetypes, + isLoading: assessmentQueries.some((query) => query.isLoading), + isError: assessmentQueries.some((query) => query.isError), + }; +};