diff --git a/common/constants/shared.ts b/common/constants/shared.ts index 892fa9fc0..73104419e 100644 --- a/common/constants/shared.ts +++ b/common/constants/shared.ts @@ -71,6 +71,7 @@ export const observabilityDataConnectionsTitle = 'Data sources'; export const observabilityDataConnectionsPluginOrder = 9030; export const queryWorkbenchPluginID = 'opensearch-query-workbench'; +export const queryWorkbenchPluginCheck = 'plugin:queryWorkbenchDashboards'; // Shared Constants export const SQL_DOCUMENTATION_URL = 'https://opensearch.org/docs/latest/search-plugins/sql/index/'; diff --git a/common/types/data_connections.ts b/common/types/data_connections.ts index d93ba8bf2..b5f2a4ec8 100644 --- a/common/types/data_connections.ts +++ b/common/types/data_connections.ts @@ -4,6 +4,7 @@ */ import { EuiComboBoxOptionOption } from '@elastic/eui'; +import { DirectQueryLoadingStatus } from './explorer'; export type AccelerationStatus = 'ACTIVE' | 'INACTIVE'; @@ -233,3 +234,9 @@ export interface CreateAccelerationForm { refreshIntervalOptions: RefreshIntervalType; formErrors: FormErrorsType; } + +export interface LoadCachehookOutput { + loadStatus: DirectQueryLoadingStatus; + startLoading: (dataSourceName: string, databaseName?: string, tableName?: string) => void; + stopLoading: () => void; +} diff --git a/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/create/__tests__/__snapshots__/create_acceleration.test.tsx.snap b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/create/__tests__/__snapshots__/create_acceleration.test.tsx.snap index 15533e866..9a0077790 100644 --- a/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/create/__tests__/__snapshots__/create_acceleration.test.tsx.snap +++ b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/create/__tests__/__snapshots__/create_acceleration.test.tsx.snap @@ -814,7 +814,7 @@ Array [ - No items found + Please add fields @@ -824,31 +824,57 @@ Array [
- + + + Add fields + + + +
+
+ +
+
@@ -1188,36 +1214,7 @@ Array [
- -
+ />
@@ -1713,6 +1712,7 @@ Array [ @@ -2270,7 +2271,7 @@ Array [ - No items found + Please add fields
@@ -2280,57 +2281,93 @@ Array [ ,
- + + + Add fields + + + +
+
+ +
+
- + , + "", + ]
, ], @@ -2691,43 +2728,7 @@ Array [
- -
+ />
+ Promise.resolve({ + json: () => + Promise.resolve({ + status: { + statuses: [{ id: queryWorkbenchPluginCheck }], + }, + }), + }) +); + describe('Create acceleration flyout components', () => { configure({ adapter: new Adapter() }); diff --git a/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/create/__tests__/utils.test.tsx b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/create/__tests__/utils.test.tsx index f579cc8d2..f60b78233 100644 --- a/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/create/__tests__/utils.test.tsx +++ b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/create/__tests__/utils.test.tsx @@ -109,13 +109,13 @@ describe('validateReplicaCount', () => { describe('validateRefreshInterval', () => { it('should return an array with an error message when refreshType is "interval" and refreshWindow is less than 1', () => { - expect(validateRefreshInterval('interval', 0)).toEqual([ + expect(validateRefreshInterval('autoInterval', 0)).toEqual([ 'refresh window should be greater than 0', ]); - expect(validateRefreshInterval('interval', -1)).toEqual([ + expect(validateRefreshInterval('autoInterval', -1)).toEqual([ 'refresh window should be greater than 0', ]); - expect(validateRefreshInterval('interval', -10)).toEqual([ + expect(validateRefreshInterval('autoInterval', -10)).toEqual([ 'refresh window should be greater than 0', ]); }); @@ -123,8 +123,10 @@ describe('validateRefreshInterval', () => { it('should return an empty array when refreshType is not "interval" or when refreshWindow is greater than or equal to 1', () => { expect(validateRefreshInterval('auto', 0)).toEqual([]); expect(validateRefreshInterval('auto', 1)).toEqual([]); - expect(validateRefreshInterval('interval', 1)).toEqual([]); - expect(validateRefreshInterval('auto', 5)).toEqual([]); + expect(validateRefreshInterval('autoInterval', 1)).toEqual([]); + expect(validateRefreshInterval('autoInterval', 5)).toEqual([]); + expect(validateRefreshInterval('manual', 0)).toEqual([]); + expect(validateRefreshInterval('manualIncrement', 0)).toEqual([]); }); }); diff --git a/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/create/create_acceleration.tsx b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/create/create_acceleration.tsx index dd8ca4caf..b9f1aa25e 100644 --- a/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/create/create_acceleration.tsx +++ b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/create/create_acceleration.tsx @@ -97,7 +97,11 @@ export const CreateAcceleration = ({ }, }); const [tableFieldsLoading, setTableFieldsLoading] = useState(false); - const { loadStatus, startLoading } = useLoadTableColumnsToCache(); + const { + loadStatus, + startLoading, + stopLoading: stopLoadingTableFields, + } = useLoadTableColumnsToCache(); const loadColumnsToAccelerationForm = (cachedTable: CachedTable) => { const idPrefix = htmlIdGenerator()(); @@ -117,6 +121,7 @@ export const CreateAcceleration = ({ ...accelerationFormData, dataTableFields: [], }); + stopLoadingTableFields(); if (dataTable !== '') { setTableFieldsLoading(true); const cachedTable = CatalogCacheManager.getTable(dataSource, database, dataTable); @@ -179,6 +184,7 @@ export const CreateAcceleration = ({ setAccelerationFormData={setAccelerationFormData} selectedDatasource={selectedDatasource} dataSourcesPreselected={dataSourcesPreselected} + tableFieldsLoading={tableFieldsLoading} /> { - return refreshType !== 'auto' && refreshType !== 'manual' && refreshWindow < 1 + return refreshType === 'autoInterval' && refreshWindow < 1 ? ['refresh window should be greater than 0'] : []; }; diff --git a/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/selectors/__tests__/__snapshots__/preview_sql_defintion.test.tsx.snap b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/selectors/__tests__/__snapshots__/preview_sql_defintion.test.tsx.snap index afecf2109..802f07f9e 100644 --- a/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/selectors/__tests__/__snapshots__/preview_sql_defintion.test.tsx.snap +++ b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/selectors/__tests__/__snapshots__/preview_sql_defintion.test.tsx.snap @@ -90,43 +90,7 @@ exports[`Preview SQL acceleration components renders Preview SQL settings with d
- -
+ />
- -
+ />
- -
+ />
- -
+ />
@@ -261,6 +263,7 @@ Array [ diff --git a/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/selectors/__tests__/preview_sql_defintion.test.tsx b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/selectors/__tests__/preview_sql_defintion.test.tsx index 1ba021367..ce265635b 100644 --- a/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/selectors/__tests__/preview_sql_defintion.test.tsx +++ b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/selectors/__tests__/preview_sql_defintion.test.tsx @@ -8,6 +8,7 @@ import { configure, mount } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; import toJson from 'enzyme-to-json'; import React from 'react'; +import { queryWorkbenchPluginCheck } from '../../../../../../../../../common/constants/shared'; import { CreateAccelerationForm } from '../../../../../../../../../common/types/data_connections'; import { coveringIndexBuilderMock1, @@ -16,6 +17,18 @@ import { } from '../../../../../../../../../test/accelerations'; import { PreviewSQLDefinition } from '../preview_sql_defintion'; +// @ts-ignore +global.fetch = jest.fn(() => + Promise.resolve({ + json: () => + Promise.resolve({ + status: { + statuses: [{ id: queryWorkbenchPluginCheck }], + }, + }), + }) +); + describe('Preview SQL acceleration components', () => { configure({ adapter: new Adapter() }); diff --git a/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/selectors/__tests__/source_selector.test.tsx b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/selectors/__tests__/source_selector.test.tsx index 5a9667911..040b1a4b6 100644 --- a/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/selectors/__tests__/source_selector.test.tsx +++ b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/selectors/__tests__/source_selector.test.tsx @@ -35,6 +35,7 @@ describe('Source selector components', () => { accelerationFormData={accelerationFormData} setAccelerationFormData={setAccelerationFormData} dataSourcesPreselected={false} + tableFieldsLoading={false} /> ); wrapper.update(); @@ -67,6 +68,7 @@ describe('Source selector components', () => { accelerationFormData={accelerationFormData} setAccelerationFormData={setAccelerationFormData} dataSourcesPreselected={true} + tableFieldsLoading={false} /> ); wrapper.update(); diff --git a/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/selectors/preview_sql_defintion.tsx b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/selectors/preview_sql_defintion.tsx index 3170df61d..b0c61b756 100644 --- a/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/selectors/preview_sql_defintion.tsx +++ b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/selectors/preview_sql_defintion.tsx @@ -13,7 +13,13 @@ import { EuiText, } from '@elastic/eui'; import React, { useEffect, useState } from 'react'; +import { + queryWorkbenchPluginCheck, + queryWorkbenchPluginID, +} from '../../../../../../../../common/constants/shared'; import { CreateAccelerationForm } from '../../../../../../../../common/types/data_connections'; +import { coreRefs } from '../../../../../../../framework/core_refs'; +import { useToast } from '../../../../../../common/toast'; import { formValidator, hasError } from '../create/utils'; import { accelerationQueryBuilder } from '../visual_editors/query_builder'; @@ -26,14 +32,22 @@ export const PreviewSQLDefinition = ({ accelerationFormData, setAccelerationFormData, }: PreviewSQLDefinitionProps) => { + const { setToast } = useToast(); const [isPreviewStale, setIsPreviewStale] = useState(false); const [isPreviewTriggered, setIsPreviewTriggered] = useState(false); const [sqlCode, setSQLcode] = useState(''); + const [sqlWorkbenchPLuginExists, setSQLWorkbenchPluginExists] = useState(false); - const onClickPreview = () => { + const checkForErrors = () => { const errors = formValidator(accelerationFormData); if (hasError(errors)) { setAccelerationFormData({ ...accelerationFormData, formErrors: errors }); + return true; + } else return false; + }; + + const onClickPreview = () => { + if (checkForErrors()) { return; } setSQLcode(accelerationQueryBuilder(accelerationFormData)); @@ -41,10 +55,66 @@ export const PreviewSQLDefinition = ({ setIsPreviewTriggered(true); }; + const checkIfSQLWorkbenchPluginIsInstalled = () => { + fetch('../api/status', { + headers: { + 'Content-Type': 'application/json', + 'osd-xsrf': 'true', + 'accept-language': 'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7,zh-TW;q=0.6', + pragma: 'no-cache', + 'sec-fetch-dest': 'empty', + 'sec-fetch-mode': 'cors', + 'sec-fetch-site': 'same-origin', + }, + method: 'GET', + referrerPolicy: 'strict-origin-when-cross-origin', + mode: 'cors', + credentials: 'include', + }) + .then(function (response) { + return response.json(); + }) + .then((data) => { + for (let i = 0; i < data.status.statuses.length; ++i) { + if (data.status.statuses[i].id.includes(queryWorkbenchPluginCheck)) { + setSQLWorkbenchPluginExists(true); + } + } + }) + .catch((error) => { + setToast('Error checking Query Workbench Plugin Installation status.', 'danger'); + console.error(error); + }); + }; + + const openInWorkbench = () => { + if (!checkForErrors()) { + coreRefs?.application!.navigateToApp(queryWorkbenchPluginID, { + path: `#/${accelerationFormData.dataSource}`, + state: { + language: 'sql', + queryToRun: accelerationQueryBuilder(accelerationFormData), + }, + }); + } + }; + + const queryWorkbenchButton = sqlWorkbenchPLuginExists ? ( + + Open in Query Workbench + + ) : ( + <> + ); + useEffect(() => { setIsPreviewStale(true); }, [accelerationFormData]); + useEffect(() => { + checkIfSQLWorkbenchPluginIsInstalled(); + }, []); + return ( <> )} - - - Open in Query Workbench - - + {queryWorkbenchButton} diff --git a/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/selectors/selector_helpers/load_databases.tsx b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/selectors/selector_helpers/load_databases.tsx index c867be1ff..635cf6976 100644 --- a/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/selectors/selector_helpers/load_databases.tsx +++ b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/selectors/selector_helpers/load_databases.tsx @@ -11,11 +11,27 @@ import { useLoadDatabasesToCache } from '../../../../../../../../framework/catal interface SelectorLoadDatabasesProps { dataSourceName: string; loadDatabases: () => void; + loadingComboBoxes: { + dataSource: boolean; + database: boolean; + dataTable: boolean; + }; + setLoadingComboBoxes: React.Dispatch< + React.SetStateAction<{ + dataSource: boolean; + database: boolean; + dataTable: boolean; + }> + >; + tableFieldsLoading: boolean; } export const SelectorLoadDatabases = ({ dataSourceName, loadDatabases, + loadingComboBoxes, + setLoadingComboBoxes, + tableFieldsLoading, }: SelectorLoadDatabasesProps) => { const [isLoading, setIsLoading] = useState(false); const { @@ -42,6 +58,10 @@ export const SelectorLoadDatabases = ({ } }, [loadDatabasesStatus]); + useEffect(() => { + setLoadingComboBoxes({ ...loadingComboBoxes, database: isLoading }); + }, [isLoading]); + return ( <> {isLoading ? ( @@ -52,6 +72,9 @@ export const SelectorLoadDatabases = ({ size="m" display="base" onClick={onClickRefreshDatabases} + isDisabled={ + loadingComboBoxes.database || loadingComboBoxes.dataTable || tableFieldsLoading + } /> )} diff --git a/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/selectors/selector_helpers/load_objects.tsx b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/selectors/selector_helpers/load_objects.tsx index f56ad879f..d687ef6ed 100644 --- a/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/selectors/selector_helpers/load_objects.tsx +++ b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/selectors/selector_helpers/load_objects.tsx @@ -16,12 +16,28 @@ interface SelectorLoadDatabasesProps { dataSourceName: string; databaseName: string; loadTables: () => void; + loadingComboBoxes: { + dataSource: boolean; + database: boolean; + dataTable: boolean; + }; + setLoadingComboBoxes: React.Dispatch< + React.SetStateAction<{ + dataSource: boolean; + database: boolean; + dataTable: boolean; + }> + >; + tableFieldsLoading: boolean; } export const SelectorLoadObjects = ({ dataSourceName, databaseName, loadTables, + loadingComboBoxes, + setLoadingComboBoxes, + tableFieldsLoading, }: SelectorLoadDatabasesProps) => { const { setToast } = useToast(); const [isLoading, setIsLoading] = useState({ @@ -77,6 +93,10 @@ export const SelectorLoadObjects = ({ } }, [loadAccelerationsStatus]); + useEffect(() => { + setLoadingComboBoxes({ ...loadingComboBoxes, dataTable: isEitherLoading }); + }, [isEitherLoading]); + return ( <> {isEitherLoading ? ( @@ -87,6 +107,9 @@ export const SelectorLoadObjects = ({ size="m" display="base" onClick={onClickRefreshDatabases} + isDisabled={ + loadingComboBoxes.database || loadingComboBoxes.dataTable || tableFieldsLoading + } /> )} diff --git a/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/selectors/source_selector.tsx b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/selectors/source_selector.tsx index 29866ba0d..f1dac3eac 100644 --- a/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/selectors/source_selector.tsx +++ b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/selectors/source_selector.tsx @@ -35,6 +35,7 @@ interface AccelerationDataSourceSelectorProps { setAccelerationFormData: React.Dispatch>; selectedDatasource: string; dataSourcesPreselected: boolean; + tableFieldsLoading: boolean; } export const AccelerationDataSourceSelector = ({ @@ -43,6 +44,7 @@ export const AccelerationDataSourceSelector = ({ setAccelerationFormData, selectedDatasource, dataSourcesPreselected, + tableFieldsLoading, }: AccelerationDataSourceSelectorProps) => { const { setToast } = useToast(); const [databases, setDatabases] = useState>>([]); @@ -89,7 +91,6 @@ export const AccelerationDataSourceSelector = ({ }; const loadDatabases = () => { - setLoadingComboBoxes({ ...loadingComboBoxes, database: true }); const dsCache = CatalogCacheManager.getOrCreateDataSource(accelerationFormData.dataSource); if (dsCache.status === CachedDataSourceStatus.Updated && dsCache.databases.length > 0) { @@ -106,11 +107,9 @@ export const AccelerationDataSourceSelector = ({ setTables([]); setSelectedTable([]); setAccelerationFormData({ ...accelerationFormData, database: '', dataTable: '' }); - setLoadingComboBoxes({ ...loadingComboBoxes, database: false }); }; const loadTables = () => { - setLoadingComboBoxes({ ...loadingComboBoxes, dataTable: true }); if (selectedDatabase.length > 0) { const dbCache = CatalogCacheManager.getDatabase( accelerationFormData.dataSource, @@ -127,7 +126,6 @@ export const AccelerationDataSourceSelector = ({ } setSelectedTable([]); setAccelerationFormData({ ...accelerationFormData, dataTable: '' }); - setLoadingComboBoxes({ ...loadingComboBoxes, dataTable: false }); } }; @@ -212,20 +210,29 @@ export const AccelerationDataSourceSelector = ({ }} isClearable={false} isInvalid={hasError(accelerationFormData.formErrors, 'databaseError')} - isLoading={loadingComboBoxes.database} + isDisabled={ + loadingComboBoxes.database || loadingComboBoxes.dataTable || tableFieldsLoading + } /> @@ -251,7 +258,9 @@ export const AccelerationDataSourceSelector = ({ }} isClearable={false} isInvalid={hasError(accelerationFormData.formErrors, 'dataTableError')} - isLoading={loadingComboBoxes.dataTable} + isDisabled={ + loadingComboBoxes.database || loadingComboBoxes.dataTable || tableFieldsLoading + } /> @@ -259,6 +268,9 @@ export const AccelerationDataSourceSelector = ({ dataSourceName={accelerationFormData.dataSource} databaseName={accelerationFormData.database} loadTables={loadTables} + loadingComboBoxes={loadingComboBoxes} + setLoadingComboBoxes={setLoadingComboBoxes} + tableFieldsLoading={tableFieldsLoading} /> diff --git a/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/visual_editors/__tests__/__snapshots__/query_visual_editor.test.tsx.snap b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/visual_editors/__tests__/__snapshots__/query_visual_editor.test.tsx.snap index b75e89152..85e73b484 100644 --- a/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/visual_editors/__tests__/__snapshots__/query_visual_editor.test.tsx.snap +++ b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/visual_editors/__tests__/__snapshots__/query_visual_editor.test.tsx.snap @@ -241,7 +241,7 @@ Array [ - No items found + Please add fields
@@ -251,57 +251,93 @@ Array [ ,
- + + + Add fields + + + +
+
+ +
+
- + , + "", + ]
, ], @@ -601,6 +637,7 @@ Array [ > ,
- + + + Add fields + + + +
+
+ +
+
- + , + "", + ]
, ], diff --git a/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/visual_editors/skipping_index/__tests__/__snapshots__/generate_fields.test.tsx.snap b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/visual_editors/skipping_index/__tests__/__snapshots__/generate_fields.test.tsx.snap new file mode 100644 index 000000000..decdb88aa --- /dev/null +++ b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/visual_editors/skipping_index/__tests__/__snapshots__/generate_fields.test.tsx.snap @@ -0,0 +1,28 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Generate fields in skipping index Generate fields in skipping index with default options 1`] = ` +Array [ + , + "", +] +`; diff --git a/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/visual_editors/skipping_index/__tests__/__snapshots__/skipping_index_builder.test.tsx.snap b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/visual_editors/skipping_index/__tests__/__snapshots__/skipping_index_builder.test.tsx.snap index a00e12b81..d9b73eabb 100644 --- a/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/visual_editors/skipping_index/__tests__/__snapshots__/skipping_index_builder.test.tsx.snap +++ b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/visual_editors/skipping_index/__tests__/__snapshots__/skipping_index_builder.test.tsx.snap @@ -151,7 +151,7 @@ Array [ - No items found + Please add fields @@ -161,57 +161,93 @@ Array [ ,
- + + + Add fields + + + +
+
+ +
+
- + , + "", + ]
, ] @@ -355,6 +391,7 @@ Array [ > ,
- + + + Add fields + + + +
+
+ +
+
- + , + "", + ]
, ] diff --git a/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/visual_editors/skipping_index/__tests__/generate_fields.test.tsx b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/visual_editors/skipping_index/__tests__/generate_fields.test.tsx new file mode 100644 index 000000000..7a5b9ccb1 --- /dev/null +++ b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/visual_editors/skipping_index/__tests__/generate_fields.test.tsx @@ -0,0 +1,43 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { waitFor } from '@testing-library/dom'; +import { configure, mount } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import toJson from 'enzyme-to-json'; +import React from 'react'; +import { createAccelerationEmptyDataMock } from '../../../../../../../../../../test/accelerations'; +import { GenerateFields } from '../generate_fields'; + +describe('Generate fields in skipping index', () => { + configure({ adapter: new Adapter() }); + + it('Generate fields in skipping index with default options', async () => { + const accelerationFormData = createAccelerationEmptyDataMock; + const setAccelerationFormData = jest.fn(); + const wrapper = mount( + + ); + wrapper.update(); + await waitFor(() => { + expect( + toJson(wrapper, { + noKey: false, + mode: 'deep', + }) + ).toMatchSnapshot(); + }); + }); +}); diff --git a/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/visual_editors/skipping_index/generate_fields.tsx b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/visual_editors/skipping_index/generate_fields.tsx new file mode 100644 index 000000000..5eda5b73a --- /dev/null +++ b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/visual_editors/skipping_index/generate_fields.tsx @@ -0,0 +1,125 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { EuiButton, EuiConfirmModal } from '@elastic/eui'; +import producer from 'immer'; +import React, { useEffect, useState } from 'react'; +import { + CreateAccelerationForm, + SkippingIndexRowType, +} from '../../../../../../../../../common/types/data_connections'; +import { + DirectQueryLoadingStatus, + DirectQueryRequest, +} from '../../../../../../../../../common/types/explorer'; +import { + addBackticksIfNeeded, + combineSchemaAndDatarows, +} from '../../../../../../../../../common/utils/shared'; +import { useDirectQuery } from '../../../../../../../../framework/datasources/direct_query_hook'; +import { validateSkippingIndexData } from '../../create/utils'; + +interface GenerateFieldsProps { + accelerationFormData: CreateAccelerationForm; + setAccelerationFormData: React.Dispatch>; + isSkippingtableLoading: boolean; + setIsSkippingtableLoading: React.Dispatch; +} + +export const GenerateFields = ({ + accelerationFormData, + setAccelerationFormData, + isSkippingtableLoading, + setIsSkippingtableLoading, +}: GenerateFieldsProps) => { + const [isGenerateRun, setIsGenerateRun] = useState(false); + const { loadStatus, startLoading, stopLoading: _stopLoading, pollingResult } = useDirectQuery(); + const [replaceDefinitionModal, setReplaceDefinitionModal] = useState(<>); + + const mapToDataTableFields = (fieldName: string) => { + return accelerationFormData.dataTableFields.find((field) => field.fieldName === fieldName); + }; + + const loadSkippingIndexDefinition = () => { + const combinedData = combineSchemaAndDatarows(pollingResult.schema, pollingResult.datarows); + const skippingIndexRows = combinedData.map((field: any) => { + return { + ...mapToDataTableFields(field.column_name), + accelerationMethod: field.skipping_type.split(' ')[0], + } as SkippingIndexRowType; + }); + setAccelerationFormData( + producer((accData) => { + accData.skippingIndexQueryData = skippingIndexRows; + accData.formErrors.skippingIndexError = validateSkippingIndexData( + accData.accelerationIndexType, + skippingIndexRows + ); + }) + ); + }; + + useEffect(() => { + const status = loadStatus.toLowerCase(); + if (status === DirectQueryLoadingStatus.SUCCESS) { + loadSkippingIndexDefinition(); + setIsSkippingtableLoading(false); + } else if ( + status === DirectQueryLoadingStatus.FAILED || + status === DirectQueryLoadingStatus.CANCELED + ) { + setIsSkippingtableLoading(false); + } + }, [loadStatus]); + + const runGeneration = () => { + const requestPayload: DirectQueryRequest = { + lang: 'sql', + query: `ANALYZE SKIPPING INDEX ON ${addBackticksIfNeeded( + accelerationFormData.dataSource + )}.${addBackticksIfNeeded(accelerationFormData.database)}.${addBackticksIfNeeded( + accelerationFormData.dataTable + )}`, + datasource: accelerationFormData.dataSource, + }; + startLoading(requestPayload); + setIsSkippingtableLoading(true); + setIsGenerateRun(true); + setReplaceDefinitionModal(<>); + }; + + const replaceModalComponent = ( + setReplaceDefinitionModal(<>)} + onConfirm={runGeneration} + cancelButtonText="Cancel" + confirmButtonText="Replace" + defaultFocusedButton="confirm" + > +

+ Existing definitions will be removed and replaced with auto-generated definitions. Do you + want to continue? +

+
+ ); + + const onClickGenerate = () => { + if (accelerationFormData.skippingIndexQueryData.length > 0) { + setReplaceDefinitionModal(replaceModalComponent); + } else { + runGeneration(); + } + }; + + return ( + <> + + {isGenerateRun ? 'Regenerate' : 'Generate'} + + {replaceDefinitionModal} + + ); +}; diff --git a/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/visual_editors/skipping_index/skipping_index_builder.tsx b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/visual_editors/skipping_index/skipping_index_builder.tsx index ae2a03e14..1669d05c8 100644 --- a/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/visual_editors/skipping_index/skipping_index_builder.tsx +++ b/public/components/datasources/components/manage/accelerations/create_accelerations_flyout/visual_editors/skipping_index/skipping_index_builder.tsx @@ -13,7 +13,6 @@ import { EuiSpacer, EuiText, } from '@elastic/eui'; -import producer from 'immer'; import React, { useEffect, useState } from 'react'; import { SKIPPING_INDEX_ACCELERATION_METHODS } from '../../../../../../../../../common/constants/data_sources'; import { @@ -21,9 +20,9 @@ import { SkippingIndexAccMethodType, SkippingIndexRowType, } from '../../../../../../../../../common/types/data_connections'; -import { validateSkippingIndexData } from '../../create/utils'; import { AddFieldsModal } from './add_fields_modal'; import { DeleteFieldsModal } from './delete_fields_modal'; +import { GenerateFields } from './generate_fields'; interface SkippingIndexBuilderProps { accelerationFormData: CreateAccelerationForm; @@ -40,6 +39,7 @@ export const SkippingIndexBuilder = ({ const [isAddModalVisible, setIsAddModalVisible] = useState(false); const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); + const [isSkippingtableLoading, setIsSkippingtableLoading] = useState(false); let modal; @@ -86,12 +86,14 @@ export const SkippingIndexBuilder = ({ name: 'Field name', sortable: true, truncateText: true, + readOnly: isSkippingtableLoading, }, { field: 'dataType', name: 'Datatype', sortable: true, truncateText: true, + readOnly: isSkippingtableLoading, }, { name: 'Acceleration method', @@ -104,9 +106,11 @@ export const SkippingIndexBuilder = ({ aria-label="Use aria labels when no actual label is in use" /> ), + readOnly: isSkippingtableLoading, }, { name: 'Delete', + readOnly: isSkippingtableLoading, render: (item: SkippingIndexRowType) => { return ( { - if (accelerationFormData.dataTableFields.length > 0) { - const tableRows: SkippingIndexRowType[] = [ - { - ...accelerationFormData.dataTableFields[0], - accelerationMethod: 'PARTITION', - }, - ]; - setAccelerationFormData( - producer((accData) => { - accData.skippingIndexQueryData = tableRows; - accData.formErrors.skippingIndexError = validateSkippingIndexData( - accData.accelerationIndexType, - tableRows - ); - }) - ); - } else { - setAccelerationFormData({ ...accelerationFormData, skippingIndexQueryData: [] }); - } - }, [accelerationFormData.dataTableFields]); - useEffect(() => { setTotalItemCount(accelerationFormData.skippingIndexQueryData.length); }, [accelerationFormData.skippingIndexQueryData]); @@ -176,17 +158,42 @@ export const SkippingIndexBuilder = ({ onChange={({ page }) => onTableChange(page)} hasActions={true} error={accelerationFormData.formErrors.skippingIndexError.join('')} + loading={isSkippingtableLoading} + noItemsMessage={ + isSkippingtableLoading + ? 'Auto-generating skipping index definition.' + : 'Please add fields' + } /> - - - setIsAddModalVisible(true)}> - Add fields - + + + + + setIsAddModalVisible(true)} + isDisabled={isSkippingtableLoading} + > + Add fields + + + + setIsDeleteModalVisible(true)} + isDisabled={isSkippingtableLoading} + color="danger" + > + Bulk delete + + + - setIsDeleteModalVisible(true)} color="danger"> - Bulk delete - + {modal} diff --git a/public/framework/core_refs.ts b/public/framework/core_refs.ts index 275158258..ed378ae1d 100644 --- a/public/framework/core_refs.ts +++ b/public/framework/core_refs.ts @@ -10,6 +10,7 @@ import { CoreStart, HttpStart, IToasts, + OverlayStart, SavedObjectsClientContract, } from '../../../../src/core/public'; import { DashboardStart } from '../../../../src/plugins/dashboard/public'; @@ -30,6 +31,7 @@ class CoreRefs { public summarizeEnabled?: boolean; public dashboard?: DashboardStart; public dashboardProviders?: unknown; + public overlays?: OverlayStart; private constructor() { // ... } diff --git a/public/framework/datasources/direct_query_hook.tsx b/public/framework/datasources/direct_query_hook.tsx index f44c04d8f..f4776385e 100644 --- a/public/framework/datasources/direct_query_hook.tsx +++ b/public/framework/datasources/direct_query_hook.tsx @@ -88,5 +88,5 @@ export const useDirectQuery = () => { } }, [pollingResult, pollingError]); - return { loadStatus, startLoading, stopLoading }; + return { loadStatus, startLoading, stopLoading, pollingResult }; }; diff --git a/public/plugin.tsx b/public/plugin.tsx index 2b8a64f32..6b2b65ccf 100644 --- a/public/plugin.tsx +++ b/public/plugin.tsx @@ -73,6 +73,13 @@ import { } from './embeddable/observability_embeddable'; import { ObservabilityEmbeddableFactoryDefinition } from './embeddable/observability_embeddable_factory'; import { catalogCacheInterceptError } from './framework/catalog_cache/cache_intercept'; +import { + useLoadAccelerationsToCache, + useLoadDatabasesToCache, + useLoadTableColumnsToCache, + useLoadTablesToCache, +} from './framework/catalog_cache/cache_loader'; +import { CatalogCacheManager } from './framework/catalog_cache/cache_manager'; import { coreRefs } from './framework/core_refs'; import { DataSourcePluggable } from './framework/datasource_pluggables/datasource_pluggable'; import { S3DataSource } from './framework/datasources/s3_datasource'; @@ -371,6 +378,7 @@ export class ObservabilityPlugin coreRefs.dashboard = startDeps.dashboard; coreRefs.queryAssistEnabled = this.config.query_assist.enabled; coreRefs.summarizeEnabled = this.config.summarize.enabled; + coreRefs.overlays = core.overlays; const { dataSourceService, dataSourceFactory } = startDeps.data.dataSources; @@ -445,11 +453,21 @@ export class ObservabilityPlugin }; setRenderCreateAccelerationFlyout(renderCreateAccelerationFlyout); + const CatalogCacheManagerInstance = CatalogCacheManager; + const useLoadDatabasesToCacheHook = useLoadDatabasesToCache; + const useLoadTablesToCacheHook = useLoadTablesToCache; + const useLoadTableColumnsToCacheHook = useLoadTableColumnsToCache; + const useLoadAccelerationsToCacheHook = useLoadAccelerationsToCache; // Export so other plugins can use this flyout return { renderAccelerationDetailsFlyout, renderAssociatedObjectsDetailsFlyout, renderCreateAccelerationFlyout, + CatalogCacheManagerInstance, + useLoadDatabasesToCacheHook, + useLoadTablesToCacheHook, + useLoadTableColumnsToCacheHook, + useLoadAccelerationsToCacheHook, }; } diff --git a/public/types.ts b/public/types.ts index fac6d463d..e63805b59 100644 --- a/public/types.ts +++ b/public/types.ts @@ -11,7 +11,12 @@ import { ManagementOverViewPluginSetup } from '../../../src/plugins/management_o import { NavigationPublicPluginStart } from '../../../src/plugins/navigation/public'; import { UiActionsStart } from '../../../src/plugins/ui_actions/public'; import { VisualizationsSetup } from '../../../src/plugins/visualizations/public'; -import { AssociatedObject, CachedAcceleration } from '../common/types/data_connections'; +import { + AssociatedObject, + CachedAcceleration, + LoadCachehookOutput, +} from '../common/types/data_connections'; +import { CatalogCacheManager } from './framework/catalog_cache/cache_manager'; import { AssistantSetup } from './types'; export interface AppPluginStartDependencies { @@ -41,13 +46,26 @@ export interface ObservabilityStart { datasourceName: string, handleRefresh?: () => void ) => void; - renderAssociatedObjectsDetailsFlyout: ( - tableDetail: AssociatedObject, - datasourceName: string + renderAssociatedObjectsDetailsFlyout: ({ + tableDetail, + }: { + tableDetail: AssociatedObject; + }) => void; + renderCreateAccelerationFlyout: ( + dataSource: string, + databaseName?: string, + tableName?: string ) => void; - renderCreateAccelerationFlyout: (selectedDatasource: string) => void; + CatalogCacheManagerInstance: typeof CatalogCacheManager; + useLoadDatabasesToCacheHook: () => LoadCachehookOutput; + useLoadTablesToCacheHook: () => LoadCachehookOutput; + useLoadTableColumnsToCacheHook: () => LoadCachehookOutput; + useLoadAccelerationsToCacheHook: () => LoadCachehookOutput; } +export type CatalogCacheManagerType = typeof CatalogCacheManager; +export type LoadCachehookOutputType = LoadCachehookOutput; + /** * Introduce a compile dependency on dashboards-assistant * as observerability need some types from the plugin.