Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions src/containers/Cluster/Cluster.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import type {
import {cn} from '../../utils/cn';
import {useTypedDispatch, useTypedSelector} from '../../utils/hooks';
import {parseVersionsToVersionToColorMap} from '../../utils/versions';
import {NodesWrapper} from '../Nodes/NodesWrapper';
import {Nodes} from '../Nodes/Nodes';
import {StorageWrapper} from '../Storage/StorageWrapper';
import {TabletsTable} from '../Tablets/TabletsTable';
import {Tenants} from '../Tenants/Tenants';
Expand Down Expand Up @@ -192,10 +192,7 @@ export function Cluster({
<Route
path={getLocationObjectFromHref(getClusterPath(clusterTabsIds.nodes)).pathname}
>
<NodesWrapper
parentRef={container}
additionalNodesProps={additionalNodesProps}
/>
<Nodes parentRef={container} additionalNodesProps={additionalNodesProps} />
</Route>
<Route
path={
Expand Down
298 changes: 234 additions & 64 deletions src/containers/Nodes/Nodes.tsx
Original file line number Diff line number Diff line change
@@ -1,113 +1,283 @@
import React from 'react';

import {ASCENDING} from '@gravity-ui/react-data-table/build/esm/lib/constants';

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 {ResizeableDataTable} from '../../components/ResizeableDataTable/ResizeableDataTable';
import {LoaderWrapper} from '../../components/LoaderWrapper/LoaderWrapper';
import type {Column, RenderControls} from '../../components/PaginatedTable';
import {TableWithControlsLayout} from '../../components/TableWithControlsLayout/TableWithControlsLayout';
import {NODES_COLUMNS_WIDTH_LS_KEY} from '../../components/nodesColumns/constants';
import {NODES_COLUMNS_TITLES} from '../../components/nodesColumns/constants';
import type {NodesColumnId} from '../../components/nodesColumns/constants';
import {
useCapabilitiesLoaded,
useViewerNodesHandlerHasGrouping,
} from '../../store/reducers/capabilities/hooks';
import {nodesApi} from '../../store/reducers/nodes/nodes';
import {filterNodes} from '../../store/reducers/nodes/selectors';
import type {NodesSortParams} from '../../store/reducers/nodes/types';
import type {NodesPreparedEntity} from '../../store/reducers/nodes/types';
import {useProblemFilter} from '../../store/reducers/settings/hooks';
import type {AdditionalNodesProps} from '../../types/additionalProps';
import {DEFAULT_TABLE_SETTINGS} from '../../utils/constants';
import {useAutoRefreshInterval, useTableSort} from '../../utils/hooks';
import type {NodesGroupByField} from '../../types/api/nodes';
import {useAutoRefreshInterval} from '../../utils/hooks';
import {useSelectedColumns} from '../../utils/hooks/useSelectedColumns';
import {NodesUptimeFilterValues} from '../../utils/nodes';
import {TableGroup} from '../Storage/TableGroup/TableGroup';
import {useExpandedGroups} from '../Storage/TableGroup/useExpandedTableGroups';

import {NodesControls} from './NodesControls/NodesControls';
import {useNodesSelectedColumns} from './columns/hooks';
import {NodesTable} from './NodesTable';
import {getNodesColumns} from './columns/columns';
import {
ALL_NODES_GROUP_BY_PARAMS,
DEFAULT_NODES_COLUMNS,
NODES_TABLE_SELECTED_COLUMNS_LS_KEY,
REQUIRED_NODES_COLUMNS,
} from './columns/constants';
import i18n from './i18n';
import {getRowClassName} from './shared';
import {b} from './shared';
import {useNodesPageQueryParams} from './useNodesPageQueryParams';

import './Nodes.scss';

interface NodesProps {
export interface NodesProps {
path?: string;
database?: string;
parentRef: React.RefObject<HTMLElement>;
additionalNodesProps?: AdditionalNodesProps;

columns?: Column<NodesPreparedEntity>[];
defaultColumnsIds?: NodesColumnId[];
requiredColumnsIds?: NodesColumnId[];
selectedColumnsKey?: string;
groupByParams?: NodesGroupByField[];
}

export const Nodes = ({path, database, additionalNodesProps = {}}: NodesProps) => {
const {searchValue, uptimeFilter} = useNodesPageQueryParams(undefined);
const {problemFilter} = useProblemFilter();
export function Nodes({
path,
database,
parentRef,
additionalNodesProps,
columns = getNodesColumns({database, getNodeRef: additionalNodesProps?.getNodeRef}),
defaultColumnsIds = DEFAULT_NODES_COLUMNS,
requiredColumnsIds = REQUIRED_NODES_COLUMNS,
selectedColumnsKey = NODES_TABLE_SELECTED_COLUMNS_LS_KEY,
groupByParams = ALL_NODES_GROUP_BY_PARAMS,
}: NodesProps) {
const {uptimeFilter, groupByParam, handleUptimeFilterChange} =
useNodesPageQueryParams(groupByParams);
const {problemFilter, handleProblemFilterChange} = useProblemFilter();

const [autoRefreshInterval] = useAutoRefreshInterval();
const capabilitiesLoaded = useCapabilitiesLoaded();
const viewerNodesHandlerHasGrouping = useViewerNodesHandlerHasGrouping();

const {columnsToShow, columnsToSelect, setColumns} = useNodesSelectedColumns({
getNodeRef: additionalNodesProps.getNodeRef,
database,
});
// Other filters do not fit with grouping
// Reset them if grouping available
React.useEffect(() => {
if (
viewerNodesHandlerHasGrouping &&
(problemFilter !== 'All' || uptimeFilter !== NodesUptimeFilterValues.All)
) {
handleProblemFilterChange('All');
handleUptimeFilterChange(NodesUptimeFilterValues.All);
}
}, [
handleProblemFilterChange,
handleUptimeFilterChange,
problemFilter,
uptimeFilter,
viewerNodesHandlerHasGrouping,
]);

const {
currentData: data,
isLoading,
error,
} = nodesApi.useGetNodesQuery({path, database}, {pollingInterval: autoRefreshInterval});
const renderContent = () => {
if (viewerNodesHandlerHasGrouping && groupByParam) {
return (
<GroupedNodesComponent
path={path}
database={database}
parentRef={parentRef}
columns={columns}
defaultColumnsIds={defaultColumnsIds}
requiredColumnsIds={requiredColumnsIds}
selectedColumnsKey={selectedColumnsKey}
groupByParams={groupByParams}
/>
);
}

const [sortValue, setSortValue] = React.useState<NodesSortParams>({
sortValue: 'NodeId',
sortOrder: ASCENDING,
});
const [sort, handleSort] = useTableSort(sortValue, (sortParams) => {
setSortValue(sortParams as NodesSortParams);
});
return (
<NodesComponent
path={path}
database={database}
parentRef={parentRef}
columns={columns}
defaultColumnsIds={defaultColumnsIds}
requiredColumnsIds={requiredColumnsIds}
selectedColumnsKey={selectedColumnsKey}
groupByParams={groupByParams}
/>
);
};

const nodes = React.useMemo(() => {
return filterNodes(data?.Nodes, {searchValue, uptimeFilter, problemFilter});
}, [data, searchValue, uptimeFilter, problemFilter]);
return <LoaderWrapper loading={!capabilitiesLoaded}>{renderContent()}</LoaderWrapper>;
}

const totalNodes = data?.TotalNodes || 0;
interface NodesComponentProps {
path?: string;
database?: string;
parentRef: React.RefObject<HTMLElement>;

const renderControls = () => {
columns: Column<NodesPreparedEntity>[];
defaultColumnsIds: NodesColumnId[];
requiredColumnsIds: NodesColumnId[];
selectedColumnsKey: string;
groupByParams: NodesGroupByField[];
}

function NodesComponent({
path,
database,
parentRef,
columns,
defaultColumnsIds,
requiredColumnsIds,
selectedColumnsKey,
groupByParams,
}: NodesComponentProps) {
const {searchValue, uptimeFilter} = useNodesPageQueryParams(groupByParams);
const {problemFilter} = useProblemFilter();
const viewerNodesHandlerHasGrouping = useViewerNodesHandlerHasGrouping();

const {columnsToShow, columnsToSelect, setColumns} = useSelectedColumns(
columns,
selectedColumnsKey,
NODES_COLUMNS_TITLES,
defaultColumnsIds,
requiredColumnsIds,
);

const renderControls: RenderControls = ({totalEntities, foundEntities, inited}) => {
return (
<NodesControls
withGroupBySelect={viewerNodesHandlerHasGrouping}
groupByParams={groupByParams}
columnsToSelect={columnsToSelect}
handleSelectedColumnsUpdate={setColumns}
entitiesCountCurrent={nodes.length}
entitiesCountTotal={totalNodes}
entitiesLoading={isLoading}
groupByParams={undefined}
entitiesCountCurrent={foundEntities}
entitiesCountTotal={totalEntities}
entitiesLoading={!inited}
/>
);
};

const renderTable = () => {
if (nodes.length === 0) {
if (problemFilter !== 'All' || uptimeFilter !== NodesUptimeFilterValues.All) {
return <Illustration name="thumbsUp" width="200" />;
}
}
return (
<NodesTable
path={path}
database={database}
searchValue={searchValue}
problemFilter={problemFilter}
uptimeFilter={uptimeFilter}
columns={columnsToShow}
parentRef={parentRef}
renderControls={renderControls}
/>
);
}

function GroupedNodesComponent({
path,
database,
parentRef,
columns,
defaultColumnsIds,
requiredColumnsIds,
selectedColumnsKey,
groupByParams,
}: NodesComponentProps) {
const {searchValue, groupByParam} = useNodesPageQueryParams(groupByParams);
const [autoRefreshInterval] = useAutoRefreshInterval();

const {columnsToShow, columnsToSelect, setColumns} = useSelectedColumns(
columns,
selectedColumnsKey,
NODES_COLUMNS_TITLES,
defaultColumnsIds,
requiredColumnsIds,
);

const {currentData, isFetching, error} = nodesApi.useGetNodesQuery(
{
path,
database,
filter: searchValue,
group: groupByParam,
limit: 0,
},
{
pollingInterval: autoRefreshInterval,
},
);

const isLoading = currentData === undefined && isFetching;
const {
NodeGroups: tableGroups,
FoundNodes: found = 0,
TotalNodes: total = 0,
} = currentData || {};

const {expandedGroups, setIsGroupExpanded} = useExpandedGroups(tableGroups);

const renderControls = () => {
return (
<ResizeableDataTable
columnsWidthLSKey={NODES_COLUMNS_WIDTH_LS_KEY}
data={nodes || []}
columns={columnsToShow}
settings={DEFAULT_TABLE_SETTINGS}
sortOrder={sort}
onSort={handleSort}
emptyDataMessage={i18n('empty.default')}
rowClassName={getRowClassName}
<NodesControls
withGroupBySelect
groupByParams={groupByParams}
columnsToSelect={columnsToSelect}
handleSelectedColumnsUpdate={setColumns}
entitiesCountCurrent={found}
entitiesCountTotal={total}
entitiesLoading={isLoading}
/>
);
};

if (isAccessError(error)) {
return <AccessDenied />;
}
const renderGroups = () => {
if (tableGroups?.length) {
return tableGroups.map(({name, count}) => {
const isExpanded = expandedGroups[name];

return (
<TableGroup
key={name}
title={name}
count={count}
entityName={i18n('nodes')}
expanded={isExpanded}
onIsExpandedChange={setIsGroupExpanded}
>
<NodesTable
path={path}
database={database}
searchValue={searchValue}
problemFilter={'All'}
uptimeFilter={NodesUptimeFilterValues.All}
filterGroup={name}
filterGroupBy={groupByParam}
initialEntitiesCount={count}
columns={columnsToShow}
parentRef={parentRef}
/>
</TableGroup>
);
});
}

return i18n('no-nodes-groups');
};

return (
<TableWithControlsLayout>
<TableWithControlsLayout.Controls>{renderControls()}</TableWithControlsLayout.Controls>
{error ? <ResponseError error={error} /> : null}
<TableWithControlsLayout.Table loading={isLoading}>
{data ? renderTable() : null}
<TableWithControlsLayout.Table loading={isLoading} className={b('groups-wrapper')}>
{renderGroups()}
</TableWithControlsLayout.Table>
</TableWithControlsLayout>
);
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {getNodes} from './getNodes';
import i18n from './i18n';
import {getRowClassName, renderPaginatedTableErrorMessage} from './shared';

interface PaginatedNodesTableProps {
interface NodesTableProps {
path?: string;
database?: string;

Expand All @@ -32,7 +32,7 @@ interface PaginatedNodesTableProps {
initialEntitiesCount?: number;
}

export function PaginatedNodesTable({
export function NodesTable({
path,
database,
searchValue,
Expand All @@ -44,7 +44,7 @@ export function PaginatedNodesTable({
parentRef,
renderControls,
initialEntitiesCount,
}: PaginatedNodesTableProps) {
}: NodesTableProps) {
const tableFilters = React.useMemo(() => {
return {
path,
Expand Down
Loading
Loading