From 7a6667bb7d6c4895c1caa079a8cddb1b3adc13c5 Mon Sep 17 00:00:00 2001 From: ibolton336 Date: Mon, 27 Nov 2023 13:45:24 -0500 Subject: [PATCH] Update count to reflect apps inheriting assessments from archetypes Port over initial filter values change Properly serialize & deserialize filters to match server side implementation Signed-off-by: ibolton336 --- .../MultiselectFilterControl.tsx | 2 -- .../filtering/useFilterState.ts | 5 ++- .../applications-table/applications-table.tsx | 24 +++++++++++--- .../identified-risks-table.tsx | 33 +++++++++++++++---- client/src/app/queries/assessments.ts | 32 ++++++++++-------- 5 files changed, 69 insertions(+), 27 deletions(-) diff --git a/client/src/app/components/FilterToolbar/MultiselectFilterControl.tsx b/client/src/app/components/FilterToolbar/MultiselectFilterControl.tsx index f79e677df7..627faa55fc 100644 --- a/client/src/app/components/FilterToolbar/MultiselectFilterControl.tsx +++ b/client/src/app/components/FilterToolbar/MultiselectFilterControl.tsx @@ -138,8 +138,6 @@ export const MultiselectFilterControl = ({ const input = textInput?.toLowerCase(); return renderSelectOptions((optionProps, groupName) => { - if (!input) return false; - // TODO: Checking for a filter match against the key or the value may not be desirable. return ( groupName?.toLowerCase().includes(input) || diff --git a/client/src/app/hooks/table-controls/filtering/useFilterState.ts b/client/src/app/hooks/table-controls/filtering/useFilterState.ts index 1b2b267d68..a7deb2bf16 100644 --- a/client/src/app/hooks/table-controls/filtering/useFilterState.ts +++ b/client/src/app/hooks/table-controls/filtering/useFilterState.ts @@ -44,6 +44,7 @@ export type IFilterStateArgs< * Definitions of the filters to be used (must include `getItemValue` functions for each category when performing filtering locally) */ filterCategories: FilterCategory[]; + initialFilterValues?: IFilterValues; } >; @@ -62,6 +63,8 @@ export const useFilterState = < IFeaturePersistenceArgs ): IFilterState => { const { isFilterEnabled, persistTo = "state", persistenceKeyPrefix } = args; + const initialFilterValues: IFilterValues = + (isFilterEnabled && args.initialFilterValues) || {}; // We won't need to pass the latter two type params here if TS adds support for partial inference. // See https://github.com/konveyor/tackle2-ui/issues/1456 @@ -71,7 +74,7 @@ export const useFilterState = < "filters" >({ isEnabled: !!isFilterEnabled, - defaultValue: {}, + defaultValue: initialFilterValues, persistenceKeyPrefix, // Note: For the discriminated union here to work without TypeScript getting confused // (e.g. require the urlParams-specific options when persistTo === "urlParams"), 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 7afc874fbd..91d86f667a 100644 --- a/client/src/app/pages/applications/applications-table/applications-table.tsx +++ b/client/src/app/pages/applications/applications-table/applications-table.tsx @@ -67,8 +67,11 @@ import { checkAccess } from "@app/utils/rbac-utils"; import WarningTriangleIcon from "@patternfly/react-icons/dist/esm/icons/warning-triangle-icon"; // Hooks -import { useQueryClient } from "@tanstack/react-query"; -import { useLocalTableControls } from "@app/hooks/table-controls"; +import { useIsFetching, useQueryClient } from "@tanstack/react-query"; +import { + deserializeFilterUrlParams, + useLocalTableControls, +} from "@app/hooks/table-controls"; // Queries import { Application, Assessment, Ref, Task } from "@app/api/models"; @@ -111,6 +114,8 @@ export const ApplicationsTable: React.FC = () => { const history = useHistory(); const token = keycloak.tokenParsed; + const isFetching = useIsFetching(); + const { pushNotification } = React.useContext(NotificationsContext); const { identities } = useFetchIdentities(); @@ -303,6 +308,11 @@ export const ApplicationsTable: React.FC = () => { } }; + const urlParams = new URLSearchParams(window.location.search); + const filters = urlParams.get("filters"); + + const deserializedFilterValues = deserializeFilterUrlParams({ filters }); + const tableControls = useLocalTableControls({ idProperty: "id", items: applications || [], @@ -321,6 +331,7 @@ export const ApplicationsTable: React.FC = () => { isActiveItemEnabled: true, sortableColumns: ["name", "businessService", "tags", "effort"], initialSort: { columnKey: "name", direction: "asc" }, + initialFilterValues: deserializedFilterValues, getSortValues: (app) => ({ name: app.name, businessService: app.businessService?.name || "", @@ -331,12 +342,17 @@ export const ApplicationsTable: React.FC = () => { { key: "name", title: t("terms.name"), - type: FilterType.search, + type: FilterType.multiselect, placeholderText: t("actions.filterBy", { what: t("terms.name").toLowerCase(), }) + "...", getItemValue: (item) => item?.name || "", + selectOptions: [ + ...new Set( + applications.map((application) => application.name).filter(Boolean) + ), + ].map((name) => ({ key: name, value: name })), }, { key: "archetypes", @@ -690,7 +706,7 @@ export const ApplicationsTable: React.FC = () => { return ( } >
{ if (!acc.find((item) => item?.id === current.id)) { - // Assuming 'id' is the unique identifier acc.push(current); } return acc; @@ -85,9 +89,7 @@ export const IdentifiedRisksTable: React.FC< section: section.name, question: question.text, answer: answer.text, - applications: assessment.application - ? [assessment.application] - : [], + applications: uniqueApplications ? uniqueApplications : [], assessmentName: assessment.questionnaire.name, questionId: itemId, }); @@ -200,7 +202,13 @@ export const IdentifiedRisksTable: React.FC< {item.answer ?? "N/A"} - {item?.applications.length ?? "N/A"} + {item?.applications.length ? ( + + {item.applications.length} + + ) : ( + "N/A" + )} @@ -212,3 +220,16 @@ export const IdentifiedRisksTable: React.FC< ); }; + +const getApplicationsUrl = (applications: Ref[]) => { + const filterValues = { + name: applications.map((app) => app.name), + }; + + const serializedParams = serializeFilterUrlParams(filterValues); + + const queryString = serializedParams.filters + ? `filters=${serializedParams.filters}` + : ""; + return `${Paths.applications}?${queryString}`; +}; diff --git a/client/src/app/queries/assessments.ts b/client/src/app/queries/assessments.ts index 8b47de1e07..3ec9b8925d 100644 --- a/client/src/app/queries/assessments.ts +++ b/client/src/app/queries/assessments.ts @@ -19,7 +19,6 @@ import { AxiosError } from "axios"; import { Assessment, AssessmentWithSectionOrder, - AssessmentWithArchetypeApplications, InitialAssessment, } from "@app/api/models"; import { QuestionnairesQueryKey } from "./questionnaires"; @@ -234,20 +233,25 @@ export const useFetchAssessmentsWithArchetypeApplications = () => { }); const isArchetypesLoading = archetypeQueries.some((query) => query.isLoading); - const archetypesData = archetypeQueries - .map((query) => query.data) - .filter(Boolean); - const assessmentsWithArchetypeApplications: AssessmentWithArchetypeApplications[] = - assessments.map((assessment, index) => { - const archetypeInfo = archetypesData[index]; - return { - ...assessment, - archetypeApplications: archetypeInfo.applications - ? archetypeInfo.applications - : [], - }; - }); + const archetypeApplicationsMap = new Map(); + archetypeQueries.forEach((query, index) => { + if (query.data && assessments[index].archetype?.id) { + archetypeApplicationsMap.set( + assessments[index]?.archetype?.id, + query.data.applications + ); + } + }); + + const assessmentsWithArchetypeApplications = assessments.map( + (assessment) => ({ + ...assessment, + archetypeApplications: assessment.archetype?.id + ? archetypeApplicationsMap.get(assessment.archetype.id) || [] + : [], + }) + ); return { assessmentsWithArchetypeApplications,