From b9434b185e6d641e942f6824c90a3d5377b339af Mon Sep 17 00:00:00 2001 From: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> Date: Tue, 26 Mar 2024 22:29:34 +0000 Subject: [PATCH 01/15] Add MDS support to TSVB Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> --- .../vis_type_timeseries/common/constants.ts | 1 + .../vis_type_timeseries/common/vis_schema.ts | 1 + .../opensearch_dashboards.json | 2 +- .../components/data_source_picker.tsx | 77 +++++++++++++++++++ .../application/components/index_pattern.js | 30 ++++++++ .../application/components/vis_editor.js | 9 ++- .../public/application/lib/fetch_fields.js | 7 +- .../vis_type_timeseries/public/plugin.ts | 8 +- .../vis_type_timeseries/public/services.ts | 17 +++- .../server/lib/get_fields.ts | 11 ++- .../strategies/abstract_search_strategy.ts | 3 +- .../server/lib/vis_data/get_series_data.js | 4 +- .../server/lib/vis_data/get_table_data.js | 19 +++-- .../vis_type_timeseries/server/plugin.ts | 2 + .../server/routes/fields.ts | 9 ++- 15 files changed, 179 insertions(+), 21 deletions(-) create mode 100644 src/plugins/vis_type_timeseries/public/application/components/data_source_picker.tsx diff --git a/src/plugins/vis_type_timeseries/common/constants.ts b/src/plugins/vis_type_timeseries/common/constants.ts index f97c4aaae3ad..279b9298f197 100644 --- a/src/plugins/vis_type_timeseries/common/constants.ts +++ b/src/plugins/vis_type_timeseries/common/constants.ts @@ -30,3 +30,4 @@ export const MAX_BUCKETS_SETTING = 'metrics:max_buckets'; export const INDEXES_SEPARATOR = ','; +export const DATA_SOURCE_ID_KEY = 'data_source_id'; diff --git a/src/plugins/vis_type_timeseries/common/vis_schema.ts b/src/plugins/vis_type_timeseries/common/vis_schema.ts index c6c79b165727..53d7e35519f5 100644 --- a/src/plugins/vis_type_timeseries/common/vis_schema.ts +++ b/src/plugins/vis_type_timeseries/common/vis_schema.ts @@ -238,6 +238,7 @@ export const panel = schema.object({ ignore_global_filters: numberOptional, ignore_global_filter: numberOptional, index_pattern: stringRequired, + data_source_id: stringOptionalNullable, interval: stringRequired, isModelInvalid: schema.maybe(schema.boolean()), legend_position: stringOptionalNullable, diff --git a/src/plugins/vis_type_timeseries/opensearch_dashboards.json b/src/plugins/vis_type_timeseries/opensearch_dashboards.json index 92441a8a2e4d..23aa8049928b 100644 --- a/src/plugins/vis_type_timeseries/opensearch_dashboards.json +++ b/src/plugins/vis_type_timeseries/opensearch_dashboards.json @@ -5,6 +5,6 @@ "server": true, "ui": true, "requiredPlugins": ["charts", "data", "expressions", "visualizations"], - "optionalPlugins": ["usageCollection"], + "optionalPlugins": ["usageCollection", "dataSourceManagement", "dataSource"], "requiredBundles": ["opensearchDashboardsUtils", "opensearchDashboardsReact"] } diff --git a/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.tsx b/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.tsx new file mode 100644 index 000000000000..4479628467d0 --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.tsx @@ -0,0 +1,77 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useEffect, useState } from 'react'; +import _ from 'lodash'; + +import { SavedObjectsClientContract, ToastsStart } from 'src/core/public'; +import { DataSourceManagementPluginSetup } from 'src/plugins/data_source_management/public'; +import { DATA_SOURCE_ID_KEY } from '../../../common/constants'; + +export interface DataSourcePickerProps { + model: any; + savedObjectsClient: SavedObjectsClientContract; + dataSourceManagement: DataSourceManagementPluginSetup; + toasts: ToastsStart; + onChange: () => void; +} + +export const DataSourcePicker = (props: DataSourcePickerProps) => { + const { savedObjectsClient, model } = props; + const [defaultOption, setDefaultOption] = useState(); + const handleSelectChange = createDataSourcePickerHandler(props.onChange); + const DataSourceSelector = props.dataSourceManagement.ui.DataSourceSelector; + + const onDataSourceSelectChange = (dataSourceOption: DataSourceOption[]) => { + handleSelectChange(dataSourceOption); + }; + + useEffect(() => { + (async () => { + const id = model[DATA_SOURCE_ID_KEY] || undefined; + if (!!id) { + const label = await getDataSourceTitleFromId(id, savedObjectsClient); + setDefaultOption([ + { + id, + label, + }, + ]); + } + })(); + }); + + return ( + ds.attributes.auth.type !== 'no_auth'} + /> + ); +}; + +const createDataSourcePickerHandler = (handleChange: any) => { + return (selectedOptions: DataSourceOption[]): void => { + return handleChange?.({ + [DATA_SOURCE_ID_KEY]: _.get(selectedOptions, '[0].id', null), + }); + }; +}; + +const getDataSourceTitleFromId = async ( + id: string, + savedObjectsClient: SavedObjectsClientContract +) => { + return savedObjectsClient.get('data-source', id).then((response) => { + // @ts-expect-error + return response.attributes ? response.attributes.title : undefined; + }); +}; diff --git a/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js b/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js index b28be12ffc33..adc62c1fffa9 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js +++ b/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js @@ -41,6 +41,12 @@ import { EuiText, } from '@elastic/eui'; import { FieldSelect } from './aggs/field_select'; +import { DataSourcePicker } from './data_source_picker'; +import { + getSavedObjectsClient, + getNotifications, + getDataSourceManagementSetup, +} from '../../services'; import { createSelectHandler } from './lib/create_select_handler'; import { createTextHandler } from './lib/create_text_handler'; import { YesNo } from './yes_no'; @@ -112,6 +118,10 @@ export const IndexPattern = ({ fields, prefix, onChange, disabled, model: _model }; const model = { ...defaults, ..._model }; + + const dataSourceManagementEnabled = + !!getDataSourceManagementSetup().dataSourceManagement || false; + const isDefaultIndexPatternUsed = model.default_index_pattern && !model[indexPatternName]; const intervalValidation = validateIntervalValue(model[intervalName]); const selectedTimeRangeOption = timeRangeOptions.find( @@ -157,6 +167,26 @@ export const IndexPattern = ({ fields, prefix, onChange, disabled, model: _model )} + {!!dataSourceManagementEnabled && ( + + + + + + + + )} + if ( + !isEqual(this.state.extractedIndexPatterns, extractedIndexPatterns) || + !isEqual(this.state.model[DATA_SOURCE_ID_KEY], dataSourceId) + ) { + fetchFields(extractedIndexPatterns, dataSourceId).then((visFields) => this.setState({ visFields, extractedIndexPatterns, diff --git a/src/plugins/vis_type_timeseries/public/application/lib/fetch_fields.js b/src/plugins/vis_type_timeseries/public/application/lib/fetch_fields.js index cac4c910fee4..1bf1ae03bd1e 100644 --- a/src/plugins/vis_type_timeseries/public/application/lib/fetch_fields.js +++ b/src/plugins/vis_type_timeseries/public/application/lib/fetch_fields.js @@ -30,9 +30,10 @@ import { i18n } from '@osd/i18n'; import { extractIndexPatterns } from '../../../common/extract_index_patterns'; +import { DATA_SOURCE_ID_KEY } from '../../../common/constants'; import { getCoreStart } from '../../services'; -export async function fetchFields(indexPatterns = ['*']) { +export async function fetchFields(indexPatterns = ['*'], dataSourceId = undefined) { const patterns = Array.isArray(indexPatterns) ? indexPatterns : [indexPatterns]; try { const indexFields = await Promise.all( @@ -40,6 +41,7 @@ export async function fetchFields(indexPatterns = ['*']) { return getCoreStart().http.get('/api/metrics/fields', { query: { index: pattern, + dataSourceId, }, }); }) @@ -62,7 +64,8 @@ export async function fetchFields(indexPatterns = ['*']) { } export async function fetchIndexPatternFields({ params, fields = {} }) { + const dataSourceId = params[DATA_SOURCE_ID_KEY] || undefined; const indexPatterns = extractIndexPatterns(params, fields); - return await fetchFields(indexPatterns); + return await fetchFields(indexPatterns, dataSourceId); } diff --git a/src/plugins/vis_type_timeseries/public/plugin.ts b/src/plugins/vis_type_timeseries/public/plugin.ts index da565a160164..a3c3d946c0fe 100644 --- a/src/plugins/vis_type_timeseries/public/plugin.ts +++ b/src/plugins/vis_type_timeseries/public/plugin.ts @@ -36,6 +36,7 @@ import { CoreStart, Plugin, } from 'opensearch-dashboards/public'; +import { DataSourceManagementPluginSetup } from 'src/plugins/data_source_management/public'; import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public'; import { VisualizationsSetup } from '../../visualizations/public'; @@ -49,6 +50,8 @@ import { setCoreStart, setDataStart, setChartsSetup, + setDataSourceManagementSetup, + setNotifications, } from './services'; import { DataPublicPluginStart } from '../../data/public'; import { ChartsPluginSetup } from '../../charts/public'; @@ -58,6 +61,7 @@ export interface MetricsPluginSetupDependencies { expressions: ReturnType; visualizations: VisualizationsSetup; charts: ChartsPluginSetup; + dataSourceManagement?: DataSourceManagementPluginSetup; } /** @internal */ @@ -75,12 +79,13 @@ export class MetricsPlugin implements Plugin, void> { public async setup( core: CoreSetup, - { expressions, visualizations, charts }: MetricsPluginSetupDependencies + { expressions, visualizations, charts, dataSourceManagement }: MetricsPluginSetupDependencies ) { expressions.registerFunction(createMetricsFn); setUISettings(core.uiSettings); setChartsSetup(charts); visualizations.createReactVisualization(metricsVisDefinition); + setDataSourceManagementSetup({ dataSourceManagement }); } public start(core: CoreStart, { data }: MetricsPluginStartDependencies) { @@ -89,5 +94,6 @@ export class MetricsPlugin implements Plugin, void> { setFieldFormats(data.fieldFormats); setDataStart(data); setCoreStart(core); + setNotifications(core.notifications); } } diff --git a/src/plugins/vis_type_timeseries/public/services.ts b/src/plugins/vis_type_timeseries/public/services.ts index 15532bc4fd6f..5f54ac3e7546 100644 --- a/src/plugins/vis_type_timeseries/public/services.ts +++ b/src/plugins/vis_type_timeseries/public/services.ts @@ -28,7 +28,14 @@ * under the License. */ -import { I18nStart, SavedObjectsStart, IUiSettingsClient, CoreStart } from 'src/core/public'; +import { + I18nStart, + SavedObjectsStart, + IUiSettingsClient, + CoreStart, + NotificationsStart, +} from 'src/core/public'; +import { DataSourceManagementPluginSetup } from 'src/plugins/data_source_management/public'; import { createGetterSetter } from '../../opensearch_dashboards_utils/public'; import { ChartsPluginSetup } from '../../charts/public'; import { DataPublicPluginStart } from '../../data/public'; @@ -52,3 +59,11 @@ export const [getI18n, setI18n] = createGetterSetter('I18n'); export const [getChartsSetup, setChartsSetup] = createGetterSetter( 'ChartsPluginSetup' ); + +export const [getDataSourceManagementSetup, setDataSourceManagementSetup] = createGetterSetter<{ + dataSourceManagement: DataSourceManagementPluginSetup | undefined; +}>('DataSourceManagementSetup'); + +export const [getNotifications, setNotifications] = createGetterSetter( + 'Notifications' +); diff --git a/src/plugins/vis_type_timeseries/server/lib/get_fields.ts b/src/plugins/vis_type_timeseries/server/lib/get_fields.ts index 1752d3f91f86..5380fb3ff833 100644 --- a/src/plugins/vis_type_timeseries/server/lib/get_fields.ts +++ b/src/plugins/vis_type_timeseries/server/lib/get_fields.ts @@ -44,21 +44,24 @@ export async function getFields( requestContext: RequestHandlerContext, request: OpenSearchDashboardsRequest, framework: Framework, - indexPattern: string + indexPattern: string, + dataSourceId: string | null ) { // NOTE / TODO: This facade has been put in place to make migrating to the New Platform easier. It // removes the need to refactor many layers of dependencies on "req", and instead just augments the top // level object passed from here. The layers should be refactored fully at some point, but for now // this works and we are still using the New Platform services for these vis data portions. + const client = + !!dataSourceId && !!requestContext.dataSource + ? requestContext.dataSource.opensearch.legacy.getClient(dataSourceId).callAPI + : requestContext.core.opensearch.legacy.client.callAsCurrentUser; const reqFacade: ReqFacade = { requestContext, ...request, framework, payload: {}, pre: { - indexPatternsService: new IndexPatternsFetcher( - requestContext.core.opensearch.legacy.client.callAsCurrentUser - ), + indexPatternsService: new IndexPatternsFetcher(client), }, getUiSettingsService: () => requestContext.core.uiSettings.client, getSavedObjectsClient: () => requestContext.core.savedObjects.client, diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts index 6b69628353ac..216a412abb89 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts @@ -66,7 +66,7 @@ export class AbstractSearchStrategy { this.additionalParams = additionalParams; } - async search(req: ReqFacade, bodies: any[], options = {}) { + async search(req: ReqFacade, bodies: any[], options = {}, dataSourceId: string) { const [, deps] = await req.framework.core.getStartServices(); const requests: any[] = []; bodies.forEach((body) => { @@ -74,6 +74,7 @@ export class AbstractSearchStrategy { deps.data.search.search( req.requestContext, { + ...(!!dataSourceId && { dataSourceId }), params: { ...body, ...this.additionalParams, diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.js index a5c8a239d2b0..e1973fc3d513 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.js @@ -34,6 +34,7 @@ import { handleErrorResponse } from './handle_error_response'; import { getAnnotations } from './get_annotations'; import { getOpenSearchQueryConfig } from './helpers/get_opensearch_query_uisettings'; import { getActiveSeries } from './helpers/get_active_series'; +import { DATA_SOURCE_ID_KEY } from '../../../common/constants'; export async function getSeriesData(req, panel) { const { @@ -41,6 +42,7 @@ export async function getSeriesData(req, panel) { capabilities, } = await req.framework.searchStrategyRegistry.getViableStrategyForPanel(req, panel); const opensearchQueryConfig = await getOpenSearchQueryConfig(req); + const panelDataSourceId = panel[DATA_SOURCE_ID_KEY] || undefined; const meta = { type: panel.type, uiRestrictions: capabilities.uiRestrictions, @@ -56,7 +58,7 @@ export async function getSeriesData(req, panel) { [] ); - const data = await searchStrategy.search(req, searches); + const data = await searchStrategy.search(req, searches, {}, panelDataSourceId); const handleResponseBodyFn = handleResponseBody(panel); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.js index 0b744638c3d8..6a7ed3434fcb 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.js @@ -34,9 +34,11 @@ import { get } from 'lodash'; import { processBucket } from './table/process_bucket'; import { getOpenSearchQueryConfig } from './helpers/get_opensearch_query_uisettings'; import { getIndexPatternObject } from './helpers/get_index_pattern'; +import { DATA_SOURCE_ID_KEY } from '../../../common/constants'; export async function getTableData(req, panel) { const panelIndexPattern = panel.index_pattern; + const panelDataSourceId = panel[DATA_SOURCE_ID_KEY] || undefined; const { searchStrategy, @@ -58,12 +60,17 @@ export async function getTableData(req, panel) { indexPatternObject, capabilities ); - const [resp] = await searchStrategy.search(req, [ - { - body, - index: panelIndexPattern, - }, - ]); + const [resp] = await searchStrategy.search( + req, + [ + { + body, + index: panelIndexPattern, + }, + ], + {}, + panelDataSourceId + ); const buckets = get( resp.rawResponse ? resp.rawResponse : resp, diff --git a/src/plugins/vis_type_timeseries/server/plugin.ts b/src/plugins/vis_type_timeseries/server/plugin.ts index 654c85d452b1..af95e2e6b5dc 100644 --- a/src/plugins/vis_type_timeseries/server/plugin.ts +++ b/src/plugins/vis_type_timeseries/server/plugin.ts @@ -40,6 +40,7 @@ import { } from 'src/core/server'; import { Observable } from 'rxjs'; import { Server } from '@hapi/hapi'; +import { DataSourcePluginSetup } from 'src/plugins/data_source/server'; import { VisTypeTimeseriesConfig } from './config'; import { getVisData, GetVisData, GetVisDataOptions } from './lib/get_vis_data'; import { ValidationTelemetryService } from './validation_telemetry'; @@ -57,6 +58,7 @@ export interface LegacySetup { interface VisTypeTimeseriesPluginSetupDependencies { usageCollection?: UsageCollectionSetup; + dataSource?: DataSourcePluginSetup; } interface VisTypeTimeseriesPluginStartDependencies { diff --git a/src/plugins/vis_type_timeseries/server/routes/fields.ts b/src/plugins/vis_type_timeseries/server/routes/fields.ts index bff34ee159f5..c0ace9d35677 100644 --- a/src/plugins/vis_type_timeseries/server/routes/fields.ts +++ b/src/plugins/vis_type_timeseries/server/routes/fields.ts @@ -38,12 +38,17 @@ export const fieldsRoutes = (framework: Framework) => { { path: '/api/metrics/fields', validate: { - query: schema.object({ index: schema.string() }), + query: schema.object({ + index: schema.string(), + dataSourceId: schema.nullable(schema.string()), + }), }, }, async (context, req, res) => { try { - return res.ok({ body: await getFields(context, req, framework, req.query.index) }); + return res.ok({ + body: await getFields(context, req, framework, req.query.index, req.query.dataSourceId), + }); } catch (err) { if (isBoom(err) && err.output.statusCode === 401) { return res.customError({ From 6a0094999cd5f2fd43659344614d8324622d3593 Mon Sep 17 00:00:00 2001 From: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> Date: Tue, 26 Mar 2024 23:13:56 +0000 Subject: [PATCH 02/15] Refactor datasource picker component Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> --- .../components/data_source_picker.tsx | 17 ++++++++--------- .../application/components/index_pattern.js | 5 +++-- .../server/lib/vis_data/get_annotations.js | 4 +++- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.tsx b/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.tsx index 4479628467d0..1fc7f86dfd76 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.tsx @@ -15,17 +15,16 @@ export interface DataSourcePickerProps { savedObjectsClient: SavedObjectsClientContract; dataSourceManagement: DataSourceManagementPluginSetup; toasts: ToastsStart; - onChange: () => void; + handleChange: (e: Array<{}>) => void; } export const DataSourcePicker = (props: DataSourcePickerProps) => { - const { savedObjectsClient, model } = props; - const [defaultOption, setDefaultOption] = useState(); - const handleSelectChange = createDataSourcePickerHandler(props.onChange); + const { savedObjectsClient, model, handleChange } = props; + const [defaultOption, setDefaultOption] = useState>(); const DataSourceSelector = props.dataSourceManagement.ui.DataSourceSelector; - const onDataSourceSelectChange = (dataSourceOption: DataSourceOption[]) => { - handleSelectChange(dataSourceOption); + const onDataSourceSelectChange = (dataSourceOption: Array<{ id: string; label: string }>) => { + handleChange(dataSourceOption); }; useEffect(() => { @@ -41,7 +40,7 @@ export const DataSourcePicker = (props: DataSourcePickerProps) => { ]); } })(); - }); + }, [model, savedObjectsClient]); return ( { ); }; -const createDataSourcePickerHandler = (handleChange: any) => { - return (selectedOptions: DataSourceOption[]): void => { +export const createDataSourcePickerHandler = (handleChange: any) => { + return (selectedOptions: []): void => { return handleChange?.({ [DATA_SOURCE_ID_KEY]: _.get(selectedOptions, '[0].id', null), }); diff --git a/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js b/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js index adc62c1fffa9..60e9dc9d672d 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js +++ b/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js @@ -41,7 +41,7 @@ import { EuiText, } from '@elastic/eui'; import { FieldSelect } from './aggs/field_select'; -import { DataSourcePicker } from './data_source_picker'; +import { DataSourcePicker, createDataSourcePickerHandler } from './data_source_picker'; import { getSavedObjectsClient, getNotifications, @@ -121,6 +121,7 @@ export const IndexPattern = ({ fields, prefix, onChange, disabled, model: _model const dataSourceManagementEnabled = !!getDataSourceManagementSetup().dataSourceManagement || false; + const handleDataSourceSelectChange = createDataSourcePickerHandler(onChange); const isDefaultIndexPatternUsed = model.default_index_pattern && !model[indexPatternName]; const intervalValidation = validateIntervalValue(model[intervalName]); @@ -181,7 +182,7 @@ export const IndexPattern = ({ fields, prefix, onChange, disabled, model: _model toasts={getNotifications().toasts} model={model} dataSourceManagement={getDataSourceManagementSetup().dataSourceManagement} - onChange={onChange} + handleChange={handleDataSourceSelectChange} /> diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_annotations.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_annotations.js index 16b526d1ba2e..1b4e9fee5e40 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_annotations.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_annotations.js @@ -31,6 +31,7 @@ import { handleAnnotationResponse } from './response_processors/annotations'; import { getAnnotationRequestParams } from './annotations/get_request_params'; import { getLastSeriesTimestamp } from './helpers/timestamp'; +import { DATA_SOURCE_ID_KEY } from '../../../common/constants'; function validAnnotation(annotation) { return ( @@ -54,6 +55,7 @@ export async function getAnnotations({ const annotations = panel.annotations.filter(validAnnotation); const lastSeriesTimestamp = getLastSeriesTimestamp(series); const handleAnnotationResponseBy = handleAnnotationResponse(lastSeriesTimestamp); + const panelDataSourceId = panel[DATA_SOURCE_ID_KEY] || undefined; const bodiesPromises = annotations.map((annotation) => getAnnotationRequestParams(req, panel, annotation, opensearchQueryConfig, capabilities) @@ -67,7 +69,7 @@ export async function getAnnotations({ if (!searches.length) return { responses: [] }; try { - const data = await searchStrategy.search(req, searches); + const data = await searchStrategy.search(req, searches, {}, panelDataSourceId); return annotations.reduce((acc, annotation, index) => { acc[annotation.id] = handleAnnotationResponseBy(data[index].rawResponse, annotation); From 910e93aec51cf5ac93e9ccc1deedf6f2240b9371 Mon Sep 17 00:00:00 2001 From: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> Date: Wed, 27 Mar 2024 01:44:40 +0000 Subject: [PATCH 03/15] Allow picker to persist state Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> --- .../components/data_source_picker.tsx | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.tsx b/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.tsx index 1fc7f86dfd76..fc2d1de690fc 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.tsx @@ -24,24 +24,35 @@ export const DataSourcePicker = (props: DataSourcePickerProps) => { const DataSourceSelector = props.dataSourceManagement.ui.DataSourceSelector; const onDataSourceSelectChange = (dataSourceOption: Array<{ id: string; label: string }>) => { + setDefaultOption(dataSourceOption); handleChange(dataSourceOption); }; useEffect(() => { - (async () => { - const id = model[DATA_SOURCE_ID_KEY] || undefined; - if (!!id) { - const label = await getDataSourceTitleFromId(id, savedObjectsClient); + const id = model[DATA_SOURCE_ID_KEY] || undefined; + if (!id || id === '') { + setDefaultOption(null); + return; + } + + getDataSourceTitleFromId(id, savedObjectsClient).then((label) => { + if (!!label) { setDefaultOption([ { id, label, }, ]); + return; } - })(); + setDefaultOption(null); + }); }, [model, savedObjectsClient]); + if (defaultOption === undefined) { + return
Loading...
; + } + return ( Date: Wed, 27 Mar 2024 17:02:51 +0000 Subject: [PATCH 04/15] Refactored picker component params Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> --- .../application/components/data_source_picker.tsx | 15 ++++++++------- .../application/components/index_pattern.js | 3 ++- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.tsx b/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.tsx index fc2d1de690fc..24e4ac90c130 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.tsx @@ -11,15 +11,15 @@ import { DataSourceManagementPluginSetup } from 'src/plugins/data_source_managem import { DATA_SOURCE_ID_KEY } from '../../../common/constants'; export interface DataSourcePickerProps { - model: any; savedObjectsClient: SavedObjectsClientContract; dataSourceManagement: DataSourceManagementPluginSetup; toasts: ToastsStart; + defaultDataSourceId?: string; handleChange: (e: Array<{}>) => void; } export const DataSourcePicker = (props: DataSourcePickerProps) => { - const { savedObjectsClient, model, handleChange } = props; + const { savedObjectsClient, defaultDataSourceId, handleChange } = props; const [defaultOption, setDefaultOption] = useState>(); const DataSourceSelector = props.dataSourceManagement.ui.DataSourceSelector; @@ -29,25 +29,26 @@ export const DataSourcePicker = (props: DataSourcePickerProps) => { }; useEffect(() => { - const id = model[DATA_SOURCE_ID_KEY] || undefined; - if (!id || id === '') { + if (!defaultDataSourceId || defaultDataSourceId === '') { + // @ts-expect-error setDefaultOption(null); return; } - getDataSourceTitleFromId(id, savedObjectsClient).then((label) => { + getDataSourceTitleFromId(defaultDataSourceId, savedObjectsClient).then((label) => { if (!!label) { setDefaultOption([ { - id, + id: defaultDataSourceId, label, }, ]); return; } + // @ts-expect-error setDefaultOption(null); }); - }, [model, savedObjectsClient]); + }, [defaultDataSourceId, savedObjectsClient]); if (defaultOption === undefined) { return
Loading...
; diff --git a/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js b/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js index 60e9dc9d672d..313bf55c5119 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js +++ b/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js @@ -63,6 +63,7 @@ import { TIME_RANGE_DATA_MODES, TIME_RANGE_MODE_KEY } from '../../../common/time import { PANEL_TYPES } from '../../../common/panel_types'; import { isTimerangeModeEnabled } from '../lib/check_ui_restrictions'; import { VisDataContext } from '../contexts/vis_data_context'; +import { DATA_SOURCE_ID_KEY } from '../../../common/constants'; const RESTRICT_FIELDS = [OSD_FIELD_TYPES.DATE]; @@ -180,7 +181,7 @@ export const IndexPattern = ({ fields, prefix, onChange, disabled, model: _model From 7be618de4e9147fff6e84c2049eb7e47bb231114 Mon Sep 17 00:00:00 2001 From: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> Date: Wed, 27 Mar 2024 23:45:05 +0000 Subject: [PATCH 05/15] Add unit tests Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> --- .../data_source_picker.test.tsx.snap | 1896 +++++++++++++++++ .../components/data_source_picker.test.tsx | 156 ++ .../components/data_source_picker.tsx | 15 + .../abstract_search_strategy.test.js | 48 +- 4 files changed, 2114 insertions(+), 1 deletion(-) create mode 100644 src/plugins/vis_type_timeseries/public/application/components/__snapshots__/data_source_picker.test.tsx.snap create mode 100644 src/plugins/vis_type_timeseries/public/application/components/data_source_picker.test.tsx diff --git a/src/plugins/vis_type_timeseries/public/application/components/__snapshots__/data_source_picker.test.tsx.snap b/src/plugins/vis_type_timeseries/public/application/components/__snapshots__/data_source_picker.test.tsx.snap new file mode 100644 index 000000000000..d0b596af58cd --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/components/__snapshots__/data_source_picker.test.tsx.snap @@ -0,0 +1,1896 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`DataSourcePicker render correct default option when datasource id is valid 1`] = ` + + + + +
+ + +
+
+
+ + + some-title-for-some-valid-id + + + +
+ +
+
+ +
+ +
+ + + + + + + + +
+
+
+
+ + +
+ + + + +`; + +exports[`DataSourcePicker render local cluster when datasource id = "" 1`] = ` + + + + +
+ + +
+
+
+ + + Local cluster + + + +
+ +
+
+ +
+ +
+ + + + + + + + +
+
+
+
+ + +
+ + + + +`; + +exports[`DataSourcePicker render local cluster when datasource id is not in saved objects 1`] = ` + + + + +
+ + +
+
+
+ + + Local cluster + + + +
+ +
+
+ +
+ +
+ + + + + + + + +
+
+
+
+ + +
+ + + + +`; + +exports[`DataSourcePicker should render local cluster without datasource id provided 1`] = ` + + + + +
+ + +
+
+
+ + + Local cluster + + + +
+ +
+
+ +
+ +
+ + + + + + + + +
+
+
+
+ + +
+ + + + +`; diff --git a/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.test.tsx b/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.test.tsx new file mode 100644 index 000000000000..2a457f26f88e --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.test.tsx @@ -0,0 +1,156 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { mount } from 'enzyme'; + +import { DataSourcePicker } from './data_source_picker'; +import { + coreMock, + notificationServiceMock, + savedObjectsServiceMock, +} from '../../../../../core/public/mocks'; +import { testDataSourceManagementPlugin } from '../../../../data_source_management/public/mocks'; +import { DataSourceSelector } from '../../../../data_source_management/public'; + +describe('DataSourcePicker', () => { + const nextTick = () => new Promise((res) => process.nextTick(res)); + const getPluginSetupStartMocks = () => { + const savedObjectsClient = savedObjectsServiceMock.createStartContract().client; + savedObjectsClient.get = jest.fn().mockImplementation((type: string, id: string) => { + if (type === 'data-source' && id !== 'nonexistent-id') { + return Promise.resolve({ + id, + type, + attributes: { + title: `some-title-for-${id}`, + }, + }); + } + + return Promise.resolve({ + id, + type, + error: { + statusCode: 404, + message: 'Not found', + }, + }); + }); + + return { + savedObjectsClient, + dataSourceManagement: testDataSourceManagementPlugin( + coreMock.createSetup(), + coreMock.createStart() + ).setup, + toasts: notificationServiceMock.createStartContract().toasts, + handleChange: jest.fn(), + }; + }; + + test('should render local cluster without datasource id provided', () => { + const { + dataSourceManagement, + savedObjectsClient, + toasts, + handleChange, + } = getPluginSetupStartMocks(); + const component = mount( + + ); + + const dataSourceSelector = component.find(DataSourceSelector); + expect(dataSourceSelector.prop('defaultOption')).toBe(null); + expect(component).toMatchSnapshot(); + expect(toasts.addWarning).toBeCalledTimes(0); + expect(handleChange).toBeCalledTimes(0); + }); + + test('render local cluster when datasource id = ""', () => { + const { + dataSourceManagement, + savedObjectsClient, + toasts, + handleChange, + } = getPluginSetupStartMocks(); + const component = mount( + + ); + + const dataSourceSelector = component.find(DataSourceSelector); + expect(dataSourceSelector.prop('defaultOption')).toBe(null); + expect(component).toMatchSnapshot(); + expect(toasts.addWarning).toBeCalledTimes(0); + expect(handleChange).toBeCalledTimes(0); + }); + + test('render local cluster when datasource id is not in saved objects', async () => { + const { + dataSourceManagement, + savedObjectsClient, + toasts, + handleChange, + } = getPluginSetupStartMocks(); + const component = mount( + + ); + + await nextTick(); + component.update(); + const dataSourceSelector = component.find(DataSourceSelector); + expect(dataSourceSelector.prop('defaultOption')).toBe(null); + expect(component).toMatchSnapshot(); + expect(toasts.addWarning).toBeCalledTimes(0); + expect(handleChange).toBeCalledTimes(0); + expect(savedObjectsClient.get).toBeCalledTimes(1); + }); + + test('render correct default option when datasource id is valid', async () => { + const { + dataSourceManagement, + savedObjectsClient, + toasts, + handleChange, + } = getPluginSetupStartMocks(); + const component = mount( + + ); + + await nextTick(); + component.update(); + const dataSourceSelector = component.find(DataSourceSelector); + expect(dataSourceSelector.prop('defaultOption')).toMatchObject([ + { label: 'some-title-for-some-valid-id', id: 'some-valid-id' }, + ]); + expect(component).toMatchSnapshot(); + expect(toasts.addWarning).toBeCalledTimes(0); + expect(handleChange).toBeCalledTimes(0); + expect(savedObjectsClient.get).toBeCalledTimes(1); + }); +}); diff --git a/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.tsx b/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.tsx index 24e4ac90c130..e2ff6c5507a6 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.tsx @@ -10,6 +10,15 @@ import { SavedObjectsClientContract, ToastsStart } from 'src/core/public'; import { DataSourceManagementPluginSetup } from 'src/plugins/data_source_management/public'; import { DATA_SOURCE_ID_KEY } from '../../../common/constants'; +/** + * Provide only the necessary plugin setup/start and dataSourceId and this component will render a DataSourceSelector with a default selection + * + * @property {SavedObjectsClientContract} savedObjectsClient + * @property {DataSourceManagementPluginSetup} dataSourceManagement + * @property {ToastsStart} toasts + * @property {string} [defaultDataSourceId] - the datasource id as the default option when the component first renders + * @property {(e: Array<{}>) => void} handleChange - the function that will update the model when a datasource is selected + */ export interface DataSourcePickerProps { savedObjectsClient: SavedObjectsClientContract; dataSourceManagement: DataSourceManagementPluginSetup; @@ -18,6 +27,12 @@ export interface DataSourcePickerProps { handleChange: (e: Array<{}>) => void; } +/** + * Provides a wrapper around the DataSourceSelector component exposed by core + * + * @param {DataSourcePickerProps} props + * @returns the DataSourceSelector + */ export const DataSourcePicker = (props: DataSourcePickerProps) => { const { savedObjectsClient, defaultDataSourceId, handleChange } = props; const [defaultOption, setDefaultOption] = useState>(); diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js index fa130462e78d..068a7ef06c04 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js @@ -65,7 +65,7 @@ describe('AbstractSearchStrategy', () => { }); }); - test('should return response', async () => { + test('should return response for local cluster queries', async () => { const searches = [{ body: 'body', index: 'index' }]; const searchFn = jest.fn().mockReturnValue(Promise.resolve({})); @@ -107,4 +107,50 @@ describe('AbstractSearchStrategy', () => { } ); }); + + test('should return response for datasource query', async () => { + const searches = [{ body: 'body', index: 'index' }]; + const searchFn = jest.fn().mockReturnValue(Promise.resolve({})); + + const responses = await abstractSearchStrategy.search( + { + requestContext: {}, + framework: { + core: { + getStartServices: jest.fn().mockReturnValue( + Promise.resolve([ + {}, + { + data: { + search: { + search: searchFn, + }, + }, + }, + ]) + ), + }, + }, + }, + searches, + {}, + 'some-data-source-id' + ); + + expect(responses).toEqual([{}]); + expect(searchFn).toHaveBeenCalledWith( + {}, + { + dataSourceId: 'some-data-source-id', + params: { + body: 'body', + index: 'index', + }, + indexType: undefined, + }, + { + strategy: 'opensearch', + } + ); + }); }); From cae4f7f4fd4e4c29623fa3d0c5bda92b0489201a Mon Sep 17 00:00:00 2001 From: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> Date: Fri, 29 Mar 2024 20:17:47 +0000 Subject: [PATCH 06/15] Add to CHANGELOG Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89299cd00193..a2cec559e79b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,6 +72,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Multiple Datasource] Make sure customer always have a default datasource ([#6237](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6237)) - [Workspace] Add workspace list page ([#6182](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6182)) - [Multiple Datasource] Add multi data source support to sample vega visualizations ([#6218](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6218)) +- [Mulitple Datasource] Add multi data source support to TSVB ([#6298](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6298)) ### 🐛 Bug Fixes From e2a738b98fa4cddef040435ca6a44581bfa807e0 Mon Sep 17 00:00:00 2001 From: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> Date: Tue, 2 Apr 2024 23:05:36 +0000 Subject: [PATCH 07/15] Refactor components to use hideLocalCluster Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> --- .../components/data_source_selector/index.ts | 2 +- .../data_source_management/public/index.ts | 2 +- .../data_source_picker.test.tsx.snap | 16 +++--------- .../components/data_source_picker.test.tsx | 1 + .../components/data_source_picker.tsx | 25 +++++++++++-------- .../application/components/index_pattern.js | 2 ++ .../vis_type_timeseries/public/plugin.ts | 12 ++++++++- .../vis_type_timeseries/public/services.ts | 4 +++ .../server/lib/vis_data/get_annotations.js | 2 +- .../server/lib/vis_data/get_series_data.js | 2 +- .../server/lib/vis_data/get_table_data.js | 2 +- 11 files changed, 42 insertions(+), 28 deletions(-) diff --git a/src/plugins/data_source_management/public/components/data_source_selector/index.ts b/src/plugins/data_source_management/public/components/data_source_selector/index.ts index a1d1d97b9828..ea44c761e1d4 100644 --- a/src/plugins/data_source_management/public/components/data_source_selector/index.ts +++ b/src/plugins/data_source_management/public/components/data_source_selector/index.ts @@ -3,4 +3,4 @@ * SPDX-License-Identifier: Apache-2.0 */ -export { DataSourceSelector } from './data_source_selector'; +export { DataSourceSelector, DataSourceOption } from './data_source_selector'; diff --git a/src/plugins/data_source_management/public/index.ts b/src/plugins/data_source_management/public/index.ts index 471792ddd726..da7fd8992459 100644 --- a/src/plugins/data_source_management/public/index.ts +++ b/src/plugins/data_source_management/public/index.ts @@ -12,7 +12,7 @@ export function plugin() { } export { DataSourceManagementPluginStart } from './types'; -export { DataSourceSelector } from './components/data_source_selector'; +export { DataSourceSelector, DataSourceOption } from './components/data_source_selector'; export { DataSourceMenu } from './components/data_source_menu'; export { DataSourceManagementPlugin, DataSourceManagementPluginSetup } from './plugin'; export { diff --git a/src/plugins/vis_type_timeseries/public/application/components/__snapshots__/data_source_picker.test.tsx.snap b/src/plugins/vis_type_timeseries/public/application/components/__snapshots__/data_source_picker.test.tsx.snap index d0b596af58cd..bc709367844d 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/__snapshots__/data_source_picker.test.tsx.snap +++ b/src/plugins/vis_type_timeseries/public/application/components/__snapshots__/data_source_picker.test.tsx.snap @@ -6,8 +6,8 @@ exports[`DataSourcePicker render correct default option when datasource id is va Object { "registerAuthenticationMethod": [Function], "ui": Object { - "DataSourceMenu": [Function], "DataSourceSelector": [Function], + "getDataSourceMenu": [Function], }, } } @@ -82,7 +82,6 @@ exports[`DataSourcePicker render correct default option when datasource id is va ] } disabled={false} - filterFunc={[Function]} fullWidth={false} notifications={ Object { @@ -155,7 +154,6 @@ exports[`DataSourcePicker render correct default option when datasource id is va ] } disabled={false} - filterFunc={[Function]} fullWidth={false} notifications={ Object { @@ -510,8 +508,8 @@ exports[`DataSourcePicker render local cluster when datasource id = "" 1`] = ` Object { "registerAuthenticationMethod": [Function], "ui": Object { - "DataSourceMenu": [Function], "DataSourceSelector": [Function], + "getDataSourceMenu": [Function], }, } } @@ -566,7 +564,6 @@ exports[`DataSourcePicker render local cluster when datasource id = "" 1`] = ` { ).setup, toasts: notificationServiceMock.createStartContract().toasts, handleChange: jest.fn(), + hideLocalCluster: false, }; }; diff --git a/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.tsx b/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.tsx index e2ff6c5507a6..15eeba0aa61e 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.tsx @@ -7,7 +7,11 @@ import React, { useEffect, useState } from 'react'; import _ from 'lodash'; import { SavedObjectsClientContract, ToastsStart } from 'src/core/public'; -import { DataSourceManagementPluginSetup } from 'src/plugins/data_source_management/public'; +import { + DataSourceManagementPluginSetup, + DataSourceOption, +} from 'src/plugins/data_source_management/public'; +import { PanelSchema } from 'src/plugins/vis_type_timeseries/common/types'; import { DATA_SOURCE_ID_KEY } from '../../../common/constants'; /** @@ -17,14 +21,16 @@ import { DATA_SOURCE_ID_KEY } from '../../../common/constants'; * @property {DataSourceManagementPluginSetup} dataSourceManagement * @property {ToastsStart} toasts * @property {string} [defaultDataSourceId] - the datasource id as the default option when the component first renders - * @property {(e: Array<{}>) => void} handleChange - the function that will update the model when a datasource is selected + * @property {(e: DataSourceOption[]) => void} handleChange - the function that will update the model when a datasource is selected + * @property {boolean} hideLocalCluster - the config option to hide the local cluster */ export interface DataSourcePickerProps { savedObjectsClient: SavedObjectsClientContract; dataSourceManagement: DataSourceManagementPluginSetup; toasts: ToastsStart; defaultDataSourceId?: string; - handleChange: (e: Array<{}>) => void; + handleChange: (e: DataSourceOption[]) => void; + hideLocalCluster: boolean; } /** @@ -35,16 +41,16 @@ export interface DataSourcePickerProps { */ export const DataSourcePicker = (props: DataSourcePickerProps) => { const { savedObjectsClient, defaultDataSourceId, handleChange } = props; - const [defaultOption, setDefaultOption] = useState>(); + const [defaultOption, setDefaultOption] = useState(); const DataSourceSelector = props.dataSourceManagement.ui.DataSourceSelector; - const onDataSourceSelectChange = (dataSourceOption: Array<{ id: string; label: string }>) => { + const onDataSourceSelectChange = (dataSourceOption: DataSourceOption[]) => { setDefaultOption(dataSourceOption); handleChange(dataSourceOption); }; useEffect(() => { - if (!defaultDataSourceId || defaultDataSourceId === '') { + if (!defaultDataSourceId) { // @ts-expect-error setDefaultOption(null); return; @@ -78,17 +84,16 @@ export const DataSourcePicker = (props: DataSourcePickerProps) => { disabled={false} fullWidth={false} removePrepend={true} - // @ts-expect-error - filterFunc={(ds) => ds.attributes.auth.type !== 'no_auth'} + hideLocalCluster={props.hideLocalCluster} /> ); }; -export const createDataSourcePickerHandler = (handleChange: any) => { +export const createDataSourcePickerHandler = (handleChange: (e: PanelSchema) => void) => { return (selectedOptions: []): void => { return handleChange?.({ [DATA_SOURCE_ID_KEY]: _.get(selectedOptions, '[0].id', null), - }); + } as PanelSchema); }; }; diff --git a/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js b/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js index 313bf55c5119..95177b70e08e 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js +++ b/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js @@ -46,6 +46,7 @@ import { getSavedObjectsClient, getNotifications, getDataSourceManagementSetup, + getHideLocalCluster, } from '../../services'; import { createSelectHandler } from './lib/create_select_handler'; import { createTextHandler } from './lib/create_text_handler'; @@ -184,6 +185,7 @@ export const IndexPattern = ({ fields, prefix, onChange, disabled, model: _model defaultDataSourceId={model[DATA_SOURCE_ID_KEY] || undefined} dataSourceManagement={getDataSourceManagementSetup().dataSourceManagement} handleChange={handleDataSourceSelectChange} + hideLocalCluster={!!getHideLocalCluster().hideLocalCluster} /> diff --git a/src/plugins/vis_type_timeseries/public/plugin.ts b/src/plugins/vis_type_timeseries/public/plugin.ts index a3c3d946c0fe..b5a20f70971d 100644 --- a/src/plugins/vis_type_timeseries/public/plugin.ts +++ b/src/plugins/vis_type_timeseries/public/plugin.ts @@ -37,6 +37,7 @@ import { Plugin, } from 'opensearch-dashboards/public'; import { DataSourceManagementPluginSetup } from 'src/plugins/data_source_management/public'; +import { DataSourcePluginSetup } from 'src/plugins/data_source/public'; import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public'; import { VisualizationsSetup } from '../../visualizations/public'; @@ -52,6 +53,7 @@ import { setChartsSetup, setDataSourceManagementSetup, setNotifications, + setHideLocalCluster, } from './services'; import { DataPublicPluginStart } from '../../data/public'; import { ChartsPluginSetup } from '../../charts/public'; @@ -62,6 +64,7 @@ export interface MetricsPluginSetupDependencies { visualizations: VisualizationsSetup; charts: ChartsPluginSetup; dataSourceManagement?: DataSourceManagementPluginSetup; + dataSource?: DataSourcePluginSetup; } /** @internal */ @@ -79,13 +82,20 @@ export class MetricsPlugin implements Plugin, void> { public async setup( core: CoreSetup, - { expressions, visualizations, charts, dataSourceManagement }: MetricsPluginSetupDependencies + { + expressions, + visualizations, + charts, + dataSourceManagement, + dataSource, + }: MetricsPluginSetupDependencies ) { expressions.registerFunction(createMetricsFn); setUISettings(core.uiSettings); setChartsSetup(charts); visualizations.createReactVisualization(metricsVisDefinition); setDataSourceManagementSetup({ dataSourceManagement }); + setHideLocalCluster({ hideLocalCluster: dataSource?.hideLocalCluster }); } public start(core: CoreStart, { data }: MetricsPluginStartDependencies) { diff --git a/src/plugins/vis_type_timeseries/public/services.ts b/src/plugins/vis_type_timeseries/public/services.ts index 5f54ac3e7546..46b37a06157e 100644 --- a/src/plugins/vis_type_timeseries/public/services.ts +++ b/src/plugins/vis_type_timeseries/public/services.ts @@ -64,6 +64,10 @@ export const [getDataSourceManagementSetup, setDataSourceManagementSetup] = crea dataSourceManagement: DataSourceManagementPluginSetup | undefined; }>('DataSourceManagementSetup'); +export const [getHideLocalCluster, setHideLocalCluster] = createGetterSetter<{ + hideLocalCluster: boolean | undefined; +}>('HideLocalCluster'); + export const [getNotifications, setNotifications] = createGetterSetter( 'Notifications' ); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_annotations.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_annotations.js index 1b4e9fee5e40..642634b3d5ea 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_annotations.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_annotations.js @@ -55,7 +55,7 @@ export async function getAnnotations({ const annotations = panel.annotations.filter(validAnnotation); const lastSeriesTimestamp = getLastSeriesTimestamp(series); const handleAnnotationResponseBy = handleAnnotationResponse(lastSeriesTimestamp); - const panelDataSourceId = panel[DATA_SOURCE_ID_KEY] || undefined; + const panelDataSourceId = panel[DATA_SOURCE_ID_KEY]; const bodiesPromises = annotations.map((annotation) => getAnnotationRequestParams(req, panel, annotation, opensearchQueryConfig, capabilities) diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.js index e1973fc3d513..b59b28b5c747 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.js @@ -42,7 +42,7 @@ export async function getSeriesData(req, panel) { capabilities, } = await req.framework.searchStrategyRegistry.getViableStrategyForPanel(req, panel); const opensearchQueryConfig = await getOpenSearchQueryConfig(req); - const panelDataSourceId = panel[DATA_SOURCE_ID_KEY] || undefined; + const panelDataSourceId = panel[DATA_SOURCE_ID_KEY]; const meta = { type: panel.type, uiRestrictions: capabilities.uiRestrictions, diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.js index 6a7ed3434fcb..67da6b3850a7 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.js @@ -38,7 +38,7 @@ import { DATA_SOURCE_ID_KEY } from '../../../common/constants'; export async function getTableData(req, panel) { const panelIndexPattern = panel.index_pattern; - const panelDataSourceId = panel[DATA_SOURCE_ID_KEY] || undefined; + const panelDataSourceId = panel[DATA_SOURCE_ID_KEY]; const { searchStrategy, From 26cd786980e0698589a124118bc58ee70b6ad8bf Mon Sep 17 00:00:00 2001 From: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> Date: Thu, 4 Apr 2024 21:48:18 +0000 Subject: [PATCH 08/15] Remove Picker wrapper Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> --- .../components/data_source_picker.tsx | 94 +------------------ .../application/components/index_pattern.js | 13 ++- 2 files changed, 9 insertions(+), 98 deletions(-) diff --git a/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.tsx b/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.tsx index 15eeba0aa61e..1634e08460b7 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.tsx @@ -2,107 +2,15 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ - -import React, { useEffect, useState } from 'react'; import _ from 'lodash'; -import { SavedObjectsClientContract, ToastsStart } from 'src/core/public'; -import { - DataSourceManagementPluginSetup, - DataSourceOption, -} from 'src/plugins/data_source_management/public'; import { PanelSchema } from 'src/plugins/vis_type_timeseries/common/types'; import { DATA_SOURCE_ID_KEY } from '../../../common/constants'; -/** - * Provide only the necessary plugin setup/start and dataSourceId and this component will render a DataSourceSelector with a default selection - * - * @property {SavedObjectsClientContract} savedObjectsClient - * @property {DataSourceManagementPluginSetup} dataSourceManagement - * @property {ToastsStart} toasts - * @property {string} [defaultDataSourceId] - the datasource id as the default option when the component first renders - * @property {(e: DataSourceOption[]) => void} handleChange - the function that will update the model when a datasource is selected - * @property {boolean} hideLocalCluster - the config option to hide the local cluster - */ -export interface DataSourcePickerProps { - savedObjectsClient: SavedObjectsClientContract; - dataSourceManagement: DataSourceManagementPluginSetup; - toasts: ToastsStart; - defaultDataSourceId?: string; - handleChange: (e: DataSourceOption[]) => void; - hideLocalCluster: boolean; -} - -/** - * Provides a wrapper around the DataSourceSelector component exposed by core - * - * @param {DataSourcePickerProps} props - * @returns the DataSourceSelector - */ -export const DataSourcePicker = (props: DataSourcePickerProps) => { - const { savedObjectsClient, defaultDataSourceId, handleChange } = props; - const [defaultOption, setDefaultOption] = useState(); - const DataSourceSelector = props.dataSourceManagement.ui.DataSourceSelector; - - const onDataSourceSelectChange = (dataSourceOption: DataSourceOption[]) => { - setDefaultOption(dataSourceOption); - handleChange(dataSourceOption); - }; - - useEffect(() => { - if (!defaultDataSourceId) { - // @ts-expect-error - setDefaultOption(null); - return; - } - - getDataSourceTitleFromId(defaultDataSourceId, savedObjectsClient).then((label) => { - if (!!label) { - setDefaultOption([ - { - id: defaultDataSourceId, - label, - }, - ]); - return; - } - // @ts-expect-error - setDefaultOption(null); - }); - }, [defaultDataSourceId, savedObjectsClient]); - - if (defaultOption === undefined) { - return
Loading...
; - } - - return ( - - ); -}; - export const createDataSourcePickerHandler = (handleChange: (e: PanelSchema) => void) => { return (selectedOptions: []): void => { return handleChange?.({ [DATA_SOURCE_ID_KEY]: _.get(selectedOptions, '[0].id', null), } as PanelSchema); }; -}; - -const getDataSourceTitleFromId = async ( - id: string, - savedObjectsClient: SavedObjectsClientContract -) => { - return savedObjectsClient.get('data-source', id).then((response) => { - // @ts-expect-error - return response.attributes ? response.attributes.title : undefined; - }); -}; +}; \ No newline at end of file diff --git a/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js b/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js index 95177b70e08e..28f1d30627dc 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js +++ b/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js @@ -124,6 +124,7 @@ export const IndexPattern = ({ fields, prefix, onChange, disabled, model: _model const dataSourceManagementEnabled = !!getDataSourceManagementSetup().dataSourceManagement || false; const handleDataSourceSelectChange = createDataSourcePickerHandler(onChange); + const DataSourceSelector = getDataSourceManagementSetup().dataSourceManagement.ui.DataSourceSelector; const isDefaultIndexPatternUsed = model.default_index_pattern && !model[indexPatternName]; const intervalValidation = validateIntervalValue(model[intervalName]); @@ -179,12 +180,14 @@ export const IndexPattern = ({ fields, prefix, onChange, disabled, model: _model defaultMessage: 'Data source', })} > - From c880943b6bd419922f0a0a082b0beeae2d81755b Mon Sep 17 00:00:00 2001 From: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> Date: Wed, 10 Apr 2024 21:20:33 +0000 Subject: [PATCH 09/15] Update selector component and rename field to index name Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> --- .../data_source_selector.tsx | 2 + .../data_source_picker.test.tsx.snap | 1888 ----------------- .../components/data_source_picker.test.tsx | 157 -- .../application/components/index_pattern.js | 23 +- 4 files changed, 16 insertions(+), 2054 deletions(-) delete mode 100644 src/plugins/vis_type_timeseries/public/application/components/__snapshots__/data_source_picker.test.tsx.snap delete mode 100644 src/plugins/vis_type_timeseries/public/application/components/data_source_picker.test.tsx diff --git a/src/plugins/data_source_management/public/components/data_source_selector/data_source_selector.tsx b/src/plugins/data_source_management/public/components/data_source_selector/data_source_selector.tsx index fa0235a6ec3d..11932fcfd893 100644 --- a/src/plugins/data_source_management/public/components/data_source_selector/data_source_selector.tsx +++ b/src/plugins/data_source_management/public/components/data_source_selector/data_source_selector.tsx @@ -31,6 +31,7 @@ export interface DataSourceSelectorProps { dataSourceFilter?: (dataSource: SavedObject) => boolean; compressed?: boolean; uiSettings?: IUiSettingsClient; + isClearable?: boolean; } interface DataSourceSelectorState { @@ -204,6 +205,7 @@ export class DataSourceSelector extends React.Component< return ( - - - -
- - -
-
-
- - - some-title-for-some-valid-id - - - -
- -
-
- -
- -
- - - - - - - - -
-
-
-
- - -
- - - - -`; - -exports[`DataSourcePicker render local cluster when datasource id = "" 1`] = ` - - - - -
- - -
-
-
- - - Local cluster - - - -
- -
-
- -
- -
- - - - - - - - -
-
-
-
- - -
- - - - -`; - -exports[`DataSourcePicker render local cluster when datasource id is not in saved objects 1`] = ` - - - - -
- - -
-
-
- - - Local cluster - - - -
- -
-
- -
- -
- - - - - - - - -
-
-
-
- - -
- - - - -`; - -exports[`DataSourcePicker should render local cluster without datasource id provided 1`] = ` - - - - -
- - -
-
-
- - - Local cluster - - - -
- -
-
- -
- -
- - - - - - - - -
-
-
-
- - -
- - - - -`; diff --git a/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.test.tsx b/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.test.tsx deleted file mode 100644 index bfa942f70e9b..000000000000 --- a/src/plugins/vis_type_timeseries/public/application/components/data_source_picker.test.tsx +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React from 'react'; -import { mount } from 'enzyme'; - -import { DataSourcePicker } from './data_source_picker'; -import { - coreMock, - notificationServiceMock, - savedObjectsServiceMock, -} from '../../../../../core/public/mocks'; -import { testDataSourceManagementPlugin } from '../../../../data_source_management/public/mocks'; -import { DataSourceSelector } from '../../../../data_source_management/public'; - -describe('DataSourcePicker', () => { - const nextTick = () => new Promise((res) => process.nextTick(res)); - const getPluginSetupStartMocks = () => { - const savedObjectsClient = savedObjectsServiceMock.createStartContract().client; - savedObjectsClient.get = jest.fn().mockImplementation((type: string, id: string) => { - if (type === 'data-source' && id !== 'nonexistent-id') { - return Promise.resolve({ - id, - type, - attributes: { - title: `some-title-for-${id}`, - }, - }); - } - - return Promise.resolve({ - id, - type, - error: { - statusCode: 404, - message: 'Not found', - }, - }); - }); - - return { - savedObjectsClient, - dataSourceManagement: testDataSourceManagementPlugin( - coreMock.createSetup(), - coreMock.createStart() - ).setup, - toasts: notificationServiceMock.createStartContract().toasts, - handleChange: jest.fn(), - hideLocalCluster: false, - }; - }; - - test('should render local cluster without datasource id provided', () => { - const { - dataSourceManagement, - savedObjectsClient, - toasts, - handleChange, - } = getPluginSetupStartMocks(); - const component = mount( - - ); - - const dataSourceSelector = component.find(DataSourceSelector); - expect(dataSourceSelector.prop('defaultOption')).toBe(null); - expect(component).toMatchSnapshot(); - expect(toasts.addWarning).toBeCalledTimes(0); - expect(handleChange).toBeCalledTimes(0); - }); - - test('render local cluster when datasource id = ""', () => { - const { - dataSourceManagement, - savedObjectsClient, - toasts, - handleChange, - } = getPluginSetupStartMocks(); - const component = mount( - - ); - - const dataSourceSelector = component.find(DataSourceSelector); - expect(dataSourceSelector.prop('defaultOption')).toBe(null); - expect(component).toMatchSnapshot(); - expect(toasts.addWarning).toBeCalledTimes(0); - expect(handleChange).toBeCalledTimes(0); - }); - - test('render local cluster when datasource id is not in saved objects', async () => { - const { - dataSourceManagement, - savedObjectsClient, - toasts, - handleChange, - } = getPluginSetupStartMocks(); - const component = mount( - - ); - - await nextTick(); - component.update(); - const dataSourceSelector = component.find(DataSourceSelector); - expect(dataSourceSelector.prop('defaultOption')).toBe(null); - expect(component).toMatchSnapshot(); - expect(toasts.addWarning).toBeCalledTimes(0); - expect(handleChange).toBeCalledTimes(0); - expect(savedObjectsClient.get).toBeCalledTimes(1); - }); - - test('render correct default option when datasource id is valid', async () => { - const { - dataSourceManagement, - savedObjectsClient, - toasts, - handleChange, - } = getPluginSetupStartMocks(); - const component = mount( - - ); - - await nextTick(); - component.update(); - const dataSourceSelector = component.find(DataSourceSelector); - expect(dataSourceSelector.prop('defaultOption')).toMatchObject([ - { label: 'some-title-for-some-valid-id', id: 'some-valid-id' }, - ]); - expect(component).toMatchSnapshot(); - expect(toasts.addWarning).toBeCalledTimes(0); - expect(handleChange).toBeCalledTimes(0); - expect(savedObjectsClient.get).toBeCalledTimes(1); - }); -}); diff --git a/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js b/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js index 28f1d30627dc..516597a21228 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js +++ b/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js @@ -41,7 +41,7 @@ import { EuiText, } from '@elastic/eui'; import { FieldSelect } from './aggs/field_select'; -import { DataSourcePicker, createDataSourcePickerHandler } from './data_source_picker'; +import { createDataSourcePickerHandler } from './data_source_picker'; import { getSavedObjectsClient, getNotifications, @@ -64,7 +64,6 @@ import { TIME_RANGE_DATA_MODES, TIME_RANGE_MODE_KEY } from '../../../common/time import { PANEL_TYPES } from '../../../common/panel_types'; import { isTimerangeModeEnabled } from '../lib/check_ui_restrictions'; import { VisDataContext } from '../contexts/vis_data_context'; -import { DATA_SOURCE_ID_KEY } from '../../../common/constants'; const RESTRICT_FIELDS = [OSD_FIELD_TYPES.DATE]; @@ -124,7 +123,8 @@ export const IndexPattern = ({ fields, prefix, onChange, disabled, model: _model const dataSourceManagementEnabled = !!getDataSourceManagementSetup().dataSourceManagement || false; const handleDataSourceSelectChange = createDataSourcePickerHandler(onChange); - const DataSourceSelector = getDataSourceManagementSetup().dataSourceManagement.ui.DataSourceSelector; + const DataSourceSelector = getDataSourceManagementSetup().dataSourceManagement.ui + .DataSourceSelector; const isDefaultIndexPatternUsed = model.default_index_pattern && !model[indexPatternName]; const intervalValidation = validateIntervalValue(model[intervalName]); @@ -184,10 +184,11 @@ export const IndexPattern = ({ fields, prefix, onChange, disabled, model: _model savedObjectsClient={getSavedObjectsClient().client} notifications={getNotifications().toasts} onSelectedDataSource={handleDataSourceSelectChange} - defaultOption={model.data_source_id ? [{ id: model.data_source_id }] : [{id: ""}]} + defaultOption={model.data_source_id ? [{ id: model.data_source_id }] : [{ id: '' }]} disabled={false} fullWidth={false} removePrepend={true} + isClearable={false} hideLocalCluster={!!getHideLocalCluster().hideLocalCluster} /> @@ -199,13 +200,17 @@ export const IndexPattern = ({ fields, prefix, onChange, disabled, model: _model , | are not allowed.', + }) } > Date: Wed, 10 Apr 2024 22:08:52 +0000 Subject: [PATCH 10/15] Address comments Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> --- src/plugins/data_source/server/index.ts | 2 ++ .../data_source/server/util/decide_client.ts | 15 +++++++++++++++ .../application/components/annotations_editor.js | 7 ++++++- .../application/components/index_pattern.js | 7 ++++--- .../create_data_source_change_handler.ts} | 4 ++-- .../vis_type_timeseries/server/lib/get_fields.ts | 7 +++---- .../server/lib/vis_data/get_annotations.js | 3 +-- .../server/lib/vis_data/get_series_data.js | 3 +-- .../server/lib/vis_data/get_table_data.js | 3 +-- 9 files changed, 35 insertions(+), 16 deletions(-) create mode 100644 src/plugins/data_source/server/util/decide_client.ts rename src/plugins/vis_type_timeseries/public/application/components/{data_source_picker.tsx => lib/create_data_source_change_handler.ts} (86%) diff --git a/src/plugins/data_source/server/index.ts b/src/plugins/data_source/server/index.ts index 156b76066fbb..bd558c544bc7 100644 --- a/src/plugins/data_source/server/index.ts +++ b/src/plugins/data_source/server/index.ts @@ -25,3 +25,5 @@ export { DataSourcePluginStart, DataSourcePluginRequestContext, } from './types'; + +export { decideLegacyClient } from './util/decide_client'; diff --git a/src/plugins/data_source/server/util/decide_client.ts b/src/plugins/data_source/server/util/decide_client.ts new file mode 100644 index 000000000000..1e591bb1311e --- /dev/null +++ b/src/plugins/data_source/server/util/decide_client.ts @@ -0,0 +1,15 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { RequestHandlerContext } from 'src/core/server'; + +export const decideLegacyClient = ( + requestContext: RequestHandlerContext, + dataSourceId: string | null +) => { + return !!dataSourceId && !!requestContext.dataSource + ? requestContext.dataSource.opensearch.legacy.getClient(dataSourceId).callAPI + : requestContext.core.opensearch.legacy.client.callAsCurrentUser; +}; diff --git a/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js b/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js index 8a473c396511..f75284ff7439 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js +++ b/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js @@ -55,6 +55,7 @@ import { EuiText, } from '@elastic/eui'; import { FormattedMessage } from '@osd/i18n/react'; +import { i18n } from '@osd/i18n'; function newAnnotation() { return { @@ -131,9 +132,13 @@ export class AnnotationsEditor extends Component { label={ } + helpText={i18n.translate('visTypeTimeseries.indexPattern.searchByIndex', { + defaultMessage: + 'Use an asterisk (*) to match multiple indices. Spaces and the characters , /, ?, ", <, >, | are not allowed.', + })} fullWidth > void) => { return (selectedOptions: []): void => { @@ -13,4 +13,4 @@ export const createDataSourcePickerHandler = (handleChange: (e: PanelSchema) => [DATA_SOURCE_ID_KEY]: _.get(selectedOptions, '[0].id', null), } as PanelSchema); }; -}; \ No newline at end of file +}; diff --git a/src/plugins/vis_type_timeseries/server/lib/get_fields.ts b/src/plugins/vis_type_timeseries/server/lib/get_fields.ts index 5380fb3ff833..5b74488d13c2 100644 --- a/src/plugins/vis_type_timeseries/server/lib/get_fields.ts +++ b/src/plugins/vis_type_timeseries/server/lib/get_fields.ts @@ -39,6 +39,7 @@ import { IndexPatternsFetcher, } from '../../../data/server'; import { ReqFacade } from './search_strategies/strategies/abstract_search_strategy'; +import { decideLegacyClient } from '../../../data_source/server'; export async function getFields( requestContext: RequestHandlerContext, @@ -51,10 +52,8 @@ export async function getFields( // removes the need to refactor many layers of dependencies on "req", and instead just augments the top // level object passed from here. The layers should be refactored fully at some point, but for now // this works and we are still using the New Platform services for these vis data portions. - const client = - !!dataSourceId && !!requestContext.dataSource - ? requestContext.dataSource.opensearch.legacy.getClient(dataSourceId).callAPI - : requestContext.core.opensearch.legacy.client.callAsCurrentUser; + const client = decideLegacyClient(requestContext, dataSourceId); + const reqFacade: ReqFacade = { requestContext, ...request, diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_annotations.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_annotations.js index 642634b3d5ea..3dd88b830346 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_annotations.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_annotations.js @@ -31,7 +31,6 @@ import { handleAnnotationResponse } from './response_processors/annotations'; import { getAnnotationRequestParams } from './annotations/get_request_params'; import { getLastSeriesTimestamp } from './helpers/timestamp'; -import { DATA_SOURCE_ID_KEY } from '../../../common/constants'; function validAnnotation(annotation) { return ( @@ -55,7 +54,7 @@ export async function getAnnotations({ const annotations = panel.annotations.filter(validAnnotation); const lastSeriesTimestamp = getLastSeriesTimestamp(series); const handleAnnotationResponseBy = handleAnnotationResponse(lastSeriesTimestamp); - const panelDataSourceId = panel[DATA_SOURCE_ID_KEY]; + const panelDataSourceId = panel.data_source_id; const bodiesPromises = annotations.map((annotation) => getAnnotationRequestParams(req, panel, annotation, opensearchQueryConfig, capabilities) diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.js index b59b28b5c747..59d445f93324 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_series_data.js @@ -34,7 +34,6 @@ import { handleErrorResponse } from './handle_error_response'; import { getAnnotations } from './get_annotations'; import { getOpenSearchQueryConfig } from './helpers/get_opensearch_query_uisettings'; import { getActiveSeries } from './helpers/get_active_series'; -import { DATA_SOURCE_ID_KEY } from '../../../common/constants'; export async function getSeriesData(req, panel) { const { @@ -42,7 +41,7 @@ export async function getSeriesData(req, panel) { capabilities, } = await req.framework.searchStrategyRegistry.getViableStrategyForPanel(req, panel); const opensearchQueryConfig = await getOpenSearchQueryConfig(req); - const panelDataSourceId = panel[DATA_SOURCE_ID_KEY]; + const panelDataSourceId = panel.data_source_id; const meta = { type: panel.type, uiRestrictions: capabilities.uiRestrictions, diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.js index 67da6b3850a7..20dfa06c1a78 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.js @@ -34,11 +34,10 @@ import { get } from 'lodash'; import { processBucket } from './table/process_bucket'; import { getOpenSearchQueryConfig } from './helpers/get_opensearch_query_uisettings'; import { getIndexPatternObject } from './helpers/get_index_pattern'; -import { DATA_SOURCE_ID_KEY } from '../../../common/constants'; export async function getTableData(req, panel) { const panelIndexPattern = panel.index_pattern; - const panelDataSourceId = panel[DATA_SOURCE_ID_KEY]; + const panelDataSourceId = panel.data_source_id; const { searchStrategy, From 64c45e8dcd9b08a64bf14c6718d9877cafb8d732 Mon Sep 17 00:00:00 2001 From: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> Date: Thu, 11 Apr 2024 22:38:38 +0000 Subject: [PATCH 11/15] Refactor to use different decideClient Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> --- src/plugins/data/server/index.ts | 1 + src/plugins/data/server/index_patterns/index.ts | 1 + src/plugins/data/server/index_patterns/routes.ts | 2 +- src/plugins/data_source/server/index.ts | 2 -- .../data_source/server/util/decide_client.ts | 15 --------------- .../public/application/lib/fetch_fields.js | 2 +- .../vis_type_timeseries/server/lib/get_fields.ts | 7 +++---- .../vis_type_timeseries/server/routes/fields.ts | 4 ++-- 8 files changed, 9 insertions(+), 25 deletions(-) delete mode 100644 src/plugins/data_source/server/util/decide_client.ts diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index 46c2b1ca0477..4bc3ad62a4ae 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -145,6 +145,7 @@ export { FieldDescriptor as IndexPatternFieldDescriptor, shouldReadFieldFromDocValues, // used only in logstash_fields fixture FieldDescriptor, + decideClient, } from './index_patterns'; export { diff --git a/src/plugins/data/server/index_patterns/index.ts b/src/plugins/data/server/index_patterns/index.ts index b2e832294e41..771aa9c09ab8 100644 --- a/src/plugins/data/server/index_patterns/index.ts +++ b/src/plugins/data/server/index_patterns/index.ts @@ -31,3 +31,4 @@ export * from './utils'; export { IndexPatternsFetcher, FieldDescriptor, shouldReadFieldFromDocValues } from './fetcher'; export { IndexPatternsService, IndexPatternsServiceStart } from './index_patterns_service'; +export { decideClient } from './routes'; diff --git a/src/plugins/data/server/index_patterns/routes.ts b/src/plugins/data/server/index_patterns/routes.ts index 3adc1970dd81..8b3c7139ffc0 100644 --- a/src/plugins/data/server/index_patterns/routes.ts +++ b/src/plugins/data/server/index_patterns/routes.ts @@ -155,7 +155,7 @@ export function registerRoutes(http: HttpServiceSetup) { ); } -const decideClient = async ( +export const decideClient = async ( context: RequestHandlerContext, request: any ): Promise => { diff --git a/src/plugins/data_source/server/index.ts b/src/plugins/data_source/server/index.ts index bd558c544bc7..156b76066fbb 100644 --- a/src/plugins/data_source/server/index.ts +++ b/src/plugins/data_source/server/index.ts @@ -25,5 +25,3 @@ export { DataSourcePluginStart, DataSourcePluginRequestContext, } from './types'; - -export { decideLegacyClient } from './util/decide_client'; diff --git a/src/plugins/data_source/server/util/decide_client.ts b/src/plugins/data_source/server/util/decide_client.ts deleted file mode 100644 index 1e591bb1311e..000000000000 --- a/src/plugins/data_source/server/util/decide_client.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { RequestHandlerContext } from 'src/core/server'; - -export const decideLegacyClient = ( - requestContext: RequestHandlerContext, - dataSourceId: string | null -) => { - return !!dataSourceId && !!requestContext.dataSource - ? requestContext.dataSource.opensearch.legacy.getClient(dataSourceId).callAPI - : requestContext.core.opensearch.legacy.client.callAsCurrentUser; -}; diff --git a/src/plugins/vis_type_timeseries/public/application/lib/fetch_fields.js b/src/plugins/vis_type_timeseries/public/application/lib/fetch_fields.js index 1bf1ae03bd1e..8aa9bb618cad 100644 --- a/src/plugins/vis_type_timeseries/public/application/lib/fetch_fields.js +++ b/src/plugins/vis_type_timeseries/public/application/lib/fetch_fields.js @@ -41,7 +41,7 @@ export async function fetchFields(indexPatterns = ['*'], dataSourceId = undefine return getCoreStart().http.get('/api/metrics/fields', { query: { index: pattern, - dataSourceId, + data_source: dataSourceId, }, }); }) diff --git a/src/plugins/vis_type_timeseries/server/lib/get_fields.ts b/src/plugins/vis_type_timeseries/server/lib/get_fields.ts index 5b74488d13c2..56a58b43b45e 100644 --- a/src/plugins/vis_type_timeseries/server/lib/get_fields.ts +++ b/src/plugins/vis_type_timeseries/server/lib/get_fields.ts @@ -37,22 +37,21 @@ import { indexPatterns, IndexPatternFieldDescriptor, IndexPatternsFetcher, + decideClient, } from '../../../data/server'; import { ReqFacade } from './search_strategies/strategies/abstract_search_strategy'; -import { decideLegacyClient } from '../../../data_source/server'; export async function getFields( requestContext: RequestHandlerContext, request: OpenSearchDashboardsRequest, framework: Framework, - indexPattern: string, - dataSourceId: string | null + indexPattern: string ) { // NOTE / TODO: This facade has been put in place to make migrating to the New Platform easier. It // removes the need to refactor many layers of dependencies on "req", and instead just augments the top // level object passed from here. The layers should be refactored fully at some point, but for now // this works and we are still using the New Platform services for these vis data portions. - const client = decideLegacyClient(requestContext, dataSourceId); + const client = await decideClient(requestContext, request); const reqFacade: ReqFacade = { requestContext, diff --git a/src/plugins/vis_type_timeseries/server/routes/fields.ts b/src/plugins/vis_type_timeseries/server/routes/fields.ts index c0ace9d35677..80a04918c517 100644 --- a/src/plugins/vis_type_timeseries/server/routes/fields.ts +++ b/src/plugins/vis_type_timeseries/server/routes/fields.ts @@ -40,14 +40,14 @@ export const fieldsRoutes = (framework: Framework) => { validate: { query: schema.object({ index: schema.string(), - dataSourceId: schema.nullable(schema.string()), + data_source: schema.maybe(schema.string()), }), }, }, async (context, req, res) => { try { return res.ok({ - body: await getFields(context, req, framework, req.query.index, req.query.dataSourceId), + body: await getFields(context, req, framework, req.query.index), }); } catch (err) { if (isBoom(err) && err.output.statusCode === 401) { From 817e3427e4cec7fd2323c7031bf7d7d8ecdd424a Mon Sep 17 00:00:00 2001 From: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> Date: Thu, 11 Apr 2024 22:54:01 +0000 Subject: [PATCH 12/15] Add optional arg Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> --- .../search_strategies/strategies/abstract_search_strategy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts index 216a412abb89..7c333fad7f73 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts @@ -66,7 +66,7 @@ export class AbstractSearchStrategy { this.additionalParams = additionalParams; } - async search(req: ReqFacade, bodies: any[], options = {}, dataSourceId: string) { + async search(req: ReqFacade, bodies: any[], options = {}, dataSourceId?: string) { const [, deps] = await req.framework.core.getStartServices(); const requests: any[] = []; bodies.forEach((body) => { From 1436a53ffeb78f3f3da3a6113a727fb19c3da594 Mon Sep 17 00:00:00 2001 From: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> Date: Tue, 16 Apr 2024 02:20:16 +0000 Subject: [PATCH 13/15] Remove hidelocalcluster as a setting Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> --- .../public/application/components/index_pattern.js | 2 -- src/plugins/vis_type_timeseries/public/plugin.ts | 2 -- src/plugins/vis_type_timeseries/public/services.ts | 4 ---- 3 files changed, 8 deletions(-) diff --git a/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js b/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js index a95f51c0732c..c22c4f11c9b2 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js +++ b/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js @@ -46,7 +46,6 @@ import { getSavedObjectsClient, getNotifications, getDataSourceManagementSetup, - getHideLocalCluster, } from '../../services'; import { createSelectHandler } from './lib/create_select_handler'; import { createTextHandler } from './lib/create_text_handler'; @@ -190,7 +189,6 @@ export const IndexPattern = ({ fields, prefix, onChange, disabled, model: _model fullWidth={false} removePrepend={true} isClearable={false} - hideLocalCluster={!!getHideLocalCluster().hideLocalCluster} /> diff --git a/src/plugins/vis_type_timeseries/public/plugin.ts b/src/plugins/vis_type_timeseries/public/plugin.ts index b5a20f70971d..0220a5320422 100644 --- a/src/plugins/vis_type_timeseries/public/plugin.ts +++ b/src/plugins/vis_type_timeseries/public/plugin.ts @@ -53,7 +53,6 @@ import { setChartsSetup, setDataSourceManagementSetup, setNotifications, - setHideLocalCluster, } from './services'; import { DataPublicPluginStart } from '../../data/public'; import { ChartsPluginSetup } from '../../charts/public'; @@ -95,7 +94,6 @@ export class MetricsPlugin implements Plugin, void> { setChartsSetup(charts); visualizations.createReactVisualization(metricsVisDefinition); setDataSourceManagementSetup({ dataSourceManagement }); - setHideLocalCluster({ hideLocalCluster: dataSource?.hideLocalCluster }); } public start(core: CoreStart, { data }: MetricsPluginStartDependencies) { diff --git a/src/plugins/vis_type_timeseries/public/services.ts b/src/plugins/vis_type_timeseries/public/services.ts index 46b37a06157e..5f54ac3e7546 100644 --- a/src/plugins/vis_type_timeseries/public/services.ts +++ b/src/plugins/vis_type_timeseries/public/services.ts @@ -64,10 +64,6 @@ export const [getDataSourceManagementSetup, setDataSourceManagementSetup] = crea dataSourceManagement: DataSourceManagementPluginSetup | undefined; }>('DataSourceManagementSetup'); -export const [getHideLocalCluster, setHideLocalCluster] = createGetterSetter<{ - hideLocalCluster: boolean | undefined; -}>('HideLocalCluster'); - export const [getNotifications, setNotifications] = createGetterSetter( 'Notifications' ); From 76ed1a4dd06d86e722e4a1bb6bb5aa81e7786f53 Mon Sep 17 00:00:00 2001 From: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> Date: Tue, 16 Apr 2024 17:33:38 +0000 Subject: [PATCH 14/15] Fixed case where local cluster is disabled but the datasource id could be local cluster Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> --- .../public/application/components/index_pattern.js | 4 +++- .../components/lib/create_data_source_change_handler.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js b/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js index c22c4f11c9b2..da05e3e90138 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js +++ b/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js @@ -184,7 +184,9 @@ export const IndexPattern = ({ fields, prefix, onChange, disabled, model: _model savedObjectsClient={getSavedObjectsClient().client} notifications={getNotifications().toasts} onSelectedDataSource={handleDataSourceSelectChange} - defaultOption={model.data_source_id ? [{ id: model.data_source_id }] : [{ id: '' }]} + defaultOption={ + model.data_source_id !== undefined ? [{ id: model.data_source_id }] : undefined + } disabled={false} fullWidth={false} removePrepend={true} diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/create_data_source_change_handler.ts b/src/plugins/vis_type_timeseries/public/application/components/lib/create_data_source_change_handler.ts index 441fa7d3b169..5fa18d74c5b3 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/lib/create_data_source_change_handler.ts +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/create_data_source_change_handler.ts @@ -10,7 +10,7 @@ import { DATA_SOURCE_ID_KEY } from '../../../../common/constants'; export const createDataSourcePickerHandler = (handleChange: (e: PanelSchema) => void) => { return (selectedOptions: []): void => { return handleChange?.({ - [DATA_SOURCE_ID_KEY]: _.get(selectedOptions, '[0].id', null), + [DATA_SOURCE_ID_KEY]: _.get(selectedOptions, '[0].id', undefined), } as PanelSchema); }; }; From fe2b79442202456346da81155f7f04f71ae45431 Mon Sep 17 00:00:00 2001 From: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> Date: Tue, 16 Apr 2024 17:53:48 +0000 Subject: [PATCH 15/15] Add test for create data source picker handler Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> --- .../create_data_source_change_handler.test.ts | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/plugins/vis_type_timeseries/public/application/components/lib/create_data_source_change_handler.test.ts diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/create_data_source_change_handler.test.ts b/src/plugins/vis_type_timeseries/public/application/components/lib/create_data_source_change_handler.test.ts new file mode 100644 index 000000000000..2a95a2756b50 --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/create_data_source_change_handler.test.ts @@ -0,0 +1,49 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { createDataSourcePickerHandler } from './create_data_source_change_handler'; + +describe('createDataSourcePickerHandler()', () => { + let handleChange: jest.Mock; + let changeHandler: (selectedOptions: []) => void; + + beforeEach(() => { + handleChange = jest.fn(); + changeHandler = createDataSourcePickerHandler(handleChange); + }); + + test.each([ + { + id: undefined, + }, + {}, + ])( + 'calls handleChange() and sets data_source_id to undefined if id cannot be found or is undefined', + ({ id }) => { + // @ts-ignore + changeHandler([{ id }]); + expect(handleChange.mock.calls.length).toEqual(1); + expect(handleChange.mock.calls[0][0]).toEqual({ + data_source_id: undefined, + }); + } + ); + + test.each([ + { + id: '', + }, + { + id: 'foo', + }, + ])('calls handleChange() function with partial and updates the data_source_id', ({ id }) => { + // @ts-ignore + changeHandler([{ id }]); + expect(handleChange.mock.calls.length).toEqual(1); + expect(handleChange.mock.calls[0][0]).toEqual({ + data_source_id: id, + }); + }); +});