diff --git a/src/components/InfoViewer/InfoViewer.scss b/src/components/InfoViewer/InfoViewer.scss index c75126ee02..7f6a3c656f 100644 --- a/src/components/InfoViewer/InfoViewer.scss +++ b/src/components/InfoViewer/InfoViewer.scss @@ -4,14 +4,13 @@ --ydb-info-viewer-font-size: var(--g-text-body-2-font-size); --ydb-info-viewer-line-height: var(--g-text-body-2-line-height); --ydb-info-viewer-title-font-weight: 600; - --ydb-info-viewer-title-margin: 15px 0 10px; --ydb-info-viewer-items-gap: 7px; font-size: var(--ydb-info-viewer-font-size); line-height: var(--ydb-info-viewer-line-height); &__title { - margin: var(--ydb-info-viewer-title-margin); + margin: var(--ydb-info-viewer-title-margin, 15px 0 10px); font-weight: var(--ydb-info-viewer-title-font-weight); } diff --git a/src/components/InfoViewer/schemaInfo/TableIndexInfo.tsx b/src/components/InfoViewer/schemaInfo/TableIndexInfo.tsx index ec27f38d2f..aa0429540c 100644 --- a/src/components/InfoViewer/schemaInfo/TableIndexInfo.tsx +++ b/src/components/InfoViewer/schemaInfo/TableIndexInfo.tsx @@ -6,11 +6,7 @@ import type {TEvDescribeSchemeResult} from '../../../types/api/schema'; import {cn} from '../../../utils/cn'; import i18n from './i18n'; -import { - buildFulltextIndexSettingsInfo, - buildIndexInfo, - buildVectorIndexSettingsInfo, -} from './utils'; +import {buildFulltextIndexSettingsInfo, buildVectorIndexSettingsInfo} from './utils'; const b = cn('ydb-diagnostics-table-info'); @@ -26,7 +22,6 @@ export const TableIndexInfo = ({data}: TableIndexInfoProps) => { } const TableIndex = data.PathDescription?.TableIndex; - const info: Array = buildIndexInfo(TableIndex); let settings: Array = []; if (TableIndex?.Type === EIndexType.EIndexTypeGlobalVectorKmeansTree) { @@ -34,25 +29,25 @@ export const TableIndexInfo = ({data}: TableIndexInfoProps) => { TableIndex?.VectorIndexKmeansTreeDescription?.Settings, ); } - if (TableIndex?.Type === EIndexType.EIndexTypeGlobalFulltext) { + if ( + TableIndex?.Type === EIndexType.EIndexTypeGlobalFulltext || + TableIndex?.Type === EIndexType.EIndexTypeGlobalFulltextPlain || + TableIndex?.Type === EIndexType.EIndexTypeGlobalFulltextRelevance + ) { settings = buildFulltextIndexSettingsInfo(TableIndex?.FulltextIndexDescription?.Settings); } + if (!settings.length) { + return null; + } + return (
{entityName}
} /> - {settings && settings.length ? ( - - ) : null}
); }; diff --git a/src/components/InfoViewer/schemaInfo/i18n/en.json b/src/components/InfoViewer/schemaInfo/i18n/en.json index 7acf9717ab..dc59a413ec 100644 --- a/src/components/InfoViewer/schemaInfo/i18n/en.json +++ b/src/components/InfoViewer/schemaInfo/i18n/en.json @@ -1,6 +1,16 @@ { "title_index-settings": "Index Settings", + "description_subtype_global": "Synchronous index is updated simultaneously with the indexed table and provides strict consistency.", + "description_subtype_global-async": "Asynchronous index receives table changes in the background and provides eventual consistency.", + "description_subtype_global-unique": "Unique secondary index enforces uniqueness for the indexed columns and is updated synchronously.", + "description_subtype_fulltext": "Full-text index enables text search by words, phrases, and, with n-grams, substrings.", + "description_subtype_fulltext-plain": "Plain full-text index stores only the inverted index and supports filtering without relevance ranking.", + "description_subtype_fulltext-relevance": "Relevance full-text index stores term frequency statistics required for relevance ranking.", + "description_subtype_vector": "Vector index enables vector search using distance or similarity functions.", + "description_subtype_local-bloom-filter": "Local bloom_filter index skips data fragments that cannot contain an exact value for equality and IN predicates.", + "description_subtype_local-bloom-ngram-filter": "Local bloom_ngram_filter index skips data fragments that cannot contain a substring for LIKE predicates.", + "description_subtype_local-min-max": "Local min_max index stores minimum and maximum values for each chunk (portion) of a column table, allowing chunks that cannot match query filters to be skipped.", "field_clusters": "Clusters", "field_overlap_clusters": "Overlap Clusters", "field_levels": "Levels", diff --git a/src/components/InfoViewer/schemaInfo/index.ts b/src/components/InfoViewer/schemaInfo/index.ts index 137a1443ca..ca1e86227d 100644 --- a/src/components/InfoViewer/schemaInfo/index.ts +++ b/src/components/InfoViewer/schemaInfo/index.ts @@ -1 +1,2 @@ export * from './TableIndexInfo'; +export {buildTableIndexOverviewInfo} from './utils'; diff --git a/src/components/InfoViewer/schemaInfo/utils.ts b/src/components/InfoViewer/schemaInfo/utils.tsx similarity index 78% rename from src/components/InfoViewer/schemaInfo/utils.ts rename to src/components/InfoViewer/schemaInfo/utils.tsx index 70ef5ce186..fba6ee31e4 100644 --- a/src/components/InfoViewer/schemaInfo/utils.ts +++ b/src/components/InfoViewer/schemaInfo/utils.tsx @@ -1,4 +1,6 @@ +import tenantKeyset from '../../../containers/Tenant/i18n'; import type {TIndexDescription} from '../../../types/api/schema'; +import {EIndexType} from '../../../types/api/schema'; import type { TFulltextIndexAnalyzers, TFulltextIndexSettings, @@ -6,32 +8,44 @@ import type { TVectorIndexSettings, } from '../../../types/api/schema/tableIndex'; import {formatNumber} from '../../../utils/dataFormatters/dataFormatters'; +import {SchemaObjectTypeLabel} from '../../SchemaObjectTypeLabel/SchemaObjectTypeLabel'; +import type {YDBDefinitionListItem} from '../../YDBDefinitionList/YDBDefinitionList'; import type {InfoViewerItem} from '../InfoViewer'; import {formatTableIndexItem} from '../formatters'; import {createInfoFormatter} from '../utils'; import i18n from './i18n'; -const DISPLAYED_FIELDS_KEYS = [ - 'Type', +const INDEX_DETAILS_FIELDS_KEYS = [ 'State', 'DataSize', 'KeyColumnNames', 'DataColumnNames', ] as const; -type DisplayedIndexField = (typeof DISPLAYED_FIELDS_KEYS)[number]; -export function getDisplayedIndexFields(): ReadonlySet { - return new Set(DISPLAYED_FIELDS_KEYS); -} +const INDEX_TYPE_DESCRIPTIONS: Record = { + [EIndexType.EIndexTypeInvalid]: undefined, + [EIndexType.EIndexTypeGlobal]: i18n('description_subtype_global'), + [EIndexType.EIndexTypeGlobalAsync]: i18n('description_subtype_global-async'), + [EIndexType.EIndexTypeGlobalUnique]: i18n('description_subtype_global-unique'), + [EIndexType.EIndexTypeGlobalVectorKmeansTree]: i18n('description_subtype_vector'), + [EIndexType.EIndexTypeGlobalFulltext]: i18n('description_subtype_fulltext'), + [EIndexType.EIndexTypeGlobalFulltextPlain]: i18n('description_subtype_fulltext-plain'), + [EIndexType.EIndexTypeGlobalFulltextRelevance]: i18n('description_subtype_fulltext-relevance'), + [EIndexType.EIndexTypeLocalBloomFilter]: i18n('description_subtype_local-bloom-filter'), + [EIndexType.EIndexTypeLocalBloomNgramFilter]: i18n( + 'description_subtype_local-bloom-ngram-filter', + ), + [EIndexType.EIndexTypeLocalMinMax]: i18n('description_subtype_local-min-max'), +}; -export function buildIndexInfo(tableIndex?: TIndexDescription): InfoViewerItem[] { +function buildIndexInfo(tableIndex?: TIndexDescription): InfoViewerItem[] { const info: InfoViewerItem[] = []; if (!tableIndex) { return info; } - getDisplayedIndexFields().forEach((key) => { + INDEX_DETAILS_FIELDS_KEYS.forEach((key) => { const value = tableIndex[key]; if (value !== undefined) { info.push(formatTableIndexItem(key, value)); @@ -41,6 +55,31 @@ export function buildIndexInfo(tableIndex?: TIndexDescription): InfoViewerItem[] return info; } +export function buildTableIndexOverviewInfo(tableIndex?: TIndexDescription) { + const type = tableIndex?.Type; + const itemsAfterType: YDBDefinitionListItem[] = []; + + if (type !== undefined) { + const typeItem = formatTableIndexItem('Type', type); + itemsAfterType.push({ + name: tenantKeyset('summary.subtype'), + content: ( + + ), + }); + } + + const additionalItems = buildIndexInfo(tableIndex).map(({label, value}) => ({ + name: String(label), + content: value, + })); + + return {itemsAfterType, additionalItems}; +} + /* eslint-disable camelcase */ export function buildVectorIndexSettingsInfo( diff --git a/src/components/LabelWithHelpMark/LabelWithHelpMark.tsx b/src/components/LabelWithHelpMark/LabelWithHelpMark.tsx new file mode 100644 index 0000000000..11e6cec2b0 --- /dev/null +++ b/src/components/LabelWithHelpMark/LabelWithHelpMark.tsx @@ -0,0 +1,31 @@ +import React from 'react'; + +import type {HelpMarkProps, LabelProps} from '@gravity-ui/uikit'; +import {Flex, HelpMark, Label} from '@gravity-ui/uikit'; + +interface LabelWithHelpMarkProps extends LabelProps { + help?: React.ReactNode; + helpMarkProps?: Omit; + contentGap?: React.ComponentProps['gap']; +} + +export function LabelWithHelpMark({ + children, + help, + helpMarkProps, + contentGap = '1', + ...labelProps +}: LabelWithHelpMarkProps) { + return ( + + ); +} diff --git a/src/components/QueryDetails/QueryDetails.scss b/src/components/QueryDetails/QueryDetails.scss index 744bd01c0d..2d981234d3 100644 --- a/src/components/QueryDetails/QueryDetails.scss +++ b/src/components/QueryDetails/QueryDetails.scss @@ -1,5 +1,3 @@ -@use '../../styles/mixins.scss'; - .ydb-query-details { --g-definition-list-item-gap: var(--g-spacing-2); flex: 1; @@ -14,35 +12,15 @@ padding: var(--g-spacing-5) var(--g-spacing-4) var(--g-spacing-5) var(--g-spacing-4); } - &__query-header { - display: flex; - justify-content: space-between; - align-items: center; - - padding: var(--g-spacing-2) var(--g-spacing-3); - - border-bottom: 1px solid var(--g-color-line-generic); - } - - &__query-title { - font-size: 14px; - font-weight: 500; - } - &__query-details-info { flex-grow: 0; } &__query-content { - position: relative; + --ydb-syntax-highlighter-height: 100%; - display: flex; flex: 1; - flex-direction: column; margin-top: var(--g-spacing-5); - - border-radius: 4px; - background-color: var(--code-background-color); } } diff --git a/src/components/QueryDetails/QueryDetails.tsx b/src/components/QueryDetails/QueryDetails.tsx index 96348fae09..55678d75d4 100644 --- a/src/components/QueryDetails/QueryDetails.tsx +++ b/src/components/QueryDetails/QueryDetails.tsx @@ -2,9 +2,9 @@ import {Code} from '@gravity-ui/icons'; import {Button, Flex, Icon} from '@gravity-ui/uikit'; import {cn} from '../../utils/cn'; -import {YDBSyntaxHighlighter} from '../SyntaxHighlighter/YDBSyntaxHighlighter'; import type {YDBDefinitionListItem} from '../YDBDefinitionList/YDBDefinitionList'; import {YDBDefinitionList} from '../YDBDefinitionList/YDBDefinitionList'; +import {YQLCodePreview} from '../YQLCodePreview/YQLCodePreview'; import i18n from './i18n'; @@ -15,7 +15,7 @@ const b = cn('ydb-query-details'); interface QueryDetailsProps { queryText: string; infoItems?: YDBDefinitionListItem[]; - onOpenInEditor: () => void; + onOpenInEditor?: () => void; } export const QueryDetails = ({queryText, infoItems, onOpenInEditor}: QueryDetailsProps) => { @@ -29,25 +29,24 @@ export const QueryDetails = ({queryText, infoItems, onOpenInEditor}: QueryDetail /> )} -
-
-
{i18n('title_query-details')}
- -
- -
+ + + {i18n('action_open-in-editor')} + + ) : undefined + } + /> ); diff --git a/src/components/SchemaObjectTypeLabel/SchemaObjectTypeLabel.scss b/src/components/SchemaObjectTypeLabel/SchemaObjectTypeLabel.scss new file mode 100644 index 0000000000..02763cfd7b --- /dev/null +++ b/src/components/SchemaObjectTypeLabel/SchemaObjectTypeLabel.scss @@ -0,0 +1,9 @@ +.ydb-schema-object-type-label { + &__help-mark { + color: var(--g-color-text-misc-heavy); + } + + &__description { + max-width: 360px; + } +} diff --git a/src/components/SchemaObjectTypeLabel/SchemaObjectTypeLabel.tsx b/src/components/SchemaObjectTypeLabel/SchemaObjectTypeLabel.tsx new file mode 100644 index 0000000000..e7e9ae6389 --- /dev/null +++ b/src/components/SchemaObjectTypeLabel/SchemaObjectTypeLabel.tsx @@ -0,0 +1,29 @@ +import React from 'react'; + +import {cn} from '../../utils/cn'; +import {EMPTY_DATA_PLACEHOLDER} from '../../utils/constants'; +import {LabelWithHelpMark} from '../LabelWithHelpMark/LabelWithHelpMark'; + +import './SchemaObjectTypeLabel.scss'; + +const b = cn('ydb-schema-object-type-label'); + +interface SchemaObjectTypeLabelProps { + value?: React.ReactNode; + description?: React.ReactNode; +} + +export function SchemaObjectTypeLabel({value, description}: SchemaObjectTypeLabelProps) { + return ( + {description} : undefined} + helpMarkProps={{ + className: b('help-mark'), + popoverProps: {placement: ['right', 'bottom']}, + }} + > + {value ?? EMPTY_DATA_PLACEHOLDER} + + ); +} diff --git a/src/components/YQLCodePreview/YQLCodePreview.scss b/src/components/YQLCodePreview/YQLCodePreview.scss new file mode 100644 index 0000000000..ac9d6a36c1 --- /dev/null +++ b/src/components/YQLCodePreview/YQLCodePreview.scss @@ -0,0 +1,26 @@ +.ydb-yql-code-preview { + --ydb-syntax-highlighter-height: auto; + + position: relative; + + display: flex; + flex-direction: column; + + border-radius: 4px; + background-color: var(--code-background-color); + + &__header { + display: flex; + justify-content: space-between; + align-items: center; + + padding: var(--g-spacing-2) var(--g-spacing-3); + + border-bottom: 1px solid var(--g-color-line-generic); + } + + &__title { + font-size: 14px; + font-weight: 500; + } +} diff --git a/src/components/YQLCodePreview/YQLCodePreview.tsx b/src/components/YQLCodePreview/YQLCodePreview.tsx new file mode 100644 index 0000000000..3a3f3e15da --- /dev/null +++ b/src/components/YQLCodePreview/YQLCodePreview.tsx @@ -0,0 +1,31 @@ +import React from 'react'; + +import {cn} from '../../utils/cn'; +import {YDBSyntaxHighlighter} from '../SyntaxHighlighter/YDBSyntaxHighlighter'; + +import './YQLCodePreview.scss'; + +const b = cn('ydb-yql-code-preview'); + +interface YQLCodePreviewProps { + text: string; + title: React.ReactNode; + actions?: React.ReactNode; + className?: string; +} + +export function YQLCodePreview({text, title, actions, className}: YQLCodePreviewProps) { + return ( +
+
+
{title}
+ {actions} +
+ +
+ ); +} diff --git a/src/containers/Cluster/ClusterOverview/components/BridgeInfoTable.tsx b/src/containers/Cluster/ClusterOverview/components/BridgeInfoTable.tsx index 2e523000d8..d5671ffbbb 100644 --- a/src/containers/Cluster/ClusterOverview/components/BridgeInfoTable.tsx +++ b/src/containers/Cluster/ClusterOverview/components/BridgeInfoTable.tsx @@ -1,9 +1,10 @@ import React from 'react'; import {ArrowChevronRight, Ban, CircleCheck, CirclePause, LinkSlash} from '@gravity-ui/icons'; -import {Flex, HelpMark, Icon, Label, Text} from '@gravity-ui/uikit'; +import {Flex, Icon, Text} from '@gravity-ui/uikit'; import type {LabelProps} from '@gravity-ui/uikit'; +import {LabelWithHelpMark} from '../../../../components/LabelWithHelpMark/LabelWithHelpMark'; import type {TBridgePile} from '../../../../types/api/cluster'; import {BridgePileGroupStatus, BridgePileState} from '../../../../types/api/cluster'; import {cn} from '../../../../utils/cn'; @@ -258,28 +259,23 @@ const BridgePileCard = React.memo(function BridgePileCard({pile}: BridgePileCard const showStateHelp = Boolean(help); return ( - + {label} + ); }, [pile.State]); diff --git a/src/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.scss b/src/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.scss index dcfeab5f10..032e5ce687 100644 --- a/src/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.scss +++ b/src/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.scss @@ -1,17 +1,23 @@ .kv-detailed-overview { + --g-definition-list-item-gap: var(--g-spacing-3); + --ydb-info-viewer-title-margin: 0 0 10px; + display: flex; flex-direction: column; - gap: 20px; width: 100%; - height: 100%; + min-height: 100%; + padding-bottom: var(--g-spacing-5); + + > :not(:first-child) { + margin-top: var(--g-spacing-5); + } - // Delete padding for all query text on Diagnostics -> Info tab - pre { - padding: 0; + > .ydb-schema-object-info + .ydb-definition-list { + margin-top: var(--g-spacing-3); } - .g-definition-list__copy-container { - align-items: baseline; + .info-viewer { + --ydb-info-viewer-items-gap: var(--g-spacing-2); } } diff --git a/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts b/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts index 6d2dceb513..8e9a448d7b 100644 --- a/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts +++ b/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts @@ -272,6 +272,7 @@ const pathSubTypeToPages: Record = { [EPathSubType.EPathSubTypeAsyncIndexImplTable]: undefined, [EPathSubType.EPathSubTypeVectorKmeansTreeIndexImplTable]: undefined, [EPathSubType.EPathSubTypeFulltextIndexImplTable]: undefined, + [EPathSubType.EPathSubTypeLocalMinMaxIndex]: undefined, [EPathSubType.EPathSubTypeEmpty]: undefined, }; diff --git a/src/containers/Tenant/Diagnostics/Overview/AsyncReplicationInfo/AsyncReplicationInfo.tsx b/src/containers/Tenant/Diagnostics/Overview/AsyncReplicationInfo/AsyncReplicationInfo.tsx index a90b15403d..a5a29e9079 100644 --- a/src/containers/Tenant/Diagnostics/Overview/AsyncReplicationInfo/AsyncReplicationInfo.tsx +++ b/src/containers/Tenant/Diagnostics/Overview/AsyncReplicationInfo/AsyncReplicationInfo.tsx @@ -1,10 +1,8 @@ -import {Flex, Text} from '@gravity-ui/uikit'; +import {Text} from '@gravity-ui/uikit'; import {AsyncReplicationState} from '../../../../../components/AsyncReplicationState'; import type {YDBDefinitionListItem} from '../../../../../components/YDBDefinitionList/YDBDefinitionList'; -import {YDBDefinitionList} from '../../../../../components/YDBDefinitionList/YDBDefinitionList'; import type {TEvDescribeSchemeResult} from '../../../../../types/api/schema'; -import {getEntityName} from '../../../utils'; import {AsyncReplicationPaths} from '../AsyncReplicationPaths'; import {Credentials} from './Credentials'; @@ -16,27 +14,14 @@ interface AsyncReplicationProps { /** Displays overview for Replication EPathType */ export function AsyncReplicationInfo({data}: AsyncReplicationProps) { - const entityName = getEntityName(data?.PathDescription); - if (!data) { - return ( -
- {i18n('noData')} {entityName} -
- ); + return null; } - const replicationItems = prepareReplicationItems(data); - - return ( - - - - - ); + return ; } -function prepareReplicationItems(data: TEvDescribeSchemeResult) { +export function prepareReplicationItems(data: TEvDescribeSchemeResult): YDBDefinitionListItem[] { const replicationDescription = data.PathDescription?.ReplicationDescription || {}; const state = replicationDescription.State; const srcConnectionParams = replicationDescription.Config?.SrcConnectionParams || {}; diff --git a/src/containers/Tenant/Diagnostics/Overview/AsyncReplicationInfo/i18n/en.json b/src/containers/Tenant/Diagnostics/Overview/AsyncReplicationInfo/i18n/en.json index ecd7f0044e..fc7f489481 100644 --- a/src/containers/Tenant/Diagnostics/Overview/AsyncReplicationInfo/i18n/en.json +++ b/src/containers/Tenant/Diagnostics/Overview/AsyncReplicationInfo/i18n/en.json @@ -1,6 +1,5 @@ { "credentials.label": "Credentials", - "noData": "No data for entity:", "srcConnection.database.label": "Source Database Path", "srcConnection.endpoint.label": "Source Cluster Endpoint", "state.label": "State" diff --git a/src/containers/Tenant/Diagnostics/Overview/ChangefeedInfo/ChangefeedInfo.tsx b/src/containers/Tenant/Diagnostics/Overview/ChangefeedInfo/ChangefeedInfo.tsx index b0e668fab7..3ee4440d5c 100644 --- a/src/containers/Tenant/Diagnostics/Overview/ChangefeedInfo/ChangefeedInfo.tsx +++ b/src/containers/Tenant/Diagnostics/Overview/ChangefeedInfo/ChangefeedInfo.tsx @@ -1,35 +1,24 @@ -import type {InfoViewerItem} from '../../../../../components/InfoViewer'; -import {InfoViewer, formatObject} from '../../../../../components/InfoViewer'; -import { - formatCdcStreamItem, - formatCommonItem, -} from '../../../../../components/InfoViewer/formatters'; +import {formatObject} from '../../../../../components/InfoViewer'; +import {formatCdcStreamItem} from '../../../../../components/InfoViewer/formatters'; +import type {YDBDefinitionListItem} from '../../../../../components/YDBDefinitionList/YDBDefinitionList'; import type {TEvDescribeSchemeResult} from '../../../../../types/api/schema'; -import {getEntityName} from '../../../utils'; import {TopicStats} from '../TopicStats'; -const prepareChangefeedInfo = (changefeedData?: TEvDescribeSchemeResult): Array => { +export function prepareChangefeedInfo( + changefeedData?: TEvDescribeSchemeResult, +): YDBDefinitionListItem[] { if (!changefeedData) { return []; } - const streamDescription = changefeedData?.PathDescription?.CdcStreamDescription; - + const streamDescription = changefeedData.PathDescription?.CdcStreamDescription; const {Mode, Format} = streamDescription || {}; - const changefeedInfo = formatObject(formatCdcStreamItem, { - Mode, - Format, - }); - - const createStep = changefeedData?.PathDescription?.Self?.CreateStep; - - if (Number(createStep)) { - changefeedInfo.unshift(formatCommonItem('CreateStep', createStep)); - } - - return changefeedInfo; -}; + return formatObject(formatCdcStreamItem, {Mode, Format}).map(({label, value}) => ({ + name: String(label), + content: value, + })); +} interface ChangefeedProps { path: string; @@ -41,16 +30,9 @@ interface ChangefeedProps { /** Displays overview for CDCStream EPathType */ export const ChangefeedInfo = ({path, database, data, databaseFullPath}: ChangefeedProps) => { - const entityName = getEntityName(data?.PathDescription); - if (!data) { - return
No {entityName} data
; + return null; } - return ( -
- - -
- ); + return ; }; diff --git a/src/containers/Tenant/Diagnostics/Overview/DatabaseInfo/DatabaseInfo.scss b/src/containers/Tenant/Diagnostics/Overview/DatabaseInfo/DatabaseInfo.scss index 4d16a2db7b..c4b3ac3718 100644 --- a/src/containers/Tenant/Diagnostics/Overview/DatabaseInfo/DatabaseInfo.scss +++ b/src/containers/Tenant/Diagnostics/Overview/DatabaseInfo/DatabaseInfo.scss @@ -4,10 +4,6 @@ &__wrapper { margin-top: var(--g-spacing-2); } - &__note { - max-width: 360px; - } - &__title { margin: var(--g-spacing-4) 0; } diff --git a/src/containers/Tenant/Diagnostics/Overview/DatabaseInfo/DatabaseInfo.tsx b/src/containers/Tenant/Diagnostics/Overview/DatabaseInfo/DatabaseInfo.tsx index 85376033b4..0f1034cc2a 100644 --- a/src/containers/Tenant/Diagnostics/Overview/DatabaseInfo/DatabaseInfo.tsx +++ b/src/containers/Tenant/Diagnostics/Overview/DatabaseInfo/DatabaseInfo.tsx @@ -1,14 +1,12 @@ import React from 'react'; -import {Alert, Card, Flex, HelpMark, Label, Text} from '@gravity-ui/uikit'; +import {Alert, Card, Flex, Text} from '@gravity-ui/uikit'; import {SegmentedProgress} from '../../../../../components/SegmentedProgress/SegmentedProgress'; -import type {YDBDefinitionListItem} from '../../../../../components/YDBDefinitionList/YDBDefinitionList'; -import {YDBDefinitionList} from '../../../../../components/YDBDefinitionList/YDBDefinitionList'; import type {TEvDescribeSchemeResult} from '../../../../../types/api/schema'; import {cn} from '../../../../../utils/cn'; -import {formatDateTime, formatNumber} from '../../../../../utils/dataFormatters/dataFormatters'; -import {isDomain} from '../../../ObjectSummary/transformPath'; +import {formatNumber} from '../../../../../utils/dataFormatters/dataFormatters'; +import {SchemaObjectInfo} from '../SchemaObjectInfo'; import {dbInfoKeyset} from './i18n'; @@ -22,16 +20,12 @@ interface DBInfoProps { } export function DatabaseInfo({data, path}: DBInfoProps) { - const items = React.useMemo(() => { - return prepareDatabaseInfo({data, path}); - }, [data, path]); - const {PathsInside, PathsLimit, ShardsInside, ShardsLimit} = data?.PathDescription?.DomainDescription ?? {}; return ( - + {dbInfoKeyset('title_limits-and-usage')} @@ -53,67 +47,6 @@ export function DatabaseInfo({data, path}: DBInfoProps) { ); } -function prepareDatabaseInfo({data, path}: DBInfoProps): YDBDefinitionListItem[] { - const items: YDBDefinitionListItem[] = []; - - const {Path, PathId} = data || {}; - const {PathVersion, CreateStep} = data?.PathDescription?.Self || {}; - - const created = formatDateTime(CreateStep); - - items.push( - { - name: dbInfoKeyset('title_type'), - content: , - }, - { - name: dbInfoKeyset('title_id'), - content: PathId, - copyText: PathId, - }, - { - name: dbInfoKeyset('title_version'), - content: PathVersion, - copyText: PathVersion, - }, - ); - - if (Number(CreateStep)) { - items.push({ - name: dbInfoKeyset('title_created'), - content: created, - copyText: created, - }); - } - - items.push({ - name: dbInfoKeyset('title_path'), - content: Path, - copyText: Path, - }); - - return items; -} - -function TypeLabel({data, path}: DBInfoProps) { - const isDomainDB = isDomain(path, data?.PathDescription?.Self?.PathType); - const dbType = isDomainDB ? dbInfoKeyset('title_domain') : dbInfoKeyset('title_sub-domain'); - const dbTypeNote = isDomainDB - ? dbInfoKeyset('description_domain') - : dbInfoKeyset('description_sub-domain'); - - return ( - - ); -} - function DBInfoStatsCard({ value, limit, diff --git a/src/containers/Tenant/Diagnostics/Overview/DatabaseInfo/i18n/en.json b/src/containers/Tenant/Diagnostics/Overview/DatabaseInfo/i18n/en.json index e321e23404..d4be21918e 100644 --- a/src/containers/Tenant/Diagnostics/Overview/DatabaseInfo/i18n/en.json +++ b/src/containers/Tenant/Diagnostics/Overview/DatabaseInfo/i18n/en.json @@ -1,13 +1,4 @@ { - "title_type": "Type", - "title_domain": "Domain", - "title_sub-domain": "SubDomain", - "description_sub-domain": "Internal YDB type. All user-created databases are SubDomains. Domain type is used only for top-level cluster directories.", - "description_domain": "Internal YDB type. Domain is top-level cluster directory.", - "title_id": "ID", - "title_version": "Version", - "title_created": "Created", - "title_path": "Full Path", "title_limits-and-usage": "Limits & usage", "title_paths": "Paths", "title_shards": "Shards", diff --git a/src/containers/Tenant/Diagnostics/Overview/Overview.tsx b/src/containers/Tenant/Diagnostics/Overview/Overview.tsx index fc75ac49ce..ada28bef2b 100644 --- a/src/containers/Tenant/Diagnostics/Overview/Overview.tsx +++ b/src/containers/Tenant/Diagnostics/Overview/Overview.tsx @@ -7,9 +7,6 @@ import {useClusterWithProxy} from '../../../../store/reducers/cluster/cluster'; import {overviewApi} from '../../../../store/reducers/overview/overview'; import {EPathType} from '../../../../types/api/schema'; import {useAutoRefreshInterval} from '../../../../utils/hooks'; -import {ExternalDataSourceInfo} from '../../Info/ExternalDataSource/ExternalDataSource'; -import {ExternalTableInfo} from '../../Info/ExternalTable/ExternalTable'; -import {SystemViewInfo} from '../../Info/SystemView/SystemView'; import {ViewInfo} from '../../Info/View/View'; import {isDomain} from '../../ObjectSummary/transformPath'; import {useNavigationV2Enabled} from '../../utils/useNavigationV2Enabled'; @@ -18,6 +15,7 @@ import {AsyncReplicationInfo} from './AsyncReplicationInfo'; import {ChangefeedInfo} from './ChangefeedInfo'; import {DatabaseInfo} from './DatabaseInfo/DatabaseInfo'; import {DefaultEntityInfo} from './DefaultEntityInfo'; +import {SchemaObjectInfoContainer} from './SchemaObjectInfo/SchemaObjectInfoContainer'; import {StreamingQueryInfo} from './StreamingQueryInfo'; import {TableInfo} from './TableInfo'; import {TopicInfo} from './TopicInfo'; @@ -38,8 +36,7 @@ function Overview({type, path, database, databaseFullPath}: OverviewProps) { const {currentData, isFetching, error} = overviewApi.useGetOverviewQuery( {path, database, databaseFullPath, useMetaProxy}, - //overview is not supported for streaming query, data request is inside StreamingQueryInfo - {pollingInterval: autoRefreshInterval, skip: type === EPathType.EPathTypeStreamingQuery}, + {pollingInterval: autoRefreshInterval}, ); const loading = isFetching && currentData === undefined; @@ -59,7 +56,7 @@ function Overview({type, path, database, databaseFullPath}: OverviewProps) { [EPathType.EPathTypeResourcePool]: undefined, [EPathType.EPathTypeSecret]: undefined, [EPathType.EPathTypeTable]: renderTableInfo, - [EPathType.EPathTypeSysView]: () => , + [EPathType.EPathTypeSysView]: undefined, [EPathType.EPathTypeSubDomain]: undefined, [EPathType.EPathTypeTableIndex]: () => , [EPathType.EPathTypeExtSubDomain]: undefined, @@ -81,8 +78,8 @@ function Overview({type, path, database, databaseFullPath}: OverviewProps) { database={database} /> ), - [EPathType.EPathTypeExternalTable]: () => , - [EPathType.EPathTypeExternalDataSource]: () => , + [EPathType.EPathTypeExternalTable]: undefined, + [EPathType.EPathTypeExternalDataSource]: undefined, [EPathType.EPathTypeView]: () => , [EPathType.EPathTypeReplication]: () => , [EPathType.EPathTypeTransfer]: () => ( @@ -107,13 +104,36 @@ function Overview({type, path, database, databaseFullPath}: OverviewProps) { return ; } - return (type && pathTypeToComponent[type]?.()) || ; + if (!type || type === EPathType.EPathTypeInvalid) { + return ; + } + + const content = pathTypeToComponent[type]?.(); + + const commonInfo = ( + + ); + + if (!content) { + return commonInfo; + } + + return ( + + {commonInfo} + {content} + + ); }; if (loading) { return ; } + if (error && !currentData && type === EPathType.EPathTypeStreamingQuery) { + return renderContent(); + } + return ( {error ? : null} diff --git a/src/containers/Tenant/Diagnostics/Overview/SchemaObjectInfo/SchemaObjectInfo.scss b/src/containers/Tenant/Diagnostics/Overview/SchemaObjectInfo/SchemaObjectInfo.scss new file mode 100644 index 0000000000..3b234860d5 --- /dev/null +++ b/src/containers/Tenant/Diagnostics/Overview/SchemaObjectInfo/SchemaObjectInfo.scss @@ -0,0 +1,5 @@ +.ydb-schema-object-info { + .g-definition-list__copy-button { + opacity: 1; + } +} diff --git a/src/containers/Tenant/Diagnostics/Overview/SchemaObjectInfo/SchemaObjectInfo.tsx b/src/containers/Tenant/Diagnostics/Overview/SchemaObjectInfo/SchemaObjectInfo.tsx new file mode 100644 index 0000000000..3a8f7b2a99 --- /dev/null +++ b/src/containers/Tenant/Diagnostics/Overview/SchemaObjectInfo/SchemaObjectInfo.tsx @@ -0,0 +1,44 @@ +import React from 'react'; + +import type {YDBDefinitionListItem} from '../../../../../components/YDBDefinitionList/YDBDefinitionList'; +import {YDBDefinitionList} from '../../../../../components/YDBDefinitionList/YDBDefinitionList'; +import type {EPathType, TEvDescribeSchemeResult} from '../../../../../types/api/schema'; +import {cn} from '../../../../../utils/cn'; + +import {prepareSchemaObjectInfoItems} from './prepareSchemaObjectInfo'; + +import './SchemaObjectInfo.scss'; + +const b = cn('ydb-schema-object-info'); + +export interface SchemaObjectInfoProps { + data?: TEvDescribeSchemeResult; + fallbackType?: EPathType; + path: string; + itemsAfterType?: YDBDefinitionListItem[]; + additionalItems?: YDBDefinitionListItem[]; +} + +export function SchemaObjectInfo({ + data, + fallbackType, + path, + itemsAfterType, + additionalItems, +}: SchemaObjectInfoProps) { + const items = React.useMemo( + () => + prepareSchemaObjectInfoItems({ + data, + fallbackType, + path, + itemsAfterType, + additionalItems, + }), + [additionalItems, data, fallbackType, itemsAfterType, path], + ); + + return ( + + ); +} diff --git a/src/containers/Tenant/Diagnostics/Overview/SchemaObjectInfo/SchemaObjectInfoContainer.tsx b/src/containers/Tenant/Diagnostics/Overview/SchemaObjectInfo/SchemaObjectInfoContainer.tsx new file mode 100644 index 0000000000..dc24c4af81 --- /dev/null +++ b/src/containers/Tenant/Diagnostics/Overview/SchemaObjectInfo/SchemaObjectInfoContainer.tsx @@ -0,0 +1,85 @@ +import React from 'react'; + +import {useLocation} from 'react-router-dom'; + +import {buildTableIndexOverviewInfo} from '../../../../../components/InfoViewer/schemaInfo'; +import {createExternalUILink, parseQuery} from '../../../../../routes'; +import type {TEvDescribeSchemeResult} from '../../../../../types/api/schema'; +import {EPathType} from '../../../../../types/api/schema'; +import {prepareReplicationItems} from '../AsyncReplicationInfo'; +import {prepareChangefeedInfo} from '../ChangefeedInfo'; +import {prepareColumnTableGeneralInfo} from '../TableInfo/prepareTableInfo/prepareColumnTableInfo'; + +import {SchemaObjectInfo} from './SchemaObjectInfo'; +import { + prepareExternalDataSourceInfo, + prepareExternalTableInfo, + prepareSystemViewTypeItems, +} from './prepareSpecificSchemaObjectInfo'; + +interface SchemaObjectInfoContainerProps { + data?: TEvDescribeSchemeResult; + type?: EPathType; + path: string; +} + +export function SchemaObjectInfoContainer({data, type, path}: SchemaObjectInfoContainerProps) { + const location = useLocation(); + const externalTableInfoItems = React.useMemo(() => { + const externalTableDescription = data?.PathDescription?.ExternalTableDescription; + if (!externalTableDescription) { + return []; + } + + const pathToDataSource = createExternalUILink({ + ...parseQuery(location), + schema: externalTableDescription.DataSourcePath, + }); + + return prepareExternalTableInfo(data, pathToDataSource); + }, [data, location]); + const tableIndexOverviewInfo = React.useMemo( + () => + type === EPathType.EPathTypeTableIndex + ? buildTableIndexOverviewInfo(data?.PathDescription?.TableIndex) + : undefined, + [data, type], + ); + const itemsAfterType = React.useMemo(() => { + if (type === EPathType.EPathTypeSysView) { + return prepareSystemViewTypeItems(data); + } + + return tableIndexOverviewInfo?.itemsAfterType; + }, [data, tableIndexOverviewInfo, type]); + const additionalItems = React.useMemo(() => { + const columnTableDescription = data?.PathDescription?.ColumnTableDescription; + + switch (type) { + case EPathType.EPathTypeColumnTable: + return columnTableDescription + ? prepareColumnTableGeneralInfo(columnTableDescription) + : undefined; + case EPathType.EPathTypeCdcStream: + return prepareChangefeedInfo(data); + case EPathType.EPathTypeExternalDataSource: + return data ? prepareExternalDataSourceInfo(data) : undefined; + case EPathType.EPathTypeExternalTable: + return externalTableInfoItems; + case EPathType.EPathTypeReplication: + return data ? prepareReplicationItems(data) : undefined; + default: + return tableIndexOverviewInfo?.additionalItems; + } + }, [data, externalTableInfoItems, tableIndexOverviewInfo, type]); + + return ( + + ); +} diff --git a/src/containers/Tenant/Diagnostics/Overview/SchemaObjectInfo/i18n/en.json b/src/containers/Tenant/Diagnostics/Overview/SchemaObjectInfo/i18n/en.json new file mode 100644 index 0000000000..11942d8dc8 --- /dev/null +++ b/src/containers/Tenant/Diagnostics/Overview/SchemaObjectInfo/i18n/en.json @@ -0,0 +1,34 @@ +{ + "field_full-path": "Full Path", + "field_location": "Location", + "field_auth-method": "Auth Method", + + "value_domain": "Domain", + "value_sub-domain": "SubDomain", + "value_auth-method-none": "None", + "value_auth-method-service-account": "Service Account", + "value_auth-method-aws": "AWS", + "value_auth-method-token": "Token", + "value_auth-method-basic": "Basic", + "value_auth-method-mdb-basic": "MDB Basic", + + "description_domain": "Internal YDB type. Domain is top-level cluster directory.", + "description_sub-domain": "Internal YDB type. All user-created databases are SubDomains. Domain type is used only for top-level cluster directories.", + "description_directory": "Directory organizes schema objects within the hierarchical database namespace.", + "description_resource-pool": "Resource pool defines CPU, memory, concurrency, and queue limits for its queries.", + "description_secret": "Secret stores credentials for external-system authentication, and its value cannot be read back.", + "description_table": "Row-oriented table stores related data as rows and columns and is optimized for transactional workloads.", + "description_system-view": "System view exposes service information about the current database state under the .sys path.", + "description_table-index": "Secondary index is a separate global structure that accelerates queries on non-key columns.", + "description_table-store": "Table store is a container for column-oriented tables.", + "description_column-table": "Column-oriented table stores each column separately and is optimized for analytical workloads.", + "description_changefeed": "Changefeed captures table row changes and writes change records to a topic for further processing.", + "description_topic": "Topic stores unstructured messages and delivers them to independent subscribers.", + "description_external-table": "External table describes the schema and placement of data stored in an external source.", + "description_external-data-source": "External data source stores the connection parameters required to access an external system.", + "description_view": "View represents a table-shaped result defined by a query and contains no data of its own.", + "description_replication": "Asynchronous replication instance stores the settings used to replicate data to a target database.", + "description_transfer": "Transfer asynchronously reads messages from a topic and writes transformed data to a table.", + "description_streaming-query": "Streaming query continuously processes unbounded data instead of finishing after a single result.", + "description_system-view-type": "This value identifies which database service information the system view exposes." +} diff --git a/src/containers/Tenant/Diagnostics/Overview/SchemaObjectInfo/i18n/index.ts b/src/containers/Tenant/Diagnostics/Overview/SchemaObjectInfo/i18n/index.ts new file mode 100644 index 0000000000..a2f520791f --- /dev/null +++ b/src/containers/Tenant/Diagnostics/Overview/SchemaObjectInfo/i18n/index.ts @@ -0,0 +1,7 @@ +import {registerKeysets} from '../../../../../../utils/i18n'; + +import en from './en.json'; + +const COMPONENT = 'ydb-schema-object-info'; + +export const schemaObjectInfoKeyset = registerKeysets(COMPONENT, {en}); diff --git a/src/containers/Tenant/Diagnostics/Overview/SchemaObjectInfo/index.ts b/src/containers/Tenant/Diagnostics/Overview/SchemaObjectInfo/index.ts new file mode 100644 index 0000000000..b1df602937 --- /dev/null +++ b/src/containers/Tenant/Diagnostics/Overview/SchemaObjectInfo/index.ts @@ -0,0 +1,3 @@ +export {SchemaObjectInfo} from './SchemaObjectInfo'; +export type {SchemaObjectInfoProps} from './SchemaObjectInfo'; +export {prepareSchemaObjectInfoItems} from './prepareSchemaObjectInfo'; diff --git a/src/containers/Tenant/Diagnostics/Overview/SchemaObjectInfo/prepareSchemaObjectInfo.tsx b/src/containers/Tenant/Diagnostics/Overview/SchemaObjectInfo/prepareSchemaObjectInfo.tsx new file mode 100644 index 0000000000..9ec152364f --- /dev/null +++ b/src/containers/Tenant/Diagnostics/Overview/SchemaObjectInfo/prepareSchemaObjectInfo.tsx @@ -0,0 +1,129 @@ +import {SchemaObjectTypeLabel} from '../../../../../components/SchemaObjectTypeLabel/SchemaObjectTypeLabel'; +import type {YDBDefinitionListItem} from '../../../../../components/YDBDefinitionList/YDBDefinitionList'; +import type {TEvDescribeSchemeResult} from '../../../../../types/api/schema'; +import {EPathType} from '../../../../../types/api/schema'; +import {EMPTY_DATA_PLACEHOLDER} from '../../../../../utils/constants'; +import {formatDateTime} from '../../../../../utils/dataFormatters/dataFormatters'; +import {getPathTypeName, isDomain} from '../../../ObjectSummary/transformPath'; +import tenantKeyset from '../../../i18n'; + +import {schemaObjectInfoKeyset} from './i18n'; + +const PATH_TYPE_DESCRIPTIONS: Record = { + [EPathType.EPathTypeInvalid]: undefined, + [EPathType.EPathTypeDir]: schemaObjectInfoKeyset('description_directory'), + [EPathType.EPathTypeResourcePool]: schemaObjectInfoKeyset('description_resource-pool'), + [EPathType.EPathTypeSecret]: schemaObjectInfoKeyset('description_secret'), + [EPathType.EPathTypeTable]: schemaObjectInfoKeyset('description_table'), + [EPathType.EPathTypeSysView]: schemaObjectInfoKeyset('description_system-view'), + [EPathType.EPathTypeSubDomain]: schemaObjectInfoKeyset('description_sub-domain'), + [EPathType.EPathTypeTableIndex]: schemaObjectInfoKeyset('description_table-index'), + [EPathType.EPathTypeExtSubDomain]: schemaObjectInfoKeyset('description_sub-domain'), + [EPathType.EPathTypeColumnStore]: schemaObjectInfoKeyset('description_table-store'), + [EPathType.EPathTypeColumnTable]: schemaObjectInfoKeyset('description_column-table'), + [EPathType.EPathTypeCdcStream]: schemaObjectInfoKeyset('description_changefeed'), + [EPathType.EPathTypePersQueueGroup]: schemaObjectInfoKeyset('description_topic'), + [EPathType.EPathTypeExternalTable]: schemaObjectInfoKeyset('description_external-table'), + [EPathType.EPathTypeExternalDataSource]: schemaObjectInfoKeyset( + 'description_external-data-source', + ), + [EPathType.EPathTypeView]: schemaObjectInfoKeyset('description_view'), + [EPathType.EPathTypeReplication]: schemaObjectInfoKeyset('description_replication'), + [EPathType.EPathTypeTransfer]: schemaObjectInfoKeyset('description_transfer'), + [EPathType.EPathTypeStreamingQuery]: schemaObjectInfoKeyset('description_streaming-query'), +}; + +interface PrepareSchemaObjectInfoItemsParams { + data?: TEvDescribeSchemeResult; + fallbackType?: EPathType; + path: string; + itemsAfterType?: YDBDefinitionListItem[]; + additionalItems?: YDBDefinitionListItem[]; +} + +function isPresent(value: string | number | undefined): value is string | number { + return value !== undefined && value !== ''; +} + +function prepareTypeLabel({ + data, + fallbackType, + path, +}: Pick) { + const pathDescription = data?.PathDescription; + const self = pathDescription?.Self; + const pathType = self?.PathType ?? fallbackType; + + const isDomainDatabase = isDomain(path, pathType); + let value = getPathTypeName(path, pathType); + let description: string | undefined; + if (isDomainDatabase) { + value = schemaObjectInfoKeyset('value_domain'); + description = schemaObjectInfoKeyset('description_domain'); + } else if ( + pathType === EPathType.EPathTypeSubDomain || + pathType === EPathType.EPathTypeExtSubDomain + ) { + value = schemaObjectInfoKeyset('value_sub-domain'); + description = schemaObjectInfoKeyset('description_sub-domain'); + } else if (pathType) { + description = PATH_TYPE_DESCRIPTIONS[pathType]; + } + + return ( + + ); +} + +export function prepareSchemaObjectInfoItems({ + data, + fallbackType, + path, + itemsAfterType = [], + additionalItems = [], +}: PrepareSchemaObjectInfoItemsParams): YDBDefinitionListItem[] { + const self = data?.PathDescription?.Self; + const pathId = isPresent(self?.PathId) ? self.PathId : data?.PathId; + const pathVersion = self?.PathVersion; + const createStep = self?.CreateStep; + const fullPath = isPresent(data?.Path) ? data.Path : path; + + const items: YDBDefinitionListItem[] = [ + { + name: tenantKeyset('summary.type'), + content: prepareTypeLabel({data, fallbackType, path}), + }, + ...itemsAfterType, + ]; + + items.push( + { + name: tenantKeyset('summary.id'), + content: isPresent(pathId) ? pathId : EMPTY_DATA_PLACEHOLDER, + copyText: isPresent(pathId) ? pathId : undefined, + }, + { + name: tenantKeyset('summary.version'), + content: isPresent(pathVersion) ? pathVersion : EMPTY_DATA_PLACEHOLDER, + copyText: isPresent(pathVersion) ? pathVersion : undefined, + }, + ); + + if (Number(createStep)) { + items.push({ + name: tenantKeyset('summary.created'), + content: formatDateTime(createStep), + }); + } + + items.push( + { + name: schemaObjectInfoKeyset('field_full-path'), + content: isPresent(fullPath) ? fullPath : EMPTY_DATA_PLACEHOLDER, + copyText: isPresent(fullPath) ? fullPath : undefined, + }, + ...additionalItems, + ); + + return items; +} diff --git a/src/containers/Tenant/Diagnostics/Overview/SchemaObjectInfo/prepareSpecificSchemaObjectInfo.tsx b/src/containers/Tenant/Diagnostics/Overview/SchemaObjectInfo/prepareSpecificSchemaObjectInfo.tsx new file mode 100644 index 0000000000..deb7e834be --- /dev/null +++ b/src/containers/Tenant/Diagnostics/Overview/SchemaObjectInfo/prepareSpecificSchemaObjectInfo.tsx @@ -0,0 +1,104 @@ +import {LinkWithIcon} from '../../../../../components/LinkWithIcon/LinkWithIcon'; +import {SchemaObjectTypeLabel} from '../../../../../components/SchemaObjectTypeLabel/SchemaObjectTypeLabel'; +import type {YDBDefinitionListItem} from '../../../../../components/YDBDefinitionList/YDBDefinitionList'; +import type {TEvDescribeSchemeResult} from '../../../../../types/api/schema'; +import {EMPTY_DATA_PLACEHOLDER} from '../../../../../utils/constants'; +import {prepareSystemViewType} from '../../../../../utils/schema'; +import tenantKeyset from '../../../i18n'; + +import {schemaObjectInfoKeyset} from './i18n'; + +function getAuthMethodValue(data: TEvDescribeSchemeResult) { + const {Auth} = data.PathDescription?.ExternalDataSourceDescription || {}; + if (Auth?.ServiceAccount) { + return schemaObjectInfoKeyset('value_auth-method-service-account'); + } + if (Auth?.Aws) { + return schemaObjectInfoKeyset('value_auth-method-aws'); + } + if (Auth?.Token) { + return schemaObjectInfoKeyset('value_auth-method-token'); + } + if (Auth?.Basic) { + return schemaObjectInfoKeyset('value_auth-method-basic'); + } + if (Auth?.MdbBasic) { + return schemaObjectInfoKeyset('value_auth-method-mdb-basic'); + } + return schemaObjectInfoKeyset('value_auth-method-none'); +} + +export function prepareExternalDataSourceInfo( + data: TEvDescribeSchemeResult, +): YDBDefinitionListItem[] { + const {SourceType, Location} = data.PathDescription?.ExternalDataSourceDescription || {}; + + return [ + { + name: tenantKeyset('summary.source-type'), + content: SourceType, + }, + { + name: schemaObjectInfoKeyset('field_location'), + content: Location, + copyText: Location, + }, + { + name: schemaObjectInfoKeyset('field_auth-method'), + content: getAuthMethodValue(data), + }, + ]; +} + +export function prepareExternalTableInfo( + data: TEvDescribeSchemeResult, + pathToDataSource: string, +): YDBDefinitionListItem[] { + const {SourceType, DataSourcePath, Location} = + data.PathDescription?.ExternalTableDescription || {}; + const dataSourceName = DataSourcePath?.split('/').pop(); + + return [ + { + name: tenantKeyset('summary.source-type'), + content: SourceType, + }, + { + name: tenantKeyset('summary.data-source'), + content: DataSourcePath && ( + + + + ), + }, + { + name: schemaObjectInfoKeyset('field_location'), + content: Location, + copyText: Location, + }, + ]; +} + +function formatSystemViewType(type?: string) { + return type?.replace(/([A-Z])([A-Z][a-z])/g, '$1 $2').replace(/([a-z\d])([A-Z])/g, '$1 $2'); +} + +export function prepareSystemViewTypeItems( + data?: TEvDescribeSchemeResult, +): YDBDefinitionListItem[] { + const type = prepareSystemViewType(data?.PathDescription?.SysViewDescription?.Type); + + return [ + { + name: tenantKeyset('summary.system-view-type'), + content: ( + + ), + }, + ]; +} diff --git a/src/containers/Tenant/Diagnostics/Overview/StreamingQueryInfo/StreamingQueryInfo.scss b/src/containers/Tenant/Diagnostics/Overview/StreamingQueryInfo/StreamingQueryInfo.scss new file mode 100644 index 0000000000..6e63a41b85 --- /dev/null +++ b/src/containers/Tenant/Diagnostics/Overview/StreamingQueryInfo/StreamingQueryInfo.scss @@ -0,0 +1,5 @@ +.ydb-streaming-query-info { + &__loader { + margin-top: 50px; + } +} diff --git a/src/containers/Tenant/Diagnostics/Overview/StreamingQueryInfo/StreamingQueryInfo.tsx b/src/containers/Tenant/Diagnostics/Overview/StreamingQueryInfo/StreamingQueryInfo.tsx index fa8f4d481b..9822512862 100644 --- a/src/containers/Tenant/Diagnostics/Overview/StreamingQueryInfo/StreamingQueryInfo.tsx +++ b/src/containers/Tenant/Diagnostics/Overview/StreamingQueryInfo/StreamingQueryInfo.tsx @@ -2,14 +2,14 @@ import React from 'react'; import {Label} from '@gravity-ui/uikit'; -import {LoaderWrapper} from '../../../../../components/LoaderWrapper/LoaderWrapper'; -import {YDBSyntaxHighlighter} from '../../../../../components/SyntaxHighlighter/YDBSyntaxHighlighter'; -import {YDBDefinitionList} from '../../../../../components/YDBDefinitionList/YDBDefinitionList'; +import {Loader} from '../../../../../components/Loader'; import type {YDBDefinitionListItem} from '../../../../../components/YDBDefinitionList/YDBDefinitionList'; +import {YDBDefinitionList} from '../../../../../components/YDBDefinitionList/YDBDefinitionList'; +import {YQLCodePreview} from '../../../../../components/YQLCodePreview/YQLCodePreview'; import {streamingQueriesApi} from '../../../../../store/reducers/streamingQuery/streamingQuery'; import type {ErrorResponse} from '../../../../../types/api/query'; -import {EPathType} from '../../../../../types/api/schema'; import type {IQueryResult} from '../../../../../types/store/query'; +import {cn} from '../../../../../utils/cn'; import { getStringifiedData, stripIndentByFirstLine, @@ -17,29 +17,38 @@ import { } from '../../../../../utils/dataFormatters/dataFormatters'; import {isErrorResponse} from '../../../../../utils/query'; import {ResultIssuesModal} from '../../../Query/Issues/Issues'; -import {getEntityName} from '../../../utils'; import i18n from './i18n'; +import './StreamingQueryInfo.scss'; + interface StreamingQueryProps { database: string; path: string; } -export function StreamingQueryInfo({database, path}: StreamingQueryProps) { - const entityName = getEntityName({Self: {PathType: EPathType.EPathTypeStreamingQuery}}); +const b = cn('ydb-streaming-query-info'); +export function StreamingQueryInfo({database, path}: StreamingQueryProps) { const {data: sysData, isFetching} = streamingQueriesApi.useGetStreamingQueryInfoQuery( {database, path}, {skip: !database || !path}, ); + const loading = isFetching && sysData === undefined; - const items = prepareStreamingQueryItems(sysData); + if (loading) { + return ; + } + + const {items, queryText} = prepareStreamingQueryItems(sysData); return ( - - - + + + {queryText ? ( + + ) : null} + ); } @@ -65,9 +74,9 @@ function StateLabel({state}: {state?: string}) { return ; } -function prepareStreamingQueryItems(sysData?: IQueryResult): YDBDefinitionListItem[] { +function prepareStreamingQueryItems(sysData?: IQueryResult) { if (!sysData) { - return []; + return {items: [], queryText: undefined}; } const info: YDBDefinitionListItem[] = []; @@ -94,15 +103,7 @@ function prepareStreamingQueryItems(sysData?: IQueryResult): YDBDefinitionListIt }); } - info.push({ - name: i18n('field_query-text'), - copyText: normalizedQueryText, - content: normalizedQueryText ? ( - - ) : null, - }); - - return info; + return {items: info, queryText: normalizedQueryText}; } function parseErrorData(raw: unknown): ErrorResponse | string | undefined { diff --git a/src/containers/Tenant/Diagnostics/Overview/TableInfo/TableInfo.tsx b/src/containers/Tenant/Diagnostics/Overview/TableInfo/TableInfo.tsx index 5282b792ba..6cc2f62125 100644 --- a/src/containers/Tenant/Diagnostics/Overview/TableInfo/TableInfo.tsx +++ b/src/containers/Tenant/Diagnostics/Overview/TableInfo/TableInfo.tsx @@ -9,8 +9,7 @@ import {EPathType} from '../../../../../types/api/schema'; import type {TEvDescribeSchemeResult} from '../../../../../types/api/schema'; import {cn} from '../../../../../utils/cn'; import {useCompactionFeature} from '../../../../../utils/hooks/useCompactionFeature'; -import {EntityTitle} from '../../../EntityTitle/EntityTitle'; -import {isRowTableType} from '../../../utils/schema'; +import tenantKeyset from '../../../i18n'; import { CompactTableAction, @@ -34,18 +33,6 @@ interface TableInfoProps { path: string; } -const TableInfoHeader = ({data}: {data?: TEvDescribeSchemeResult}) => { - const actualType = data?.PathDescription?.Self?.PathType; - const isRowTable = isRowTableType(actualType); - const title: React.ReactNode = isRowTable ? ( - i18n('title_partitioning') - ) : ( - - ); - - return
{title}
; -}; - export const TableInfo = ({data, type, database, path}: TableInfoProps) => { const isRowTable = type === EPathType.EPathTypeTable; @@ -124,39 +111,41 @@ export const TableInfo = ({data, type, database, path}: TableInfoProps) => { isCancelling={isCancellingOperation} /> )} - - - - {managePartitioningDialogConfig && ( - - - - )} - {compactionEnabledForTable && ( - - )} + {isRowTable && ( + +
{tenantKeyset('summary.partitioning')}
+ + {managePartitioningDialogConfig && ( + + + + )} + {compactionEnabledForTable && ( + + )} +
-
+ )} {partitionProgressConfig && (
{ />
)} -
-
- + {(generalInfoLeft.length > 0 || generalInfoRight.length > 0) && ( +
+
+ +
+
+ +
-
- -
-
+ )}
diff --git a/src/containers/Tenant/Diagnostics/Overview/TableInfo/i18n/en.json b/src/containers/Tenant/Diagnostics/Overview/TableInfo/i18n/en.json index 806cdafa71..d2042faaa1 100644 --- a/src/containers/Tenant/Diagnostics/Overview/TableInfo/i18n/en.json +++ b/src/containers/Tenant/Diagnostics/Overview/TableInfo/i18n/en.json @@ -1,5 +1,4 @@ { - "title_partitioning": "Partitioning", "title_table-stats": "Stats", "title_tablet-metrics": "Metrics", "title_partition-config": "Partition config", diff --git a/src/containers/Tenant/Diagnostics/Overview/TableInfo/prepareTableInfo/index.ts b/src/containers/Tenant/Diagnostics/Overview/TableInfo/prepareTableInfo/index.ts index d96deec9c2..bf637ed1dc 100644 --- a/src/containers/Tenant/Diagnostics/Overview/TableInfo/prepareTableInfo/index.ts +++ b/src/containers/Tenant/Diagnostics/Overview/TableInfo/prepareTableInfo/index.ts @@ -3,7 +3,6 @@ import {EPathType} from '../../../../../../types/api/schema'; import type {TEvDescribeSchemeResult} from '../../../../../../types/api/schema'; import type {ManagePartitioningFormState} from '../ManagePartitioningDialog/types'; -import {prepareColumnTableGeneralInfo} from './prepareColumnTableInfo'; import { prepareGeneralMetrics, prepareGeneralStats, @@ -36,7 +35,7 @@ export interface PreparedTableInfo { /** * Prepares all table information for display. - * Handles different table types: Table, ColumnTable, and ColumnStore. + * Prepares row-table-specific information and statistics shared by all table types. * @param data - Schema description result from YDB API * @param type - Path type (Table, ColumnTable, ColumnStore) * @returns Prepared table information organized by sections @@ -59,7 +58,6 @@ export function prepareTableInfo( TableStats = {}, TabletMetrics = {}, Table: {PartitionConfig = {}, TTLSettings} = {}, - ColumnTableDescription = {}, } = PathDescription; let generalInfoLeft: YDBDefinitionListItem[] = []; @@ -67,33 +65,21 @@ export function prepareTableInfo( let partitionProgressConfig: PartitionProgressConfig | undefined; let managePartitioningDialogConfig: ManagePartitioningFormState | undefined; - // Prepare type-specific information - switch (type) { - case EPathType.EPathTypeTable: { - partitionProgressConfig = preparePartitionProgressConfig( - PartitionConfig, - TablePartitions, - ); + if (type === EPathType.EPathTypeTable) { + partitionProgressConfig = preparePartitionProgressConfig(PartitionConfig, TablePartitions); - managePartitioningDialogConfig = prepareManagePartitioningDialogConfig( - PartitionConfig, - partitionProgressConfig, - ); + managePartitioningDialogConfig = prepareManagePartitioningDialogConfig( + PartitionConfig, + partitionProgressConfig, + ); - const {left, right} = prepareRowTableGeneralInfo( - PartitionConfig, - partitionProgressConfig, - TTLSettings, - ); - generalInfoLeft = left; - generalInfoRight = right; - - break; - } - case EPathType.EPathTypeColumnTable: { - generalInfoLeft = prepareColumnTableGeneralInfo(ColumnTableDescription); - break; - } + const {left, right} = prepareRowTableGeneralInfo( + PartitionConfig, + partitionProgressConfig, + TTLSettings, + ); + generalInfoLeft = left; + generalInfoRight = right; } // Prepare common information (shared across all table types) diff --git a/src/containers/Tenant/Diagnostics/Overview/TopicInfo/TopicInfo.tsx b/src/containers/Tenant/Diagnostics/Overview/TopicInfo/TopicInfo.tsx index b4af76fc53..ba0c952b84 100644 --- a/src/containers/Tenant/Diagnostics/Overview/TopicInfo/TopicInfo.tsx +++ b/src/containers/Tenant/Diagnostics/Overview/TopicInfo/TopicInfo.tsx @@ -1,7 +1,7 @@ import {InfoViewer} from '../../../../../components/InfoViewer'; import {EPathSubType} from '../../../../../types/api/schema'; import type {TEvDescribeSchemeResult} from '../../../../../types/api/schema'; -import {getEntityName} from '../../../utils'; +import tenantKeyset from '../../../i18n'; import {TopicStats} from '../TopicStats'; import {prepareTopicSchemaInfo} from '../utils'; @@ -14,10 +14,8 @@ interface TopicInfoProps { /** Displays overview for PersQueueGroup EPathType */ export const TopicInfo = ({data, path, database, databaseFullPath}: TopicInfoProps) => { - const entityName = getEntityName(data?.PathDescription); - if (!data) { - return
No {entityName} data
; + return null; } const renderStats = () => { @@ -31,7 +29,10 @@ export const TopicInfo = ({data, path, database, databaseFullPath}: TopicInfoPro return (
- + {renderStats()}
); diff --git a/src/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.scss b/src/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.scss index 5fe9b4a4e4..36242ba4ed 100644 --- a/src/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.scss +++ b/src/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.scss @@ -1,38 +1,39 @@ @use '../../../../../styles/mixins.scss'; .ydb-overview-topic-stats { + $column-width: 310px; + + margin-top: var(--g-spacing-5); + &__title { @include mixins.info-viewer-title(); } - .ydb-loader { - margin-top: 50px; - } - - .info-viewer__row { + &__content { + display: flex; + flex-wrap: wrap; align-items: flex-start; + gap: var(--g-spacing-9); } - .speed-multimeter { - margin-top: -5px; + &__info, + &__bytes-written { + width: $column-width; - &__content { - justify-content: flex-start; + .info-viewer__label { + min-width: 180px; } } - &__info { - .info-viewer__label-text_multiline { - max-width: 150px; - } + .ydb-loader { + margin-top: 50px; } - &__bytes-written { - margin-top: 7px; - padding-left: 20px; + .info-viewer__row { + align-items: flex-start; + } - .info-viewer__label { - min-width: 180px; - } + .info-viewer__label-text_multiline { + max-width: 150px; } } diff --git a/src/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.tsx b/src/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.tsx index bf3c8b824a..99aabd4093 100644 --- a/src/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.tsx +++ b/src/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.tsx @@ -1,12 +1,9 @@ -import React from 'react'; - import {ResponseError} from '../../../../../components/Errors/ResponseError'; import type {InfoViewerItem} from '../../../../../components/InfoViewer'; import {InfoViewer} from '../../../../../components/InfoViewer'; import {LabelWithPopover} from '../../../../../components/LabelWithPopover'; import {LagPopoverContent} from '../../../../../components/LagPopoverContent'; import {Loader} from '../../../../../components/Loader'; -import {SpeedMultiMeter} from '../../../../../components/SpeedMultiMeter'; import {useClusterWithProxy} from '../../../../../store/reducers/cluster/cluster'; import {selectPreparedTopicStats, topicApi} from '../../../../../store/reducers/topic'; import type {IPreparedTopicStats} from '../../../../../types/store/topic'; @@ -46,10 +43,6 @@ const prepareTopicInfo = (data: IPreparedTopicStats): Array => { ), value: formatDurationToShortTimeFormat(data.partitionsWriteLag), }, - { - label: 'Average write speed', - value: , - }, ]; }; @@ -105,17 +98,18 @@ export const TopicStats = ({path, database, databaseFullPath}: TopicStatsProps) return (
-
Stats
{errorContent} {data ? ( - +
+
{i18n('title_stats')}
+
{i18n('title_average-write-speed')}
- +
) : null}
); diff --git a/src/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/en.json b/src/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/en.json index 4facf85be7..0c7f4e8915 100644 --- a/src/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/en.json +++ b/src/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/en.json @@ -1,4 +1,6 @@ { + "title_stats": "Stats", + "title_average-write-speed": "Average write speed", "writeLagPopover": "Write lag, maximum among all topic partitions", "writeIdleTimePopover": "Write idle time, maximum among all topic partitions" } diff --git a/src/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/ru.json b/src/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/ru.json index eae8f6f4a2..cecc28eb7b 100644 --- a/src/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/ru.json +++ b/src/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/ru.json @@ -1,4 +1,6 @@ { + "title_stats": "Статистика", + "title_average-write-speed": "Средняя скорость записи", "writeLagPopover": "Лаг записи, максимальное значение среди всех партиций топика", "writeIdleTimePopover": "Время без записи, максимальное значение среди всех партиций топика" } diff --git a/src/containers/Tenant/Diagnostics/Overview/TransferInfo/TransferInfo.scss b/src/containers/Tenant/Diagnostics/Overview/TransferInfo/TransferInfo.scss new file mode 100644 index 0000000000..5eddd345ca --- /dev/null +++ b/src/containers/Tenant/Diagnostics/Overview/TransferInfo/TransferInfo.scss @@ -0,0 +1,5 @@ +.ydb-transfer-info { + &__loader { + margin-top: 50px; + } +} diff --git a/src/containers/Tenant/Diagnostics/Overview/TransferInfo/TransferInfo.tsx b/src/containers/Tenant/Diagnostics/Overview/TransferInfo/TransferInfo.tsx index 67209d3b3d..8c971e961b 100644 --- a/src/containers/Tenant/Diagnostics/Overview/TransferInfo/TransferInfo.tsx +++ b/src/containers/Tenant/Diagnostics/Overview/TransferInfo/TransferInfo.tsx @@ -1,17 +1,22 @@ -import {Flex, Label, Text} from '@gravity-ui/uikit'; +import React from 'react'; -import {YDBSyntaxHighlighter} from '../../../../../components/SyntaxHighlighter/YDBSyntaxHighlighter'; +import {Label, Text} from '@gravity-ui/uikit'; + +import {Loader} from '../../../../../components/Loader'; import type {YDBDefinitionListItem} from '../../../../../components/YDBDefinitionList/YDBDefinitionList'; import {YDBDefinitionList} from '../../../../../components/YDBDefinitionList/YDBDefinitionList'; +import {YQLCodePreview} from '../../../../../components/YQLCodePreview/YQLCodePreview'; import {useClusterWithProxy} from '../../../../../store/reducers/cluster/cluster'; import {replicationApi} from '../../../../../store/reducers/replication'; import type {DescribeReplicationResult} from '../../../../../types/api/replication'; import type {TEvDescribeSchemeResult} from '../../../../../types/api/schema'; -import {getEntityName} from '../../../utils'; +import {cn} from '../../../../../utils/cn'; import {Credentials} from './Credentials'; import i18n from './i18n'; +import './TransferInfo.scss'; + interface TransferProps { path: string; database: string; @@ -19,29 +24,35 @@ interface TransferProps { data?: TEvDescribeSchemeResult; } +const b = cn('ydb-transfer-info'); + /** Displays overview for Transfer EPathType */ export function TransferInfo({path, database, data, databaseFullPath}: TransferProps) { - const entityName = getEntityName(data?.PathDescription); const useMetaProxy = useClusterWithProxy(); if (!data) { - return ( -
- {i18n('noData')} {entityName} -
- ); + return null; } - const {data: replicationData} = replicationApi.useGetReplicationQuery( + const {data: replicationData, isFetching} = replicationApi.useGetReplicationQuery( {path, database, databaseFullPath, useMetaProxy}, {}, ); - const transferItems = prepareTransferItems(data, replicationData); + const loading = isFetching && replicationData === undefined; + + if (loading) { + return ; + } + + const {items, transformLambda} = prepareTransferItems(data, replicationData); return ( - - - + + + {transformLambda ? ( + + ) : null} + ); } @@ -134,13 +145,5 @@ function prepareTransferItems( content: {dstPath}, }); - info.push({ - name: i18n('transformLambda.label'), - copyText: transformLambda, - content: transformLambda ? ( - - ) : null, - }); - - return info; + return {items: info, transformLambda}; } diff --git a/src/containers/Tenant/Diagnostics/Overview/TransferInfo/i18n/en.json b/src/containers/Tenant/Diagnostics/Overview/TransferInfo/i18n/en.json index d53ccaf5bd..a3a8f8b3c9 100644 --- a/src/containers/Tenant/Diagnostics/Overview/TransferInfo/i18n/en.json +++ b/src/containers/Tenant/Diagnostics/Overview/TransferInfo/i18n/en.json @@ -1,6 +1,5 @@ { "credentials.label": "Credentials", - "noData": "No data for entity:", "srcConnection.database.label": "Source Database Path", "srcConnection.endpoint.label": "Source Cluster Endpoint", "state.label": "State", diff --git a/src/containers/Tenant/Info/ExternalDataSource/ExternalDataSource.tsx b/src/containers/Tenant/Info/ExternalDataSource/ExternalDataSource.tsx deleted file mode 100644 index 08f090d23f..0000000000 --- a/src/containers/Tenant/Info/ExternalDataSource/ExternalDataSource.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import type {YDBDefinitionListItem} from '../../../../components/YDBDefinitionList/YDBDefinitionList'; -import {YDBDefinitionList} from '../../../../components/YDBDefinitionList/YDBDefinitionList'; -import type {TEvDescribeSchemeResult} from '../../../../types/api/schema'; -import {getEntityName} from '../../utils'; -import i18n from '../i18n'; -import {prepareCreateTimeItem, renderNoEntityDataError} from '../utils'; - -function prepareExternalDataSourceSummary(data: TEvDescribeSchemeResult): YDBDefinitionListItem[] { - const info: YDBDefinitionListItem[] = [ - { - name: i18n('external-objects.source-type'), - content: data.PathDescription?.ExternalDataSourceDescription?.SourceType, - }, - ]; - - const createStep = data.PathDescription?.Self?.CreateStep; - - if (Number(createStep)) { - info.push(prepareCreateTimeItem(createStep)); - } - - return info; -} - -function getAuthMethodValue(data: TEvDescribeSchemeResult) { - const {Auth} = data.PathDescription?.ExternalDataSourceDescription || {}; - if (Auth?.ServiceAccount) { - return i18n('external-objects.auth-method.service-account'); - } - if (Auth?.Aws) { - return i18n('external-objects.auth-method.aws'); - } - if (Auth?.Token) { - return i18n('external-objects.auth-method.token'); - } - if (Auth?.Basic) { - return i18n('external-objects.auth-method.basic'); - } - if (Auth?.MdbBasic) { - return i18n('external-objects.auth-method.mdb-basic'); - } - return i18n('external-objects.auth-method.none'); -} - -function prepareExternalDataSourceInfo(data: TEvDescribeSchemeResult): YDBDefinitionListItem[] { - const {Location} = data.PathDescription?.ExternalDataSourceDescription || {}; - - return [ - ...prepareExternalDataSourceSummary(data), - { - name: i18n('external-objects.location'), - content: Location, - copyText: Location, - }, - { - name: i18n('external-objects.auth-method'), - content: getAuthMethodValue(data), - }, - ]; -} - -interface ExternalDataSourceProps { - data?: TEvDescribeSchemeResult; - prepareData: (data: TEvDescribeSchemeResult) => YDBDefinitionListItem[]; -} - -const ExternalDataSource = ({data, prepareData}: ExternalDataSourceProps) => { - const entityName = getEntityName(data?.PathDescription); - - if (!data) { - return renderNoEntityDataError(entityName); - } - return ; -}; - -export const ExternalDataSourceInfo = ({data}: {data?: TEvDescribeSchemeResult}) => { - return ; -}; - -export const ExternalDataSourceSummary = ({data}: {data?: TEvDescribeSchemeResult}) => { - return ; -}; diff --git a/src/containers/Tenant/Info/ExternalTable/ExternalTable.tsx b/src/containers/Tenant/Info/ExternalTable/ExternalTable.tsx deleted file mode 100644 index dd61fe91c9..0000000000 --- a/src/containers/Tenant/Info/ExternalTable/ExternalTable.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import {useLocation} from 'react-router-dom'; - -import {LinkWithIcon} from '../../../../components/LinkWithIcon/LinkWithIcon'; -import type {YDBDefinitionListItem} from '../../../../components/YDBDefinitionList/YDBDefinitionList'; -import {YDBDefinitionList} from '../../../../components/YDBDefinitionList/YDBDefinitionList'; -import {createExternalUILink, parseQuery} from '../../../../routes'; -import type {TEvDescribeSchemeResult} from '../../../../types/api/schema'; -import {getEntityName} from '../../utils'; -import i18n from '../i18n'; -import {prepareCreateTimeItem, renderNoEntityDataError} from '../utils'; - -const prepareExternalTableSummary = (data: TEvDescribeSchemeResult, pathToDataSource: string) => { - const {CreateStep} = data.PathDescription?.Self || {}; - const {SourceType, DataSourcePath} = data.PathDescription?.ExternalTableDescription || {}; - - const dataSourceName = DataSourcePath?.split('/').pop(); - - const info: YDBDefinitionListItem[] = [ - {name: i18n('external-objects.source-type'), content: SourceType}, - ]; - - if (Number(CreateStep)) { - info.push(prepareCreateTimeItem(CreateStep)); - } - - info.push({ - name: i18n('external-objects.data-source'), - content: DataSourcePath && ( - - - - ), - }); - - return info; -}; - -const prepareExternalTableInfo = ( - data: TEvDescribeSchemeResult, - pathToDataSource: string, -): YDBDefinitionListItem[] => { - const location = data.PathDescription?.ExternalTableDescription?.Location; - - return [ - ...prepareExternalTableSummary(data, pathToDataSource), - { - name: i18n('external-objects.location'), - content: location, - copyText: location, - }, - ]; -}; - -interface ExternalTableProps { - data?: TEvDescribeSchemeResult; - prepareData: ( - data: TEvDescribeSchemeResult, - pathToDataSource: string, - ) => YDBDefinitionListItem[]; -} - -const ExternalTable = ({data, prepareData}: ExternalTableProps) => { - const location = useLocation(); - const query = parseQuery(location); - - const pathToDataSource = createExternalUILink({ - ...query, - schema: data?.PathDescription?.ExternalTableDescription?.DataSourcePath, - }); - - const entityName = getEntityName(data?.PathDescription); - - if (!data) { - return renderNoEntityDataError(entityName); - } - - return ; -}; - -export const ExternalTableInfo = ({data}: {data?: TEvDescribeSchemeResult}) => { - return ; -}; - -export const ExternalTableSummary = ({data}: {data?: TEvDescribeSchemeResult}) => { - return ; -}; diff --git a/src/containers/Tenant/Info/SystemView/SystemView.tsx b/src/containers/Tenant/Info/SystemView/SystemView.tsx deleted file mode 100644 index 09cb8794a9..0000000000 --- a/src/containers/Tenant/Info/SystemView/SystemView.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import type {YDBDefinitionListItem} from '../../../../components/YDBDefinitionList/YDBDefinitionList'; -import {YDBDefinitionList} from '../../../../components/YDBDefinitionList/YDBDefinitionList'; -import type {TEvDescribeSchemeResult} from '../../../../types/api/schema'; -import {prepareSystemViewType} from '../../../../utils/schema'; -import {getEntityName} from '../../utils'; -import i18n from '../i18n'; -import {renderNoEntityDataError} from '../utils'; - -const prepareSystemViewItems = (data: TEvDescribeSchemeResult): YDBDefinitionListItem[] => { - const systemViewType = data.PathDescription?.SysViewDescription?.Type; - - return [ - { - name: i18n('field_system-view-type'), - content: prepareSystemViewType(systemViewType), - }, - ]; -}; - -interface SystemViewInfoProps { - data?: TEvDescribeSchemeResult; -} - -export function SystemViewInfo({data}: SystemViewInfoProps) { - const entityName = getEntityName(data?.PathDescription); - - if (!data) { - return renderNoEntityDataError(entityName); - } - - const items = prepareSystemViewItems(data); - - return ; -} diff --git a/src/containers/Tenant/Info/View/View.tsx b/src/containers/Tenant/Info/View/View.tsx index 6386d6d691..4d53db24df 100644 --- a/src/containers/Tenant/Info/View/View.tsx +++ b/src/containers/Tenant/Info/View/View.tsx @@ -1,35 +1,23 @@ -import {YDBSyntaxHighlighter} from '../../../../components/SyntaxHighlighter/YDBSyntaxHighlighter'; -import type {YDBDefinitionListItem} from '../../../../components/YDBDefinitionList/YDBDefinitionList'; -import {YDBDefinitionList} from '../../../../components/YDBDefinitionList/YDBDefinitionList'; +import {YQLCodePreview} from '../../../../components/YQLCodePreview/YQLCodePreview'; import type {TEvDescribeSchemeResult} from '../../../../types/api/schema'; -import {getEntityName} from '../../utils'; -import i18n from '../i18n'; -import {renderNoEntityDataError} from '../utils'; - -const prepareViewItems = (data: TEvDescribeSchemeResult): YDBDefinitionListItem[] => { - const queryText = data.PathDescription?.ViewDescription?.QueryText; - - return [ - { - name: i18n('view.query-text'), - copyText: queryText, - content: queryText ? : null, - }, - ]; -}; +import {EMPTY_DATA_PLACEHOLDER} from '../../../../utils/constants'; +import tenantKeyset from '../../i18n'; interface ViewInfoProps { data?: TEvDescribeSchemeResult; } export function ViewInfo({data}: ViewInfoProps) { - const entityName = getEntityName(data?.PathDescription); - if (!data) { - return renderNoEntityDataError(entityName); + return null; } - const items = prepareViewItems(data); + const queryText = data.PathDescription?.ViewDescription?.QueryText; - return ; + return ( + + ); } diff --git a/src/containers/Tenant/Info/i18n/en.json b/src/containers/Tenant/Info/i18n/en.json deleted file mode 100644 index 13d57e9baf..0000000000 --- a/src/containers/Tenant/Info/i18n/en.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "created": "Created", - - "external-objects.source-type": "Source Type", - "external-objects.data-source": "Data Source", - "external-objects.location": "Location", - "external-objects.auth-method": "Auth Method", - "external-objects.auth-method.none": "None", - "external-objects.auth-method.service-account": "Service Account", - "external-objects.auth-method.aws": "AWS", - "external-objects.auth-method.token": "Token", - "external-objects.auth-method.basic": "Basic", - "external-objects.auth-method.mdb-basic": "MDB Basic", - - "view.query-text": "Query Text", - - "field_system-view-type": "System view type", - - "no-entity-data": "No {{entityName}} data" -} diff --git a/src/containers/Tenant/Info/i18n/index.ts b/src/containers/Tenant/Info/i18n/index.ts deleted file mode 100644 index 7f9d3e3e58..0000000000 --- a/src/containers/Tenant/Info/i18n/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {registerKeysets} from '../../../../utils/i18n'; - -import en from './en.json'; - -const COMPONENT = 'ydb-tenant-objects-info'; - -export default registerKeysets(COMPONENT, {en}); diff --git a/src/containers/Tenant/Info/utils.tsx b/src/containers/Tenant/Info/utils.tsx deleted file mode 100644 index 3ea6ff337f..0000000000 --- a/src/containers/Tenant/Info/utils.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import {Text} from '@gravity-ui/uikit'; - -import type {YDBDefinitionListItem} from '../../../components/YDBDefinitionList/YDBDefinitionList'; -import {EMPTY_DATA_PLACEHOLDER} from '../../../utils/constants'; -import {formatDateTime} from '../../../utils/dataFormatters/dataFormatters'; - -import i18n from './i18n'; - -export function prepareCreateTimeItem(createStep?: string | number): YDBDefinitionListItem { - return { - name: i18n('created'), - content: formatDateTime(createStep, {defaultValue: EMPTY_DATA_PLACEHOLDER}), - }; -} - -export function renderNoEntityDataError(entityName: string | undefined) { - return ( - - {i18n('no-entity-data', {entityName: entityName ?? ''})} - - ); -} diff --git a/src/containers/Tenant/ObjectSummary/ObjectSummary.scss b/src/containers/Tenant/ObjectSummary/ObjectSummary.scss index 4b75e9a676..a5d47cd9ac 100644 --- a/src/containers/Tenant/ObjectSummary/ObjectSummary.scss +++ b/src/containers/Tenant/ObjectSummary/ObjectSummary.scss @@ -45,6 +45,21 @@ flex-direction: column; } + &__tree-only { + display: flex; + overflow: hidden; + flex-grow: 1; + flex-direction: column; + + min-height: 0; + + > * { + flex: 1 1 auto; + + min-height: 0; + } + } + &__tree { overflow-y: scroll; flex: 1 1 auto; diff --git a/src/containers/Tenant/ObjectSummary/ObjectSummary.tsx b/src/containers/Tenant/ObjectSummary/ObjectSummary.tsx index 2ff5c33147..a1cbbb2cc4 100644 --- a/src/containers/Tenant/ObjectSummary/ObjectSummary.tsx +++ b/src/containers/Tenant/ObjectSummary/ObjectSummary.tsx @@ -42,6 +42,7 @@ import {useCurrentSchema} from '../TenantContext'; import {useTenantPage} from '../TenantNavigation/useTenantNavigation'; import {TENANT_INFO_TABS, TENANT_SCHEMA_TAB, TenantTabsGroups} from '../TenantPages'; import {ROW_COUNT_NOTE} from '../constants'; +import tenantKeyset from '../i18n'; import {useTenantQueryParams} from '../useTenantQueryParams'; import {getSummaryControls} from '../utils/controls'; import { @@ -57,7 +58,7 @@ import {SchemaActions} from './SchemaActions'; import {RefreshTreeButton} from './SchemaTree/RefreshTreeButton'; import i18n from './i18n'; import {b} from './shared'; -import {isDomain, transformPath} from './transformPath'; +import {getPathTypeName, isDomain, transformPath} from './transformPath'; import './ObjectSummary.scss'; @@ -83,6 +84,7 @@ export function ObjectSummary({ false, ); const isV2Navigation = useNavigationV2Enabled(); + const showLegacyDatabaseInfo = !isV2Navigation && path === databaseFullPath; const [commonInfoVisibilityState, dispatchCommonInfoVisibilityState] = React.useReducer( paneVisibilityToggleReducer, @@ -168,26 +170,24 @@ export function ObjectSummary({ note?: DefinitionListItemProps['note']; }[] = []; - const normalizedType = isDomain(path, PathType) - ? 'Domain' - : PathType?.replace(/^EPathType/, ''); + const normalizedType = getPathTypeName(path, PathType); - overview.push({name: i18n('field_type'), content: normalizedType}); + overview.push({name: tenantKeyset('summary.type'), content: normalizedType}); if (PathSubType !== EPathSubType.EPathSubTypeEmpty) { overview.push({ - name: i18n('field_subtype'), + name: tenantKeyset('summary.subtype'), content: PathSubType?.replace(/^EPathSubType/, ''), }); } - overview.push({name: i18n('field_id'), content: PathId}); + overview.push({name: tenantKeyset('summary.id'), content: PathId}); - overview.push({name: i18n('field_version'), content: PathVersion}); + overview.push({name: tenantKeyset('summary.version'), content: PathVersion}); if (Number(CreateStep)) { overview.push({ - name: i18n('field_created'), + name: tenantKeyset('summary.created'), content: formatDateTime(CreateStep), }); } @@ -199,11 +199,11 @@ export function ObjectSummary({ overview.push( { - name: i18n('field_data-size'), + name: tenantKeyset('summary.data-size'), content: toFormattedSize(DataSize), }, { - name: i18n('field_row-count'), + name: tenantKeyset('summary.row-count'), content: formatNumber(RowCount), note: ROW_COUNT_NOTE, }, @@ -226,11 +226,11 @@ export function ObjectSummary({ return [ { - name: i18n('field_paths'), + name: tenantKeyset('summary.paths'), content: paths, }, { - name: i18n('field_shards'), + name: tenantKeyset('summary.shards'), content: shards, }, ]; @@ -251,13 +251,13 @@ export function ObjectSummary({ [EPathType.EPathTypeSecret]: undefined, [EPathType.EPathTypeTable]: () => [ { - name: i18n('field_partitions'), + name: tenantKeyset('summary.partitions'), content: PathDescription?.TablePartitions?.length, }, ], [EPathType.EPathTypeSysView]: () => [ { - name: i18n('field_system-view-type'), + name: tenantKeyset('summary.system-view-type'), content: prepareSystemViewType(PathDescription?.SysViewDescription?.Type), }, ], @@ -266,13 +266,13 @@ export function ObjectSummary({ [EPathType.EPathTypeExtSubDomain]: isV2Navigation ? undefined : getDatabaseOverview, [EPathType.EPathTypeColumnStore]: () => [ { - name: i18n('field_partitions'), + name: tenantKeyset('summary.partitions'), content: PathDescription?.ColumnStoreDescription?.ColumnShards?.length, }, ], [EPathType.EPathTypeColumnTable]: () => [ { - name: i18n('field_partitions'), + name: tenantKeyset('summary.partitions'), content: PathDescription?.ColumnTableDescription?.Sharding?.ColumnShards?.length, }, @@ -282,11 +282,11 @@ export function ObjectSummary({ return [ { - name: i18n('field_mode'), + name: tenantKeyset('summary.mode'), content: Mode?.replace(/^ECdcStreamMode/, ''), }, { - name: i18n('field_format'), + name: tenantKeyset('summary.format'), content: Format?.replace(/^ECdcStreamFormat/, ''), }, ]; @@ -297,11 +297,11 @@ export function ObjectSummary({ return [ { - name: i18n('field_partitions'), + name: tenantKeyset('summary.partitions'), content: pqGroup?.Partitions?.length, }, { - name: i18n('field_retention'), + name: tenantKeyset('summary.retention'), content: value && formatSecondsToHours(value), }, ]; @@ -318,9 +318,9 @@ export function ObjectSummary({ const dataSourceName = DataSourcePath?.match(/([^/]*)\/*$/)?.[1] || ''; return [ - {name: i18n('field_source-type'), content: SourceType}, + {name: tenantKeyset('summary.source-type'), content: SourceType}, { - name: i18n('field_data-source'), + name: tenantKeyset('summary.data-source'), content: DataSourcePath && ( @@ -331,7 +331,7 @@ export function ObjectSummary({ }, [EPathType.EPathTypeExternalDataSource]: () => [ { - name: i18n('field_source-type'), + name: tenantKeyset('summary.source-type'), content: PathDescription?.ExternalDataSourceDescription?.SourceType, }, ], @@ -345,7 +345,7 @@ export function ObjectSummary({ return [ { - name: i18n('field_state'), + name: tenantKeyset('summary.state'), content: , }, ]; @@ -359,7 +359,7 @@ export function ObjectSummary({ return [ { - name: i18n('field_state'), + name: tenantKeyset('summary.state'), content: , }, ]; @@ -478,36 +478,46 @@ export function ObjectSummary({ return (
- - -
-
-
-
- {renderEntityTypeBadge()} -
{relativePath}
-
-
- {renderCommonInfoControls()} + {showLegacyDatabaseInfo ? ( + + +
+
+
+
+ {renderEntityTypeBadge()} +
{relativePath}
+
+
+ {renderCommonInfoControls()} +
+ {renderTabs()}
- {renderTabs()} +
{renderTabContent()}
-
{renderTabContent()}
+
+ ) : ( +
+
- + )}
{!isCollapsed && } diff --git a/src/containers/Tenant/ObjectSummary/i18n/en.json b/src/containers/Tenant/ObjectSummary/i18n/en.json index 89cd116f5b..c7acf131bb 100644 --- a/src/containers/Tenant/ObjectSummary/i18n/en.json +++ b/src/containers/Tenant/ObjectSummary/i18n/en.json @@ -1,22 +1,5 @@ { "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_system-view-type": "System view type", - "field_paths": "Paths", - "field_shards": "Shards", - "field_state": "State", - "field_mode": "Mode", - "field_format": "Format", - "field_retention": "Retention" + "action_openInDiagnostics": "Open in Diagnostics" } diff --git a/src/containers/Tenant/ObjectSummary/transformPath.ts b/src/containers/Tenant/ObjectSummary/transformPath.ts index 1a0879f81a..517bf80fed 100644 --- a/src/containers/Tenant/ObjectSummary/transformPath.ts +++ b/src/containers/Tenant/ObjectSummary/transformPath.ts @@ -26,3 +26,7 @@ export function isDomain(path: string, type?: EPathType) { } return path.split('/').length === 2 && path.startsWith('/'); } + +export function getPathTypeName(path: string, type?: EPathType) { + return isDomain(path, type) ? 'Domain' : type?.replace(/^EPathType/, ''); +} diff --git a/src/containers/Tenant/i18n/en.json b/src/containers/Tenant/i18n/en.json index ce284e261a..157e05c72d 100644 --- a/src/containers/Tenant/i18n/en.json +++ b/src/containers/Tenant/i18n/en.json @@ -11,18 +11,21 @@ "summary.copySchemaPath": "Copy schema path", "summary.type": "Type", "summary.subtype": "SubType", - "summary.id": "Id", + "summary.id": "ID", "summary.version": "Version", "summary.created": "Created", "summary.data-size": "Data size", "summary.row-count": "Row count", "summary.partitions": "Partitions count", + "summary.partitioning": "Partitioning", + "summary.system-view-type": "System view type", "summary.paths": "Paths", "summary.shards": "Shards", "summary.state": "State", "summary.mode": "Mode", "summary.format": "Format", "summary.retention": "Retention", + "title_query-text": "Query text", "label.read-only": "ReadOnly", "actions.copied": "The path is copied to the clipboard", "actions.notCopied": "Couldn’t copy the path", diff --git a/src/containers/Tenant/utils/schema.ts b/src/containers/Tenant/utils/schema.ts index fe7a69f390..004b191808 100644 --- a/src/containers/Tenant/utils/schema.ts +++ b/src/containers/Tenant/utils/schema.ts @@ -13,6 +13,7 @@ const pathSubTypeToNodeType: Record = { 'entity-name_vector-index-table', ), [EPathSubType.EPathSubTypeFulltextIndexImplTable]: i18n('entity-name_fulltext-index-table'), + [EPathSubType.EPathSubTypeLocalMinMaxIndex]: undefined, [EPathSubType.EPathSubTypeStreamImpl]: undefined, [EPathSubType.EPathSubTypeEmpty]: undefined, @@ -133,8 +135,14 @@ const indexTypeToEntityName: Record = { [EIndexType.EIndexTypeInvalid]: undefined, [EIndexType.EIndexTypeGlobal]: i18n('entity-name_secondary-index'), [EIndexType.EIndexTypeGlobalAsync]: i18n('entity-name_secondary-index'), + [EIndexType.EIndexTypeGlobalUnique]: i18n('entity-name_secondary-index'), [EIndexType.EIndexTypeGlobalVectorKmeansTree]: i18n('entity-name_vector-index'), [EIndexType.EIndexTypeGlobalFulltext]: i18n('entity-name_fulltext-index'), + [EIndexType.EIndexTypeGlobalFulltextPlain]: i18n('entity-name_fulltext-index'), + [EIndexType.EIndexTypeGlobalFulltextRelevance]: i18n('entity-name_fulltext-index'), + [EIndexType.EIndexTypeLocalBloomFilter]: i18n('entity-name_secondary-index'), + [EIndexType.EIndexTypeLocalBloomNgramFilter]: i18n('entity-name_secondary-index'), + [EIndexType.EIndexTypeLocalMinMax]: i18n('entity-name_secondary-index'), }; export const mapIndexTypeToEntityName = (type?: EIndexType) => type && indexTypeToEntityName[type]; @@ -184,21 +192,6 @@ export const isTableType = (pathType?: EPathType) => // ==================== -const pathSubTypeToIsIndexImpl: Record = { - [EPathSubType.EPathSubTypeSyncIndexImplTable]: true, - [EPathSubType.EPathSubTypeAsyncIndexImplTable]: true, - [EPathSubType.EPathSubTypeVectorKmeansTreeIndexImplTable]: true, - [EPathSubType.EPathSubTypeFulltextIndexImplTable]: true, - - [EPathSubType.EPathSubTypeStreamImpl]: false, - [EPathSubType.EPathSubTypeEmpty]: false, -}; - -export const isIndexTableType = (subType?: EPathSubType) => - (subType && pathSubTypeToIsIndexImpl[subType]) ?? false; - -// ==================== - const pathTypeToIsColumn: Record = { [EPathType.EPathTypeColumnStore]: true, [EPathType.EPathTypeColumnTable]: true, @@ -277,6 +270,7 @@ const pathSubTypeToChildless: Record = { [EPathSubType.EPathSubTypeVectorKmeansTreeIndexImplTable]: true, [EPathSubType.EPathSubTypeFulltextIndexImplTable]: true, [EPathSubType.EPathSubTypeStreamImpl]: true, + [EPathSubType.EPathSubTypeLocalMinMaxIndex]: true, [EPathSubType.EPathSubTypeEmpty]: false, }; diff --git a/src/styles/mixins.scss b/src/styles/mixins.scss index ca0685da73..ef8f072984 100644 --- a/src/styles/mixins.scss +++ b/src/styles/mixins.scss @@ -99,7 +99,7 @@ } @mixin info-viewer-title { - margin: 15px 0 10px; + margin: var(--ydb-info-viewer-title-margin, 15px 0 10px); font-weight: 600; @include body-2-typography(); diff --git a/src/types/api/schema/schema.ts b/src/types/api/schema/schema.ts index 9ae1e04064..4b21a610ef 100644 --- a/src/types/api/schema/schema.ts +++ b/src/types/api/schema/schema.ts @@ -313,6 +313,7 @@ export enum EPathSubType { EPathSubTypeStreamImpl = 'EPathSubTypeStreamImpl', EPathSubTypeVectorKmeansTreeIndexImplTable = 'EPathSubTypeVectorKmeansTreeIndexImplTable', EPathSubTypeFulltextIndexImplTable = 'EPathSubTypeFulltextIndexImplTable', + EPathSubTypeLocalMinMaxIndex = 'EPathSubTypeLocalMinMaxIndex', } enum EPathState { diff --git a/src/types/api/schema/tableIndex.ts b/src/types/api/schema/tableIndex.ts index 35ac78acea..459316516a 100644 --- a/src/types/api/schema/tableIndex.ts +++ b/src/types/api/schema/tableIndex.ts @@ -35,8 +35,14 @@ export enum EIndexType { EIndexTypeInvalid = 'EIndexTypeInvalid', EIndexTypeGlobal = 'EIndexTypeGlobal', EIndexTypeGlobalAsync = 'EIndexTypeGlobalAsync', + EIndexTypeGlobalUnique = 'EIndexTypeGlobalUnique', EIndexTypeGlobalVectorKmeansTree = 'EIndexTypeGlobalVectorKmeansTree', EIndexTypeGlobalFulltext = 'EIndexTypeGlobalFulltext', + EIndexTypeGlobalFulltextPlain = 'EIndexTypeGlobalFulltextPlain', + EIndexTypeGlobalFulltextRelevance = 'EIndexTypeGlobalFulltextRelevance', + EIndexTypeLocalBloomFilter = 'EIndexTypeLocalBloomFilter', + EIndexTypeLocalBloomNgramFilter = 'EIndexTypeLocalBloomNgramFilter', + EIndexTypeLocalMinMax = 'EIndexTypeLocalMinMax', } export enum EIndexState { diff --git a/tests/suites/tenant/diagnostics/tabs/info.test.ts b/tests/suites/tenant/diagnostics/tabs/info.test.ts index e3a5b47c16..fb37b390fb 100644 --- a/tests/suites/tenant/diagnostics/tabs/info.test.ts +++ b/tests/suites/tenant/diagnostics/tabs/info.test.ts @@ -12,7 +12,7 @@ async function expectMetricTabsScreenshot(metricTabs: Locator, name: string) { await expect(metricTabs).toHaveScreenshot(name); } -async function setupMonitoringUserMock(page: Page) { +async function setupMonitoringUserMock(page: Page, isAdministrationAllowed = true) { await page.route('**/viewer/json/whoami?*', async (route) => { await route.fulfill({ status: 200, @@ -23,7 +23,7 @@ async function setupMonitoringUserMock(page: Page) { AuthType: 'Login', IsViewerAllowed: true, IsMonitoringAllowed: true, - IsAdministrationAllowed: true, + IsAdministrationAllowed: isAdministrationAllowed, }), }); }); @@ -321,6 +321,7 @@ test.describe('Diagnostics Info tab', async () => { test('Info tab displays overlap_clusters for vector index', async ({page}) => { const mockIndexPath = '/local/test_table/my_vector_index'; + await setupMonitoringUserMock(page, false); // Mock describe API to return a vector index with overlap_clusters await page.route(`**/viewer/json/describe?*`, async (route) => { @@ -338,6 +339,9 @@ test.describe('Diagnostics Info tab', async () => { Self: { Name: 'my_vector_index', PathType: 'EPathTypeTableIndex', + PathId: '42', + PathVersion: '7', + CreateStep: '1710000000000', }, TableIndex: { Name: 'my_vector_index', @@ -375,8 +379,10 @@ test.describe('Diagnostics Info tab', async () => { await tenantPage.goto(pageQueryParams); // Verify vector index settings are displayed including overlap_clusters - const infoContent = page.locator('.ydb-diagnostics-table-info'); + const infoContent = page.locator('.kv-detailed-overview'); await infoContent.waitFor({state: 'visible', timeout: 10000}); + await expect(infoContent.getByText('42', {exact: true})).toBeVisible(); + await expect(infoContent.getByText('7', {exact: true})).toBeVisible(); // Check Index Settings section contains Overlap Clusters const indexSettings = infoContent.locator('.info-viewer'); @@ -411,6 +417,9 @@ test.describe('Diagnostics Info tab', async () => { Self: { Name: 'my_fulltext_index', PathType: 'EPathTypeTableIndex', + PathId: '42', + PathVersion: '7', + CreateStep: '1710000000000', }, TableIndex: { Name: 'my_fulltext_index', @@ -453,7 +462,7 @@ test.describe('Diagnostics Info tab', async () => { await tenantPage.goto(pageQueryParams); // Verify fulltext index settings are displayed - const infoContent = page.locator('.ydb-diagnostics-table-info'); + const infoContent = page.locator('.kv-detailed-overview'); await infoContent.waitFor({state: 'visible', timeout: 10000}); // Check Index Settings section contains fulltext-specific fields @@ -467,4 +476,140 @@ test.describe('Diagnostics Info tab', async () => { // Visual snapshot of fulltext index info with all settings await expect(infoContent).toHaveScreenshot('fulltext-index-info-settings.png'); }); + + test('Streaming Query Info remains available when describe fails', async ({page}) => { + test.slow(); + + const mockStreamingQueryPath = '/local/test_streaming_query'; + + await page.route('**/viewer/json/describe?*', async (route) => { + const url = new URL(route.request().url()); + const path = url.searchParams.get('path'); + const subs = url.searchParams.get('subs'); + + if (path === mockStreamingQueryPath && subs === '0') { + await route.fulfill({ + status: 500, + contentType: 'application/json', + json: {error: 'Streaming Query describe is not supported'}, + }); + return; + } + + if (path === database && subs === '1') { + await route.fulfill({ + json: { + Path: database, + PathDescription: { + Self: { + Name: 'local', + PathType: 'EPathTypeSubDomain', + }, + Children: [ + { + Name: 'test_streaming_query', + PathType: 'EPathTypeStreamingQuery', + }, + ], + }, + }, + }); + return; + } + + await route.continue(); + }); + + await page.route('**/viewer/json/query?*', async (route) => { + await route.fulfill({ + json: { + version: 8, + result: [ + { + rows: [['RUNNING', '{}', 'SELECT 1;']], + columns: [ + {name: 'State', type: 'Utf8?'}, + {name: 'Error', type: 'Utf8?'}, + {name: 'Text', type: 'Utf8?'}, + ], + }, + ], + }, + }); + }); + + const tenantPage = new TenantPage(page); + await tenantPage.goto({ + schema: mockStreamingQueryPath, + database, + databasePage: 'diagnostics', + diagnosticsTab: 'overview', + }); + + const infoContent = page.locator('.kv-detailed-overview'); + await expect(infoContent.getByText('RUNNING')).toBeVisible(); + await expect(infoContent.locator('.ydb-yql-code-preview .shiki')).toBeVisible(); + const infoContentBox = await infoContent.boundingBox(); + if (!infoContentBox) { + throw new Error('Cannot take Streaming Query Info screenshot'); + } + await expect(page).toHaveScreenshot('streaming-query-info-describe-fallback.png', { + clip: infoContentBox, + }); + }); + + test('View Info includes YQL code preview', async ({page}) => { + const mockViewPath = '/local/test_view'; + + await setupMonitoringUserMock(page, false); + await page.route('**/viewer/json/describe?*', async (route) => { + const url = new URL(route.request().url()); + + if (url.searchParams.get('path') !== mockViewPath) { + await route.continue(); + return; + } + + await route.fulfill({ + json: { + Status: 'StatusSuccess', + Path: mockViewPath, + PathDescription: { + Self: { + Name: 'test_view', + PathType: 'EPathTypeView', + PathId: '42', + PathVersion: '7', + CreateStep: '1710000000000', + }, + ViewDescription: { + QueryText: [ + 'SELECT', + ' series_id,', + ' title,', + ' release_date', + 'FROM series', + 'WHERE release_date >= Date("2020-01-01");', + ].join('\n'), + }, + }, + }, + }); + }); + + const tenantPage = new TenantPage(page); + await tenantPage.goto({ + schema: mockViewPath, + database, + databasePage: 'diagnostics', + diagnosticsTab: 'overview', + }); + + const infoContent = page.locator('.kv-detailed-overview'); + const yqlCodePreview = infoContent.locator('.ydb-yql-code-preview'); + + await expect(yqlCodePreview).toBeVisible(); + await expect(yqlCodePreview.locator('.shiki')).toBeVisible(); + await expect(infoContent).toHaveScreenshot('view-info-yql-code-preview.png'); + }); }); diff --git a/tests/suites/tenant/diagnostics/tabs/info.test.ts-snapshots/fulltext-index-info-settings-chromium-linux.png b/tests/suites/tenant/diagnostics/tabs/info.test.ts-snapshots/fulltext-index-info-settings-chromium-linux.png index 82736ce7b7..261fc0d99e 100644 Binary files a/tests/suites/tenant/diagnostics/tabs/info.test.ts-snapshots/fulltext-index-info-settings-chromium-linux.png and b/tests/suites/tenant/diagnostics/tabs/info.test.ts-snapshots/fulltext-index-info-settings-chromium-linux.png differ diff --git a/tests/suites/tenant/diagnostics/tabs/info.test.ts-snapshots/fulltext-index-info-settings-safari-linux.png b/tests/suites/tenant/diagnostics/tabs/info.test.ts-snapshots/fulltext-index-info-settings-safari-linux.png index 5f616ba0c2..6fe36be0dc 100644 Binary files a/tests/suites/tenant/diagnostics/tabs/info.test.ts-snapshots/fulltext-index-info-settings-safari-linux.png and b/tests/suites/tenant/diagnostics/tabs/info.test.ts-snapshots/fulltext-index-info-settings-safari-linux.png differ diff --git a/tests/suites/tenant/diagnostics/tabs/info.test.ts-snapshots/streaming-query-info-describe-fallback-chromium-linux.png b/tests/suites/tenant/diagnostics/tabs/info.test.ts-snapshots/streaming-query-info-describe-fallback-chromium-linux.png new file mode 100644 index 0000000000..ff0b5a668e Binary files /dev/null and b/tests/suites/tenant/diagnostics/tabs/info.test.ts-snapshots/streaming-query-info-describe-fallback-chromium-linux.png differ diff --git a/tests/suites/tenant/diagnostics/tabs/info.test.ts-snapshots/streaming-query-info-describe-fallback-safari-linux.png b/tests/suites/tenant/diagnostics/tabs/info.test.ts-snapshots/streaming-query-info-describe-fallback-safari-linux.png new file mode 100644 index 0000000000..b30ab0086b Binary files /dev/null and b/tests/suites/tenant/diagnostics/tabs/info.test.ts-snapshots/streaming-query-info-describe-fallback-safari-linux.png differ diff --git a/tests/suites/tenant/diagnostics/tabs/info.test.ts-snapshots/vector-index-info-overlap-clusters-chromium-linux.png b/tests/suites/tenant/diagnostics/tabs/info.test.ts-snapshots/vector-index-info-overlap-clusters-chromium-linux.png index ea2ca0adcc..f26dc2369e 100644 Binary files a/tests/suites/tenant/diagnostics/tabs/info.test.ts-snapshots/vector-index-info-overlap-clusters-chromium-linux.png and b/tests/suites/tenant/diagnostics/tabs/info.test.ts-snapshots/vector-index-info-overlap-clusters-chromium-linux.png differ diff --git a/tests/suites/tenant/diagnostics/tabs/info.test.ts-snapshots/vector-index-info-overlap-clusters-safari-linux.png b/tests/suites/tenant/diagnostics/tabs/info.test.ts-snapshots/vector-index-info-overlap-clusters-safari-linux.png index 76ff00875f..142443d6e6 100644 Binary files a/tests/suites/tenant/diagnostics/tabs/info.test.ts-snapshots/vector-index-info-overlap-clusters-safari-linux.png and b/tests/suites/tenant/diagnostics/tabs/info.test.ts-snapshots/vector-index-info-overlap-clusters-safari-linux.png differ diff --git a/tests/suites/tenant/diagnostics/tabs/info.test.ts-snapshots/view-info-yql-code-preview-chromium-linux.png b/tests/suites/tenant/diagnostics/tabs/info.test.ts-snapshots/view-info-yql-code-preview-chromium-linux.png new file mode 100644 index 0000000000..940fdc5cd9 Binary files /dev/null and b/tests/suites/tenant/diagnostics/tabs/info.test.ts-snapshots/view-info-yql-code-preview-chromium-linux.png differ diff --git a/tests/suites/tenant/diagnostics/tabs/info.test.ts-snapshots/view-info-yql-code-preview-safari-linux.png b/tests/suites/tenant/diagnostics/tabs/info.test.ts-snapshots/view-info-yql-code-preview-safari-linux.png new file mode 100644 index 0000000000..31f8d748db Binary files /dev/null and b/tests/suites/tenant/diagnostics/tabs/info.test.ts-snapshots/view-info-yql-code-preview-safari-linux.png differ diff --git a/tests/suites/tenant/summary/objectSummary.test.ts b/tests/suites/tenant/summary/objectSummary.test.ts index 9fa3424f40..cbf83d7f98 100644 --- a/tests/suites/tenant/summary/objectSummary.test.ts +++ b/tests/suites/tenant/summary/objectSummary.test.ts @@ -13,7 +13,7 @@ import {setupMonitoringGenericErrorMock} from '../../errorDisplay/errorDisplayMo import {TenantPage} from '../TenantPage'; import {QueryEditor} from '../queryEditor/models/QueryEditor'; -import {ObjectSummary, ObjectSummaryTab} from './ObjectSummary'; +import {ObjectSummary} from './ObjectSummary'; import {RowTableAction} from './types'; test.describe('Object Summary', async () => { @@ -27,6 +27,25 @@ test.describe('Object Summary', async () => { await tenantPage.goto(pageQueryParams, {waitUntil: 'domcontentloaded'}); }); + test('Navigation v2 renders only the tree in Object Summary', async ({page}) => { + const objectSummary = new ObjectSummary(page); + const summaryActions = page.locator('.ydb-object-summary__actions'); + const infoControls = page.locator('.ydb-object-summary__info-controls'); + + await expect(objectSummary.isTreeVisible()).resolves.toBe(true); + await expect(page.locator('.ydb-object-summary__info')).toHaveCount(0); + await expect(page.locator('.ydb-object-summary__overview-wrapper')).toHaveCount(0); + await expect(infoControls.locator('.kv-pane-visibility-button_type_collapse')).toHaveCount( + 0, + ); + await expect(infoControls.locator('.kv-pane-visibility-button_type_expand')).toHaveCount(0); + await expect(summaryActions).toBeVisible(); + await expect(summaryActions.locator('.ydb-object-summary__refresh-button')).toBeVisible(); + await expect( + summaryActions.locator('.kv-pane-visibility-button_type_collapse'), + ).toBeVisible(); + }); + test('Open Preview icon appears on hover for "dv_slots" tree item', async ({page}) => { const objectSummary = new ObjectSummary(page); await expect(objectSummary.isTreeVisible()).resolves.toBe(true); @@ -58,17 +77,17 @@ test.describe('Object Summary', async () => { await expect(queryEditor.resultTable.isPreviewVisible()).resolves.toBe(true); }); - test('Primary keys header is visible in Schema tab', async ({page}) => { - const objectSummary = new ObjectSummary(page); + test('Legacy navigation renders only the tree for schema objects', async ({page}) => { + await page.evaluate(() => { + localStorage.setItem('enableTenantNavigationV2', JSON.stringify(false)); + }); + await page.reload({waitUntil: 'domcontentloaded'}); - await objectSummary.clickTab(ObjectSummaryTab.Schema); - await expect(objectSummary.isSchemaViewerVisible()).resolves.toBe(true); + const objectSummary = new ObjectSummary(page); - await expect(objectSummary.getPrimaryKeys()).resolves.toEqual([ - 'NodeId', - 'PDiskId', - 'VSlotId', - ]); + await expect(objectSummary.isTreeVisible()).resolves.toBe(true); + await expect(page.locator('.ydb-object-summary__info')).toHaveCount(0); + await expect(page.locator('.ydb-object-summary__overview-wrapper')).toHaveCount(0); }); test('Actions dropdown menu opens and contains expected items', async ({page}) => { @@ -295,9 +314,24 @@ test.describe('Object Summary', async () => { await expect(treeItemAfterRefresh).toBeVisible(); }); - test('Info panel collapse and expand functionality', async ({page}) => { + test('Info panel collapse and expand functionality in legacy navigation', async ({page}) => { + await page.evaluate(() => { + localStorage.setItem('enableTenantNavigationV2', JSON.stringify(false)); + }); + + const tenantPage = new TenantPage(page); + await tenantPage.goto( + { + schema: database, + database, + databasePage: 'query', + }, + {waitUntil: 'domcontentloaded'}, + ); + const objectSummary = new ObjectSummary(page); await expect(objectSummary.isTreeVisible()).resolves.toBe(true); + await expect(page.locator('.ydb-object-summary__info')).toBeVisible(); // Test info panel collapse/expand await objectSummary.collapseInfoPanel();