diff --git a/src/components/ComponentsProvider/componentsRegistry.ts b/src/components/ComponentsProvider/componentsRegistry.ts index 8c56eaf0d..971c37b5c 100644 --- a/src/components/ComponentsProvider/componentsRegistry.ts +++ b/src/components/ComponentsProvider/componentsRegistry.ts @@ -1,5 +1,6 @@ import {AsideNavigation} from '../../containers/AsideNavigation/AsideNavigation'; import {ErrorBoundaryInner} from '../ErrorBoundary/ErrorBoundary'; +import {ShardsTable} from '../ShardsTable/ShardsTable'; import {StaffCard} from '../User/StaffCard'; import type {ComponentsRegistryTemplate} from './registry'; @@ -8,7 +9,8 @@ import {Registry} from './registry'; const componentsRegistryInner = new Registry() .register('StaffCard', StaffCard) .register('AsideNavigation', AsideNavigation) - .register('ErrorBoundary', ErrorBoundaryInner); + .register('ErrorBoundary', ErrorBoundaryInner) + .register('ShardsTable', ShardsTable); export type ComponentsRegistry = ComponentsRegistryTemplate; diff --git a/src/components/LinkToSchemaObject/LinkToSchemaObject.tsx b/src/components/LinkToSchemaObject/LinkToSchemaObject.tsx index a741d2971..aceace59f 100644 --- a/src/components/LinkToSchemaObject/LinkToSchemaObject.tsx +++ b/src/components/LinkToSchemaObject/LinkToSchemaObject.tsx @@ -1,15 +1,15 @@ import {Link} from '@gravity-ui/uikit'; import type {LinkProps} from '@gravity-ui/uikit'; -import type {Location} from 'history'; +import {useLocation} from 'react-router-dom'; import {createExternalUILink, parseQuery} from '../../routes'; interface LinkToSchemaObjectProps extends Omit { path: string; - location: Location; } -export function LinkToSchemaObject({path, location, ...props}: LinkToSchemaObjectProps) { +export function LinkToSchemaObject({path, ...props}: LinkToSchemaObjectProps) { + const location = useLocation(); const queryParams = parseQuery(location); const pathToSchemaObject = createExternalUILink({ ...queryParams, diff --git a/src/components/ShardsTable/ShardsTable.tsx b/src/components/ShardsTable/ShardsTable.tsx new file mode 100644 index 000000000..ebf0e15a9 --- /dev/null +++ b/src/components/ShardsTable/ShardsTable.tsx @@ -0,0 +1,41 @@ +import React from 'react'; + +import type {KeyValueRow} from '../../types/api/query'; +import type {ResizeableDataTableProps} from '../ResizeableDataTable/ResizeableDataTable'; +import {ResizeableDataTable} from '../ResizeableDataTable/ResizeableDataTable'; + +import {shardsColumnIdToGetColumn} from './columns'; +import type {TopShardsColumnId} from './constants'; +import {TOP_SHARDS_COLUMNS_WIDTH_LS_KEY, isSortableTopShardsColumn} from './constants'; + +export interface ShardsTableProps + extends Omit, 'columnsWidthLSKey' | 'columns'> { + columnsIds: TopShardsColumnId[]; + database: string; + schemaPath?: string; +} + +export function ShardsTable({columnsIds, schemaPath, database, ...props}: ShardsTableProps) { + const columns = React.useMemo( + () => + columnsIds + .filter((id) => id in shardsColumnIdToGetColumn) + .map((id) => { + const column = shardsColumnIdToGetColumn[id]({database, schemaPath}); + + return { + ...column, + sortable: isSortableTopShardsColumn(column.name), + }; + }), + [columnsIds, database, schemaPath], + ); + + return ( + + ); +} diff --git a/src/components/ShardsTable/columns.tsx b/src/components/ShardsTable/columns.tsx new file mode 100644 index 000000000..dec0cd5fa --- /dev/null +++ b/src/components/ShardsTable/columns.tsx @@ -0,0 +1,113 @@ +import DataTable from '@gravity-ui/react-data-table'; + +import {getDefaultNodePath} from '../../containers/Node/NodePages'; +import {EMPTY_DATA_PLACEHOLDER} from '../../utils/constants'; +import {formatNumber, roundToPrecision} from '../../utils/dataFormatters/dataFormatters'; +import {getUsageSeverity} from '../../utils/generateEvaluator'; +import {InternalLink} from '../InternalLink'; +import {LinkToSchemaObject} from '../LinkToSchemaObject/LinkToSchemaObject'; +import {TabletNameWrapper} from '../TabletNameWrapper/TabletNameWrapper'; +import {UsageLabel} from '../UsageLabel/UsageLabel'; + +import type {TopShardsColumnId} from './constants'; +import {TOP_SHARDS_COLUMNS_IDS, TOP_SHARDS_COLUMNS_TITLES} from './constants'; +import type {GetShardsColumn} from './types'; +import {prepareDateTimeValue} from './utils'; + +export const getPathColumn: GetShardsColumn = ({schemaPath = ''}) => { + return { + name: TOP_SHARDS_COLUMNS_IDS.Path, + header: TOP_SHARDS_COLUMNS_TITLES.Path, + render: ({row}) => { + // row.Path - relative schema path + return {row.Path}; + }, + width: 300, + }; +}; +export const getDataSizeColumn: GetShardsColumn = () => { + return { + name: TOP_SHARDS_COLUMNS_IDS.DataSize, + header: TOP_SHARDS_COLUMNS_TITLES.DataSize, + render: ({row}) => { + return formatNumber(row.DataSize); + }, + align: DataTable.RIGHT, + }; +}; +export const getTabletIdColumn: GetShardsColumn = () => { + return { + name: TOP_SHARDS_COLUMNS_IDS.TabletId, + header: TOP_SHARDS_COLUMNS_TITLES.TabletId, + render: ({row}) => { + if (!row.TabletId) { + return EMPTY_DATA_PLACEHOLDER; + } + return ; + }, + width: 220, + }; +}; +export const getNodeIdColumn: GetShardsColumn = () => { + return { + name: TOP_SHARDS_COLUMNS_IDS.NodeId, + header: TOP_SHARDS_COLUMNS_TITLES.NodeId, + render: ({row}) => { + if (!row.NodeId) { + return EMPTY_DATA_PLACEHOLDER; + } + return {row.NodeId}; + }, + align: DataTable.RIGHT, + }; +}; +export const getCpuCoresColumn: GetShardsColumn = () => { + return { + name: TOP_SHARDS_COLUMNS_IDS.CPUCores, + header: TOP_SHARDS_COLUMNS_TITLES.CPUCores, + render: ({row}) => { + const usage = Number(row.CPUCores) * 100 || 0; + return ( + + ); + }, + align: DataTable.RIGHT, + width: 110, + resizeMinWidth: 110, + }; +}; +export const getInFlightTxCountColumn: GetShardsColumn = () => { + return { + name: TOP_SHARDS_COLUMNS_IDS.InFlightTxCount, + header: TOP_SHARDS_COLUMNS_TITLES.InFlightTxCount, + render: ({row}) => formatNumber(row.InFlightTxCount), + align: DataTable.RIGHT, + }; +}; +export const getPeakTimeColumn: GetShardsColumn = () => { + return { + name: TOP_SHARDS_COLUMNS_IDS.PeakTime, + render: ({row}) => { + return prepareDateTimeValue(row.PeakTime); + }, + }; +}; +export const getIntervalEndColumn: GetShardsColumn = () => { + return { + name: TOP_SHARDS_COLUMNS_IDS.IntervalEnd, + render: ({row}) => { + return prepareDateTimeValue(row.IntervalEnd); + }, + }; +}; + +export const shardsColumnIdToGetColumn: Record = { + [TOP_SHARDS_COLUMNS_IDS.Path]: getPathColumn, + [TOP_SHARDS_COLUMNS_IDS.DataSize]: getDataSizeColumn, + [TOP_SHARDS_COLUMNS_IDS.TabletId]: getTabletIdColumn, + [TOP_SHARDS_COLUMNS_IDS.NodeId]: getNodeIdColumn, + [TOP_SHARDS_COLUMNS_IDS.CPUCores]: getCpuCoresColumn, + [TOP_SHARDS_COLUMNS_IDS.InFlightTxCount]: getInFlightTxCountColumn, + [TOP_SHARDS_COLUMNS_IDS.PeakTime]: getPeakTimeColumn, + [TOP_SHARDS_COLUMNS_IDS.IntervalEnd]: getIntervalEndColumn, +}; diff --git a/src/containers/Tenant/Diagnostics/TopShards/columns/constants.ts b/src/components/ShardsTable/constants.ts similarity index 90% rename from src/containers/Tenant/Diagnostics/TopShards/columns/constants.ts rename to src/components/ShardsTable/constants.ts index 5be1db8b8..8bd368abf 100644 --- a/src/containers/Tenant/Diagnostics/TopShards/columns/constants.ts +++ b/src/components/ShardsTable/constants.ts @@ -1,4 +1,4 @@ -import type {ValueOf} from '../../../../../types/common'; +import type {ValueOf} from '../../types/common'; import i18n from './i18n'; @@ -15,7 +15,7 @@ export const TOP_SHARDS_COLUMNS_IDS = { IntervalEnd: 'IntervalEnd', } as const; -type TopShardsColumnId = ValueOf; +export type TopShardsColumnId = ValueOf; export const TOP_SHARDS_COLUMNS_TITLES: Record = { get TabletId() { @@ -52,7 +52,7 @@ const TOP_SHARDS_COLUMNS_TO_SORT_FIELDS: Record; + +export type GetShardsColumn = (params: {database: string; schemaPath?: string}) => ShardsColumn; diff --git a/src/components/ShardsTable/utils.ts b/src/components/ShardsTable/utils.ts new file mode 100644 index 000000000..2172f38b2 --- /dev/null +++ b/src/components/ShardsTable/utils.ts @@ -0,0 +1,10 @@ +import type {CellValue} from '../../types/api/query'; +import {EMPTY_DATA_PLACEHOLDER} from '../../utils/constants'; +import {formatDateTime} from '../../utils/dataFormatters/dataFormatters'; + +export function prepareDateTimeValue(value: CellValue) { + if (!value) { + return EMPTY_DATA_PLACEHOLDER; + } + return formatDateTime(new Date(value).getTime()); +} diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopShards.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopShards.tsx index f8c524213..92c4b0412 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopShards.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopShards.tsx @@ -1,6 +1,7 @@ import {useLocation} from 'react-router-dom'; -import {ResizeableDataTable} from '../../../../../components/ResizeableDataTable/ResizeableDataTable'; +import {useComponent} from '../../../../../components/ComponentsProvider/ComponentsProvider'; +import type {TopShardsColumnId} from '../../../../../components/ShardsTable/constants'; import {parseQuery} from '../../../../../routes'; import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../../../store/reducers/tenant/constants'; import {topShardsApi} from '../../../../../store/reducers/tenantOverview/topShards/tenantOverviewTopShards'; @@ -8,18 +9,20 @@ import {TENANT_OVERVIEW_TABLES_SETTINGS} from '../../../../../utils/constants'; import {useAutoRefreshInterval} from '../../../../../utils/hooks'; import {parseQueryErrorToString} from '../../../../../utils/query'; import {TenantTabsGroups, getTenantPath} from '../../../TenantPages'; -import {getTopShardsColumns} from '../../TopShards/columns/columns'; -import {TOP_SHARDS_COLUMNS_WIDTH_LS_KEY} from '../../TopShards/columns/constants'; import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout'; import {getSectionTitle} from '../getSectionTitle'; import i18n from '../i18n'; +const columnsIds: TopShardsColumnId[] = ['TabletId', 'Path', 'CPUCores']; + interface TopShardsProps { tenantName: string; path: string; } export const TopShards = ({tenantName, path}: TopShardsProps) => { + const ShardsTable = useComponent('ShardsTable'); + const location = useLocation(); const query = parseQuery(location); @@ -34,8 +37,6 @@ export const TopShards = ({tenantName, path}: TopShardsProps) => { const loading = isFetching && currentData === undefined; const data = currentData?.resultSets?.[0]?.result || []; - const columns = getTopShardsColumns(tenantName, location); - const title = getSectionTitle({ entity: i18n('shards'), postfix: i18n('by-cpu-usage'), @@ -52,10 +53,11 @@ export const TopShards = ({tenantName, path}: TopShardsProps) => { error={parseQueryErrorToString(error)} withData={Boolean(currentData)} > - diff --git a/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopTables.tsx b/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopTables.tsx index be6a87eef..85e8aece8 100644 --- a/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopTables.tsx +++ b/src/containers/Tenant/Diagnostics/TenantOverview/TenantStorage/TopTables.tsx @@ -1,6 +1,5 @@ import type {Column} from '@gravity-ui/react-data-table'; import DataTable from '@gravity-ui/react-data-table'; -import {useLocation} from 'react-router-dom'; import {CellWithPopover} from '../../../../../components/CellWithPopover/CellWithPopover'; import {LinkToSchemaObject} from '../../../../../components/LinkToSchemaObject/LinkToSchemaObject'; @@ -24,8 +23,6 @@ interface TopTablesProps { const TOP_TABLES_COLUMNS_WIDTH_LS_KEY = 'topTablesTableColumnsWidth'; export function TopTables({database}: TopTablesProps) { - const location = useLocation(); - const [autoRefreshInterval] = useAutoRefreshInterval(); const {currentData, error, isFetching} = topTablesApi.useGetTopTablesQuery( @@ -55,9 +52,7 @@ export function TopTables({database}: TopTablesProps) { render: ({row}) => row.Path ? ( - - {row.Path} - + {row.Path} ) : null, }, diff --git a/src/containers/Tenant/Diagnostics/TopShards/TopShards.tsx b/src/containers/Tenant/Diagnostics/TopShards/TopShards.tsx index 35574b7c0..342959509 100644 --- a/src/containers/Tenant/Diagnostics/TopShards/TopShards.tsx +++ b/src/containers/Tenant/Diagnostics/TopShards/TopShards.tsx @@ -1,11 +1,10 @@ import React from 'react'; -import type {Column, Settings} from '@gravity-ui/react-data-table'; -import DataTable from '@gravity-ui/react-data-table'; -import {useLocation} from 'react-router-dom'; +import type {Settings} from '@gravity-ui/react-data-table'; +import {useComponent} from '../../../../components/ComponentsProvider/ComponentsProvider'; import {ResponseError} from '../../../../components/Errors/ResponseError'; -import {ResizeableDataTable} from '../../../../components/ResizeableDataTable/ResizeableDataTable'; +import type {TopShardsColumnId} from '../../../../components/ShardsTable/constants'; import {TableWithControlsLayout} from '../../../../components/TableWithControlsLayout/TableWithControlsLayout'; import { setShardsQueryFilters, @@ -13,20 +12,12 @@ import { } from '../../../../store/reducers/shardsWorkload/shardsWorkload'; import {EShardsWorkloadMode} from '../../../../store/reducers/shardsWorkload/types'; import type {ShardsWorkloadFilters} from '../../../../store/reducers/shardsWorkload/types'; -import type {CellValue, KeyValueRow} from '../../../../types/api/query'; import {cn} from '../../../../utils/cn'; import {DEFAULT_TABLE_SETTINGS} from '../../../../utils/constants'; -import {formatDateTime} from '../../../../utils/dataFormatters/dataFormatters'; import {useAutoRefreshInterval, useTypedDispatch, useTypedSelector} from '../../../../utils/hooks'; import {parseQueryErrorToString} from '../../../../utils/query'; import {Filters} from './Filters'; -import {getShardsWorkloadColumns} from './columns/columns'; -import { - TOP_SHARDS_COLUMNS_IDS, - TOP_SHARDS_COLUMNS_WIDTH_LS_KEY, - isSortableTopShardsColumn, -} from './columns/constants'; import i18n from './i18n'; import {useTopShardSort} from './utils'; @@ -39,16 +30,9 @@ const TABLE_SETTINGS: Settings = { dynamicRender: false, // no more than 20 rows externalSort: true, disableSortReset: true, - defaultOrder: DataTable.DESCENDING, + defaultOrder: -1, }; -function prepareDateTimeValue(value: CellValue) { - if (!value) { - return '–'; - } - return formatDateTime(new Date(value).getTime()); -} - function fillDateRangeFor(value: ShardsWorkloadFilters) { value.to = 'now'; value.from = 'now-1h'; @@ -61,8 +45,9 @@ interface TopShardsProps { } export const TopShards = ({tenantName, path}: TopShardsProps) => { + const ShardsTable = useComponent('ShardsTable'); + const dispatch = useTypedDispatch(); - const location = useLocation(); const [autoRefreshInterval] = useAutoRefreshInterval(); @@ -123,33 +108,27 @@ export const TopShards = ({tenantName, path}: TopShardsProps) => { setFilters((state) => ({...state, ...newStateValue})); }; - const tableColumns = React.useMemo(() => { - const rawColumns: Column[] = getShardsWorkloadColumns(tenantName, location); - - const columns: Column[] = rawColumns.map((column) => ({ - ...column, - sortable: isSortableTopShardsColumn(column.name), - })); + const columnsIds = React.useMemo(() => { + let columns: TopShardsColumnId[]; if (filters.mode === EShardsWorkloadMode.History) { - // after NodeId - columns.splice(5, 0, { - name: TOP_SHARDS_COLUMNS_IDS.PeakTime, - render: ({row}) => { - return prepareDateTimeValue(row.PeakTime); - }, - sortable: false, - }); - columns.push({ - name: TOP_SHARDS_COLUMNS_IDS.IntervalEnd, - render: ({row}) => { - return prepareDateTimeValue(row.IntervalEnd); - }, - }); + // Add PeakTime and IntervalEnd columns + columns = [ + 'Path', + 'CPUCores', + 'DataSize', + 'TabletId', + 'NodeId', + 'PeakTime', + 'InFlightTxCount', + 'IntervalEnd', + ]; + } else { + columns = ['Path', 'CPUCores', 'DataSize', 'TabletId', 'NodeId', 'InFlightTxCount']; } return columns; - }, [filters.mode, location, tenantName]); + }, [filters.mode]); const renderControls = () => { return ; @@ -161,9 +140,10 @@ export const TopShards = ({tenantName, path}: TopShardsProps) => { } return ( - => ({ - name: TOP_SHARDS_COLUMNS_IDS.Path, - header: TOP_SHARDS_COLUMNS_TITLES.Path, - render: ({row}) => { - // row.Path - relative schema path - return ( - - {row.Path} - - ); - }, - sortable: false, - width: 300, -}); - -const dataSizeColumn: Column = { - name: TOP_SHARDS_COLUMNS_IDS.DataSize, - header: TOP_SHARDS_COLUMNS_TITLES.DataSize, - render: ({row}) => { - return formatNumber(row.DataSize); - }, - align: DataTable.RIGHT, -}; - -const tabletIdColumn: Column = { - name: TOP_SHARDS_COLUMNS_IDS.TabletId, - header: TOP_SHARDS_COLUMNS_TITLES.TabletId, - render: ({row}) => { - if (!row.TabletId) { - return '–'; - } - return ; - }, - sortable: false, - width: 220, -}; - -const nodeIdColumn: Column = { - name: TOP_SHARDS_COLUMNS_IDS.NodeId, - header: TOP_SHARDS_COLUMNS_TITLES.NodeId, - render: ({row}) => { - if (!row.NodeId) { - return '–'; - } - return {row.NodeId}; - }, - align: DataTable.RIGHT, -}; - -const cpuCoresColumn: Column = { - name: TOP_SHARDS_COLUMNS_IDS.CPUCores, - header: TOP_SHARDS_COLUMNS_TITLES.CPUCores, - render: ({row}) => { - const usage = Number(row.CPUCores) * 100 || 0; - - return ; - }, - align: DataTable.RIGHT, - width: 110, - resizeMinWidth: 110, -}; - -const inFlightTxCountColumn: Column = { - name: TOP_SHARDS_COLUMNS_IDS.InFlightTxCount, - header: TOP_SHARDS_COLUMNS_TITLES.InFlightTxCount, - render: ({row}) => formatNumber(row.InFlightTxCount), - align: DataTable.RIGHT, -}; - -export const getShardsWorkloadColumns = (schemaPath: string, location: Location) => { - return [ - getPathColumn(schemaPath, location), - cpuCoresColumn, - dataSizeColumn, - tabletIdColumn, - nodeIdColumn, - inFlightTxCountColumn, - ]; -}; - -export const getTopShardsColumns = (schemaPath: string, location: Location) => { - return [tabletIdColumn, getPathColumn(schemaPath, location), cpuCoresColumn]; -}; diff --git a/src/containers/Tenant/Diagnostics/TopShards/columns/i18n/index.ts b/src/containers/Tenant/Diagnostics/TopShards/columns/i18n/index.ts deleted file mode 100644 index 332949ec5..000000000 --- a/src/containers/Tenant/Diagnostics/TopShards/columns/i18n/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {registerKeysets} from '../../../../../../utils/i18n'; - -import en from './en.json'; - -const COMPONENT = 'ydb-top-shards-columns'; - -export default registerKeysets(COMPONENT, {en}); diff --git a/src/containers/Tenant/Diagnostics/TopShards/utils.ts b/src/containers/Tenant/Diagnostics/TopShards/utils.ts index f5b5b2059..3979daf25 100644 --- a/src/containers/Tenant/Diagnostics/TopShards/utils.ts +++ b/src/containers/Tenant/Diagnostics/TopShards/utils.ts @@ -1,9 +1,11 @@ import React from 'react'; +import { + TOP_SHARDS_COLUMNS_IDS, + getTopShardsColumnSortField, +} from '../../../../components/ShardsTable/constants'; import {prepareBackendSortFieldsFromTableSort, useTableSort} from '../../../../utils/hooks'; -import {TOP_SHARDS_COLUMNS_IDS, getTopShardsColumnSortField} from './columns/constants'; - export function useTopShardSort() { const [tableSort, handleTableSort] = useTableSort({ initialSortColumn: TOP_SHARDS_COLUMNS_IDS.CPUCores,