diff --git a/CHANGELOG.md b/CHANGELOG.md index ac6c30e58d6..655418cf889 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Multiple Datasource] Add datasource picker component and use it in devtools and tutorial page when multiple datasource is enabled ([#5756](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5756)) - [Multiple Datasource] Add datasource picker to import saved object flyout when multiple data source is enabled ([#5781](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5781)) - [Multiple Datasource] Add interfaces to register add-on authentication method from plug-in module ([#5851](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5851)) +- [Multiple Datasource] Able to Hide "Local Cluster" option from datasource DropDown ([#5827](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5827)) ### 🐛 Bug Fixes @@ -66,7 +67,6 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [BUG] Remove duplicate sample data as id 90943e30-9a47-11e8-b64d-95841ca0b247 ([5668](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5668)) - [BUG][Multiple Datasource] Fix datasource testing connection unexpectedly passed with wrong endpoint [#5663](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5663) - [Table Visualization] Fix filter action buttons for split table aggregations ([#5619](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5619)) -- [BUG][Multiple Datasource] Datasource id is required if multiple datasource is enabled and no default cluster supported [#5751](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5751) ### 🚞 Infrastructure diff --git a/config/opensearch_dashboards.yml b/config/opensearch_dashboards.yml index e1adad20eb3..9797335e3cc 100644 --- a/config/opensearch_dashboards.yml +++ b/config/opensearch_dashboards.yml @@ -237,8 +237,8 @@ # Set the value of this setting to true to enable multiple data source feature. #data_source.enabled: false -# Set the value of this setting to false to disable default cluster in data source feature. -#data_source.defaultCluster: true +# Set the value of this setting to true to hide local cluster in data source feature. +#data_source.hideLocalCluster: false # Set the value of these settings to customize crypto materials to encryption saved credentials # in data sources. #data_source.encryption.wrappingKeyName: 'changeme' diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.test.tsx b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.test.tsx index 0cbe7f929fe..8bf887a54fe 100644 --- a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.test.tsx +++ b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.test.tsx @@ -53,7 +53,7 @@ import { getEndpointFromPosition } from '../../../../../lib/autocomplete/get_end import * as consoleMenuActions from '../console_menu_actions'; import { Editor } from './editor'; -describe('Legacy (Ace) Console Editor Component Smoke Test', () => { +describe('Legacy (Ace) Console Editor Component Smoke Test with dataSourceId', () => { let mockedAppContextValue: ContextValue; const sandbox = sinon.createSandbox(); @@ -63,7 +63,70 @@ describe('Legacy (Ace) Console Editor Component Smoke Test', () => { - + + + + + + ); + + beforeEach(() => { + document.queryCommandSupported = sinon.fake(() => true); + mockedAppContextValue = serviceContextMock.create(); + }); + + afterEach(() => { + jest.clearAllMocks(); + sandbox.restore(); + }); + + it('calls send current request to OpenSearch', async () => { + (getEndpointFromPosition as jest.Mock).mockReturnValue({ patterns: [] }); + (sendRequestToOpenSearch as jest.Mock).mockRejectedValue({}); + const editor = doMount(); + act(() => { + editor.find('[data-test-subj~="sendRequestButton"]').simulate('click'); + }); + await nextTick(); + expect(sendRequestToOpenSearch).toBeCalledTimes(1); + }); + + it('opens docs', () => { + const stub = sandbox.stub(consoleMenuActions, 'getDocumentation'); + const editor = doMount(); + const consoleMenuToggle = editor.find('[data-test-subj~="toggleConsoleMenu"]').last(); + consoleMenuToggle.simulate('click'); + + const docsButton = editor.find('[data-test-subj~="consoleMenuOpenDocs"]').last(); + docsButton.simulate('click'); + + expect(stub.callCount).toBe(1); + }); + + it('prompts auto-indent', () => { + const stub = sandbox.stub(consoleMenuActions, 'autoIndent'); + const editor = doMount(); + const consoleMenuToggle = editor.find('[data-test-subj~="toggleConsoleMenu"]').last(); + consoleMenuToggle.simulate('click'); + + const autoIndentButton = editor.find('[data-test-subj~="consoleMenuAutoIndent"]').last(); + autoIndentButton.simulate('click'); + + expect(stub.callCount).toBe(1); + }); +}); + +describe('Legacy (Ace) Console Editor Component Smoke Test with empty dataSourceId (Local Cluster)', () => { + let mockedAppContextValue: ContextValue; + const sandbox = sinon.createSandbox(); + + const doMount = () => + mount( + + + + + @@ -115,3 +178,73 @@ describe('Legacy (Ace) Console Editor Component Smoke Test', () => { expect(stub.callCount).toBe(1); }); }); + +describe('Legacy (Ace) Console Editor Component Smoke Test with dataSouceId undefined', () => { + let mockedAppContextValue: ContextValue; + const sandbox = sinon.createSandbox(); + + const doMount = () => + mount( + + + + + + + + + + ); + + beforeEach(() => { + document.queryCommandSupported = sinon.fake(() => true); + mockedAppContextValue = serviceContextMock.create(); + }); + + afterEach(() => { + jest.clearAllMocks(); + sandbox.restore(); + }); + + it('diasbles send request button', async () => { + (getEndpointFromPosition as jest.Mock).mockReturnValue({ patterns: [] }); + (sendRequestToOpenSearch as jest.Mock).mockRejectedValue({}); + const editor = doMount(); + expect(editor.find('[data-test-subj~="sendRequestButton"]').get(0).props.disabled); + }); + + it('not able to send current request to OpenSearch', async () => { + (getEndpointFromPosition as jest.Mock).mockReturnValue({ patterns: [] }); + (sendRequestToOpenSearch as jest.Mock).mockRejectedValue({}); + const editor = doMount(); + act(() => { + editor.find('[data-test-subj~="sendRequestButton"]').simulate('click'); + }); + await nextTick(); + expect(sendRequestToOpenSearch).toBeCalledTimes(0); + }); + + it('opens docs', () => { + const stub = sandbox.stub(consoleMenuActions, 'getDocumentation'); + const editor = doMount(); + const consoleMenuToggle = editor.find('[data-test-subj~="toggleConsoleMenu"]').last(); + consoleMenuToggle.simulate('click'); + + const docsButton = editor.find('[data-test-subj~="consoleMenuOpenDocs"]').last(); + docsButton.simulate('click'); + + expect(stub.callCount).toBe(1); + }); + + it('prompts auto-indent', () => { + const stub = sandbox.stub(consoleMenuActions, 'autoIndent'); + const editor = doMount(); + const consoleMenuToggle = editor.find('[data-test-subj~="toggleConsoleMenu"]').last(); + consoleMenuToggle.simulate('click'); + + const autoIndentButton = editor.find('[data-test-subj~="consoleMenuAutoIndent"]').last(); + autoIndentButton.simulate('click'); + + expect(stub.callCount).toBe(1); + }); +}); diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx index 1c47cc41e92..4422cbc4743 100644 --- a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx +++ b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx @@ -228,6 +228,11 @@ function EditorUI({ initialTextValue, dataSourceId }: EditorProps) { }); }, [sendCurrentRequestToOpenSearch, openDocumentation]); + const tooltipDefaultMessage = + dataSourceId === undefined ? `Select a data source` : `Click to send request`; + + const toolTipButtonDiasbled = dataSourceId === undefined; + return (
@@ -240,16 +245,17 @@ function EditorUI({ initialTextValue, dataSourceId }: EditorProps) { diff --git a/src/plugins/data/server/search/opensearch_search/decide_client.ts b/src/plugins/data/server/search/opensearch_search/decide_client.ts index 9e0306d1926..2ff2339add4 100644 --- a/src/plugins/data/server/search/opensearch_search/decide_client.ts +++ b/src/plugins/data/server/search/opensearch_search/decide_client.ts @@ -9,14 +9,14 @@ import { IOpenSearchSearchRequest } from '..'; export const decideClient = async ( context: RequestHandlerContext, request: IOpenSearchSearchRequest, - withDataSourceEnabled: boolean = false, withLongNumeralsSupport: boolean = false ): Promise => { - const defaultOpenSearchClient = withLongNumeralsSupport - ? context.core.opensearch.client.asCurrentUserWithLongNumeralsSupport - : context.core.opensearch.client.asCurrentUser; - - return withDataSourceEnabled && request.dataSourceId && context.dataSource - ? await context.dataSource.opensearch.getClient(request.dataSourceId) - : defaultOpenSearchClient; + // if data source feature is disabled, return default opensearch client of current user + const client = + request.dataSourceId && context.dataSource + ? await context.dataSource.opensearch.getClient(request.dataSourceId) + : withLongNumeralsSupport + ? context.core.opensearch.client.asCurrentUserWithLongNumeralsSupport + : context.core.opensearch.client.asCurrentUser; + return client; }; diff --git a/src/plugins/data/server/search/opensearch_search/opensearch_search_strategy.test.ts b/src/plugins/data/server/search/opensearch_search/opensearch_search_strategy.test.ts index 6bd4eea5d17..39c367a04a4 100644 --- a/src/plugins/data/server/search/opensearch_search/opensearch_search_strategy.test.ts +++ b/src/plugins/data/server/search/opensearch_search/opensearch_search_strategy.test.ts @@ -33,20 +33,11 @@ import { pluginInitializerContextConfigMock } from '../../../../../core/server/m import { opensearchSearchStrategyProvider } from './opensearch_search_strategy'; import { DataSourceError } from '../../../../data_source/server/lib/error'; import { DataSourcePluginSetup } from '../../../../data_source/server'; -import { SearchUsage } from '../collectors'; describe('OpenSearch search strategy', () => { const mockLogger: any = { debug: () => {}, }; - const mockSearchUsage: SearchUsage = { - trackError(): Promise { - return Promise.resolve(undefined); - }, - trackSuccess(duration: number): Promise { - return Promise.resolve(undefined); - }, - }; const body = { body: { _shards: { @@ -140,21 +131,8 @@ describe('OpenSearch search strategy', () => { expect(response).toHaveProperty('rawResponse'); }); - it('dataSource enabled and default cluster disabled, send request with dataSourceId get data source client', async () => { - const mockDataSourcePluginSetupWithDataSourceEnabled: DataSourcePluginSetup = { - createDataSourceError(err: any): DataSourceError { - return new DataSourceError({}); - }, - dataSourceEnabled: jest.fn(() => true), - defaultClusterEnabled: jest.fn(() => false), - }; - - const opensearchSearch = await opensearchSearchStrategyProvider( - mockConfig$, - mockLogger, - mockSearchUsage, - mockDataSourcePluginSetupWithDataSourceEnabled - ); + it('dataSource enabled, send request with dataSourceId get data source client', async () => { + const opensearchSearch = await opensearchSearchStrategyProvider(mockConfig$, mockLogger); await opensearchSearch.search( (mockDataSourceEnabledContext as unknown) as RequestHandlerContext, @@ -166,35 +144,6 @@ describe('OpenSearch search strategy', () => { expect(mockOpenSearchApiCaller).not.toBeCalled(); }); - it('dataSource enabled and default cluster disabled, send request with empty dataSourceId should throw exception', async () => { - const mockDataSourcePluginSetupWithDataSourceEnabled: DataSourcePluginSetup = { - createDataSourceError(err: any): DataSourceError { - return new DataSourceError({}); - }, - dataSourceEnabled: jest.fn(() => true), - defaultClusterEnabled: jest.fn(() => false), - }; - - try { - const opensearchSearch = opensearchSearchStrategyProvider( - mockConfig$, - mockLogger, - mockSearchUsage, - mockDataSourcePluginSetupWithDataSourceEnabled - ); - - await opensearchSearch.search( - (mockDataSourceEnabledContext as unknown) as RequestHandlerContext, - { - dataSourceId: '', - } - ); - } catch (e) { - expect(e).toBeTruthy(); - expect(e).toBeInstanceOf(DataSourceError); - } - }); - it('dataSource disabled, send request with dataSourceId get default client', async () => { const opensearchSearch = await opensearchSearchStrategyProvider(mockConfig$, mockLogger); @@ -205,47 +154,8 @@ describe('OpenSearch search strategy', () => { expect(mockDataSourceApiCaller).not.toBeCalled(); }); - it('dataSource enabled and default cluster enabled, send request with dataSourceId get datasource client', async () => { - const mockDataSourcePluginSetupWithDataSourceEnabled: DataSourcePluginSetup = { - createDataSourceError(err: any): DataSourceError { - return new DataSourceError({}); - }, - dataSourceEnabled: jest.fn(() => true), - defaultClusterEnabled: jest.fn(() => true), - }; - - const opensearchSearch = await opensearchSearchStrategyProvider( - mockConfig$, - mockLogger, - mockSearchUsage, - mockDataSourcePluginSetupWithDataSourceEnabled - ); - - await opensearchSearch.search( - (mockDataSourceEnabledContext as unknown) as RequestHandlerContext, - { - dataSourceId, - } - ); - expect(mockDataSourceApiCaller).toBeCalled(); - expect(mockOpenSearchApiCaller).not.toBeCalled(); - }); - - it('dataSource enabled and default cluster enabled, send request without dataSourceId get default client', async () => { - const mockDataSourcePluginSetupWithDataSourceEnabled: DataSourcePluginSetup = { - createDataSourceError(err: any): DataSourceError { - return new DataSourceError({}); - }, - dataSourceEnabled: jest.fn(() => true), - defaultClusterEnabled: jest.fn(() => true), - }; - - const opensearchSearch = await opensearchSearchStrategyProvider( - mockConfig$, - mockLogger, - mockSearchUsage, - mockDataSourcePluginSetupWithDataSourceEnabled - ); + it('dataSource enabled, send request without dataSourceId get default client', async () => { + const opensearchSearch = await opensearchSearchStrategyProvider(mockConfig$, mockLogger); await opensearchSearch.search((mockContext as unknown) as RequestHandlerContext, {}); expect(mockOpenSearchApiCaller).toBeCalled(); diff --git a/src/plugins/data/server/search/opensearch_search/opensearch_search_strategy.ts b/src/plugins/data/server/search/opensearch_search/opensearch_search_strategy.ts index 00507faaf0f..5eb29051779 100644 --- a/src/plugins/data/server/search/opensearch_search/opensearch_search_strategy.ts +++ b/src/plugins/data/server/search/opensearch_search/opensearch_search_strategy.ts @@ -73,20 +73,7 @@ export const opensearchSearchStrategyProvider = ( }); try { - if ( - dataSource?.dataSourceEnabled() && - !dataSource?.defaultClusterEnabled() && - !request.dataSourceId - ) { - throw new Error(`Data source id is required when no openseach hosts config provided`); - } - - const client = await decideClient( - context, - request, - dataSource?.dataSourceEnabled(), - withLongNumeralsSupport - ); + const client = await decideClient(context, request, withLongNumeralsSupport); const promise = shimAbortSignal(client.search(params), options?.abortSignal); const { body: rawResponse } = (await promise) as ApiResponse>; @@ -105,7 +92,7 @@ export const opensearchSearchStrategyProvider = ( } catch (e) { if (usage) usage.trackError(); - if (dataSource?.dataSourceEnabled()) { + if (dataSource && request.dataSourceId) { throw dataSource.createDataSourceError(e); } throw e; diff --git a/src/plugins/data_source/config.ts b/src/plugins/data_source/config.ts index 1131c500162..d5412d32f0f 100644 --- a/src/plugins/data_source/config.ts +++ b/src/plugins/data_source/config.ts @@ -13,7 +13,7 @@ const WRAPPING_KEY_SIZE: number = 32; export const configSchema = schema.object({ enabled: schema.boolean({ defaultValue: false }), - defaultCluster: schema.boolean({ defaultValue: true }), + hideLocalCluster: schema.boolean({ defaultValue: false }), encryption: schema.object({ wrappingKeyName: schema.string({ minLength: KEY_NAME_MIN_LENGTH, diff --git a/src/plugins/data_source/public/index.ts b/src/plugins/data_source/public/index.ts index b69e07784ff..56bcd4c8bb1 100644 --- a/src/plugins/data_source/public/index.ts +++ b/src/plugins/data_source/public/index.ts @@ -3,12 +3,13 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { PluginInitializerContext } from 'opensearch-dashboards/public'; import { DataSourcePlugin } from './plugin'; // This exports static code and TypeScript types, // as well as, OpenSearch Dashboards Platform `plugin()` initializer. -export function plugin() { - return new DataSourcePlugin(); +export function plugin(initializerContext: PluginInitializerContext) { + return new DataSourcePlugin(initializerContext); } export { DataSourcePluginSetup, DataSourcePluginStart } from './types'; diff --git a/src/plugins/data_source/public/plugin.ts b/src/plugins/data_source/public/plugin.ts index 672db78e15c..65bee912255 100644 --- a/src/plugins/data_source/public/plugin.ts +++ b/src/plugins/data_source/public/plugin.ts @@ -3,16 +3,34 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { CoreSetup, CoreStart, Plugin } from '../../../core/public'; +import { + CoreSetup, + CoreStart, + Plugin, + PluginInitializerContext, +} from 'opensearch-dashboards/public'; import { DataSourcePluginSetup, DataSourcePluginStart } from './types'; +import { DataSourcePluginConfigType } from '../config'; export class DataSourcePlugin implements Plugin { + constructor( + private readonly initializerContext: PluginInitializerContext + ) {} + public setup(core: CoreSetup): DataSourcePluginSetup { - return {}; + const config = this.initializerContext.config.get(); + return { + dataSourceEnabled: config.enabled, + hideLocalCluster: config.hideLocalCluster, + }; } public start(core: CoreStart): DataSourcePluginStart { - return {}; + const config = this.initializerContext.config.get(); + return { + dataSourceEnabled: config.enabled, + hideLocalCluster: config.hideLocalCluster, + }; } public stop() {} diff --git a/src/plugins/data_source/public/types.ts b/src/plugins/data_source/public/types.ts index 0c57dd1ea1b..030ea5b7709 100644 --- a/src/plugins/data_source/public/types.ts +++ b/src/plugins/data_source/public/types.ts @@ -3,8 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface DataSourcePluginSetup {} +export interface DataSourcePluginSetup { + dataSourceEnabled: boolean; + hideLocalCluster: boolean; +} -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface DataSourcePluginStart {} +export interface DataSourcePluginStart { + dataSourceEnabled: boolean; + hideLocalCluster: boolean; +} diff --git a/src/plugins/data_source/server/index.ts b/src/plugins/data_source/server/index.ts index f05b833817d..b88b1beb863 100644 --- a/src/plugins/data_source/server/index.ts +++ b/src/plugins/data_source/server/index.ts @@ -8,6 +8,10 @@ import { DataSourcePlugin } from './plugin'; import { configSchema, DataSourcePluginConfigType } from '../config'; export const config: PluginConfigDescriptor = { + exposeToBrowser: { + enabled: true, + hideLocalCluster: true, + }, schema: configSchema, }; diff --git a/src/plugins/data_source/server/plugin.ts b/src/plugins/data_source/server/plugin.ts index 1a90d22960e..5eaafc29000 100644 --- a/src/plugins/data_source/server/plugin.ts +++ b/src/plugins/data_source/server/plugin.ts @@ -134,8 +134,6 @@ export class DataSourcePlugin implements Plugin createDataSourceError(e), - dataSourceEnabled: () => config.enabled, - defaultClusterEnabled: () => config.defaultCluster, registerCredentialProvider, }; } diff --git a/src/plugins/data_source/server/types.ts b/src/plugins/data_source/server/types.ts index b54eb5db0eb..146a28eb8cf 100644 --- a/src/plugins/data_source/server/types.ts +++ b/src/plugins/data_source/server/types.ts @@ -76,8 +76,6 @@ declare module 'src/core/server' { export interface DataSourcePluginSetup { createDataSourceError: (err: any) => DataSourceError; - dataSourceEnabled: () => boolean; - defaultClusterEnabled: () => boolean; registerCredentialProvider: (method: AuthenticationMethod) => void; } diff --git a/src/plugins/data_source_management/public/components/cluster_selector/__snapshots__/cluster_selector.test.tsx.snap b/src/plugins/data_source_management/public/components/cluster_selector/__snapshots__/cluster_selector.test.tsx.snap index 3012d5c61bc..34c1df89f05 100644 --- a/src/plugins/data_source_management/public/components/cluster_selector/__snapshots__/cluster_selector.test.tsx.snap +++ b/src/plugins/data_source_management/public/components/cluster_selector/__snapshots__/cluster_selector.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ClusterSelector should render normally 1`] = ` +exports[`ClusterSelector should render normally with local cluster is hidden 1`] = ` +`; + +exports[`ClusterSelector should render normally with local cluster not hidden 1`] = ` + +`; + +exports[`ClusterSelector: check dataSource options should always place local cluster option as the first option when local cluster not hidden 1`] = ` + { let component: ShallowWrapper, React.Component<{}, {}, any>>; @@ -19,18 +20,39 @@ describe('ClusterSelector', () => { client = { find: jest.fn().mockResolvedValue([]), } as any; + }); + + it('should render normally with local cluster not hidden', () => { component = shallow( ); + expect(component).toMatchSnapshot(); + expect(client.find).toBeCalledWith({ + fields: ['id', 'description', 'title'], + perPage: 10000, + type: 'data-source', + }); + expect(toasts.addWarning).toBeCalledTimes(0); }); - it('should render normally', () => { + it('should render normally with local cluster is hidden', () => { + component = shallow( + + ); expect(component).toMatchSnapshot(); expect(client.find).toBeCalledWith({ fields: ['id', 'description', 'title'], @@ -40,3 +62,36 @@ describe('ClusterSelector', () => { expect(toasts.addWarning).toBeCalledTimes(0); }); }); + +describe('ClusterSelector: check dataSource options', () => { + let component: ShallowWrapper, React.Component<{}, {}, any>>; + let client: SavedObjectsClientContract; + const { toasts } = notificationServiceMock.createStartContract(); + const nextTick = () => new Promise((res) => process.nextTick(res)); + + beforeEach(async () => { + client = { + find: jest.fn().mockResolvedValue([]), + } as any; + + mockResponseForSavedObjectsCalls(client, 'find', getDataSourcesResponse); + }); + + it('should always place local cluster option as the first option when local cluster not hidden', async () => { + component = shallow( + + ); + + component.instance().componentDidMount!(); + await nextTick(); + expect(component).toMatchSnapshot(); + expect(toasts.addWarning).toBeCalledTimes(0); + }); +}); diff --git a/src/plugins/data_source_management/public/components/cluster_selector/cluster_selector.tsx b/src/plugins/data_source_management/public/components/cluster_selector/cluster_selector.tsx index c30c7624d51..59f73f1b879 100644 --- a/src/plugins/data_source_management/public/components/cluster_selector/cluster_selector.tsx +++ b/src/plugins/data_source_management/public/components/cluster_selector/cluster_selector.tsx @@ -21,6 +21,7 @@ interface ClusterSelectorProps { notifications: ToastsStart; onSelectedDataSource: (clusterOption: ClusterOption[]) => void; disabled: boolean; + hideLocalCluster: boolean; fullWidth: boolean; } @@ -41,8 +42,8 @@ export class ClusterSelector extends React.Component(null); @@ -136,6 +138,7 @@ function DevToolsWrapper({ notifications={toasts} onSelectedDataSource={onChange} disabled={!dataSourceEnabled} + hideLocalCluster={hideLocalCluster} fullWidth={false} />
@@ -153,7 +156,12 @@ function DevToolsWrapper({ mountedTool.current.devTool !== activeDevTool || mountedTool.current.mountpoint !== element) ) { - await remount(element); + let initialDataSourceId; + if (!dataSourceEnabled || (dataSourceEnabled && !hideLocalCluster)) { + initialDataSourceId = ''; + } + + await remount(element, initialDataSourceId); } }} /> @@ -212,6 +220,7 @@ export function renderApp( { dataSource }: DevToolsSetupDependencies ) { const dataSourceEnabled = !!dataSource; + const hideLocalCluster = dataSource?.hideLocalCluster ?? false; if (redirectOnMissingCapabilities(application)) { return () => {}; } @@ -241,6 +250,7 @@ export function renderApp( savedObjects={savedObjects} notifications={notifications} dataSourceEnabled={dataSourceEnabled} + hideLocalCluster={hideLocalCluster} /> )} /> diff --git a/src/plugins/home/public/application/components/sample_data_set_card.js b/src/plugins/home/public/application/components/sample_data_set_card.js index 7d8b97a1c98..086484fa12c 100644 --- a/src/plugins/home/public/application/components/sample_data_set_card.js +++ b/src/plugins/home/public/application/components/sample_data_set_card.js @@ -65,6 +65,15 @@ export class SampleDataSetCard extends React.Component { }; renderBtn = () => { + const dataSourceEnabled = this.props.isDataSourceEnabled; + const hideLocalCluster = this.props.isLocalClusterHidden; + const dataSourceId = this.props.dataSourceId; + + let buttonDisabled = false; + if (dataSourceEnabled && hideLocalCluster) { + buttonDisabled = dataSourceId === undefined; + } + switch (this.props.status) { case INSTALLED_STATUS: return ( @@ -121,6 +130,7 @@ export class SampleDataSetCard extends React.Component { ); diff --git a/src/plugins/home/public/application/components/tutorial_directory.js b/src/plugins/home/public/application/components/tutorial_directory.js index 9f71652bfd8..45da6b07fd9 100644 --- a/src/plugins/home/public/application/components/tutorial_directory.js +++ b/src/plugins/home/public/application/components/tutorial_directory.js @@ -83,6 +83,7 @@ class TutorialDirectoryUi extends React.Component { tutorialCards: [], notices: getServices().tutorialService.getDirectoryNotices(), isDataSourceEnabled: !!getServices().dataSource, + isLocalClusterHidden: getServices().dataSource?.hideLocalCluster ?? false, }; } @@ -185,6 +186,7 @@ class TutorialDirectoryUi extends React.Component { addBasePath={this.props.addBasePath} dataSourceId={this.state.selectedDataSourceId} isDataSourceEnabled={this.state.isDataSourceEnabled} + isLocalClusterHidden={this.state.isLocalClusterHidden} /> ); } @@ -224,7 +226,7 @@ class TutorialDirectoryUi extends React.Component { }; renderDataSourceSelector = () => { - const { isDataSourceEnabled } = this.state; + const { isDataSourceEnabled, isLocalClusterHidden } = this.state; return isDataSourceEnabled ? (
@@ -233,6 +235,7 @@ class TutorialDirectoryUi extends React.Component { notifications={getServices().toastNotifications} onSelectedDataSource={this.onSelectedDataSourceChange} disabled={!isDataSourceEnabled} + hideLocalCluster={isLocalClusterHidden} />
) : null; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/__snapshots__/step_data_source.test.tsx.snap b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/__snapshots__/step_data_source.test.tsx.snap index ba690127435..4f8ef0404f5 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/__snapshots__/step_data_source.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/__snapshots__/step_data_source.test.tsx.snap @@ -1,6 +1,44 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`StepDataSource should render normally 1`] = ` +exports[`StepDataSource should render normally with hideLocalCluster false 1`] = ` + + +
+ + +`; + +exports[`StepDataSource should render normally with hideLocalCluster true 1`] = ` + + +
+ + +`; + +exports[`StepDataSource should render normally with hideLocalCluster undefined 1`] = `
{ goToNextStep={() => {}} isNextStepDisabled={true} stepInfo={{ totalStepNumber: 0, currentStepNumber: 0 }} + hideLocalCluster={false} /> ); @@ -53,6 +54,7 @@ describe('Header', () => { goToNextStep={() => {}} isNextStepDisabled={true} stepInfo={{ totalStepNumber: 0, currentStepNumber: 0 }} + hideLocalCluster={false} /> ); @@ -79,6 +81,7 @@ describe('Header', () => { goToNextStep={() => {}} isNextStepDisabled={true} stepInfo={{ totalStepNumber: 0, currentStepNumber: 0 }} + hideLocalCluster={false} /> ); @@ -96,4 +99,23 @@ describe('Header', () => { .prop('isDisabled') ).toEqual(false); }); + + it('should disable next step when local cluster option is hidden and no other option selected', () => { + const component = shallowWithIntl( +
{}} + dataSourceRef={{ type: 'type', id: 'id', title: 'title' }!} + goToNextStep={() => {}} + isNextStepDisabled={true} + stepInfo={{ totalStepNumber: 0, currentStepNumber: 0 }} + hideLocalCluster={true} + /> + ); + + expect( + component + .find('[data-test-subj="createIndexPatternStepDataSourceNextStepButton"]') + .prop('isDisabled') + ).toEqual(true); + }); }); 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 e5a6fdf60c0..bf011d5d14b 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 @@ -34,14 +34,22 @@ interface HeaderProps { goToNextStep: (dataSourceRef: DataSourceRef) => void; isNextStepDisabled: boolean; stepInfo: StepInfo; + hideLocalCluster: boolean; } export const Header: React.FC = (props: HeaderProps) => { - const { dataSourceRef, onDataSourceSelected, goToNextStep, isNextStepDisabled, stepInfo } = props; + const { + dataSourceRef, + onDataSourceSelected, + goToNextStep, + isNextStepDisabled, + stepInfo, + hideLocalCluster, + } = props; const { currentStepNumber, totalStepNumber } = stepInfo; - const [defaultChecked, setDefaultChecked] = useState(true); - const [dataSourceChecked, setDataSourceChecked] = useState(false); + const [defaultChecked, setDefaultChecked] = useState(!hideLocalCluster); + const [dataSourceChecked, setDataSourceChecked] = useState(hideLocalCluster); const [dataSources, setDataSources] = useState([]); const [isLoading, setIsLoading] = useState(false); @@ -113,34 +121,38 @@ export const Header: React.FC = (props: HeaderProps) => { defaultMessage="Pick a data source within which to configure index patterns." /> - - + + + } + checked={defaultChecked} + onChange={(e) => onChangeDefaultChecked(e)} + compressed /> - } - checked={defaultChecked} - onChange={(e) => onChangeDefaultChecked(e)} - compressed - /> - - + + } + checked={dataSourceChecked} + onChange={(e) => onChangeDataSourceChecked(e)} + compressed /> - } - checked={dataSourceChecked} - onChange={(e) => onChangeDataSourceChecked(e)} - compressed - /> + + )} {dataSourceChecked && ( diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/step_data_source.test.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/step_data_source.test.tsx index 42c5ffa4cee..3ce1e5f441b 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/step_data_source.test.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/step_data_source.test.tsx @@ -8,7 +8,7 @@ import { shallow } from 'enzyme'; import { StepDataSource } from './step_data_source'; describe('StepDataSource', () => { - it('should render normally', () => { + it('should render normally with hideLocalCluster undefined', () => { const component = shallow( {}} @@ -18,4 +18,28 @@ describe('StepDataSource', () => { expect(component).toMatchSnapshot(); }); + + it('should render normally with hideLocalCluster false', () => { + const component = shallow( + {}} + stepInfo={{ totalStepNumber: 0, currentStepNumber: 0 }} + hideLocalCluster={false} + /> + ); + + expect(component).toMatchSnapshot(); + }); + + it('should render normally with hideLocalCluster true', () => { + const component = shallow( + {}} + stepInfo={{ totalStepNumber: 0, currentStepNumber: 0 }} + hideLocalCluster={true} + /> + ); + + expect(component).toMatchSnapshot(); + }); }); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/step_data_source.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/step_data_source.tsx index fa91b455ae6..4dcd4661884 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/step_data_source.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/step_data_source.tsx @@ -13,10 +13,11 @@ import { Header } from './components/header'; interface StepDataSourceProps { goToNextStep: (dataSourceRef: DataSourceRef) => void; stepInfo: StepInfo; + hideLocalCluster: boolean; } export const StepDataSource = (props: StepDataSourceProps) => { - const { goToNextStep, stepInfo } = props; + const { goToNextStep, stepInfo, hideLocalCluster } = props; const [selectedDataSource, setSelectedDataSource] = useState(); const [isNextStepDisabled, setIsNextStepDisabled] = useState(true); @@ -37,6 +38,7 @@ export const StepDataSource = (props: StepDataSourceProps) => { goToNextStep={() => goToNextStep(selectedDataSource!)} isNextStepDisabled={isNextStepDisabled} stepInfo={stepInfo} + hideLocalCluster={hideLocalCluster} /> ); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx index 37669ba43e9..337efa752ae 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx @@ -280,6 +280,8 @@ export class CreateIndexPatternWizard extends Component< currentStepNumber: getCurrentStepNumber(step, this.dataSourceEnabled), }; + const hideLocalCluster = this.context.services.hideLocalCluster; + if (isInitiallyLoadingIndices) { return ; } @@ -291,7 +293,11 @@ export class CreateIndexPatternWizard extends Component< {header} - + ); } diff --git a/src/plugins/index_pattern_management/public/management_app/mount_management_section.tsx b/src/plugins/index_pattern_management/public/management_app/mount_management_section.tsx index 162d1d0876c..af37e6ddb71 100644 --- a/src/plugins/index_pattern_management/public/management_app/mount_management_section.tsx +++ b/src/plugins/index_pattern_management/public/management_app/mount_management_section.tsx @@ -36,6 +36,7 @@ import { i18n } from '@osd/i18n'; import { I18nProvider } from '@osd/i18n/react'; import { StartServicesAccessor } from 'src/core/public'; +import { DataSourcePluginSetup } from 'src/plugins/data_source/public'; import { OpenSearchDashboardsContextProvider } from '../../../opensearch_dashboards_react/public'; import { ManagementAppMountParams } from '../../../management/public'; import { @@ -60,15 +61,17 @@ const readOnlyBadge = { export async function mountManagementSection( getStartServices: StartServicesAccessor, params: ManagementAppMountParams, - getMlCardState: () => MlCardState + getMlCardState: () => MlCardState, + dataSource?: DataSourcePluginSetup ) { const [ { chrome, application, savedObjects, uiSettings, notifications, overlays, http, docLinks }, - { data, dataSource }, + { data }, indexPatternManagementStart, ] = await getStartServices(); const canSave = Boolean(application.capabilities.indexPatterns.save); - const dataSourceEnabled = !!dataSource; + const dataSourceEnabled = dataSource?.dataSourceEnabled ?? false; + const hideLocalCluster = dataSource?.hideLocalCluster ?? false; if (!canSave) { chrome.setBadge(readOnlyBadge); @@ -88,6 +91,7 @@ export async function mountManagementSection( setBreadcrumbs: params.setBreadcrumbs, getMlCardState, dataSourceEnabled, + hideLocalCluster, }; ReactDOM.render( diff --git a/src/plugins/index_pattern_management/public/plugin.ts b/src/plugins/index_pattern_management/public/plugin.ts index cf68e043b76..98eaab6160e 100644 --- a/src/plugins/index_pattern_management/public/plugin.ts +++ b/src/plugins/index_pattern_management/public/plugin.ts @@ -31,7 +31,7 @@ import { i18n } from '@osd/i18n'; import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public'; import { DataPublicPluginStart } from 'src/plugins/data/public'; -import { DataSourcePluginStart } from 'src/plugins/data_source/public'; +import { DataSourcePluginSetup, DataSourcePluginStart } from 'src/plugins/data_source/public'; import { UrlForwardingSetup } from '../../url_forwarding/public'; import { IndexPatternManagementService, @@ -44,6 +44,7 @@ import { ManagementSetup } from '../../management/public'; export interface IndexPatternManagementSetupDependencies { management: ManagementSetup; urlForwarding: UrlForwardingSetup; + dataSource?: DataSourcePluginSetup; } export interface IndexPatternManagementStartDependencies { @@ -75,8 +76,10 @@ export class IndexPatternManagementPlugin public setup( core: CoreSetup, - { management, urlForwarding }: IndexPatternManagementSetupDependencies + dependencies: IndexPatternManagementSetupDependencies ) { + const { urlForwarding, management, dataSource } = dependencies; + const opensearchDashboardsSection = management.sections.section.opensearchDashboards; if (!opensearchDashboardsSection) { @@ -103,8 +106,11 @@ export class IndexPatternManagementPlugin mount: async (params) => { const { mountManagementSection } = await import('./management_app'); - return mountManagementSection(core.getStartServices, params, () => - this.indexPatternManagementService.environmentService.getEnvironment().ml() + return mountManagementSection( + core.getStartServices, + params, + () => this.indexPatternManagementService.environmentService.getEnvironment().ml(), + dataSource ); }, }); diff --git a/src/plugins/index_pattern_management/public/types.ts b/src/plugins/index_pattern_management/public/types.ts index 24dc2cd59c3..7b2cd8575a7 100644 --- a/src/plugins/index_pattern_management/public/types.ts +++ b/src/plugins/index_pattern_management/public/types.ts @@ -59,6 +59,7 @@ export interface IndexPatternManagmentContext { setBreadcrumbs: ManagementAppMountParams['setBreadcrumbs']; getMlCardState: () => MlCardState; dataSourceEnabled: boolean; + hideLocalCluster: boolean; } export type IndexPatternManagmentContextValue = OpenSearchDashboardsReactContextValue< diff --git a/src/plugins/saved_objects_management/public/management_section/mount_section.tsx b/src/plugins/saved_objects_management/public/management_section/mount_section.tsx index f56ece8b51d..967dd93290d 100644 --- a/src/plugins/saved_objects_management/public/management_section/mount_section.tsx +++ b/src/plugins/saved_objects_management/public/management_section/mount_section.tsx @@ -45,6 +45,7 @@ interface MountParams { serviceRegistry: ISavedObjectsManagementServiceRegistry; mountParams: ManagementAppMountParams; dataSourceEnabled: boolean; + hideLocalCluster: boolean; } let allowedObjectTypes: string[] | undefined; @@ -60,6 +61,7 @@ export const mountManagementSection = async ({ mountParams, serviceRegistry, dataSourceEnabled, + hideLocalCluster, }: MountParams) => { const [coreStart, { data, uiActions }, pluginStart] = await core.getStartServices(); const { element, history, setBreadcrumbs } = mountParams; @@ -111,6 +113,7 @@ export const mountManagementSection = async ({ allowedTypes={allowedObjectTypes} setBreadcrumbs={setBreadcrumbs} dataSourceEnabled={dataSourceEnabled} + hideLocalCluster={hideLocalCluster} /> diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/flyout.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/flyout.test.tsx.snap index b40211fffe3..e15e72d6a2c 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/flyout.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/flyout.test.tsx.snap @@ -526,7 +526,7 @@ Array [ ] `; -exports[`Flyout should render cluster selector and import options 1`] = ` +exports[`Flyout should render cluster selector and import options when local cluster option is hidden 1`] = ` + + + + + + +`; + +exports[`Flyout should render cluster selector and import options when local cluster option is not hidden 1`] = ` + + + +

+ +

+
+
+ + + + } + labelType="label" + > + + } + onChange={[Function]} + /> + +
+ + + + Import options + + , + } + } + > + + + + + + +
+
+
+ + + + + + + + + { expect(component).toMatchSnapshot(); }); - it('should render cluster selector and import options', async () => { + it('should render cluster selector and import options when local cluster option is not hidden', async () => { const component = shallowRender({ ...defaultProps, dataSourceEnabled: true, + hideLocalCluster: false, + notifications: notificationServiceMock.createStartContract(), + }); + + // Ensure all promises resolve + await new Promise((resolve) => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + + expect(component).toMatchSnapshot(); + }); + + it('should render cluster selector and import options when local cluster option is hidden', async () => { + const component = shallowRender({ + ...defaultProps, + dataSourceEnabled: true, + hideLocalCluster: true, notifications: notificationServiceMock.createStartContract(), }); diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx index 82b763d18db..f170713e023 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx @@ -94,6 +94,7 @@ export interface FlyoutProps { http: HttpStart; search: DataPublicPluginStart['search']; dataSourceEnabled: boolean; + hideLocalCluster: boolean; savedObjects: SavedObjectsClientContract; notifications: NotificationsStart; } @@ -821,6 +822,7 @@ export class Flyout extends Component { notifications={this.props.notifications.toasts} onSelectedDataSource={this.onSelectedDataSourceChange} disabled={!this.props.dataSourceEnabled} + hideLocalCluster={this.props.hideLocalCluster} fullWidth={true} /> @@ -841,11 +843,16 @@ export class Flyout extends Component { } renderFooter() { - const { isLegacyFile, status } = this.state; + const { isLegacyFile, status, selectedDataSourceId } = this.state; const { done, close } = this.props; let confirmButton; + let importButtonDisabled = false; + if (this.props.dataSourceEnabled && this.props.hideLocalCluster && !selectedDataSourceId) { + importButtonDisabled = true; + } + if (status === 'success') { confirmButton = ( @@ -877,6 +884,7 @@ export class Flyout extends Component { size="s" fill isLoading={status === 'loading'} + disabled={importButtonDisabled} data-test-subj="importSavedObjectsImportBtn" > boolean; dateFormat: string; dataSourceEnabled: boolean; + hideLocalCluster: boolean; } export interface SavedObjectsTableState { @@ -560,6 +561,7 @@ export class SavedObjectsTable extends Component diff --git a/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx b/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx index 1776f3e7bfd..b3ef976d828 100644 --- a/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx +++ b/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx @@ -50,6 +50,7 @@ const SavedObjectsTablePage = ({ namespaceRegistry, setBreadcrumbs, dataSourceEnabled, + hideLocalCluster, }: { coreStart: CoreStart; dataStart: DataPublicPluginStart; @@ -60,6 +61,7 @@ const SavedObjectsTablePage = ({ namespaceRegistry: SavedObjectsManagementNamespaceServiceStart; setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void; dataSourceEnabled: boolean; + hideLocalCluster: boolean; }) => { const capabilities = coreStart.application.capabilities; const itemsPerPage = coreStart.uiSettings.get('savedObjects:perPage', 50); @@ -105,6 +107,7 @@ const SavedObjectsTablePage = ({ return inAppUrl ? Boolean(get(capabilities, inAppUrl.uiCapabilitiesPath)) : false; }} dataSourceEnabled={dataSourceEnabled} + hideLocalCluster={hideLocalCluster} /> ); }; diff --git a/src/plugins/saved_objects_management/public/plugin.ts b/src/plugins/saved_objects_management/public/plugin.ts index 690dee9c21e..d03342a4f1d 100644 --- a/src/plugins/saved_objects_management/public/plugin.ts +++ b/src/plugins/saved_objects_management/public/plugin.ts @@ -139,6 +139,7 @@ export class SavedObjectsManagementPlugin serviceRegistry: this.serviceRegistry, mountParams, dataSourceEnabled: !!dataSource, + hideLocalCluster: dataSource?.hideLocalCluster ?? false, }); }, });