diff --git a/src/components/InfoViewer/schemaInfo/TableIndexInfo.tsx b/src/components/InfoViewer/schemaInfo/TableIndexInfo.tsx index 83ff559cfc..ec27f38d2f 100644 --- a/src/components/InfoViewer/schemaInfo/TableIndexInfo.tsx +++ b/src/components/InfoViewer/schemaInfo/TableIndexInfo.tsx @@ -3,9 +3,16 @@ import {InfoViewer} from '..'; import {getEntityName} from '../../../containers/Tenant/utils'; import {EIndexType} from '../../../types/api/schema'; import type {TEvDescribeSchemeResult} from '../../../types/api/schema'; +import {cn} from '../../../utils/cn'; import i18n from './i18n'; -import {buildIndexInfo, buildVectorIndexInfo} from './utils'; +import { + buildFulltextIndexSettingsInfo, + buildIndexInfo, + buildVectorIndexSettingsInfo, +} from './utils'; + +const b = cn('ydb-diagnostics-table-info'); interface TableIndexInfoProps { data?: TEvDescribeSchemeResult; @@ -21,19 +28,31 @@ export const TableIndexInfo = ({data}: TableIndexInfoProps) => { const TableIndex = data.PathDescription?.TableIndex; const info: Array = buildIndexInfo(TableIndex); - const vectorSettings = TableIndex?.VectorIndexKmeansTreeDescription?.Settings; - - const isVectorIndex = TableIndex?.Type === EIndexType.EIndexTypeGlobalVectorKmeansTree; - - if (isVectorIndex) { - const vectorInfo: Array = buildVectorIndexInfo(vectorSettings); - return ( - <> - - - + let settings: Array = []; + if (TableIndex?.Type === EIndexType.EIndexTypeGlobalVectorKmeansTree) { + settings = buildVectorIndexSettingsInfo( + TableIndex?.VectorIndexKmeansTreeDescription?.Settings, ); } + if (TableIndex?.Type === EIndexType.EIndexTypeGlobalFulltext) { + settings = buildFulltextIndexSettingsInfo(TableIndex?.FulltextIndexDescription?.Settings); + } - return ; + 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 7d7e5172d9..c08005d131 100644 --- a/src/components/InfoViewer/schemaInfo/i18n/en.json +++ b/src/components/InfoViewer/schemaInfo/i18n/en.json @@ -1,12 +1,29 @@ { - "title_vector-index": "Vector Index", + "title_index-settings": "Index Settings", + "field_clusters": "Clusters", "field_levels": "Levels", - "field_vector-dimension": "Vector Dimension", - "field_vector-type": "Vector Type", + "field_vector_dimension": "Vector Dimension", + "field_vector_type": "Vector Type", "field_metric": "Metric", "field_columns": "Columns", "field_includes": "Includes", "field_data-size": "Data Size", + + "field_layout": "Layout", + "field_tokenizer": "Tokenizer", + "field_language": "Language", + "field_use_filter_lowercase": "Filter Lowercase", + "field_use_filter_stopwords": "Filter Stopwords", + "field_use_filter_ngram": "Filter Ngram", + "field_use_filter_edge_ngram": "Filter Edge Ngram", + "field_filter_ngram_min_length": "Filter Ngram Min Length", + "field_filter_ngram_max_length": "Filter Ngram Max Length", + "field_use_filter_length": "Filter Length", + "field_filter_length_min": "Filter Length Min", + "field_filter_length_max": "Filter Length Max", + "filter_enabled": "Enabled", + "filter_disabled": "Disabled", + "alert_no-entity-data": "No {{entity}} data" } diff --git a/src/components/InfoViewer/schemaInfo/utils.ts b/src/components/InfoViewer/schemaInfo/utils.ts index 8918b5ef95..fdb0cf3750 100644 --- a/src/components/InfoViewer/schemaInfo/utils.ts +++ b/src/components/InfoViewer/schemaInfo/utils.ts @@ -1,7 +1,9 @@ import type {TIndexDescription} from '../../../types/api/schema'; import type { - TVectorIndexKmeansTreeDescriptionSettings, - TVectorIndexKmeansTreeDescriptionSettingsInner, + TFulltextIndexAnalyzers, + TFulltextIndexSettings, + TKMeansTreeSettings, + TVectorIndexSettings, } from '../../../types/api/schema/tableIndex'; import {formatNumber} from '../../../utils/dataFormatters/dataFormatters'; import type {InfoViewerItem} from '../InfoViewer'; @@ -39,15 +41,19 @@ export function buildIndexInfo(tableIndex?: TIndexDescription): InfoViewerItem[] return info; } -type VectorSettings = TVectorIndexKmeansTreeDescriptionSettings; +/* eslint-disable camelcase */ -export function buildVectorIndexInfo(vectorSettings?: VectorSettings): InfoViewerItem[] { +export function buildVectorIndexSettingsInfo( + kMeansTreeSettings?: TKMeansTreeSettings, +): InfoViewerItem[] { const info: InfoViewerItem[] = []; - if (!vectorSettings) { + if (!kMeansTreeSettings) { return info; } - const vectorFormatter = createInfoFormatter>({ + const kMeansTreeSettingsFormatter = createInfoFormatter< + Pick + >({ values: { clusters: (v) => (typeof v === 'number' ? formatNumber(v) : v), levels: (v) => (typeof v === 'number' ? formatNumber(v) : v), @@ -58,37 +64,150 @@ export function buildVectorIndexInfo(vectorSettings?: VectorSettings): InfoViewe }, }); - const settingsFormatter = createInfoFormatter({ + const {clusters, levels, settings} = kMeansTreeSettings; + if (clusters !== undefined) { + info.push(kMeansTreeSettingsFormatter('clusters', clusters)); + } + if (levels !== undefined) { + info.push(kMeansTreeSettingsFormatter('levels', levels)); + } + + const vectorIndexSettingsFormatter = createInfoFormatter({ values: { vector_dimension: (v) => (typeof v === 'number' ? formatNumber(v) : v), vector_type: (v) => (typeof v === 'string' ? v.replace(/^VECTOR_TYPE_/, '') : v), }, labels: { - vector_dimension: i18n('field_vector-dimension'), - vector_type: i18n('field_vector-type'), + vector_dimension: i18n('field_vector_dimension'), + vector_type: i18n('field_vector_type'), metric: i18n('field_metric'), }, }); - const {clusters, levels, settings} = vectorSettings ?? {}; - if (clusters !== undefined) { - info.push(vectorFormatter('clusters', clusters)); + const {metric, vector_type, vector_dimension} = settings ?? {}; + if (metric !== undefined) { + info.push(vectorIndexSettingsFormatter('metric', metric)); } - if (levels !== undefined) { - info.push(vectorFormatter('levels', levels)); + if (vector_type !== undefined) { + info.push(vectorIndexSettingsFormatter('vector_type', vector_type)); + } + if (vector_dimension !== undefined) { + info.push(vectorIndexSettingsFormatter('vector_dimension', vector_dimension)); } - const {vector_dimension: vectorDimension, vector_type: vectorType, metric} = settings ?? {}; + return info; +} - if (vectorDimension !== undefined) { - info.push(settingsFormatter('vector_dimension', vectorDimension)); +export function buildFulltextIndexSettingsInfo( + fulltextSettings?: TFulltextIndexSettings, +): InfoViewerItem[] { + const info: InfoViewerItem[] = []; + if (!fulltextSettings) { + return info; } - if (vectorType !== undefined) { - info.push(settingsFormatter('vector_type', vectorType)); + + const fulltextFormatter = createInfoFormatter>({ + values: { + layout: (v) => v, + }, + labels: { + layout: i18n('field_layout'), + }, + }); + + const {layout} = fulltextSettings; + if (layout !== undefined) { + info.push(fulltextFormatter('layout', layout)); } - if (metric !== undefined) { - info.push(settingsFormatter('metric', metric)); + + const fulltextIndexAnalyzersFormatter = createInfoFormatter({ + values: { + tokenizer: (v) => v, + language: (v) => v, + use_filter_lowercase: (v) => + v === true ? i18n('filter_enabled') : i18n('filter_disabled'), + use_filter_stopwords: (v) => + v === true ? i18n('filter_enabled') : i18n('filter_disabled'), + use_filter_ngram: (v) => + v === true ? i18n('filter_enabled') : i18n('filter_disabled'), + use_filter_edge_ngram: (v) => + v === true ? i18n('filter_enabled') : i18n('filter_disabled'), + filter_ngram_min_length: (v) => (typeof v === 'number' ? formatNumber(v) : v), + filter_ngram_max_length: (v) => (typeof v === 'number' ? formatNumber(v) : v), + use_filter_length: (v) => + v === true ? i18n('filter_enabled') : i18n('filter_disabled'), + filter_length_min: (v) => (typeof v === 'number' ? formatNumber(v) : v), + filter_length_max: (v) => (typeof v === 'number' ? formatNumber(v) : v), + }, + labels: { + tokenizer: i18n('field_tokenizer'), + language: i18n('field_language'), + use_filter_lowercase: i18n('field_use_filter_lowercase'), + use_filter_stopwords: i18n('field_use_filter_stopwords'), + use_filter_ngram: i18n('field_use_filter_ngram'), + use_filter_edge_ngram: i18n('field_use_filter_edge_ngram'), + filter_ngram_min_length: i18n('field_filter_ngram_min_length'), + filter_ngram_max_length: i18n('field_filter_ngram_max_length'), + use_filter_length: i18n('field_use_filter_length'), + filter_length_min: i18n('field_filter_length_min'), + filter_length_max: i18n('field_filter_length_max'), + }, + }); + + const analyzers = fulltextSettings.columns?.filter((col) => col.analyzers !== undefined)[0] + ?.analyzers; + const { + tokenizer, + language, + use_filter_lowercase, + use_filter_stopwords, + use_filter_ngram, + use_filter_edge_ngram, + filter_ngram_min_length, + filter_ngram_max_length, + use_filter_length, + filter_length_min, + filter_length_max, + } = analyzers ?? {}; + if (tokenizer !== undefined) { + info.push(fulltextIndexAnalyzersFormatter('tokenizer', tokenizer)); + } + if (language !== undefined) { + info.push(fulltextIndexAnalyzersFormatter('language', language)); + } + if (use_filter_lowercase !== undefined) { + info.push(fulltextIndexAnalyzersFormatter('use_filter_lowercase', use_filter_lowercase)); + } + if (use_filter_stopwords !== undefined) { + info.push(fulltextIndexAnalyzersFormatter('use_filter_stopwords', use_filter_stopwords)); + } + if (use_filter_ngram !== undefined) { + info.push(fulltextIndexAnalyzersFormatter('use_filter_ngram', use_filter_ngram)); + } + if (use_filter_edge_ngram !== undefined) { + info.push(fulltextIndexAnalyzersFormatter('use_filter_edge_ngram', use_filter_edge_ngram)); + } + if (filter_ngram_min_length !== undefined) { + info.push( + fulltextIndexAnalyzersFormatter('filter_ngram_min_length', filter_ngram_min_length), + ); + } + if (filter_ngram_max_length !== undefined) { + info.push( + fulltextIndexAnalyzersFormatter('filter_ngram_max_length', filter_ngram_max_length), + ); + } + if (use_filter_length !== undefined) { + info.push(fulltextIndexAnalyzersFormatter('use_filter_length', use_filter_length)); + } + if (filter_length_min !== undefined) { + info.push(fulltextIndexAnalyzersFormatter('filter_length_min', filter_length_min)); + } + if (filter_length_max !== undefined) { + info.push(fulltextIndexAnalyzersFormatter('filter_length_max', filter_length_max)); } return info; } + +/* eslint-enable camelcase */ diff --git a/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts b/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts index 67462186ab..c6c81b8d34 100644 --- a/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts +++ b/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts @@ -187,6 +187,8 @@ const pathSubTypeToPages: Record = { [EPathSubType.EPathSubTypeSyncIndexImplTable]: undefined, [EPathSubType.EPathSubTypeAsyncIndexImplTable]: undefined, + [EPathSubType.EPathSubTypeVectorKmeansTreeIndexImplTable]: undefined, + [EPathSubType.EPathSubTypeFulltextIndexImplTable]: undefined, [EPathSubType.EPathSubTypeEmpty]: undefined, }; diff --git a/src/containers/Tenant/ObjectSummary/ObjectSummary.tsx b/src/containers/Tenant/ObjectSummary/ObjectSummary.tsx index de70290c9e..004eb76f92 100644 --- a/src/containers/Tenant/ObjectSummary/ObjectSummary.tsx +++ b/src/containers/Tenant/ObjectSummary/ObjectSummary.tsx @@ -44,7 +44,7 @@ import { PaneVisibilityToggleButtons, paneVisibilityToggleReducerCreator, } from '../utils/paneVisibilityToggleHelpers'; -import {isIndexTableType, isTableType} from '../utils/schema'; +import {isTableType} from '../utils/schema'; import {ObjectTree} from './ObjectTree'; import {SchemaActions} from './SchemaActions'; @@ -77,7 +77,7 @@ export function ObjectSummary({ onExpandSummary, isCollapsed, }: ObjectSummaryProps) { - const {path, database, type, subType, databaseFullPath} = useCurrentSchema(); + const {path, database, type, databaseFullPath} = useCurrentSchema(); const dispatch = useTypedDispatch(); const {handleSchemaChange} = useTenantQueryParams(); @@ -404,7 +404,7 @@ export function ObjectSummary({ const relativePath = transformPath(path, databaseFullPath); const renderCommonInfoControls = () => { - const showPreview = isTableType(type) && !isIndexTableType(subType); + const showPreview = isTableType(type); return ( {showPreview && diff --git a/src/containers/Tenant/i18n/en.json b/src/containers/Tenant/i18n/en.json index ec666ac589..371eb83583 100644 --- a/src/containers/Tenant/i18n/en.json +++ b/src/containers/Tenant/i18n/en.json @@ -75,6 +75,8 @@ "entity-name_table": "Table", "entity-name_system-view": "System view", "entity-name_secondary-index": "Secondary Index", + "entity-name_vector-index": "Vector Index", + "entity-name_fulltext-index": "Fulltext Index", "entity-name_tablestore": "Tablestore", "entity-name_column-oriented-table": "Column-oriented table", "entity-name_changefeed": "Changefeed", @@ -85,5 +87,7 @@ "entity-name_async-replication": "Async Replication", "entity-name_transfer": "Transfer", "entity-name_resource-pool": "Resource Pool", - "entity-name_secondary-index-table": "Secondary Index Table" + "entity-name_secondary-index-table": "Secondary Index Table", + "entity-name_vector-index-table": "Vector Index Table", + "entity-name_fulltext-index-table": "Fulltext Index Table" } diff --git a/src/containers/Tenant/utils/controls.tsx b/src/containers/Tenant/utils/controls.tsx index d5374ce86e..4cbaca6787 100644 --- a/src/containers/Tenant/utils/controls.tsx +++ b/src/containers/Tenant/utils/controls.tsx @@ -77,7 +77,7 @@ export const getSchemaControls = column_table: openPreview, system_table: openPreview, - index_table: undefined, + index_table: openPreview, topic: isTopicPreviewAvailable && !isCdcTopic ? openPreview : undefined, stream: undefined, diff --git a/src/containers/Tenant/utils/index.ts b/src/containers/Tenant/utils/index.ts index d0861b5a8a..1d56435ae4 100644 --- a/src/containers/Tenant/utils/index.ts +++ b/src/containers/Tenant/utils/index.ts @@ -1,10 +1,15 @@ +import {EPathType} from '../../../types/api/schema'; import type {TPathDescription} from '../../../types/api/schema'; -import {mapPathTypeToEntityName} from './schema'; +import {mapIndexTypeToEntityName, mapPathTypeToEntityName} from './schema'; export const getEntityName = (pathDescription?: TPathDescription) => { const {PathType, PathSubType} = pathDescription?.Self || {}; + if (PathType === EPathType.EPathTypeTableIndex) { + return mapIndexTypeToEntityName(pathDescription?.TableIndex?.Type); + } + return mapPathTypeToEntityName(PathType, PathSubType); }; diff --git a/src/containers/Tenant/utils/schema.ts b/src/containers/Tenant/utils/schema.ts index 43eb559f9d..fca7b829a3 100644 --- a/src/containers/Tenant/utils/schema.ts +++ b/src/containers/Tenant/utils/schema.ts @@ -1,6 +1,6 @@ import type {NavigationTreeNodeType} from 'ydb-ui-components'; -import {EPathSubType, EPathType} from '../../../types/api/schema'; +import {EIndexType, EPathSubType, EPathType} from '../../../types/api/schema'; import type {ETenantType} from '../../../types/api/tenant'; import i18n from '../i18n'; @@ -11,6 +11,8 @@ import i18n from '../i18n'; const pathSubTypeToNodeType: Record = { [EPathSubType.EPathSubTypeSyncIndexImplTable]: 'index_table', [EPathSubType.EPathSubTypeAsyncIndexImplTable]: 'index_table', + [EPathSubType.EPathSubTypeVectorKmeansTreeIndexImplTable]: 'index_table', + [EPathSubType.EPathSubTypeFulltextIndexImplTable]: 'index_table', [EPathSubType.EPathSubTypeStreamImpl]: undefined, [EPathSubType.EPathSubTypeEmpty]: undefined, @@ -65,6 +67,10 @@ export const mapPathTypeToNavigationTreeType = ( const pathSubTypeToEntityName: Record = { [EPathSubType.EPathSubTypeSyncIndexImplTable]: i18n('entity-name_secondary-index-table'), [EPathSubType.EPathSubTypeAsyncIndexImplTable]: i18n('entity-name_secondary-index-table'), + [EPathSubType.EPathSubTypeVectorKmeansTreeIndexImplTable]: i18n( + 'entity-name_vector-index-table', + ), + [EPathSubType.EPathSubTypeFulltextIndexImplTable]: i18n('entity-name_fulltext-index-table'), [EPathSubType.EPathSubTypeStreamImpl]: undefined, [EPathSubType.EPathSubTypeEmpty]: undefined, @@ -103,6 +109,18 @@ export const mapPathTypeToEntityName = ( // ==================== +const indexTypeToEntityName: Record = { + [EIndexType.EIndexTypeInvalid]: undefined, + [EIndexType.EIndexTypeGlobal]: i18n('entity-name_secondary-index'), + [EIndexType.EIndexTypeGlobalAsync]: i18n('entity-name_secondary-index'), + [EIndexType.EIndexTypeGlobalVectorKmeansTree]: i18n('entity-name_vector-index'), + [EIndexType.EIndexTypeGlobalFulltext]: i18n('entity-name_fulltext-index'), +}; + +export const mapIndexTypeToEntityName = (type?: EIndexType) => type && indexTypeToEntityName[type]; + +// ==================== + const databaseTypeToDBName: Record = { UnknownTenantType: 'Database', Domain: 'Cluster Root', @@ -147,6 +165,8 @@ 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, @@ -223,6 +243,8 @@ export const isTopicEntityType = (type?: EPathType) => type === EPathType.EPathT const pathSubTypeToChildless: Record = { [EPathSubType.EPathSubTypeSyncIndexImplTable]: true, [EPathSubType.EPathSubTypeAsyncIndexImplTable]: true, + [EPathSubType.EPathSubTypeVectorKmeansTreeIndexImplTable]: true, + [EPathSubType.EPathSubTypeFulltextIndexImplTable]: true, [EPathSubType.EPathSubTypeStreamImpl]: true, [EPathSubType.EPathSubTypeEmpty]: false, diff --git a/src/types/api/schema/schema.ts b/src/types/api/schema/schema.ts index 7175a26f82..df2909125a 100644 --- a/src/types/api/schema/schema.ts +++ b/src/types/api/schema/schema.ts @@ -308,6 +308,8 @@ export enum EPathSubType { EPathSubTypeSyncIndexImplTable = 'EPathSubTypeSyncIndexImplTable', EPathSubTypeAsyncIndexImplTable = 'EPathSubTypeAsyncIndexImplTable', EPathSubTypeStreamImpl = 'EPathSubTypeStreamImpl', + EPathSubTypeVectorKmeansTreeIndexImplTable = 'EPathSubTypeVectorKmeansTreeIndexImplTable', + EPathSubTypeFulltextIndexImplTable = 'EPathSubTypeFulltextIndexImplTable', } enum EPathState { diff --git a/src/types/api/schema/tableIndex.ts b/src/types/api/schema/tableIndex.ts index 20cd3be8d6..6eb6b837b2 100644 --- a/src/types/api/schema/tableIndex.ts +++ b/src/types/api/schema/tableIndex.ts @@ -24,6 +24,11 @@ export interface TIndexDescription { * Present for vector indexes of type EIndexTypeGlobalVectorKmeansTree */ VectorIndexKmeansTreeDescription?: TVectorIndexKmeansTreeDescription; + + /** + * Present for vector indexes of type EIndexTypeGlobalFulltext + */ + FulltextIndexDescription?: TFulltextIndexDescription; } export enum EIndexType { @@ -31,6 +36,7 @@ export enum EIndexType { EIndexTypeGlobal = 'EIndexTypeGlobal', EIndexTypeGlobalAsync = 'EIndexTypeGlobalAsync', EIndexTypeGlobalVectorKmeansTree = 'EIndexTypeGlobalVectorKmeansTree', + EIndexTypeGlobalFulltext = 'EIndexTypeGlobalFulltext', } export enum EIndexState { @@ -40,18 +46,46 @@ export enum EIndexState { EIndexStateWriteOnly = 'EIndexStateWriteOnly', } -export interface TVectorIndexKmeansTreeDescriptionSettingsInner { - vector_dimension?: number; - vector_type?: string; +export interface TVectorIndexSettings { metric?: string; + vector_type?: string; + vector_dimension?: number; } -export interface TVectorIndexKmeansTreeDescriptionSettings { +export interface TKMeansTreeSettings { clusters?: number; levels?: number; - settings?: TVectorIndexKmeansTreeDescriptionSettingsInner; + settings?: TVectorIndexSettings; } export interface TVectorIndexKmeansTreeDescription { - Settings?: TVectorIndexKmeansTreeDescriptionSettings; + Settings?: TKMeansTreeSettings; +} + +export interface TFulltextIndexAnalyzers { + tokenizer?: string; + language?: string; + use_filter_lowercase?: boolean; + use_filter_stopwords?: boolean; + use_filter_ngram?: boolean; + use_filter_edge_ngram?: boolean; + filter_ngram_min_length?: number; + filter_ngram_max_length?: number; + use_filter_length?: boolean; + filter_length_min?: number; + filter_length_max?: number; +} + +export interface TFulltextIndexColumnAnalyzers { + column?: string; + analyzers?: TFulltextIndexAnalyzers; +} + +export interface TFulltextIndexSettings { + layout?: string; + columns?: TFulltextIndexColumnAnalyzers[]; +} + +export interface TFulltextIndexDescription { + Settings?: TFulltextIndexSettings; }