From 8aed8927ed3671b9aea574ea95d5a9e168f0cfa1 Mon Sep 17 00:00:00 2001 From: mufazalov Date: Thu, 6 Nov 2025 10:28:37 +0300 Subject: [PATCH 1/4] fix: problem filter to query --- .../ProblemFilter/ProblemFilter.tsx | 29 ++++---- src/components/ProblemFilter/i18n/en.json | 4 ++ src/components/ProblemFilter/i18n/index.ts | 7 ++ src/components/ProblemFilter/index.ts | 1 - .../Nodes/NodesControls/NodesControls.tsx | 8 +-- src/containers/Nodes/NodesTable.tsx | 11 ++-- .../PaginatedNodes/GroupedNodesComponent.tsx | 2 +- .../Nodes/PaginatedNodes/NodesComponent.tsx | 8 +-- .../Nodes/PaginatedNodes/PaginatedNodes.tsx | 22 +++---- src/containers/Nodes/getNodes.ts | 6 +- .../Nodes/useNodesPageQueryParams.ts | 9 ++- .../Tenant/Diagnostics/Network/Network.tsx | 33 ++++------ src/containers/Tenants/Tenants.tsx | 66 +++++++++---------- .../Tenants/useTenantsQueryParams.ts | 31 +++++++++ src/store/reducers/index.ts | 2 - src/store/reducers/nodes/types.ts | 3 +- src/store/reducers/settings/hooks.ts | 19 ------ src/store/reducers/settings/settings.ts | 15 +---- src/store/reducers/settings/types.ts | 6 -- src/store/reducers/tenants/filters.ts | 26 ++++++++ src/store/reducers/tenants/selectors.ts | 63 ------------------ src/store/reducers/tenants/tenants.ts | 20 +----- src/store/reducers/tenants/types.ts | 8 --- src/store/state-url-mapping.ts | 21 +----- src/utils/hooks/useWithProblemsQueryParam.ts | 21 ++++++ src/utils/nodes.ts | 6 -- 26 files changed, 190 insertions(+), 257 deletions(-) create mode 100644 src/components/ProblemFilter/i18n/en.json create mode 100644 src/components/ProblemFilter/i18n/index.ts delete mode 100644 src/components/ProblemFilter/index.ts create mode 100644 src/containers/Tenants/useTenantsQueryParams.ts delete mode 100644 src/store/reducers/settings/hooks.ts create mode 100644 src/store/reducers/tenants/filters.ts delete mode 100644 src/store/reducers/tenants/selectors.ts create mode 100644 src/utils/hooks/useWithProblemsQueryParam.ts diff --git a/src/components/ProblemFilter/ProblemFilter.tsx b/src/components/ProblemFilter/ProblemFilter.tsx index 32960dbc23..bc0afc4f80 100644 --- a/src/components/ProblemFilter/ProblemFilter.tsx +++ b/src/components/ProblemFilter/ProblemFilter.tsx @@ -1,23 +1,28 @@ import {SegmentedRadioGroup} from '@gravity-ui/uikit'; -import {ProblemFilterValues} from '../../store/reducers/settings/settings'; -import type {ProblemFilterValue} from '../../store/reducers/settings/types'; +import i18n from './i18n'; interface ProblemFilterProps { - value: ProblemFilterValue; - onChange: (value: ProblemFilterValue) => void; + value: boolean; + onChange: (value: boolean) => void; className?: string; } -export const ProblemFilter = ({value, onChange, className}: ProblemFilterProps) => { +export function ProblemFilter({value, onChange, className}: ProblemFilterProps) { + const handleValueChange = (value: string) => { + onChange(value === 'true'); + }; + return ( - - - {ProblemFilterValues.ALL} - - - {ProblemFilterValues.PROBLEMS} + + {i18n('all')} + + {i18n('with-problems')} ); -}; +} diff --git a/src/components/ProblemFilter/i18n/en.json b/src/components/ProblemFilter/i18n/en.json new file mode 100644 index 0000000000..5880908a40 --- /dev/null +++ b/src/components/ProblemFilter/i18n/en.json @@ -0,0 +1,4 @@ +{ + "all": "All", + "with-problems": "With problems" +} diff --git a/src/components/ProblemFilter/i18n/index.ts b/src/components/ProblemFilter/i18n/index.ts new file mode 100644 index 0000000000..4ca95a27dc --- /dev/null +++ b/src/components/ProblemFilter/i18n/index.ts @@ -0,0 +1,7 @@ +import {registerKeysets} from '../../../utils/i18n'; + +import en from './en.json'; + +const COMPONENT = 'ydb-problem-filter'; + +export default registerKeysets(COMPONENT, {en}); diff --git a/src/components/ProblemFilter/index.ts b/src/components/ProblemFilter/index.ts deleted file mode 100644 index 66cce332cc..0000000000 --- a/src/components/ProblemFilter/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ProblemFilter'; diff --git a/src/containers/Nodes/NodesControls/NodesControls.tsx b/src/containers/Nodes/NodesControls/NodesControls.tsx index 401bfb9f45..8f567fc615 100644 --- a/src/containers/Nodes/NodesControls/NodesControls.tsx +++ b/src/containers/Nodes/NodesControls/NodesControls.tsx @@ -3,14 +3,13 @@ import React from 'react'; import {Select, Text} from '@gravity-ui/uikit'; import {EntitiesCount} from '../../../components/EntitiesCount'; -import {ProblemFilter} from '../../../components/ProblemFilter'; +import {ProblemFilter} from '../../../components/ProblemFilter/ProblemFilter'; import {Search} from '../../../components/Search'; import {UptimeFilter} from '../../../components/UptimeFIlter'; import { useViewerNodesHandlerHasGroupingBySystemState, useViewerNodesHandlerHasNetworkStats, } from '../../../store/reducers/capabilities/hooks'; -import {useProblemFilter} from '../../../store/reducers/settings/hooks'; import type {NodesGroupByField} from '../../../types/api/nodes'; import {useIsViewerUser} from '../../../utils/hooks/useIsUserAllowedToMakeChanges'; import {PeerRoleFilter} from '../PeerRoleFilter/PeerRoleFilter'; @@ -45,13 +44,14 @@ export function NodesControls({ uptimeFilter, peerRoleFilter, groupByParam, + withProblems, handleSearchQueryChange, handleUptimeFilterChange, handlePeerRoleFilterChange, handleGroupByParamChange, + handleWithProblemsChange, } = useNodesPageQueryParams(groupByParams, withPeerRoleFilter); - const {problemFilter, handleProblemFilterChange} = useProblemFilter(); const isViewerUser = useIsViewerUser(); const systemStateGroupingAvailable = useViewerNodesHandlerHasGroupingBySystemState(); @@ -73,7 +73,7 @@ export function NodesControls({ value={searchValue} /> {systemStateGroupingAvailable && withGroupBySelect ? null : ( - + )} {withGroupBySelect ? null : ( diff --git a/src/containers/Nodes/NodesTable.tsx b/src/containers/Nodes/NodesTable.tsx index 37c96d847a..8728cb02e3 100644 --- a/src/containers/Nodes/NodesTable.tsx +++ b/src/containers/Nodes/NodesTable.tsx @@ -6,7 +6,6 @@ import {ResizeablePaginatedTable} from '../../components/PaginatedTable'; import {NODES_COLUMNS_WIDTH_LS_KEY} from '../../components/nodesColumns/constants'; import type {NodesColumn} from '../../components/nodesColumns/types'; import type {NodesFilters} from '../../store/reducers/nodes/types'; -import type {ProblemFilterValue} from '../../store/reducers/settings/types'; import type {PreparedStorageNode} from '../../store/reducers/storage/types'; import type {NodesGroupByField, NodesPeerRole} from '../../types/api/nodes'; import {NodesUptimeFilterValues} from '../../utils/nodes'; @@ -22,7 +21,7 @@ interface NodesTableProps { databaseFullPath?: string; searchValue: string; - problemFilter: ProblemFilterValue; + withProblems: boolean; uptimeFilter: NodesUptimeFilterValues; peerRoleFilter?: NodesPeerRole; @@ -42,7 +41,7 @@ export function NodesTable({ database, databaseFullPath, searchValue, - problemFilter, + withProblems, uptimeFilter, peerRoleFilter, filterGroup, @@ -58,7 +57,7 @@ export function NodesTable({ databaseFullPath, database, searchValue, - problemFilter, + withProblems, uptimeFilter, peerRoleFilter, filterGroup, @@ -69,7 +68,7 @@ export function NodesTable({ databaseFullPath, database, searchValue, - problemFilter, + withProblems, uptimeFilter, peerRoleFilter, filterGroup, @@ -77,7 +76,7 @@ export function NodesTable({ ]); const renderEmptyDataMessage = () => { - if (problemFilter !== 'All' || uptimeFilter !== NodesUptimeFilterValues.All) { + if (withProblems || uptimeFilter !== NodesUptimeFilterValues.All) { return ; } diff --git a/src/containers/Nodes/PaginatedNodes/GroupedNodesComponent.tsx b/src/containers/Nodes/PaginatedNodes/GroupedNodesComponent.tsx index fc7c6a0ec3..ee1b86d82d 100644 --- a/src/containers/Nodes/PaginatedNodes/GroupedNodesComponent.tsx +++ b/src/containers/Nodes/PaginatedNodes/GroupedNodesComponent.tsx @@ -71,7 +71,7 @@ const NodeGroup = React.memo(function NodeGroup({ databaseFullPath={databaseFullPath} database={database} searchValue={searchValue} - problemFilter={'All'} + withProblems={false} uptimeFilter={NodesUptimeFilterValues.All} peerRoleFilter={peerRoleFilter} filterGroup={name} diff --git a/src/containers/Nodes/PaginatedNodes/NodesComponent.tsx b/src/containers/Nodes/PaginatedNodes/NodesComponent.tsx index d0c6993761..d6cedfa1af 100644 --- a/src/containers/Nodes/PaginatedNodes/NodesComponent.tsx +++ b/src/containers/Nodes/PaginatedNodes/NodesComponent.tsx @@ -7,7 +7,6 @@ import {NODES_COLUMNS_TITLES} from '../../../components/nodesColumns/constants'; import type {NodesColumnId} from '../../../components/nodesColumns/constants'; import type {NodesColumn} from '../../../components/nodesColumns/types'; import {useViewerNodesHandlerHasGrouping} from '../../../store/reducers/capabilities/hooks'; -import {useProblemFilter} from '../../../store/reducers/settings/hooks'; import type {PreparedStorageNode} from '../../../store/reducers/storage/types'; import type {NodesGroupByField} from '../../../types/api/nodes'; import {useSelectedColumns} from '../../../utils/hooks/useSelectedColumns'; @@ -43,11 +42,10 @@ export function NodesComponent({ groupByParams, onDataFetched, }: NodesComponentProps) { - const {searchValue, uptimeFilter, peerRoleFilter} = useNodesPageQueryParams( + const {searchValue, uptimeFilter, peerRoleFilter, withProblems} = useNodesPageQueryParams( groupByParams, withPeerRoleFilter, ); - const {problemFilter} = useProblemFilter(); const viewerNodesHandlerHasGrouping = useViewerNodesHandlerHasGrouping(); const {columnsToShow, columnsToSelect, setColumns} = useSelectedColumns( @@ -81,7 +79,7 @@ export function NodesComponent({ database={database} databaseFullPath={databaseFullPath} searchValue={searchValue} - problemFilter={problemFilter} + withProblems={withProblems} uptimeFilter={uptimeFilter} peerRoleFilter={peerRoleFilter} columns={columnsToShow} @@ -91,7 +89,7 @@ export function NodesComponent({ } tableWrapperProps={{ scrollContainerRef, - scrollDependencies: [searchValue, problemFilter, uptimeFilter, peerRoleFilter], + scrollDependencies: [searchValue, withProblems, uptimeFilter, peerRoleFilter], }} /> ); diff --git a/src/containers/Nodes/PaginatedNodes/PaginatedNodes.tsx b/src/containers/Nodes/PaginatedNodes/PaginatedNodes.tsx index c47eefdccd..9438921d7c 100644 --- a/src/containers/Nodes/PaginatedNodes/PaginatedNodes.tsx +++ b/src/containers/Nodes/PaginatedNodes/PaginatedNodes.tsx @@ -8,7 +8,6 @@ import { useCapabilitiesLoaded, useViewerNodesHandlerHasGrouping, } from '../../../store/reducers/capabilities/hooks'; -import {useProblemFilter} from '../../../store/reducers/settings/hooks'; import type {PreparedStorageNode} from '../../../store/reducers/storage/types'; import type {NodesGroupByField} from '../../../types/api/nodes'; import {NodesUptimeFilterValues} from '../../../utils/nodes'; @@ -34,12 +33,13 @@ export interface PaginatedNodesProps { } export function PaginatedNodes(props: PaginatedNodesProps) { - const {uptimeFilter, groupByParam, handleUptimeFilterChange} = useNodesPageQueryParams( - props.groupByParams, - props.withPeerRoleFilter, - ); - - const {problemFilter, handleProblemFilterChange} = useProblemFilter(); + const { + uptimeFilter, + groupByParam, + withProblems, + handleUptimeFilterChange, + handleWithProblemsChange, + } = useNodesPageQueryParams(props.groupByParams, props.withPeerRoleFilter); const capabilitiesLoaded = useCapabilitiesLoaded(); const viewerNodesHandlerHasGrouping = useViewerNodesHandlerHasGrouping(); @@ -49,15 +49,15 @@ export function PaginatedNodes(props: PaginatedNodesProps) { React.useEffect(() => { if ( viewerNodesHandlerHasGrouping && - (problemFilter !== 'All' || uptimeFilter !== NodesUptimeFilterValues.All) + (withProblems || uptimeFilter !== NodesUptimeFilterValues.All) ) { - handleProblemFilterChange('All'); + handleWithProblemsChange(false); handleUptimeFilterChange(NodesUptimeFilterValues.All); } }, [ - handleProblemFilterChange, + handleWithProblemsChange, handleUptimeFilterChange, - problemFilter, + withProblems, uptimeFilter, viewerNodesHandlerHasGrouping, ]); diff --git a/src/containers/Nodes/getNodes.ts b/src/containers/Nodes/getNodes.ts index 7bdbc71803..391b5f0f2e 100644 --- a/src/containers/Nodes/getNodes.ts +++ b/src/containers/Nodes/getNodes.ts @@ -10,7 +10,7 @@ import type {PreparedStorageNode} from '../../store/reducers/storage/types'; import {prepareStorageNodesResponse} from '../../store/reducers/storage/utils'; import type {NodesRequestParams} from '../../types/api/nodes'; import {prepareSortValue} from '../../utils/filters'; -import {getProblemParamValue, getUptimeParamValue} from '../../utils/nodes'; +import {getUptimeParamValue} from '../../utils/nodes'; import {getRequiredDataFields} from '../../utils/tableUtils/getRequiredDataFields'; export const getNodes: FetchData< @@ -26,7 +26,7 @@ export const getNodes: FetchData< databaseFullPath, database, searchValue, - problemFilter, + withProblems, uptimeFilter, peerRoleFilter, filterGroup, @@ -50,7 +50,7 @@ export const getNodes: FetchData< path: schemePathParam, database, filter: searchValue, - problems_only: getProblemParamValue(problemFilter), + problems_only: withProblems, uptime: getUptimeParamValue(uptimeFilter), filter_peer_role: peerRoleFilter, filter_group: filterGroup, diff --git a/src/containers/Nodes/useNodesPageQueryParams.ts b/src/containers/Nodes/useNodesPageQueryParams.ts index af2a4ade29..f6cd61d8f4 100644 --- a/src/containers/Nodes/useNodesPageQueryParams.ts +++ b/src/containers/Nodes/useNodesPageQueryParams.ts @@ -1,4 +1,4 @@ -import {StringParam, useQueryParams} from 'use-query-params'; +import {BooleanParam, StringParam, useQueryParams} from 'use-query-params'; import {useViewerNodesHandlerHasGroupingBySystemState} from '../../store/reducers/capabilities/hooks'; import type {NodesGroupByField, NodesPeerRole} from '../../types/api/nodes'; @@ -18,12 +18,14 @@ export function useNodesPageQueryParams( peerRole: StringParam, search: StringParam, nodesGroupBy: StringParam, + withProblems: BooleanParam, }); const isViewerUser = useIsViewerUser(); const uptimeFilter = nodesUptimeFilterValuesSchema.parse(queryParams.uptimeFilter); const searchValue = queryParams.search ?? ''; + const withProblems = Boolean(queryParams.withProblems); let peerRoleFilter: NodesPeerRole | undefined; @@ -53,16 +55,21 @@ export function useNodesPageQueryParams( const handleGroupByParamChange = (value: string) => { setQueryParams({nodesGroupBy: value}, 'replaceIn'); }; + const handleWithProblemsChange = (value: boolean) => { + setQueryParams({withProblems: value || undefined}, 'replaceIn'); + }; return { uptimeFilter, searchValue, peerRoleFilter, groupByParam, + withProblems, handleSearchQueryChange, handleUptimeFilterChange, handlePeerRoleFilterChange, handleGroupByParamChange, + handleWithProblemsChange, }; } diff --git a/src/containers/Tenant/Diagnostics/Network/Network.tsx b/src/containers/Tenant/Diagnostics/Network/Network.tsx index e0ef92e59b..fcc91fbe8d 100644 --- a/src/containers/Tenant/Diagnostics/Network/Network.tsx +++ b/src/containers/Tenant/Diagnostics/Network/Network.tsx @@ -5,18 +5,14 @@ import {Link} from 'react-router-dom'; import {ResponseError} from '../../../../components/Errors/ResponseError'; import {Illustration} from '../../../../components/Illustration'; -import {ProblemFilter} from '../../../../components/ProblemFilter'; +import {ProblemFilter} from '../../../../components/ProblemFilter/ProblemFilter'; import {getDefaultNodePath} from '../../../../routes'; import {networkApi} from '../../../../store/reducers/network/network'; -import { - ProblemFilterValues, - changeFilter, - selectProblemFilter, -} from '../../../../store/reducers/settings/settings'; import {hideTooltip, showTooltip} from '../../../../store/reducers/tooltip'; import type {TNetNodeInfo, TNetNodePeerInfo} from '../../../../types/api/netInfo'; import {cn} from '../../../../utils/cn'; -import {useAutoRefreshInterval, useTypedDispatch, useTypedSelector} from '../../../../utils/hooks'; +import {useAutoRefreshInterval, useTypedDispatch} from '../../../../utils/hooks'; +import {useWithProblemsQueryParam} from '../../../../utils/hooks/useWithProblemsQueryParam'; import {NodeNetwork} from './NodeNetwork/NodeNetwork'; import {getConnectedNodesCount} from './utils'; @@ -33,8 +29,7 @@ interface NetworkProps { } export function Network({database, databaseFullPath}: NetworkProps) { const [autoRefreshInterval] = useAutoRefreshInterval(); - const filter = useTypedSelector(selectProblemFilter); - const dispatch = useTypedDispatch(); + const {withProblems, handleWithProblemsChange} = useWithProblemsQueryParam(); const [clickedNode, setClickedNode] = React.useState(); const [showId, setShowId] = React.useState(false); @@ -75,10 +70,8 @@ export function Network({database, databaseFullPath}: NetworkProps) {
{ - dispatch(changeFilter(v)); - }} + value={withProblems} + onChange={handleWithProblemsChange} className={b('problem-filter')} />
@@ -164,7 +157,7 @@ interface NodesProps { onClickNode: (node: TNetNodeInfo | undefined) => void; } function Nodes({nodes, isRight, showId, showRacks, clickedNode, onClickNode}: NodesProps) { - const filter = useTypedSelector(selectProblemFilter); + const {withProblems} = useWithProblemsQueryParam(); const dispatch = useTypedDispatch(); let problemNodesCount = 0; @@ -188,9 +181,8 @@ function Nodes({nodes, isRight, showId, showRacks, clickedNode, onClickNode}: No } if ( - (filter === ProblemFilterValues.PROBLEMS && - capacity !== connected) || - filter === ProblemFilterValues.ALL || + (withProblems && capacity !== connected) || + !withProblems || isRight ) { problemNodesCount++; @@ -248,9 +240,8 @@ function Nodes({nodes, isRight, showId, showRacks, clickedNode, onClickNode}: No } if ( - (filter === ProblemFilterValues.PROBLEMS && - capacity !== connected) || - filter === ProblemFilterValues.ALL || + (withProblems && capacity !== connected) || + !withProblems || isRight ) { problemNodesCount++; @@ -301,7 +292,7 @@ function Nodes({nodes, isRight, showId, showRacks, clickedNode, onClickNode}: No ); }); - if (filter === ProblemFilterValues.PROBLEMS && problemNodesCount === 0) { + if (withProblems && problemNodesCount === 0) { return ; } else { return result; diff --git a/src/containers/Tenants/Tenants.tsx b/src/containers/Tenants/Tenants.tsx index 0908b10d17..4105c397c5 100644 --- a/src/containers/Tenants/Tenants.tsx +++ b/src/containers/Tenants/Tenants.tsx @@ -10,7 +10,7 @@ import {EntitiesCount} from '../../components/EntitiesCount'; import {ResponseError} from '../../components/Errors/ResponseError'; import {Illustration} from '../../components/Illustration'; import {PoolsGraph} from '../../components/PoolsGraph/PoolsGraph'; -import {ProblemFilter} from '../../components/ProblemFilter'; +import {ProblemFilter} from '../../components/ProblemFilter/ProblemFilter'; import {ResizeableDataTable} from '../../components/ResizeableDataTable/ResizeableDataTable'; import {Search} from '../../components/Search'; import {TableWithControlsLayout} from '../../components/TableWithControlsLayout/TableWithControlsLayout'; @@ -22,17 +22,11 @@ import { useEditDatabaseFeatureAvailable, } from '../../store/reducers/capabilities/hooks'; import { - ProblemFilterValues, - changeFilter, - selectProblemFilter, -} from '../../store/reducers/settings/settings'; -import type {ProblemFilterValue} from '../../store/reducers/settings/types'; -import { - selectFilteredTenants, - selectTenants, - selectTenantsSearchValue, -} from '../../store/reducers/tenants/selectors'; -import {setSearchValue, tenantsApi} from '../../store/reducers/tenants/tenants'; + filterTenantsByDomain, + filterTenantsByProblems, + filterTenantsBySearch, +} from '../../store/reducers/tenants/filters'; +import {tenantsApi} from '../../store/reducers/tenants/tenants'; import type {PreparedTenant} from '../../store/reducers/tenants/types'; import type {AdditionalTenantsProps} from '../../types/additionalProps'; import {State} from '../../types/api/tenant'; @@ -42,6 +36,7 @@ import {cn} from '../../utils/cn'; import { DEFAULT_TABLE_SETTINGS, EMPTY_DATA_PLACEHOLDER, + SHOW_DOMAIN_DATABASE_KEY, SHOW_NETWORK_UTILIZATION, } from '../../utils/constants'; import { @@ -49,16 +44,12 @@ import { formatNumber, formatStorageValuesToGb, } from '../../utils/dataFormatters/dataFormatters'; -import { - useAutoRefreshInterval, - useSetting, - useTypedDispatch, - useTypedSelector, -} from '../../utils/hooks'; +import {useAutoRefreshInterval, useSetting} from '../../utils/hooks'; import {useClusterNameFromQuery} from '../../utils/hooks/useDatabaseFromQuery'; import {isNumeric} from '../../utils/utils'; import i18n from './i18n'; +import {useTenantsQueryParams} from './useTenantsQueryParams'; import './Tenants.scss'; @@ -89,8 +80,6 @@ interface TenantsProps { } export const Tenants = ({additionalTenantsProps, scrollContainerRef}: TenantsProps) => { - const dispatch = useTypedDispatch(); - const clusterName = useClusterNameFromQuery(); const isMetaDatabasesAvailable = useDatabasesAvailable(); const [autoRefreshInterval] = useAutoRefreshInterval(); @@ -109,20 +98,27 @@ export const Tenants = ({additionalTenantsProps, scrollContainerRef}: TenantsPro const isDeleteDBAvailable = useDeleteDatabaseFeatureAvailable() && uiFactory.onDeleteDB !== undefined; - const tenants = useTypedSelector((state) => selectTenants(state, clusterName)); - const searchValue = useTypedSelector(selectTenantsSearchValue); - const filteredTenants = useTypedSelector((state) => selectFilteredTenants(state, clusterName)); - const problemFilter = useTypedSelector(selectProblemFilter); + const {search, withProblems, handleSearchChange, handleWithProblemsChange} = + useTenantsQueryParams(); const [showNetworkUtilization] = useSetting(SHOW_NETWORK_UTILIZATION); + const [showDomainDatabase] = useSetting(SHOW_DOMAIN_DATABASE_KEY); - const handleProblemFilterChange = (value: ProblemFilterValue) => { - dispatch(changeFilter(value)); - }; + // We should apply domain filter before other filters + // It is done to ensure proper entities count + // It should be 8/8 instead of 8/9 when no filters applied but show domain setting is off + const tenants = React.useMemo(() => { + const rawTenants = currentData ?? []; - const handleSearchChange = (value: string) => { - dispatch(setSearchValue(value)); - }; + return filterTenantsByDomain(rawTenants, showDomainDatabase); + }, [currentData, showDomainDatabase]); + + const filteredTenants = React.useMemo(() => { + const filteredByProblems = filterTenantsByProblems(tenants, withProblems); + const filteredBySearch = filterTenantsBySearch(filteredByProblems, search); + + return filteredBySearch; + }, [tenants, withProblems, search]); const renderCreateDBButton = () => { const buttonAvailable = isCreateDBAvailable && clusterName; @@ -143,14 +139,14 @@ export const Tenants = ({additionalTenantsProps, scrollContainerRef}: TenantsPro return ( - + ; } @@ -338,7 +334,7 @@ export const Tenants = ({additionalTenantsProps, scrollContainerRef}: TenantsPro {currentData ? renderTable() : null} diff --git a/src/containers/Tenants/useTenantsQueryParams.ts b/src/containers/Tenants/useTenantsQueryParams.ts new file mode 100644 index 0000000000..22af36cba6 --- /dev/null +++ b/src/containers/Tenants/useTenantsQueryParams.ts @@ -0,0 +1,31 @@ +import React from 'react'; + +import {BooleanParam, StringParam, useQueryParams} from 'use-query-params'; + +export function useTenantsQueryParams() { + const [{withProblems, search}, setQueryParams] = useQueryParams({ + withProblems: BooleanParam, + search: StringParam, + }); + + const handleWithProblemsChange = React.useCallback( + (value: boolean) => { + setQueryParams({withProblems: value || undefined}, 'replaceIn'); + }, + [setQueryParams], + ); + const handleSearchChange = React.useCallback( + (value: string) => { + setQueryParams({search: value || undefined}, 'replaceIn'); + }, + [setQueryParams], + ); + + return { + withProblems: Boolean(withProblems), + search: search || '', + + handleWithProblemsChange, + handleSearchChange, + }; +} diff --git a/src/store/reducers/index.ts b/src/store/reducers/index.ts index 249f83a90b..a395229e56 100644 --- a/src/store/reducers/index.ts +++ b/src/store/reducers/index.ts @@ -16,7 +16,6 @@ import settings from './settings/settings'; import shardsWorkload from './shardsWorkload/shardsWorkload'; import singleClusterMode from './singleClusterMode'; import tenant from './tenant/tenant'; -import tenants from './tenants/tenants'; import tooltip from './tooltip'; export const rootReducer = { @@ -26,7 +25,6 @@ export const rootReducer = { tenant, tooltip, schema, - tenants, partitions, query, heatmap, diff --git a/src/store/reducers/nodes/types.ts b/src/store/reducers/nodes/types.ts index 5ca4d071b4..023ab558a3 100644 --- a/src/store/reducers/nodes/types.ts +++ b/src/store/reducers/nodes/types.ts @@ -1,10 +1,9 @@ import type {NodesGroupByField, NodesPeerRole} from '../../../types/api/nodes'; import type {NodesUptimeFilterValues} from '../../../utils/nodes'; -import type {ProblemFilterValue} from '../settings/types'; export interface NodesFilters { searchValue: string; - problemFilter: ProblemFilterValue; + withProblems: boolean; uptimeFilter: NodesUptimeFilterValues; peerRoleFilter?: NodesPeerRole; diff --git a/src/store/reducers/settings/hooks.ts b/src/store/reducers/settings/hooks.ts deleted file mode 100644 index ebe760b40a..0000000000 --- a/src/store/reducers/settings/hooks.ts +++ /dev/null @@ -1,19 +0,0 @@ -import {useTypedDispatch, useTypedSelector} from '../../../utils/hooks'; - -import {changeFilter, selectProblemFilter} from './settings'; -import type {ProblemFilterValue} from './types'; - -export function useProblemFilter() { - const dispatch = useTypedDispatch(); - - const problemFilter = useTypedSelector(selectProblemFilter); - - const handleProblemFilterChange = (value: ProblemFilterValue) => { - dispatch(changeFilter(value)); - }; - - return { - problemFilter, - handleProblemFilterChange, - }; -} diff --git a/src/store/reducers/settings/settings.ts b/src/store/reducers/settings/settings.ts index 260bbd5dde..8a38f988a4 100644 --- a/src/store/reducers/settings/settings.ts +++ b/src/store/reducers/settings/settings.ts @@ -5,18 +5,12 @@ import {DEFAULT_USER_SETTINGS, settingsManager} from '../../../services/settings import {parseJson} from '../../../utils/utils'; import type {AppDispatch} from '../../defaultStore'; -import type {ProblemFilterValue, SettingsState} from './types'; - -export const ProblemFilterValues = { - ALL: 'All', - PROBLEMS: 'With problems', -} as const; +import type {SettingsState} from './types'; const userSettings = settingsManager.extractSettingsFromLS(DEFAULT_USER_SETTINGS); const systemSettings = window.systemSettings || {}; export const initialState: SettingsState = { - problemFilter: ProblemFilterValues.ALL, userSettings, systemSettings, }; @@ -25,21 +19,16 @@ const settingsSlice = createSlice({ name: 'settings', initialState, reducers: (create) => ({ - changeFilter: create.reducer((state, action) => { - state.problemFilter = action.payload; - }), setSettingValue: create.reducer<{name: string; value: unknown}>((state, action) => { state.userSettings[action.payload.name] = action.payload.value; }), }), selectors: { getSettingValue: (state, name: string) => state.userSettings[name], - selectProblemFilter: (state) => state.problemFilter, }, }); -export const {changeFilter} = settingsSlice.actions; -export const {getSettingValue, selectProblemFilter} = settingsSlice.selectors; +export const {getSettingValue} = settingsSlice.selectors; export const setSettingValue = (name: string, value: unknown) => { return (dispatch: AppDispatch) => { diff --git a/src/store/reducers/settings/types.ts b/src/store/reducers/settings/types.ts index 40dfcecd3f..62f752d92d 100644 --- a/src/store/reducers/settings/types.ts +++ b/src/store/reducers/settings/types.ts @@ -1,12 +1,6 @@ import type {SettingsObject} from '../../../services/settings'; -import type {ValueOf} from '../../../types/common'; - -import type {ProblemFilterValues} from './settings'; - -export type ProblemFilterValue = ValueOf; export interface SettingsState { - problemFilter: ProblemFilterValue; userSettings: SettingsObject; systemSettings: SettingsObject; } diff --git a/src/store/reducers/tenants/filters.ts b/src/store/reducers/tenants/filters.ts new file mode 100644 index 0000000000..63d75767bb --- /dev/null +++ b/src/store/reducers/tenants/filters.ts @@ -0,0 +1,26 @@ +import escapeRegExp from 'lodash/escapeRegExp'; + +import {EFlag} from '../../../types/api/enums'; + +import type {PreparedTenant} from './types'; + +export function filterTenantsByProblems(tenants: PreparedTenant[], withProblems: boolean) { + if (withProblems) { + return tenants.filter((tenant) => { + return tenant.Overall && tenant.Overall !== EFlag.Green; + }); + } + return tenants; +} +export function filterTenantsBySearch(tenants: PreparedTenant[], search: string) { + return tenants.filter((item) => { + const re = new RegExp(escapeRegExp(search), 'i'); + return re.test(item.Name || '') || re.test(item.controlPlaneName); + }); +} +export function filterTenantsByDomain(tenants: PreparedTenant[], showDomainDatabase?: boolean) { + if (!showDomainDatabase && tenants.length > 1) { + return tenants.filter((item) => item.Type !== 'Domain'); + } + return tenants; +} diff --git a/src/store/reducers/tenants/selectors.ts b/src/store/reducers/tenants/selectors.ts deleted file mode 100644 index 54dadf15be..0000000000 --- a/src/store/reducers/tenants/selectors.ts +++ /dev/null @@ -1,63 +0,0 @@ -import {createSelector} from '@reduxjs/toolkit'; -import escapeRegExp from 'lodash/escapeRegExp'; - -import type {RootState} from '../..'; -import {EFlag} from '../../../types/api/enums'; -import {SHOW_DOMAIN_DATABASE_KEY} from '../../../utils/constants'; -import {ProblemFilterValues, getSettingValue, selectProblemFilter} from '../settings/settings'; -import type {ProblemFilterValue} from '../settings/types'; - -import {tenantsApi} from './tenants'; -import type {PreparedTenant, TenantsStateSlice} from './types'; - -// ==== Filters ==== - -const filterTenantsByProblems = (tenants: PreparedTenant[], problemFilter: ProblemFilterValue) => { - if (problemFilter === ProblemFilterValues.ALL) { - return tenants; - } - - return tenants.filter((tenant) => { - return tenant.Overall && tenant.Overall !== EFlag.Green; - }); -}; - -const filteredTenantsBySearch = (tenants: PreparedTenant[], searchQuery: string) => { - return tenants.filter((item) => { - const re = new RegExp(escapeRegExp(searchQuery), 'i'); - return re.test(item.Name || '') || re.test(item.controlPlaneName); - }); -}; - -// ==== Simple selectors ==== -const createGetTenantsInfoSelector = createSelector( - (clusterName: string | undefined) => clusterName, - (clusterName) => tenantsApi.endpoints.getTenantsInfo.select({clusterName}), -); - -export const selectTenants = createSelector( - (state: RootState) => state, - (_state: RootState, clusterName: string | undefined) => - createGetTenantsInfoSelector(clusterName), - (state: RootState) => getSettingValue(state, SHOW_DOMAIN_DATABASE_KEY), - (state: RootState, selectTenantsInfo, showDomainDatabase) => { - const result = selectTenantsInfo(state).data ?? []; - - return !showDomainDatabase && result.length > 1 - ? result.filter((item) => item.Type !== 'Domain') - : result; - }, -); -export const selectTenantsSearchValue = (state: TenantsStateSlice) => state.tenants.searchValue; - -// ==== Complex selectors ==== - -export const selectFilteredTenants = createSelector( - [selectTenants, selectProblemFilter, selectTenantsSearchValue], - (tenants, problemFilter, searchQuery) => { - let result = filterTenantsByProblems(tenants, problemFilter); - result = filteredTenantsBySearch(result, searchQuery); - - return result; - }, -); diff --git a/src/store/reducers/tenants/tenants.ts b/src/store/reducers/tenants/tenants.ts index b04297dfc0..ff34fd9d45 100644 --- a/src/store/reducers/tenants/tenants.ts +++ b/src/store/reducers/tenants/tenants.ts @@ -1,27 +1,9 @@ -import {createSlice} from '@reduxjs/toolkit'; -import type {PayloadAction} from '@reduxjs/toolkit'; - import type {TTenantInfo} from '../../../types/api/tenant'; import {api} from '../api'; -import type {PreparedTenant, TenantsState} from './types'; +import type {PreparedTenant} from './types'; import {prepareTenants} from './utils'; -const initialState: TenantsState = {searchValue: ''}; - -const slice = createSlice({ - name: 'tenants', - initialState, - reducers: { - setSearchValue: (state, action: PayloadAction) => { - state.searchValue = action.payload; - }, - }, -}); - -export const {setSearchValue} = slice.actions; -export default slice.reducer; - export const tenantsApi = api.injectEndpoints({ endpoints: (build) => ({ getTenantsInfo: build.query({ diff --git a/src/store/reducers/tenants/types.ts b/src/store/reducers/tenants/types.ts index 4675d785db..f92f3f4305 100644 --- a/src/store/reducers/tenants/types.ts +++ b/src/store/reducers/tenants/types.ts @@ -14,12 +14,4 @@ export interface PreparedTenant extends TTenant { groupsCount: number; } -export interface TenantsState { - searchValue: string; -} - -export interface TenantsStateSlice { - tenants: TenantsState; -} - export type MetricStatus = ValueOf; diff --git a/src/store/state-url-mapping.ts b/src/store/state-url-mapping.ts index 8aea912df4..df1933729a 100644 --- a/src/store/state-url-mapping.ts +++ b/src/store/state-url-mapping.ts @@ -12,16 +12,11 @@ import {parseQuery} from 'redux-location-state/lib/parseQuery'; import {stateToParams} from 'redux-location-state/lib/stateToParams'; import {initialState as initialHeatmapState} from './reducers/heatmap'; -import {initialState as initialSettingsState} from './reducers/settings/settings'; import {initialState as initialTenantState} from './reducers/tenant/tenant'; export const paramSetup = { - global: { - problemFilter: { - stateKey: 'settings.problemFilter', - initialState: initialSettingsState.problemFilter, - }, - }, + // Do not delete, without `global` params redux-location-state goes crazy + global: {}, '/tenant': { sort: { stateKey: 'heatmap.sort', @@ -122,18 +117,6 @@ export const paramSetup = { stateKey: 'partitions.selectedConsumer', }, }, - '/cluster/tenants': { - search: { - stateKey: 'tenants.searchValue', - initialState: '', - }, - }, - '/*/cluster/tenants': { - search: { - stateKey: 'tenants.searchValue', - initialState: '', - }, - }, } as const; function mergeLocationToState(state: S, location: Pick): S { diff --git a/src/utils/hooks/useWithProblemsQueryParam.ts b/src/utils/hooks/useWithProblemsQueryParam.ts new file mode 100644 index 0000000000..c68c5a2937 --- /dev/null +++ b/src/utils/hooks/useWithProblemsQueryParam.ts @@ -0,0 +1,21 @@ +import React from 'react'; + +import {BooleanParam, useQueryParams} from 'use-query-params'; + +export function useWithProblemsQueryParam() { + const [{withProblems}, setQueryParams] = useQueryParams({ + withProblems: BooleanParam, + }); + + const handleWithProblemsChange = React.useCallback( + (value: boolean) => { + setQueryParams({withProblems: value || undefined}, 'replaceIn'); + }, + [setQueryParams], + ); + + return { + withProblems: Boolean(withProblems), + handleWithProblemsChange, + }; +} diff --git a/src/utils/nodes.ts b/src/utils/nodes.ts index 26a3c3e495..53414ef3ac 100644 --- a/src/utils/nodes.ts +++ b/src/utils/nodes.ts @@ -1,7 +1,5 @@ import {z} from 'zod'; -import {ProblemFilterValues} from '../store/reducers/settings/settings'; -import type {ProblemFilterValue} from '../store/reducers/settings/types'; import {EFlag} from '../types/api/enums'; import type {TSystemStateInfo} from '../types/api/nodes'; import type {TNodeInfo} from '../types/api/nodesList'; @@ -92,10 +90,6 @@ export function prepareNodeSystemState( }; } -export const getProblemParamValue = (problemFilter: ProblemFilterValue | undefined) => { - return problemFilter === ProblemFilterValues.PROBLEMS; -}; - export const getUptimeParamValue = (nodesUptimeFilter: NodesUptimeFilterValues | undefined) => { return nodesUptimeFilter === NodesUptimeFilterValues.SmallUptime ? HOUR_IN_SECONDS : undefined; }; From 1d0e5330b351f20b79d824b652186906ab8b21de Mon Sep 17 00:00:00 2001 From: mufazalov Date: Thu, 6 Nov 2025 11:11:52 +0300 Subject: [PATCH 2/4] fix: copilot review --- .../ProblemFilter/ProblemFilter.tsx | 11 +++-- .../Nodes/useNodesPageQueryParams.ts | 47 +++++++++++++------ 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/src/components/ProblemFilter/ProblemFilter.tsx b/src/components/ProblemFilter/ProblemFilter.tsx index bc0afc4f80..ffb87d5f52 100644 --- a/src/components/ProblemFilter/ProblemFilter.tsx +++ b/src/components/ProblemFilter/ProblemFilter.tsx @@ -1,3 +1,5 @@ +import React from 'react'; + import {SegmentedRadioGroup} from '@gravity-ui/uikit'; import i18n from './i18n'; @@ -9,9 +11,12 @@ interface ProblemFilterProps { } export function ProblemFilter({value, onChange, className}: ProblemFilterProps) { - const handleValueChange = (value: string) => { - onChange(value === 'true'); - }; + const handleValueChange = React.useCallback( + (value: string) => { + onChange(value === 'true'); + }, + [onChange], + ); return ( { - setQueryParams({search: value || undefined}, 'replaceIn'); - }; - const handleUptimeFilterChange = (value: NodesUptimeFilterValues) => { - setQueryParams({uptimeFilter: value}, 'replaceIn'); - }; - const handlePeerRoleFilterChange = (value: NodesPeerRole) => { - setQueryParams({peerRole: value}, 'replaceIn'); - }; - const handleGroupByParamChange = (value: string) => { - setQueryParams({nodesGroupBy: value}, 'replaceIn'); - }; - const handleWithProblemsChange = (value: boolean) => { - setQueryParams({withProblems: value || undefined}, 'replaceIn'); - }; + const handleSearchQueryChange = React.useCallback( + (value: string) => { + setQueryParams({search: value || undefined}, 'replaceIn'); + }, + [setQueryParams], + ); + const handleUptimeFilterChange = React.useCallback( + (value: NodesUptimeFilterValues) => { + setQueryParams({uptimeFilter: value}, 'replaceIn'); + }, + [setQueryParams], + ); + const handlePeerRoleFilterChange = React.useCallback( + (value: NodesPeerRole) => { + setQueryParams({peerRole: value}, 'replaceIn'); + }, + [setQueryParams], + ); + const handleGroupByParamChange = React.useCallback( + (value: string) => { + setQueryParams({nodesGroupBy: value}, 'replaceIn'); + }, + [setQueryParams], + ); + const handleWithProblemsChange = React.useCallback( + (value: boolean) => { + setQueryParams({withProblems: value || undefined}, 'replaceIn'); + }, + [setQueryParams], + ); return { uptimeFilter, From b257f1130f044c932fbbf01bc9e91987a038d492 Mon Sep 17 00:00:00 2001 From: mufazalov Date: Thu, 6 Nov 2025 11:40:11 +0300 Subject: [PATCH 3/4] fix: tests --- tests/suites/nodes/nodes.test.ts | 2 +- tests/suites/tenants/tenants.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/suites/nodes/nodes.test.ts b/tests/suites/nodes/nodes.test.ts index e2b0a65413..3dbdf9fc71 100644 --- a/tests/suites/nodes/nodes.test.ts +++ b/tests/suites/nodes/nodes.test.ts @@ -16,7 +16,7 @@ test.describe('Test Nodes page', async () => { const nodesPage = new NodesPage(page); // Get table with all nodes - await nodesPage.goto({problemFilter: 'All'}); + await nodesPage.goto(); // Check if table is present await expect(nodesPage.table).toBeVisible(); diff --git a/tests/suites/tenants/tenants.test.ts b/tests/suites/tenants/tenants.test.ts index 32f1824ce0..c56fcb6324 100644 --- a/tests/suites/tenants/tenants.test.ts +++ b/tests/suites/tenants/tenants.test.ts @@ -13,7 +13,7 @@ test.describe('Test Tenants page', async () => { const tenantsPage = new TenantsPage(page); // Get table with all tenants - await tenantsPage.goto({problemFilter: 'All'}); + await tenantsPage.goto(); // Check if table is present await expect(tenantsPage.table).toBeVisible(); From 5434fce20e0318b08b9b2e58e7935bfa27dc28ff Mon Sep 17 00:00:00 2001 From: mufazalov Date: Thu, 6 Nov 2025 11:56:54 +0300 Subject: [PATCH 4/4] fix: i18n keysets --- src/components/ProblemFilter/ProblemFilter.tsx | 6 ++++-- src/components/ProblemFilter/i18n/en.json | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/ProblemFilter/ProblemFilter.tsx b/src/components/ProblemFilter/ProblemFilter.tsx index ffb87d5f52..6e39ae53d5 100644 --- a/src/components/ProblemFilter/ProblemFilter.tsx +++ b/src/components/ProblemFilter/ProblemFilter.tsx @@ -24,9 +24,11 @@ export function ProblemFilter({value, onChange, className}: ProblemFilterProps) onUpdate={handleValueChange} className={className} > - {i18n('all')} + + {i18n('value_all')} + - {i18n('with-problems')} + {i18n('value_with-problems')} ); diff --git a/src/components/ProblemFilter/i18n/en.json b/src/components/ProblemFilter/i18n/en.json index 5880908a40..9149032b7d 100644 --- a/src/components/ProblemFilter/i18n/en.json +++ b/src/components/ProblemFilter/i18n/en.json @@ -1,4 +1,4 @@ { - "all": "All", - "with-problems": "With problems" + "value_all": "All", + "value_with-problems": "With problems" }