From 878ada6652b160d4aea3db2301728f1ab98e6446 Mon Sep 17 00:00:00 2001 From: Eric Wei Date: Fri, 5 Apr 2024 16:53:55 -0700 Subject: [PATCH] [Discover - datasource selector] Add extension group title and modal (#5815) * add log explorer re-directon modal Signed-off-by: Eric * adjustments to comments Signed-off-by: Eric * add one missing i18n Signed-off-by: Eric * add redirection text to group title Signed-off-by: Eric * include changes in changelog Signed-off-by: Eric * remove redundent title addition and unnecessary modal toggle functions Signed-off-by: Eric * remove one comment Signed-off-by: Eric * add i18n Signed-off-by: Eric * add unit tests for modal Signed-off-by: Eric * test id change Signed-off-by: Eric * add devDependencies for tests Signed-off-by: Eric * use open confirm api and move mock file to discover mock folder Signed-off-by: Eric * remove unused type Signed-off-by: Eric * remove modal for log explorer redirection Signed-off-by: Eric * modify changelog Signed-off-by: Eric * remove modal test Signed-off-by: Eric * remove one modal related test Signed-off-by: Eric --------- Signed-off-by: Eric --- CHANGELOG.md | 10 +- package.json | 2 + .../datasource_selectable.tsx | 27 ++++- .../public/components/sidebar/index.test.tsx | 110 ++++++++++++++++++ .../public/components/sidebar/index.tsx | 20 +++- .../public/__mock__/index.test.mock.ts | 28 +++++ yarn.lock | 14 +++ 7 files changed, 192 insertions(+), 19 deletions(-) create mode 100644 src/plugins/data_explorer/public/components/sidebar/index.test.tsx create mode 100644 src/plugins/discover/public/__mock__/index.test.mock.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index a2c5c0a7b165..64b89cc0365d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,15 +39,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Discover] Enhanced the data source selector with added sorting functionality ([#5609](https://github.com/opensearch-project/OpenSearch-Dashboards/issues/5609)) - [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)) -- [Multiple Datasource] Add api registry and allow it to be added into client config in data source plugin ([#5895](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5895)) -- [Multiple Datasource] Concatenate data source name with index pattern name and change delimiter to double colon ([#5907](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5907)) -- [Multiple Datasource] Refactor client and legacy client to use authentication registry ([#5881](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5881)) -- [Multiple Datasource] Improved error handling for the search API when a null value is passed for the dataSourceId ([#5882](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5882)) -- [Multiple Datasource] Hide/Show authentication method in multi data source plugin based on configuration ([#5916](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5916)) -- [[Dynamic Configurations] Add support for dynamic application configurations ([#5855](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5855)) -- [Workspace] Optional workspaces params in repository ([#5949](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5949)) +- [Discover] Add extension group title to non-index data source groups to indicate log explorer redirection in discover data source selector. ([#5815](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5815)) ### 🐛 Bug Fixes diff --git a/package.json b/package.json index cb24289e686f..8bdd92b6371c 100644 --- a/package.json +++ b/package.json @@ -329,6 +329,7 @@ "@types/react-router-dom": "^5.3.2", "@types/react-virtualized": "^9.18.7", "@types/recompose": "^0.30.6", + "@types/redux-mock-store": "^1.0.6", "@types/selenium-webdriver": "^4.0.9", "@types/semver": "^7.5.0", "@types/sinon": "^7.0.13", @@ -446,6 +447,7 @@ "react-test-renderer": "^16.12.0", "reactcss": "1.2.3", "redux": "^4.0.5", + "redux-mock-store": "^1.5.4", "regenerate": "^1.4.0", "reselect": "^4.0.0", "resize-observer-polyfill": "^1.5.1", diff --git a/src/plugins/data/public/data_sources/datasource_selector/datasource_selectable.tsx b/src/plugins/data/public/data_sources/datasource_selector/datasource_selectable.tsx index 1c6876815a0e..682c9c5f5a24 100644 --- a/src/plugins/data/public/data_sources/datasource_selector/datasource_selectable.tsx +++ b/src/plugins/data/public/data_sources/datasource_selector/datasource_selectable.tsx @@ -13,10 +13,17 @@ import { DataSourceGroup, DataSourceSelectableProps } from './types'; type DataSourceTypeKey = 'DEFAULT_INDEX_PATTERNS' | 's3glue' | 'spark'; // Mapping between datasource type and its display name. +// Temporary solution, will be removed along with refactoring of data source APIs const DATASOURCE_TYPE_DISPLAY_NAME_MAP: Record = { - DEFAULT_INDEX_PATTERNS: 'Index patterns', - s3glue: 'Amazon S3', - spark: 'Spark', + DEFAULT_INDEX_PATTERNS: i18n.translate('dataExplorer.dataSourceSelector.indexPatternGroupTitle', { + defaultMessage: 'Index patterns', + }), + s3glue: i18n.translate('dataExplorer.dataSourceSelector.amazonS3GroupTitle', { + defaultMessage: 'Amazon S3', + }), + spark: i18n.translate('dataExplorer.dataSourceSelector.sparkGroupTitle', { + defaultMessage: 'Spark', + }), }; type DataSetType = ISourceDataSet['data_sets'][number]; @@ -66,7 +73,19 @@ const getSourceList = (allDataSets: ISourceDataSet[]) => { const finalList = [] as DataSourceGroup[]; allDataSets.forEach((curDataSet) => { const typeKey = curDataSet.ds.getType() as DataSourceTypeKey; - const groupName = DATASOURCE_TYPE_DISPLAY_NAME_MAP[typeKey] || 'Default Group'; + let groupName = + DATASOURCE_TYPE_DISPLAY_NAME_MAP[typeKey] || + i18n.translate('dataExplorer.dataSourceSelector.defaultGroupTitle', { + defaultMessage: 'Default Group', + }); + + // add '- Opens in Log Explorer' to hint user that selecting these types of data sources + // will lead to redirection to log explorer + if (typeKey !== 'DEFAULT_INDEX_PATTERNS') { + groupName = `${groupName}${i18n.translate('dataExplorer.dataSourceSelector.redirectionHint', { + defaultMessage: ' - Opens in Log Explorer', + })}`; + } const existingGroup = finalList.find((item) => item.label === groupName); const mappedOptions = curDataSet.data_sets.map((dataSet) => diff --git a/src/plugins/data_explorer/public/components/sidebar/index.test.tsx b/src/plugins/data_explorer/public/components/sidebar/index.test.tsx new file mode 100644 index 000000000000..eccb0ffa909e --- /dev/null +++ b/src/plugins/data_explorer/public/components/sidebar/index.test.tsx @@ -0,0 +1,110 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { render, fireEvent, waitFor } from '@testing-library/react'; +import { Sidebar } from './index'; // Adjust the import path as necessary +import { Provider } from 'react-redux'; +import configureMockStore from 'redux-mock-store'; +import { MockS3DataSource } from '../../../../discover/public/__mock__/index.test.mock'; +import { createMemoryHistory } from 'history'; +import { Router } from 'react-router-dom'; + +const mockStore = configureMockStore(); +const initialState = { + metadata: { indexPattern: 'some-index-pattern-id' }, +}; +const store = mockStore(initialState); + +jest.mock('../../../../opensearch_dashboards_react/public', () => { + return { + toMountPoint: jest.fn().mockImplementation((component) => () => component), + useOpenSearchDashboards: jest.fn().mockReturnValue({ + services: { + data: { + indexPatterns: {}, + dataSources: { + dataSourceService: { + dataSources$: { + subscribe: jest.fn((callback) => { + callback({ + 's3-prod-mock': new MockS3DataSource({ + name: 's3-prod-mock', + type: 's3glue', + metadata: {}, + }), + }); + return { unsubscribe: jest.fn() }; + }), + }, + }, + }, + }, + notifications: { + toasts: { + addError: jest.fn(), + }, + }, + application: { + navigateToUrl: jest.fn(), + }, + overlays: { + openConfirm: jest.fn(), + }, + }, + }), + withOpenSearchDashboards: () => (Component: React.ComponentClass) => (props: any) => ( + + ), + }; +}); + +jest.mock('../../../../data_explorer/public', () => ({ + useTypedSelector: jest.fn(), + useTypedDispatch: jest.fn(), +})); + +describe('Sidebar Component', () => { + it('renders without crashing', () => { + const { container, getByTestId } = render( + + + + ); + expect(container).toBeInTheDocument(); + expect(getByTestId('dataExplorerDSSelect')).toBeInTheDocument(); + }); + + it('shows title extensions on the non-index pattern data source', () => { + const { getByText, getByTestId } = render( + + + + ); + + fireEvent.click(getByTestId('comboBoxToggleListButton')); + waitFor(() => { + expect(getByText('Open in Log Explorer')).toBeInTheDocument(); + }); + }); + + it('redirects to log explorer when clicking open-in-log-explorer button', () => { + const history = createMemoryHistory(); + const { getByText, getByTestId } = render( + + + + + + ); + + fireEvent.click(getByTestId('comboBoxToggleListButton')); + waitFor(() => { + expect(getByText('s3-prod-mock')).toBeInTheDocument(); + fireEvent.click(getByText('s3-prod-mock')); + expect(history.location.pathname).toContain('observability-logs#/explorer'); + }); + }); +}); diff --git a/src/plugins/data_explorer/public/components/sidebar/index.tsx b/src/plugins/data_explorer/public/components/sidebar/index.tsx index ee2dd63a6d57..ff07a59ab4b7 100644 --- a/src/plugins/data_explorer/public/components/sidebar/index.tsx +++ b/src/plugins/data_explorer/public/components/sidebar/index.tsx @@ -59,23 +59,31 @@ export const Sidebar: FC = ({ children }) => { } }, [indexPatternId, activeDataSources, dataSourceOptionList]); + const redirectToLogExplorer = useCallback( + (dsName: string, dsType: string) => { + return application.navigateToUrl( + `../observability-logs#/explorer?datasourceName=${dsName}&datasourceType=${dsType}` + ); + }, + [application] + ); + const handleSourceSelection = useCallback( (selectedDataSources: DataSourceOption[]) => { if (selectedDataSources.length === 0) { setSelectedSources(selectedDataSources); return; } - // Temporary redirection solution for 2.11, where clicking non-index-pattern datasource - // will redirect user to Observability event explorer + // Temporary redirection solution for 2.11, where clicking non-index-pattern data sources + // will prompt users with modal explaining they are being redirected to Observability log explorer if (selectedDataSources[0]?.ds?.getType() !== 'DEFAULT_INDEX_PATTERNS') { - return application.navigateToUrl( - `../observability-logs#/explorer?datasourceName=${selectedDataSources[0].label}&datasourceType=${selectedDataSources[0].type}` - ); + redirectToLogExplorer(selectedDataSources[0].label, selectedDataSources[0].type); + return; } setSelectedSources(selectedDataSources); dispatch(setIndexPattern(selectedDataSources[0].value)); }, - [application, dispatch] + [dispatch, redirectToLogExplorer, setSelectedSources] ); const handleGetDataSetError = useCallback( diff --git a/src/plugins/discover/public/__mock__/index.test.mock.ts b/src/plugins/discover/public/__mock__/index.test.mock.ts new file mode 100644 index 000000000000..6b09d1d84253 --- /dev/null +++ b/src/plugins/discover/public/__mock__/index.test.mock.ts @@ -0,0 +1,28 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export class MockS3DataSource { + protected name: string; + protected type: string; + protected metadata: any; + + constructor({ name, type, metadata }: { name: string; type: string; metadata: any }) { + this.name = name; + this.type = type; + this.metadata = metadata; + } + + async getDataSet(dataSetParams?: any) { + return [this.name]; + } + + getName() { + return this.name; + } + + getType() { + return this.type; + } +} diff --git a/yarn.lock b/yarn.lock index e1ac33225f8d..a6bd5836ef6c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3904,6 +3904,13 @@ dependencies: "@types/react" "*" +"@types/redux-mock-store@^1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/redux-mock-store/-/redux-mock-store-1.0.6.tgz#0a03b2655028b7cf62670d41ac1de5ca1b1f5958" + integrity sha512-eg5RDfhJTXuoJjOMyXiJbaDb1B8tfTaJixscmu+jOusj6adGC0Krntz09Tf4gJgXeCqCrM5bBMd+B7ez0izcAQ== + dependencies: + redux "^4.0.5" + "@types/refractor@^3.0.0": version "3.0.2" resolved "https://registry.yarnpkg.com/@types/refractor/-/refractor-3.0.2.tgz#2d42128d59f78f84d2c799ffc5ab5cadbcba2d82" @@ -15452,6 +15459,13 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" +redux-mock-store@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/redux-mock-store/-/redux-mock-store-1.5.4.tgz#90d02495fd918ddbaa96b83aef626287c9ab5872" + integrity sha512-xmcA0O/tjCLXhh9Fuiq6pMrJCwFRaouA8436zcikdIpYWWCjU76CRk+i2bHx8EeiSiMGnB85/lZdU3wIJVXHTA== + dependencies: + lodash.isplainobject "^4.0.6" + redux-thunk@^2.3.0, redux-thunk@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.1.tgz#0dd8042cf47868f4b29699941de03c9301a75714"