From 84cc866ddeab95cb14bfa7d6ffa7313cd7ea6eb2 Mon Sep 17 00:00:00 2001 From: Ian Bolton Date: Wed, 8 Nov 2023 10:58:11 -0500 Subject: [PATCH] :bug: Identified risk refactor (#1521) - Drives the table from assessment data as requested Resolves https://issues.redhat.com/browse/MTA-1434? --------- Signed-off-by: ibolton336 --- .../adoption-plan/adoption-plan.tsx | 13 +- .../identified-risks-table-old.tsx | 210 ------------ .../identified-risks-table.tsx | 311 ++++++++++-------- client/src/app/pages/reports/reports.tsx | 8 +- 4 files changed, 183 insertions(+), 359 deletions(-) delete mode 100644 client/src/app/pages/reports/components/identified-risks-table/identified-risks-table-old.tsx diff --git a/client/src/app/pages/reports/components/adoption-plan/adoption-plan.tsx b/client/src/app/pages/reports/components/adoption-plan/adoption-plan.tsx index 558e7f5301..1608cba61c 100644 --- a/client/src/app/pages/reports/components/adoption-plan/adoption-plan.tsx +++ b/client/src/app/pages/reports/components/adoption-plan/adoption-plan.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useMemo } from "react"; +import React, { useEffect, useMemo } from "react"; import Measure from "react-measure"; import { useTranslation } from "react-i18next"; @@ -8,6 +8,7 @@ import { ChartAxis, ChartBar, ChartGroup, + ChartThemeColor, ChartVoronoiContainer, } from "@patternfly/react-charts"; @@ -16,9 +17,9 @@ import { StateError } from "@app/components/StateError"; import { PROPOSED_ACTION_LIST } from "@app/Constants"; import { ApplicationAdoptionPlan } from "@app/api/models"; import { getApplicationAdoptionPlan } from "@app/api/rest"; -import { ApplicationSelectionContext } from "../../application-selection-context"; import { NoApplicationSelectedEmptyState } from "../no-application-selected-empty-state"; import { useQuery } from "@tanstack/react-query"; +import { useFetchApplications } from "@app/queries/applications"; interface IChartData { applicationId: number; @@ -30,11 +31,7 @@ interface IChartData { export const AdoptionPlan: React.FC = () => { const { t } = useTranslation(); - - // Context - const { selectedItems: applications } = useContext( - ApplicationSelectionContext - ); + const { data: applications } = useFetchApplications(); const { data: adoptionPlan, @@ -107,7 +104,7 @@ export const AdoptionPlan: React.FC = () => { }} > diff --git a/client/src/app/pages/reports/components/identified-risks-table/identified-risks-table-old.tsx b/client/src/app/pages/reports/components/identified-risks-table/identified-risks-table-old.tsx deleted file mode 100644 index 9305fe1184..0000000000 --- a/client/src/app/pages/reports/components/identified-risks-table/identified-risks-table-old.tsx +++ /dev/null @@ -1,210 +0,0 @@ -import React, { useContext, useEffect, useMemo } from "react"; -import { useTranslation } from "react-i18next"; -import { - breakWord, - cellWidth, - ICell, - IRow, - TableVariant, -} from "@patternfly/react-table"; - -import { AppTableWithControls } from "@app/components/AppTableWithControls"; - -import { Application, AssessmentQuestionRisk } from "@app/api/models"; -// import { getAssessmentIdentifiedRisks } from "@app/api/rest"; - -import { ApplicationSelectionContext } from "../../application-selection-context"; -import { useLegacyPaginationState } from "@app/hooks/useLegacyPaginationState"; -import { - FilterCategory, - FilterToolbar, - FilterType, -} from "@app/components/FilterToolbar"; -import { useLegacyFilterState } from "@app/hooks/useLegacyFilterState"; -import { useQuery } from "@tanstack/react-query"; - -export interface ITableRowData { - category: string; - question: string; - answer: string; - applications: Application[]; -} - -export interface IIdentifiedRisksTableProps {} - -export const IdentifiedRisksTableOld: React.FC< - IIdentifiedRisksTableProps -> = () => { - // i18 - const { t } = useTranslation(); - - const { allItems: allApplications } = useContext(ApplicationSelectionContext); - - const { - data: assessmentQuestionRisks, - refetch: refreshTable, - error: fetchError, - isFetching, - } = useQuery({ - queryKey: ["assessmentQuestionRisks"], - queryFn: async () => - // ( - // await getAssessmentIdentifiedRisks( - // allApplications.length > 0 ? allApplications.map((f) => f.id!) : [] - // ) - // ).data, - [], - onError: (error) => console.log("error, ", error), - }); - - const tableData: ITableRowData[] = useMemo(() => { - return (assessmentQuestionRisks || []).map((risk) => ({ - ...risk, - applications: risk.applications.reduce((prev, current) => { - const exists = allApplications.find((f) => f.id === current); - return exists ? [...prev, exists] : [...prev]; - }, [] as Application[]), - })); - }, [assessmentQuestionRisks, allApplications]); - - useEffect(() => { - refreshTable(); - }, [allApplications, refreshTable]); - - // Table - const columns: ICell[] = [ - { - title: t("terms.category"), - transforms: [cellWidth(15)], - cellTransforms: [breakWord], - cellFormatters: [], - }, - { - title: t("terms.question"), - transforms: [cellWidth(35)], - cellTransforms: [breakWord], - cellFormatters: [], - }, - { - title: t("terms.answer"), - transforms: [cellWidth(35)], - cellTransforms: [breakWord], - cellFormatters: [], - }, - { - title: t("terms.application(s)"), - transforms: [cellWidth(15)], - cellTransforms: [breakWord], - cellFormatters: [], - }, - ]; - const filterCategories: FilterCategory< - ITableRowData, - "category" | "question" | "answer" | "applications" - >[] = [ - { - key: "category", - title: t("terms.category"), - type: FilterType.search, - placeholderText: - t("actions.filterBy", { - what: t("terms.category").toLowerCase(), - }) + "...", - getItemValue: (item) => { - return item?.category || ""; - }, - }, - { - key: "question", - title: t("terms.question"), - type: FilterType.search, - placeholderText: - t("actions.filterBy", { - what: t("terms.question").toLowerCase(), - }) + "...", - getItemValue: (item) => { - return item?.question || ""; - }, - }, - { - key: "answer", - title: t("terms.answer"), - type: FilterType.search, - placeholderText: - t("actions.filterBy", { - what: t("terms.answer").toLowerCase(), - }) + "...", - getItemValue: (item) => { - return item?.answer || ""; - }, - }, - { - key: "applications", - title: t("terms.name"), - type: FilterType.search, - placeholderText: - t("actions.filterBy", { - what: t("terms.name").toLowerCase(), - }) + "...", - getItemValue: (item) => { - const applicationNames = item?.applications.map((app) => app.name); - return applicationNames?.join("") || ""; - }, - }, - ]; - - const { filterValues, setFilterValues, filteredItems } = useLegacyFilterState( - tableData || [], - filterCategories - ); - - const { currentPageItems, paginationProps } = useLegacyPaginationState( - filteredItems, - 10 - ); - - const rows: IRow[] = []; - currentPageItems.forEach((item) => { - rows.push({ - cells: [ - { - title: item.category, - }, - { - title: item.question, - }, - { - title: item.answer, - }, - { - title: item.applications.map((f) => f.name).join(", "), - }, - ], - }); - }); - - const handleOnClearAllFilters = () => { - setFilterValues({}); - }; - - return ( - - } - /> - ); -}; diff --git a/client/src/app/pages/reports/components/identified-risks-table/identified-risks-table.tsx b/client/src/app/pages/reports/components/identified-risks-table/identified-risks-table.tsx index a3ee604cf4..74507382f5 100644 --- a/client/src/app/pages/reports/components/identified-risks-table/identified-risks-table.tsx +++ b/client/src/app/pages/reports/components/identified-risks-table/identified-risks-table.tsx @@ -1,161 +1,194 @@ -import React, { useContext } from "react"; -import { Table } from "@patternfly/react-table"; - +import React from "react"; +import { Table, Tbody, Td, Th, Thead, Tr } from "@patternfly/react-table"; +import { useFetchAssessments } from "@app/queries/assessments"; import { useTranslation } from "react-i18next"; +import { Ref } from "@app/api/models"; +import { NoDataEmptyState } from "@app/components/NoDataEmptyState"; import { - breakWord, - cellWidth, - ICell, - IRow, - TableVariant, -} from "@patternfly/react-table"; - -import { Application } from "@app/api/models"; -import { ApplicationSelectionContext } from "../../application-selection-context"; -import { useLegacyPaginationState } from "@app/hooks/useLegacyPaginationState"; -import { FilterCategory, FilterType } from "@app/components/FilterToolbar"; -import { useLegacyFilterState } from "@app/hooks/useLegacyFilterState"; -import { useFetchAssessments } from "@app/queries/assessments"; - -export interface ITableRowData { - category: string; - question: string; - answer: string; - applications: Application[]; -} + TableHeaderContentWithControls, + ConditionalTableBody, + TableRowContentWithControls, +} from "@app/components/TableControls"; +import { useLocalTableControls } from "@app/hooks/table-controls"; +import { SimplePagination } from "@app/components/SimplePagination"; +import { Toolbar, ToolbarContent, ToolbarItem } from "@patternfly/react-core"; export interface IIdentifiedRisksTableProps {} export const IdentifiedRisksTable: React.FC< IIdentifiedRisksTableProps > = () => { - // i18 const { t } = useTranslation(); - const { allItems: allApplications } = useContext(ApplicationSelectionContext); const { assessments } = useFetchAssessments(); - const tableData: any = []; - // Table - const columns: ICell[] = [ - { - title: t("terms.category"), - transforms: [cellWidth(15)], - cellTransforms: [breakWord], - cellFormatters: [], - }, - { - title: t("terms.question"), - transforms: [cellWidth(35)], - cellTransforms: [breakWord], - cellFormatters: [], - }, - { - title: t("terms.answer"), - transforms: [cellWidth(35)], - cellTransforms: [breakWord], - cellFormatters: [], - }, - { - title: t("terms.application(s)"), - transforms: [cellWidth(15)], - cellTransforms: [breakWord], - cellFormatters: [], - }, - ]; - const filterCategories: FilterCategory< - ITableRowData, - "category" | "question" | "answer" | "applications" - >[] = [ - { - key: "category", - title: t("terms.category"), - type: FilterType.search, - placeholderText: - t("actions.filterBy", { - what: t("terms.category").toLowerCase(), - }) + "...", - getItemValue: (item) => { - return item?.category || ""; - }, - }, - { - key: "question", - title: t("terms.question"), - type: FilterType.search, - placeholderText: - t("actions.filterBy", { - what: t("terms.question").toLowerCase(), - }) + "...", - getItemValue: (item) => { - return item?.question || ""; - }, - }, - { - key: "answer", - title: t("terms.answer"), - type: FilterType.search, - placeholderText: - t("actions.filterBy", { - what: t("terms.answer").toLowerCase(), - }) + "...", - getItemValue: (item) => { - return item?.answer || ""; - }, - }, - { - key: "applications", - title: t("terms.name"), - type: FilterType.search, - placeholderText: - t("actions.filterBy", { - what: t("terms.name").toLowerCase(), - }) + "...", - getItemValue: (item) => { - const applicationNames = item?.applications.map((app) => app.name); - return applicationNames?.join("") || ""; - }, - }, - ]; + interface ITableRowData { + assessmentName: string; + questionId: string; + section: string; + question: string; + answer: string; + applications: Ref[]; + } - const { filterValues, setFilterValues, filteredItems } = useLegacyFilterState( - tableData || [], - filterCategories - ); + const tableData: ITableRowData[] = []; - const { currentPageItems, paginationProps } = useLegacyPaginationState( - filteredItems, - 10 - ); + // ... + assessments.forEach((assessment) => { + assessment.sections.forEach((section) => { + section.questions.forEach((question) => { + question.answers.forEach((answer) => { + if (answer.selected) { + const itemId = [ + assessment.id, + section.order, + question.order, + answer.order, + ].join("."); - const rows: IRow[] = []; - currentPageItems.forEach((item) => { - rows.push({ - cells: [ - { - title: item.category, - }, - { - title: item.question, - }, - { - title: item.answer, - }, - { - title: item.applications.map((f) => f.name).join(", "), - }, - ], + const existingItemIndex = tableData.findIndex( + (item) => item.questionId === itemId + ); + + if (existingItemIndex !== -1) { + const existingItem = tableData[existingItemIndex]; + if ( + assessment.application && + !existingItem.applications + .map((app) => app.name) + .includes(assessment.application.name) + ) { + existingItem.applications.push(assessment.application); + } + } else { + tableData.push({ + section: section.name, + question: question.text, + answer: answer.text, + applications: assessment.application + ? [assessment.application] + : [], + assessmentName: assessment.questionnaire.name, + questionId: itemId, + }); + } + } + }); + }); }); }); - const handleOnClearAllFilters = () => { - setFilterValues({}); - }; + const tableControls = useLocalTableControls({ + idProperty: "questionId", + items: tableData || [], + columnNames: { + assessmentName: "Assessment Name", + section: "Section", + question: "Question", + answer: "Answer", + applications: "Applications", + }, + variant: "compact", + isPaginationEnabled: true, + isSortEnabled: true, + hasActionsColumn: false, + getSortValues: (item) => ({ + assessmentName: item.assessmentName, + section: item.section, + question: item.question, + answer: item.answer, + applications: item.applications.length, + }), + sortableColumns: ["assessmentName", "section", "question", "answer"], + }); + + const { + currentPageItems, + numRenderedColumns, + propHelpers: { + toolbarProps, + paginationToolbarItemProps, + paginationProps, + tableProps, + getThProps, + getTrProps, + getTdProps, + }, + } = tableControls; return ( -
+ <> + + + + + + + + + + + + + + + + + + + + + + + } + > + + {currentPageItems?.map((item, rowIndex) => { + return ( + + + + + + + + + + ); + })} + + +
+ Assessment name + SectionQuestionAnswer + Application +
+ {item.assessmentName} + + {item?.section ?? "N/A"} + + {item?.question ?? "N/A"} + + {item.answer ?? "N/A"} + + {item?.applications.length ?? "N/A"} +
+ ); }; diff --git a/client/src/app/pages/reports/reports.tsx b/client/src/app/pages/reports/reports.tsx index 0920666376..201b3bcadb 100644 --- a/client/src/app/pages/reports/reports.tsx +++ b/client/src/app/pages/reports/reports.tsx @@ -1,6 +1,7 @@ import React, { useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { + Bullseye, Button, ButtonVariant, Card, @@ -14,6 +15,8 @@ import { Popover, Select, SelectOption, + Split, + SplitItem, Stack, StackItem, Text, @@ -34,6 +37,7 @@ import { ApplicationSelectionContextProvider } from "./application-selection-con import { Landscape } from "./components/landscape"; import AdoptionCandidateTable from "./components/adoption-candidate-table/adoption-candidate-table"; import { AdoptionPlan } from "./components/adoption-plan"; +import { IdentifiedRisksTable } from "./components/identified-risks-table"; const ALL_QUESTIONNAIRES = -1; @@ -264,7 +268,7 @@ export const Reports: React.FC = () => { - {/* + setIsRiskCardOpen((current) => !current)} @@ -289,7 +293,7 @@ export const Reports: React.FC = () => { - */} +