From 4ed295050ad2633455c980c4872e6c3a0b543b0e Mon Sep 17 00:00:00 2001 From: ibolton336 Date: Wed, 8 Nov 2023 14:37:05 -0500 Subject: [PATCH 1/3] Add tooltip for arch review & break up delete funct Signed-off-by: ibolton336 --- client/public/locales/en/translation.json | 5 +- .../applications-table/applications-table.tsx | 120 ++++++++++++++---- 2 files changed, 96 insertions(+), 29 deletions(-) diff --git a/client/public/locales/en/translation.json b/client/public/locales/en/translation.json index 3e66f825c1..b5bbf95c94 100644 --- a/client/public/locales/en/translation.json +++ b/client/public/locales/en/translation.json @@ -29,7 +29,9 @@ "createTags": "Create tags", "cancelAnalysis": "Cancel analysis", "delete": "Delete", - "discardAssessment": "Discard assessment/review", + "discardAssessment": "Discard assessment(s)", + "discardReview": "Discard review", + "downloadCsvTemplate": "Download CSV template", "download": "Download {{what}}", "duplicate": "Duplicate", @@ -360,6 +362,7 @@ "reports": "Reports", "repositoryType": "Repository type", "review": "Review", + "reviewedArchetype": "Archetype reviewed", "reviews": "Reviews", "reviewComments": "Review comments", "risk": "Risk", diff --git a/client/src/app/pages/applications/applications-table/applications-table.tsx b/client/src/app/pages/applications/applications-table/applications-table.tsx index 71eac890f8..e715f06bc4 100644 --- a/client/src/app/pages/applications/applications-table/applications-table.tsx +++ b/client/src/app/pages/applications/applications-table/applications-table.tsx @@ -17,6 +17,9 @@ import { MenuToggle, MenuToggleElement, Modal, + Tooltip, + Grid, + GridItem, } from "@patternfly/react-core"; import { PencilAltIcon, TagIcon, EllipsisVIcon } from "@patternfly/react-icons"; import { @@ -28,6 +31,7 @@ import { ActionsColumn, Tbody, } from "@patternfly/react-table"; +import { QuestionCircleIcon } from "@patternfly/react-icons/dist/esm/icons/question-circle-icon"; // @app components and utilities import { AppPlaceholder } from "@app/components/AppPlaceholder"; @@ -184,7 +188,10 @@ export const ApplicationsTable: React.FC = () => { Application[] >([]); - const [assessmentOrReviewToDiscard, setAssessmentOrReviewToDiscard] = + const [assessmentToDiscard, setAssessmentToDiscard] = + React.useState(null); + + const [reviewToDiscard, setReviewToDiscard] = React.useState(null); const { @@ -257,15 +264,8 @@ export const ApplicationsTable: React.FC = () => { onDeleteError ); - const discardAssessmentAndReview = async (application: Application) => { + const discardAssessment = async (application: Application) => { try { - if (application.review?.id) { - await deleteReview({ - id: application.review.id, - name: application.name, - }); - } - if (application.assessments) { await Promise.all( application.assessments.map(async (assessment) => { @@ -277,7 +277,20 @@ export const ApplicationsTable: React.FC = () => { ); } } catch (error) { - console.error("Error while deleting assessments and/or reviews:", error); + console.error("Error while deleting assessments:", error); + } + }; + + const discardReview = async (application: Application) => { + try { + if (application.review?.id) { + await deleteReview({ + id: application.review.id, + name: application.name, + }); + } + } catch (error) { + console.error("Error while deleting review:", error); } }; @@ -822,13 +835,27 @@ export const ApplicationsTable: React.FC = () => { modifier="truncate" {...getTdProps({ columnKey: "review" })} > - + + + + + + {hasReviewedArchetype ? ( + + + + ) : null} + + { title: t("actions.review"), onClick: () => reviewSelectedApp(application), }, - ...(application?.review + ...(application?.assessments?.length ? [ { title: t("actions.discardAssessment"), onClick: () => - setAssessmentOrReviewToDiscard( - application - ), + setAssessmentToDiscard(application), + }, + ] + : []), + ...(application?.review + ? [ + { + title: t("actions.discardReview"), + onClick: () => + setReviewToDiscard(application), }, ] : []), @@ -1066,16 +1100,46 @@ export const ApplicationsTable: React.FC = () => { what: t("terms.assessment").toLowerCase(), })} titleIconVariant={"warning"} - isOpen={assessmentOrReviewToDiscard !== null} + isOpen={assessmentToDiscard !== null} message={ + The assessment(s) for{" "} + {assessmentToDiscard?.name} will be discarded. + Do you wish to continue? + + + } + confirmBtnVariant={ButtonVariant.primary} + confirmBtnLabel={t("actions.continue")} + cancelBtnLabel={t("actions.cancel")} + onCancel={() => setAssessmentToDiscard(null)} + onClose={() => setAssessmentToDiscard(null)} + onConfirm={() => { + discardAssessment(assessmentToDiscard!); + setAssessmentToDiscard(null); + }} + /> + + - The assessment for applicationName will be + The review for {reviewToDiscard?.name} will be discarded, as well as the review result. Do you wish to continue? @@ -1084,11 +1148,11 @@ export const ApplicationsTable: React.FC = () => { confirmBtnVariant={ButtonVariant.primary} confirmBtnLabel={t("actions.continue")} cancelBtnLabel={t("actions.cancel")} - onCancel={() => setAssessmentOrReviewToDiscard(null)} - onClose={() => setAssessmentOrReviewToDiscard(null)} + onCancel={() => setReviewToDiscard(null)} + onClose={() => setReviewToDiscard(null)} onConfirm={() => { - discardAssessmentAndReview(assessmentOrReviewToDiscard!); - setAssessmentOrReviewToDiscard(null); + discardReview(reviewToDiscard!); + setReviewToDiscard(null); }} /> Date: Wed, 8 Nov 2023 14:56:04 -0500 Subject: [PATCH 2/3] Invalidate assessments by app id on app fetch to fix assessment status Signed-off-by: ibolton336 --- client/src/app/queries/applications.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/src/app/queries/applications.ts b/client/src/app/queries/applications.ts index e2d034de50..1adf91d17f 100644 --- a/client/src/app/queries/applications.ts +++ b/client/src/app/queries/applications.ts @@ -21,7 +21,10 @@ import { updateApplication, } from "@app/api/rest"; import { reviewsQueryKey } from "./reviews"; -import { assessmentsQueryKey } from "./assessments"; +import { + assessmentsByItemIdQueryKey, + assessmentsQueryKey, +} from "./assessments"; import saveAs from "file-saver"; export const ApplicationDependencyQueryKey = "applicationdependencies"; @@ -43,6 +46,7 @@ export const useFetchApplications = (refetchDisabled: boolean = false) => { onSuccess: () => { queryClient.invalidateQueries([reviewsQueryKey]); queryClient.invalidateQueries([assessmentsQueryKey]); + queryClient.invalidateQueries([assessmentsByItemIdQueryKey]); }, onError: (error: AxiosError) => console.log(error), }); From cf4cfee41204bc623e6991045c0e4b1598e493e2 Mon Sep 17 00:00:00 2001 From: ibolton336 Date: Wed, 8 Nov 2023 15:09:47 -0500 Subject: [PATCH 3/3] Add discard options for archetypes Signed-off-by: ibolton336 --- .../app/pages/archetypes/archetypes-page.tsx | 149 +++++++++++++++++- client/src/app/queries/archetypes.ts | 12 ++ 2 files changed, 160 insertions(+), 1 deletion(-) diff --git a/client/src/app/pages/archetypes/archetypes-page.tsx b/client/src/app/pages/archetypes/archetypes-page.tsx index 0f78ade644..ba53891879 100644 --- a/client/src/app/pages/archetypes/archetypes-page.tsx +++ b/client/src/app/pages/archetypes/archetypes-page.tsx @@ -1,5 +1,5 @@ import React, { useState } from "react"; -import { useTranslation } from "react-i18next"; +import { Trans, useTranslation } from "react-i18next"; import { useHistory } from "react-router-dom"; import { Button, @@ -57,6 +57,8 @@ import { AxiosError } from "axios"; import { Paths } from "@app/Paths"; import { SimplePagination } from "@app/components/SimplePagination"; import { TablePersistenceKeyPrefix } from "@app/Constants"; +import { useDeleteAssessmentMutation } from "@app/queries/assessments"; +import { useDeleteReviewMutation } from "@app/queries/reviews"; const Archetypes: React.FC = () => { const { t } = useTranslation(); @@ -71,6 +73,11 @@ const Archetypes: React.FC = () => { const [archetypeToEdit, setArchetypeToEdit] = useState( null ); + const [assessmentToDiscard, setAssessmentToDiscard] = + React.useState(null); + + const [reviewToDiscard, setReviewToDiscard] = + React.useState(null); const [archetypeToDuplicate, setArchetypeToDuplicate] = useState(null); @@ -97,6 +104,70 @@ const Archetypes: React.FC = () => { }), onError ); + const onDeleteAssessmentSuccess = (name: string) => { + pushNotification({ + title: t("toastr.success.assessmentDiscarded", { + application: name, + }), + variant: "success", + }); + }; + + const onDeleteError = (error: AxiosError) => { + pushNotification({ + title: getAxiosErrorMessage(error), + variant: "danger", + }); + }; + + const { mutate: deleteAssessment } = useDeleteAssessmentMutation( + onDeleteAssessmentSuccess, + onDeleteError + ); + + const discardAssessment = async (archetype: Archetype) => { + try { + if (archetype.assessments) { + await Promise.all( + archetype.assessments.map(async (assessment) => { + await deleteAssessment({ + assessmentId: assessment.id, + archetypeId: archetype.id, + }); + }) + ); + } + } catch (error) { + console.error("Error while deleting assessments:", error); + } + }; + + const onDeleteReviewSuccess = (name: string) => { + pushNotification({ + title: t("toastr.success.reviewDiscarded", { + application: name, + }), + variant: "success", + }); + }; + + const { mutate: deleteReview } = useDeleteReviewMutation( + onDeleteReviewSuccess, + onDeleteError + ); + + const discardReview = async (archetype: Archetype) => { + try { + if (archetype.review?.id) { + await deleteReview({ + id: archetype.review.id, + name: archetype.name, + }); + } + } catch (error) { + console.error("Error while deleting review:", error); + } + }; const tableControls = useLocalTableControls({ persistTo: "urlParams", @@ -319,6 +390,24 @@ const Archetypes: React.FC = () => { title: t("actions.edit"), onClick: () => setArchetypeToEdit(archetype), }, + ...(archetype?.assessments?.length + ? [ + { + title: t("actions.discardAssessment"), + onClick: () => + setAssessmentToDiscard(archetype), + }, + ] + : []), + ...(archetype?.review + ? [ + { + title: t("actions.discardReview"), + onClick: () => + setReviewToDiscard(archetype), + }, + ] + : []), { isSeparator: true }, { title: t("actions.delete"), @@ -386,6 +475,64 @@ const Archetypes: React.FC = () => { onClose={() => setArchetypeToDuplicate(null)} /> + + + The assessment(s) for {assessmentToDiscard?.name}{" "} + will be discarded. Do you wish to continue? + + + } + confirmBtnVariant={ButtonVariant.primary} + confirmBtnLabel={t("actions.continue")} + cancelBtnLabel={t("actions.cancel")} + onCancel={() => setAssessmentToDiscard(null)} + onClose={() => setAssessmentToDiscard(null)} + onConfirm={() => { + discardAssessment(assessmentToDiscard!); + setAssessmentToDiscard(null); + }} + /> + + + The review for {reviewToDiscard?.name} will be + discarded, as well as the review result. Do you wish to continue? + + + } + confirmBtnVariant={ButtonVariant.primary} + confirmBtnLabel={t("actions.continue")} + cancelBtnLabel={t("actions.cancel")} + onCancel={() => setReviewToDiscard(null)} + onClose={() => setReviewToDiscard(null)} + onConfirm={() => { + discardReview(reviewToDiscard!); + setReviewToDiscard(null); + }} + /> {/* Delete confirm modal */} { + const queryClient = useQueryClient(); const { isLoading, isSuccess, error, refetch, data } = useQuery({ initialData: [], queryKey: [ARCHETYPES_QUERY_KEY], queryFn: getArchetypes, refetchInterval: 5000, + onSuccess: () => { + queryClient.invalidateQueries([reviewsQueryKey]); + queryClient.invalidateQueries([assessmentsQueryKey]); + queryClient.invalidateQueries([assessmentsByItemIdQueryKey]); + }, + onError: (error: AxiosError) => console.log(error), });