diff --git a/CHANGELOG.md b/CHANGELOG.md index 46fc61baa7b6..0f86e98aedc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * Add updated_at column to objects' tables ([#1218](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/1218)) * [Viz Builder] State validation before dispatching and loading ([#2351](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2351)) * [Viz Builder] Create a new wizard directly on a dashboard ([#2384](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2384)) +* [Multi DataSource] UX enhacement on index pattern management stack ([#2505]https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2505)) ### 🐛 Bug Fixes diff --git a/src/plugins/index_pattern_management/opensearch_dashboards.json b/src/plugins/index_pattern_management/opensearch_dashboards.json index 1efd05cb1a49..611f122c8c16 100644 --- a/src/plugins/index_pattern_management/opensearch_dashboards.json +++ b/src/plugins/index_pattern_management/opensearch_dashboards.json @@ -5,5 +5,5 @@ "ui": true, "optionalPlugins": ["dataSource"], "requiredPlugins": ["management", "data", "urlForwarding"], - "requiredBundles": ["opensearchDashboardsReact", "opensearchDashboardsUtils", "savedObjects"] + "requiredBundles": ["opensearchDashboardsReact", "opensearchDashboardsUtils"] } diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/components/header/__snapshots__/header.test.tsx.snap b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/components/header/__snapshots__/header.test.tsx.snap index e15b9428b45c..ded0546ce491 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/components/header/__snapshots__/header.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_data_source/components/header/__snapshots__/header.test.tsx.snap @@ -1,238 +1,249 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Header should render data source finder when choose to use data source 1`] = ` -
- -

- +
+ +

+ -

-
- - - - - +

+
+ - } - onChange={[Function]} - /> - - + - - - + } + onChange={[Function]} + /> + - } - onChange={[Function]} - /> - - - - + - } - showFilter={false} + onChange={[Function]} /> - - - + + + - + + } + isPreFiltered={false} + onChange={[Function]} + options={Array []} + searchProps={ + Object { + "data-test-subj": "selectDataSources", + "isInvalid": true, + "placeholder": "Search data sources", + } + } + searchable={true} + singleSelection="always" > - - + + - -
+ + + + + + + + + + `; exports[`Header should render normally 1`] = ` -
- -

- +
+ +

+ +

+
+ + -

-
- - + - - - + } + onChange={[Function]} + /> + - } - onChange={[Function]} - /> - - + - - - + } + onChange={[Function]} + /> + - } - onChange={[Function]} - /> - - + - - - - - - - - - -
+ + + + + + + `; 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 0805f6a5f78e..dc8b72341b80 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 @@ -6,13 +6,24 @@ import React from 'react'; import { Header } from '../header'; import { shallow } from 'enzyme'; +import { shallowWithIntl } from 'test_utils/enzyme_helpers'; + +jest.mock('../../../../../../../../../plugins/opensearch_dashboards_react/public', () => ({ + useOpenSearchDashboards: jest.fn().mockReturnValue({ + services: { + notifications: { toast: { addWarning: jest.fn() } }, + }, + }), +})); + +afterAll(() => jest.clearAllMocks()); describe('Header', () => { it('should render normally', () => { - const component = shallow( + const component = shallowWithIntl(
{}} - dataSourceRef={{ type: 'type', id: 'id' }!} + dataSourceRef={{ type: 'type', id: 'id', title: 'title' }!} goToNextStep={() => {}} isNextStepDisabled={true} stepInfo={{ totalStepNumber: 0, currentStepNumber: 0 }} @@ -22,11 +33,11 @@ describe('Header', () => { expect(component).toMatchSnapshot(); }); - it('should render data source finder when choose to use data source', () => { - const component = shallow( + it('should render existing data sources list when choose to use data source', () => { + const component = shallowWithIntl(
{}} - dataSourceRef={{ type: 'type', id: 'id' }!} + dataSourceRef={{ type: 'type', id: 'id', title: 'title' }!} goToNextStep={() => {}} isNextStepDisabled={true} stepInfo={{ totalStepNumber: 0, currentStepNumber: 0 }} @@ -45,10 +56,10 @@ describe('Header', () => { }); it('should disable next step before select data source', () => { - const component = shallow( + const component = shallowWithIntl(
{}} - dataSourceRef={{ type: 'type', id: 'id' }!} + dataSourceRef={{ type: 'type', id: 'id', title: 'title' }!} goToNextStep={() => {}} isNextStepDisabled={true} stepInfo={{ totalStepNumber: 0, currentStepNumber: 0 }} @@ -71,10 +82,10 @@ describe('Header', () => { }); it('should enable next step when pick default option', () => { - const component = shallow( + const component = shallowWithIntl(
{}} - dataSourceRef={{ type: 'type', id: 'id' }!} + dataSourceRef={{ type: 'type', id: 'id', title: 'title' }!} goToNextStep={() => {}} isNextStepDisabled={true} stepInfo={{ totalStepNumber: 0, currentStepNumber: 0 }} 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 ea463a97a709..75f641b70164 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 @@ -3,49 +3,88 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useState } from 'react'; +import React, { Fragment, useState } from 'react'; import './header.scss'; import { - EuiTitle, - EuiSpacer, - EuiText, - EuiFlexItem, EuiButton, EuiFlexGroup, + EuiFlexItem, EuiRadio, + EuiSelectable, + EuiSpacer, + EuiText, + EuiTitle, } from '@elastic/eui'; -import { FormattedMessage } from '@osd/i18n/react'; import { i18n } from '@osd/i18n'; +import { FormattedMessage } from '@osd/i18n/react'; +import { useEffectOnce } from 'react-use'; import { DataSourceRef, IndexPatternManagmentContext, } from 'src/plugins/index_pattern_management/public/types'; -import { SavedObjectFinderUi } from '../../../../../../../../../plugins/saved_objects/public'; import { useOpenSearchDashboards } from '../../../../../../../../../plugins/opensearch_dashboards_react/public'; -import { StepInfo } from '../../../../types'; +import { getDataSources } from '../../../../../../components/utils'; +import { DataSourceTableItem, StepInfo } from '../../../../types'; +import { LoadingState } from '../../../loading_state'; interface HeaderProps { - onDataSourceSelected: (id: string, type: string) => void; + onDataSourceSelected: (id: string, type: string, title: string) => void; dataSourceRef: DataSourceRef; goToNextStep: (dataSourceRef: DataSourceRef) => void; isNextStepDisabled: boolean; stepInfo: StepInfo; } -const DATA_SOURCE_PAGE_SIZE = 5; - export const Header: React.FC = (props: HeaderProps) => { const { dataSourceRef, onDataSourceSelected, goToNextStep, isNextStepDisabled, stepInfo } = props; const { currentStepNumber, totalStepNumber } = stepInfo; const [defaultChecked, setDefaultChecked] = useState(true); const [dataSourceChecked, setDataSourceChecked] = useState(false); + const [dataSources, setDataSources] = useState([]); + const [isLoading, setIsLoading] = useState(false); + + const { + savedObjects, + notifications: { toasts }, + } = useOpenSearchDashboards().services; + + useEffectOnce(() => { + fetchDataSources(); + }); + + const fetchDataSources = () => { + setIsLoading(true); + getDataSources(savedObjects.client) + .then((fetchedDataSources: DataSourceTableItem[]) => { + setIsLoading(false); + if (fetchedDataSources?.length) { + setDataSources(fetchedDataSources); + } + }) + .catch(() => { + toasts.addWarning( + i18n.translate( + 'indexPatternManagement.createIndexPattern.stepDataSource.fetchDataSourceError', + { + defaultMessage: 'Unable to find existing data sources', + } + ) + ); + }); + }; - const { savedObjects, uiSettings } = useOpenSearchDashboards< - IndexPatternManagmentContext - >().services; + const onSelectedDataSource = (options: DataSourceTableItem[]) => { + const selectedDataSource = options.find(({ checked }) => checked); + setDataSources(options); + onDataSourceSelected( + selectedDataSource!.id, + selectedDataSource!.type, + selectedDataSource!.title + ); + }; const onChangeDefaultChecked = (e) => { setDefaultChecked(e.target.checked); @@ -58,111 +97,119 @@ export const Header: React.FC = (props: HeaderProps) => { }; return ( -
- -

+ <> +
+ +

+ +

+
+ -

-
- - + + + } + 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 && ( - - - + {dataSourceChecked && ( + + + } - )} - savedObjectMetaData={[ - { - type: 'data-source', - getIconForSavedObject: () => 'apps', // todo: #2034 - name: i18n.translate( - 'indexPatternManagement.createIndexPattern.searchSelection.savedObjectType.dataSource', + searchable + searchProps={{ + 'data-test-subj': 'selectDataSources', + placeholder: i18n.translate( + 'indexPatternManagement.createIndexPattern.stepDataSource.searchPlaceHolder', { - defaultMessage: 'Data Source', + defaultMessage: 'Search data sources', } ), - }, - ]} - fixedPageSize={DATA_SOURCE_PAGE_SIZE} - uiSettings={uiSettings} - savedObjects={savedObjects} - /> - - )} - - - - goToNextStep(dataSourceRef)} - isDisabled={isNextStepDisabled && !defaultChecked} - > - - - - -
+ isInvalid: !!dataSources, + }} + singleSelection={'always'} + options={dataSources} + onChange={(newOptions) => onSelectedDataSource(newOptions)} + > + {(list, search) => ( + + {search} + + {list} + + )} + + + )} + + + + goToNextStep(dataSourceRef)} + isDisabled={isNextStepDisabled && !defaultChecked} + > + + + + + + + {isLoading ? : null} + ); }; 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 5e1b1c92f1aa..fa91b455ae6c 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 @@ -21,8 +21,8 @@ export const StepDataSource = (props: StepDataSourceProps) => { const [selectedDataSource, setSelectedDataSource] = useState(); const [isNextStepDisabled, setIsNextStepDisabled] = useState(true); - const onDataSourceSelected = (id: string, selectedType: string) => { - const selected = { id, type: selectedType }; + const onDataSourceSelected = (id: string, selectedType: string, title: string) => { + const selected = { id, type: selectedType, title }; setSelectedDataSource(selected); setIsNextStepDisabled(false); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/header.scss b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/header.scss new file mode 100644 index 000000000000..a71690db08cf --- /dev/null +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/header.scss @@ -0,0 +1,5 @@ +.dataSourceIndexPatternDot { + margin-top: 45px; + margin-left: 5px; + margin-right: 5px; +} diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/header.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/header.tsx index b4e1803a8d21..5e8db4ac9208 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/header.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/components/header/header.tsx @@ -29,6 +29,7 @@ */ import React from 'react'; +import './header.scss'; import { EuiTitle, @@ -45,7 +46,12 @@ import { import { i18n } from '@osd/i18n'; import { FormattedMessage } from '@osd/i18n/react'; +import { + DataSourceRef, + IndexPatternManagmentContext, +} from 'src/plugins/index_pattern_management/public/types'; import { StepInfo } from '../../../../types'; +import { useOpenSearchDashboards } from '../../../../../../../../../plugins/opensearch_dashboards_react/public'; interface HeaderProps { isInputInvalid: boolean; @@ -59,6 +65,7 @@ interface HeaderProps { onChangeIncludingSystemIndices: (event: EuiSwitchEvent) => void; isIncludingSystemIndices: boolean; stepInfo: StepInfo; + dataSourceRef?: DataSourceRef; } export const Header: React.FC = ({ @@ -73,101 +80,176 @@ export const Header: React.FC = ({ onChangeIncludingSystemIndices, isIncludingSystemIndices, stepInfo, + dataSourceRef, ...rest -}) => ( -
- -

- -

-
- - - - - { + const { dataSourceEnabled } = useOpenSearchDashboards().services; + + return ( +
+ +

+ +

+
+ + + + + {dataSourceEnabled + ? renderDataSourceAndIndexPatternInput( + isInputInvalid, + errors, + characterList, + query, + onQueryChanged, + dataSourceRef! + ) + : renderIndexPatternInput( + isInputInvalid, + errors, + characterList, + query, + onQueryChanged + )} + {showSystemIndices ? ( + + + } + id="checkboxShowSystemIndices" + checked={isIncludingSystemIndices} + onChange={onChangeIncludingSystemIndices} + data-test-subj="showSystemAndHiddenIndices" + /> + + ) : null} + + + + + goToNextStep(query)} + isDisabled={isNextStepDisabled} + data-test-subj="createIndexPatternGoToStep2Button" + > - } - isInvalid={isInputInvalid} - error={errors} - helpText={ - <> - * }} - />{' '} - {characterList} }} - /> - - } - > - + + + +
+ ); +}; - {showSystemIndices ? ( - - - } - id="checkboxShowSystemIndices" - checked={isIncludingSystemIndices} - onChange={onChangeIncludingSystemIndices} - data-test-subj="showSystemAndHiddenIndices" - /> - - ) : null} -
-
- - - goToNextStep(query)} - isDisabled={isNextStepDisabled} - data-test-subj="createIndexPatternGoToStep2Button" - > +const renderIndexPatternInput = ( + isInputInvalid: boolean, + errors: any, + characterList: string, + query: string, + onQueryChanged: (e: React.ChangeEvent) => void +) => { + return ( + + } + isInvalid={isInputInvalid} + error={errors} + helpText={ + <> + * }} + />{' '} + {characterList} }} + /> + + } + > + + + ); +}; + +const renderDataSourceAndIndexPatternInput = ( + isInputInvalid: boolean, + errors: any, + characterList: string, + query: string, + onQueryChanged: (e: React.ChangeEvent) => void, + dataSourceRef: DataSourceRef +) => { + return ( + + + - + } + isInvalid={isInputInvalid} + error={errors} + > + +
{`.`}
+ + {renderIndexPatternInput(isInputInvalid, errors, characterList, query, onQueryChanged)} +
-
-); + ); +}; diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx index cf6d9f1398f4..e42fc3218dd2 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx @@ -352,7 +352,7 @@ export class StepIndexPattern extends Component ); } 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 9705bbc4b3ee..917abd60f7f3 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 @@ -84,3 +84,12 @@ export interface StepInfo { totalStepNumber: number; currentStepNumber: number; } + +export interface DataSourceTableItem { + id: string; + type: string; + title: string; + sort: string; + checked?: 'on' | 'off'; + label: string; +} diff --git a/src/plugins/index_pattern_management/public/components/utils.ts b/src/plugins/index_pattern_management/public/components/utils.ts index 4d9ba4b396dd..3929563fdc1c 100644 --- a/src/plugins/index_pattern_management/public/components/utils.ts +++ b/src/plugins/index_pattern_management/public/components/utils.ts @@ -30,6 +30,7 @@ import { IIndexPattern } from 'src/plugins/data/public'; import { SavedObjectsClientContract } from 'src/core/public'; +import { DataSourceAttributes } from 'src/plugins/data_source/common/data_sources'; import { IndexPatternManagementStart } from '../plugin'; export async function getIndexPatterns( @@ -79,3 +80,31 @@ export async function getIndexPatterns( ) || [] ); } + +export async function getDataSources(savedObjectsClient: SavedObjectsClientContract) { + return ( + savedObjectsClient + .find({ + type: 'data-source', + fields: ['title', 'type'], + perPage: 10000, + }) + .then((response) => + response.savedObjects + .map((dataSource) => { + const id = dataSource.id; + const type = dataSource.type; + const title = dataSource.get('title'); + + return { + id, + title, + type, + label: title, + sort: `${title}`, + }; + }) + .sort((a, b) => a.sort.localeCompare(b.sort)) + ) || [] + ); +} diff --git a/src/plugins/index_pattern_management/public/types.ts b/src/plugins/index_pattern_management/public/types.ts index 4c23651ec844..bc96fc15c8ef 100644 --- a/src/plugins/index_pattern_management/public/types.ts +++ b/src/plugins/index_pattern_management/public/types.ts @@ -70,4 +70,4 @@ export enum MlCardState { ENABLED, } -export type DataSourceRef = Pick; +export type DataSourceRef = { title: string } & Pick;