From 58ee10d6d0d3ba7061774a665069d13dddead0ab Mon Sep 17 00:00:00 2001 From: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> Date: Wed, 26 Feb 2025 22:22:20 +0000 Subject: [PATCH 1/2] Refactor changes Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> --- .../data_importer_app.test.tsx.snap | 407 ++++++++++-------- .../import_type_selector.test.tsx.snap | 179 ++++---- .../components/data_importer_app.test.tsx | 1 + .../public/components/data_importer_app.tsx | 119 ++--- .../public/lib/cat_indices.test.ts | 35 ++ .../data_importer/public/lib/cat_indices.ts | 17 + .../public/lib/import_file.test.ts | 58 ++- .../data_importer/public/lib/import_file.ts | 34 +- .../public/lib/import_text.test.ts | 51 ++- .../data_importer/public/lib/import_text.ts | 32 +- .../data_importer/public/lib/preview.ts | 2 + src/plugins/data_importer/public/types.ts | 4 + .../server/routes/cat_indices.ts | 13 +- .../server/routes/import_file.ts | 33 +- .../server/routes/import_text.ts | 27 +- .../data_importer/server/routes/preview.ts | 5 +- 16 files changed, 634 insertions(+), 383 deletions(-) create mode 100644 src/plugins/data_importer/public/lib/cat_indices.test.ts create mode 100644 src/plugins/data_importer/public/lib/cat_indices.ts diff --git a/src/plugins/data_importer/public/components/__snapshots__/data_importer_app.test.tsx.snap b/src/plugins/data_importer/public/components/__snapshots__/data_importer_app.test.tsx.snap index 12aee687b351..a452893f4fce 100644 --- a/src/plugins/data_importer/public/components/__snapshots__/data_importer_app.test.tsx.snap +++ b/src/plugins/data_importer/public/components/__snapshots__/data_importer_app.test.tsx.snap @@ -10,109 +10,6 @@ exports[`App should render with MDS 1`] = ` useDefaultBehaviors={true} /> - - - - - Data Source Options - - - - -
- <[object Object] - componentConfig={ - Object { - "fullWidth": true, - "notifications": Object { - "toasts": Object { - "add": [MockFunction], - "addDanger": [MockFunction], - "addError": [MockFunction], - "addInfo": [MockFunction], - "addSuccess": [MockFunction], - "addWarning": [MockFunction], - "get$": [MockFunction], - "remove": [MockFunction], - }, - }, - "onSelectedDataSources": [Function], - "savedObjects": Object { - "bulkCreate": [MockFunction], - "bulkGet": [MockFunction], - "bulkUpdate": [MockFunction], - "create": [MockFunction], - "delete": [MockFunction], - "find": [MockFunction], - "get": [MockFunction], - "setCurrentWorkspace": [MockFunction], - "update": [MockFunction], - }, - "selectedOption": undefined, - } - } - componentType="DataSourceSelectable" - dataSourceManagement={ - Object { - "dataSourceSelection": DataSourceSelectionService { - "getSelection$": [Function], - "getSelectionValue": [Function], - "remove": [Function], - "removedComponentIds": Array [], - "selectDataSource": [Function], - "selectedDataSource$": BehaviorSubject { - "_isScalar": false, - "_value": Map {}, - "closed": false, - "hasError": false, - "isStopped": false, - "observers": Array [], - "thrownError": null, - }, - }, - "getDefaultDataSourceId": [Function], - "getDefaultDataSourceId$": [Function], - "registerAuthenticationMethod": [Function], - "ui": Object { - "DataSourceSelector": [Function], - "getDataSourceMenu": [MockFunction] { - "calls": Array [ - Array [], - ], - "results": Array [ - Object { - "type": "return", - "value":
- DataSourceMenu -
, - }, - ], - }, - }, - } - } - /> - -
- - Import - -
@@ -122,39 +19,147 @@ exports[`App should render with MDS 1`] = ` >

- - -

- + + + + + Select target data source + + +
+ <[object Object] + componentConfig={ + Object { + "fullWidth": true, + "notifications": Object { + "toasts": Object { + "add": [MockFunction], + "addDanger": [MockFunction], + "addError": [MockFunction], + "addInfo": [MockFunction], + "addSuccess": [MockFunction], + "addWarning": [MockFunction], + "get$": [MockFunction], + "remove": [MockFunction], + }, + }, + "onManageDataSource": [Function], + "onSelectedDataSources": [Function], + "savedObjects": Object { + "bulkCreate": [MockFunction], + "bulkGet": [MockFunction], + "bulkUpdate": [MockFunction], + "create": [MockFunction], + "delete": [MockFunction], + "find": [MockFunction], + "get": [MockFunction], + "setCurrentWorkspace": [MockFunction], + "update": [MockFunction], + }, + } + } + componentType="DataSourceSelectable" + onManageDataSource={[Function]} + /> + +
+ + + + Create/Select Index Name + + + + + + + + + Preview + + + + Import + +
+ +
+ -

-
-
- + + +
@@ -172,32 +177,6 @@ exports[`App should render without MDS 1`] = ` useDefaultBehaviors={true} /> - - - - - Data Source Options - - - - - - Import - - @@ -207,39 +186,101 @@ exports[`App should render without MDS 1`] = ` >

- - -

- + + + + + + Create/Select Index Name + + + + + + + + + Preview + + + + Import + + + +
+ -

-
-
- + + +
diff --git a/src/plugins/data_importer/public/components/__snapshots__/import_type_selector.test.tsx.snap b/src/plugins/data_importer/public/components/__snapshots__/import_type_selector.test.tsx.snap index 42a06daddffb..3176b5f039b3 100644 --- a/src/plugins/data_importer/public/components/__snapshots__/import_type_selector.test.tsx.snap +++ b/src/plugins/data_importer/public/components/__snapshots__/import_type_selector.test.tsx.snap @@ -13,119 +13,128 @@ exports[`ImportTypeSelector should render 1`] = `
-
-
-
-
- -
-
-
-
-
-
- -
- +
{ const savedObjectsMock = coreMock.createStart().savedObjects; const navigationMock = navigationPluginMock.createStartContract(); const mockConfig: PublicConfigSchema = { + filePreviewDocumentsCount: 10, enabledFileTypes: DEFAULT_SUPPORTED_FILE_TYPES_LIST, maxFileSizeBytes: 104857600, maxTextCount: 10000, diff --git a/src/plugins/data_importer/public/components/data_importer_app.tsx b/src/plugins/data_importer/public/components/data_importer_app.tsx index 2bf9212e197f..1a41443cb25d 100644 --- a/src/plugins/data_importer/public/components/data_importer_app.tsx +++ b/src/plugins/data_importer/public/components/data_importer_app.tsx @@ -46,6 +46,7 @@ import { CSV_FILE_TYPE, CSV_SUPPORTED_DELIMITERS } from '../../common/constants' import { DelimiterSelect } from './delimiter_select'; import { previewFile } from '../lib/preview'; import { PreviewComponent } from './preview_table'; +import { catIndices } from '../lib/cat_indices'; interface DataImporterPluginAppProps { basename: string; @@ -92,31 +93,11 @@ export const DataImporterPluginApp = ({ const [indexOptions, setIndexOptions] = useState>([]); const [createMode, setCreateMode] = useState(false); - useEffect(() => { - const fetchIndices = async () => { - try { - const response = await http.get('/api/data_importer/_cat_indices'); - setIndexOptions(response.indices.map((index: string) => ({ label: index }))); - } catch (error) { - notifications.toasts.addDanger( - i18n.translate('dataImporter.indicesFetchError', { - defaultMessage: `Failed to fetch indices: {error}`, - values: { error }, - }) - ); - } - }; - - fetchIndices(); - }, [http, notifications.toasts]); - const onImportTypeChange = (type: ImportChoices) => { if (type === IMPORT_CHOICE_FILE) { setInputFile(undefined); - setShowDelimiterChoice(true); } else if (type === IMPORT_CHOICE_TEXT) { setText(undefined); - setShowDelimiterChoice(false); } setImportType(type); }; @@ -126,7 +107,6 @@ export const DataImporterPluginApp = ({ setDelimiter(undefined); } setDataType(type); - setShowDelimiterChoice(type === CSV_FILE_TYPE && importType === IMPORT_CHOICE_FILE); }; const onFileInput = (file?: File) => { @@ -168,7 +148,7 @@ export const DataImporterPluginApp = ({ if (importType === IMPORT_CHOICE_FILE) { if (inputFile) { const fileExtension = extname(inputFile.name); - setIsLoadingPreview(true); // Set loading state to true + setIsLoadingPreview(true); try { const response = await previewFile( http, @@ -176,10 +156,11 @@ export const DataImporterPluginApp = ({ createMode, fileExtension, indexName!, + config.filePreviewDocumentsCount, delimiter, dataSourceId ); - setIsLoadingPreview(false); // Set loading state to false + setIsLoadingPreview(false); if (response) { setFilePreviewData(response); notifications.toasts.addSuccess( @@ -195,7 +176,6 @@ export const DataImporterPluginApp = ({ ); } } catch (error) { - // console.error(error, 'error'); setIsLoadingPreview(false); } } @@ -207,25 +187,28 @@ export const DataImporterPluginApp = ({ try { if (importType === IMPORT_CHOICE_FILE) { - response = await importFile( + response = await importFile({ http, - inputFile!, - indexName!, + file: inputFile!, + indexName: indexName!, createMode, // TODO This should be determined from the file type selectable - extname(inputFile!.name), + fileExtension: extname(inputFile!.name), delimiter, - dataSourceId - ); + selectedDataSourceId: dataSourceId, + mapping: filePreviewData.mapping, + }); } else if (importType === IMPORT_CHOICE_TEXT) { - response = await importText( + response = await importText({ http, - inputText!, - dataType!, - indexName!, + text: inputText!, + textFormat: dataType!, + createMode, + indexName: indexName!, delimiter, - dataSourceId - ); + selectedDataSourceId: dataSourceId, + mapping: filePreviewData.mapping, + }); } } catch (error) { notifications.toasts.addDanger( @@ -247,6 +230,21 @@ export const DataImporterPluginApp = ({ }, }) ); + + if (createMode) { + try { + const catIndicesResponse = await catIndices({ http, dataSourceId }); + setIndexOptions(catIndicesResponse.indices.map((index: string) => ({ label: index }))); + setCreateMode(false); + } catch (error) { + notifications.toasts.addDanger( + i18n.translate('dataImporter.indicesFetchError', { + defaultMessage: `Failed to fetch indices: {error}`, + values: { error }, + }) + ); + } + } } else { const errorMessage = response ? `: ${response.message.message}` : ''; notifications.toasts.addDanger( @@ -258,6 +256,24 @@ export const DataImporterPluginApp = ({ } }; + useEffect(() => { + async function fetchIndices() { + try { + const response = await catIndices({ http, dataSourceId }); + setIndexOptions(response.indices.map((index: string) => ({ label: index }))); + } catch (error) { + notifications.toasts.addDanger( + i18n.translate('dataImporter.indicesFetchError', { + defaultMessage: `Failed to fetch indices: {error}`, + values: { error }, + }) + ); + } + } + + fetchIndices(); + }, [http, dataSourceId, notifications.toasts, filePreviewData]); + useEffect(() => { setDisableImport(shouldDisableImportButton()); setShowDelimiterChoice(shouldShowDelimiter()); @@ -295,7 +311,7 @@ export const DataImporterPluginApp = ({ savedObjects: savedObjects.client, notifications, onSelectedDataSources: onDataSourceSelect, - onManageDataSource: () => {}, // Add a proper handler if needed + onManageDataSource: () => {}, }} onManageDataSource={() => {}} /> @@ -319,7 +335,12 @@ export const DataImporterPluginApp = ({ } function shouldShowDelimiter() { - return importType === IMPORT_CHOICE_FILE && dataType === CSV_FILE_TYPE; + return ( + (inputFile && + importType === IMPORT_CHOICE_FILE && + extname(inputFile.name) === `.${CSV_FILE_TYPE}`) || + (importType === IMPORT_CHOICE_TEXT && dataType === CSV_FILE_TYPE) + ); } const loadMoreRows = () => { @@ -378,19 +399,17 @@ export const DataImporterPluginApp = ({ /> + {showDelimiterChoice && ( + + )} {importType === IMPORT_CHOICE_FILE && ( - <> - {showDelimiterChoice && ( - - )} - - + )} {importType === IMPORT_CHOICE_FILE && ( diff --git a/src/plugins/data_importer/public/lib/cat_indices.test.ts b/src/plugins/data_importer/public/lib/cat_indices.test.ts new file mode 100644 index 000000000000..14e3234ae108 --- /dev/null +++ b/src/plugins/data_importer/public/lib/cat_indices.test.ts @@ -0,0 +1,35 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { httpServiceMock } from '../../../../core/public/mocks'; +import { catIndices, CatIndicesProps } from './cat_indices'; + +describe('catIndices()', () => { + const httpMock = httpServiceMock.createStartContract(); + const mockIndexNames = ['foo', 'bar']; + httpMock.get.mockResolvedValue({ + indices: mockIndexNames, + }); + + it.each([ + { + http: httpMock, + dataSourceId: undefined, + }, + { + http: httpMock, + dataSourceId: 'data-source-id', + }, + ])( + 'should call /api/data_importer/_cat_indices with the correct args', + async ({ http, dataSourceId }) => { + const response = await catIndices({ http, dataSourceId }); + expect(response.indices).toMatchObject([...mockIndexNames]); + expect(http.get).toBeCalledWith('/api/data_importer/_cat_indices', { + ...(dataSourceId && { query: { dataSource: dataSourceId } }), + }); + } + ); +}); diff --git a/src/plugins/data_importer/public/lib/cat_indices.ts b/src/plugins/data_importer/public/lib/cat_indices.ts new file mode 100644 index 000000000000..f2dc4849afe7 --- /dev/null +++ b/src/plugins/data_importer/public/lib/cat_indices.ts @@ -0,0 +1,17 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { HttpStart } from 'opensearch-dashboards/public'; +import { CatIndicesResponse } from '../types'; + +export interface CatIndicesProps { + http: HttpStart; + dataSourceId?: string; +} + +export async function catIndices({ http, dataSourceId }: CatIndicesProps) { + const query = dataSourceId ? { dataSource: dataSourceId } : undefined; + return http.get('/api/data_importer/_cat_indices', { query }); +} diff --git a/src/plugins/data_importer/public/lib/import_file.test.ts b/src/plugins/data_importer/public/lib/import_file.test.ts index 2c1677e350a4..3951f1687fb6 100644 --- a/src/plugins/data_importer/public/lib/import_file.test.ts +++ b/src/plugins/data_importer/public/lib/import_file.test.ts @@ -4,7 +4,7 @@ */ import { httpServiceMock } from '../../../../core/public/mocks'; -import { importFile } from './import_file'; +import { importFile, ImportFileProps } from './import_file'; describe('importFile()', () => { const httpMock = httpServiceMock.createStartContract(); @@ -23,56 +23,100 @@ describe('importFile()', () => { httpMock.post.mockClear(); }); - it.each([ + it.each([ { + http: httpMock, file: new File([''], 'test.csv'), indexName: 'foo', delimiter: ';', + createMode: true, + fileExtension: '.csv', + mapping: { foo: 'bar' }, selectedDataSourceId: undefined, }, { + http: httpMock, file: new File([''], 'mds.csv'), indexName: 'bar', delimiter: ',', + createMode: true, + fileExtension: '.csv', + mapping: { foo: 'bar' }, selectedDataSourceId: 'datasource-csv', }, { + http: httpMock, file: new File([''], 'test.ndjson'), indexName: 'bar', - selectedDataSourceId: undefined, + createMode: false, delimiter: undefined, + fileExtension: '.ndjson', + selectedDataSourceId: undefined, }, { + http: httpMock, file: new File([''], 'mds.ndjson'), indexName: 'bar', + createMode: true, + fileExtension: '.ndjson', selectedDataSourceId: 'datasource-ndjson', + mapping: { foo: 'bar' }, delimiter: undefined, }, { + http: httpMock, file: new File([''], 'test.json'), indexName: 'baz', - selectedDataSourceId: undefined, + createMode: false, + fileExtension: '.json', delimiter: undefined, + selectedDataSourceId: undefined, }, { + http: httpMock, file: new File([''], 'mds.json'), indexName: 'qux', - selectedDataSourceId: 'datasource-json', + createMode: false, + fileExtension: '.json', delimiter: undefined, + selectedDataSourceId: 'datasource-json', }, ])( 'should call /api/data_importer/_import_file with the correct args', - async ({ file, indexName, delimiter, selectedDataSourceId }) => { - const response = await importFile(httpMock, file, indexName, delimiter, selectedDataSourceId); + async ({ + http, + file, + indexName, + createMode, + fileExtension, + delimiter, + selectedDataSourceId, + mapping, + }) => { + const response = await importFile({ + http, + file, + createMode, + fileExtension, + indexName, + delimiter, + selectedDataSourceId, + mapping, + }); expect(response.success).toBe(true); expect(response.message.total).toBe(5); const formData = new FormData(); formData.append('file', file); + if (mapping) { + formData.append('mapping', JSON.stringify(mapping)); + } expect(httpMock.post).toBeCalledWith('/api/data_importer/_import_file', { query: { indexName, delimiter, + createMode, + fileExtension, ...(selectedDataSourceId && { dataSource: selectedDataSourceId }), }, headers: { diff --git a/src/plugins/data_importer/public/lib/import_file.ts b/src/plugins/data_importer/public/lib/import_file.ts index 8b59f769e166..aaec6abeab65 100644 --- a/src/plugins/data_importer/public/lib/import_file.ts +++ b/src/plugins/data_importer/public/lib/import_file.ts @@ -6,17 +6,33 @@ import { HttpStart } from '../../../../core/public'; import { ImportResponse } from '../types'; -export async function importFile( - http: HttpStart, - file: File, - indexName: string, - createMode: boolean, - fileExtension: string, - delimiter?: string, - selectedDataSourceId?: string -) { +export interface ImportFileProps { + http: HttpStart; + file: File; + indexName: string; + createMode: boolean; + fileExtension: string; + delimiter?: string; + selectedDataSourceId?: string; + mapping?: Record; +} + +export async function importFile({ + http, + file, + indexName, + createMode, + fileExtension, + delimiter, + selectedDataSourceId, + mapping, +}: ImportFileProps) { const formData = new FormData(); formData.append('file', file); + if (mapping) { + formData.append('mapping', JSON.stringify(mapping)); + } + const query = { indexName, createMode, diff --git a/src/plugins/data_importer/public/lib/import_text.test.ts b/src/plugins/data_importer/public/lib/import_text.test.ts index 17916985905c..2b040b42731b 100644 --- a/src/plugins/data_importer/public/lib/import_text.test.ts +++ b/src/plugins/data_importer/public/lib/import_text.test.ts @@ -4,15 +4,7 @@ */ import { httpServiceMock } from '../../../../core/public/mocks'; -import { importText } from './import_text'; - -interface ImportTextTestCaseFormat { - text: string; - textFormat: string; - indexName: string; - delimiter?: string; - selectedDataSourceId?: string; -} +import { importText, ImportTextProps } from './import_text'; describe('importText()', () => { const httpMock = httpServiceMock.createStartContract(); @@ -31,60 +23,86 @@ describe('importText()', () => { httpMock.post.mockClear(); }); - it.each([ + it.each([ { + http: httpMock, text: `{"foo":"bar"}\n{"baz":"qux"}`, textFormat: 'ndjson', indexName: 'test-ndjson', + createMode: true, + mapping: { foo: 'bar' }, delimiter: undefined, selectedDataSourceId: undefined, }, { + http: httpMock, text: `{"Product ID":7631,"SKU":"HEH-9133","Name":"On Cloud Nine Pillow","Product URL":"https://www.domain.com/product/heh-9133","Price":24.99,"Retail Price":24.99,"Thumbnail URL":"https://www.domain.com/images/heh-9133_600x600.png","Search Keywords":"lorem, ipsum, dolor, ...","Description":"Sociosqu facilisis duis ...","Category":"Home>Home Decor>Pillows|Back In Stock","Category ID":"298|511","Brand":"FabDecor","Child SKU":"","Child Price":"","Color":"White","Color Family":"White","Color Swatches":"","Size":"","Shoe Size":"","Pants Size":"","Occassion":"","Season":"","Badges":"","Rating Avg":4.2,"Rating Count":8,"Inventory Count":21,"Date Created":"2018-03-03 17:41:13"}\n{"Product ID":7615,"SKU":"HEH-2245","Name":"Simply Sweet Blouse","Product URL":"https://www.domain.com/product/heh-2245","Price":42,"Retail Price":59.95,"Thumbnail URL":"https://www.domain.com/images/heh-2245_600x600.png","Search Keywords":"lorem, ipsum, dolor, ...","Description":"Sociosqu facilisis duis ...","Category":"Clothing>Tops>Blouses|Clearance|Tops On Sale","Category ID":"285|512|604","Brand":"Entity Apparel","Child SKU":"HEH-2245-RSWD-SM|HEH-2245-RSWD-MD|HEH-2245-THGR-SM|EH-2245-THGR-MD|HEH-2245-DKCH-SM|HEH-2245-DKCH-MD","Child Price":"42|59.99","Color":"Rosewood|Thyme Green|Dark Charcoal","Color Family":"Red|Green|Grey","Color Swatches":"[{\"color\":\"Rosewood\", \"family\":\"Red\", \"swatch_hex\":\"#65000b\", \"thumbnail\":\"/images/heh-2245-rswd-sm_600x600.png\", \"price\":42}, {\"color\":\"Thyme Green\", \"family\":\"Green\", \"swatch_img\":\"/swatches/thyme_green.png\", \"thumbnail\":\"/images/heh-2245-thgr-sm_600x600.png\", \"price\":59.99}, {\"color\":\"Dark Charcoal\", \"family\":\"Grey\", \"swatch_hex\":\"#36454f\", \"thumbnail\":\"/images/heh-2245-dkch-sm_600x600.png\", \"price\":59.99}]","Size":"Small|Medium","Shoe Size":"","Pants Size":"","Occassion":"","Season":"Summer|Spring","Badges":"Exclusive|Clearance","Rating Avg":4.5,"Rating Count":10,"Inventory Count":8,"Date Created":"2018-03-20 22:24:21"}`, textFormat: 'ndjson', indexName: 'mds-ndjson', delimiter: undefined, selectedDataSourceId: 'datasource-ndjson', + createMode: false, }, { + http: httpMock, text: `field1;field2;field3\n111;Test CSV;"I am awesome"\n852;`, textFormat: 'csv', indexName: 'test-csv', delimiter: ';', selectedDataSourceId: undefined, + createMode: false, }, { + http: httpMock, text: `field1|field2|field3|field4\nMary Jane,26,TX,\nJohn Smith,22,WA,john@example.com`, textFormat: 'csv', indexName: 'mds-csv', delimiter: '|', selectedDataSourceId: 'datasource-csv', + createMode: true, + mapping: { foo: 'bar' }, }, { + http: httpMock, text: `{"name":"John", "age":30, "car":null}`, textFormat: 'json', indexName: 'test-json', delimiter: undefined, selectedDataSourceId: undefined, + createMode: true, + mapping: { foo: 'bar' }, }, { + http: httpMock, text: `{"name":"JohnDoe","age":30,"isStudent":false,"skills":["JavaScript","Python","HTML","CSS"],"address":{"street":"123ElmStreet","city":"Springfield","state":"IL","postalCode":"62704"},"hobbies":[{"name":"Photography","experience":"Intermediate"},{"name":"Hiking","experience":"Beginner"}]}`, textFormat: 'json', indexName: 'mds-json', delimiter: undefined, selectedDataSourceId: 'datasource-json', + createMode: false, }, ])( 'should call /api/data_importer/_import_text with the correct args for a $textFormat file', - async ({ text, textFormat, indexName, delimiter, selectedDataSourceId }) => { - const response = await importText( - httpMock, + async ({ + http, + text, + textFormat, + indexName, + delimiter, + createMode, + selectedDataSourceId, + mapping, + }) => { + const response = await importText({ + http, text, textFormat, indexName, + createMode, delimiter, - selectedDataSourceId - ); + selectedDataSourceId, + mapping, + }); expect(response.success).toBe(true); expect(response.message.total).toBe(5); @@ -92,10 +110,11 @@ describe('importText()', () => { query: { indexName, delimiter, + createMode, fileType: textFormat, ...(selectedDataSourceId && { dataSource: selectedDataSourceId }), }, - body: JSON.stringify({ text }), + body: JSON.stringify({ text, ...(mapping && { mapping: JSON.stringify(mapping) }) }), }); } ); diff --git a/src/plugins/data_importer/public/lib/import_text.ts b/src/plugins/data_importer/public/lib/import_text.ts index 316c8eb39ade..4637e0daf76a 100644 --- a/src/plugins/data_importer/public/lib/import_text.ts +++ b/src/plugins/data_importer/public/lib/import_text.ts @@ -6,23 +6,37 @@ import { HttpStart } from '../../../../core/public'; import { ImportResponse } from '../types'; -export async function importText( - http: HttpStart, - text: string, - textFormat: string, - indexName: string, - delimiter?: string, - selectedDataSourceId?: string -) { +export interface ImportTextProps { + http: HttpStart; + text: string; + textFormat: string; + indexName: string; + createMode: boolean; + delimiter?: string; + selectedDataSourceId?: string; + mapping?: Record; +} + +export async function importText({ + http, + text, + textFormat, + indexName, + createMode, + delimiter, + selectedDataSourceId, + mapping, +}: ImportTextProps) { const query = { indexName, fileType: textFormat, + createMode, ...(selectedDataSourceId && { dataSource: selectedDataSourceId }), delimiter, }; return await http.post('/api/data_importer/_import_text', { - body: JSON.stringify({ text }), + body: JSON.stringify({ text, ...(mapping && { mapping: JSON.stringify(mapping) }) }), query, }); } diff --git a/src/plugins/data_importer/public/lib/preview.ts b/src/plugins/data_importer/public/lib/preview.ts index 8193fe549b9b..d75839a4ca89 100644 --- a/src/plugins/data_importer/public/lib/preview.ts +++ b/src/plugins/data_importer/public/lib/preview.ts @@ -12,6 +12,7 @@ export async function previewFile( createMode: boolean, fileExtension: string, indexName: string, + previewCount: number, delimiter?: string, selectedDataSourceId?: string ) { @@ -21,6 +22,7 @@ export async function previewFile( indexName, fileExtension, createMode, + previewCount, ...(selectedDataSourceId && { dataSource: selectedDataSourceId }), delimiter, }; diff --git a/src/plugins/data_importer/public/types.ts b/src/plugins/data_importer/public/types.ts index bafa7e5f5e36..7c39b162ef2f 100644 --- a/src/plugins/data_importer/public/types.ts +++ b/src/plugins/data_importer/public/types.ts @@ -34,3 +34,7 @@ export interface PreviewResponse { documents: Array>; existingMapping?: Record; } + +export interface CatIndicesResponse { + indices: string[]; +} diff --git a/src/plugins/data_importer/server/routes/cat_indices.ts b/src/plugins/data_importer/server/routes/cat_indices.ts index b1d7768b6789..170d3ef8766e 100644 --- a/src/plugins/data_importer/server/routes/cat_indices.ts +++ b/src/plugins/data_importer/server/routes/cat_indices.ts @@ -6,6 +6,7 @@ import { IRouter } from 'src/core/server'; import { schema, TypeOf } from '@osd/config-schema'; import _ from 'lodash'; +import { CatIndicesIndicesRecord } from '@opensearch-project/opensearch/api/types'; import { configSchema } from '../../config'; import { decideClient } from '../utils/util'; @@ -24,11 +25,7 @@ export function catIndicesRoute( }, }, async (context, request, response) => { - const client = await decideClient( - dataSourceEnabled, - context, - (request.query as { dataSource?: string }).dataSource - ); + const client = await decideClient(dataSourceEnabled, context, request.query.dataSource); if (!!!client) { return response.notFound({ body: 'Data source is not enabled or does not exist', @@ -41,7 +38,11 @@ export function catIndicesRoute( }); return response.ok({ body: { - indices: indices.body.map((index: { index?: string }) => index.index || 'unknown'), + indices: indices.body + .filter((index: CatIndicesIndicesRecord) => { + return !index.index?.startsWith('.'); + }) + .map((index: CatIndicesIndicesRecord) => index.index || 'unknown'), }, }); } catch (e) { diff --git a/src/plugins/data_importer/server/routes/import_file.ts b/src/plugins/data_importer/server/routes/import_file.ts index 1e2597a206ea..a3c36e873ebe 100644 --- a/src/plugins/data_importer/server/routes/import_file.ts +++ b/src/plugins/data_importer/server/routes/import_file.ts @@ -69,23 +69,28 @@ export function importFileRoute( }); } - if (!request.query.createMode) { - try { - const indexExists = await client.indices.exists({ - index: request.query.indexName, - }); + try { + const indexExists = await client.indices.exists({ + index: request.query.indexName, + }); - if (!indexExists.body) { - return response.notFound({ - body: `Index ${request.query.indexName} does not exist`, - }); - } - } catch (e) { - return response.internalError({ - body: `Error checking if index exists: ${e}`, + if (!request.query.createMode && !indexExists.body) { + return response.notFound({ + body: `Index ${request.query.indexName} does not exist`, }); } - } else { + if (request.query.createMode && indexExists.body) { + return response.badRequest({ + body: `Index ${request.query.indexName} already exists`, + }); + } + } catch (e) { + return response.internalError({ + body: `Error checking if index exists: ${e}`, + }); + } + + if (request.query.createMode) { try { const mapping = request.body.mapping ? JSON.parse(request.body.mapping) : {}; await client.indices.create({ diff --git a/src/plugins/data_importer/server/routes/import_text.ts b/src/plugins/data_importer/server/routes/import_text.ts index a5f01c2cbff6..94ab3cccabc0 100644 --- a/src/plugins/data_importer/server/routes/import_text.ts +++ b/src/plugins/data_importer/server/routes/import_text.ts @@ -28,7 +28,8 @@ export function importTextRoute( } }, }), - indexName: schema.string(), + indexName: schema.string({ minLength: 1 }), + createMode: schema.boolean(), delimiter: schema.maybe( schema.string({ validate(value: string) { @@ -42,6 +43,7 @@ export function importTextRoute( }), body: schema.object({ text: schema.string({ minLength: 1, maxLength: config.maxTextCount }), + mapping: schema.maybe(schema.string({ minLength: 1 })), }), }, }, @@ -66,17 +68,38 @@ export function importTextRoute( index: request.query.indexName, }); - if (!indexExists.body) { + if (!request.query.createMode && !indexExists.body) { return response.notFound({ body: `Index ${request.query.indexName} does not exist`, }); } + if (request.query.createMode && indexExists.body) { + return response.badRequest({ + body: `Index ${request.query.indexName} already exists`, + }); + } } catch (e) { return response.internalError({ body: `Error checking if index exists: ${e}`, }); } + if (request.query.createMode) { + try { + const mapping = request.body.mapping ? JSON.parse(request.body.mapping) : {}; + await client.indices.create({ + index: request.query.indexName, + body: { + mappings: mapping, + }, + }); + } catch (e) { + return response.internalError({ + body: `Error creating index: ${e}`, + }); + } + } + let isValid; try { isValid = await parser.validateText(request.body.text, { diff --git a/src/plugins/data_importer/server/routes/preview.ts b/src/plugins/data_importer/server/routes/preview.ts index 11f7be60fc09..7794c4022d86 100644 --- a/src/plugins/data_importer/server/routes/preview.ts +++ b/src/plugins/data_importer/server/routes/preview.ts @@ -43,6 +43,7 @@ export function previewRoute( }, }) ), + previewCount: schema.number({ min: 1, max: config.filePreviewDocumentsCount }), }), body: schema.object({ file: schema.stream(), @@ -88,10 +89,10 @@ export function previewRoute( const file = request.body.file as FileStream; const documents = ( - await parser.parseFile(file, config.filePreviewDocumentsCount, { + await parser.parseFile(file, request.query.previewCount, { delimiter: request.query.delimiter, }) - ).slice(0, config.filePreviewDocumentsCount); + ).slice(0, request.query.previewCount); try { // Ensure OpenSearch can handle the deeply nested objects From 14ea06930adb408a848bb855daf21bee306a9d13 Mon Sep 17 00:00:00 2001 From: "opensearch-changeset-bot[bot]" <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com> Date: Thu, 27 Feb 2025 18:18:09 +0000 Subject: [PATCH 2/2] Changeset file for PR #9462 created/updated --- changelogs/fragments/9462.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changelogs/fragments/9462.yml diff --git a/changelogs/fragments/9462.yml b/changelogs/fragments/9462.yml new file mode 100644 index 000000000000..35388d272da3 --- /dev/null +++ b/changelogs/fragments/9462.yml @@ -0,0 +1,2 @@ +fix: +- Remove system indexes from the index name selector ([#9462](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/9462)) \ No newline at end of file