diff --git a/package.json b/package.json index 6269f9d17..77f6963c2 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,8 @@ "typecheck": "tsc --noEmit", "prepare": "husky", "test:e2e:install": "npx playwright install --with-deps", - "test:e2e": "npx playwright test --config=playwright.config.ts" + "test:e2e": "npx playwright test --config=playwright.config.ts", + "test:e2e:local": "PLAYWRIGHT_BASE_URL=http://localhost:3000/ npm run test:e2e" }, "lint-staged": { "*.{scss, css}": [ diff --git a/src/components/PaginatedTable/types.ts b/src/components/PaginatedTable/types.ts index 4e4092e7f..b20b9778f 100644 --- a/src/components/PaginatedTable/types.ts +++ b/src/components/PaginatedTable/types.ts @@ -67,4 +67,4 @@ export type RenderControls = (params: ControlsParams) => React.ReactNode; export type RenderEmptyDataMessage = () => React.ReactNode; export type RenderErrorMessage = (error: IResponseError) => React.ReactNode; -export type GetRowClassName = (row: T) => string | undefined; +export type GetRowClassName = (row: T) => string; diff --git a/src/components/Search/Search.tsx b/src/components/Search/Search.tsx index 7beef51eb..fb259ae14 100644 --- a/src/components/Search/Search.tsx +++ b/src/components/Search/Search.tsx @@ -11,6 +11,7 @@ const b = cn('ydb-search'); interface SearchProps { onChange: (value: string) => void; value?: string; + width?: React.CSSProperties['width']; className?: string; debounce?: number; placeholder?: string; @@ -19,6 +20,7 @@ interface SearchProps { export const Search = ({ onChange, value = '', + width, className, debounce = 200, placeholder, @@ -50,6 +52,7 @@ export const Search = ({ = { NodeId: ['NodeId'], + SystemState: ['SystemState'], Host: ['Host', 'Rack', 'Database', 'SystemState'], + Database: ['Database'], NodeName: ['NodeName'], DC: ['DC'], Rack: ['Rack'], diff --git a/src/components/nodesColumns/i18n/en.json b/src/components/nodesColumns/i18n/en.json index 78a282b59..be32e5332 100644 --- a/src/components/nodesColumns/i18n/en.json +++ b/src/components/nodesColumns/i18n/en.json @@ -1,6 +1,8 @@ { "node-id": "Node ID", + "system-state": "System State", "host": "Host", + "database": "Database", "node-name": "Node Name", "dc": "DC", "rack": "Rack", diff --git a/src/containers/Nodes/Nodes.scss b/src/containers/Nodes/Nodes.scss index de91414dd..15459b7d2 100644 --- a/src/containers/Nodes/Nodes.scss +++ b/src/containers/Nodes/Nodes.scss @@ -15,4 +15,8 @@ &__node_unavailable { opacity: 0.6; } + + &__groups-wrapper { + padding-right: 20px; + } } diff --git a/src/containers/Nodes/Nodes.tsx b/src/containers/Nodes/Nodes.tsx index eedee76d7..c60d6ed91 100644 --- a/src/containers/Nodes/Nodes.tsx +++ b/src/containers/Nodes/Nodes.tsx @@ -1,51 +1,31 @@ import React from 'react'; import {ASCENDING} from '@gravity-ui/react-data-table/build/esm/lib/constants'; -import {TableColumnSetup} from '@gravity-ui/uikit'; -import {StringParam, useQueryParams} from 'use-query-params'; -import {EntitiesCount} from '../../components/EntitiesCount'; import {AccessDenied} from '../../components/Errors/403'; import {isAccessError} from '../../components/Errors/PageError/PageError'; import {ResponseError} from '../../components/Errors/ResponseError'; import {Illustration} from '../../components/Illustration'; -import {ProblemFilter} from '../../components/ProblemFilter'; import {ResizeableDataTable} from '../../components/ResizeableDataTable/ResizeableDataTable'; -import {Search} from '../../components/Search'; import {TableWithControlsLayout} from '../../components/TableWithControlsLayout/TableWithControlsLayout'; -import {UptimeFilter} from '../../components/UptimeFIlter'; import {NODES_COLUMNS_WIDTH_LS_KEY} from '../../components/nodesColumns/constants'; import {nodesApi} from '../../store/reducers/nodes/nodes'; import {filterNodes} from '../../store/reducers/nodes/selectors'; import type {NodesSortParams} from '../../store/reducers/nodes/types'; -import { - ProblemFilterValues, - changeFilter, - selectProblemFilter, -} from '../../store/reducers/settings/settings'; -import type {ProblemFilterValue} from '../../store/reducers/settings/types'; +import {useProblemFilter} from '../../store/reducers/settings/hooks'; import type {AdditionalNodesProps} from '../../types/additionalProps'; -import {cn} from '../../utils/cn'; import {DEFAULT_TABLE_SETTINGS} from '../../utils/constants'; -import { - useAutoRefreshInterval, - useTableSort, - useTypedDispatch, - useTypedSelector, -} from '../../utils/hooks'; -import { - NodesUptimeFilterValues, - isUnavailableNode, - nodesUptimeFilterValuesSchema, -} from '../../utils/nodes'; +import {useAutoRefreshInterval, useTableSort} from '../../utils/hooks'; +import {NodesUptimeFilterValues} from '../../utils/nodes'; +import {NodesControls} from './NodesControls/NodesControls'; import {useNodesSelectedColumns} from './columns/hooks'; import i18n from './i18n'; +import {getRowClassName} from './shared'; +import {useNodesPageQueryParams} from './useNodesPageQueryParams'; import './Nodes.scss'; -const b = cn('ydb-nodes'); - interface NodesProps { path?: string; database?: string; @@ -53,16 +33,9 @@ interface NodesProps { } export const Nodes = ({path, database, additionalNodesProps = {}}: NodesProps) => { - const [queryParams, setQueryParams] = useQueryParams({ - uptimeFilter: StringParam, - search: StringParam, - }); - const uptimeFilter = nodesUptimeFilterValuesSchema.parse(queryParams.uptimeFilter); - const searchValue = queryParams.search ?? ''; - - const dispatch = useTypedDispatch(); + const {searchValue, uptimeFilter} = useNodesPageQueryParams(); + const {problemFilter} = useProblemFilter(); - const problemFilter = useTypedSelector(selectProblemFilter); const [autoRefreshInterval] = useAutoRefreshInterval(); const {columnsToShow, columnsToSelect, setColumns} = useNodesSelectedColumns({ @@ -84,18 +57,6 @@ export const Nodes = ({path, database, additionalNodesProps = {}}: NodesProps) = setSortValue(sortParams as NodesSortParams); }); - const handleSearchQueryChange = (value: string) => { - setQueryParams({search: value || undefined}, 'replaceIn'); - }; - - const handleProblemFilterChange = (value: ProblemFilterValue) => { - dispatch(changeFilter(value)); - }; - - const handleUptimeFilterChange = (value: NodesUptimeFilterValues) => { - setQueryParams({uptimeFilter: value}, 'replaceIn'); - }; - const nodes = React.useMemo(() => { return filterNodes(data?.Nodes, {searchValue, uptimeFilter, problemFilter}); }, [data, searchValue, uptimeFilter, problemFilter]); @@ -104,38 +65,19 @@ export const Nodes = ({path, database, additionalNodesProps = {}}: NodesProps) = const renderControls = () => { return ( - - - - - - - + ); }; const renderTable = () => { if (nodes.length === 0) { - if ( - problemFilter !== ProblemFilterValues.ALL || - uptimeFilter !== NodesUptimeFilterValues.All - ) { + if (problemFilter !== 'All' || uptimeFilter !== NodesUptimeFilterValues.All) { return ; } } @@ -149,7 +91,7 @@ export const Nodes = ({path, database, additionalNodesProps = {}}: NodesProps) = sortOrder={sort} onSort={handleSort} emptyDataMessage={i18n('empty.default')} - rowClassName={(row) => b('node', {unavailable: isUnavailableNode(row)})} + rowClassName={getRowClassName} /> ); }; diff --git a/src/containers/Nodes/NodesControls/NodesControls.tsx b/src/containers/Nodes/NodesControls/NodesControls.tsx new file mode 100644 index 000000000..ebf4d851d --- /dev/null +++ b/src/containers/Nodes/NodesControls/NodesControls.tsx @@ -0,0 +1,100 @@ +import React from 'react'; + +import type {TableColumnSetupItem} from '@gravity-ui/uikit'; +import {Select, TableColumnSetup, Text} from '@gravity-ui/uikit'; + +import {EntitiesCount} from '../../../components/EntitiesCount'; +import {ProblemFilter} from '../../../components/ProblemFilter'; +import {Search} from '../../../components/Search'; +import {UptimeFilter} from '../../../components/UptimeFIlter'; +import {useViewerNodesHandlerHasGroupingBySystemState} from '../../../store/reducers/capabilities/hooks'; +import {useProblemFilter} from '../../../store/reducers/settings/hooks'; +import {getNodesGroupByOptions} from '../columns/constants'; +import i18n from '../i18n'; +import {b} from '../shared'; +import {useNodesPageQueryParams} from '../useNodesPageQueryParams'; + +interface NodesControlsProps { + withGroupBySelect?: boolean; + + columnsToSelect: TableColumnSetupItem[]; + handleSelectedColumnsUpdate: (updated: TableColumnSetupItem[]) => void; + + entitiesCountCurrent: number; + entitiesCountTotal?: number; + entitiesLoading: boolean; +} + +export function NodesControls({ + withGroupBySelect, + + columnsToSelect, + handleSelectedColumnsUpdate, + + entitiesCountCurrent, + entitiesCountTotal, + entitiesLoading, +}: NodesControlsProps) { + const { + searchValue, + uptimeFilter, + groupByParam, + + handleSearchQueryChange, + handleUptimeFilterChange, + handleGroupByParamChange, + } = useNodesPageQueryParams(); + const {problemFilter, handleProblemFilterChange} = useProblemFilter(); + + const systemStateGroupingAvailable = useViewerNodesHandlerHasGroupingBySystemState(); + const groupByoptions = getNodesGroupByOptions(systemStateGroupingAvailable); + + const handleGroupBySelectUpdate = (value: string[]) => { + handleGroupByParamChange(value[0]); + }; + + return ( + + + {systemStateGroupingAvailable && withGroupBySelect ? null : ( + + )} + {withGroupBySelect ? null : ( + + )} + + {withGroupBySelect ? ( + + {i18n('controls_group-by-placeholder')} +