From 2b670bc80702c9e15afb3da377f06b8ad8fcd1cb Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Thu, 31 Oct 2024 08:55:03 +0100 Subject: [PATCH 1/5] Add Apply button to the Filter popup --- src/components/Assets/AssetsFilter/index.tsx | 72 +++++----- src/components/Assets/tracking.ts | 2 + .../GlobalErrorsFilters/index.tsx | 37 +++-- src/components/Errors/tracking.ts | 2 + .../IssuesFilter/IssuesFilter.stories.tsx | 4 +- .../FilterPanel}/IssuesFilter/index.tsx | 129 +++++++++++------- .../FilterPanel}/IssuesFilter/styles.ts | 2 +- .../FilterPanel}/IssuesFilter/tracking.ts | 3 +- .../FilterPanel}/IssuesFilter/types.ts | 2 +- .../Insights/InsightsCatalog/index.tsx | 2 +- .../Insights/Issues/useIssuesFilters.ts | 2 +- src/components/Insights/index.tsx | 2 +- src/components/common/FilterButton/index.tsx | 4 +- src/components/common/FilterButton/types.ts | 1 + src/components/common/FilterPopup/index.tsx | 20 ++- src/components/common/FilterPopup/types.ts | 1 + src/components/common/NewPopover/index.tsx | 7 +- src/components/common/NewPopover/types.ts | 1 + src/store/insights/insightsSlice.ts | 2 +- 19 files changed, 188 insertions(+), 107 deletions(-) rename src/components/Insights/{Issues => InsightsCatalog/FilterPanel}/IssuesFilter/IssuesFilter.stories.tsx (90%) rename src/components/Insights/{Issues => InsightsCatalog/FilterPanel}/IssuesFilter/index.tsx (62%) rename src/components/Insights/{Issues => InsightsCatalog/FilterPanel}/IssuesFilter/styles.ts (83%) rename src/components/Insights/{Issues => InsightsCatalog/FilterPanel}/IssuesFilter/tracking.ts (70%) rename src/components/Insights/{Issues => InsightsCatalog/FilterPanel}/IssuesFilter/types.ts (82%) diff --git a/src/components/Assets/AssetsFilter/index.tsx b/src/components/Assets/AssetsFilter/index.tsx index 08e8fba0c..108f6ec63 100644 --- a/src/components/Assets/AssetsFilter/index.tsx +++ b/src/components/Assets/AssetsFilter/index.tsx @@ -195,6 +195,12 @@ export const AssetsFilter = () => { isEnvironment(previousEnvironment) && previousEnvironment.id !== environment?.id ) { + setSelectedServices([]); + setSelectedEndpoints([]); + setSelectedConsumers([]); + setSelectedInternals([]); + setSelectedInsights([]); + const defaultFilters = { services: [], operations: [], @@ -227,6 +233,10 @@ export const AssetsFilter = () => { previousScope && previousScope.span?.spanCodeObjectId !== scopeSpanCodeObjectId ) { + setSelectedEndpoints([]); + setSelectedConsumers([]); + setSelectedInternals([]); + setSelectedInsights([]); const newFilters = { services: selectedServices, operations: [], @@ -337,39 +347,6 @@ export const AssetsFilter = () => { isServicesFilterEnabled ]); - // Apply filters when the popover is closed - useEffect(() => { - if (previousIsOpen && !isOpen) { - const filtersQuery = { - services: isServicesFilterEnabled ? selectedServices : [], - operations: [ - ...selectedEndpoints, - ...selectedConsumers, - ...selectedInternals - ], - insights: selectedInsights - }; - setFilters(filtersQuery); - setPersistedFilters(filtersQuery); - if (isServicesFilterEnabled) { - setGloballySelectedServices(filtersQuery.services); - } - sendTrackingEvent(trackingEvents.FILTER_APPLIED); - } - }, [ - previousIsOpen, - isOpen, - selectedConsumers, - selectedEndpoints, - selectedInsights, - selectedInternals, - selectedServices, - setPersistedFilters, - setGloballySelectedServices, - isServicesFilterEnabled, - setFilters - ]); - const handleClearFiltersButtonClick = () => { getData({ ...query, @@ -465,7 +442,35 @@ export const AssetsFilter = () => { sendUserActionTrackingEvent( trackingEvents.FILTERS_POPUP_CLOSE_BUTTON_CLICKED ); + + // TODO: discard changes + }; + + const handleApplyButtonClick = () => { + sendUserActionTrackingEvent( + trackingEvents.FILTERS_POPUP_APPLY_FILTERS_BUTTON_CLICKED + ); + + const filtersQuery = { + services: isServicesFilterEnabled ? selectedServices : [], + operations: [ + ...selectedEndpoints, + ...selectedConsumers, + ...selectedInternals + ], + insights: selectedInsights + }; + + setFilters(filtersQuery); + setPersistedFilters(filtersQuery); + + if (isServicesFilterEnabled) { + setGloballySelectedServices(filtersQuery.services); + } + + sendTrackingEvent(trackingEvents.FILTER_APPLIED); }; + const handleOnStateChange = (state: boolean) => { setIsOpen(state); }; @@ -530,6 +535,7 @@ export const AssetsFilter = () => { return ( options.filter((x) => x.selected).length > 0 ? placeholder : "All"; export const GlobalErrorsFilters = () => { - const { environment, backendInfo } = useConfigSelector(); - + const { + environment, + backendInfo, + selectedServices: globallySelectedServices + } = useConfigSelector(); const areGlobalErrorsCriticalityAndUnhandledFiltersEnabled = getFeatureFlagValue( backendInfo, FeatureFlag.ARE_GLOBAL_ERRORS_CRITICALITY_AND_UNHANDLED_FILTERS_ENABLED ); - const { globalErrorsFilters, globalErrorsSelectedFilters } = useErrorsSelector(); - const { setGlobalErrorsFilters, setGlobalErrorsSelectedFilters } = - useStore.getState(); + const { + setGlobalErrorsFilters, + setGlobalErrorsSelectedFilters, + setSelectedServices: setGloballySelectedServices + } = useStore.getState(); const { services, endpoints, errorTypes } = globalErrorsFilters; const environmentId = environment?.id; const [lastChangedFilter, setLastChangedFilter] = useState< ErrorFilter | undefined >(undefined); const [selectedServices, setSelectedServices] = useState( - globalErrorsSelectedFilters.services + globallySelectedServices ?? [] ); const [selectedEndpoints, setSelectedEndpoints] = useState( globalErrorsSelectedFilters.endpoints @@ -160,6 +165,8 @@ export const GlobalErrorsFilters = () => { globalErrorsSelectedFilters.errorTypes ]); + // TODO: clear filters on environment change. but keep the selected services + const handleServicesChange = (value: string | string[]) => { sendUserActionTrackingEvent( trackingEvents.GLOBAL_ERRORS_VIEW_SERVICES_FILTER_CHANGED @@ -408,12 +415,22 @@ export const GlobalErrorsFilters = () => { criticalities: selectedCriticalities, handlingTypes: selectedHandlingTypes }); + setGloballySelectedServices(selectedServices); }; const handleClose = () => { sendUserActionTrackingEvent( trackingEvents.GLOBAL_ERRORS_VIEW_FILTERS_CLOSE_BUTTON_CLICKED ); + + // TODO: discard changes + }; + + const handleApply = () => { + sendUserActionTrackingEvent( + trackingEvents.GLOBAL_ERRORS_VIEW_FILTERS_APPLY_FILTERS_BUTTON_CLICKED + ); + applyFilters(); }; @@ -436,19 +453,13 @@ export const GlobalErrorsFilters = () => { selectedHandlingTypes.length ].filter((x) => x > 0).length; - const handlePopupOpenStateChange = (isOpen: boolean) => { - if (!isOpen) { - applyFilters(); - } - }; - return ( ); diff --git a/src/components/Errors/tracking.ts b/src/components/Errors/tracking.ts index ce1a48155..f0e14ffa2 100644 --- a/src/components/Errors/tracking.ts +++ b/src/components/Errors/tracking.ts @@ -42,6 +42,8 @@ export const trackingEvents = addPrefix( "global errors view unhandled filter changed", GLOBAL_ERRORS_VIEW_FILTERS_CLOSE_BUTTON_CLICKED: "global errors view filters close button clicked", + GLOBAL_ERRORS_VIEW_FILTERS_APPLY_FILTERS_BUTTON_CLICKED: + "global errors view filters apply filters button clicked", GLOBAL_ERRORS_VIEW_CLEAR_FILTERS_BUTTON_CLICKED: "global errors view clear filters button clicked", ERROR_CARD_SOURCE_LINK_CLICKED: "error card source link clicked", diff --git a/src/components/Insights/Issues/IssuesFilter/IssuesFilter.stories.tsx b/src/components/Insights/InsightsCatalog/FilterPanel/IssuesFilter/IssuesFilter.stories.tsx similarity index 90% rename from src/components/Insights/Issues/IssuesFilter/IssuesFilter.stories.tsx rename to src/components/Insights/InsightsCatalog/FilterPanel/IssuesFilter/IssuesFilter.stories.tsx index 096bd6911..7af58c787 100644 --- a/src/components/Insights/Issues/IssuesFilter/IssuesFilter.stories.tsx +++ b/src/components/Insights/InsightsCatalog/FilterPanel/IssuesFilter/IssuesFilter.stories.tsx @@ -1,11 +1,11 @@ import { Meta, StoryObj } from "@storybook/react"; import { IssuesFilter } from "."; -import { actions } from "../actions"; +import { actions } from "../../../Issues/actions"; // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction const meta: Meta = { - title: "Insights/Issues/IssuesFilter", + title: "Insights/Issues/InsightsCatalog/FilterPanel/IssuesFilter", component: IssuesFilter, parameters: { // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout diff --git a/src/components/Insights/Issues/IssuesFilter/index.tsx b/src/components/Insights/InsightsCatalog/FilterPanel/IssuesFilter/index.tsx similarity index 62% rename from src/components/Insights/Issues/IssuesFilter/index.tsx rename to src/components/Insights/InsightsCatalog/FilterPanel/IssuesFilter/index.tsx index 69da1203e..acfc786e9 100644 --- a/src/components/Insights/Issues/IssuesFilter/index.tsx +++ b/src/components/Insights/InsightsCatalog/FilterPanel/IssuesFilter/index.tsx @@ -1,37 +1,42 @@ -import { useEffect, useMemo } from "react"; -import { getFeatureFlagValue } from "../../../../featureFlags"; -import { usePrevious } from "../../../../hooks/usePrevious"; -import { useConfigSelector } from "../../../../store/config/useConfigSelector"; -import { useInsightsSelector } from "../../../../store/insights/useInsightsSelector"; -import { useStore } from "../../../../store/useStore"; -import { FeatureFlag } from "../../../../types"; -import { sendUserActionTrackingEvent } from "../../../../utils/actions/sendUserActionTrackingEvent"; -import { getInsightTypeInfo } from "../../../../utils/getInsightTypeInfo"; -import { FilterPopup } from "../../../common/FilterPopup"; -import { EyeIcon } from "../../../common/icons/12px/EyeIcon"; -import { FourPointedStarIcon } from "../../../common/icons/12px/FourPointedStarIcon"; -import { WrenchIcon } from "../../../common/icons/12px/WrenchIcon"; -import { WarningTriangleIcon } from "../../../common/icons/WarningTriangleIcon"; -import { IconProps } from "../../../common/icons/types"; -import { SelectItem } from "../../../common/v3/Select/types"; -import { InsightFilterType } from "../../InsightsCatalog/types"; -import { useIssuesFilters } from "../useIssuesFilters"; +import { useEffect, useState } from "react"; +import { getFeatureFlagValue } from "../../../../../featureFlags"; +import { usePrevious } from "../../../../../hooks/usePrevious"; +import { useConfigSelector } from "../../../../../store/config/useConfigSelector"; +import { useInsightsSelector } from "../../../../../store/insights/useInsightsSelector"; +import { useStore } from "../../../../../store/useStore"; +import { FeatureFlag } from "../../../../../types"; +import { sendUserActionTrackingEvent } from "../../../../../utils/actions/sendUserActionTrackingEvent"; +import { getInsightTypeInfo } from "../../../../../utils/getInsightTypeInfo"; +import { FilterPopup } from "../../../../common/FilterPopup"; +import { EyeIcon } from "../../../../common/icons/12px/EyeIcon"; +import { FourPointedStarIcon } from "../../../../common/icons/12px/FourPointedStarIcon"; +import { WrenchIcon } from "../../../../common/icons/12px/WrenchIcon"; +import { WarningTriangleIcon } from "../../../../common/icons/WarningTriangleIcon"; +import { IconProps } from "../../../../common/icons/types"; +import { SelectItem } from "../../../../common/v3/Select/types"; +import { useIssuesFilters } from "../../../Issues/useIssuesFilters"; +import { InsightFilterType } from "../../types"; import * as s from "./styles"; import { trackingEvents } from "./tracking"; export const IssuesFilter = () => { const { filteredInsightTypes, filters } = useInsightsSelector(); - const { selectedServices, backendInfo, scope } = useConfigSelector(); const { - setSelectedServices, + selectedServices: globallySelectedServices, + backendInfo, + scope + } = useConfigSelector(); + const { + setSelectedServices: setGloballySelectedServices, setInsightsFilteredInsightTypes: setFilteredInsightTypes, setInsightsFilters: setFilters } = useStore.getState(); - const isCriticalOnly = useMemo( - () => filters.includes("criticality"), - [filters] + const [isCriticalOnly, setIsCriticalOnly] = useState( + filters.includes("criticality") + ); + const [isUnreadOnly, setIsUnreadOnly] = useState( + filters.includes("unread") ); - const isUnreadOnly = useMemo(() => filters.includes("unread"), [filters]); const { data } = useIssuesFilters(); const previousData = usePrevious(data); const scopeSpanCodeObjectId = scope?.span?.spanCodeObjectId; @@ -39,29 +44,30 @@ export const IssuesFilter = () => { Boolean( getFeatureFlagValue(backendInfo, FeatureFlag.ARE_ISSUES_FILTERS_ENABLED) ) && !scopeSpanCodeObjectId; - const filteredServices = useMemo( - () => selectedServices ?? [], - [selectedServices] + const [selectedServices, setSelectedServices] = useState( + globallySelectedServices ?? [] ); + const [selectedInsightTypes, setSelectedInsightTypes] = + useState(filteredInsightTypes); useEffect(() => { if (previousData && previousData !== data) { - if (filteredInsightTypes.length > 0) { - const newSelection = filteredInsightTypes.filter((e) => + if (selectedInsightTypes.length > 0) { + const newSelection = selectedInsightTypes.filter((e) => Boolean(data?.issueTypeFilters.find((x) => x.name === e && x.enabled)) ); - if (newSelection.length !== filteredInsightTypes.length) { - setFilteredInsightTypes(newSelection); + if (newSelection.length !== selectedInsightTypes.length) { + setSelectedInsightTypes(newSelection); } } - if (isServicesFilterEnabled && filteredServices.length > 0) { - const newSelection = filteredServices.filter((e) => + if (isServicesFilterEnabled && selectedServices.length > 0) { + const newSelection = selectedServices.filter((e) => Boolean(data?.services?.find((x) => x === e)) ); - if (newSelection.length !== filteredServices.length) { + if (newSelection.length !== selectedServices.length) { setSelectedServices(newSelection); } } @@ -69,23 +75,42 @@ export const IssuesFilter = () => { }, [ previousData, data, - filteredInsightTypes, - setFilteredInsightTypes, + selectedInsightTypes, + setSelectedInsightTypes, filters, setSelectedServices, - filteredServices, + selectedServices, isServicesFilterEnabled ]); + const handleApplyFiltersButtonClick = () => { + sendUserActionTrackingEvent(trackingEvents.APPLY_FILTERS_BUTTON_CLICKED); + + setFilteredInsightTypes(selectedInsightTypes); + setFilters([ + ...(isCriticalOnly ? ["criticality"] : []), + ...(isUnreadOnly ? ["unread"] : []) + ] as InsightFilterType[]); + if (isServicesFilterEnabled) { + setGloballySelectedServices(selectedServices); + } + }; + const handleClearFiltersButtonClick = () => { sendUserActionTrackingEvent( trackingEvents.CLEAR_ALL_FILTERS_BUTTON_CLICKED ); + + setSelectedServices([]); + setFilteredInsightTypes([]); + setIsCriticalOnly(false); + setIsUnreadOnly(false); + setFilteredInsightTypes([]); setFilters([]); if (isServicesFilterEnabled) { - setSelectedServices([]); + setGloballySelectedServices([]); } }; @@ -107,6 +132,8 @@ export const IssuesFilter = () => { const handleCloseButtonClick = () => { sendUserActionTrackingEvent(trackingEvents.CLOSE_FILTER_DIALOG_CLICKED); + + // TODO: discard changes }; const handleToggleFilterChange = ( @@ -116,14 +143,21 @@ export const IssuesFilter = () => { sendUserActionTrackingEvent(trackingEvents.FILTER_OPTION_SELECTED, { filterType }); + + if (filterType === "criticality") { + setIsCriticalOnly(value === filterType); + } + + if (filterType === "unread") { + setIsUnreadOnly(value === filterType); + } + const selectedFilters = new Set(filters); if (value === filterType) { selectedFilters.add(filterType); } else { selectedFilters.delete(filterType); } - - setFilters(Array.from(selectedFilters)); }; const servicesFilterOptions: SelectItem[] = @@ -131,15 +165,15 @@ export const IssuesFilter = () => { value: entry, label: entry, enabled: true, - selected: filteredServices.includes(entry) + selected: selectedServices.includes(entry) })) ?? []; const issueTypesFilterOptions: SelectItem[] = data?.issueTypeFilters?.map((entry) => ({ value: entry.name, label: getInsightTypeInfo(entry.name)?.label ?? entry.name, - enabled: entry.enabled || filteredInsightTypes.includes(entry.name), - selected: filteredInsightTypes.includes(entry.name) && entry.enabled + enabled: entry.enabled || selectedInsightTypes.includes(entry.name), + selected: selectedInsightTypes.includes(entry.name) && entry.enabled })) ?? []; const criticalityFilterOptions: SelectItem[] = [ @@ -177,8 +211,8 @@ export const IssuesFilter = () => { const readStatusFilterPlaceholder = readStatusFilterOptions.find((x) => x.selected)?.label ?? "All"; const selectedFiltersCount = - filteredInsightTypes.length + - (isServicesFilterEnabled ? filteredServices.length : 0) + + selectedInsightTypes.length + + (isServicesFilterEnabled ? selectedServices.length : 0) + (isCriticalOnly ? 1 : 0) + (isUnreadOnly ? 1 : 0); @@ -190,7 +224,7 @@ export const IssuesFilter = () => { key={"issues"} items={issueTypesFilterOptions} onChange={handleIssueTypesChange} - placeholder={filteredInsightTypes.length > 0 ? "Issues" : "All"} + placeholder={selectedInsightTypes.length > 0 ? "Issues" : "All"} multiselect={true} icon={(props: IconProps) => ( @@ -245,7 +279,7 @@ export const IssuesFilter = () => { key={"services"} items={servicesFilterOptions} onChange={handleServiceChange} - placeholder={filteredServices.length > 0 ? "Services" : "All"} + placeholder={selectedServices.length > 0 ? "Services" : "All"} multiselect={true} icon={(props: IconProps) => ( @@ -260,6 +294,7 @@ export const IssuesFilter = () => { return ( ( 0} $isActive={isActive} + onClick={onClick} > {title} diff --git a/src/components/common/FilterButton/types.ts b/src/components/common/FilterButton/types.ts index 76a48a685..e5c0b5d13 100644 --- a/src/components/common/FilterButton/types.ts +++ b/src/components/common/FilterButton/types.ts @@ -4,6 +4,7 @@ export interface FilterButtonProps { selectedCount?: number; showCount?: boolean; isActive: boolean; + onClick?: () => void; } export interface ButtonProps { diff --git a/src/components/common/FilterPopup/index.tsx b/src/components/common/FilterPopup/index.tsx index 782841fed..1417832af 100644 --- a/src/components/common/FilterPopup/index.tsx +++ b/src/components/common/FilterPopup/index.tsx @@ -3,6 +3,7 @@ import { usePrevious } from "../../../hooks/usePrevious"; import { FilterButton } from "../FilterButton"; import { CrossIcon } from "../icons/16px/CrossIcon"; import { NewPopover } from "../NewPopover"; +import { NewButton } from "../v3/NewButton"; import * as s from "./styles"; import { FilterPopupProps } from "./types"; @@ -12,7 +13,8 @@ export const FilterPopup = ({ title, selectedFiltersCount, filters, - onStateChange + onStateChange, + onApply }: FilterPopupProps) => { const [isOpen, setIsOpen] = useState(false); const previousIsOpen = usePrevious(isOpen); @@ -23,14 +25,24 @@ export const FilterPopup = ({ } }, [isOpen, previousIsOpen, onStateChange]); + const handleApplyButtonClick = () => { + setIsOpen(false); + onApply(); + }; + const handleCloseButtonClick = () => { setIsOpen(false); onClose(); }; + const handleFilterButtonClick = () => { + setIsOpen(!isOpen); + }; + return ( @@ -39,7 +51,6 @@ export const FilterPopup = ({ - {filters.map((x) => ( {x.title} @@ -47,6 +58,10 @@ export const FilterPopup = ({ ))} + diff --git a/src/components/common/FilterPopup/types.ts b/src/components/common/FilterPopup/types.ts index 259210b2a..147cf8a06 100644 --- a/src/components/common/FilterPopup/types.ts +++ b/src/components/common/FilterPopup/types.ts @@ -1,6 +1,7 @@ import { ReactNode } from "react"; export interface FilterPopupProps { + onApply: () => void; onClose: () => void; title: string; onClearAll: () => void; diff --git a/src/components/common/NewPopover/index.tsx b/src/components/common/NewPopover/index.tsx index 0e34cb0df..67288d387 100644 --- a/src/components/common/NewPopover/index.tsx +++ b/src/components/common/NewPopover/index.tsx @@ -74,7 +74,8 @@ export const NewPopover = ({ children, sameWidth, width, - content + content, + closeOnOutsidePress }: PopoverProps) => { const arrowRef = useRef(null); const theme = useTheme(); @@ -102,7 +103,9 @@ export const NewPopover = ({ }); const click = useClick(context); - const dismiss = useDismiss(context); + const dismiss = useDismiss(context, { + outsidePress: closeOnOutsidePress + }); const { getReferenceProps, getFloatingProps } = useInteractions([ ...(useClickInteraction === false ? [] : [click]), diff --git a/src/components/common/NewPopover/types.ts b/src/components/common/NewPopover/types.ts index cb6235754..d6416042c 100644 --- a/src/components/common/NewPopover/types.ts +++ b/src/components/common/NewPopover/types.ts @@ -12,4 +12,5 @@ export interface PopoverProps { width?: number | string; sameWidth?: boolean; useClickInteraction?: boolean; + closeOnOutsidePress?: boolean; } diff --git a/src/store/insights/insightsSlice.ts b/src/store/insights/insightsSlice.ts index d8ed3c787..2147cd2c2 100644 --- a/src/store/insights/insightsSlice.ts +++ b/src/store/insights/insightsSlice.ts @@ -3,12 +3,12 @@ import { Sorting, SORTING_ORDER } from "../../components/common/SortingSelector/types"; +import { IssuesFiltersData } from "../../components/Insights/InsightsCatalog/FilterPanel/IssuesFilter/types"; import { InsightFilterType, SORTING_CRITERION, ViewMode } from "../../components/Insights/InsightsCatalog/types"; -import { IssuesFiltersData } from "../../components/Insights/Issues/IssuesFilter/types"; import { InsightsData, InsightViewType } from "../../components/Insights/types"; interface InsightsState { From 28843cb870081f7ad1b08ea72782004126ac3b44 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Tue, 5 Nov 2024 08:56:15 +0100 Subject: [PATCH 2/5] Discard filters changes on popup close --- src/components/Assets/AssetList/index.tsx | 8 +- src/components/Assets/AssetTypeList/index.tsx | 24 ++- src/components/Assets/AssetTypeList/types.ts | 11 + src/components/Assets/AssetsFilter/index.tsx | 199 ++++++++++++------ src/components/Assets/tracking.ts | 4 +- src/components/Assets/utils.tsx | 8 +- .../GlobalErrorsFilters/index.tsx | 101 +++++++-- .../FilterPanel/IssuesFilter/index.tsx | 140 ++++++++---- .../FilterPanel/IssuesFilter/tracking.ts | 3 +- .../Insights/Issues/useIssuesFilters.ts | 3 +- src/components/common/FilterPopup/index.tsx | 21 +- src/components/common/FilterPopup/types.ts | 4 +- src/store/assets/assetsSlice.ts | 22 +- 13 files changed, 390 insertions(+), 158 deletions(-) diff --git a/src/components/Assets/AssetList/index.tsx b/src/components/Assets/AssetList/index.tsx index d5a9bae19..9eab20305 100644 --- a/src/components/Assets/AssetList/index.tsx +++ b/src/components/Assets/AssetList/index.tsx @@ -144,7 +144,13 @@ export const AssetList = ({ sortOrder: sorting.order, scopedSpanCodeObjectId: scopeSpanCodeObjectId, ...(search.length > 0 ? { displayName: search } : {}), - ...(scopeSpanCodeObjectId ? { ...filters, services: [] } : filters), + insights: filters.insights, + operations: [ + ...filters.endpoints, + ...filters.consumers, + ...filters.internals + ], + services: scopeSpanCodeObjectId ? [] : filters.services, directOnly: viewMode === "children" } }), diff --git a/src/components/Assets/AssetTypeList/index.tsx b/src/components/Assets/AssetTypeList/index.tsx index 64143134a..afcfedad7 100644 --- a/src/components/Assets/AssetTypeList/index.tsx +++ b/src/components/Assets/AssetTypeList/index.tsx @@ -2,6 +2,7 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { DigmaMessageError } from "../../../api/types"; import { dispatcher } from "../../../dispatcher"; import { usePrevious } from "../../../hooks/usePrevious"; +import { AssetsFilters } from "../../../store/assets/assetsSlice"; import { useAssetsSelector } from "../../../store/assets/useAssetsSelector"; import { useConfigSelector } from "../../../store/config/useConfigSelector"; import { useStore } from "../../../store/useStore"; @@ -12,7 +13,6 @@ import { SCOPE_CHANGE_EVENTS } from "../../../types"; import { changeScope } from "../../../utils/actions/changeScope"; import { sendUserActionTrackingEvent } from "../../../utils/actions/sendUserActionTrackingEvent"; import { ChildIcon } from "../../common/icons/30px/ChildIcon"; -import { AssetFilterQuery } from "../AssetsFilter/types"; import { ViewMode } from "../AssetsViewScopeConfiguration/types"; import { NoDataMessage } from "../NoDataMessage"; import { actions } from "../actions"; @@ -23,7 +23,8 @@ import * as s from "./styles"; import { AssetCategoriesData, AssetCategoryData, - AssetTypeListProps + AssetTypeListProps, + GetAssetCategoriesDataPayload } from "./types"; const REFRESH_INTERVAL = 10 * 1000; // in milliseconds @@ -40,23 +41,26 @@ export const ASSET_TYPE_IDS = [ ]; const getData = ( - filters: AssetFilterQuery, + filters: AssetsFilters, searchQuery: string, viewMode: ViewMode, scopeSpanCodeObjectId?: string ) => { - window.sendMessageToDigma({ + window.sendMessageToDigma({ action: actions.GET_CATEGORIES_DATA, payload: { query: { directOnly: viewMode === "children", scopedSpanCodeObjectId: scopeSpanCodeObjectId, - ...(scopeSpanCodeObjectId - ? { - ...filters, - services: [] - } - : filters), + ...{ + insights: filters.insights, + operations: [ + ...filters.endpoints, + ...filters.consumers, + ...filters.internals + ], + services: scopeSpanCodeObjectId ? [] : filters.services + }, ...(searchQuery.length > 0 ? { displayName: searchQuery } : {}) } } diff --git a/src/components/Assets/AssetTypeList/types.ts b/src/components/Assets/AssetTypeList/types.ts index b4f2e3b07..457766206 100644 --- a/src/components/Assets/AssetTypeList/types.ts +++ b/src/components/Assets/AssetTypeList/types.ts @@ -21,3 +21,14 @@ export interface AssetCategoryData { label?: string; icon?: MemoExoticComponent<(props: IconProps) => JSX.Element>; } + +export interface GetAssetCategoriesDataPayload { + query: { + directOnly: boolean; + scopedSpanCodeObjectId?: string; + services: string[]; + operations: string[]; + insights: string[]; + displayName?: string; + }; +} diff --git a/src/components/Assets/AssetsFilter/index.tsx b/src/components/Assets/AssetsFilter/index.tsx index 108f6ec63..bf613cd89 100644 --- a/src/components/Assets/AssetsFilter/index.tsx +++ b/src/components/Assets/AssetsFilter/index.tsx @@ -1,4 +1,10 @@ -import { ComponentType, useEffect, useMemo, useState } from "react"; +import { + ComponentType, + useCallback, + useEffect, + useMemo, + useState +} from "react"; import { dispatcher } from "../../../dispatcher"; import { getFeatureFlagValue } from "../../../featureFlags"; import { usePersistence } from "../../../hooks/usePersistence"; @@ -92,8 +98,8 @@ export const AssetsFilter = () => { setAssetsFilters: setFilters, setSelectedServices: setGloballySelectedServices } = useStore.getState(); - const [isOpen, setIsOpen] = useState(false); - const previousIsOpen = usePrevious(isOpen); + const [isPopupOpen, setIsPopupOpen] = useState(false); + const previousIsOpen = usePrevious(isPopupOpen); const { selectedServices: globallySelectedServices, environment, @@ -122,8 +128,12 @@ export const AssetsFilter = () => { const query = useMemo( () => ({ services: isServicesFilterEnabled ? selectedServices : [], - operations: filters.operations, - insights: filters.insights as InsightType[], + operations: [ + ...selectedEndpoints, + ...selectedConsumers, + ...selectedInternals + ], + insights: selectedInsights, viewMode: areExtendedAssetsFiltersEnabled ? viewMode : undefined, scopeSpanCodeObjectId: areExtendedAssetsFiltersEnabled ? scopeSpanCodeObjectId @@ -133,8 +143,10 @@ export const AssetsFilter = () => { [ isServicesFilterEnabled, selectedServices, - filters.operations, - filters.insights, + selectedEndpoints, + selectedConsumers, + selectedInternals, + selectedInsights, viewMode, searchQuery, scopeSpanCodeObjectId, @@ -203,11 +215,17 @@ export const AssetsFilter = () => { const defaultFilters = { services: [], - operations: [], + endpoints: [], + consumers: [], + internals: [], insights: [] }; setFilters(defaultFilters); - setPersistedFilters(defaultFilters); + setPersistedFilters({ + services: [], + operations: [], + insights: [] + }); if (isServicesFilterEnabled) { setGloballySelectedServices(defaultFilters.services); } @@ -237,19 +255,27 @@ export const AssetsFilter = () => { setSelectedConsumers([]); setSelectedInternals([]); setSelectedInsights([]); - const newFilters = { + + setPersistedFilters({ services: selectedServices, operations: [], insights: [] - }; - setPersistedFilters(newFilters); + }); if (isServicesFilterEnabled) { - setGloballySelectedServices(newFilters.services); + setGloballySelectedServices(selectedServices); } - setFilters(newFilters); + setFilters({ + services: selectedServices, + endpoints: [], + consumers: [], + internals: [], + insights: [] + }); getData({ ...query, - ...newFilters + services: selectedServices, + operations: [], + insights: [] }); } }, [ @@ -264,14 +290,58 @@ export const AssetsFilter = () => { query ]); + const discardChanges = useCallback(() => { + setSelectedServices(filters.services); + setSelectedEndpoints(filters.endpoints); + setSelectedConsumers(filters.consumers); + setSelectedInternals(filters.internals); + setSelectedInsights(filters.insights as InsightType[]); + + getData({ + ...query, + services: filters.services, + operations: [ + ...filters.endpoints, + ...filters.consumers, + ...filters.internals + ], + insights: filters.insights as InsightType[] + }); + }, [ + filters.services, + filters.endpoints, + filters.consumers, + filters.internals, + filters.insights, + query + ]); + + // Close popup on environment or scope changes + useEffect(() => { + if ( + previousEnvironment?.id !== environment?.id || + previousScope?.span?.spanCodeObjectId !== scopeSpanCodeObjectId + ) { + setIsPopupOpen(false); + + discardChanges(); + } + }, [ + environment, + scopeSpanCodeObjectId, + previousEnvironment, + previousScope, + discardChanges + ]); + // Get data when the popover is opened useEffect(() => { - if (isOpen && !previousIsOpen) { + if (isPopupOpen && !previousIsOpen) { getData(query); } - }, [isOpen, previousIsOpen, query]); + }, [isPopupOpen, previousIsOpen, query]); - // Apply filters when data is loaded + // Update selected filters when data is fetched useEffect(() => { if (previousData === data || isNull(data)) { return; @@ -314,40 +384,11 @@ export const AssetsFilter = () => { ?.entries?.filter((x) => x.selected) .map((x) => x.name) ?? []) as InsightType[]; setSelectedInsights(insightsToSelect); - - if (!filters) { - const filtersQuery = { - services: servicesToSelect, - operations: [ - ...endpointsToSelect, - ...consumersToSelect, - ...internalsToSelect - ], - insights: insightsToSelect - }; - - setPersistedFilters(filtersQuery); - if (isServicesFilterEnabled) { - setGloballySelectedServices(filtersQuery.services); - } - setFilters(filtersQuery); - } - }, [ - previousData, - data, - filters, - setFilters, - selectedServices, - selectedEndpoints, - selectedConsumers, - selectedInternals, - selectedInsights, - setPersistedFilters, - setGloballySelectedServices, - isServicesFilterEnabled - ]); + }, [previousData, data]); const handleClearFiltersButtonClick = () => { + sendUserActionTrackingEvent(trackingEvents.CLEAR_FILTERS_BUTTON_CLICKED); + getData({ ...query, services: isServicesFilterEnabled ? [] : selectedServices, @@ -430,20 +471,40 @@ export const AssetsFilter = () => { entries: [] }; - const selectedFilters = [ + const selectedFiltersCount = [ ...(isServicesFilterEnabled ? selectedServices : []), ...selectedEndpoints, ...selectedConsumers, ...selectedInternals, ...selectedInsights - ]; + ].length; + + const appliedFiltersCount = [ + ...(isServicesFilterEnabled ? filters.services : []), + ...filters.endpoints, + ...filters.consumers, + ...filters.internals, + ...filters.insights + ].length; const handleCloseButtonClick = () => { sendUserActionTrackingEvent( trackingEvents.FILTERS_POPUP_CLOSE_BUTTON_CLICKED ); - // TODO: discard changes + setIsPopupOpen(false); + + discardChanges(); + }; + + const handleFiltersButtonClick = () => { + sendUserActionTrackingEvent(trackingEvents.FILTERS_BUTTON_CLICKED); + + setIsPopupOpen(!isPopupOpen); + + if (isPopupOpen) { + discardChanges(); + } }; const handleApplyButtonClick = () => { @@ -451,30 +512,34 @@ export const AssetsFilter = () => { trackingEvents.FILTERS_POPUP_APPLY_FILTERS_BUTTON_CLICKED ); - const filtersQuery = { - services: isServicesFilterEnabled ? selectedServices : [], + setIsPopupOpen(false); + + const newServices = isServicesFilterEnabled ? selectedServices : []; + + setFilters({ + services: newServices, + endpoints: selectedEndpoints, + consumers: selectedConsumers, + internals: selectedInternals, + insights: selectedInsights + }); + setPersistedFilters({ + services: newServices, operations: [ ...selectedEndpoints, ...selectedConsumers, ...selectedInternals ], insights: selectedInsights - }; - - setFilters(filtersQuery); - setPersistedFilters(filtersQuery); + }); if (isServicesFilterEnabled) { - setGloballySelectedServices(filtersQuery.services); + setGloballySelectedServices(newServices); } sendTrackingEvent(trackingEvents.FILTER_APPLIED); }; - const handleOnStateChange = (state: boolean) => { - setIsOpen(state); - }; - const filterComponents = [ { title: "Operations", @@ -540,8 +605,10 @@ export const AssetsFilter = () => { onClearAll={handleClearFiltersButtonClick} title={"Filters"} filters={filterComponents} - selectedFiltersCount={selectedFilters.length} - onStateChange={handleOnStateChange} + selectedFiltersCount={selectedFiltersCount} + appliedFiltersCount={appliedFiltersCount} + isOpen={isPopupOpen} + onFiltersButtonClick={handleFiltersButtonClick} /> ); }; diff --git a/src/components/Assets/tracking.ts b/src/components/Assets/tracking.ts index b6b98731c..393b6c7c9 100644 --- a/src/components/Assets/tracking.ts +++ b/src/components/Assets/tracking.ts @@ -10,7 +10,9 @@ export const trackingEvents = addPrefix( FILTERS_POPUP_CLOSE_BUTTON_CLICKED: "filter popup close button clicked", FILTERS_POPUP_APPLY_FILTERS_BUTTON_CLICKED: "filter popup apply filters button clicked", - ALL_ASSETS_LINK_CLICKED: "all assets link clicked" + ALL_ASSETS_LINK_CLICKED: "all assets link clicked", + FILTERS_BUTTON_CLICKED: "filters button clicked", + CLEAR_FILTERS_BUTTON_CLICKED: "clear filters button clicked" }, " " ); diff --git a/src/components/Assets/utils.tsx b/src/components/Assets/utils.tsx index 721a69313..443ed1a04 100644 --- a/src/components/Assets/utils.tsx +++ b/src/components/Assets/utils.tsx @@ -1,11 +1,11 @@ import { MemoExoticComponent } from "react"; +import { AssetsFilters } from "../../store/assets/assetsSlice"; import { CodeMarkerPinIcon } from "../common/icons/CodeMarkerPinIcon"; import { DatabaseIcon } from "../common/icons/DatabaseIcon"; import { EndpointIcon } from "../common/icons/EndpointIcon"; import { HTTPClientIcon } from "../common/icons/HTTPClientIcon"; import { UserIcon } from "../common/icons/UserIcon"; import { IconProps } from "../common/icons/types"; -import { AssetFilterQuery } from "./AssetsFilter/types"; interface AssetTypeInfo { label: string; @@ -53,14 +53,16 @@ export const getAssetTypeInfo = ( }; export const checkIfAnyFiltersApplied = ( - filters: AssetFilterQuery, + filters: AssetsFilters, searchQuery: string, isServicesFilterEnabled: boolean ) => Boolean( [ ...filters.insights, - ...filters.operations, + ...filters.endpoints, + ...filters.consumers, + ...filters.internals, ...(isServicesFilterEnabled ? filters.services : []) ].length > 0 ) || searchQuery.length > 0; diff --git a/src/components/Errors/GlobalErrorsList/GlobalErrorsFilters/index.tsx b/src/components/Errors/GlobalErrorsList/GlobalErrorsFilters/index.tsx index d5063007b..6f89a254a 100644 --- a/src/components/Errors/GlobalErrorsList/GlobalErrorsFilters/index.tsx +++ b/src/components/Errors/GlobalErrorsList/GlobalErrorsFilters/index.tsx @@ -34,10 +34,12 @@ const getSelectPlaceholder = (options: SelectItem[], placeholder: string) => options.filter((x) => x.selected).length > 0 ? placeholder : "All"; export const GlobalErrorsFilters = () => { + const [isPopupOpen, setIsPopupOpen] = useState(false); const { environment, backendInfo, - selectedServices: globallySelectedServices + selectedServices: globallySelectedServices, + scope } = useConfigSelector(); const areGlobalErrorsCriticalityAndUnhandledFiltersEnabled = getFeatureFlagValue( @@ -53,6 +55,10 @@ export const GlobalErrorsFilters = () => { } = useStore.getState(); const { services, endpoints, errorTypes } = globalErrorsFilters; const environmentId = environment?.id; + const previousEnvironmentId = usePrevious(environmentId); + const scopeSpanCodeObjectId = scope?.span?.spanCodeObjectId; + const previousScopeSpanCodeObjectId = usePrevious(scopeSpanCodeObjectId); + const [lastChangedFilter, setLastChangedFilter] = useState< ErrorFilter | undefined >(undefined); @@ -165,7 +171,47 @@ export const GlobalErrorsFilters = () => { globalErrorsSelectedFilters.errorTypes ]); - // TODO: clear filters on environment change. but keep the selected services + // Clear filters on environment change, but keep the selected services + useEffect(() => { + if (previousEnvironmentId !== environmentId) { + setSelectedEndpoints([]); + setSelectedErrorTypes([]); + setSelectedCriticalities([]); + setSelectedHandlingTypes([]); + } + }, [environmentId, previousEnvironmentId]); + + const discardChanges = useCallback(() => { + setSelectedServices(globalErrorsSelectedFilters.services); + setSelectedEndpoints(globalErrorsSelectedFilters.endpoints); + setSelectedErrorTypes(globalErrorsSelectedFilters.errorTypes); + setSelectedCriticalities(globalErrorsSelectedFilters.criticalities); + setSelectedHandlingTypes(globalErrorsSelectedFilters.handlingTypes); + }, [ + globalErrorsSelectedFilters.services, + globalErrorsSelectedFilters.endpoints, + globalErrorsSelectedFilters.errorTypes, + globalErrorsSelectedFilters.criticalities, + globalErrorsSelectedFilters.handlingTypes + ]); + + // Close popup on environment or scope changes + useEffect(() => { + if ( + previousEnvironmentId !== environmentId || + previousScopeSpanCodeObjectId !== scopeSpanCodeObjectId + ) { + setIsPopupOpen(false); + + discardChanges(); + } + }, [ + environmentId, + scopeSpanCodeObjectId, + previousEnvironmentId, + previousScopeSpanCodeObjectId, + discardChanges + ]); const handleServicesChange = (value: string | string[]) => { sendUserActionTrackingEvent( @@ -406,24 +452,26 @@ export const GlobalErrorsFilters = () => { : []) ]; - const applyFilters = () => { - setGlobalErrorsSelectedFilters({ - ...globalErrorsSelectedFilters, - services: selectedServices, - endpoints: selectedEndpoints, - errorTypes: selectedErrorTypes, - criticalities: selectedCriticalities, - handlingTypes: selectedHandlingTypes - }); - setGloballySelectedServices(selectedServices); - }; - const handleClose = () => { sendUserActionTrackingEvent( trackingEvents.GLOBAL_ERRORS_VIEW_FILTERS_CLOSE_BUTTON_CLICKED ); - // TODO: discard changes + setIsPopupOpen(false); + + discardChanges(); + }; + + const handleFiltersButtonClick = () => { + sendUserActionTrackingEvent( + trackingEvents.GLOBAL_ERRORS_VIEW_FILTERS_BUTTON_CLICKED + ); + + setIsPopupOpen(!isPopupOpen); + + if (isPopupOpen) { + discardChanges(); + } }; const handleApply = () => { @@ -431,7 +479,17 @@ export const GlobalErrorsFilters = () => { trackingEvents.GLOBAL_ERRORS_VIEW_FILTERS_APPLY_FILTERS_BUTTON_CLICKED ); - applyFilters(); + setIsPopupOpen(false); + + setGlobalErrorsSelectedFilters({ + ...globalErrorsSelectedFilters, + services: selectedServices, + endpoints: selectedEndpoints, + errorTypes: selectedErrorTypes, + criticalities: selectedCriticalities, + handlingTypes: selectedHandlingTypes + }); + setGloballySelectedServices(selectedServices); }; const handleClearAll = () => { @@ -453,6 +511,14 @@ export const GlobalErrorsFilters = () => { selectedHandlingTypes.length ].filter((x) => x > 0).length; + const appliedFiltersCount = [ + (globallySelectedServices ?? []).length, + globalErrorsSelectedFilters.endpoints.length, + globalErrorsSelectedFilters.errorTypes.length, + globalErrorsSelectedFilters.criticalities.length, + globalErrorsSelectedFilters.handlingTypes.length + ].filter((x) => x > 0).length; + return ( { onClearAll={handleClearAll} title={"Filters"} selectedFiltersCount={selectedFiltersCount} + appliedFiltersCount={appliedFiltersCount} filters={filters} + isOpen={isPopupOpen} + onFiltersButtonClick={handleFiltersButtonClick} /> ); }; diff --git a/src/components/Insights/InsightsCatalog/FilterPanel/IssuesFilter/index.tsx b/src/components/Insights/InsightsCatalog/FilterPanel/IssuesFilter/index.tsx index acfc786e9..6e337b335 100644 --- a/src/components/Insights/InsightsCatalog/FilterPanel/IssuesFilter/index.tsx +++ b/src/components/Insights/InsightsCatalog/FilterPanel/IssuesFilter/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { getFeatureFlagValue } from "../../../../../featureFlags"; import { usePrevious } from "../../../../../hooks/usePrevious"; import { useConfigSelector } from "../../../../../store/config/useConfigSelector"; @@ -15,16 +15,19 @@ import { WarningTriangleIcon } from "../../../../common/icons/WarningTriangleIco import { IconProps } from "../../../../common/icons/types"; import { SelectItem } from "../../../../common/v3/Select/types"; import { useIssuesFilters } from "../../../Issues/useIssuesFilters"; -import { InsightFilterType } from "../../types"; +import { InsightFilterType, ViewMode } from "../../types"; import * as s from "./styles"; import { trackingEvents } from "./tracking"; export const IssuesFilter = () => { - const { filteredInsightTypes, filters } = useInsightsSelector(); + const [isPopupOpen, setIsPopupOpen] = useState(false); + const { filteredInsightTypes, filters, viewMode, search } = + useInsightsSelector(); const { selectedServices: globallySelectedServices, backendInfo, - scope + scope, + environment } = useConfigSelector(); const { setSelectedServices: setGloballySelectedServices, @@ -37,9 +40,12 @@ export const IssuesFilter = () => { const [isUnreadOnly, setIsUnreadOnly] = useState( filters.includes("unread") ); - const { data } = useIssuesFilters(); + const { data, getData } = useIssuesFilters(); const previousData = usePrevious(data); + const environmentId = environment?.id; + const previousEnvironmentId = usePrevious(environmentId); const scopeSpanCodeObjectId = scope?.span?.spanCodeObjectId; + const previousScopeSpanCodeObjectId = usePrevious(scopeSpanCodeObjectId); const isServicesFilterEnabled = Boolean( getFeatureFlagValue(backendInfo, FeatureFlag.ARE_ISSUES_FILTERS_ENABLED) @@ -50,6 +56,7 @@ export const IssuesFilter = () => { const [selectedInsightTypes, setSelectedInsightTypes] = useState(filteredInsightTypes); + // Update selected filters when data is fetched useEffect(() => { if (previousData && previousData !== data) { if (selectedInsightTypes.length > 0) { @@ -83,9 +90,56 @@ export const IssuesFilter = () => { isServicesFilterEnabled ]); + const discardChanges = useCallback(() => { + const newServices = globallySelectedServices ?? []; + setSelectedServices(newServices); + setSelectedInsightTypes(filteredInsightTypes); + setIsCriticalOnly(filters.includes("criticality")); + setIsUnreadOnly(filters.includes("unread")); + + getData({ + displayName: search, + showDismissed: ViewMode.OnlyDismissed === viewMode, + filters: [ + ...(filters.includes("criticality") ? ["criticality"] : []), + ...(filters.includes("unread") ? ["unread"] : []) + ] as InsightFilterType[], + insightTypes: filteredInsightTypes, + services: newServices, + scopedSpanCodeObjectId: scopeSpanCodeObjectId + }); + }, [ + filteredInsightTypes, + filters, + globallySelectedServices, + search, + scopeSpanCodeObjectId, + viewMode, + getData + ]); + + // Close popup and discard changes on environment or scope changes + useEffect(() => { + if ( + previousEnvironmentId !== environmentId || + previousScopeSpanCodeObjectId !== scopeSpanCodeObjectId + ) { + setIsPopupOpen(false); + discardChanges(); + } + }, [ + discardChanges, + environmentId, + previousEnvironmentId, + previousScopeSpanCodeObjectId, + scopeSpanCodeObjectId + ]); + const handleApplyFiltersButtonClick = () => { sendUserActionTrackingEvent(trackingEvents.APPLY_FILTERS_BUTTON_CLICKED); + setIsPopupOpen(false); + setFilteredInsightTypes(selectedInsightTypes); setFilters([ ...(isCriticalOnly ? ["criticality"] : []), @@ -102,16 +156,9 @@ export const IssuesFilter = () => { ); setSelectedServices([]); - setFilteredInsightTypes([]); + setSelectedInsightTypes([]); setIsCriticalOnly(false); setIsUnreadOnly(false); - - setFilteredInsightTypes([]); - setFilters([]); - - if (isServicesFilterEnabled) { - setGloballySelectedServices([]); - } }; const handleIssueTypesChange = (value: string | string[]) => { @@ -119,7 +166,7 @@ export const IssuesFilter = () => { filterType: "issueType" }); const newInsightTypes = Array.isArray(value) ? value : [value]; - setFilteredInsightTypes(newInsightTypes); + setSelectedInsightTypes(newInsightTypes); }; const handleServiceChange = (value: string | string[]) => { @@ -133,7 +180,17 @@ export const IssuesFilter = () => { const handleCloseButtonClick = () => { sendUserActionTrackingEvent(trackingEvents.CLOSE_FILTER_DIALOG_CLICKED); - // TODO: discard changes + setIsPopupOpen(false); + discardChanges(); + }; + + const handleFiltersButtonClick = () => { + sendUserActionTrackingEvent(trackingEvents.FILTERS_BUTTON_CLICKED); + setIsPopupOpen(!isPopupOpen); + + if (isPopupOpen) { + discardChanges(); + } }; const handleToggleFilterChange = ( @@ -210,13 +267,42 @@ export const IssuesFilter = () => { criticalityFilterOptions.find((x) => x.selected)?.label ?? "All"; const readStatusFilterPlaceholder = readStatusFilterOptions.find((x) => x.selected)?.label ?? "All"; + const selectedFiltersCount = selectedInsightTypes.length + (isServicesFilterEnabled ? selectedServices.length : 0) + (isCriticalOnly ? 1 : 0) + (isUnreadOnly ? 1 : 0); + const appliedFiltersCount = + filteredInsightTypes.length + + (isServicesFilterEnabled ? (globallySelectedServices ?? []).length : 0) + + (filters.includes("criticality") ? 1 : 0) + + (filters.includes("unread") ? 1 : 0); + const filterComponents = [ + ...(isServicesFilterEnabled + ? [ + { + title: "Services", + component: ( + 0 ? "Services" : "All"} + multiselect={true} + icon={(props: IconProps) => ( + + + + )} + disabled={issueTypesFilterOptions?.length === 0} + /> + ) + } + ] + : []), { title: "Issues", component: ( @@ -271,35 +357,17 @@ export const IssuesFilter = () => { } ]; - if (isServicesFilterEnabled) { - filterComponents.unshift({ - title: "Services", - component: ( - 0 ? "Services" : "All"} - multiselect={true} - icon={(props: IconProps) => ( - - - - )} - disabled={issueTypesFilterOptions?.length === 0} - /> - ) - }); - } - return ( ); }; diff --git a/src/components/Insights/InsightsCatalog/FilterPanel/IssuesFilter/tracking.ts b/src/components/Insights/InsightsCatalog/FilterPanel/IssuesFilter/tracking.ts index be76ca8ff..4bf92e6c1 100644 --- a/src/components/Insights/InsightsCatalog/FilterPanel/IssuesFilter/tracking.ts +++ b/src/components/Insights/InsightsCatalog/FilterPanel/IssuesFilter/tracking.ts @@ -8,7 +8,8 @@ export const trackingEvents = addPrefix( FILTER_OPTION_SELECTED: "filter option selected", APPLY_FILTERS_BUTTON_CLICKED: "apply filters button clicked", CLEAR_ALL_FILTERS_BUTTON_CLICKED: "clear all filters button clicked", - CLOSE_FILTER_DIALOG_CLICKED: "close filter dialog clicked" + CLOSE_FILTER_DIALOG_CLICKED: "close filter dialog clicked", + FILTERS_BUTTON_CLICKED: "filters button clicked" }, " " ); diff --git a/src/components/Insights/Issues/useIssuesFilters.ts b/src/components/Insights/Issues/useIssuesFilters.ts index 712b79320..aa7223f51 100644 --- a/src/components/Insights/Issues/useIssuesFilters.ts +++ b/src/components/Insights/Issues/useIssuesFilters.ts @@ -103,6 +103,7 @@ export const useIssuesFilters = () => { return { data, - refresh + refresh, + getData: getFilters }; }; diff --git a/src/components/common/FilterPopup/index.tsx b/src/components/common/FilterPopup/index.tsx index 1417832af..ab76de0fd 100644 --- a/src/components/common/FilterPopup/index.tsx +++ b/src/components/common/FilterPopup/index.tsx @@ -1,5 +1,3 @@ -import { useEffect, useState } from "react"; -import { usePrevious } from "../../../hooks/usePrevious"; import { FilterButton } from "../FilterButton"; import { CrossIcon } from "../icons/16px/CrossIcon"; import { NewPopover } from "../NewPopover"; @@ -13,30 +11,20 @@ export const FilterPopup = ({ title, selectedFiltersCount, filters, - onStateChange, - onApply + onApply, + isOpen, + onFiltersButtonClick }: FilterPopupProps) => { - const [isOpen, setIsOpen] = useState(false); - const previousIsOpen = usePrevious(isOpen); - - useEffect(() => { - if (isOpen !== previousIsOpen && onStateChange) { - onStateChange(isOpen); - } - }, [isOpen, previousIsOpen, onStateChange]); - const handleApplyButtonClick = () => { - setIsOpen(false); onApply(); }; const handleCloseButtonClick = () => { - setIsOpen(false); onClose(); }; const handleFilterButtonClick = () => { - setIsOpen(!isOpen); + onFiltersButtonClick(); }; return ( @@ -71,7 +59,6 @@ export const FilterPopup = ({ } - onOpenChange={setIsOpen} isOpen={isOpen} placement={"bottom-end"} > diff --git a/src/components/common/FilterPopup/types.ts b/src/components/common/FilterPopup/types.ts index 147cf8a06..8a898f411 100644 --- a/src/components/common/FilterPopup/types.ts +++ b/src/components/common/FilterPopup/types.ts @@ -3,12 +3,14 @@ import { ReactNode } from "react"; export interface FilterPopupProps { onApply: () => void; onClose: () => void; + onFiltersButtonClick: () => void; title: string; onClearAll: () => void; selectedFiltersCount: number; + appliedFiltersCount: number; filters: { title: string; component: ReactNode; }[]; - onStateChange?: (state: boolean) => void; + isOpen: boolean; } diff --git a/src/store/assets/assetsSlice.ts b/src/store/assets/assetsSlice.ts index 8545a1f10..886c5d680 100644 --- a/src/store/assets/assetsSlice.ts +++ b/src/store/assets/assetsSlice.ts @@ -6,16 +6,26 @@ import { SORTING_ORDER } from "../../components/Assets/AssetList/types"; import { AssetCategoriesData } from "../../components/Assets/AssetTypeList/types"; -import { AssetFilterQuery } from "../../components/Assets/AssetsFilter/types"; import { ViewMode } from "../../components/Assets/AssetsViewScopeConfiguration/types"; +export interface AssetsFilters { + services: string[]; + endpoints: string[]; + consumers: string[]; + internals: string[]; + insights: string[]; + scopedSpanCodeObjectId?: string; + directOnly?: boolean; + displayName?: string; +} + export interface AssetsState { assetCategoriesData: AssetCategoriesData | null; isAssetCategoriesDataLoading: boolean; selectedAssetCategory: string | null; assets: AssetsData | null; areAssetsLoading: boolean; - filters: AssetFilterQuery; + filters: AssetsFilters; viewMode: ViewMode; search: string; page: number; @@ -24,7 +34,7 @@ export interface AssetsState { } const allFiltersInitialState: { - filters: AssetFilterQuery; + filters: AssetsFilters; viewMode: ViewMode; search: string; page: number; @@ -32,7 +42,9 @@ const allFiltersInitialState: { } = { filters: { services: [], - operations: [], + endpoints: [], + consumers: [], + internals: [], insights: [] }, viewMode: "descendants", @@ -72,7 +84,7 @@ export const assetsSlice = createSlice({ setAssets: (assets: AssetsData) => set({ assets }), setAreAssetsLoading: (isLoading: boolean) => set({ areAssetsLoading: isLoading }), - setAssetsFilters: (filters: AssetFilterQuery) => set({ filters }), + setAssetsFilters: (filters: AssetsFilters) => set({ filters }), setAssetsViewMode: (viewMode: ViewMode) => set({ viewMode }), setAssetsSearch: (search: string) => set({ search }), setAssetsPage: (page: number) => set({ page }), From 08fb916d7fb2b2751a0a80901a4836fbe15278ba Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Tue, 5 Nov 2024 08:59:33 +0100 Subject: [PATCH 3/5] Fix selected filter counter --- src/components/common/FilterPopup/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/common/FilterPopup/index.tsx b/src/components/common/FilterPopup/index.tsx index ab76de0fd..852e4d59b 100644 --- a/src/components/common/FilterPopup/index.tsx +++ b/src/components/common/FilterPopup/index.tsx @@ -10,6 +10,7 @@ export const FilterPopup = ({ onClose, title, selectedFiltersCount, + appliedFiltersCount, filters, onApply, isOpen, @@ -66,7 +67,7 @@ export const FilterPopup = ({ From 8376a11daf5693e032ad9c2acd22087661e6792a Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Tue, 5 Nov 2024 16:32:46 +0100 Subject: [PATCH 4/5] Fix ESLint config --- .eslintrc.js | 3 +-- .../Errors/GlobalErrorsList/DaysFilter/index.tsx | 12 ++++++------ src/components/Errors/NewErrorCard/index.tsx | 2 +- .../insightCards/common/InsightCard/index.tsx | 6 +++--- .../ScopeBar/LinkedEndpointsMenu/index.tsx | 2 +- src/components/common/Loader/index.tsx | 1 + src/components/common/NewCircleLoader/index.tsx | 1 + .../16px/{QuestionMark.tsx => QuestionMarkIcon.tsx} | 4 ++-- src/components/common/icons/DigmaLoginLogo.tsx | 1 + src/components/common/v3/Select/index.tsx | 3 +++ 10 files changed, 20 insertions(+), 15 deletions(-) rename src/components/common/icons/16px/{QuestionMark.tsx => QuestionMarkIcon.tsx} (85%) diff --git a/.eslintrc.js b/.eslintrc.js index c3c055859..1de8c9e9b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -36,8 +36,7 @@ module.exports = { }, overrides: [ { - files: ["./**/*.tsx"], - excludedFiles: ["./src/common/icons/**/*Icon.tsx"], + files: ["./src/components/common/icons/**/*Icon.tsx"], rules: { "react/jsx-curly-brace-presence": "off" } diff --git a/src/components/Errors/GlobalErrorsList/DaysFilter/index.tsx b/src/components/Errors/GlobalErrorsList/DaysFilter/index.tsx index c008d4fa2..445ab69b4 100644 --- a/src/components/Errors/GlobalErrorsList/DaysFilter/index.tsx +++ b/src/components/Errors/GlobalErrorsList/DaysFilter/index.tsx @@ -120,8 +120,8 @@ export const DaysFilter = ({ onChanged }: DaysFilterProps) => { } + buttonType={"secondaryBorderless"} + icon={() => } onClick={handleDecrement} /> { value={currentValue?.toString()} /> } + buttonType={"secondaryBorderless"} + icon={() => } onClick={handleIncrement} /> Last days diff --git a/src/components/Errors/NewErrorCard/index.tsx b/src/components/Errors/NewErrorCard/index.tsx index d6ab8f254..7f566763e 100644 --- a/src/components/Errors/NewErrorCard/index.tsx +++ b/src/components/Errors/NewErrorCard/index.tsx @@ -310,7 +310,7 @@ export const NewErrorCard = ({ {isDismissEnabled && ( { setShowInfo(!showInfo); @@ -437,7 +437,7 @@ export const InsightCard = ({ handleMenuItemClick(x)}> - + {x.displayName} ), diff --git a/src/components/common/Loader/index.tsx b/src/components/common/Loader/index.tsx index 3ed91b6fb..c6e6c5316 100644 --- a/src/components/common/Loader/index.tsx +++ b/src/components/common/Loader/index.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react/jsx-curly-brace-presence */ import React from "react"; import { CircleLoader } from "../CircleLoader"; import { LoaderProps } from "./types"; diff --git a/src/components/common/NewCircleLoader/index.tsx b/src/components/common/NewCircleLoader/index.tsx index d5173b7de..20a31c319 100644 --- a/src/components/common/NewCircleLoader/index.tsx +++ b/src/components/common/NewCircleLoader/index.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react/jsx-curly-brace-presence */ // Source: https://www.benmvp.com/blog/how-to-create-circle-svg-gradient-loading-spinner/ import { DefaultTheme, useTheme } from "styled-components"; import { DEFAULT_ICON_SIZE } from "../icons/hooks"; diff --git a/src/components/common/icons/16px/QuestionMark.tsx b/src/components/common/icons/16px/QuestionMarkIcon.tsx similarity index 85% rename from src/components/common/icons/16px/QuestionMark.tsx rename to src/components/common/icons/16px/QuestionMarkIcon.tsx index 1d6fecb05..7aa84291a 100644 --- a/src/components/common/icons/16px/QuestionMark.tsx +++ b/src/components/common/icons/16px/QuestionMarkIcon.tsx @@ -2,7 +2,7 @@ import React from "react"; import { useIconProps } from "../hooks"; import { IconProps } from "../types"; -const QuestionMarkComponent = (props: IconProps) => { +const QuestionMarkIconComponent = (props: IconProps) => { const { size, color } = useIconProps(props); return ( @@ -25,4 +25,4 @@ const QuestionMarkComponent = (props: IconProps) => { ); }; -export const QuestionMark = React.memo(QuestionMarkComponent); +export const QuestionMarkIcon = React.memo(QuestionMarkIconComponent); diff --git a/src/components/common/icons/DigmaLoginLogo.tsx b/src/components/common/icons/DigmaLoginLogo.tsx index 5c448e71c..e5dec6513 100644 --- a/src/components/common/icons/DigmaLoginLogo.tsx +++ b/src/components/common/icons/DigmaLoginLogo.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react/jsx-curly-brace-presence */ import React from "react"; const DigmaLoginLogoComponent = () => { diff --git a/src/components/common/v3/Select/index.tsx b/src/components/common/v3/Select/index.tsx index 1e6cf8ec0..9eb0322f3 100644 --- a/src/components/common/v3/Select/index.tsx +++ b/src/components/common/v3/Select/index.tsx @@ -49,6 +49,7 @@ export const Select = ({ const handleButtonClick = () => { setIsOpen(!isOpen); + setSearchValue(""); }; const handleItemClick = (item: SelectItem) => { @@ -59,6 +60,7 @@ export const Select = ({ if (!multiselect) { onChange(item.value); setIsOpen(false); + setSearchValue(""); return; } @@ -112,6 +114,7 @@ export const Select = ({ type={"text"} placeholder={"Search"} onChange={handleSearchChange} + value={searchValue} /> )} From 4425c1fff9a5bbd39b1fcbfe007d00a51d43813a Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Thu, 7 Nov 2024 08:55:29 +0100 Subject: [PATCH 5/5] Fix assets filters rehydration --- src/components/Assets/AssetList/index.tsx | 10 +- src/components/Assets/AssetTypeList/index.tsx | 12 +- src/components/Assets/AssetsFilter/index.tsx | 107 ++++-------------- src/components/Assets/index.tsx | 34 +++++- src/components/Assets/utils.tsx | 17 +-- src/store/assets/assetsSlice.ts | 12 +- 6 files changed, 77 insertions(+), 115 deletions(-) diff --git a/src/components/Assets/AssetList/index.tsx b/src/components/Assets/AssetList/index.tsx index 9eab20305..a005a3a20 100644 --- a/src/components/Assets/AssetList/index.tsx +++ b/src/components/Assets/AssetList/index.tsx @@ -144,13 +144,13 @@ export const AssetList = ({ sortOrder: sorting.order, scopedSpanCodeObjectId: scopeSpanCodeObjectId, ...(search.length > 0 ? { displayName: search } : {}), - insights: filters.insights, + insights: filters?.insights ?? [], operations: [ - ...filters.endpoints, - ...filters.consumers, - ...filters.internals + ...(filters?.endpoints ?? []), + ...(filters?.consumers ?? []), + ...(filters?.internals ?? []) ], - services: scopeSpanCodeObjectId ? [] : filters.services, + services: scopeSpanCodeObjectId ? [] : filters?.services ?? [], directOnly: viewMode === "children" } }), diff --git a/src/components/Assets/AssetTypeList/index.tsx b/src/components/Assets/AssetTypeList/index.tsx index afcfedad7..9b1dfb11a 100644 --- a/src/components/Assets/AssetTypeList/index.tsx +++ b/src/components/Assets/AssetTypeList/index.tsx @@ -41,7 +41,7 @@ export const ASSET_TYPE_IDS = [ ]; const getData = ( - filters: AssetsFilters, + filters: AssetsFilters | null, searchQuery: string, viewMode: ViewMode, scopeSpanCodeObjectId?: string @@ -53,13 +53,13 @@ const getData = ( directOnly: viewMode === "children", scopedSpanCodeObjectId: scopeSpanCodeObjectId, ...{ - insights: filters.insights, + insights: filters?.insights ?? [], operations: [ - ...filters.endpoints, - ...filters.consumers, - ...filters.internals + ...(filters?.endpoints ?? []), + ...(filters?.consumers ?? []), + ...(filters?.internals ?? []) ], - services: scopeSpanCodeObjectId ? [] : filters.services + services: scopeSpanCodeObjectId ? [] : filters?.services ?? [] }, ...(searchQuery.length > 0 ? { displayName: searchQuery } : {}) } diff --git a/src/components/Assets/AssetsFilter/index.tsx b/src/components/Assets/AssetsFilter/index.tsx index bf613cd89..d94b03bff 100644 --- a/src/components/Assets/AssetsFilter/index.tsx +++ b/src/components/Assets/AssetsFilter/index.tsx @@ -7,14 +7,12 @@ import { } from "react"; import { dispatcher } from "../../../dispatcher"; import { getFeatureFlagValue } from "../../../featureFlags"; -import { usePersistence } from "../../../hooks/usePersistence"; import { usePrevious } from "../../../hooks/usePrevious"; import { useAssetsSelector } from "../../../store/assets/useAssetsSelector"; import { useConfigSelector } from "../../../store/config/useConfigSelector"; import { useStore } from "../../../store/useStore"; import { isEnvironment } from "../../../typeGuards/isEnvironment"; import { isNull } from "../../../typeGuards/isNull"; -import { isUndefined } from "../../../typeGuards/isUndefined"; import { FeatureFlag, InsightType } from "../../../types"; import { sendTrackingEvent } from "../../../utils/actions/sendTrackingEvent"; import { sendUserActionTrackingEvent } from "../../../utils/actions/sendUserActionTrackingEvent"; @@ -29,14 +27,11 @@ import { trackingEvents } from "../tracking"; import * as s from "./styles"; import { AssetFilterCategory, - AssetFilterQuery, AssetsFiltersData, GetAssetFiltersDataParams, GetAssetFiltersDataPayload } from "./types"; -const PERSISTENCE_KEY = "assetsFilters"; - const getData = ({ services, operations, @@ -106,9 +101,7 @@ export const AssetsFilter = () => { scope, backendInfo } = useConfigSelector(); - const [persistedFilters, setPersistedFilters] = - usePersistence(PERSISTENCE_KEY, "project"); - const previousPersistedFilters = usePrevious(persistedFilters); + const isServicesFilterEnabled = !scope?.span?.spanCodeObjectId; const [selectedServices, setSelectedServices] = useState( isServicesFilterEnabled ? globallySelectedServices ?? [] : [] @@ -154,36 +147,6 @@ export const AssetsFilter = () => { ] ); - // Get data after filters have been rehydrated - useEffect(() => { - if ( - isUndefined(previousPersistedFilters) && - !isUndefined(persistedFilters) - ) { - getData({ - ...query, - services: isServicesFilterEnabled ? selectedServices : [], - operations: persistedFilters?.operations ?? [ - ...selectedEndpoints, - ...selectedConsumers, - ...selectedInternals - ], - insights: - (persistedFilters?.insights as InsightType[]) || selectedInsights - }); - } - }, [ - previousPersistedFilters, - persistedFilters, - selectedServices, - selectedEndpoints, - selectedConsumers, - selectedInternals, - selectedInsights, - isServicesFilterEnabled, - query - ]); - // Handle filters data response useEffect(() => { const handleData = (data: unknown) => { @@ -221,11 +184,6 @@ export const AssetsFilter = () => { insights: [] }; setFilters(defaultFilters); - setPersistedFilters({ - services: [], - operations: [], - insights: [] - }); if (isServicesFilterEnabled) { setGloballySelectedServices(defaultFilters.services); } @@ -238,10 +196,8 @@ export const AssetsFilter = () => { setFilters, previousEnvironment, environment, - setPersistedFilters, isServicesFilterEnabled, setGloballySelectedServices, - areExtendedAssetsFiltersEnabled, query ]); @@ -256,11 +212,6 @@ export const AssetsFilter = () => { setSelectedInternals([]); setSelectedInsights([]); - setPersistedFilters({ - services: selectedServices, - operations: [], - insights: [] - }); if (isServicesFilterEnabled) { setGloballySelectedServices(selectedServices); } @@ -280,41 +231,32 @@ export const AssetsFilter = () => { } }, [ setFilters, - setPersistedFilters, setGloballySelectedServices, previousScope, scopeSpanCodeObjectId, selectedServices, isServicesFilterEnabled, - areExtendedAssetsFiltersEnabled, query ]); const discardChanges = useCallback(() => { - setSelectedServices(filters.services); - setSelectedEndpoints(filters.endpoints); - setSelectedConsumers(filters.consumers); - setSelectedInternals(filters.internals); - setSelectedInsights(filters.insights as InsightType[]); + setSelectedServices(filters?.services ?? []); + setSelectedEndpoints(filters?.endpoints ?? []); + setSelectedConsumers(filters?.consumers ?? []); + setSelectedInternals(filters?.internals ?? []); + setSelectedInsights((filters?.insights as InsightType[]) ?? []); getData({ ...query, - services: filters.services, + services: filters?.services ?? [], operations: [ - ...filters.endpoints, - ...filters.consumers, - ...filters.internals + ...(filters?.endpoints ?? []), + ...(filters?.consumers ?? []), + ...(filters?.internals ?? []) ], - insights: filters.insights as InsightType[] + insights: (filters?.insights as InsightType[]) ?? [] }); - }, [ - filters.services, - filters.endpoints, - filters.consumers, - filters.internals, - filters.insights, - query - ]); + }, [filters, query]); // Close popup on environment or scope changes useEffect(() => { @@ -479,13 +421,15 @@ export const AssetsFilter = () => { ...selectedInsights ].length; - const appliedFiltersCount = [ - ...(isServicesFilterEnabled ? filters.services : []), - ...filters.endpoints, - ...filters.consumers, - ...filters.internals, - ...filters.insights - ].length; + const appliedFiltersCount = filters + ? [ + ...(isServicesFilterEnabled ? filters.services : []), + ...filters.endpoints, + ...filters.consumers, + ...filters.internals, + ...filters.insights + ].length + : 0; const handleCloseButtonClick = () => { sendUserActionTrackingEvent( @@ -523,15 +467,6 @@ export const AssetsFilter = () => { internals: selectedInternals, insights: selectedInsights }); - setPersistedFilters({ - services: newServices, - operations: [ - ...selectedEndpoints, - ...selectedConsumers, - ...selectedInternals - ], - insights: selectedInsights - }); if (isServicesFilterEnabled) { setGloballySelectedServices(newServices); diff --git a/src/components/Assets/index.tsx b/src/components/Assets/index.tsx index be77b0f3c..0374f04a1 100644 --- a/src/components/Assets/index.tsx +++ b/src/components/Assets/index.tsx @@ -1,10 +1,13 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { useParams } from "react-router-dom"; import { useDebounce } from "../../hooks/useDebounce"; +import { usePersistence } from "../../hooks/usePersistence"; import { usePrevious } from "../../hooks/usePrevious"; +import { AssetsFilters } from "../../store/assets/assetsSlice"; import { useAssetsSelector } from "../../store/assets/useAssetsSelector"; import { useConfigSelector } from "../../store/config/useConfigSelector"; import { useStore } from "../../store/useStore"; +import { isUndefined } from "../../typeGuards/isUndefined"; import { sendUserActionTrackingEvent } from "../../utils/actions/sendUserActionTrackingEvent"; import { useHistory } from "../Main/useHistory"; import { EmptyState } from "../common/EmptyState"; @@ -22,6 +25,7 @@ import * as s from "./styles"; import { trackingEvents } from "./tracking"; import { DataRefresher } from "./types"; +const PERSISTENCE_KEY = "assetsFiltersV2"; const SEARCH_INPUT_DEBOUNCE_DELAY = 1000; // in milliseconds const getAssetCategoryCount = ( @@ -33,11 +37,17 @@ const getAssetCategoryCount = ( ) ?? 0; export const Assets = () => { + const [persistedFilters, setPersistedFilters] = usePersistence( + PERSISTENCE_KEY, + "project" + ); + const previousPersistedFilters = usePrevious(persistedFilters); const [assetsCount, setAssetsCount] = useState(); const params = useParams(); const selectedAssetTypeId = useMemo(() => params.typeId, [params]); const { search, filters, assets, assetCategoriesData } = useAssetsSelector(); - const { setAssetsSearch: setSearch } = useStore.getState(); + const { setAssetsSearch: setSearch, setAssetsFilters: setFilters } = + useStore.getState(); const [searchInputValue, setSearchInputValue] = useState(search); const debouncedSearchInputValue = useDebounce( searchInputValue, @@ -53,6 +63,7 @@ export const Assets = () => { const { goTo } = useHistory(); const isBackendUpgradeMessageVisible = false; const { showAssetsHeaderToolBox } = useAssetsSelector(); + const isInitialized = Boolean(filters); useEffect(() => { if (previousScopeSpanCodeObjectId !== scopeSpanCodeObjectId) { @@ -64,6 +75,23 @@ export const Assets = () => { setSearch(debouncedSearchInputValue); }, [debouncedSearchInputValue, setSearch]); + useEffect(() => { + if ( + isUndefined(previousPersistedFilters) && + !isUndefined(persistedFilters) && + persistedFilters + ) { + setFilters(persistedFilters); + } + }, [previousPersistedFilters, persistedFilters, setFilters]); + + // Update persisted filters on filters change + useEffect(() => { + if (isInitialized) { + setPersistedFilters(filters); + } + }, [isInitialized, filters, setPersistedFilters]); + const handleGoToAllAssets = () => { goTo(".."); }; @@ -155,6 +183,10 @@ export const Assets = () => { ); }; + if (!isInitialized) { + return null; + } + return ( diff --git a/src/components/Assets/utils.tsx b/src/components/Assets/utils.tsx index 443ed1a04..abc70fda4 100644 --- a/src/components/Assets/utils.tsx +++ b/src/components/Assets/utils.tsx @@ -53,16 +53,17 @@ export const getAssetTypeInfo = ( }; export const checkIfAnyFiltersApplied = ( - filters: AssetsFilters, + filters: AssetsFilters | null, searchQuery: string, isServicesFilterEnabled: boolean ) => Boolean( - [ - ...filters.insights, - ...filters.endpoints, - ...filters.consumers, - ...filters.internals, - ...(isServicesFilterEnabled ? filters.services : []) - ].length > 0 + filters && + [ + ...filters.insights, + ...filters.endpoints, + ...filters.consumers, + ...filters.internals, + ...(isServicesFilterEnabled ? filters.services : []) + ].length > 0 ) || searchQuery.length > 0; diff --git a/src/store/assets/assetsSlice.ts b/src/store/assets/assetsSlice.ts index 886c5d680..5c288f7e4 100644 --- a/src/store/assets/assetsSlice.ts +++ b/src/store/assets/assetsSlice.ts @@ -25,7 +25,7 @@ export interface AssetsState { selectedAssetCategory: string | null; assets: AssetsData | null; areAssetsLoading: boolean; - filters: AssetsFilters; + filters: AssetsFilters | null; viewMode: ViewMode; search: string; page: number; @@ -34,19 +34,13 @@ export interface AssetsState { } const allFiltersInitialState: { - filters: AssetsFilters; + filters: AssetsFilters | null; viewMode: ViewMode; search: string; page: number; sorting: Sorting; } = { - filters: { - services: [], - endpoints: [], - consumers: [], - internals: [], - insights: [] - }, + filters: null, viewMode: "descendants", search: "", page: 0,