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 && ( + + )} ) : (