Skip to content

Commit

Permalink
Improve Empty State Handling: Add No Index Patterns Panel with Data S…
Browse files Browse the repository at this point in the history
…election in Discover View (#8613)

* Improve Empty State Handling: Add No Index Patterns Panel with Data Selection in Discover View

This PR primarily addresses the scenario when no index patterns (general) is available in the Discover view.
Instead of redirecting users to the index management page, it introduces a new "No Index Patterns" panel.
This panel provides users with the option to open a data selector and add index patterns
directly from the Discover view, improving the user experience for new or empty deployments.

To achieve, we move the selectedDataset state from ConnectedDatasetSelector to the app container's
state management. This allows the AdvancedSelector, opened from the AppContainer, to update
the dataset state effectively. Key changes include:

* Implementing NoIndexPatternsPanel and AdvancedSelector components.
* Refactoring dataset state management in AppContainer and Sidebar.
* Modifying DiscoverCanvas to conditionally render NoIndexPatternsPanel.
* Updating ConnectedDatasetSelector to use shared state and dataset change handling.

Signed-off-by: Anan Zhuang <ananzh@amazon.com>
Signed-off-by: Miki <miki@amazon.com>

* Update design of no data selected

Signed-off-by: Miki <miki@amazon.com>

* use i18n

Signed-off-by: Anan Zhuang <ananzh@amazon.com>
Signed-off-by: Miki <miki@amazon.com>

* fix comments

Signed-off-by: Anan Zhuang <ananzh@amazon.com>

* Update design of no data selected

Signed-off-by: Miki <miki@amazon.com>

* fix lint error

Signed-off-by: Anan Zhuang <ananzh@amazon.com>

---------

Signed-off-by: Anan Zhuang <ananzh@amazon.com>
Signed-off-by: Miki <miki@amazon.com>
Co-authored-by: Miki <miki@amazon.com>
  • Loading branch information
ananzh and AMoo-Miki authored Oct 18, 2024
1 parent e5b7067 commit 6659139
Show file tree
Hide file tree
Showing 22 changed files with 479 additions and 94 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ import { includes } from 'lodash';
import { IndexPatternsContract } from './index_patterns';
import { UiSettingsCommon } from '../types';

export type EnsureDefaultIndexPattern = () => Promise<unknown> | undefined;
export type EnsureDefaultIndexPattern = (
shouldRedirect?: boolean
) => Promise<unknown | void> | undefined;

export const createEnsureDefaultIndexPattern = (
uiSettings: UiSettingsCommon,
Expand All @@ -42,7 +44,10 @@ export const createEnsureDefaultIndexPattern = (
* Checks whether a default index pattern is set and exists and defines
* one otherwise.
*/
return async function ensureDefaultIndexPattern(this: IndexPatternsContract) {
return async function ensureDefaultIndexPattern(
this: IndexPatternsContract,
shouldRedirect: boolean = true
) {
const patterns = await this.getIds();
let defaultId = await uiSettings.get('defaultIndex');
let defined = !!defaultId;
Expand All @@ -62,7 +67,8 @@ export const createEnsureDefaultIndexPattern = (
defaultId = patterns[0];
await uiSettings.set('defaultIndex', defaultId);
} else {
return onRedirectNoIndexPattern();
if (shouldRedirect) return onRedirectNoIndexPattern();
else return;
}
};
};
3 changes: 2 additions & 1 deletion src/plugins/data/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,14 @@ import {
} from '../common';

import { FilterLabel } from './ui';

export {
createEditor,
DefaultInput,
DQLBody,
SingleLineInput,
DatasetSelector,
AdvancedSelector,
NoIndexPatternsPanel,
DatasetSelectorAppearance,
} from './ui';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,11 @@ export class DatasetService {
return Number(this.sessionStorage.get('lastCacheTime')) || undefined;
}

public removeFromRecentDatasets(datasetId: string): void {
this.recentDatasets.del(datasetId);
this.serializeRecentDatasets();
}

private setLastCacheTime(time: number): void {
this.sessionStorage.set('lastCacheTime', time);
}
Expand Down
11 changes: 11 additions & 0 deletions src/plugins/data/public/ui/_common.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.dataUI-centerPanel {
height: 100%;
width: 100%;

// Push the centralized child up, just like ouiOverlayMask
padding-bottom: 10vh;

& > * {
@include euiLegibilityMaxWidth(100%);
}
}
1 change: 1 addition & 0 deletions src/plugins/data/public/ui/_index.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@import "./common";
@import "./filter_bar/index";
@import "./typeahead/index";
@import "./saved_query_management/index";
Expand Down
38 changes: 31 additions & 7 deletions src/plugins/data/public/ui/dataset_selector/advanced_selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,27 @@ import {
} from '../../../common';
import { DatasetExplorer } from './dataset_explorer';
import { Configurator } from './configurator';
import { getQueryService } from '../../services';
import { IDataPluginServices } from '../../types';

export const AdvancedSelector = ({
services,
onSelect,
onCancel,
selectedDataset,
setSelectedDataset,
setIndexPattern,
direct = false,
}: {
services: IDataPluginServices;
onSelect: (dataset: Dataset) => void;
onCancel: () => void;
selectedDataset?: Dataset;
setSelectedDataset: (data: Dataset | undefined) => void;
setIndexPattern: (id: string | undefined) => void;
direct?: boolean;
}) => {
const queryString = getQueryService().queryString;
const queryService = services.data.query;
const queryString = queryService.queryString;

const [path, setPath] = useState<DataStructure[]>([
{
Expand All @@ -48,22 +56,38 @@ export const AdvancedSelector = ({
}),
},
]);
const [selectedDataset, setSelectedDataset] = useState<BaseDataset | undefined>();

return selectedDataset ? (
const [currentSelectedDataset, setCurrentSelectedDataset] = useState<BaseDataset | undefined>(
selectedDataset
);

return currentSelectedDataset ? (
<Configurator
baseDataset={selectedDataset}
baseDataset={currentSelectedDataset}
onConfirm={onSelect}
onCancel={onCancel}
onPrevious={() => setSelectedDataset(undefined)}
onPrevious={() => {
setSelectedDataset(undefined);
setCurrentSelectedDataset(undefined);
}}
queryService={queryService}
/>
) : (
<DatasetExplorer
services={services}
queryString={queryString}
path={path}
setPath={setPath}
onNext={(dataset) => setSelectedDataset(dataset)}
onNext={(dataset) => {
setSelectedDataset(dataset);
setIndexPattern(dataset.id);
setCurrentSelectedDataset(dataset);
if (direct) {
const query = queryString.getInitialQueryByDataset(dataset);
queryString.setQuery(query);
queryString.getDatasetService().addRecentDataset(dataset);
}
}}
onCancel={onCancel}
/>
);
Expand Down
5 changes: 3 additions & 2 deletions src/plugins/data/public/ui/dataset_selector/configurator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,21 @@ import { i18n } from '@osd/i18n';
import { FormattedMessage } from '@osd/i18n/react';
import React, { useEffect, useMemo, useState } from 'react';
import { BaseDataset, DEFAULT_DATA, Dataset, DatasetField } from '../../../common';
import { getIndexPatterns, getQueryService } from '../../services';
import { getIndexPatterns } from '../../services';

export const Configurator = ({
baseDataset,
onConfirm,
onCancel,
onPrevious,
queryService,
}: {
baseDataset: BaseDataset;
onConfirm: (dataset: Dataset) => void;
onCancel: () => void;
onPrevious: () => void;
queryService: any;
}) => {
const queryService = getQueryService();
const queryString = queryService.queryString;
const languageService = queryService.queryString.getLanguageService();
const indexPatternsService = getIndexPatterns();
Expand Down
18 changes: 13 additions & 5 deletions src/plugins/data/public/ui/dataset_selector/dataset_selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ type EuiSmallButtonEmptyProps = React.ComponentProps<typeof EuiSmallButtonEmpty>

interface DatasetSelectorProps {
selectedDataset?: Dataset;
setSelectedDataset: (dataset: Dataset) => void;
setSelectedDataset: (data: Dataset | undefined) => void;
setIndexPattern: (id: string | undefined) => void;
handleDatasetChange: (dataset: Dataset) => void;
services: IDataPluginServices;
}

Expand Down Expand Up @@ -71,6 +73,8 @@ const RootComponent: React.FC<
export const DatasetSelector = ({
selectedDataset,
setSelectedDataset,
setIndexPattern,
handleDatasetChange,
services,
appearance,
buttonProps,
Expand Down Expand Up @@ -102,7 +106,7 @@ export const DatasetSelector = ({

// If no dataset is selected, select the first one
if (!selectedDataset && fetchedDatasets.length > 0) {
setSelectedDataset(fetchedDatasets[0]);
handleDatasetChange(fetchedDatasets[0]);
}
};

Expand Down Expand Up @@ -179,11 +183,11 @@ export const DatasetSelector = ({
indexPatterns.find((dataset) => dataset.id === selectedOption.key);
if (foundDataset) {
closePopover();
setSelectedDataset(foundDataset);
handleDatasetChange(foundDataset);
}
}
},
[recentDatasets, indexPatterns, setSelectedDataset, closePopover]
[recentDatasets, indexPatterns, handleDatasetChange, closePopover]
);

const datasetTitle = useMemo(() => {
Expand Down Expand Up @@ -266,10 +270,14 @@ export const DatasetSelector = ({
onSelect={(dataset?: Dataset) => {
overlay?.close();
if (dataset) {
setSelectedDataset(dataset);
handleDatasetChange(dataset);
}
}}
onCancel={() => overlay?.close()}
selectedDataset={undefined}
setSelectedDataset={setSelectedDataset}
setIndexPattern={setIndexPattern}
direct={true}
/>
),
{
Expand Down
51 changes: 44 additions & 7 deletions src/plugins/data/public/ui/dataset_selector/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,40 +49,77 @@ describe('ConnectedDatasetSelector', () => {
});

it('should render DatasetSelector with correct props', () => {
const wrapper = mount(<ConnectedDatasetSelector onSubmit={mockOnSubmit} />);
const wrapper = mount(
<ConnectedDatasetSelector
onSubmit={mockOnSubmit}
selectedDataset={undefined}
setSelectedDataset={jest.fn()}
setIndexPattern={jest.fn()}
services={mockServices}
/>
);
expect(wrapper.find(DatasetSelector).props()).toEqual({
selectedDataset: undefined,
setSelectedDataset: expect.any(Function),
setIndexPattern: expect.any(Function),
handleDatasetChange: expect.any(Function),
services: mockServices,
});
});

it('should initialize selectedDataset correctly', () => {
const mockDataset: Dataset = { id: 'initial', title: 'Initial Dataset', type: 'test' };
mockQueryString.getQuery.mockReturnValueOnce({ dataset: mockDataset });

const wrapper = mount(<ConnectedDatasetSelector onSubmit={mockOnSubmit} />);
const wrapper = mount(
<ConnectedDatasetSelector
onSubmit={mockOnSubmit}
selectedDataset={mockDataset}
setSelectedDataset={jest.fn()}
setIndexPattern={jest.fn()}
services={mockServices}
/>
);
expect(wrapper.find(DatasetSelector).prop('selectedDataset')).toEqual(mockDataset);
});

it('should call handleDatasetChange only once when dataset changes', () => {
const wrapper = mount(<ConnectedDatasetSelector onSubmit={mockOnSubmit} />);
const setSelectedDataset = wrapper.find(DatasetSelector).prop('setSelectedDataset') as (
const setSelectedDataset = jest.fn();
const setIndexPattern = jest.fn();
const wrapper = mount(
<ConnectedDatasetSelector
onSubmit={mockOnSubmit}
selectedDataset={undefined}
setSelectedDataset={setSelectedDataset}
setIndexPattern={setIndexPattern}
services={mockServices}
/>
);
const handleDatasetChange = wrapper.find(DatasetSelector).prop('handleDatasetChange') as (
dataset?: Dataset
) => void;

const newDataset: Dataset = { id: 'test', title: 'Test Dataset', type: 'test' };
act(() => {
setSelectedDataset(newDataset);
handleDatasetChange(newDataset);
});

expect(mockQueryString.getInitialQueryByDataset).toHaveBeenCalledTimes(1);
expect(mockQueryString.setQuery).toHaveBeenCalledTimes(1);
expect(mockOnSubmit).toHaveBeenCalledTimes(1);
expect(setSelectedDataset).toHaveBeenCalledWith(newDataset);
expect(setIndexPattern).toHaveBeenCalledWith(newDataset.id);
});

it('should subscribe to queryString.getUpdates$ and unsubscribe on unmount', () => {
const wrapper = mount(<ConnectedDatasetSelector onSubmit={mockOnSubmit} />);
const wrapper = mount(
<ConnectedDatasetSelector
onSubmit={mockOnSubmit}
selectedDataset={undefined}
setSelectedDataset={jest.fn()}
setIndexPattern={jest.fn()}
services={mockServices}
/>
);

expect(mockQueryString.getUpdates$).toHaveBeenCalledTimes(1);
expect(mockSubscribe).toHaveBeenCalledTimes(1);
Expand Down
Loading

0 comments on commit 6659139

Please sign in to comment.