From 86326977876960f1b74a3bc619f81f4c4397eef3 Mon Sep 17 00:00:00 2001 From: ibolton336 Date: Fri, 15 Sep 2023 12:33:17 -0400 Subject: [PATCH] Fix missing archetype flow pieces Signed-off-by: ibolton336 Drive isArchetype off of location in case of hard nav Signed-off-by: ibolton336 Remove redundant query Signed-off-by: ibolton336 Modify confirm dialog to include archetype assessment override alert Only show matching assessments for each entity type Update assessment override to use fetchassessment by archetype id approach wire up create assessment on confirm Use hook for isArchetype update test file Update logic for take assessment Wire discard assessment/review back in Signed-off-by: ibolton336 --- client/public/locales/en/translation.json | 4 +- client/src/app/Paths.ts | 3 +- client/src/app/Routes.tsx | 7 +- client/src/app/api/models.ts | 3 +- client/src/app/components/ConfirmDialog.tsx | 7 ++ .../questionnaire-summary.tsx | 23 ++-- client/src/app/hooks/useIsArchetype.ts | 17 +++ .../applications-table-assessment.tsx | 9 ++ .../application-assessment-status.tsx | 2 +- .../questionnaire-upload-test-file.yml | 11 +- .../app/pages/assessment/assessment-page.tsx | 1 + .../assessment-actions-page.tsx | 11 +- .../dynamic-assessment-actions-row.tsx | 114 ++++++++++++++++-- .../components/questionnaires-table.tsx | 19 ++- .../components/assessment-page-header.tsx | 36 +++--- .../assessment-wizard/assessment-wizard.tsx | 91 ++++++++------ .../custom-wizard-footer.tsx | 20 +-- client/src/app/queries/applications.ts | 7 +- client/src/app/queries/assessments.ts | 6 +- client/src/app/queries/reviews.ts | 4 +- 20 files changed, 279 insertions(+), 116 deletions(-) create mode 100644 client/src/app/hooks/useIsArchetype.ts diff --git a/client/public/locales/en/translation.json b/client/public/locales/en/translation.json index 73e78cd41e..83afdec1a8 100644 --- a/client/public/locales/en/translation.json +++ b/client/public/locales/en/translation.json @@ -168,8 +168,8 @@ "noDataAvailableTitle": "No data available", "noResultsFoundBody": "No results match the filter criteria. Remove all filters or clear all filters to show results.", "noResultsFoundTitle": "No results found", - "overrideAssessmentConfirmation": "This application has already been assessed. Do you want to continue?", - "overrideArchetypeConfirmation": "The archetype for this application already has an assessment. Do you want to create a dedicated assessment for this application?", + "overrideAssessmentDescription": "The archetype for {{what}} already has an assessment.", + "overrideAssessmentConfirmation": "Do you want to create a dedicated assessment for this application?", "overrideReviewConfirmation": "This application has already been reviewed. Do you want to continue?", "reasonForError": "The reported reason for the error:", "reviewInstructions": "Use this section to provide your assessment of the possible migration/modernization plan and effort estimation.", diff --git a/client/src/app/Paths.ts b/client/src/app/Paths.ts index 01875abfb4..d293facac0 100644 --- a/client/src/app/Paths.ts +++ b/client/src/app/Paths.ts @@ -12,7 +12,8 @@ export enum Paths { applicationsAssessment = "/applications/assessment/:assessmentId", applicationAssessmentActions = "/applications/assessment-actions/:applicationId", archetypeAssessmentActions = "/archetypes/assessment-actions/:archetypeId", - assessmentSummary = "/applications/assessment-summary/:assessmentId", + applicationAssessmentSummary = "/applications/assessment-summary/:assessmentId", + archetypeAssessmentSummary = "/archetypes/assessment-summary/:assessmentId", applicationsReview = "/applications/:applicationId/review", applicationsAnalysis = "/applications/analysis", archetypes = "/archetypes", diff --git a/client/src/app/Routes.tsx b/client/src/app/Routes.tsx index e98bcb483a..25a700720c 100644 --- a/client/src/app/Routes.tsx +++ b/client/src/app/Routes.tsx @@ -102,7 +102,12 @@ export const devRoutes: IRoute[] = [ exact: false, }, { - path: Paths.assessmentSummary, + path: Paths.applicationAssessmentSummary, + comp: AssessmentSummary, + exact: false, + }, + { + path: Paths.archetypeAssessmentSummary, comp: AssessmentSummary, exact: false, }, diff --git a/client/src/app/api/models.ts b/client/src/app/api/models.ts index b9524cc588..78983090a6 100644 --- a/client/src/app/api/models.ts +++ b/client/src/app/api/models.ts @@ -128,6 +128,7 @@ export interface Application { migrationWave: Ref | null; assessments?: Ref[]; assessed?: boolean; + archetypes?: Ref[]; } export interface Review { @@ -749,6 +750,6 @@ export interface Archetype { assessmentTags?: Tag[]; stakeholders?: Ref[]; stakeholderGroups?: Ref[]; - applications?: Application[]; + applications?: Ref[]; assessments?: Ref[]; } diff --git a/client/src/app/components/ConfirmDialog.tsx b/client/src/app/components/ConfirmDialog.tsx index f42e046c44..534efa7be3 100644 --- a/client/src/app/components/ConfirmDialog.tsx +++ b/client/src/app/components/ConfirmDialog.tsx @@ -4,6 +4,7 @@ import { Modal, ButtonVariant, ModalVariant, + Alert, } from "@patternfly/react-core"; export interface ConfirmDialogProps { @@ -24,6 +25,8 @@ export interface ConfirmDialogProps { inProgress?: boolean; confirmBtnVariant: ButtonVariant; + alertMessage?: string; + onClose: () => void; onConfirm: () => void; onCancel?: () => void; @@ -41,6 +44,7 @@ export const ConfirmDialog: React.FC = ({ onClose, onConfirm, onCancel, + alertMessage, }) => { const confirmBtn = ( ) : null} + setArchetypeRefToOverride(null)} + onClose={() => setArchetypeRefToOverride(null)} + onConfirm={() => { + history.push( + formatPath(Paths.applicationsAssessment, { + assessmentId: archetypeRefToOverride?.id, + }) + ); + setArchetypeRefToOverride(null); + createAssessment(); + }} + /> ); }; diff --git a/client/src/app/pages/assessment/components/assessment-actions/components/questionnaires-table.tsx b/client/src/app/pages/assessment/components/assessment-actions/components/questionnaires-table.tsx index 9eaaa5f221..77acd47bd3 100644 --- a/client/src/app/pages/assessment/components/assessment-actions/components/questionnaires-table.tsx +++ b/client/src/app/pages/assessment/components/assessment-actions/components/questionnaires-table.tsx @@ -74,8 +74,16 @@ const QuestionnairesTable: React.FC = ({ > {currentPageItems?.map((questionnaire, rowIndex) => { - const matchingAssessment = assessments?.find( - (assessment) => assessment.questionnaire.id === questionnaire.id + const matchingArchetypeAssessment = assessments?.find( + (assessment) => + assessment.questionnaire.id === questionnaire.id && + assessment?.archetype?.id === archetype?.id + ); + + const matchingApplicationAssessment = assessments?.find( + (assessment) => + assessment.questionnaire.id === questionnaire.id && + assessment?.application?.id === application?.id ); return ( @@ -93,9 +101,12 @@ const QuestionnairesTable: React.FC = ({ {application || archetype ? ( diff --git a/client/src/app/pages/assessment/components/assessment-page-header.tsx b/client/src/app/pages/assessment/components/assessment-page-header.tsx index c587b96679..6644d0e3f8 100644 --- a/client/src/app/pages/assessment/components/assessment-page-header.tsx +++ b/client/src/app/pages/assessment/components/assessment-page-header.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React from "react"; import { useHistory } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { Button, ButtonVariant, Modal, Text } from "@patternfly/react-core"; @@ -8,7 +8,9 @@ import { PageHeader } from "@app/components/PageHeader"; import { ApplicationDependenciesFormContainer } from "@app/components/ApplicationDependenciesFormContainer"; import { Paths } from "@app/Paths"; import { Application, Assessment } from "@app/api/models"; -import { getApplicationById } from "@app/api/rest"; +import { useFetchApplicationById } from "@app/queries/applications"; +import { useFetchArchetypeById } from "@app/queries/archetypes"; +import useIsArchetype from "@app/hooks/useIsArchetype"; export interface AssessmentPageHeaderProps { assessment?: Assessment; @@ -19,20 +21,14 @@ export const AssessmentPageHeader: React.FC = ({ }) => { const { t } = useTranslation(); const history = useHistory(); + const isArchetype = useIsArchetype(); + + const { archetype } = useFetchArchetypeById(assessment?.archetype?.id); + const { application } = useFetchApplicationById(assessment?.application?.id); const [isConfirmDialogOpen, setIsConfirmDialogOpen] = React.useState(false); - const [application, setApplication] = useState(); - - useEffect(() => { - if (assessment?.application?.id) { - getApplicationById(assessment?.application?.id).then((data) => { - setApplication(data); - }); - } - }, [assessment]); - const [applicationDependenciesToManage, setApplicationDependenciesToManage] = React.useState(null); @@ -40,17 +36,23 @@ export const AssessmentPageHeader: React.FC = ({ <> {application?.name}} + description={ + + {isArchetype ? archetype?.name : application?.name} + + } breadcrumbs={[ { - title: t("terms.applications"), + title: isArchetype ? t("terms.archetype") : t("terms.applications"), path: () => { setIsConfirmDialogOpen(true); }, }, { title: t("terms.assessment"), - path: Paths.applicationsAssessment, + path: isArchetype + ? Paths.archetypesAssessment + : Paths.applicationsAssessment, }, ]} btnActions={ @@ -92,7 +94,9 @@ export const AssessmentPageHeader: React.FC = ({ cancelBtnLabel={t("actions.cancel")} onCancel={() => setIsConfirmDialogOpen(false)} onClose={() => setIsConfirmDialogOpen(false)} - onConfirm={() => history.push(Paths.applications)} + onConfirm={() => + history.push(isArchetype ? Paths.archetypes : Paths.applications) + } /> )} diff --git a/client/src/app/pages/assessment/components/assessment-wizard/assessment-wizard.tsx b/client/src/app/pages/assessment/components/assessment-wizard/assessment-wizard.tsx index b7353d8e56..2c9c2a8589 100644 --- a/client/src/app/pages/assessment/components/assessment-wizard/assessment-wizard.tsx +++ b/client/src/app/pages/assessment/components/assessment-wizard/assessment-wizard.tsx @@ -18,7 +18,6 @@ import { NotificationsContext } from "@app/components/NotificationsContext"; import { WizardStepNavDescription } from "../wizard-step-nav-description"; import { QuestionnaireForm } from "../questionnaire-form"; import { ConfirmDialog } from "@app/components/ConfirmDialog"; -import { useFetchQuestionnaires } from "@app/queries/questionnaires"; import { COMMENTS_KEY, QUESTIONS_KEY, @@ -34,6 +33,7 @@ import { formatPath, getAxiosErrorMessage } from "@app/utils/utils"; import { Paths } from "@app/Paths"; import { yupResolver } from "@hookform/resolvers/yup"; import { AssessmentStakeholdersForm } from "../assessment-stakeholders-form/assessment-stakeholders-form"; +import useIsArchetype from "@app/hooks/useIsArchetype"; export const SAVE_ACTION_KEY = "saveAction"; @@ -58,16 +58,15 @@ export interface AssessmentWizardValues { export interface AssessmentWizardProps { assessment?: Assessment; isOpen: boolean; - isArchetype?: boolean; } export const AssessmentWizard: React.FC = ({ assessment, isOpen, - isArchetype, }) => { + const isArchetype = useIsArchetype(); const queryClient = useQueryClient(); - const { questionnaires } = useFetchQuestionnaires(); + const onHandleUpdateAssessmentSuccess = () => { queryClient.invalidateQueries([ assessmentsByItemIdQueryKey, @@ -78,10 +77,6 @@ export const AssessmentWizard: React.FC = ({ onHandleUpdateAssessmentSuccess ); - const matchingQuestionnaire = questionnaires.find( - (questionnaire) => questionnaire.id === assessment?.questionnaire?.id - ); - const { t } = useTranslation(); const [currentStep, setCurrentStep] = useState(0); @@ -94,10 +89,10 @@ export const AssessmentWizard: React.FC = ({ const { pushNotification } = React.useContext(NotificationsContext); const sortedSections = useMemo(() => { - return (matchingQuestionnaire ? matchingQuestionnaire.sections : []).sort( + return (assessment ? assessment.sections : []).sort( (a, b) => a.order - b.order ); - }, [matchingQuestionnaire]); + }, [assessment]); //TODO: Add comments to the sections when/if available from api // const initialComments = useMemo(() => { @@ -112,8 +107,8 @@ export const AssessmentWizard: React.FC = ({ const initialQuestions = useMemo(() => { const questions: { [key: string]: string | undefined } = {}; - if (assessment && matchingQuestionnaire) { - matchingQuestionnaire.sections + if (assessment) { + assessment.sections .flatMap((f) => f.questions) .forEach((question) => { const existingAnswer = assessment.sections @@ -126,17 +121,21 @@ export const AssessmentWizard: React.FC = ({ }); } return questions; - }, [assessment, matchingQuestionnaire]); + }, [assessment]); useEffect(() => { methods.reset({ - // stakeholders: assessment?.stakeholders || [], - // stakeholderGroups: assessment?.stakeholderGroups || [], + stakeholders: + assessment?.stakeholders.map((stakeholder) => stakeholder.name) || [], + stakeholderGroups: + assessment?.stakeholderGroups.map( + (stakeholderGroup) => stakeholderGroup.name + ) || [], // comments: initialComments, questions: initialQuestions, [SAVE_ACTION_KEY]: SAVE_ACTION_VALUE.SAVE_AS_DRAFT, }); - }, [initialQuestions]); + }, [initialQuestions, assessment]); const validationSchema = yup.object().shape({ stakeholders: yup.array().of(yup.string()), @@ -235,7 +234,7 @@ export const AssessmentWizard: React.FC = ({ // Create an array of sections based on the questionsData const sections: Section[] = - matchingQuestionnaire?.sections?.map((section) => { + assessment?.sections?.map((section) => { //TODO: Add comments to the sections // const commentValues = values["comments"]; // const fieldName = getCommentFieldName(category, false); @@ -263,7 +262,7 @@ export const AssessmentWizard: React.FC = ({ const handleSaveAsDraft = async (formValues: AssessmentWizardValues) => { try { - if (!assessment?.application?.id) { + if (!assessment?.application?.id && !assessment?.archetype?.id) { console.log("An assessment must exist in order to save as draft"); return; } @@ -307,7 +306,7 @@ export const AssessmentWizard: React.FC = ({ const handleSave = async (formValues: AssessmentWizardValues) => { try { - if (!assessment?.application?.id) { + if (!assessment?.application?.id && !assessment?.archetype?.id) { console.log("An assessment must exist in order to save."); return; } @@ -352,7 +351,7 @@ export const AssessmentWizard: React.FC = ({ const handleSaveAndReview = async (formValues: AssessmentWizardValues) => { try { - if (!assessment?.application?.id) { + if (!assessment?.application?.id && !assessment?.archetype?.id) { console.log("An assessment must exist in order to save."); return; } @@ -375,22 +374,41 @@ export const AssessmentWizard: React.FC = ({ title: "Assessment has been saved.", variant: "success", }); - - assessment?.application?.id && - getApplicationById(assessment.application.id) - .then((data) => { - history.push( - formatPath(Paths.applicationsReview, { - applicationId: data.id, - }) - ); - }) - .catch((error) => { - pushNotification({ - title: getAxiosErrorMessage(error), - variant: "danger", + if (isArchetype) { + //TODO: Review Archetype? + // assessment?.archetype?.id && + // getArchetypeById(assessment.archetype.id) + // .then((data) => { + // history.push( + // formatPath(Paths.a, { + // applicationId: data.id, + // }) + // ); + // }) + // .catch((error) => { + // pushNotification({ + // title: getAxiosErrorMessage(error), + // variant: "danger", + // }); + // }); + // } + } else { + assessment?.application?.id && + getApplicationById(assessment.application.id) + .then((data) => { + history.push( + formatPath(Paths.applicationsReview, { + applicationId: data.id, + }) + ); + }) + .catch((error) => { + pushNotification({ + title: getAxiosErrorMessage(error), + variant: "danger", + }); }); - }); + } } catch (error) { pushNotification({ title: "Failed to save.", @@ -401,7 +419,7 @@ export const AssessmentWizard: React.FC = ({ }; const onSubmit = async (formValues: AssessmentWizardValues) => { - if (!assessment?.application?.id) { + if (!assessment?.application?.id && !assessment?.archetype?.id) { console.log("An assessment must exist in order to save the form"); return; } @@ -453,6 +471,7 @@ export const AssessmentWizard: React.FC = ({ const wizardFooter = ( void; onSaveAsDraft: () => void; } @@ -21,6 +22,7 @@ export const CustomWizardFooter: React.FC = ({ isLastStep, isDisabled, isFormInvalid, + isArchetype, onSave, onSaveAsDraft, }) => { @@ -47,14 +49,16 @@ export const CustomWizardFooter: React.FC = ({ > {t("actions.save")} - + {!isArchetype && ( + + )} ) : (