From 0971b5ce471d975720533124516506c5a6a4b8f1 Mon Sep 17 00:00:00 2001 From: hodanoori <107242553+hodanoori@users.noreply.github.com> Date: Wed, 21 Aug 2024 10:04:11 +0200 Subject: [PATCH] feat(heureka): Add filtering and searching for issueMatches tab (#327) * Merge branch 'main' into renovate/npm-dependencies * feat(heureka): add filtering and search functionality for services tab * adjust store actions and update package-lock.json * CleanUp * CleanUp * add fetch filters value queries and change respective files * Automatic application of license header * fix (heureka): Update the package-lock.json (#164) * fix (heureka): Update the package-lock.json to be able to build it on mac * Update the version * feat(heureka): Substitute vulnerabilities with issues tab using the new issueMatch entity (#175) * fix(heureka): Add the left behind issueMatches.js file (#178) * fix(heureka): Fix merge conflicts in package-lock.json * Adjust relevant imports to use juno npm packages * Correct filter query * feat(heureka) : Add components view (#263) * feat(heureka) : add components view * Automatic application of license header * Install date-fns * feat(heureka): Add the total number of component versions and fix some errors * feat)heureka): Add pagination info to the components query * Using container and stack for styling and some cleanup * add componentInstances to componentVersion in the component query * Change the setting to put pagination on the right side of the page --------- Co-authored-by: License Bot * Add default filtering * feat(heureka): add filtering and search functionality for services tab * adjust store actions and update package-lock.json * Correct filter query * Automatic application of license header * feat(heureka): Add msg handling per tab and mem tabs to prevent unnecessary re-rendering (#281) * feat(heureka): Add error handling per tab and mem tabs to prevent unnecessary re-rendering * remove error considition to render Messages * move messages inside each tab and add resetMessages * remove unnecessary resetMessages and correct queries * feat(heureka): Refactor controllers and reduce redundencies (#295) * feat(heureka): Refactor controllers and reduce redundencies * Automatic application of license header --------- Co-authored-by: License Bot * Remove merge conflicts in utils * Remove not adjusted filter test * Resolve test errors * Refactoring store using slices * complete filtering for services tab * generalize filtering for all tabs * Activate the working filter select for services tab * Automatic application of license header * Adjust panel manager and services and issue details impl based on new store slices * CleanUp console.logs * Display search input only on issues tab for now * Display right icon on filter button * feat(heureka): Add filtering and searching for issueMatches tab * update version * Add a comment for search prop in filter slice * Change formatDate helper func and fix code review comments * Update package-lock.json * Remove filterLabels from package.json * Remove un-used helper func --------- Co-authored-by: License Bot --- heureka/ui/package-lock.json | 4 +- heureka/ui/package.json | 2 +- heureka/ui/src/App.js | 8 +- .../src/components/filters/FilterSelect.jsx | 2 +- .../IssueMatchesDetails.jsx} | 103 ++++++++++-------- .../IssueMatchesList.jsx} | 25 ++--- .../IssueMatchesListController.jsx} | 10 +- .../IssueMatchesListItem.jsx} | 20 +--- .../IssueMatchesTab.jsx} | 6 +- ...ServicesDetail.jsx => ServicesDetails.jsx} | 0 heureka/ui/src/components/shared/Helper.jsx | 7 ++ .../src/components/shared/ListController.jsx | 6 + .../ui/src/components/shared/PanelManager.jsx | 7 +- heureka/ui/src/components/tabs/TabContext.jsx | 8 +- heureka/ui/src/hooks/useAppStore.js | 4 +- heureka/ui/src/hooks/useQueryClientFn.js | 2 +- .../ui/src/lib/slices/createFiltersSlice.js | 17 ++- .../ui/src/lib/slices/createGlobalsSlice.js | 2 +- 18 files changed, 122 insertions(+), 111 deletions(-) rename heureka/ui/src/components/{issues/IssuesDetails.jsx => issueMatches/IssueMatchesDetails.jsx} (67%) rename heureka/ui/src/components/{issues/IssuesList.jsx => issueMatches/IssueMatchesList.jsx} (66%) rename heureka/ui/src/components/{issues/IssuesListController.jsx => issueMatches/IssueMatchesListController.jsx} (59%) rename heureka/ui/src/components/{issues/IssuesListItem.jsx => issueMatches/IssueMatchesListItem.jsx} (74%) rename heureka/ui/src/components/{issues/IssuesTab.jsx => issueMatches/IssueMatchesTab.jsx} (70%) rename heureka/ui/src/components/services/{ServicesDetail.jsx => ServicesDetails.jsx} (100%) diff --git a/heureka/ui/package-lock.json b/heureka/ui/package-lock.json index 4b8114fb..60a47e7e 100644 --- a/heureka/ui/package-lock.json +++ b/heureka/ui/package-lock.json @@ -1,12 +1,12 @@ { "name": "heureka", - "version": "2.4.0", + "version": "2.4.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "heureka", - "version": "2.4.0", + "version": "2.4.1", "license": "Apache-2.0", "dependencies": { "@cloudoperators/juno-communicator": "^2.2.11", diff --git a/heureka/ui/package.json b/heureka/ui/package.json index 01f0802e..fe52e8a9 100644 --- a/heureka/ui/package.json +++ b/heureka/ui/package.json @@ -1,6 +1,6 @@ { "name": "heureka", - "version": "2.4.0", + "version": "2.4.1", "author": "UI-Team", "contributors": [ "Hoda Noori, Arturo Reuschenbach Pucernau" diff --git a/heureka/ui/src/App.js b/heureka/ui/src/App.js index b2842364..89fea365 100644 --- a/heureka/ui/src/App.js +++ b/heureka/ui/src/App.js @@ -15,16 +15,10 @@ import { MessagesProvider } from "@cloudoperators/juno-messages-provider" import AsyncWorker from "./components/AsyncWorker" import TabContext from "./components/tabs/TabContext" import { ErrorBoundary } from "react-error-boundary" -import { - useGlobalsActions, - useFilterActions, - StoreProvider, -} from "./hooks/useAppStore" +import { useGlobalsActions, StoreProvider } from "./hooks/useAppStore" import PanelManager from "./components/shared/PanelManager" function App(props = {}) { - const { setLabels, setPredefinedFilters, setActivePredefinedFilter } = - useFilterActions() const { setEmbedded, setApiEndpoint } = useGlobalsActions() const preErrorClasses = ` custom-error-pre diff --git a/heureka/ui/src/components/filters/FilterSelect.jsx b/heureka/ui/src/components/filters/FilterSelect.jsx index 8430cf68..97d5cd0d 100644 --- a/heureka/ui/src/components/filters/FilterSelect.jsx +++ b/heureka/ui/src/components/filters/FilterSelect.jsx @@ -53,7 +53,7 @@ const FilterSelect = ({ entityName, isLoading }) => { const handleSearchChange = (value) => { // Debounce search term to avoid unnecessary re-renders const debouncedSearchTerm = setTimeout(() => { - setSearchTerm(value.target.value) + setSearchTerm(entityName, value.target.value) }, 500) return () => clearTimeout(debouncedSearchTerm) } diff --git a/heureka/ui/src/components/issues/IssuesDetails.jsx b/heureka/ui/src/components/issueMatches/IssueMatchesDetails.jsx similarity index 67% rename from heureka/ui/src/components/issues/IssuesDetails.jsx rename to heureka/ui/src/components/issueMatches/IssueMatchesDetails.jsx index f17b1cd0..4322c128 100644 --- a/heureka/ui/src/components/issues/IssuesDetails.jsx +++ b/heureka/ui/src/components/issueMatches/IssueMatchesDetails.jsx @@ -7,7 +7,6 @@ import React, { useMemo } from "react" import { Pill, Stack, - ContentHeading, DataGrid, DataGridCell, DataGridHeadCell, @@ -18,16 +17,20 @@ import { useGlobalsShowIssueDetail, } from "../../hooks/useAppStore" import { useQuery } from "@tanstack/react-query" -import { listOfCommaSeparatedObjs, severityString } from "../shared/Helper" +import { + listOfCommaSeparatedObjs, + severityString, + formatDate, +} from "../shared/Helper" import LoadElement from "../shared/LoadElement" -const IssuesDetails = () => { +const IssueMatchesDetails = () => { const showIssueDetail = useGlobalsShowIssueDetail() const queryClientFnReady = useGlobalsQueryClientFnReady() const issueElem = useQuery({ - queryKey: ["Issues", { filter: { id: [showIssueDetail] } }], + queryKey: ["IssueMatches", { filter: { id: [showIssueDetail] } }], enabled: !!queryClientFnReady, }) const issue = useMemo(() => { @@ -41,40 +44,77 @@ const IssuesDetails = () => { - Component Name + Primary Name + + + + + + + Target Remediation Date + + Status + + + + + - CVE + Severity - + - Component Version + Service Name + + + + + + + + Support Group Name - Services + Component Name - + + + + + + Component Version + + + @@ -88,10 +128,10 @@ const IssuesDetails = () => { (owner, i) => ( ) )} @@ -102,23 +142,6 @@ const IssuesDetails = () => { - - Support Group - - - - {listOfCommaSeparatedObjs( - issue?.componentInstance?.service?.supportGroups, - "name" - )} - - } - /> - - - Issue Variant @@ -126,18 +149,10 @@ const IssuesDetails = () => { {} - - - Issue Severity - - - {} - - ) } -export default IssuesDetails +export default IssueMatchesDetails diff --git a/heureka/ui/src/components/issues/IssuesList.jsx b/heureka/ui/src/components/issueMatches/IssueMatchesList.jsx similarity index 66% rename from heureka/ui/src/components/issues/IssuesList.jsx rename to heureka/ui/src/components/issueMatches/IssueMatchesList.jsx index b0e9d713..97695553 100644 --- a/heureka/ui/src/components/issues/IssuesList.jsx +++ b/heureka/ui/src/components/issueMatches/IssueMatchesList.jsx @@ -12,30 +12,26 @@ import { } from "@cloudoperators/juno-ui-components" import HintNotFound from "../shared/HintNotFound" import HintLoading from "../shared/HintLoading" -import IssuesListItem from "./IssuesListItem" +import IssueMatchesListItem from "./IssueMatchesListItem" -const IssuesList = ({ items, isLoading }) => { +const IssueMatchesList = ({ items, isLoading }) => { return ( <> {/* clickableTable Table allow changes the background by css when hovering or active*/} - + Primary Name - Type {/* Secondary Name */} Target Remediation Date Status Severity - Component Name - Component Version Service Name Support Group Name - Instance Count {isLoading && !items ? ( - - + + ) : ( @@ -43,13 +39,16 @@ const IssuesList = ({ items, isLoading }) => { {items?.length > 0 ? ( <> {items.map((item, index) => ( - + ))} ) : ( - - + + )} @@ -60,4 +59,4 @@ const IssuesList = ({ items, isLoading }) => { ) } -export default IssuesList +export default IssueMatchesList diff --git a/heureka/ui/src/components/issues/IssuesListController.jsx b/heureka/ui/src/components/issueMatches/IssueMatchesListController.jsx similarity index 59% rename from heureka/ui/src/components/issues/IssuesListController.jsx rename to heureka/ui/src/components/issueMatches/IssueMatchesListController.jsx index 7b00d0b4..01996c4a 100644 --- a/heureka/ui/src/components/issues/IssuesListController.jsx +++ b/heureka/ui/src/components/issueMatches/IssueMatchesListController.jsx @@ -4,17 +4,17 @@ */ import React from "react" -import IssuesList from "./IssuesList" +import IssueMatchesList from "./IssueMatchesList" import ListController from "../shared/ListController" -const IssuesListController = () => { +const IssueMatchesListController = () => { return ( ) } -export default IssuesListController +export default IssueMatchesListController diff --git a/heureka/ui/src/components/issues/IssuesListItem.jsx b/heureka/ui/src/components/issueMatches/IssueMatchesListItem.jsx similarity index 74% rename from heureka/ui/src/components/issues/IssuesListItem.jsx rename to heureka/ui/src/components/issueMatches/IssueMatchesListItem.jsx index 148bebdf..16667262 100644 --- a/heureka/ui/src/components/issues/IssuesListItem.jsx +++ b/heureka/ui/src/components/issueMatches/IssueMatchesListItem.jsx @@ -5,8 +5,7 @@ import React from "react" import { DataGridRow, DataGridCell } from "@cloudoperators/juno-ui-components" -import { listOfCommaSeparatedObjs } from "../shared/Helper" -import { DateTime } from "luxon" +import { listOfCommaSeparatedObjs, formatDate } from "../shared/Helper" import constants from "../shared/constants" import { useGlobalsActions, @@ -14,16 +13,11 @@ import { useGlobalsShowIssueDetail, } from "../../hooks/useAppStore" -const IssuesListItem = ({ item }) => { +const IssueMatchesListItem = ({ item }) => { const { setShowPanel, setShowIssueDetail } = useGlobalsActions() const showPanel = useGlobalsShowPanel() const showIssueDetail = useGlobalsShowIssueDetail() - const formatDate = (dateStr) => { - const dateObj = DateTime.fromISO(dateStr) - return dateObj.toFormat("yyyy.MM.dd.HH:mm:ss") - } - const handleClick = () => { if ( showPanel === constants.PANEL_ISSUE && @@ -50,7 +44,6 @@ const IssuesListItem = ({ item }) => { onClick={() => handleClick()} > {item?.node?.issue?.primaryName} - {item?.node?.issue?.type} {/* {listOfCommaSeparatedObjs( item?.node?.effectiveIssueVariants, @@ -62,12 +55,6 @@ const IssuesListItem = ({ item }) => { {item?.node?.status} {item?.node?.severity?.value} - - {item?.node?.componentInstance?.componentVersion?.component?.name} - - - {item?.node?.componentInstance?.componentVersion?.version} - {item?.node?.componentInstance?.service?.name} @@ -77,9 +64,8 @@ const IssuesListItem = ({ item }) => { "name" )} - {item?.node?.componentInstance?.count} ) } -export default IssuesListItem +export default IssueMatchesListItem diff --git a/heureka/ui/src/components/issues/IssuesTab.jsx b/heureka/ui/src/components/issueMatches/IssueMatchesTab.jsx similarity index 70% rename from heureka/ui/src/components/issues/IssuesTab.jsx rename to heureka/ui/src/components/issueMatches/IssueMatchesTab.jsx index 90c1fca4..f6bf3511 100644 --- a/heureka/ui/src/components/issues/IssuesTab.jsx +++ b/heureka/ui/src/components/issueMatches/IssueMatchesTab.jsx @@ -4,7 +4,7 @@ */ import React from "react" -import IssuesListController from "./IssuesListController" +import IssueMatchesListController from "./IssueMatchesListController" import Filters from "../filters/Filters" import { Messages, @@ -16,8 +16,8 @@ const IssuesTab = () => { <> - {/* */} - + + ) diff --git a/heureka/ui/src/components/services/ServicesDetail.jsx b/heureka/ui/src/components/services/ServicesDetails.jsx similarity index 100% rename from heureka/ui/src/components/services/ServicesDetail.jsx rename to heureka/ui/src/components/services/ServicesDetails.jsx diff --git a/heureka/ui/src/components/shared/Helper.jsx b/heureka/ui/src/components/shared/Helper.jsx index 440fc1c4..322f001e 100644 --- a/heureka/ui/src/components/shared/Helper.jsx +++ b/heureka/ui/src/components/shared/Helper.jsx @@ -2,6 +2,7 @@ * SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Greenhouse contributors * SPDX-License-Identifier: Apache-2.0 */ +import { DateTime } from "luxon" export const listOfCommaSeparatedObjs = (objs, prop) => { objs = objs?.edges || [] @@ -11,6 +12,12 @@ export const listOfCommaSeparatedObjs = (objs, prop) => { .join(", ") } +export const formatDate = (dateStr) => { + const dateObj = DateTime.fromISO(dateStr) + const dateFormat = { ...DateTime.DATE_MED } // Use the predefined DATE_MED format + return dateObj.toLocaleString(dateFormat) // Format the date using the predefined format +} + export const highestSeverity = (vulnerablities) => { const highest = vulnerablities.reduce((max, vulnerability) => { const currentScore = vulnerability?.node?.severity?.score diff --git a/heureka/ui/src/components/shared/ListController.jsx b/heureka/ui/src/components/shared/ListController.jsx index 4eb67a48..c04b8edb 100644 --- a/heureka/ui/src/components/shared/ListController.jsx +++ b/heureka/ui/src/components/shared/ListController.jsx @@ -12,6 +12,7 @@ import { useActiveFilters, usePredefinedFilters, useGlobalsActiveTab, + useSearchTerm, } from "../../hooks/useAppStore" import { Pagination, @@ -29,6 +30,7 @@ const ListController = ({ queryKey, entityName, ListComponent }) => { const activeTab = useGlobalsActiveTab() const activeFilters = useActiveFilters(entityName) const predefinedFilters = usePredefinedFilters(entityName) + const searchTerm = useSearchTerm(entityName) const { isLoading, data, error } = useQuery({ queryKey: [ @@ -38,6 +40,10 @@ const ListController = ({ queryKey, entityName, ListComponent }) => { filter: { ...activeFilters, ...predefinedFilters, + ...(entityName === "IssueMatches" && { + // Currently search is only available for IssueMatches entity. + search: Array.isArray(searchTerm) ? searchTerm : [searchTerm], // Ensure searchTerm is an array + }), }, }, ], diff --git a/heureka/ui/src/components/shared/PanelManager.jsx b/heureka/ui/src/components/shared/PanelManager.jsx index bd233f6d..189a7d2b 100644 --- a/heureka/ui/src/components/shared/PanelManager.jsx +++ b/heureka/ui/src/components/shared/PanelManager.jsx @@ -11,9 +11,9 @@ import { useGlobalsShowServiceDetail, useGlobalsShowIssueDetail, } from "../../hooks/useAppStore" -import ServicesDetail from "../services/ServicesDetail" +import ServicesDetail from "../services/ServicesDetails" import constants from "./constants" -import IssuesDetails from "../issues/IssuesDetails" +import IssueMatchesDetails from "../issueMatches/IssueMatchesDetails" const PanelManger = () => { const { setShowPanel, setShowServiceDetail, setShowIssueDetail } = @@ -46,8 +46,7 @@ const PanelManger = () => { > {showPanel === constants.PANEL_SERVICE && } - - {showPanel === constants.PANEL_ISSUE && } + {showPanel === constants.PANEL_ISSUE && } ) diff --git a/heureka/ui/src/components/tabs/TabContext.jsx b/heureka/ui/src/components/tabs/TabContext.jsx index ce2b48a4..25c13a82 100644 --- a/heureka/ui/src/components/tabs/TabContext.jsx +++ b/heureka/ui/src/components/tabs/TabContext.jsx @@ -13,7 +13,7 @@ import TabPanel from "./TabPanel" import { useGlobalsActions, useGlobalsActiveTab } from "../../hooks/useAppStore" import ServicesTab from "../services/ServicesTab" -import IssuesTab from "../issues/IssuesTab" +import IssueMatchesTab from "../issueMatches/IssueMatchesTab" import ComponentsTab from "../components/ComponentsTab" const TAB_CONFIG = [ @@ -24,10 +24,10 @@ const TAB_CONFIG = [ component: ServicesTab, }, { - label: "Issues", - value: "Issues", + label: "IssueMatches", + value: "IssueMatches", icon: "autoAwesomeMotion", - component: IssuesTab, + component: IssueMatchesTab, }, { label: "Components", diff --git a/heureka/ui/src/hooks/useAppStore.js b/heureka/ui/src/hooks/useAppStore.js index 8aae05c1..0a9c8fe3 100644 --- a/heureka/ui/src/hooks/useAppStore.js +++ b/heureka/ui/src/hooks/useAppStore.js @@ -86,8 +86,8 @@ export const useFilterLabels = (entityName) => useAppStore((state) => state.filters.labels[entityName] || []) export const useActiveFilters = (entityName) => useAppStore((state) => state.filters.activeFilters[entityName] || {}) -export const useSearchTerm = () => - useAppStore((state) => state.filters.searchTerm) +export const useSearchTerm = (entityName) => + useAppStore((state) => state.filters.search[entityName] || "") export const useFilterLabelValues = (entityName) => useAppStore((state) => state.filters.filterLabelValues[entityName] || {}) export const usePredefinedFilters = (entityName) => diff --git a/heureka/ui/src/hooks/useQueryClientFn.js b/heureka/ui/src/hooks/useQueryClientFn.js index 050006ed..69d6e816 100644 --- a/heureka/ui/src/hooks/useQueryClientFn.js +++ b/heureka/ui/src/hooks/useQueryClientFn.js @@ -31,7 +31,7 @@ const useQueryClientFn = () => { }, }) - queryClient.setQueryDefaults(["Issues"], { + queryClient.setQueryDefaults(["IssueMatches"], { queryFn: async ({ queryKey }) => { const [_key, options] = queryKey return await request(endpoint, issueMatchesQuery(), options) diff --git a/heureka/ui/src/lib/slices/createFiltersSlice.js b/heureka/ui/src/lib/slices/createFiltersSlice.js index 46ecf672..39f6b7b3 100644 --- a/heureka/ui/src/lib/slices/createFiltersSlice.js +++ b/heureka/ui/src/lib/slices/createFiltersSlice.js @@ -11,7 +11,7 @@ const initialFiltersState = { filterLabelValues: {}, // Filter label values for each entity: { entityName: { label1: ["val1", "val2", ...], label2: [...] } } predefinedFilters: {}, // Predefined filters for each entity: { entityName: [{ name: "filter1", matchers: {"label1": "regex1", ...}}, ...] } activePredefinedFilter: {}, // Active predefined filter for each entity: { entityName: "filterName" } - searchTerm: "", // Global search term used for full-text filtering + search: "", // Global search term used for full-text filtering: { entityName: ["searchTerm1", "searchTerm2", ...] } } const createFiltersSlice = (set, get) => ({ @@ -129,15 +129,20 @@ const createFiltersSlice = (set, get) => ({ ) }, - setSearchTerm: (searchTerm) => { + setSearchTerm: (entityName, searchTerm) => set( - produce((state) => { - state.filters.search = searchTerm + (state) => ({ + filters: { + ...state.filters, + search: { + ...state.filters.search, + [entityName]: searchTerm, // Set search term per entityName + }, + }, }), false, "filters.setSearchTerm" - ) - }, + ), }, }, }) diff --git a/heureka/ui/src/lib/slices/createGlobalsSlice.js b/heureka/ui/src/lib/slices/createGlobalsSlice.js index db59c5b9..6aac2b8f 100644 --- a/heureka/ui/src/lib/slices/createGlobalsSlice.js +++ b/heureka/ui/src/lib/slices/createGlobalsSlice.js @@ -23,7 +23,7 @@ const createGlobalsSlice = (set, get, options) => ({ first: 20, }, }, - Issues: { + IssueMatches: { queryOptions: { first: 20, },