From ec69adbe47f9af9302fd10af9f171dd5b15ee2b0 Mon Sep 17 00:00:00 2001 From: Joey Liu Date: Wed, 8 Oct 2025 22:18:20 +0000 Subject: [PATCH 1/3] Explore: update dataset configurator Signed-off-by: Joey Liu --- .../data_views/data_views/data_views.ts | 2 + src/plugins/data/common/datasets/types.ts | 2 + .../dataset_service/dataset_service.test.ts | 81 +++++ .../dataset_service/dataset_service.ts | 48 ++- .../ui/dataset_select/dataset_select.test.tsx | 110 +++++- .../ui/dataset_select/dataset_select.tsx | 133 ++++--- .../ui/dataset_selector/advanced_selector.tsx | 17 +- .../{ => configurator}/configurator.test.tsx | 4 +- .../{ => configurator}/configurator.tsx | 8 +- .../configurator/configurator_v2.test.tsx | 188 ++++++++++ .../configurator/configurator_v2.tsx | 337 ++++++++++++++++++ .../ui/dataset_selector/dataset_explorer.tsx | 23 +- .../dataset_select/dataset_select.test.tsx | 59 ++- .../dataset_select/dataset_select.tsx | 22 +- 14 files changed, 892 insertions(+), 142 deletions(-) rename src/plugins/data/public/ui/dataset_selector/{ => configurator}/configurator.test.tsx (99%) rename src/plugins/data/public/ui/dataset_selector/{ => configurator}/configurator.tsx (98%) create mode 100644 src/plugins/data/public/ui/dataset_selector/configurator/configurator_v2.test.tsx create mode 100644 src/plugins/data/public/ui/dataset_selector/configurator/configurator_v2.tsx diff --git a/src/plugins/data/common/data_views/data_views/data_views.ts b/src/plugins/data/common/data_views/data_views/data_views.ts index f8f4342e2336..685e2ba5c69f 100644 --- a/src/plugins/data/common/data_views/data_views/data_views.ts +++ b/src/plugins/data/common/data_views/data_views/data_views.ts @@ -733,6 +733,8 @@ export class DataViewsService { title: dataView.title, type: dataView.type || DEFAULT_DATA.SET_TYPES.INDEX_PATTERN, timeFieldName: dataView.timeFieldName, + displayName: dataView.displayName, + description: dataView.description, ...(dataView.dataSourceRef?.id && { dataSource: { id: dataView.dataSourceRef.id, diff --git a/src/plugins/data/common/datasets/types.ts b/src/plugins/data/common/datasets/types.ts index 0741210b9365..51002df76c62 100644 --- a/src/plugins/data/common/datasets/types.ts +++ b/src/plugins/data/common/datasets/types.ts @@ -281,6 +281,8 @@ export interface Dataset extends BaseDataset { }; /** Optional parameter to indicate if the dataset is from a remote cluster(Cross Cluster search) */ isRemoteDataset?: boolean; + displayName?: string; + description?: string; } export interface DatasetField { diff --git a/src/plugins/data/public/query/query_string/dataset_service/dataset_service.test.ts b/src/plugins/data/public/query/query_string/dataset_service/dataset_service.test.ts index 2c45776f62e0..54357c3764a5 100644 --- a/src/plugins/data/public/query/query_string/dataset_service/dataset_service.test.ts +++ b/src/plugins/data/public/query/query_string/dataset_service/dataset_service.test.ts @@ -187,6 +187,87 @@ describe('DatasetService', () => { expect(indexPatterns.saveToCache).toHaveBeenCalledTimes(0); }); + test('cacheDataset passes signalType to index pattern spec', async () => { + const mockDataset = { + id: 'test-dataset', + title: 'Test Dataset', + type: mockType.id, + } as Dataset; + service.registerType(mockType); + + await service.cacheDataset(mockDataset, mockDataPluginServices, true, 'logs'); + expect(indexPatterns.create).toHaveBeenCalledWith( + expect.objectContaining({ + signalType: 'logs', + }), + true + ); + }); + + test('saveDataset creates and saves a new dataset', async () => { + const mockDataset = { + id: 'test-dataset', + title: 'Test Dataset', + displayName: 'My Dataset', + description: 'Test description', + type: mockType.id, + timeFieldName: 'timestamp', + } as Dataset; + + const mockDataViews = { + createAndSave: jest.fn().mockResolvedValue({}), + }; + + const servicesWithDataViews = { + ...mockDataPluginServices, + data: { + ...dataPluginMock.createStartContract(), + dataViews: mockDataViews as any, + }, + }; + + service.registerType(mockType); + await service.saveDataset(mockDataset, servicesWithDataViews, 'metrics'); + + expect(mockDataViews.createAndSave).toHaveBeenCalledWith( + expect.objectContaining({ + id: 'test-dataset', + title: 'Test Dataset', + displayName: 'My Dataset', + description: 'Test description', + timeFieldName: 'timestamp', + signalType: 'metrics', + }), + undefined, + false + ); + }); + + test('saveDataset does not save index pattern datasets', async () => { + const mockDataset = { + id: 'test-index-pattern', + title: 'Test Index Pattern', + type: DEFAULT_DATA.SET_TYPES.INDEX_PATTERN, + } as Dataset; + + const mockDataViews = { + createAndSave: jest.fn(), + }; + + const servicesWithDataViews = { + ...mockDataPluginServices, + data: { + ...dataPluginMock.createStartContract(), + dataViews: mockDataViews as any, + }, + }; + + service.registerType(indexPatternTypeConfig); + await service.saveDataset(mockDataset, servicesWithDataViews); + + expect(mockDataViews.createAndSave).not.toHaveBeenCalled(); + }); + test('addRecentDataset adds a dataset', () => { const mockDataset1: Dataset = { id: 'dataset1', diff --git a/src/plugins/data/public/query/query_string/dataset_service/dataset_service.ts b/src/plugins/data/public/query/query_string/dataset_service/dataset_service.ts index bd620fe8f8eb..8e4604668b6e 100644 --- a/src/plugins/data/public/query/query_string/dataset_service/dataset_service.ts +++ b/src/plugins/data/public/query/query_string/dataset_service/dataset_service.ts @@ -97,7 +97,8 @@ export class DatasetService { public async cacheDataset( dataset: Dataset, services: Partial, - defaultCache: boolean = true + defaultCache: boolean = true, + signalType?: string ): Promise { const type = this.getType(dataset?.type); try { @@ -112,6 +113,7 @@ export class DatasetService { timeFieldName: dataset.timeFieldName, fields: fetchedFields, fieldsLoading: asyncType, + signalType, dataSourceRef: dataset.dataSource ? { id: dataset.dataSource.id!, @@ -171,6 +173,50 @@ export class DatasetService { } } + public async saveDataset( + dataset: Dataset, + services: Partial, + signalType?: string + ): Promise { + const type = this.getType(dataset?.type); + try { + const asyncType = type?.meta.isFieldLoadAsync ?? false; + if (dataset && dataset.type !== DEFAULT_DATA.SET_TYPES.INDEX_PATTERN) { + const fetchedFields = asyncType + ? ({} as IndexPatternFieldMap) + : await type?.fetchFields(dataset, services); + const spec = { + id: dataset.id, + displayName: dataset.displayName, + title: dataset.title, + timeFieldName: dataset.timeFieldName, + description: dataset.description, + signalType, + fields: fetchedFields, + fieldsLoading: asyncType, + dataSourceRef: dataset.dataSource + ? { + id: dataset.dataSource.id!, + name: dataset.dataSource.title, + type: dataset.dataSource.type, + } + : undefined, + } as IndexPatternSpec; + + // TODO: For async field loading (when asyncType is true), the data view is created + // with skipFetchFields=true, meaning fields will be empty initially. The fields will + // be loaded asynchronously when the data view is first accessed. However, this means + // the saved data view object won't have field metadata until it's loaded. + // Consider fetching fields after createAndSave and updating the saved object: + // const dataView = await createAndSave(...); + // if (asyncType) { await type.fetchFields(...); await dataViews.updateSavedObject(dataView); } + await services.data?.dataViews.createAndSave(spec, undefined, asyncType); + } + } catch (error) { + throw new Error(`Failed to save dataset: ${dataset?.id}`); + } + } + public async fetchOptions( services: IDataPluginServices, path: DataStructure[], diff --git a/src/plugins/data/public/ui/dataset_select/dataset_select.test.tsx b/src/plugins/data/public/ui/dataset_select/dataset_select.test.tsx index 27f488fcb997..ff8ecf7b0bf5 100644 --- a/src/plugins/data/public/ui/dataset_select/dataset_select.test.tsx +++ b/src/plugins/data/public/ui/dataset_select/dataset_select.test.tsx @@ -19,8 +19,7 @@ jest.mock('../../services', () => ({ getQueryService: jest.fn(), })); -// TODO: Enable this test. skipping due to it hanging -describe.skip('DatasetSelect', () => { +describe('DatasetSelect', () => { const mockOnSelect = jest.fn(); const mockQuery = { dataset: { @@ -45,6 +44,7 @@ describe.skip('DatasetSelect', () => { icon: { type: 'database', }, + supportedAppNames: undefined, // undefined means supported by all apps }, }), cacheDataset: jest.fn(), @@ -77,6 +77,7 @@ describe.skip('DatasetSelect', () => { type: DEFAULT_DATA.SET_TYPES.INDEX_PATTERN, }); }), + clearCache: jest.fn(), }; // Create services for the component @@ -100,7 +101,7 @@ describe.skip('DatasetSelect', () => { const defaultProps: DatasetSelectProps = { onSelect: mockOnSelect, - appName: 'testApp', + singalType: null, }; const renderWithContext = (props: DatasetSelectProps = defaultProps) => { @@ -204,4 +205,107 @@ describe.skip('DatasetSelect', () => { expect(mockOnSelect).toHaveBeenCalled(); }); }); + + it('filters datasets by supportedAppNames', async () => { + // Create a dataset type that only supports 'otherApp' + const mockGetTypeRestricted = jest.fn().mockReturnValue({ + id: 'restricted-type', + title: 'Restricted Type', + meta: { + icon: { type: 'database' }, + supportedAppNames: ['otherApp'], // Does not include 'testApp' + }, + }); + + mockQueryService.queryString.getDatasetService = jest.fn().mockReturnValue({ + getType: mockGetTypeRestricted, + cacheDataset: jest.fn(), + }); + + // Mock a dataset with the restricted type + mockDataViews.getIds = jest.fn().mockResolvedValue(['restricted-id']); + mockDataViews.get = jest.fn().mockResolvedValue({ + id: 'restricted-id', + title: 'Restricted Dataset', + displayName: 'Restricted Dataset', + type: 'restricted-type', + }); + mockDataViews.convertToDataset = jest.fn().mockResolvedValue({ + id: 'restricted-id', + title: 'Restricted Dataset', + type: 'restricted-type', + }); + + renderWithContext(); + + await waitFor(() => { + expect(mockDataViews.getIds).toHaveBeenCalled(); + }); + + // The dataset should be filtered out since it doesn't support 'testApp' + const button = screen.getByTestId('datasetSelectButton'); + fireEvent.click(button); + + await waitFor(() => { + expect(screen.getByPlaceholderText('Search')).toBeInTheDocument(); + }); + + // The restricted dataset should not appear in the list + expect(screen.queryByText('Restricted Dataset')).not.toBeInTheDocument(); + }); + + it('includes datasets when supportedAppNames is undefined', async () => { + // Dataset type with undefined supportedAppNames (supports all apps) + const mockGetTypeAll = jest.fn().mockReturnValue({ + id: 'all-apps-type', + title: 'All Apps Type', + meta: { + icon: { type: 'database' }, + supportedAppNames: undefined, + }, + }); + + mockQueryService.queryString.getDatasetService = jest.fn().mockReturnValue({ + getType: mockGetTypeAll, + cacheDataset: jest.fn(), + }); + + mockDataViews.getIds = jest.fn().mockResolvedValue(['all-apps-id']); + mockDataViews.get = jest.fn().mockResolvedValue({ + id: 'all-apps-id', + title: 'all-apps-dataset', + displayName: 'All Apps Dataset', + type: 'all-apps-type', + }); + mockDataViews.convertToDataset = jest.fn().mockResolvedValue({ + id: 'all-apps-id', + title: 'all-apps-dataset', + type: 'all-apps-type', + }); + mockQueryService.queryString.getQuery = jest.fn().mockReturnValue({ + dataset: { + id: 'all-apps-id', + title: 'all-apps-dataset', + type: 'all-apps-type', + }, + }); + + renderWithContext(); + + await waitFor(() => { + expect(mockDataViews.getIds).toHaveBeenCalled(); + expect(mockDataViews.get).toHaveBeenCalled(); + }); + + const button = screen.getByTestId('datasetSelectButton'); + fireEvent.click(button); + + await waitFor(() => { + expect(screen.getByPlaceholderText('Search')).toBeInTheDocument(); + }); + + // The dataset should appear since supportedAppNames is undefined (checking by display name) + const allAppsElements = screen.getAllByText('All Apps Dataset'); + expect(allAppsElements.length).toBeGreaterThan(0); + }); }); diff --git a/src/plugins/data/public/ui/dataset_select/dataset_select.tsx b/src/plugins/data/public/ui/dataset_select/dataset_select.tsx index b7ab9a57602b..8a3f6e3ad704 100644 --- a/src/plugins/data/public/ui/dataset_select/dataset_select.tsx +++ b/src/plugins/data/public/ui/dataset_select/dataset_select.tsx @@ -29,7 +29,7 @@ import { useOpenSearchDashboards, toMountPoint, } from '../../../../opensearch_dashboards_react/public'; -import { Dataset, DEFAULT_DATA, Query } from '../../../common'; +import { CORE_SIGNAL_TYPES, Dataset, DEFAULT_DATA, Query } from '../../../common'; import { IDataPluginServices } from '../../types'; import { DatasetDetails, DatasetDetailsBody, DatasetDetailsHeader } from './dataset_details'; import { AdvancedSelector } from '../dataset_selector/advanced_selector'; @@ -43,20 +43,14 @@ export interface DetailedDataset extends Dataset { export interface DatasetSelectProps { onSelect: (dataset: Dataset) => void; - appName: string; supportedTypes?: string[]; - onFilter?: (dataset: Dataset) => boolean; + singalType: string | null; } /** * @experimental This component is experimental and may change in future versions */ -const DatasetSelect: React.FC = ({ - onSelect, - appName, - supportedTypes, - onFilter = () => true, -}) => { +const DatasetSelect: React.FC = ({ onSelect, supportedTypes, singalType }) => { const { services } = useOpenSearchDashboards(); const isMounted = useRef(true); const [isOpen, setIsOpen] = useState(false); @@ -109,56 +103,77 @@ const DatasetSelect: React.FC = ({ ); const datasetIcon = datasetTypeConfig?.meta?.icon?.type || 'database'; - useEffect(() => { - isMounted.current = true; - const fetchDatasets = async () => { - if (!isMounted.current) return; + const fetchDatasets = useCallback(async () => { + if (!isMounted.current) return; - setIsLoading(true); + setIsLoading(true); - try { - const datasetIds = await dataViews.getIds(true); - const fetchedDatasets: DetailedDataset[] = []; + try { + const datasetIds = await dataViews.getIds(true); + const fetchedDatasets: DetailedDataset[] = []; - for (const id of datasetIds) { - const dataView = await dataViews.get(id); - const dataset = await dataViews.convertToDataset(dataView); + for (const id of datasetIds) { + const dataView = await dataViews.get(id); + const dataset = await dataViews.convertToDataset(dataView); - fetchedDatasets.push({ - ...dataset, - description: dataView.description, - displayName: dataView.displayName, - signalType: dataView.signalType, - }); - } + fetchedDatasets.push({ + ...dataset, + description: dataView.description, + displayName: dataView.displayName, + signalType: dataView.signalType, + }); + } - const filteredDatasets = fetchedDatasets.filter(onFilter); + const onFilter = (detailedDataset: DetailedDataset) => { + // Filter by signal type + const signalTypeMatch = + singalType === CORE_SIGNAL_TYPES.TRACES + ? detailedDataset.signalType === CORE_SIGNAL_TYPES.TRACES + : detailedDataset.signalType !== CORE_SIGNAL_TYPES.TRACES; - const defaultDataView = await dataViews.getDefault(); - if (defaultDataView) { - setDefaultDatasetId(defaultDataView.id); - } - const defaultDataset = - filteredDatasets.find((d) => d.id === defaultDataView?.id) ?? filteredDatasets[0]; - if (defaultDataset && !currentDataset) { - onSelect(defaultDataset); - } - setDatasets(filteredDatasets); - } finally { - if (isMounted.current) { - setIsLoading(false); + if (!signalTypeMatch) { + return false; } + + // Filter by supportedAppNames + const typeConfig = datasetService.getType(detailedDataset.type); + const appNameMatch = + !typeConfig?.meta?.supportedAppNames || + typeConfig.meta.supportedAppNames.includes(services.appName); + + return appNameMatch; + }; + + const filteredDatasets = fetchedDatasets.filter(onFilter); + + const defaultDataView = await dataViews.getDefault(); + if (defaultDataView) { + setDefaultDatasetId(defaultDataView.id); } - }; + const defaultDataset = + filteredDatasets.find((d) => d.id === defaultDataView?.id) ?? filteredDatasets[0]; + // Get fresh current dataset value at execution time + const currentlySelectedDataset = queryString.getQuery().dataset; + if (defaultDataset && !currentlySelectedDataset) { + onSelect(defaultDataset); + } + setDatasets(filteredDatasets); + } finally { + if (isMounted.current) { + setIsLoading(false); + } + } + }, [dataViews, singalType, onSelect, queryString, datasetService, services.appName]); + useEffect(() => { + isMounted.current = true; fetchDatasets(); return () => { isMounted.current = false; }; - }, [datasetService, dataViews, currentDataset, onSelect, onFilter]); + }, [fetchDatasets]); const togglePopover = useCallback(() => setIsOpen(!isOpen), [isOpen]); - const closePopover = useCallback(() => { setIsOpen(false); }, []); @@ -406,19 +421,43 @@ const DatasetSelect: React.FC = ({ const overlay = overlays?.openModal( toMountPoint( ) => { + onSelect={async (query: Partial, saveDataset) => { overlay?.close(); if (query?.dataset) { try { - await datasetService.cacheDataset(query.dataset, services, false); + if (saveDataset) { + await datasetService.saveDataset( + query.dataset, + services, + singalType || undefined + ); + } else { + await datasetService.cacheDataset( + query.dataset, + services, + false, + singalType || undefined + ); + } const dataView = await data.dataViews.get( query.dataset.id, query.dataset.type !== DEFAULT_DATA.SET_TYPES.INDEX_PATTERN ); if (dataView) { - onSelect(query.dataset); + // Refresh datasets list if a new dataset was saved + if (saveDataset) { + // Convert dataView back to dataset to get the correct type + const updatedDataset = await dataViews.convertToDataset(dataView); + onSelect(updatedDataset); + // Clear cache to ensure getIds() returns fresh results including the newly saved dataset + dataViews.clearCache(); + await fetchDatasets(); + } else { + onSelect(query.dataset); + } } } catch (error) { services.notifications?.toasts.addError(error, { diff --git a/src/plugins/data/public/ui/dataset_selector/advanced_selector.tsx b/src/plugins/data/public/ui/dataset_selector/advanced_selector.tsx index d262a1e90dd4..b9829581b3e4 100644 --- a/src/plugins/data/public/ui/dataset_selector/advanced_selector.tsx +++ b/src/plugins/data/public/ui/dataset_selector/advanced_selector.tsx @@ -13,7 +13,8 @@ import { } from '../../../common'; import { getQueryService } from '../../services'; import { IDataPluginServices } from '../../types'; -import { Configurator } from './configurator'; +import { Configurator } from './configurator/configurator'; +import { ConfiguratorV2 } from './configurator/configurator_v2'; import { DatasetExplorer } from './dataset_explorer'; export const AdvancedSelector = ({ @@ -21,11 +22,13 @@ export const AdvancedSelector = ({ onSelect, onCancel, supportedTypes, + useConfiguratorV2, }: { services: IDataPluginServices; - onSelect: (query: Partial) => void; + onSelect: (query: Partial, saveDataset?: boolean) => void; onCancel: () => void; supportedTypes?: string[]; + useConfiguratorV2?: boolean; }) => { const queryString = getQueryService().queryString; @@ -39,8 +42,9 @@ export const AdvancedSelector = ({ .getTypes() .filter( (type) => - type.meta.supportedAppNames === undefined || - type.meta.supportedAppNames.includes(services.appName) + (!supportedTypes?.length || supportedTypes.includes(type.id)) && + (type.meta.supportedAppNames === undefined || + type.meta.supportedAppNames.includes(services.appName)) ) .map((type) => { return { @@ -57,8 +61,10 @@ export const AdvancedSelector = ({ ]); const [selectedDataset, setSelectedDataset] = useState(); + const ConfiguratorComponent = useConfiguratorV2 ? ConfiguratorV2 : Configurator; + return selectedDataset ? ( - setSelectedDataset(dataset)} onCancel={onCancel} - supportedTypes={supportedTypes} /> ); }; diff --git a/src/plugins/data/public/ui/dataset_selector/configurator.test.tsx b/src/plugins/data/public/ui/dataset_selector/configurator/configurator.test.tsx similarity index 99% rename from src/plugins/data/public/ui/dataset_selector/configurator.test.tsx rename to src/plugins/data/public/ui/dataset_selector/configurator/configurator.test.tsx index 097dea32a383..03fb04369d23 100644 --- a/src/plugins/data/public/ui/dataset_selector/configurator.test.tsx +++ b/src/plugins/data/public/ui/dataset_selector/configurator/configurator.test.tsx @@ -8,8 +8,8 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import React from 'react'; import { IntlProvider } from 'react-intl'; import { Dataset } from 'src/plugins/data/common'; -import { Query } from '../../../../data/public'; -import { setIndexPatterns, setQueryService } from '../../services'; +import { Query } from '../../../../../data/public'; +import { setIndexPatterns, setQueryService } from '../../../services'; import { Configurator } from './configurator'; const getQueryMock = jest.fn().mockReturnValue({ diff --git a/src/plugins/data/public/ui/dataset_selector/configurator.tsx b/src/plugins/data/public/ui/dataset_selector/configurator/configurator.tsx similarity index 98% rename from src/plugins/data/public/ui/dataset_selector/configurator.tsx rename to src/plugins/data/public/ui/dataset_selector/configurator/configurator.tsx index f43c95d1db18..175193e88e86 100644 --- a/src/plugins/data/public/ui/dataset_selector/configurator.tsx +++ b/src/plugins/data/public/ui/dataset_selector/configurator/configurator.tsx @@ -22,10 +22,10 @@ import { import { i18n } from '@osd/i18n'; import { FormattedMessage } from '@osd/i18n/react'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { BaseDataset, DEFAULT_DATA, Dataset, DatasetField, Query } from '../../../common'; -import { getIndexPatterns, getQueryService } from '../../services'; -import { IDataPluginServices } from '../../types'; -import { DatasetIndexedView } from '../../query/query_string/dataset_service'; +import { BaseDataset, DEFAULT_DATA, Dataset, DatasetField, Query } from '../../../../common'; +import { getIndexPatterns, getQueryService } from '../../../services'; +import { IDataPluginServices } from '../../../types'; +import { DatasetIndexedView } from '../../../query/query_string/dataset_service'; export const Configurator = ({ services, diff --git a/src/plugins/data/public/ui/dataset_selector/configurator/configurator_v2.test.tsx b/src/plugins/data/public/ui/dataset_selector/configurator/configurator_v2.test.tsx new file mode 100644 index 000000000000..46de51b58d6b --- /dev/null +++ b/src/plugins/data/public/ui/dataset_selector/configurator/configurator_v2.test.tsx @@ -0,0 +1,188 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import '@testing-library/jest-dom'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import React from 'react'; +import { IntlProvider } from 'react-intl'; +import { BaseDataset } from '../../../../common'; +import { setIndexPatterns, setQueryService } from '../../../services'; +import { ConfiguratorV2 } from './configurator_v2'; + +const mockQueryService = { + queryString: { + getQuery: jest.fn().mockReturnValue({ + language: 'PPL', + }), + getLanguageService: () => ({ + getLanguage: (id: string) => + id === 'PPL' ? { id: 'PPL', title: 'PPL', supportedAppNames: ['explore'] } : undefined, + }), + getDatasetService: () => ({ + getType: jest.fn().mockReturnValue({ + fetchFields: jest.fn().mockResolvedValue([{ name: 'timestamp', type: 'date' }]), + supportedLanguages: () => ['PPL'], + meta: { isFieldLoadAsync: false, supportsTimeFilter: true }, + }), + cacheDataset: jest.fn(), + }), + }, +}; + +const mockServices = { + appName: 'explore', + getQueryService: () => mockQueryService, + getIndexPatterns: jest.fn().mockResolvedValue([]), +}; + +const mockBaseDataset: BaseDataset = { + id: 'test-id', + title: 'Test Dataset', + type: 'index', + dataSource: { + id: 'ds-id', + title: 'Test Datasource', + type: 'DATA_SOURCE', + }, +}; + +const mockOnConfirm = jest.fn(); +const mockOnCancel = jest.fn(); +const mockOnPrevious = jest.fn(); + +beforeEach(() => { + jest.clearAllMocks(); + // @ts-expect-error TS2345 TODO(ts-error): fixme + setQueryService(mockServices.getQueryService()); + setIndexPatterns(mockServices.getIndexPatterns()); +}); + +describe('ConfiguratorV2', () => { + it('renders correctly', () => { + render( + + + + ); + + expect(screen.getByText('Step 2: Configure data')).toBeInTheDocument(); + }); + + it('calls onCancel when cancel is clicked', () => { + render( + + + + ); + + fireEvent.click(screen.getByTestId('advancedSelectorCancelButton')); + expect(mockOnCancel).toHaveBeenCalled(); + }); + + it('calls onPrevious when back is clicked', () => { + render( + + + + ); + + fireEvent.click(screen.getByText('Back')); + expect(mockOnPrevious).toHaveBeenCalled(); + }); + + it('displays PPL language', async () => { + render( + + + + ); + + const select = screen.getByTestId('advancedSelectorLanguageSelect'); + expect(select).toHaveValue('PPL'); + }); + + it('shows time field select for non-index-pattern datasets', async () => { + render( + + + + ); + + await waitFor(() => { + expect(screen.getByTestId('advancedSelectorTimeFieldSelect')).toBeInTheDocument(); + }); + }); + + it('disables confirm when time field not selected', async () => { + render( + + + + ); + + await waitFor(() => { + expect(screen.getByTestId('advancedSelectorConfirmButton')).toBeDisabled(); + }); + }); + + it('shows save dataset checkbox', () => { + render( + + + + ); + + expect(screen.getByTestId('saveAsDatasetCheckbox')).toBeInTheDocument(); + }); +}); diff --git a/src/plugins/data/public/ui/dataset_selector/configurator/configurator_v2.tsx b/src/plugins/data/public/ui/dataset_selector/configurator/configurator_v2.tsx new file mode 100644 index 000000000000..7418727d8d22 --- /dev/null +++ b/src/plugins/data/public/ui/dataset_selector/configurator/configurator_v2.tsx @@ -0,0 +1,337 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiButton, + EuiButtonEmpty, + EuiCheckbox, + EuiFieldText, + EuiForm, + EuiFormRow, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, + EuiSelect, + EuiSpacer, + EuiText, + EuiTextArea, +} from '@elastic/eui'; +import { i18n } from '@osd/i18n'; +import { FormattedMessage } from '@osd/i18n/react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { BaseDataset, DEFAULT_DATA, Dataset, DatasetField, Query } from '../../../../common'; +import { getIndexPatterns, getQueryService } from '../../../services'; +import { IDataPluginServices } from '../../../types'; + +export const ConfiguratorV2 = ({ + services, + baseDataset, + onConfirm, + onCancel, + onPrevious, + alwaysShowDatasetFields, +}: { + services: IDataPluginServices; + baseDataset: BaseDataset; + onConfirm: (query: Partial, saveDataset?: boolean) => void; + onCancel: () => void; + onPrevious: () => void; + alwaysShowDatasetFields?: boolean; +}) => { + const queryService = getQueryService(); + const queryString = queryService.queryString; + const languageService = queryService.queryString.getLanguageService(); + const indexPatternsService = getIndexPatterns(); + const type = queryString.getDatasetService().getType(baseDataset.type); + const supportedLanguages = type?.supportedLanguages(baseDataset) || []; + const languages = supportedLanguages.filter( + (langId) => + !services.appName || + languageService.getLanguage(langId)?.supportedAppNames?.includes(services.appName) + ); + + const [language, setLanguage] = useState(() => { + const currentLanguage = queryString.getQuery().language; + if (languages.includes(currentLanguage)) { + return currentLanguage; + } + return languages[0]; + }); + + const [dataset, setDataset] = useState(baseDataset); + const [timeFields, setTimeFields] = useState([]); + const [timeFieldName, setTimeFieldName] = useState(dataset.timeFieldName); + const noTimeFilter = i18n.translate( + 'data.explorer.datasetSelector.advancedSelector.configurator.timeField.noTimeFieldOptionLabel', + { + defaultMessage: "I don't want to use the time filter", + } + ); + const [timeFieldsLoading, setTimeFieldsLoading] = useState(false); + const [saveAsDataset, setSaveAsDataset] = useState(false); + + const isAsyncType = useMemo(() => { + const datasetType = queryString.getDatasetService().getType(dataset.type); + return datasetType?.meta.isFieldLoadAsync ?? false; + }, [dataset.type, queryString]); + + const submitDisabled = useMemo(() => { + return ( + timeFieldsLoading || + (timeFieldName === undefined && + !(dataset.type === DEFAULT_DATA.SET_TYPES.INDEX_PATTERN) && + timeFields && + timeFields.length > 0) || + (alwaysShowDatasetFields && isAsyncType) + ); + }, [dataset, timeFieldName, timeFields, timeFieldsLoading, alwaysShowDatasetFields, isAsyncType]); + + const saveAsDatasetDisabled = useMemo(() => { + return isAsyncType || dataset.type === DEFAULT_DATA.SET_TYPES.INDEX_PATTERN; + }, [dataset, isAsyncType]); + + useEffect(() => { + const fetchFields = async () => { + const datasetType = queryString.getDatasetService().getType(baseDataset.type); + if (!datasetType) { + setTimeFields([]); + return; + } + + setTimeFieldsLoading(true); + const datasetFields = await datasetType + .fetchFields(baseDataset) + .finally(() => setTimeFieldsLoading(false)); + const dateFields = datasetFields?.filter((field) => field.type === 'date'); + setTimeFields(dateFields || []); + }; + + if (baseDataset?.dataSource?.meta?.supportsTimeFilter === false && timeFields.length > 0) { + setTimeFields([]); + return; + } + + fetchFields(); + }, [baseDataset, indexPatternsService, queryString, timeFields.length]); + + const shouldRenderDatePickerField = useCallback(() => { + const datasetType = queryString.getDatasetService().getType(dataset.type); + + const supportsTimeField = datasetType?.meta?.supportsTimeFilter; + if (supportsTimeField !== undefined) { + return Boolean(supportsTimeField); + } + return true; + }, [dataset.type, queryString]); + + return ( + <> + + +

+ +

+ +

+ +

+
+
+
+ + + + + + + ({ + text: languageService.getLanguage(languageId)?.title || languageId, + value: languageId, + }))} + value={language} + onChange={(e) => { + setLanguage(e.target.value); + setDataset({ ...dataset, language: e.target.value }); + }} + data-test-subj="advancedSelectorLanguageSelect" + /> + + {shouldRenderDatePickerField() && + (dataset.type === DEFAULT_DATA.SET_TYPES.INDEX_PATTERN ? ( + + + + ) : ( + + ({ + text: field.displayName || field.name, + value: field.name, + })), + { text: '-----', value: '-----', disabled: true }, + { text: noTimeFilter, value: noTimeFilter }, + ]} + value={timeFieldName} + onChange={(e) => { + const value = e.target.value === noTimeFilter ? undefined : e.target.value; + setTimeFieldName(e.target.value); + setDataset({ ...dataset, timeFieldName: value }); + }} + hasNoInitialSelection + data-test-subj="advancedSelectorTimeFieldSelect" + /> + + ))} + {!alwaysShowDatasetFields && ( + <> + + setSaveAsDataset(e.target.checked)} + disabled={saveAsDatasetDisabled} + data-test-subj="saveAsDatasetCheckbox" + /> + {saveAsDatasetDisabled && ( + <> + + +

+ +

+
+ + )} + + )} + {(alwaysShowDatasetFields || saveAsDataset) && ( + <> + + { + setDataset({ ...dataset, displayName: e.target.value }); + }} + data-test-subj="datasetNameInput" + /> + + + { + setDataset({ ...dataset, description: e.target.value }); + }} + data-test-subj="datasetDescriptionInput" + /> + + {alwaysShowDatasetFields && isAsyncType && ( + <> + + +

+ +

+
+ + )} + + )} +
+
+ + + + + + + + { + const shouldSaveDataset = alwaysShowDatasetFields || saveAsDataset; + await queryString.getDatasetService().cacheDataset(dataset, services); + onConfirm({ dataset, language }, shouldSaveDataset || undefined); + }} + fill + disabled={submitDisabled} + data-test-subj="advancedSelectorConfirmButton" + > + + + + + ); +}; diff --git a/src/plugins/data/public/ui/dataset_selector/dataset_explorer.tsx b/src/plugins/data/public/ui/dataset_selector/dataset_explorer.tsx index 9283a6d6eb33..68256c60ea75 100644 --- a/src/plugins/data/public/ui/dataset_selector/dataset_explorer.tsx +++ b/src/plugins/data/public/ui/dataset_selector/dataset_explorer.tsx @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useState, useEffect } from 'react'; +import React, { useState } from 'react'; import { EuiButton, EuiButtonEmpty, @@ -40,7 +40,6 @@ export const DatasetExplorer = ({ setPath, onNext, onCancel, - supportedTypes, }: { services: IDataPluginServices; queryString: QueryStringContract; @@ -48,32 +47,12 @@ export const DatasetExplorer = ({ setPath: (path: DataStructure[]) => void; onNext: (dataset: BaseDataset) => void; onCancel: () => void; - supportedTypes?: string[]; }) => { const uiSettings = services.uiSettings; const [explorerDataset, setExplorerDataset] = useState(undefined); const [loading, setLoading] = useState(false); const datasetService = queryString.getDatasetService(); - useEffect(() => { - const availableTypes = path[0]?.children; - // Prevents the ability to set the supported types to an empty array and causing no option - const shouldFilter = availableTypes && supportedTypes?.length; - - if (shouldFilter) { - const filteredTypes = availableTypes.filter((type) => supportedTypes!.includes(type.id)); - if (filteredTypes.length !== availableTypes.length) { - setPath([ - { - ...path[0], - children: filteredTypes, - }, - ...path.slice(1), - ]); - } - } - }, [path, supportedTypes, setPath]); - const fetchNextDataStructure = async ( nextPath: DataStructure[], dataType: string, diff --git a/src/plugins/explore/public/components/query_panel/query_panel_widgets/dataset_select/dataset_select.test.tsx b/src/plugins/explore/public/components/query_panel/query_panel_widgets/dataset_select/dataset_select.test.tsx index 2e2150c1bae7..5d4969c717f1 100644 --- a/src/plugins/explore/public/components/query_panel/query_panel_widgets/dataset_select/dataset_select.test.tsx +++ b/src/plugins/explore/public/components/query_panel/query_panel_widgets/dataset_select/dataset_select.test.tsx @@ -36,7 +36,7 @@ jest.doMock('react-redux', () => { }; }); -let capturedOnFilter: ((dataset: any) => boolean) | undefined; +let capturedSingalType: string | null | undefined; jest.doMock('../../../../../../opensearch_dashboards_react/public', () => ({ useOpenSearchDashboards: () => ({ @@ -59,12 +59,12 @@ jest.doMock('../../../../../../opensearch_dashboards_react/public', () => ({ ui: { DatasetSelect: ({ onSelect, - onFilter, + singalType, }: { onSelect: (dataset: any) => void; - onFilter?: (dataset: any) => boolean; + singalType: string | null; }) => { - capturedOnFilter = onFilter; + capturedSingalType = singalType; return (
-
- {onFilter ? 'Filter provided' : 'No filter'} +
+ {singalType !== undefined ? `Signal type: ${singalType}` : 'No signal type'}
); @@ -227,58 +227,43 @@ describe('DatasetSelectWidget', () => { }); }); - it('provides onFilter prop to DatasetSelect', () => { + it('provides singalType prop to DatasetSelect', () => { + mockUseFlavorId.mockReturnValue('traces'); renderWithStore(); - expect(screen.getByTestId('dataset-filter-prop')).toHaveTextContent('Filter provided'); + expect(screen.getByTestId('dataset-singaltype-prop')).toHaveTextContent('Signal type: traces'); }); - describe('onFilter functionality', () => { + describe('singalType functionality', () => { beforeEach(() => { - capturedOnFilter = undefined; + capturedSingalType = undefined; }); - it('accepts Traces datasets for Traces flavor', () => { + it('passes traces signal type for Traces flavor', () => { mockUseFlavorId.mockReturnValue('traces'); renderWithStore(); - // Mock a detailed dataset with Traces signal type - const tracesDataset = { signalType: 'traces' }; - - expect(capturedOnFilter).toBeDefined(); - expect(capturedOnFilter!(tracesDataset)).toBe(true); + expect(capturedSingalType).toBe('traces'); }); - it('rejects non-Traces datasets for Traces flavor', () => { - mockUseFlavorId.mockReturnValue('traces'); + it('passes logs signal type for Logs flavor', () => { + mockUseFlavorId.mockReturnValue('logs'); renderWithStore(); - // Mock a detailed dataset with Logs signal type - const logsDataset = { signalType: 'logs' }; - - expect(capturedOnFilter).toBeDefined(); - expect(capturedOnFilter!(logsDataset)).toBe(false); + expect(capturedSingalType).toBe('logs'); }); - it('accepts non-Traces datasets for non-Traces flavor', () => { - mockUseFlavorId.mockReturnValue('logs'); + it('passes metrics signal type for Metrics flavor', () => { + mockUseFlavorId.mockReturnValue('metrics'); renderWithStore(); - // Mock a detailed dataset with Logs signal type - const logsDataset = { signalType: 'logs' }; - - expect(capturedOnFilter).toBeDefined(); - expect(capturedOnFilter!(logsDataset)).toBe(true); + expect(capturedSingalType).toBe('metrics'); }); - it('rejects Traces datasets for non-Traces flavor', () => { - mockUseFlavorId.mockReturnValue('logs'); + it('passes null when flavor is null', () => { + mockUseFlavorId.mockReturnValue(null); renderWithStore(); - // Mock a detailed dataset with Traces signal type - const tracesDataset = { signalType: 'traces' }; - - expect(capturedOnFilter).toBeDefined(); - expect(capturedOnFilter!(tracesDataset)).toBe(false); + expect(capturedSingalType).toBe(null); }); }); }); diff --git a/src/plugins/explore/public/components/query_panel/query_panel_widgets/dataset_select/dataset_select.tsx b/src/plugins/explore/public/components/query_panel/query_panel_widgets/dataset_select/dataset_select.tsx index f30770c965a7..b454a65ffd1d 100644 --- a/src/plugins/explore/public/components/query_panel/query_panel_widgets/dataset_select/dataset_select.tsx +++ b/src/plugins/explore/public/components/query_panel/query_panel_widgets/dataset_select/dataset_select.tsx @@ -5,19 +5,12 @@ import React, { useCallback, useEffect, useMemo } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { DetailedDataset } from '../../../../../../data/public'; import { useOpenSearchDashboards } from '../../../../../../opensearch_dashboards_react/public'; -import { - CORE_SIGNAL_TYPES, - Dataset, - DEFAULT_DATA, - EMPTY_QUERY, -} from '../../../../../../data/common'; +import { Dataset, DEFAULT_DATA, EMPTY_QUERY } from '../../../../../../data/common'; import { ExploreServices } from '../../../../types'; import { setQueryWithHistory } from '../../../../application/utils/state_management/slices'; import { selectQuery } from '../../../../application/utils/state_management/selectors'; import { useFlavorId } from '../../../../helpers/use_flavor_id'; -import { ExploreFlavor } from '../../../../../common'; import { useClearEditors } from '../../../../application/hooks'; export const DatasetSelectWidget = () => { @@ -113,22 +106,11 @@ export const DatasetSelectWidget = () => { ); }, [services.supportedTypes]); - const onFilter = useCallback( - (detailedDataset: DetailedDataset) => { - if (flavorId === ExploreFlavor.Traces) { - return detailedDataset.signalType === CORE_SIGNAL_TYPES.TRACES; - } - return detailedDataset.signalType !== CORE_SIGNAL_TYPES.TRACES; - }, - [flavorId] - ); - return ( ); }; From b60325c5eef23685110a40c216f5715450ca3116 Mon Sep 17 00:00:00 2001 From: "opensearch-changeset-bot[bot]" <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com> Date: Wed, 8 Oct 2025 22:20:16 +0000 Subject: [PATCH 2/3] Changeset file for PR #10690 created/updated --- changelogs/fragments/10690.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changelogs/fragments/10690.yml diff --git a/changelogs/fragments/10690.yml b/changelogs/fragments/10690.yml new file mode 100644 index 000000000000..b2c96a2c064b --- /dev/null +++ b/changelogs/fragments/10690.yml @@ -0,0 +1,2 @@ +feat: +- Allow saving dataset from dataset configurator ([#10690](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/10690)) \ No newline at end of file From e554dedaaa93d6dbcb370bf52346f9f78cb9a4e6 Mon Sep 17 00:00:00 2001 From: Joey Liu Date: Thu, 9 Oct 2025 01:25:37 +0000 Subject: [PATCH 3/3] Fix typo Signed-off-by: Joey Liu --- .../ui/dataset_select/dataset_select.test.tsx | 2 +- .../ui/dataset_select/dataset_select.tsx | 12 ++-- .../configurator/configurator.test.tsx | 66 +++++++------------ .../configurator/configurator_v2.test.tsx | 24 +++---- .../dataset_select/dataset_select.test.tsx | 24 +++---- .../dataset_select/dataset_select.tsx | 2 +- 6 files changed, 52 insertions(+), 78 deletions(-) diff --git a/src/plugins/data/public/ui/dataset_select/dataset_select.test.tsx b/src/plugins/data/public/ui/dataset_select/dataset_select.test.tsx index ff8ecf7b0bf5..f548ddb922c4 100644 --- a/src/plugins/data/public/ui/dataset_select/dataset_select.test.tsx +++ b/src/plugins/data/public/ui/dataset_select/dataset_select.test.tsx @@ -101,7 +101,7 @@ describe('DatasetSelect', () => { const defaultProps: DatasetSelectProps = { onSelect: mockOnSelect, - singalType: null, + signalType: null, }; const renderWithContext = (props: DatasetSelectProps = defaultProps) => { diff --git a/src/plugins/data/public/ui/dataset_select/dataset_select.tsx b/src/plugins/data/public/ui/dataset_select/dataset_select.tsx index 8a3f6e3ad704..0b56534f77f6 100644 --- a/src/plugins/data/public/ui/dataset_select/dataset_select.tsx +++ b/src/plugins/data/public/ui/dataset_select/dataset_select.tsx @@ -44,13 +44,13 @@ export interface DetailedDataset extends Dataset { export interface DatasetSelectProps { onSelect: (dataset: Dataset) => void; supportedTypes?: string[]; - singalType: string | null; + signalType: string | null; } /** * @experimental This component is experimental and may change in future versions */ -const DatasetSelect: React.FC = ({ onSelect, supportedTypes, singalType }) => { +const DatasetSelect: React.FC = ({ onSelect, supportedTypes, signalType }) => { const { services } = useOpenSearchDashboards(); const isMounted = useRef(true); const [isOpen, setIsOpen] = useState(false); @@ -127,7 +127,7 @@ const DatasetSelect: React.FC = ({ onSelect, supportedTypes, const onFilter = (detailedDataset: DetailedDataset) => { // Filter by signal type const signalTypeMatch = - singalType === CORE_SIGNAL_TYPES.TRACES + signalType === CORE_SIGNAL_TYPES.TRACES ? detailedDataset.signalType === CORE_SIGNAL_TYPES.TRACES : detailedDataset.signalType !== CORE_SIGNAL_TYPES.TRACES; @@ -163,7 +163,7 @@ const DatasetSelect: React.FC = ({ onSelect, supportedTypes, setIsLoading(false); } } - }, [dataViews, singalType, onSelect, queryString, datasetService, services.appName]); + }, [dataViews, signalType, onSelect, queryString, datasetService, services.appName]); useEffect(() => { isMounted.current = true; @@ -431,14 +431,14 @@ const DatasetSelect: React.FC = ({ onSelect, supportedTypes, await datasetService.saveDataset( query.dataset, services, - singalType || undefined + signalType || undefined ); } else { await datasetService.cacheDataset( query.dataset, services, false, - singalType || undefined + signalType || undefined ); } const dataView = await data.dataViews.get( diff --git a/src/plugins/data/public/ui/dataset_selector/configurator/configurator.test.tsx b/src/plugins/data/public/ui/dataset_selector/configurator/configurator.test.tsx index 03fb04369d23..d4c6a0d1f7c5 100644 --- a/src/plugins/data/public/ui/dataset_selector/configurator/configurator.test.tsx +++ b/src/plugins/data/public/ui/dataset_selector/configurator/configurator.test.tsx @@ -108,8 +108,7 @@ const mockOnPrevious = jest.fn(); beforeEach(() => { jest.clearAllMocks(); - // @ts-expect-error TS2345 TODO(ts-error): fixme - setQueryService(mockServices.getQueryService()); + setQueryService(mockServices.getQueryService() as any); setIndexPatterns(mockServices.getIndexPatterns()); }); @@ -119,8 +118,7 @@ describe('Configurator Component', () => { {/* Wrap with IntlProvider */} { render( { render( { render( { ); const languageSelect = screen.getByText('Lucene'); expect(languageSelect).toBeInTheDocument(); - // @ts-expect-error TS2339 TODO(ts-error): fixme - expect(languageSelect.value).toBe('lucene'); + expect((languageSelect as any).value).toBe('lucene'); fireEvent.change(languageSelect, { target: { value: 'kuery' } }); await waitFor(() => { - // @ts-expect-error TS2339 TODO(ts-error): fixme - expect(languageSelect.value).toBe('kuery'); + expect((languageSelect as any).value).toBe('kuery'); }); expect(mockOnConfirm).not.toHaveBeenCalled(); }); @@ -201,8 +194,7 @@ describe('Configurator Component', () => { render( { const container = render( { const container = render( { }); const indexedViewSelector = screen.getByText('view1'); expect(indexedViewSelector).toBeInTheDocument(); - // @ts-expect-error TS2339 TODO(ts-error): fixme - expect(indexedViewSelector.value).toBe('view1'); + expect((indexedViewSelector as any).value).toBe('view1'); fireEvent.change(indexedViewSelector, { target: { value: 'view2' } }); await waitFor(() => { - // @ts-expect-error TS2339 TODO(ts-error): fixme - expect(indexedViewSelector.value).toBe('view2'); + expect((indexedViewSelector as any).value).toBe('view2'); }); expect(mockOnConfirm).not.toHaveBeenCalled(); }); @@ -283,8 +271,7 @@ describe('Configurator Component', () => { render( { render( { dataset: undefined, }); + const servicesWithAppName = { ...mockServices, appName: 'unsupportedApp' }; + render( ); @@ -349,8 +335,7 @@ describe('Configurator Component', () => { render( { dataset: undefined, }); + const servicesWithAppName = { ...mockServices, appName: 'supportedApp' }; + render( ); @@ -400,8 +385,7 @@ describe('Configurator Component', () => { const { container } = render( { const { container } = render( { const { container } = render( { jest.clearAllMocks(); - // @ts-expect-error TS2345 TODO(ts-error): fixme - setQueryService(mockServices.getQueryService()); + setQueryService(mockServices.getQueryService() as any); setIndexPatterns(mockServices.getIndexPatterns()); }); @@ -64,8 +63,7 @@ describe('ConfiguratorV2', () => { render( { render( { render( { render( { render( { render( { render( { }; }); -let capturedSingalType: string | null | undefined; +let capturedSignalType: string | null | undefined; jest.doMock('../../../../../../opensearch_dashboards_react/public', () => ({ useOpenSearchDashboards: () => ({ @@ -59,12 +59,12 @@ jest.doMock('../../../../../../opensearch_dashboards_react/public', () => ({ ui: { DatasetSelect: ({ onSelect, - singalType, + signalType, }: { onSelect: (dataset: any) => void; - singalType: string | null; + signalType: string | null; }) => { - capturedSingalType = singalType; + capturedSignalType = signalType; return (
- {singalType !== undefined ? `Signal type: ${singalType}` : 'No signal type'} + {signalType !== undefined ? `Signal type: ${signalType}` : 'No signal type'}
); @@ -227,43 +227,43 @@ describe('DatasetSelectWidget', () => { }); }); - it('provides singalType prop to DatasetSelect', () => { + it('provides signalType prop to DatasetSelect', () => { mockUseFlavorId.mockReturnValue('traces'); renderWithStore(); expect(screen.getByTestId('dataset-singaltype-prop')).toHaveTextContent('Signal type: traces'); }); - describe('singalType functionality', () => { + describe('signalType functionality', () => { beforeEach(() => { - capturedSingalType = undefined; + capturedSignalType = undefined; }); it('passes traces signal type for Traces flavor', () => { mockUseFlavorId.mockReturnValue('traces'); renderWithStore(); - expect(capturedSingalType).toBe('traces'); + expect(capturedSignalType).toBe('traces'); }); it('passes logs signal type for Logs flavor', () => { mockUseFlavorId.mockReturnValue('logs'); renderWithStore(); - expect(capturedSingalType).toBe('logs'); + expect(capturedSignalType).toBe('logs'); }); it('passes metrics signal type for Metrics flavor', () => { mockUseFlavorId.mockReturnValue('metrics'); renderWithStore(); - expect(capturedSingalType).toBe('metrics'); + expect(capturedSignalType).toBe('metrics'); }); it('passes null when flavor is null', () => { mockUseFlavorId.mockReturnValue(null); renderWithStore(); - expect(capturedSingalType).toBe(null); + expect(capturedSignalType).toBe(null); }); }); }); diff --git a/src/plugins/explore/public/components/query_panel/query_panel_widgets/dataset_select/dataset_select.tsx b/src/plugins/explore/public/components/query_panel/query_panel_widgets/dataset_select/dataset_select.tsx index b454a65ffd1d..20c42066be2b 100644 --- a/src/plugins/explore/public/components/query_panel/query_panel_widgets/dataset_select/dataset_select.tsx +++ b/src/plugins/explore/public/components/query_panel/query_panel_widgets/dataset_select/dataset_select.tsx @@ -110,7 +110,7 @@ export const DatasetSelectWidget = () => { ); };