From daccab76e103bd5ad5636466785bc0bb920b284c Mon Sep 17 00:00:00 2001 From: ZilongX <99905560+ZilongX@users.noreply.github.com> Date: Tue, 2 Jul 2024 15:34:59 -0700 Subject: [PATCH] [Multiple Datasource][Version Decoupling] Add support of required backend plugins check on data sources (#7146) * [Multiple Datasource][Version Decoupling] Add support of required backend plugins check on data sources Signed-off-by: Zilong Xia * Changeset file for PR #7146 created/updated --------- Signed-off-by: Zilong Xia Co-authored-by: opensearch-changeset-bot[bot] <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com> --- changelogs/fragments/7146.yml | 2 + .../discovery/plugin_manifest_parser.test.ts | 17 ++++- .../discovery/plugin_manifest_parser.ts | 9 ++- src/core/server/plugins/plugin.test.ts | 2 + src/core/server/plugins/plugin.ts | 2 + src/core/server/plugins/types.ts | 6 ++ .../components/header/header.test.tsx | 65 ++++++++++++++++++- .../components/header/header.tsx | 25 +++++-- .../create_index_pattern_wizard/types.ts | 1 + .../public/components/utils.ts | 4 +- 10 files changed, 123 insertions(+), 10 deletions(-) create mode 100644 changelogs/fragments/7146.yml diff --git a/changelogs/fragments/7146.yml b/changelogs/fragments/7146.yml new file mode 100644 index 00000000000..f14a27c5174 --- /dev/null +++ b/changelogs/fragments/7146.yml @@ -0,0 +1,2 @@ +fix: +- [MDS][Version Decoupling] Add support of required backend plugins check on data sources ([#7146](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7146)) \ No newline at end of file diff --git a/src/core/server/plugins/discovery/plugin_manifest_parser.test.ts b/src/core/server/plugins/discovery/plugin_manifest_parser.test.ts index e92fe824122..17600d42148 100644 --- a/src/core/server/plugins/discovery/plugin_manifest_parser.test.ts +++ b/src/core/server/plugins/discovery/plugin_manifest_parser.test.ts @@ -354,6 +354,8 @@ describe('requiredEnginePlugins', () => { requiredBundles: [], server: true, ui: false, + supportedOSDataSourceVersions: '', + requiredOSDataSourcePlugins: [], }); }); }); @@ -416,7 +418,8 @@ test('set defaults for all missing optional fields', async () => { requiredBundles: [], server: true, ui: false, - supportedOSDataSourceVersions: undefined, + supportedOSDataSourceVersions: '', + requiredOSDataSourcePlugins: [], }); }); @@ -436,6 +439,10 @@ test('return all set optional fields as they are in manifest', async () => { }, ui: true, supportedOSDataSourceVersions: '>=1.0.0', + requiredOSDataSourcePlugins: [ + 'some-required-data-source-plugin-1', + 'some-required-data-source-plugin-2', + ], }) ) ); @@ -455,6 +462,10 @@ test('return all set optional fields as they are in manifest', async () => { server: false, ui: true, supportedOSDataSourceVersions: '>=1.0.0', + requiredOSDataSourcePlugins: [ + 'some-required-data-source-plugin-1', + 'some-required-data-source-plugin-2', + ], }); }); @@ -484,6 +495,8 @@ test('return manifest when plugin expected OpenSearch Dashboards version matches requiredBundles: [], server: true, ui: false, + supportedOSDataSourceVersions: '', + requiredOSDataSourcePlugins: [], }); }); @@ -512,5 +525,7 @@ test('return manifest when plugin expected OpenSearch Dashboards version is `ope requiredBundles: [], server: true, ui: true, + supportedOSDataSourceVersions: '', + requiredOSDataSourcePlugins: [], }); }); diff --git a/src/core/server/plugins/discovery/plugin_manifest_parser.ts b/src/core/server/plugins/discovery/plugin_manifest_parser.ts index 88f54fa5ae0..d4db5f3df19 100644 --- a/src/core/server/plugins/discovery/plugin_manifest_parser.ts +++ b/src/core/server/plugins/discovery/plugin_manifest_parser.ts @@ -69,6 +69,7 @@ const KNOWN_MANIFEST_FIELDS = (() => { extraPublicDirs: true, requiredBundles: true, supportedOSDataSourceVersions: true, + requiredOSDataSourcePlugins: true, }; return new Set(Object.keys(manifestFields)); @@ -246,7 +247,13 @@ export async function parseManifest( ui: includesUiPlugin, server: includesServerPlugin, extraPublicDirs: manifest.extraPublicDirs, - supportedOSDataSourceVersions: manifest.supportedOSDataSourceVersions, + supportedOSDataSourceVersions: + manifest.supportedOSDataSourceVersions !== undefined + ? manifest.supportedOSDataSourceVersions + : '', + requiredOSDataSourcePlugins: Array.isArray(manifest.requiredOSDataSourcePlugins) + ? manifest.requiredOSDataSourcePlugins + : [], }; } diff --git a/src/core/server/plugins/plugin.test.ts b/src/core/server/plugins/plugin.test.ts index b888679175d..2fc8a182a86 100644 --- a/src/core/server/plugins/plugin.test.ts +++ b/src/core/server/plugins/plugin.test.ts @@ -78,6 +78,7 @@ function createPluginManifest(manifestProps: Partial = {}): Plug server: true, ui: true, supportedOSDataSourceVersions: '>=1.0.0', + requiredOSDataSourcePlugins: ['some-required-data-source-plugin'], ...manifestProps, }; } @@ -127,6 +128,7 @@ test('`constructor` correctly initializes plugin instance', () => { expect(plugin.requiredPlugins).toEqual(['some-required-dep']); expect(plugin.optionalPlugins).toEqual(['some-optional-dep']); expect(plugin.supportedOSDataSourceVersions).toEqual('>=1.0.0'); + expect(plugin.requiredOSDataSourcePlugins).toEqual(['some-required-data-source-plugin']); }); test('`setup` fails if `plugin` initializer is not exported', async () => { diff --git a/src/core/server/plugins/plugin.ts b/src/core/server/plugins/plugin.ts index 82d9bb11a3e..34894648802 100644 --- a/src/core/server/plugins/plugin.ts +++ b/src/core/server/plugins/plugin.ts @@ -71,6 +71,7 @@ export class PluginWrapper< public readonly includesServerPlugin: PluginManifest['server']; public readonly includesUiPlugin: PluginManifest['ui']; public readonly supportedOSDataSourceVersions: PluginManifest['supportedOSDataSourceVersions']; + public readonly requiredOSDataSourcePlugins: PluginManifest['requiredOSDataSourcePlugins']; private readonly log: Logger; private readonly initializerContext: PluginInitializerContext; @@ -102,6 +103,7 @@ export class PluginWrapper< this.includesServerPlugin = params.manifest.server; this.includesUiPlugin = params.manifest.ui; this.supportedOSDataSourceVersions = params.manifest.supportedOSDataSourceVersions; + this.requiredOSDataSourcePlugins = params.manifest.requiredOSDataSourcePlugins; } /** diff --git a/src/core/server/plugins/types.ts b/src/core/server/plugins/types.ts index 5edcbc318be..60f9b1eba6f 100644 --- a/src/core/server/plugins/types.ts +++ b/src/core/server/plugins/types.ts @@ -206,6 +206,12 @@ export interface PluginManifest { * Value will be following semver as a range for example ">= 1.3.0" */ readonly supportedOSDataSourceVersions?: string; + + /** + * Specifies the required backend plugins that **must be** installed and enabled on the data source for this plugin to function properly + * when adding OpenSearch cluster as data sources. + */ + readonly requiredOSDataSourcePlugins?: readonly PluginName[]; } /** diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/components/header/header.test.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/components/header/header.test.tsx index 6420ee5d45a..a76a475f221 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/components/header/header.test.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/components/header/header.test.tsx @@ -4,7 +4,8 @@ */ import React from 'react'; -import { Header } from '../header'; +import { render } from '@testing-library/react'; +import { Header, useEffectOnce } from '../header'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; jest.mock('../../../../../../../../../plugins/opensearch_dashboards_react/public', () => ({ @@ -15,6 +16,13 @@ jest.mock('../../../../../../../../../plugins/opensearch_dashboards_react/public }), })); +const mockGetDataSourcesCompatible = jest.fn(() => + Promise.resolve([{ id: 1, attributes: { title: '213', dataSourceVersion: '2.13.0' } }]) +); +const mockGetDataSourcesNotCompatible = jest.fn(() => + Promise.resolve([{ id: 1, attributes: { title: '010', dataSourceVersion: '0.1.0' } }]) +); + afterAll(() => jest.clearAllMocks()); describe('Header', () => { @@ -118,4 +126,59 @@ describe('Header', () => { .prop('isDisabled') ).toEqual(true); }); + + it('should display compatible data source', () => { + const component = shallowWithIntl( +
{}} + dataSourceRef={{ type: 'type', id: 'id', title: 'title' }!} + goToNextStep={() => {}} + isNextStepDisabled={true} + stepInfo={{ totalStepNumber: 0, currentStepNumber: 0 }} + hideLocalCluster={false} + getDataSources={mockGetDataSourcesCompatible} + /> + ); + + component + .find('[data-test-subj="createIndexPatternStepDataSourceUseDataSourceRadio"]') + .simulate('change', { + target: { + checked: true, + }, + }); + + expect( + component + .find('[data-test-subj="createIndexPatternStepDataSourceSelectDataSource"]') + .first() + .exists() + ).toBeTruthy(); + }); + + it('should filter out incompatible data sources', () => { + const component = shallowWithIntl( +
{}} + dataSourceRef={{ type: 'type', id: 'id', title: 'title' }!} + goToNextStep={() => {}} + isNextStepDisabled={true} + stepInfo={{ totalStepNumber: 0, currentStepNumber: 0 }} + hideLocalCluster={false} + getDataSources={mockGetDataSourcesNotCompatible} + /> + ); + + component + .find('[data-test-subj="createIndexPatternStepDataSourceUseDataSourceRadio"]') + .simulate('change', { + target: { + checked: true, + }, + }); + + expect( + component.find('[data-test-subj="createIndexPatternStepDataSourceSelectDataSource"]').first() + ).toEqual({}); + }); }); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/components/header/header.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/components/header/header.tsx index f51658474b2..b62aad73960 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/components/header/header.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/components/header/header.tsx @@ -69,13 +69,26 @@ export const Header: React.FC = (props: HeaderProps) => { getDataSources(savedObjects.client) .then((fetchedDataSources: DataSourceTableItem[]) => { setIsLoading(false); + if (fetchedDataSources?.length) { - fetchedDataSources = fetchedDataSources.filter((dataSource) => - semver.satisfies( - dataSource.datasourceversion, - pluginManifest.supportedOSDataSourceVersions - ) - ); + // filter out data sources which does NOT have the required backend plugins installed + if (pluginManifest.hasOwnProperty('requiredOSDataSourcePlugins')) { + fetchedDataSources = fetchedDataSources.filter((dataSource) => + pluginManifest.requiredOSDataSourcePlugins.every((plugin) => + dataSource.installedplugins.includes(plugin) + ) + ); + } + + // filter out data sources which is NOT in the support range of plugin + if (pluginManifest.hasOwnProperty('supportedOSDataSourceVersions')) { + fetchedDataSources = fetchedDataSources.filter((dataSource) => + semver.satisfies( + dataSource.datasourceversion, + pluginManifest.supportedOSDataSourceVersions + ) + ); + } setDataSources(fetchedDataSources); } }) diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/types.ts b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/types.ts index 6643c291b03..6a6510c9c52 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/types.ts +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/types.ts @@ -93,4 +93,5 @@ export interface DataSourceTableItem { checked?: 'on' | 'off'; label: string; datasourceversion: string; + installedplugins: string[]; } diff --git a/src/plugins/index_pattern_management/public/components/utils.ts b/src/plugins/index_pattern_management/public/components/utils.ts index f8ae1d5c20e..c5c9b3acb26 100644 --- a/src/plugins/index_pattern_management/public/components/utils.ts +++ b/src/plugins/index_pattern_management/public/components/utils.ts @@ -90,7 +90,7 @@ export async function getDataSources(savedObjectsClient: SavedObjectsClientContr savedObjectsClient .find({ type: 'data-source', - fields: ['title', 'type', 'dataSourceVersion'], + fields: ['title', 'type', 'dataSourceVersion', 'installedPlugins'], perPage: 10000, }) .then((response) => @@ -100,6 +100,7 @@ export async function getDataSources(savedObjectsClient: SavedObjectsClientContr const type = dataSource.type; const title = dataSource.get('title'); const datasourceversion = dataSource.get('dataSourceVersion'); + const installedplugins = dataSource.get('installedPlugins'); return { id, @@ -108,6 +109,7 @@ export async function getDataSources(savedObjectsClient: SavedObjectsClientContr label: title, sort: `${title}`, datasourceversion, + installedplugins, }; }) .sort((a, b) => a.sort.localeCompare(b.sort))