diff --git a/.eslintrc b/.eslintrc
index 5f81ea8606..9dc0df0ea7 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -7,7 +7,10 @@
"root": true,
"overrides": [
{
- "files": ["config-overrides.js", "commitlint.config.js"],
+ "files": [
+ "config-overrides.js",
+ "commitlint.config.js"
+ ],
"env": {
"node": true,
},
@@ -17,10 +20,20 @@
"project": "./tsconfig.json",
},
"rules": {
- "import/consistent-type-specifier-style": ["error", "prefer-top-level"],
+ "import/consistent-type-specifier-style": [
+ "error",
+ "prefer-top-level"
+ ],
"@typescript-eslint/consistent-type-imports": [
"error",
- {"prefer": "type-imports", "fixStyle": "separate-type-imports"},
+ {
+ "prefer": "type-imports",
+ "fixStyle": "separate-type-imports"
+ },
+ ],
+ "curly": [
+ "error",
+ "all"
],
},
-}
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 24767d9790..4e6afaad9f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,3 +27,6 @@ yarn-debug.log*
yarn-error.log*
.env
+
+
+embedded-ui.tar.bz2
\ No newline at end of file
diff --git a/src/components/InfoViewer/schemaOverview/CDCStreamOverview.tsx b/src/components/InfoViewer/schemaOverview/CDCStreamOverview.tsx
deleted file mode 100644
index 01202fcf04..0000000000
--- a/src/components/InfoViewer/schemaOverview/CDCStreamOverview.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import React from 'react';
-
-import type {InfoViewerItem} from '..';
-import {InfoViewer, formatObject} from '..';
-import type {TEvDescribeSchemeResult} from '../../../types/api/schema';
-import {formatCdcStreamItem, formatCommonItem} from '../formatters';
-
-interface CDCStreamOverviewProps {
- data?: TEvDescribeSchemeResult;
-}
-
-export const CDCStreamOverview = ({data}: CDCStreamOverviewProps) => {
- if (!data) {
- return
No CDC Stream data
;
- }
-
- const TableIndex = data.PathDescription?.CdcStreamDescription;
- const info: Array = [];
-
- info.push(formatCommonItem('PathType', data.PathDescription?.Self?.PathType));
- info.push(formatCommonItem('CreateStep', data.PathDescription?.Self?.CreateStep));
-
- const {Mode, Format} = TableIndex || {};
- info.push(...formatObject(formatCdcStreamItem, {Mode, Format}));
-
- return (
-
- {info.length ? : 'Empty'}
-
- );
-};
diff --git a/src/components/InfoViewer/schemaOverview/PersQueueGroupOverview.tsx b/src/components/InfoViewer/schemaOverview/PersQueueGroupOverview.tsx
deleted file mode 100644
index a18939afe4..0000000000
--- a/src/components/InfoViewer/schemaOverview/PersQueueGroupOverview.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import React from 'react';
-
-import type {InfoViewerItem} from '..';
-import {InfoViewer} from '..';
-import type {TEvDescribeSchemeResult} from '../../../types/api/schema';
-import {formatCommonItem, formatPQGroupItem} from '../formatters';
-
-interface PersQueueGroupOverviewProps {
- data?: TEvDescribeSchemeResult;
-}
-
-export const PersQueueGroupOverview = ({data}: PersQueueGroupOverviewProps) => {
- if (!data) {
- return No PersQueueGroup data
;
- }
-
- const pqGroup = data.PathDescription?.PersQueueGroup;
- const info: Array = [];
-
- info.push(formatCommonItem('PathType', data.PathDescription?.Self?.PathType));
- info.push(formatCommonItem('CreateStep', data.PathDescription?.Self?.CreateStep));
-
- //@ts-expect-error
- info.push(formatPQGroupItem('Partitions', pqGroup?.Partitions || []));
- info.push(
- //@ts-expect-error
- formatPQGroupItem(
- 'PQTabletConfig',
- pqGroup?.PQTabletConfig || {PartitionConfig: {LifetimeSeconds: 0}},
- ),
- );
-
- return (
-
- {info.length ? : 'Empty'}
-
- );
-};
diff --git a/src/components/InfoViewer/schemaOverview/index.ts b/src/components/InfoViewer/schemaOverview/index.ts
deleted file mode 100644
index 5da37b7dd2..0000000000
--- a/src/components/InfoViewer/schemaOverview/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from './CDCStreamOverview';
-export * from './PersQueueGroupOverview';
diff --git a/src/containers/Tenant/ObjectSummary/ObjectSummary.tsx b/src/containers/Tenant/ObjectSummary/ObjectSummary.tsx
index c9a7c33314..ca7c84d572 100644
--- a/src/containers/Tenant/ObjectSummary/ObjectSummary.tsx
+++ b/src/containers/Tenant/ObjectSummary/ObjectSummary.tsx
@@ -11,13 +11,12 @@ import {StringParam, useQueryParam} from 'use-query-params';
import {AsyncReplicationState} from '../../../components/AsyncReplicationState';
import {ClipboardButton} from '../../../components/ClipboardButton';
import InfoViewer from '../../../components/InfoViewer/InfoViewer';
-import {
- CDCStreamOverview,
- PersQueueGroupOverview,
-} from '../../../components/InfoViewer/schemaOverview';
+import type {InfoViewerItem} from '../../../components/InfoViewer/InfoViewer';
+import {LinkWithIcon} from '../../../components/LinkWithIcon/LinkWithIcon';
import {Loader} from '../../../components/Loader';
import SplitPane from '../../../components/SplitPane';
-import routes, {createHref} from '../../../routes';
+import {getEntityName} from '../../../containers/Tenant/utils';
+import routes, {createExternalUILink, createHref} from '../../../routes';
import {schemaApi, setShowPreview} from '../../../store/reducers/schema/schema';
import {
TENANT_PAGES_IDS,
@@ -25,18 +24,15 @@ import {
TENANT_SUMMARY_TABS_IDS,
} from '../../../store/reducers/tenant/constants';
import {setQueryTab, setSummaryTab, setTenantPage} from '../../../store/reducers/tenant/tenant';
-import type {EPathSubType} from '../../../types/api/schema';
-import {EPathType} from '../../../types/api/schema';
+import {EPathSubType, EPathType} from '../../../types/api/schema';
import {cn} from '../../../utils/cn';
import {
DEFAULT_IS_TENANT_COMMON_INFO_COLLAPSED,
DEFAULT_SIZE_TENANT_SUMMARY_KEY,
} from '../../../utils/constants';
-import {formatDateTime} from '../../../utils/dataFormatters/dataFormatters';
+import {formatDateTime, formatSecondsToHours} from '../../../utils/dataFormatters/dataFormatters';
import {useTypedDispatch, useTypedSelector} from '../../../utils/hooks';
import {Acl} from '../Acl/Acl';
-import {ExternalDataSourceSummary} from '../Info/ExternalDataSource/ExternalDataSource';
-import {ExternalTableSummary} from '../Info/ExternalTable/ExternalTable';
import {SchemaTree} from '../Schema/SchemaTree/SchemaTree';
import {SchemaViewer} from '../Schema/SchemaViewer/SchemaViewer';
import {TENANT_INFO_TABS, TENANT_SCHEMA_TAB, TenantTabsGroups} from '../TenantPages';
@@ -137,65 +133,148 @@ export function ObjectSummary({
};
const renderObjectOverview = () => {
- const startTimeInMilliseconds = Number(currentSchemaData?.CreateStep);
- const createdAt = startTimeInMilliseconds
- ? formatDateTime(startTimeInMilliseconds)
- : 'unknown';
- const createdAtLabel = 'Created At';
-
- // verbose mapping to guarantee a correct render for new path types
- // TS will error when a new type is added but not mapped here
- const pathTypeToComponent: Record React.ReactNode) | undefined> = {
+ if (!currentSchemaData) {
+ return undefined;
+ }
+ const {CreateStep, PathType, PathSubType, PathId, PathVersion} = currentSchemaData;
+
+ const overview: InfoViewerItem[] = [];
+
+ overview.push({label: i18n('summary.type'), value: PathType?.replace(/^EPathType/, '')});
+
+ if (PathSubType !== EPathSubType.EPathSubTypeEmpty) {
+ overview.push({
+ label: i18n('summary.subtype'),
+ value: PathSubType?.replace(/^EPathSubType/, ''),
+ });
+ }
+
+ overview.push({label: i18n('summary.id'), value: PathId});
+
+ overview.push({label: i18n('summary.version'), value: PathVersion});
+
+ overview.push({
+ label: i18n('summary.created'),
+ value: formatDateTime(CreateStep, ''),
+ });
+
+ const {PathDescription} = currentObjectData;
+ const title = getEntityName(PathDescription);
+
+ const getPathTypeOverview: Record InfoViewerItem[]) | undefined> = {
[EPathType.EPathTypeInvalid]: undefined,
[EPathType.EPathTypeDir]: undefined,
- [EPathType.EPathTypeTable]: undefined,
+ [EPathType.EPathTypeTable]: () => [
+ {
+ label: i18n('summary.partitions'),
+ value: PathDescription?.TablePartitions?.length,
+ },
+ ],
[EPathType.EPathTypeSubDomain]: undefined,
[EPathType.EPathTypeTableIndex]: undefined,
- [EPathType.EPathTypeExtSubDomain]: undefined,
- [EPathType.EPathTypeColumnStore]: undefined,
- [EPathType.EPathTypeColumnTable]: undefined,
- [EPathType.EPathTypeCdcStream]: () => ,
- [EPathType.EPathTypePersQueueGroup]: () => (
-
- ),
- [EPathType.EPathTypeExternalTable]: () => (
-
- ),
- [EPathType.EPathTypeExternalDataSource]: () => (
-
- ),
+ [EPathType.EPathTypeExtSubDomain]: () => [
+ {
+ label: i18n('summary.paths'),
+ value: PathDescription?.DomainDescription?.PathsInside,
+ },
+ {
+ label: i18n('summary.shards'),
+ value: PathDescription?.DomainDescription?.ShardsInside,
+ },
+ ],
+ [EPathType.EPathTypeColumnStore]: () => [
+ {
+ label: i18n('summary.partitions'),
+ value: PathDescription?.ColumnStoreDescription?.ColumnShards?.length,
+ },
+ ],
+ [EPathType.EPathTypeColumnTable]: () => [
+ {
+ label: i18n('summary.partitions'),
+ value: PathDescription?.ColumnTableDescription?.Sharding?.ColumnShards?.length,
+ },
+ ],
+ [EPathType.EPathTypeCdcStream]: () => {
+ const {Mode, Format} = PathDescription?.CdcStreamDescription || {};
+
+ return [
+ {
+ label: i18n('summary.mode'),
+ value: Mode?.replace(/^ECdcStreamMode/, ''),
+ },
+ {
+ label: i18n('summary.format'),
+ value: Format?.replace(/^ECdcStreamFormat/, ''),
+ },
+ ];
+ },
+ [EPathType.EPathTypePersQueueGroup]: () => {
+ const pqGroup = PathDescription?.PersQueueGroup;
+ const value = pqGroup?.PQTabletConfig?.PartitionConfig?.LifetimeSeconds;
+
+ return [
+ {
+ label: i18n('summary.partitions'),
+ value: pqGroup?.Partitions?.length,
+ },
+ {
+ label: i18n('summary.retention'),
+ value: value && formatSecondsToHours(value),
+ },
+ ];
+ },
+ [EPathType.EPathTypeExternalTable]: () => {
+ const pathToDataSource = createExternalUILink({
+ ...queryParams,
+ schema: PathDescription?.ExternalTableDescription?.DataSourcePath,
+ });
+
+ const {SourceType, DataSourcePath} =
+ PathDescription?.ExternalTableDescription || {};
+
+ const dataSourceName = DataSourcePath?.match(/([^/]*)\/*$/)?.[1] || '';
+
+ return [
+ {label: i18n('summary.source-type'), value: SourceType},
+ {
+ label: i18n('summary.data-source'),
+ value: DataSourcePath && (
+
+
+
+ ),
+ },
+ ];
+ },
+ [EPathType.EPathTypeExternalDataSource]: () => [
+ {
+ label: i18n('summary.source-type'),
+ value: PathDescription?.ExternalDataSourceDescription?.SourceType,
+ },
+ ],
[EPathType.EPathTypeView]: undefined,
- [EPathType.EPathTypeReplication]: () => (
-
- ),
- },
- ]}
- />
- ),
+ [EPathType.EPathTypeReplication]: () => {
+ const state = PathDescription?.ReplicationDescription?.State;
+
+ if (!state) {
+ return [];
+ }
+
+ return [
+ {
+ label: i18n('summary.state'),
+ value: ,
+ },
+ ];
+ },
};
- let component =
- currentSchemaData?.PathType && pathTypeToComponent[currentSchemaData.PathType]?.();
-
- if (!component) {
- component = ;
- }
+ const pathTypeOverview = (PathType && getPathTypeOverview[PathType]?.()) || [];
+ overview.push(...pathTypeOverview);
- return component;
+ // filter all empty values in according this requirement
+ // https://github.com/ydb-platform/ydb-embedded-ui/issues/906
+ return i.value)} />;
};
const renderTabContent = () => {
diff --git a/src/containers/Tenant/i18n/en.json b/src/containers/Tenant/i18n/en.json
index 3be5439b82..defb9ee44a 100644
--- a/src/containers/Tenant/i18n/en.json
+++ b/src/containers/Tenant/i18n/en.json
@@ -1,19 +1,28 @@
{
"page.title": "Database",
-
"pages.query": "Query",
"pages.diagnostics": "Diagnostics",
-
"acl.owner": "Owner",
"acl.empty": "No Acl data",
-
"summary.navigation": "Navigation",
"summary.showPreview": "Show preview",
+ "summary.source-type": "Source Type",
+ "summary.data-source": "Data Source",
"summary.copySchemaPath": "Copy schema path",
-
+ "summary.type": "Type",
+ "summary.subtype": "SubType",
+ "summary.id": "Id",
+ "summary.version": "Version",
+ "summary.created": "Created",
+ "summary.partitions": "Partitions count",
+ "summary.paths": "Paths",
+ "summary.shards": "Shards",
+ "summary.state": "State",
+ "summary.mode": "Mode",
+ "summary.format": "Format",
+ "summary.retention": "Retention",
"actions.copied": "The path is copied to the clipboard",
"actions.notCopied": "Couldn’t copy the path",
-
"actions.copyPath": "Copy path",
"actions.openPreview": "Open preview",
"actions.createTable": "Create table...",
diff --git a/src/utils/dataFormatters/dataFormatters.ts b/src/utils/dataFormatters/dataFormatters.ts
index 063506ce74..212bd85631 100644
--- a/src/utils/dataFormatters/dataFormatters.ts
+++ b/src/utils/dataFormatters/dataFormatters.ts
@@ -6,7 +6,7 @@ import {
getSizeWithSignificantDigits,
} from '../bytesParsers/formatBytes';
import type {BytesSizes} from '../bytesParsers/formatBytes';
-import {DAY_IN_SECONDS, GIGABYTE} from '../constants';
+import {DAY_IN_SECONDS, GIGABYTE, HOUR_IN_SECONDS} from '../constants';
import {configuredNumeral} from '../numeral';
import {isNumeric} from '../utils';
@@ -99,6 +99,11 @@ export const formatNumber = (number?: unknown) => {
return configuredNumeral(number).format('0,0.[00000]');
};
+export const formatSecondsToHours = (seconds: number) => {
+ const hours = (seconds / HOUR_IN_SECONDS).toFixed(2);
+ return `${formatNumber(hours)} hours`;
+};
+
export const roundToPrecision = (value: number | string, precision = 0) => {
let [digits] = String(value).split('.');
if (Number(value) < 1) {
@@ -134,14 +139,14 @@ export const formatCPUWithLabel = (value?: number) => {
return `${localizedCores} ${i18n('format-cpu.cores', {count: cores})}`;
};
-export const formatDateTime = (value?: number | string) => {
+export const formatDateTime = (value?: number | string, defaultValue = 'N/A') => {
if (!isNumeric(value)) {
return '';
}
const formattedData = dateTimeParse(Number(value))?.format('YYYY-MM-DD HH:mm');
- return Number(value) > 0 && formattedData ? formattedData : 'N/A';
+ return Number(value) > 0 && formattedData ? formattedData : defaultValue;
};
export const calcUptimeInSeconds = (milliseconds: number | string) => {