diff --git a/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts b/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts
index 10e08c3586..34ebe3ff41 100644
--- a/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts
+++ b/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts
@@ -98,9 +98,9 @@ export const CDC_STREAM_PAGES = [overview, consumers, partitions, nodes, tablets
export const TOPIC_PAGES = [overview, consumers, partitions, nodes, tablets, describe];
export const EXTERNAL_DATA_SOURCE_PAGES = [overview, describe];
-export const EXTERNAL_TABLE_PAGES = [overview, describe];
+export const EXTERNAL_TABLE_PAGES = [overview, schema, describe];
-export const VIEW_PAGES = [overview, describe];
+export const VIEW_PAGES = [overview, schema, describe];
// verbose mapping to guarantee correct tabs for new path types
// TS will error when a new type is added but not mapped here
diff --git a/src/containers/Tenant/ObjectSummary/ObjectSummary.scss b/src/containers/Tenant/ObjectSummary/ObjectSummary.scss
index 46ca8e7b72..01439d7db2 100644
--- a/src/containers/Tenant/ObjectSummary/ObjectSummary.scss
+++ b/src/containers/Tenant/ObjectSummary/ObjectSummary.scss
@@ -1,6 +1,6 @@
@import '../../../styles/mixins.scss';
-.object-summary {
+.ydb-object-summary {
position: relative;
display: flex;
@@ -64,14 +64,13 @@
padding: 8px 12px 16px;
}
- &__tab {
- margin-right: 40px;
+ &__tabs-inner {
+ --g-tabs-border-width: 0;
+ box-shadow: inset 0 -1px 0 0 var(--g-color-line-generic);
+ }
+ &__tab {
text-decoration: none;
-
- &:first-letter {
- text-transform: uppercase;
- }
}
&__info {
diff --git a/src/containers/Tenant/ObjectSummary/ObjectSummary.tsx b/src/containers/Tenant/ObjectSummary/ObjectSummary.tsx
index 9d57bb697b..9f6fe3a491 100644
--- a/src/containers/Tenant/ObjectSummary/ObjectSummary.tsx
+++ b/src/containers/Tenant/ObjectSummary/ObjectSummary.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import {HelpPopover} from '@gravity-ui/components';
-import {Tabs} from '@gravity-ui/uikit';
+import {Flex, Tabs} from '@gravity-ui/uikit';
import qs from 'qs';
import {Link, useLocation} from 'react-router-dom';
import {StringParam, useQueryParam} from 'use-query-params';
@@ -12,14 +12,12 @@ import {toFormattedSize} from '../../../components/FormattedBytes/utils';
import {InfoViewer} from '../../../components/InfoViewer/InfoViewer';
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, {createExternalUILink, createHref} from '../../../routes';
import {useGetSchemaQuery} from '../../../store/reducers/schema/schema';
import {TENANT_SUMMARY_TABS_IDS} from '../../../store/reducers/tenant/constants';
import {setSummaryTab} from '../../../store/reducers/tenant/tenant';
import {EPathSubType, EPathType} from '../../../types/api/schema';
-import {cn} from '../../../utils/cn';
import {
DEFAULT_IS_TENANT_COMMON_INFO_COLLAPSED,
DEFAULT_SIZE_TENANT_SUMMARY_KEY,
@@ -32,10 +30,8 @@ import {
import {useTypedDispatch, useTypedSelector} from '../../../utils/hooks';
import {Acl} from '../Acl/Acl';
import {EntityTitle} from '../EntityTitle/EntityTitle';
-import {SchemaTree} from '../Schema/SchemaTree/SchemaTree';
import {SchemaViewer} from '../Schema/SchemaViewer/SchemaViewer';
import {TENANT_INFO_TABS, TENANT_SCHEMA_TAB, TenantTabsGroups} from '../TenantPages';
-import i18n from '../i18n';
import {getSummaryControls} from '../utils/controls';
import {
PaneVisibilityActionTypes,
@@ -44,12 +40,14 @@ import {
} from '../utils/paneVisibilityToggleHelpers';
import {isIndexTableType, isTableType} from '../utils/schema';
+import {ObjectTree} from './ObjectTree';
+import {SchemaActions} from './SchemaActions';
+import i18n from './i18n';
+import {b} from './shared';
import {transformPath} from './transformPath';
import './ObjectSummary.scss';
-const b = cn('object-summary');
-
const getTenantCommonInfoState = () => {
const collapsed = Boolean(localStorage.getItem(DEFAULT_IS_TENANT_COMMON_INFO_COLLAPSED));
@@ -113,24 +111,31 @@ export function ObjectSummary({
return (
- {
- const path = createHref(routes.tenant, undefined, {
- ...queryParams,
- name: tenantName,
- [TenantTabsGroups.summaryTab]: id,
- });
- return (
-
- {node}
-
- );
- }}
- allowNotSelected
- />
+
+ {
+ const path = createHref(routes.tenant, undefined, {
+ ...queryParams,
+ name: tenantName,
+ [TenantTabsGroups.summaryTab]: id,
+ });
+ return (
+
+ {node}
+
+ );
+ }}
+ allowNotSelected
+ />
+ {summaryTab === TENANT_SUMMARY_TABS_IDS.schema && }
+
);
};
@@ -143,21 +148,21 @@ export function ObjectSummary({
const overview: InfoViewerItem[] = [];
- overview.push({label: i18n('summary.type'), value: PathType?.replace(/^EPathType/, '')});
+ overview.push({label: i18n('field_type'), value: PathType?.replace(/^EPathType/, '')});
if (PathSubType !== EPathSubType.EPathSubTypeEmpty) {
overview.push({
- label: i18n('summary.subtype'),
+ label: i18n('field_subtype'),
value: PathSubType?.replace(/^EPathSubType/, ''),
});
}
- overview.push({label: i18n('summary.id'), value: PathId});
+ overview.push({label: i18n('field_id'), value: PathId});
- overview.push({label: i18n('summary.version'), value: PathVersion});
+ overview.push({label: i18n('field_version'), value: PathVersion});
overview.push({
- label: i18n('summary.created'),
+ label: i18n('field_created'),
value: formatDateTime(CreateStep),
});
@@ -168,11 +173,11 @@ export function ObjectSummary({
overview.push(
{
- label: i18n('summary.data-size'),
+ label: i18n('field_data-size'),
value: toFormattedSize(DataSize),
},
{
- label: i18n('summary.row-count'),
+ label: i18n('field_row-count'),
value: formatNumber(RowCount),
},
);
@@ -196,11 +201,11 @@ export function ObjectSummary({
return [
{
- label: i18n('summary.paths'),
+ label: i18n('field_paths'),
value: paths,
},
{
- label: i18n('summary.shards'),
+ label: i18n('field_shards'),
value: shards,
},
];
@@ -211,7 +216,7 @@ export function ObjectSummary({
[EPathType.EPathTypeDir]: undefined,
[EPathType.EPathTypeTable]: () => [
{
- label: i18n('summary.partitions'),
+ label: i18n('field_partitions'),
value: PathDescription?.TablePartitions?.length,
},
],
@@ -220,13 +225,13 @@ export function ObjectSummary({
[EPathType.EPathTypeExtSubDomain]: getDatabaseOverview,
[EPathType.EPathTypeColumnStore]: () => [
{
- label: i18n('summary.partitions'),
+ label: i18n('field_partitions'),
value: PathDescription?.ColumnStoreDescription?.ColumnShards?.length,
},
],
[EPathType.EPathTypeColumnTable]: () => [
{
- label: i18n('summary.partitions'),
+ label: i18n('field_partitions'),
value: PathDescription?.ColumnTableDescription?.Sharding?.ColumnShards?.length,
},
],
@@ -235,11 +240,11 @@ export function ObjectSummary({
return [
{
- label: i18n('summary.mode'),
+ label: i18n('field_mode'),
value: Mode?.replace(/^ECdcStreamMode/, ''),
},
{
- label: i18n('summary.format'),
+ label: i18n('field_format'),
value: Format?.replace(/^ECdcStreamFormat/, ''),
},
];
@@ -250,11 +255,11 @@ export function ObjectSummary({
return [
{
- label: i18n('summary.partitions'),
+ label: i18n('field_partitions'),
value: pqGroup?.Partitions?.length,
},
{
- label: i18n('summary.retention'),
+ label: i18n('field_retention'),
value: value && formatSecondsToHours(value),
},
];
@@ -271,9 +276,9 @@ export function ObjectSummary({
const dataSourceName = DataSourcePath?.match(/([^/]*)\/*$/)?.[1] || '';
return [
- {label: i18n('summary.source-type'), value: SourceType},
+ {label: i18n('field_source-type'), value: SourceType},
{
- label: i18n('summary.data-source'),
+ label: i18n('field_data-source'),
value: DataSourcePath && (
@@ -284,7 +289,7 @@ export function ObjectSummary({
},
[EPathType.EPathTypeExternalDataSource]: () => [
{
- label: i18n('summary.source-type'),
+ label: i18n('field_source-type'),
value: PathDescription?.ExternalDataSourceDescription?.SourceType,
},
],
@@ -298,7 +303,7 @@ export function ObjectSummary({
return [
{
- label: i18n('summary.state'),
+ label: i18n('field_state'),
value: ,
},
];
@@ -351,7 +356,7 @@ export function ObjectSummary({
-
-
- );
- }
-
- return (
-
-
{i18n('summary.navigation')}
-
- {pathData ? (
-
- ) : null}
-
-
- );
-}
diff --git a/src/containers/Tenant/ObjectSummary/ObjectTree.tsx b/src/containers/Tenant/ObjectSummary/ObjectTree.tsx
new file mode 100644
index 0000000000..e1a10b87a4
--- /dev/null
+++ b/src/containers/Tenant/ObjectSummary/ObjectTree.tsx
@@ -0,0 +1,51 @@
+import {StringParam, useQueryParam} from 'use-query-params';
+
+import {Loader} from '../../../components/Loader';
+import {useGetSchemaQuery} from '../../../store/reducers/schema/schema';
+import {SchemaTree} from '../Schema/SchemaTree/SchemaTree';
+
+import i18n from './i18n';
+import {b} from './shared';
+
+interface ObjectTreeProps {
+ tenantName: string;
+ path?: string;
+}
+
+export function ObjectTree({tenantName, path}: ObjectTreeProps) {
+ const {data: tenantData = {}, isLoading} = useGetSchemaQuery({
+ path: tenantName,
+ database: tenantName,
+ });
+ const pathData = tenantData?.PathDescription?.Self;
+
+ const [, setCurrentPath] = useQueryParam('schema', StringParam);
+
+ if (!pathData && isLoading) {
+ // If Loader isn't wrapped with div, SplitPane doesn't calculate panes height correctly
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
{i18n('title_navigation')}
+
+ {pathData ? (
+
+ ) : null}
+
+
+ );
+}
diff --git a/src/containers/Tenant/ObjectSummary/SchemaActions.tsx b/src/containers/Tenant/ObjectSummary/SchemaActions.tsx
new file mode 100644
index 0000000000..9c96ef1def
--- /dev/null
+++ b/src/containers/Tenant/ObjectSummary/SchemaActions.tsx
@@ -0,0 +1,37 @@
+import {Button, Icon} from '@gravity-ui/uikit';
+
+import {
+ TENANT_DIAGNOSTICS_TABS_IDS,
+ TENANT_PAGES_IDS,
+} from '../../../store/reducers/tenant/constants';
+import {setDiagnosticsTab, setTenantPage} from '../../../store/reducers/tenant/tenant';
+import {useTypedDispatch, useTypedSelector} from '../../../utils/hooks';
+
+import i18n from './i18n';
+
+import ArrowRightFromSquareIcon from '@gravity-ui/icons/svgs/arrow-right-from-square.svg';
+
+export function SchemaActions() {
+ const dispatch = useTypedDispatch();
+ const {diagnosticsTab, tenantPage} = useTypedSelector((state) => state.tenant);
+ const diagnosticsSchemaActive =
+ tenantPage === TENANT_PAGES_IDS.diagnostics &&
+ diagnosticsTab === TENANT_DIAGNOSTICS_TABS_IDS.schema;
+
+ return (
+
+ {!diagnosticsSchemaActive && (
+
+ )}
+
+ );
+}
diff --git a/src/containers/Tenant/ObjectSummary/i18n/en.json b/src/containers/Tenant/ObjectSummary/i18n/en.json
new file mode 100644
index 0000000000..7dcb277cf8
--- /dev/null
+++ b/src/containers/Tenant/ObjectSummary/i18n/en.json
@@ -0,0 +1,21 @@
+{
+ "title_navigation": "Navigation",
+ "field_source-type": "Source Type",
+ "field_data-source": "Data Source",
+ "action_copySchemaPath": "Copy schema path",
+ "action_openInDiagnostics": "Open in Diagnostics",
+ "field_type": "Type",
+ "field_subtype": "SubType",
+ "field_id": "Id",
+ "field_version": "Version",
+ "field_created": "Created",
+ "field_data-size": "Data size",
+ "field_row-count": "Row count",
+ "field_partitions": "Partitions count",
+ "field_paths": "Paths",
+ "field_shards": "Shards",
+ "field_state": "State",
+ "field_mode": "Mode",
+ "field_format": "Format",
+ "field_retention": "Retention"
+}
diff --git a/src/containers/Tenant/ObjectSummary/i18n/index.ts b/src/containers/Tenant/ObjectSummary/i18n/index.ts
new file mode 100644
index 0000000000..cfa8a570f2
--- /dev/null
+++ b/src/containers/Tenant/ObjectSummary/i18n/index.ts
@@ -0,0 +1,7 @@
+import {registerKeysets} from '../../../../utils/i18n';
+
+import en from './en.json';
+
+const COMPONENT = 'ydb-object-summary';
+
+export default registerKeysets(COMPONENT, {en});
diff --git a/src/containers/Tenant/ObjectSummary/shared.ts b/src/containers/Tenant/ObjectSummary/shared.ts
new file mode 100644
index 0000000000..f05505f22c
--- /dev/null
+++ b/src/containers/Tenant/ObjectSummary/shared.ts
@@ -0,0 +1,3 @@
+import {cn} from '../../../utils/cn';
+
+export const b = cn('ydb-object-summary');
diff --git a/src/containers/Tenant/utils/schema.ts b/src/containers/Tenant/utils/schema.ts
index 42de559110..4257c3e1c3 100644
--- a/src/containers/Tenant/utils/schema.ts
+++ b/src/containers/Tenant/utils/schema.ts
@@ -120,6 +120,7 @@ const pathTypeToIsTable: Record = {
[EPathType.EPathTypeReplication]: false,
};
+//if add entity with tableType, make sure that Schema is available in Diagnostics section
export const isTableType = (pathType?: EPathType) =>
(pathType && pathTypeToIsTable[pathType]) ?? false;
diff --git a/tests/suites/tenant/summary/ObjectSummary.ts b/tests/suites/tenant/summary/ObjectSummary.ts
index 364798f390..2d5dbed234 100644
--- a/tests/suites/tenant/summary/ObjectSummary.ts
+++ b/tests/suites/tenant/summary/ObjectSummary.ts
@@ -16,9 +16,9 @@ export class ObjectSummary {
private primaryKeys: Locator;
constructor(page: Page) {
- this.tree = page.locator('.object-summary__tree');
+ this.tree = page.locator('.ydb-object-summary__tree');
this.treeRows = page.locator('.ydb-tree-view');
- this.tabs = page.locator('.object-summary__tabs');
+ this.tabs = page.locator('.ydb-object-summary__tabs');
this.schemaViewer = page.locator('.schema-viewer');
this.primaryKeys = page.locator('.schema-viewer__keys_type_primary');
}
@@ -66,7 +66,7 @@ export class ObjectSummary {
}
async clickTab(tabName: ObjectSummaryTab): Promise {
- const tab = this.tabs.locator(`.object-summary__tab:has-text("${tabName}")`);
+ const tab = this.tabs.locator(`.ydb-object-summary__tab:has-text("${tabName}")`);
await tab.click();
}