From fbbffef944ccafa6064c4d7778b00907b1ba649e Mon Sep 17 00:00:00 2001 From: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> Date: Wed, 3 Apr 2024 23:51:37 +0000 Subject: [PATCH 01/10] Add MDS support to Timeline Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> --- .../opensearch_dashboards.json | 1 + .../vis_type_timeline/server/lib/services.ts | 10 ++++++ .../vis_type_timeline/server/plugin.ts | 10 +++++- .../series_functions/opensearch/index.js | 10 +++++- .../opensearch/lib/build_request.js | 31 ++++++++++++++++++- 5 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 src/plugins/vis_type_timeline/server/lib/services.ts diff --git a/src/plugins/vis_type_timeline/opensearch_dashboards.json b/src/plugins/vis_type_timeline/opensearch_dashboards.json index 14b3af41517..c4fa1e8d40f 100644 --- a/src/plugins/vis_type_timeline/opensearch_dashboards.json +++ b/src/plugins/vis_type_timeline/opensearch_dashboards.json @@ -4,6 +4,7 @@ "opensearchDashboardsVersion": "opensearchDashboards", "server": true, "ui": true, + "optionalPlugins": ["dataSource"], "requiredPlugins": ["visualizations", "data", "expressions"], "requiredBundles": ["opensearchDashboardsUtils", "opensearchDashboardsReact", "visDefaultEditor"] } diff --git a/src/plugins/vis_type_timeline/server/lib/services.ts b/src/plugins/vis_type_timeline/server/lib/services.ts new file mode 100644 index 00000000000..13b257622ab --- /dev/null +++ b/src/plugins/vis_type_timeline/server/lib/services.ts @@ -0,0 +1,10 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { createGetterSetter } from '../../../opensearch_dashboards_utils/common'; + +export const [getDataSourceEnabled, setDataSourceEnabled] = createGetterSetter<{ + enabled: boolean; +}>('DataSource'); diff --git a/src/plugins/vis_type_timeline/server/plugin.ts b/src/plugins/vis_type_timeline/server/plugin.ts index d2c7097ac41..f768e51e93d 100644 --- a/src/plugins/vis_type_timeline/server/plugin.ts +++ b/src/plugins/vis_type_timeline/server/plugin.ts @@ -34,6 +34,7 @@ import { TypeOf, schema } from '@osd/config-schema'; import { RecursiveReadonly } from '@osd/utility-types'; import { deepFreeze } from '@osd/std'; +import { DataSourcePluginSetup } from 'src/plugins/data_source/server'; import { PluginStart } from '../../data/server'; import { CoreSetup, PluginInitializerContext } from '../../../core/server'; import { configSchema } from '../config'; @@ -42,11 +43,16 @@ import { functionsRoute } from './routes/functions'; import { validateOpenSearchRoute } from './routes/validate_es'; import { runRoute } from './routes/run'; import { ConfigManager } from './lib/config_manager'; +import { setDataSourceEnabled } from './lib/services'; const experimentalLabel = i18n.translate('timeline.uiSettings.experimentalLabel', { defaultMessage: 'experimental', }); +export interface TimelinePluginSetupDeps { + dataSource?: DataSourcePluginSetup; +} + export interface TimelinePluginStartDeps { data: PluginStart; } @@ -57,7 +63,7 @@ export interface TimelinePluginStartDeps { export class Plugin { constructor(private readonly initializerContext: PluginInitializerContext) {} - public async setup(core: CoreSetup): void { + public async setup(core: CoreSetup, { dataSource }: TimelinePluginSetupDeps): void { const config = await this.initializerContext.config .create>() .pipe(first()) @@ -80,6 +86,8 @@ export class Plugin { ); }; + setDataSourceEnabled({ enabled: !!dataSource }); + const logger = this.initializerContext.logger.get('timeline'); const router = core.http.createRouter(); diff --git a/src/plugins/vis_type_timeline/server/series_functions/opensearch/index.js b/src/plugins/vis_type_timeline/server/series_functions/opensearch/index.js index 8837116bfc0..cbfcd516b6a 100644 --- a/src/plugins/vis_type_timeline/server/series_functions/opensearch/index.js +++ b/src/plugins/vis_type_timeline/server/series_functions/opensearch/index.js @@ -112,6 +112,14 @@ export default new Datasource('es', { defaultMessage: `**DO NOT USE THIS**. It's fun for debugging fit functions, but you really should use the interval picker`, }), }, + { + name: 'data_source_name', + types: ['string, null'], // If null, the query will proceed with local cluster + help: i18n.translate('timeline.help.functions.opensearch.args.dataSourceNameHelpText', { + defaultMessage: + 'Specify a data source to query from. This will only work if multiple data sources is enabled', + }), + }, ], help: i18n.translate('timeline.help.functions.opensearchHelpText', { defaultMessage: 'Pull data from an opensearch instance', @@ -148,7 +156,7 @@ export default new Datasource('es', { const opensearchShardTimeout = tlConfig.opensearchShardTimeout; - const body = buildRequest(config, tlConfig, scriptedFields, opensearchShardTimeout); + const body = await buildRequest(config, tlConfig, scriptedFields, opensearchShardTimeout); const deps = (await tlConfig.getStartServices())[1]; diff --git a/src/plugins/vis_type_timeline/server/series_functions/opensearch/lib/build_request.js b/src/plugins/vis_type_timeline/server/series_functions/opensearch/lib/build_request.js index 8436b4dbb04..71c3ff233c7 100644 --- a/src/plugins/vis_type_timeline/server/series_functions/opensearch/lib/build_request.js +++ b/src/plugins/vis_type_timeline/server/series_functions/opensearch/lib/build_request.js @@ -33,8 +33,9 @@ import moment from 'moment'; import { buildAggBody } from './agg_body'; import createDateAgg from './create_date_agg'; import { UI_SETTINGS } from '../../../../../data/server'; +import { getDataSourceEnabled } from '../../../lib/services'; -export default function buildRequest(config, tlConfig, scriptedFields, timeout) { +export default async function buildRequest(config, tlConfig, scriptedFields, timeout) { const bool = { must: [] }; const timeFilter = { @@ -48,6 +49,33 @@ export default function buildRequest(config, tlConfig, scriptedFields, timeout) }; bool.must.push(timeFilter); + let dataSourceId = undefined; + if (config.data_source_name) { + if (!getDataSourceEnabled().enabled) { + throw new Error('data_source_name cannot be used because data_source.enabled is false'); + } + + const dataSources = await tlConfig.savedObjectsClient.find({ + type: 'data-source', + perPage: 10, + search: `"${config.data_source_name}"`, + searchFields: ['title'], + fields: ['id', 'title'], + }); + + const possibleDataSourceIds = dataSources.saved_objects.filter( + (obj) => obj.attributes.title === config.data_source_name + ); + + if (possibleDataSourceIds.length !== 1) { + throw new Error( + `Expected exactly 1 result for data_source_name "${config.data_source_name}" but got ${possibleDataSourceIds.length} results` + ); + } + + dataSourceId = possibleDataSourceIds.pop()?.id; + } + // Use the opensearchDashboards and kibana filter bar filters if (config.opensearchDashboards && config.kibana) { bool.filter = _.get(tlConfig, 'request.body.extended.es.filter'); @@ -105,6 +133,7 @@ export default function buildRequest(config, tlConfig, scriptedFields, timeout) } return { + ...(!!dataSourceId && { dataSourceId }), params: request, }; } From faa28637cda5e5d38c3b3af68a289a73e106bf15 Mon Sep 17 00:00:00 2001 From: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> Date: Tue, 9 Apr 2024 18:15:59 +0000 Subject: [PATCH 02/10] Refactor to function and add unit tests Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> --- .../server/lib/fetch_data_source_id.test.ts | 149 ++++++++++++++++++ .../server/lib/fetch_data_source_id.ts | 42 +++++ .../opensearch/lib/build_request.js | 29 +--- src/plugins/vis_type_timeline/server/types.ts | 14 ++ 4 files changed, 207 insertions(+), 27 deletions(-) create mode 100644 src/plugins/vis_type_timeline/server/lib/fetch_data_source_id.test.ts create mode 100644 src/plugins/vis_type_timeline/server/lib/fetch_data_source_id.ts diff --git a/src/plugins/vis_type_timeline/server/lib/fetch_data_source_id.test.ts b/src/plugins/vis_type_timeline/server/lib/fetch_data_source_id.test.ts new file mode 100644 index 00000000000..5fc89afe333 --- /dev/null +++ b/src/plugins/vis_type_timeline/server/lib/fetch_data_source_id.test.ts @@ -0,0 +1,149 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { savedObjectsClientMock } from '../../../../core/server/mocks'; +import { fetchDataSourceIdByName } from './fetch_data_source_id'; +import { OpenSearchFunctionConfig } from '../types'; + +jest.mock('./services', () => ({ + getDataSourceEnabled: jest + .fn() + .mockReturnValueOnce({ enabled: false }) + .mockReturnValue({ enabled: true }), +})); + +describe('fetchDataSourceIdByName()', () => { + const validId = 'some-valid-id'; + const config: OpenSearchFunctionConfig = { + q: null, + metric: null, + split: null, + index: null, + timefield: null, + kibana: null, + interval: null, + }; + const client = savedObjectsClientMock.create(); + client.find = jest.fn().mockImplementation((props) => { + if (props.search === '"No Results With Filter"') { + return Promise.resolve({ + saved_objects: [ + { + id: 'some-non-matching-id', + attributes: { + title: 'No Results With Filter Some Suffix', + }, + }, + ], + }); + } + if (props.search === '"Duplicate Title"') { + return Promise.resolve({ + saved_objects: [ + { + id: 'duplicate-id-1', + attributes: { + title: 'Duplicate Title', + }, + }, + { + id: 'duplicate-id-2', + attributes: { + title: 'Duplicate Title', + }, + }, + ], + }); + } + if (props.search === '"Some Data Source"') { + return Promise.resolve({ + saved_objects: [ + { + id: validId, + attributes: { + title: 'Some Data Source', + }, + }, + ], + }); + } + if (props.search === '"Some Prefix"') { + return Promise.resolve({ + saved_objects: [ + { + id: 'some-id-2', + attributes: { + title: 'Some Prefix B', + }, + }, + { + id: validId, + attributes: { + title: 'Some Prefix', + }, + }, + ], + }); + } + + return Promise.resolve({ saved_objects: [] }); + }); + + it('should return undefined if data_source_name is not present', async () => { + expect(await fetchDataSourceIdByName(config, client)).toBe(undefined); + }); + + it('should return undefined if data_source_name is an empty string', async () => { + expect(await fetchDataSourceIdByName({ ...config, data_source_name: '' }, client)).toBe( + undefined + ); + }); + + it('should throw errors when MDS is disabled', async () => { + await expect( + fetchDataSourceIdByName({ ...config, data_source_name: 'Some Data Source' }, client) + ).rejects.toThrowError('data_source_name cannot be used because data_source.enabled is false'); + }); + + it.each([ + { + dataSourceName: 'Non-existent Data Source', + expectedResultCount: 0, + }, + { + dataSourceName: 'No Results With Filter', + expectedResultCount: 0, + }, + { + dataSourceName: 'Duplicate Title', + expectedResultCount: 2, + }, + ])( + 'should throw errors when non-existent or duplicate data_source_name is provided', + async ({ dataSourceName, expectedResultCount }) => { + await expect( + fetchDataSourceIdByName({ ...config, data_source_name: dataSourceName }, client) + ).rejects.toThrowError( + `Expected exactly 1 result for data_source_name "${dataSourceName}" but got ${expectedResultCount} results` + ); + } + ); + + it.each([ + { + dataSourceName: 'Some Data Source', + }, + { + dataSourceName: 'Some Prefix', + }, + ])( + 'should return valid id when data_source_name exists and is unique', + async ({ dataSourceName }) => { + expect( + await fetchDataSourceIdByName({ ...config, data_source_name: dataSourceName }, client) + ).toBe(validId); + } + ); +}); diff --git a/src/plugins/vis_type_timeline/server/lib/fetch_data_source_id.ts b/src/plugins/vis_type_timeline/server/lib/fetch_data_source_id.ts new file mode 100644 index 00000000000..c6521cafca2 --- /dev/null +++ b/src/plugins/vis_type_timeline/server/lib/fetch_data_source_id.ts @@ -0,0 +1,42 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { SavedObjectsClientContract } from 'src/core/server'; +import { DataSourceAttributes } from 'src/plugins/data_source/common/data_sources'; +import { getDataSourceEnabled } from './services'; +import { OpenSearchFunctionConfig } from '../types'; + +export const fetchDataSourceIdByName = async ( + config: OpenSearchFunctionConfig, + client: SavedObjectsClientContract +) => { + if (config.data_source_name) { + if (!getDataSourceEnabled().enabled) { + throw new Error('data_source_name cannot be used because data_source.enabled is false'); + } + + const dataSources = await client.find({ + type: 'data-source', + perPage: 100, + search: `"${config.data_source_name}"`, + searchFields: ['title'], + fields: ['id', 'title'], + }); + + const possibleDataSourceIds = dataSources.saved_objects.filter( + (obj) => obj.attributes.title === config.data_source_name + ); + + if (possibleDataSourceIds.length !== 1) { + throw new Error( + `Expected exactly 1 result for data_source_name "${config.data_source_name}" but got ${possibleDataSourceIds.length} results` + ); + } + + return possibleDataSourceIds.pop()?.id; + } + + return undefined; +}; diff --git a/src/plugins/vis_type_timeline/server/series_functions/opensearch/lib/build_request.js b/src/plugins/vis_type_timeline/server/series_functions/opensearch/lib/build_request.js index 71c3ff233c7..ce0acb58040 100644 --- a/src/plugins/vis_type_timeline/server/series_functions/opensearch/lib/build_request.js +++ b/src/plugins/vis_type_timeline/server/series_functions/opensearch/lib/build_request.js @@ -33,7 +33,7 @@ import moment from 'moment'; import { buildAggBody } from './agg_body'; import createDateAgg from './create_date_agg'; import { UI_SETTINGS } from '../../../../../data/server'; -import { getDataSourceEnabled } from '../../../lib/services'; +import { fetchDataSourceIdByName } from '../../../lib/fetch_data_source_id'; export default async function buildRequest(config, tlConfig, scriptedFields, timeout) { const bool = { must: [] }; @@ -49,32 +49,7 @@ export default async function buildRequest(config, tlConfig, scriptedFields, tim }; bool.must.push(timeFilter); - let dataSourceId = undefined; - if (config.data_source_name) { - if (!getDataSourceEnabled().enabled) { - throw new Error('data_source_name cannot be used because data_source.enabled is false'); - } - - const dataSources = await tlConfig.savedObjectsClient.find({ - type: 'data-source', - perPage: 10, - search: `"${config.data_source_name}"`, - searchFields: ['title'], - fields: ['id', 'title'], - }); - - const possibleDataSourceIds = dataSources.saved_objects.filter( - (obj) => obj.attributes.title === config.data_source_name - ); - - if (possibleDataSourceIds.length !== 1) { - throw new Error( - `Expected exactly 1 result for data_source_name "${config.data_source_name}" but got ${possibleDataSourceIds.length} results` - ); - } - - dataSourceId = possibleDataSourceIds.pop()?.id; - } + const dataSourceId = await fetchDataSourceIdByName(config, tlConfig.savedObjectsClient); // Use the opensearchDashboards and kibana filter bar filters if (config.opensearchDashboards && config.kibana) { diff --git a/src/plugins/vis_type_timeline/server/types.ts b/src/plugins/vis_type_timeline/server/types.ts index f021ffeae00..a149dadb3b0 100644 --- a/src/plugins/vis_type_timeline/server/types.ts +++ b/src/plugins/vis_type_timeline/server/types.ts @@ -29,3 +29,17 @@ */ export { TimelineFunctionInterface, TimelineFunctionConfig } from './lib/classes/timeline_function'; + +export interface OpenSearchFunctionConfig { + q: string | null; + metric: string | null; + split: string | null; + index: string | null; + timefield: string | null; + kibana: boolean | null; + /** + * @deprecated The interval picker should be used instead + */ + interval: string | null; + data_source_name?: string | null; +} From ba471b5147f8043695ac50bac3925919903fa1b3 Mon Sep 17 00:00:00 2001 From: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> Date: Tue, 9 Apr 2024 21:19:26 +0000 Subject: [PATCH 03/10] Fix typo in args Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> --- .../server/series_functions/opensearch/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/vis_type_timeline/server/series_functions/opensearch/index.js b/src/plugins/vis_type_timeline/server/series_functions/opensearch/index.js index cbfcd516b6a..290459cdf85 100644 --- a/src/plugins/vis_type_timeline/server/series_functions/opensearch/index.js +++ b/src/plugins/vis_type_timeline/server/series_functions/opensearch/index.js @@ -114,7 +114,7 @@ export default new Datasource('es', { }, { name: 'data_source_name', - types: ['string, null'], // If null, the query will proceed with local cluster + types: ['string', 'null'], // If null, the query will proceed with local cluster help: i18n.translate('timeline.help.functions.opensearch.args.dataSourceNameHelpText', { defaultMessage: 'Specify a data source to query from. This will only work if multiple data sources is enabled', From 140a1c68b0e11bb4dea03c4d7653e100874d802b Mon Sep 17 00:00:00 2001 From: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> Date: Tue, 9 Apr 2024 23:09:53 +0000 Subject: [PATCH 04/10] Refactor build request to pass unit tests Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> --- .../server/series_functions/opensearch/index.js | 11 ++++++++++- .../series_functions/opensearch/lib/build_request.js | 5 +---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/plugins/vis_type_timeline/server/series_functions/opensearch/index.js b/src/plugins/vis_type_timeline/server/series_functions/opensearch/index.js index 290459cdf85..5192059f3e6 100644 --- a/src/plugins/vis_type_timeline/server/series_functions/opensearch/index.js +++ b/src/plugins/vis_type_timeline/server/series_functions/opensearch/index.js @@ -34,6 +34,7 @@ import { OPENSEARCH_SEARCH_STRATEGY } from '../../../../data/server'; import Datasource from '../../lib/classes/datasource'; import buildRequest from './lib/build_request'; import toSeriesList from './lib/agg_response_to_series_list'; +import { fetchDataSourceIdByName } from '../../lib/fetch_data_source_id'; export default new Datasource('es', { args: [ @@ -156,7 +157,15 @@ export default new Datasource('es', { const opensearchShardTimeout = tlConfig.opensearchShardTimeout; - const body = await buildRequest(config, tlConfig, scriptedFields, opensearchShardTimeout); + const dataSourceId = await fetchDataSourceIdByName(config, tlConfig.savedObjectsClient); + + const body = buildRequest( + config, + tlConfig, + scriptedFields, + opensearchShardTimeout, + dataSourceId + ); const deps = (await tlConfig.getStartServices())[1]; diff --git a/src/plugins/vis_type_timeline/server/series_functions/opensearch/lib/build_request.js b/src/plugins/vis_type_timeline/server/series_functions/opensearch/lib/build_request.js index ce0acb58040..90fb7b819a0 100644 --- a/src/plugins/vis_type_timeline/server/series_functions/opensearch/lib/build_request.js +++ b/src/plugins/vis_type_timeline/server/series_functions/opensearch/lib/build_request.js @@ -33,9 +33,8 @@ import moment from 'moment'; import { buildAggBody } from './agg_body'; import createDateAgg from './create_date_agg'; import { UI_SETTINGS } from '../../../../../data/server'; -import { fetchDataSourceIdByName } from '../../../lib/fetch_data_source_id'; -export default async function buildRequest(config, tlConfig, scriptedFields, timeout) { +export default function buildRequest(config, tlConfig, scriptedFields, timeout, dataSourceId) { const bool = { must: [] }; const timeFilter = { @@ -49,8 +48,6 @@ export default async function buildRequest(config, tlConfig, scriptedFields, tim }; bool.must.push(timeFilter); - const dataSourceId = await fetchDataSourceIdByName(config, tlConfig.savedObjectsClient); - // Use the opensearchDashboards and kibana filter bar filters if (config.opensearchDashboards && config.kibana) { bool.filter = _.get(tlConfig, 'request.body.extended.es.filter'); From 4e1397853739c51481cd2f8525a434194df5a5ff Mon Sep 17 00:00:00 2001 From: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> Date: Wed, 10 Apr 2024 16:58:34 +0000 Subject: [PATCH 05/10] 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 ad586bc9dd7..4fa7648dadd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,6 +86,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Multiple Datasource] Add default icon in multi-selectable picker ([#6357](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6357)) - [Workspace] Add APIs to support plugin state in request ([#6303](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6303)) - [Workspace] Filter left nav menu items according to the current workspace ([#6234](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6234)) +- [Multiple Datasource] Add support for Timeline ([#6385](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6385)) - [Multiple Datasource] Refactor data source selector component to include placeholder and add tests ([#6372](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6372)) - Replace control characters before logging ([#6402](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6402)) - [Dynamic Configurations] Improve dynamic configurations by adding cache and simplifying client fetch ([#6364](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6364)) From 28edd7f873f775d34d393a926f01c27a0a6f4e1f Mon Sep 17 00:00:00 2001 From: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> Date: Fri, 12 Apr 2024 00:44:45 +0000 Subject: [PATCH 06/10] Refactor error messages + address comments Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> --- CHANGELOG.md | 2 +- .../public/helpers/timeline_request_handler.ts | 4 +++- .../vis_type_timeline/server/handlers/chain_runner.js | 4 +++- .../vis_type_timeline/server/lib/fetch_data_source_id.ts | 2 +- src/plugins/vis_type_timeline/server/routes/run.ts | 5 ++++- src/plugins/vis_type_timeline/server/types.ts | 3 ++- 6 files changed, 14 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fa7648dadd..69949755939 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,7 +86,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Multiple Datasource] Add default icon in multi-selectable picker ([#6357](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6357)) - [Workspace] Add APIs to support plugin state in request ([#6303](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6303)) - [Workspace] Filter left nav menu items according to the current workspace ([#6234](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6234)) -- [Multiple Datasource] Add support for Timeline ([#6385](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6385)) +- [Multiple Datasource] Add multi data source support to Timeline ([#6385](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6385)) - [Multiple Datasource] Refactor data source selector component to include placeholder and add tests ([#6372](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6372)) - Replace control characters before logging ([#6402](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6402)) - [Dynamic Configurations] Improve dynamic configurations by adding cache and simplifying client fetch ([#6364](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6364)) diff --git a/src/plugins/vis_type_timeline/public/helpers/timeline_request_handler.ts b/src/plugins/vis_type_timeline/public/helpers/timeline_request_handler.ts index 467fef727f2..d7b955f96ef 100644 --- a/src/plugins/vis_type_timeline/public/helpers/timeline_request_handler.ts +++ b/src/plugins/vis_type_timeline/public/helpers/timeline_request_handler.ts @@ -129,10 +129,12 @@ export function getTimelineRequestHandler({ }); } catch (e) { if (e && e.body) { + const errorTitle = + e.body.attributes && e.body.attributes.title ? ` (${e.body.attributes.title})` : ''; const err = new Error( `${i18n.translate('timeline.requestHandlerErrorTitle', { defaultMessage: 'Timeline request error', - })}: ${e.body.title} ${e.body.message}` + })}${errorTitle}: ${e.body.message}` ); err.stack = e.stack; throw err; diff --git a/src/plugins/vis_type_timeline/server/handlers/chain_runner.js b/src/plugins/vis_type_timeline/server/handlers/chain_runner.js index 75382b73de5..39af9939056 100644 --- a/src/plugins/vis_type_timeline/server/handlers/chain_runner.js +++ b/src/plugins/vis_type_timeline/server/handlers/chain_runner.js @@ -47,7 +47,9 @@ export default function chainRunner(tlConfig) { let sheet; function throwWithCell(cell, exception) { - throw new Error(' in cell #' + (cell + 1) + ': ' + exception.message); + const e = new Error(exception.message); + e.name = `Expression parse error in cell #${cell + 1}`; + throw e; } // Invokes a modifier function, resolving arguments into series as needed diff --git a/src/plugins/vis_type_timeline/server/lib/fetch_data_source_id.ts b/src/plugins/vis_type_timeline/server/lib/fetch_data_source_id.ts index c6521cafca2..a5e71841e2a 100644 --- a/src/plugins/vis_type_timeline/server/lib/fetch_data_source_id.ts +++ b/src/plugins/vis_type_timeline/server/lib/fetch_data_source_id.ts @@ -14,7 +14,7 @@ export const fetchDataSourceIdByName = async ( ) => { if (config.data_source_name) { if (!getDataSourceEnabled().enabled) { - throw new Error('data_source_name cannot be used because data_source.enabled is false'); + throw new Error('To query from multiple data sources, first enable the data sources feature'); } const dataSources = await client.find({ diff --git a/src/plugins/vis_type_timeline/server/routes/run.ts b/src/plugins/vis_type_timeline/server/routes/run.ts index ab6a993b4bb..af1005ebcb8 100644 --- a/src/plugins/vis_type_timeline/server/routes/run.ts +++ b/src/plugins/vis_type_timeline/server/routes/run.ts @@ -122,7 +122,10 @@ export function runRoute( } else { return response.internalError({ body: { - message: err.toString(), + attributes: { + title: err.name, + }, + message: err.message, }, }); } diff --git a/src/plugins/vis_type_timeline/server/types.ts b/src/plugins/vis_type_timeline/server/types.ts index a149dadb3b0..2fa3a25c581 100644 --- a/src/plugins/vis_type_timeline/server/types.ts +++ b/src/plugins/vis_type_timeline/server/types.ts @@ -37,8 +37,9 @@ export interface OpenSearchFunctionConfig { index: string | null; timefield: string | null; kibana: boolean | null; + opensearchDashboards: boolean | null; /** - * @deprecated The interval picker should be used instead + * @deprecated This property should not be set in the Timeline expression. Users should use the interval picker React component instead */ interval: string | null; data_source_name?: string | null; From b4411b3ecaaa53974f53ca8c7c35b1bbe1c33c71 Mon Sep 17 00:00:00 2001 From: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> Date: Fri, 12 Apr 2024 00:51:30 +0000 Subject: [PATCH 07/10] Fix ut Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> --- .../server/lib/fetch_data_source_id.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/plugins/vis_type_timeline/server/lib/fetch_data_source_id.test.ts b/src/plugins/vis_type_timeline/server/lib/fetch_data_source_id.test.ts index 5fc89afe333..52fb3adc343 100644 --- a/src/plugins/vis_type_timeline/server/lib/fetch_data_source_id.test.ts +++ b/src/plugins/vis_type_timeline/server/lib/fetch_data_source_id.test.ts @@ -23,6 +23,7 @@ describe('fetchDataSourceIdByName()', () => { index: null, timefield: null, kibana: null, + opensearchDashboards: null, interval: null, }; const client = savedObjectsClientMock.create(); @@ -104,7 +105,9 @@ describe('fetchDataSourceIdByName()', () => { it('should throw errors when MDS is disabled', async () => { await expect( fetchDataSourceIdByName({ ...config, data_source_name: 'Some Data Source' }, client) - ).rejects.toThrowError('data_source_name cannot be used because data_source.enabled is false'); + ).rejects.toThrowError( + 'To query from multiple data sources, first enable the data sources feature' + ); }); it.each([ From a274600c5fefbb621d916701ef3efb3b06b9eb75 Mon Sep 17 00:00:00 2001 From: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> Date: Mon, 15 Apr 2024 22:20:33 +0000 Subject: [PATCH 08/10] Change to data source feature Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> --- .../vis_type_timeline/server/lib/fetch_data_source_id.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/vis_type_timeline/server/lib/fetch_data_source_id.ts b/src/plugins/vis_type_timeline/server/lib/fetch_data_source_id.ts index a5e71841e2a..85de50a6260 100644 --- a/src/plugins/vis_type_timeline/server/lib/fetch_data_source_id.ts +++ b/src/plugins/vis_type_timeline/server/lib/fetch_data_source_id.ts @@ -14,7 +14,7 @@ export const fetchDataSourceIdByName = async ( ) => { if (config.data_source_name) { if (!getDataSourceEnabled().enabled) { - throw new Error('To query from multiple data sources, first enable the data sources feature'); + throw new Error('To query from multiple data sources, first enable the data source feature'); } const dataSources = await client.find({ From e8a41b41e41407f690b2fabb2ae3f82a9ef6a31c Mon Sep 17 00:00:00 2001 From: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> Date: Mon, 15 Apr 2024 23:24:07 +0000 Subject: [PATCH 09/10] Fix UT Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> --- .../vis_type_timeline/server/lib/fetch_data_source_id.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/vis_type_timeline/server/lib/fetch_data_source_id.test.ts b/src/plugins/vis_type_timeline/server/lib/fetch_data_source_id.test.ts index 52fb3adc343..e5596a001a2 100644 --- a/src/plugins/vis_type_timeline/server/lib/fetch_data_source_id.test.ts +++ b/src/plugins/vis_type_timeline/server/lib/fetch_data_source_id.test.ts @@ -106,7 +106,7 @@ describe('fetchDataSourceIdByName()', () => { await expect( fetchDataSourceIdByName({ ...config, data_source_name: 'Some Data Source' }, client) ).rejects.toThrowError( - 'To query from multiple data sources, first enable the data sources feature' + 'To query from multiple data sources, first enable the data source feature' ); }); From 541cbdd38e89da3372b09e476f2c79ab5b771822 Mon Sep 17 00:00:00 2001 From: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> Date: Tue, 16 Apr 2024 18:14:31 +0000 Subject: [PATCH 10/10] Address comments Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> --- .../server/lib/fetch_data_source_id.ts | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/plugins/vis_type_timeline/server/lib/fetch_data_source_id.ts b/src/plugins/vis_type_timeline/server/lib/fetch_data_source_id.ts index 85de50a6260..e3d0d76d23e 100644 --- a/src/plugins/vis_type_timeline/server/lib/fetch_data_source_id.ts +++ b/src/plugins/vis_type_timeline/server/lib/fetch_data_source_id.ts @@ -12,31 +12,31 @@ export const fetchDataSourceIdByName = async ( config: OpenSearchFunctionConfig, client: SavedObjectsClientContract ) => { - if (config.data_source_name) { - if (!getDataSourceEnabled().enabled) { - throw new Error('To query from multiple data sources, first enable the data source feature'); - } + if (!config.data_source_name) { + return undefined; + } - const dataSources = await client.find({ - type: 'data-source', - perPage: 100, - search: `"${config.data_source_name}"`, - searchFields: ['title'], - fields: ['id', 'title'], - }); + if (!getDataSourceEnabled().enabled) { + throw new Error('To query from multiple data sources, first enable the data source feature'); + } - const possibleDataSourceIds = dataSources.saved_objects.filter( - (obj) => obj.attributes.title === config.data_source_name - ); + const dataSources = await client.find({ + type: 'data-source', + perPage: 100, + search: `"${config.data_source_name}"`, + searchFields: ['title'], + fields: ['id', 'title'], + }); - if (possibleDataSourceIds.length !== 1) { - throw new Error( - `Expected exactly 1 result for data_source_name "${config.data_source_name}" but got ${possibleDataSourceIds.length} results` - ); - } + const possibleDataSourceIds = dataSources.saved_objects.filter( + (obj) => obj.attributes.title === config.data_source_name + ); - return possibleDataSourceIds.pop()?.id; + if (possibleDataSourceIds.length !== 1) { + throw new Error( + `Expected exactly 1 result for data_source_name "${config.data_source_name}" but got ${possibleDataSourceIds.length} results` + ); } - return undefined; + return possibleDataSourceIds.pop()?.id; };