Skip to content

Commit 11ddc82

Browse files
kungaastandrik
andauthored
feat(ui): support fulltext index (#2962)
Co-authored-by: Anton Standrik <[email protected]>
1 parent 5d116e9 commit 11ddc82

File tree

11 files changed

+274
-50
lines changed

11 files changed

+274
-50
lines changed

src/components/InfoViewer/schemaInfo/TableIndexInfo.tsx

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,16 @@ import {InfoViewer} from '..';
33
import {getEntityName} from '../../../containers/Tenant/utils';
44
import {EIndexType} from '../../../types/api/schema';
55
import type {TEvDescribeSchemeResult} from '../../../types/api/schema';
6+
import {cn} from '../../../utils/cn';
67

78
import i18n from './i18n';
8-
import {buildIndexInfo, buildVectorIndexInfo} from './utils';
9+
import {
10+
buildFulltextIndexSettingsInfo,
11+
buildIndexInfo,
12+
buildVectorIndexSettingsInfo,
13+
} from './utils';
14+
15+
const b = cn('ydb-diagnostics-table-info');
916

1017
interface TableIndexInfoProps {
1118
data?: TEvDescribeSchemeResult;
@@ -21,19 +28,31 @@ export const TableIndexInfo = ({data}: TableIndexInfoProps) => {
2128
const TableIndex = data.PathDescription?.TableIndex;
2229
const info: Array<InfoViewerItem> = buildIndexInfo(TableIndex);
2330

24-
const vectorSettings = TableIndex?.VectorIndexKmeansTreeDescription?.Settings;
25-
26-
const isVectorIndex = TableIndex?.Type === EIndexType.EIndexTypeGlobalVectorKmeansTree;
27-
28-
if (isVectorIndex) {
29-
const vectorInfo: Array<InfoViewerItem> = buildVectorIndexInfo(vectorSettings);
30-
return (
31-
<>
32-
<InfoViewer title={i18n('title_vector-index')} info={info} />
33-
<InfoViewer info={vectorInfo} />
34-
</>
31+
let settings: Array<InfoViewerItem> = [];
32+
if (TableIndex?.Type === EIndexType.EIndexTypeGlobalVectorKmeansTree) {
33+
settings = buildVectorIndexSettingsInfo(
34+
TableIndex?.VectorIndexKmeansTreeDescription?.Settings,
3535
);
3636
}
37+
if (TableIndex?.Type === EIndexType.EIndexTypeGlobalFulltext) {
38+
settings = buildFulltextIndexSettingsInfo(TableIndex?.FulltextIndexDescription?.Settings);
39+
}
3740

38-
return <InfoViewer title={entityName} info={info} />;
41+
return (
42+
<div className={b()}>
43+
<InfoViewer
44+
info={info}
45+
title={entityName}
46+
className={b('info-block')}
47+
renderEmptyState={() => <div className={b('title')}>{entityName}</div>}
48+
/>
49+
{settings && settings.length ? (
50+
<InfoViewer
51+
info={settings}
52+
title={i18n('title_index-settings')}
53+
className={b('info-block')}
54+
/>
55+
) : null}
56+
</div>
57+
);
3958
};
Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,29 @@
11
{
2-
"title_vector-index": "Vector Index",
2+
"title_index-settings": "Index Settings",
3+
34
"field_clusters": "Clusters",
45
"field_levels": "Levels",
5-
"field_vector-dimension": "Vector Dimension",
6-
"field_vector-type": "Vector Type",
6+
"field_vector_dimension": "Vector Dimension",
7+
"field_vector_type": "Vector Type",
78
"field_metric": "Metric",
89
"field_columns": "Columns",
910
"field_includes": "Includes",
1011
"field_data-size": "Data Size",
12+
13+
"field_layout": "Layout",
14+
"field_tokenizer": "Tokenizer",
15+
"field_language": "Language",
16+
"field_use_filter_lowercase": "Filter Lowercase",
17+
"field_use_filter_stopwords": "Filter Stopwords",
18+
"field_use_filter_ngram": "Filter Ngram",
19+
"field_use_filter_edge_ngram": "Filter Edge Ngram",
20+
"field_filter_ngram_min_length": "Filter Ngram Min Length",
21+
"field_filter_ngram_max_length": "Filter Ngram Max Length",
22+
"field_use_filter_length": "Filter Length",
23+
"field_filter_length_min": "Filter Length Min",
24+
"field_filter_length_max": "Filter Length Max",
25+
"filter_enabled": "Enabled",
26+
"filter_disabled": "Disabled",
27+
1128
"alert_no-entity-data": "No {{entity}} data"
1229
}

src/components/InfoViewer/schemaInfo/utils.ts

Lines changed: 140 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import type {TIndexDescription} from '../../../types/api/schema';
22
import type {
3-
TVectorIndexKmeansTreeDescriptionSettings,
4-
TVectorIndexKmeansTreeDescriptionSettingsInner,
3+
TFulltextIndexAnalyzers,
4+
TFulltextIndexSettings,
5+
TKMeansTreeSettings,
6+
TVectorIndexSettings,
57
} from '../../../types/api/schema/tableIndex';
68
import {formatNumber} from '../../../utils/dataFormatters/dataFormatters';
79
import type {InfoViewerItem} from '../InfoViewer';
@@ -39,15 +41,19 @@ export function buildIndexInfo(tableIndex?: TIndexDescription): InfoViewerItem[]
3941
return info;
4042
}
4143

42-
type VectorSettings = TVectorIndexKmeansTreeDescriptionSettings;
44+
/* eslint-disable camelcase */
4345

44-
export function buildVectorIndexInfo(vectorSettings?: VectorSettings): InfoViewerItem[] {
46+
export function buildVectorIndexSettingsInfo(
47+
kMeansTreeSettings?: TKMeansTreeSettings,
48+
): InfoViewerItem[] {
4549
const info: InfoViewerItem[] = [];
46-
if (!vectorSettings) {
50+
if (!kMeansTreeSettings) {
4751
return info;
4852
}
4953

50-
const vectorFormatter = createInfoFormatter<Pick<VectorSettings, 'clusters' | 'levels'>>({
54+
const kMeansTreeSettingsFormatter = createInfoFormatter<
55+
Pick<TKMeansTreeSettings, 'clusters' | 'levels'>
56+
>({
5157
values: {
5258
clusters: (v) => (typeof v === 'number' ? formatNumber(v) : v),
5359
levels: (v) => (typeof v === 'number' ? formatNumber(v) : v),
@@ -58,37 +64,150 @@ export function buildVectorIndexInfo(vectorSettings?: VectorSettings): InfoViewe
5864
},
5965
});
6066

61-
const settingsFormatter = createInfoFormatter<TVectorIndexKmeansTreeDescriptionSettingsInner>({
67+
const {clusters, levels, settings} = kMeansTreeSettings;
68+
if (clusters !== undefined) {
69+
info.push(kMeansTreeSettingsFormatter('clusters', clusters));
70+
}
71+
if (levels !== undefined) {
72+
info.push(kMeansTreeSettingsFormatter('levels', levels));
73+
}
74+
75+
const vectorIndexSettingsFormatter = createInfoFormatter<TVectorIndexSettings>({
6276
values: {
6377
vector_dimension: (v) => (typeof v === 'number' ? formatNumber(v) : v),
6478
vector_type: (v) => (typeof v === 'string' ? v.replace(/^VECTOR_TYPE_/, '') : v),
6579
},
6680
labels: {
67-
vector_dimension: i18n('field_vector-dimension'),
68-
vector_type: i18n('field_vector-type'),
81+
vector_dimension: i18n('field_vector_dimension'),
82+
vector_type: i18n('field_vector_type'),
6983
metric: i18n('field_metric'),
7084
},
7185
});
7286

73-
const {clusters, levels, settings} = vectorSettings ?? {};
74-
if (clusters !== undefined) {
75-
info.push(vectorFormatter('clusters', clusters));
87+
const {metric, vector_type, vector_dimension} = settings ?? {};
88+
if (metric !== undefined) {
89+
info.push(vectorIndexSettingsFormatter('metric', metric));
7690
}
77-
if (levels !== undefined) {
78-
info.push(vectorFormatter('levels', levels));
91+
if (vector_type !== undefined) {
92+
info.push(vectorIndexSettingsFormatter('vector_type', vector_type));
93+
}
94+
if (vector_dimension !== undefined) {
95+
info.push(vectorIndexSettingsFormatter('vector_dimension', vector_dimension));
7996
}
8097

81-
const {vector_dimension: vectorDimension, vector_type: vectorType, metric} = settings ?? {};
98+
return info;
99+
}
82100

83-
if (vectorDimension !== undefined) {
84-
info.push(settingsFormatter('vector_dimension', vectorDimension));
101+
export function buildFulltextIndexSettingsInfo(
102+
fulltextSettings?: TFulltextIndexSettings,
103+
): InfoViewerItem[] {
104+
const info: InfoViewerItem[] = [];
105+
if (!fulltextSettings) {
106+
return info;
85107
}
86-
if (vectorType !== undefined) {
87-
info.push(settingsFormatter('vector_type', vectorType));
108+
109+
const fulltextFormatter = createInfoFormatter<Pick<TFulltextIndexSettings, 'layout'>>({
110+
values: {
111+
layout: (v) => v,
112+
},
113+
labels: {
114+
layout: i18n('field_layout'),
115+
},
116+
});
117+
118+
const {layout} = fulltextSettings;
119+
if (layout !== undefined) {
120+
info.push(fulltextFormatter('layout', layout));
88121
}
89-
if (metric !== undefined) {
90-
info.push(settingsFormatter('metric', metric));
122+
123+
const fulltextIndexAnalyzersFormatter = createInfoFormatter<TFulltextIndexAnalyzers>({
124+
values: {
125+
tokenizer: (v) => v,
126+
language: (v) => v,
127+
use_filter_lowercase: (v) =>
128+
v === true ? i18n('filter_enabled') : i18n('filter_disabled'),
129+
use_filter_stopwords: (v) =>
130+
v === true ? i18n('filter_enabled') : i18n('filter_disabled'),
131+
use_filter_ngram: (v) =>
132+
v === true ? i18n('filter_enabled') : i18n('filter_disabled'),
133+
use_filter_edge_ngram: (v) =>
134+
v === true ? i18n('filter_enabled') : i18n('filter_disabled'),
135+
filter_ngram_min_length: (v) => (typeof v === 'number' ? formatNumber(v) : v),
136+
filter_ngram_max_length: (v) => (typeof v === 'number' ? formatNumber(v) : v),
137+
use_filter_length: (v) =>
138+
v === true ? i18n('filter_enabled') : i18n('filter_disabled'),
139+
filter_length_min: (v) => (typeof v === 'number' ? formatNumber(v) : v),
140+
filter_length_max: (v) => (typeof v === 'number' ? formatNumber(v) : v),
141+
},
142+
labels: {
143+
tokenizer: i18n('field_tokenizer'),
144+
language: i18n('field_language'),
145+
use_filter_lowercase: i18n('field_use_filter_lowercase'),
146+
use_filter_stopwords: i18n('field_use_filter_stopwords'),
147+
use_filter_ngram: i18n('field_use_filter_ngram'),
148+
use_filter_edge_ngram: i18n('field_use_filter_edge_ngram'),
149+
filter_ngram_min_length: i18n('field_filter_ngram_min_length'),
150+
filter_ngram_max_length: i18n('field_filter_ngram_max_length'),
151+
use_filter_length: i18n('field_use_filter_length'),
152+
filter_length_min: i18n('field_filter_length_min'),
153+
filter_length_max: i18n('field_filter_length_max'),
154+
},
155+
});
156+
157+
const analyzers = fulltextSettings.columns?.filter((col) => col.analyzers !== undefined)[0]
158+
?.analyzers;
159+
const {
160+
tokenizer,
161+
language,
162+
use_filter_lowercase,
163+
use_filter_stopwords,
164+
use_filter_ngram,
165+
use_filter_edge_ngram,
166+
filter_ngram_min_length,
167+
filter_ngram_max_length,
168+
use_filter_length,
169+
filter_length_min,
170+
filter_length_max,
171+
} = analyzers ?? {};
172+
if (tokenizer !== undefined) {
173+
info.push(fulltextIndexAnalyzersFormatter('tokenizer', tokenizer));
174+
}
175+
if (language !== undefined) {
176+
info.push(fulltextIndexAnalyzersFormatter('language', language));
177+
}
178+
if (use_filter_lowercase !== undefined) {
179+
info.push(fulltextIndexAnalyzersFormatter('use_filter_lowercase', use_filter_lowercase));
180+
}
181+
if (use_filter_stopwords !== undefined) {
182+
info.push(fulltextIndexAnalyzersFormatter('use_filter_stopwords', use_filter_stopwords));
183+
}
184+
if (use_filter_ngram !== undefined) {
185+
info.push(fulltextIndexAnalyzersFormatter('use_filter_ngram', use_filter_ngram));
186+
}
187+
if (use_filter_edge_ngram !== undefined) {
188+
info.push(fulltextIndexAnalyzersFormatter('use_filter_edge_ngram', use_filter_edge_ngram));
189+
}
190+
if (filter_ngram_min_length !== undefined) {
191+
info.push(
192+
fulltextIndexAnalyzersFormatter('filter_ngram_min_length', filter_ngram_min_length),
193+
);
194+
}
195+
if (filter_ngram_max_length !== undefined) {
196+
info.push(
197+
fulltextIndexAnalyzersFormatter('filter_ngram_max_length', filter_ngram_max_length),
198+
);
199+
}
200+
if (use_filter_length !== undefined) {
201+
info.push(fulltextIndexAnalyzersFormatter('use_filter_length', use_filter_length));
202+
}
203+
if (filter_length_min !== undefined) {
204+
info.push(fulltextIndexAnalyzersFormatter('filter_length_min', filter_length_min));
205+
}
206+
if (filter_length_max !== undefined) {
207+
info.push(fulltextIndexAnalyzersFormatter('filter_length_max', filter_length_max));
91208
}
92209

93210
return info;
94211
}
212+
213+
/* eslint-enable camelcase */

src/containers/Tenant/Diagnostics/DiagnosticsPages.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,8 @@ const pathSubTypeToPages: Record<EPathSubType, Page[] | undefined> = {
187187

188188
[EPathSubType.EPathSubTypeSyncIndexImplTable]: undefined,
189189
[EPathSubType.EPathSubTypeAsyncIndexImplTable]: undefined,
190+
[EPathSubType.EPathSubTypeVectorKmeansTreeIndexImplTable]: undefined,
191+
[EPathSubType.EPathSubTypeFulltextIndexImplTable]: undefined,
190192
[EPathSubType.EPathSubTypeEmpty]: undefined,
191193
};
192194

src/containers/Tenant/ObjectSummary/ObjectSummary.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ import {
4444
PaneVisibilityToggleButtons,
4545
paneVisibilityToggleReducerCreator,
4646
} from '../utils/paneVisibilityToggleHelpers';
47-
import {isIndexTableType, isTableType} from '../utils/schema';
47+
import {isTableType} from '../utils/schema';
4848

4949
import {ObjectTree} from './ObjectTree';
5050
import {SchemaActions} from './SchemaActions';
@@ -77,7 +77,7 @@ export function ObjectSummary({
7777
onExpandSummary,
7878
isCollapsed,
7979
}: ObjectSummaryProps) {
80-
const {path, database, type, subType, databaseFullPath} = useCurrentSchema();
80+
const {path, database, type, databaseFullPath} = useCurrentSchema();
8181

8282
const dispatch = useTypedDispatch();
8383
const {handleSchemaChange} = useTenantQueryParams();
@@ -404,7 +404,7 @@ export function ObjectSummary({
404404
const relativePath = transformPath(path, databaseFullPath);
405405

406406
const renderCommonInfoControls = () => {
407-
const showPreview = isTableType(type) && !isIndexTableType(subType);
407+
const showPreview = isTableType(type);
408408
return (
409409
<React.Fragment>
410410
{showPreview &&

src/containers/Tenant/i18n/en.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@
7575
"entity-name_table": "Table",
7676
"entity-name_system-view": "System view",
7777
"entity-name_secondary-index": "Secondary Index",
78+
"entity-name_vector-index": "Vector Index",
79+
"entity-name_fulltext-index": "Fulltext Index",
7880
"entity-name_tablestore": "Tablestore",
7981
"entity-name_column-oriented-table": "Column-oriented table",
8082
"entity-name_changefeed": "Changefeed",
@@ -85,5 +87,7 @@
8587
"entity-name_async-replication": "Async Replication",
8688
"entity-name_transfer": "Transfer",
8789
"entity-name_resource-pool": "Resource Pool",
88-
"entity-name_secondary-index-table": "Secondary Index Table"
90+
"entity-name_secondary-index-table": "Secondary Index Table",
91+
"entity-name_vector-index-table": "Vector Index Table",
92+
"entity-name_fulltext-index-table": "Fulltext Index Table"
8993
}

src/containers/Tenant/utils/controls.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export const getSchemaControls =
7777
column_table: openPreview,
7878
system_table: openPreview,
7979

80-
index_table: undefined,
80+
index_table: openPreview,
8181
topic: isTopicPreviewAvailable && !isCdcTopic ? openPreview : undefined,
8282
stream: undefined,
8383

src/containers/Tenant/utils/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1+
import {EPathType} from '../../../types/api/schema';
12
import type {TPathDescription} from '../../../types/api/schema';
23

3-
import {mapPathTypeToEntityName} from './schema';
4+
import {mapIndexTypeToEntityName, mapPathTypeToEntityName} from './schema';
45

56
export const getEntityName = (pathDescription?: TPathDescription) => {
67
const {PathType, PathSubType} = pathDescription?.Self || {};
78

9+
if (PathType === EPathType.EPathTypeTableIndex) {
10+
return mapIndexTypeToEntityName(pathDescription?.TableIndex?.Type);
11+
}
12+
813
return mapPathTypeToEntityName(PathType, PathSubType);
914
};
1015

0 commit comments

Comments
 (0)