diff --git a/client/src/app/api/models.ts b/client/src/app/api/models.ts index 968e9b678a..1fd51790c0 100644 --- a/client/src/app/api/models.ts +++ b/client/src/app/api/models.ts @@ -646,7 +646,7 @@ export interface Questionnaire { revision: number; questions: number; rating: string; - dateImported: string; + createTime: string; required: boolean; system: boolean; sections: Section[]; @@ -687,10 +687,12 @@ export interface Answer { selected?: boolean; } export interface Thresholds { - red: number; - unknown: number; - yellow: number; + red?: number; + unknown?: number; + yellow?: number; + green?: number; } + export type AssessmentStatus = "empty" | "started" | "complete"; export type Risk = "green" | "yellow" | "red" | "unknown"; diff --git a/client/src/app/pages/applications/analysis-wizard/analysis-wizard.tsx b/client/src/app/pages/applications/analysis-wizard/analysis-wizard.tsx index 8f3294da6f..dd85595c0a 100644 --- a/client/src/app/pages/applications/analysis-wizard/analysis-wizard.tsx +++ b/client/src/app/pages/applications/analysis-wizard/analysis-wizard.tsx @@ -1,11 +1,15 @@ import * as React from "react"; import { useIsMutating } from "@tanstack/react-query"; import { FormProvider, useForm } from "react-hook-form"; -import { Truncate } from "@patternfly/react-core"; import { + Modal, + ModalVariant, Wizard, - WizardStepFunctionType, -} from "@patternfly/react-core/deprecated"; + WizardStep, + WizardStepType, + WizardHeader, + Truncate, +} from "@patternfly/react-core"; import { useTranslation } from "react-i18next"; import { @@ -297,10 +301,8 @@ export const AnalysisWizard: React.FC = ({ onClose(); }; - const onMove: WizardStepFunctionType = ( - { id, name }, - { prevId, prevName } - ) => { + const onMove = (current: WizardStepType) => { + const id = current.id; if (id && stepIdReached < (id as number)) setStepIdReached(id as number); if (id === StepId.SetTargets) { if (!taskGroup) { @@ -311,93 +313,116 @@ export const AnalysisWizard: React.FC = ({ const analyzableApplications = useAnalyzableApplications(applications, mode); - const getStepNavProps = (stepId: StepId, forceBlock = false) => ({ - enableNext: - !forceBlock && - stepIdReached >= stepId && - (firstInvalidStep === null || firstInvalidStep >= stepId + 1), - canJumpTo: - !forceBlock && - stepIdReached >= stepId && - (firstInvalidStep === null || firstInvalidStep >= stepId), - }); + const isStepEnabled = (stepId: StepId) => { + return ( + stepIdReached + 1 >= stepId && + (firstInvalidStep === null || firstInvalidStep >= stepId) + ); + }; const steps = [ - { - name: t("wizard.terms.configureAnalysis"), - steps: [ - { - id: StepId.AnalysisMode, - name: t("wizard.terms.analysisMode"), - component: ( + + <> - ), - ...getStepNavProps(StepId.AnalysisMode, !!isMutating), - }, - { - id: StepId.SetTargets, - name: t("wizard.terms.setTargets"), - component: , - ...getStepNavProps(StepId.SetTargets), - }, - { - id: StepId.Scope, - name: t("wizard.terms.scope"), - component: , - ...getStepNavProps(StepId.Scope), - }, - ], - }, - { - name: t("wizard.terms.advanced"), - steps: [ - { - id: StepId.CustomRules, - name: t("wizard.terms.customRules"), - component: , - ...getStepNavProps(StepId.CustomRules), - }, - { - id: StepId.Options, - name: t("wizard.terms.options"), - component: , - ...getStepNavProps(StepId.Options), - }, - ], - }, - { - id: StepId.Review, - name: t("wizard.terms.review"), - component: , - nextButtonText: "Run", - ...getStepNavProps(StepId.Review), - }, + + , + + + , + + + , + ]} + >, + + + , + + + , + ]} + >, + + + , ]; + return ( <> {isOpen && ( - app.name).join(", ")} - /> - } - navAriaLabel={`${title} steps`} - mainAriaLabel={`${title} content`} - steps={steps} - onNext={onMove} - onBack={onMove} - onSave={handleSubmit(onSubmit)} - onClose={() => { - handleCancel(); - }} - /> + showClose={false} + aria-label="Application analysis wizard modal" + hasNoBodyWrapper + onEscapePress={handleCancel} + variant={ModalVariant.large} + > + + onMove(currentStep) + } + header={ + app.name).join(", ")} + /> + } + /> + } + > + {steps} + + )} diff --git a/client/src/app/pages/assessment-management/assessment-settings/assessment-settings-page.tsx b/client/src/app/pages/assessment-management/assessment-settings/assessment-settings-page.tsx index dbc8a1fa36..3462a66b72 100644 --- a/client/src/app/pages/assessment-management/assessment-settings/assessment-settings-page.tsx +++ b/client/src/app/pages/assessment-management/assessment-settings/assessment-settings-page.tsx @@ -8,6 +8,7 @@ import { EmptyState, EmptyStateBody, EmptyStateIcon, + List, MenuToggle, MenuToggleElement, Modal, @@ -51,7 +52,10 @@ import { useHistory } from "react-router-dom"; import { Paths } from "@app/Paths"; import { ImportQuestionnaireForm } from "@app/pages/assessment-management/import-questionnaire-form/import-questionnaire-form"; import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog/ConfirmDeleteDialog"; -import { ExportQuestionnaireDropdownItem } from "./ExportQuestionnaireDropdownItem"; +import { ExportQuestionnaireDropdownItem } from "./components/export-questionnaire-dropdown-item"; +import dayjs from "dayjs"; +import { QuestionnaireQuestionsColumn } from "./components/questionnaire-questions-column"; +import { QuestionnaireThresholdsColumn } from "./components/questionnaire-thresholds-column"; const AssessmentSettings: React.FC = () => { const { t } = useTranslation(); @@ -108,7 +112,7 @@ const AssessmentSettings: React.FC = () => { name: "Name", questions: "Questions", rating: "Rating", - dateImported: "Date imported", + createTime: "Date imported", }, isSelectable: false, expandableVariant: null, @@ -127,10 +131,10 @@ const AssessmentSettings: React.FC = () => { }, }, ], - sortableColumns: ["name", "dateImported"], + sortableColumns: ["name", "createTime"], getSortValues: (questionnaire) => ({ name: questionnaire.name || "", - dateImported: questionnaire.dateImported || "", + createTime: questionnaire.createTime || "", }), initialSort: { columnKey: "name", direction: "asc" }, hasPagination: true, @@ -225,7 +229,7 @@ const AssessmentSettings: React.FC = () => { - + @@ -247,6 +251,10 @@ const AssessmentSettings: React.FC = () => { numRenderedColumns={numRenderedColumns} > {currentPageItems?.map((questionnaire, rowIndex) => { + const formattedDate = dayjs(questionnaire.createTime) + .utc() + .format("YYYY-MM-DD HH:mm:ss"); + return ( @@ -283,19 +291,25 @@ const AssessmentSettings: React.FC = () => { width={10} {...getTdProps({ columnKey: "questions" })} > - {questionnaire.questions} + - {questionnaire.rating} + + + - {questionnaire.dateImported} + {formattedDate} = ({ questionnaire }) => { + const totalQuestions = (questionnaire.sections || []).reduce( + (total, section) => { + return total + (section.questions ? section.questions.length : 0); + }, + 0 + ); + return {totalQuestions}; +}; diff --git a/client/src/app/pages/assessment-management/assessment-settings/components/questionnaire-thresholds-column.tsx b/client/src/app/pages/assessment-management/assessment-settings/components/questionnaire-thresholds-column.tsx new file mode 100644 index 0000000000..b639a948ad --- /dev/null +++ b/client/src/app/pages/assessment-management/assessment-settings/components/questionnaire-thresholds-column.tsx @@ -0,0 +1,27 @@ +import React from "react"; +import { ListItem } from "@patternfly/react-core"; +import { Questionnaire, Thresholds } from "@app/api/models"; + +export const QuestionnaireThresholdsColumn: React.FC<{ + questionnaire: Questionnaire; +}> = ({ questionnaire }) => { + const thresholdsToListItems = (thresholds: Thresholds) => { + const thresholdKeys: (keyof Thresholds)[] = Object.keys( + thresholds + ) as (keyof Thresholds)[]; + + return ( + <> + {thresholdKeys.map((color) => { + const percentage: number = thresholds[color] || 0; + return ( + + {color} {percentage}% + + ); + })} + + ); + }; + return thresholdsToListItems(questionnaire.thresholds || {}); +}; diff --git a/client/src/app/pages/migration-waves/components/migration-wave-form.tsx b/client/src/app/pages/migration-waves/components/migration-wave-form.tsx index 51aec5403a..d9d8a50293 100644 --- a/client/src/app/pages/migration-waves/components/migration-wave-form.tsx +++ b/client/src/app/pages/migration-waves/components/migration-wave-form.tsx @@ -22,8 +22,6 @@ import { useUpdateMigrationWaveMutation, } from "@app/queries/migration-waves"; import dayjs from "dayjs"; -import utc from "dayjs/plugin/utc"; -import customParseFormat from "dayjs/plugin/customParseFormat"; import { Stakeholder, StakeholderGroup, @@ -37,8 +35,6 @@ import { import { OptionWithValue, SimpleSelect } from "@app/components/SimpleSelect"; import { NotificationsContext } from "@app/components/NotificationsContext"; import { DEFAULT_SELECT_MAX_HEIGHT } from "@app/Constants"; -dayjs.extend(utc); -dayjs.extend(customParseFormat); const stakeholderGroupToOption = ( value: StakeholderGroup diff --git a/client/src/app/pages/migration-waves/migration-waves.tsx b/client/src/app/pages/migration-waves/migration-waves.tsx index d2ebd9e260..e71c81e4e7 100644 --- a/client/src/app/pages/migration-waves/migration-waves.tsx +++ b/client/src/app/pages/migration-waves/migration-waves.tsx @@ -33,8 +33,6 @@ import { import { useTranslation } from "react-i18next"; import { AxiosError, AxiosResponse } from "axios"; import dayjs from "dayjs"; -import utc from "dayjs/plugin/utc"; -import timezone from "dayjs/plugin/timezone"; import CubesIcon from "@patternfly/react-icons/dist/esm/icons/cubes-icon"; import EllipsisVIcon from "@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon"; @@ -70,9 +68,6 @@ import { AppPlaceholder } from "@app/components/AppPlaceholder"; import { ToolbarBulkSelector } from "@app/components/ToolbarBulkSelector"; import { ConfirmDialog } from "@app/components/ConfirmDialog"; -dayjs.extend(utc); -dayjs.extend(timezone); - export const MigrationWaves: React.FC = () => { const { t } = useTranslation(); const { pushNotification } = React.useContext(NotificationsContext); diff --git a/client/src/index.tsx b/client/src/index.tsx index 0cc1915b8b..a645470ac5 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -6,6 +6,14 @@ import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import App from "@app/App"; import reportWebVitals from "@app/reportWebVitals"; import { KeycloakProvider } from "@app/components/KeycloakProvider"; +import dayjs from "dayjs"; +import utc from "dayjs/plugin/utc"; +import timezone from "dayjs/plugin/timezone"; +import customParseFormat from "dayjs/plugin/customParseFormat"; + +dayjs.extend(utc); +dayjs.extend(timezone); +dayjs.extend(customParseFormat); if (process.env.NODE_ENV === "development") { import("./mocks/browser").then((browserMocks) => { diff --git a/client/src/mocks/stub-new-work/questionnaireData.ts b/client/src/mocks/stub-new-work/questionnaireData.ts index 0986f99e46..36560b9147 100644 --- a/client/src/mocks/stub-new-work/questionnaireData.ts +++ b/client/src/mocks/stub-new-work/questionnaireData.ts @@ -10,7 +10,7 @@ const questionnaireData: Record = { revision: 1, questions: 42, rating: "5% Red, 25% Yellow", - dateImported: "8 Aug. 2023, 10:20 AM EST", + createTime: "8 Aug. 2023, 10:20 AM EST", required: false, system: true, sections: [ @@ -189,7 +189,7 @@ const questionnaireData: Record = { revision: 1, questions: 24, rating: "15% Red, 35% Yellow", - dateImported: "9 Aug. 2023, 03:32 PM EST", + createTime: "9 Aug. 2023, 03:32 PM EST", required: true, system: false, sections: [ @@ -369,7 +369,7 @@ const questionnaireData: Record = { revision: 1, questions: 34, rating: "7% Red, 25% Yellow", - dateImported: "10 Aug. 2023, 11:23 PM EST", + createTime: "10 Aug. 2023, 11:23 PM EST", required: true, system: false, sections: [