Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Spacetime] [Discover] Add demo of possible in-product help to Discover #123888

1 change: 1 addition & 0 deletions src/plugins/discover/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ export const MAX_DOC_FIELDS_DISPLAYED = 'discover:maxDocFieldsDisplayed';
export const SHOW_FIELD_STATISTICS = 'discover:showFieldStatistics';
export const SHOW_MULTIFIELDS = 'discover:showMultiFields';
export const TRUNCATE_MAX_HEIGHT = 'truncate:maxHeight';
export const ROW_HEIGHT_OPTION = 'discover:rowHeightOption';
export const SEARCH_EMBEDDABLE_TYPE = 'search';
26 changes: 26 additions & 0 deletions src/plugins/discover/public/__mocks__/local_storage_mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

export class LocalStorageMock {
private store: Record<string, unknown>;
constructor(defaultStore: Record<string, unknown>) {
this.store = defaultStore;
}
clear() {
this.store = {};
}
get(key: string) {
return this.store[key] || null;
}
set(key: string, value: unknown) {
this.store[key] = String(value);
}
remove(key: string) {
delete this.store[key];
}
}
5 changes: 2 additions & 3 deletions src/plugins/discover/public/__mocks__/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
import { UI_SETTINGS } from '../../../data/common';
import { TopNavMenu } from '../../../navigation/public';
import { FORMATS_UI_SETTINGS } from 'src/plugins/field_formats/common';
import { LocalStorageMock } from './local_storage_mock';
const dataPlugin = dataPluginMock.createStartContract();

export const discoverServiceMock = {
Expand Down Expand Up @@ -94,8 +95,6 @@ export const discoverServiceMock = {
useChartsTheme: jest.fn(() => EUI_CHARTS_THEME_LIGHT.theme),
useChartsBaseTheme: jest.fn(() => EUI_CHARTS_THEME_LIGHT.theme),
},
storage: {
get: jest.fn(),
},
storage: new LocalStorageMock({}) as unknown as Storage,
addBasePath: jest.fn(),
} as unknown as DiscoverServices;
3 changes: 3 additions & 0 deletions src/plugins/discover/public/__mocks__/ui_settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
SAMPLE_SIZE_SETTING,
SHOW_MULTIFIELDS,
SEARCH_FIELDS_FROM_SOURCE,
ROW_HEIGHT_OPTION,
} from '../../common';

export const uiSettingsMock = {
Expand All @@ -30,6 +31,8 @@ export const uiSettingsMock = {
return false;
} else if (key === SHOW_MULTIFIELDS) {
return false;
} else if (key === ROW_HEIGHT_OPTION) {
return 3;
}
},
} as unknown as IUiSettingsClient;
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import React from 'react';
import { mountWithIntl } from '@kbn/test/jest';
import { findTestSubject } from '@elastic/eui/lib/test';
import { ActionBar } from './components/action_bar/action_bar';
import { AppState, GetStateReturn } from './services/context_state';
import { GetStateReturn } from './services/context_state';
import { SortDirection } from 'src/plugins/data/common';
import { ContextAppContent, ContextAppContentProps } from './context_app_content';
import { LoadingStatus } from './services/context_query_state';
Expand Down Expand Up @@ -52,7 +52,6 @@ describe('ContextAppContent test', () => {
const props = {
columns: ['order_date', '_source'],
indexPattern: indexPatternMock,
appState: {} as unknown as AppState,
stateContainer: {} as unknown as GetStateReturn,
anchorStatus: anchorStatus || LoadingStatus.LOADED,
predecessorsStatus: LoadingStatus.LOADED,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.dscDocumentTourCallout {
.euiCallOutHeader__title {
width: 100%;
}
p {
margin-bottom: 0.5rem;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React, { useState } from 'react';
import './document_tour_callout.scss';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiButton, EuiButtonIcon, EuiCallOut, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { Storage } from '../../../../../../kibana_utils/public';
import { useDiscoverServices } from '../../../../utils/use_discover_services';

const CALLOUT_STATE_KEY = 'discover:docTourCalloutClosed';

const getStoredCalloutState = (storage: Storage): boolean => {
const calloutClosed = storage.get(CALLOUT_STATE_KEY);
return Boolean(calloutClosed);
};
const updateStoredCalloutState = (newState: boolean, storage: Storage) => {
storage.set(CALLOUT_STATE_KEY, newState);
};

export const DocumentTourCallout = ({ onStartTour }: { onStartTour: () => void }) => {
const { storage, capabilities } = useDiscoverServices();
const [calloutClosed, setCalloutClosed] = useState(getStoredCalloutState(storage));

const onCloseCallout = () => {
updateStoredCalloutState(true, storage);
setCalloutClosed(true);
};

if (calloutClosed && capabilities.advancedSettings.save) {
return null;
}

return (
<EuiCallOut
className="dscDocumentTourCallout"
title={<CalloutTitle onCloseCallout={onCloseCallout} />}
iconType="tableDensityNormal"
>
<p>
<FormattedMessage
id="discover.docTourCallout.bodyMessage"
defaultMessage="Quickly sort, select, and compare data, resize columns, and view documents in fullscreen with the Document Explorer."
/>
</p>
<p>
<EuiButton
size="s"
onClick={() => {
onStartTour();
onCloseCallout();
}}
>
<FormattedMessage
id="discover.docTourCallout.tryDocumentTour"
defaultMessage="Take a tour"
/>
</EuiButton>
</p>
</EuiCallOut>
);
};

function CalloutTitle({ onCloseCallout }: { onCloseCallout: () => void }) {
return (
<EuiFlexGroup justifyContent="spaceBetween" gutterSize="none" responsive={false}>
<EuiFlexItem grow={false}>
<FormattedMessage
id="discover.docTourCallout.headerMessage"
defaultMessage="A better way to explore"
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonIcon
aria-label={i18n.translate('discover.docTourCallout.closeButtonAriaLabel', {
defaultMessage: 'Close',
})}
onClick={onCloseCallout}
type="button"
iconType="cross"
/>
</EuiFlexItem>
</EuiFlexGroup>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

export { DocumentTourCallout } from './document_tour_callout';
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import React from 'react';
import { BehaviorSubject } from 'rxjs';
import { mountWithIntl } from '@kbn/test/jest';
import { setHeaderActionMenuMounter } from '../../../../kibana_services';
import { setHeaderActionMenuMounter, setServices } from '../../../../kibana_services';
import { esHits } from '../../../../__mocks__/es_hits';
import { savedSearchMock } from '../../../../__mocks__/saved_search';
import { GetStateReturn } from '../../services/discover_state';
Expand All @@ -28,6 +28,7 @@ function mountComponent(fetchStatus: FetchStatus, hits: ElasticSearchHit[]) {
services.data.query.timefilter.timefilter.getTime = () => {
return { from: '2020-05-14T11:05:13.590', to: '2020-05-14T11:20:13.590' };
};
setServices(services);

const documents$ = new BehaviorSubject({
fetchStatus,
Expand All @@ -43,7 +44,7 @@ function mountComponent(fetchStatus: FetchStatus, hits: ElasticSearchHit[]) {
searchSource: documents$,
setExpandedDoc: jest.fn(),
state: { columns: [] },
stateContainer: {} as GetStateReturn,
stateContainer: { setAppState: () => {} } as unknown as GetStateReturn,
navigateTo: jest.fn(),
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React, { useMemo, useCallback, memo } from 'react';
import React, { useMemo, useCallback, useEffect, memo } from 'react';
import {
EuiFlexItem,
EuiSpacer,
EuiText,
EuiLoadingSpinner,
EuiScreenReaderOnly,
EuiTourState,
useEuiTour,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { useDiscoverServices } from '../../../../utils/use_discover_services';
Expand All @@ -33,6 +35,13 @@ import { useDataState } from '../../utils/use_data_state';
import { DocTableInfinite } from '../../../../components/doc_table/doc_table_infinite';
import { SortPairArr } from '../../../../components/doc_table/lib/get_sort';
import { ElasticSearchHit } from '../../../../types';
import { DocumentTourCallout } from '../document_tour_callout';
import {
DiscoverTourDetails,
tourConfig,
discoverTourSteps,
STORAGE_KEY,
} from '../../../../components/discover_grid/discover_grid_tour';

const DocTableInfiniteMemoized = React.memo(DocTableInfinite);
const DataGridMemoized = React.memo(DiscoverGrid);
Expand Down Expand Up @@ -78,6 +87,25 @@ function DiscoverDocumentsComponent({
useNewFieldsApi,
});

const initialState = localStorage.getItem(STORAGE_KEY);
let tourState: EuiTourState;
if (initialState) {
tourState = JSON.parse(initialState);
tourState = { ...tourState, isTourActive: false };
} else {
tourState = tourConfig;
}
const [steps, actions, reducerState] = useEuiTour(discoverTourSteps, tourState);
const discoverTour = { steps, actions, state: reducerState } as DiscoverTourDetails;

useEffect(() => {
localStorage.setItem(STORAGE_KEY, JSON.stringify(discoverTour.state));
}, [discoverTour.state]);

const onStartTour = () => {
discoverTour.actions.resetTour();
};

const onResize = useCallback(
(colSettings: { columnId: string; width: number }) => {
const grid = { ...state.grid } || {};
Expand All @@ -98,6 +126,13 @@ function DiscoverDocumentsComponent({
[stateContainer]
);

const onUpdateRowHeight = useCallback(
(newRowHeight?: number) => {
stateContainer.setAppState({ rowHeight: newRowHeight });
},
[stateContainer]
);

const showTimeCol = useMemo(
() => !uiSettings.get(DOC_HIDE_TIME_COLUMN_SETTING, false) && !!indexPattern.timeFieldName,
[uiSettings, indexPattern.timeFieldName]
Expand Down Expand Up @@ -144,30 +179,36 @@ function DiscoverDocumentsComponent({
/>
)}
{!isLegacy && (
<div className="dscDiscoverGrid">
<DataGridMemoized
ariaLabelledBy="documentsAriaLabel"
columns={columns}
expandedDoc={expandedDoc}
indexPattern={indexPattern}
isLoading={isLoading}
rows={rows}
sort={(state.sort as SortPairArr[]) || []}
sampleSize={sampleSize}
searchDescription={savedSearch.description}
searchTitle={savedSearch.title}
setExpandedDoc={setExpandedDoc}
showTimeCol={showTimeCol}
settings={state.grid}
onAddColumn={onAddColumn}
onFilter={onAddFilter as DocViewFilterFn}
onRemoveColumn={onRemoveColumn}
onSetColumns={onSetColumns}
onSort={onSort}
onResize={onResize}
useNewFieldsApi={useNewFieldsApi}
/>
</div>
<>
<DocumentTourCallout onStartTour={onStartTour} />
<div className="dscDiscoverGrid">
<DataGridMemoized
ariaLabelledBy="documentsAriaLabel"
columns={columns}
expandedDoc={expandedDoc}
indexPattern={indexPattern}
isLoading={isLoading}
rows={rows}
sort={(state.sort as SortPairArr[]) || []}
sampleSize={sampleSize}
searchDescription={savedSearch.description}
searchTitle={savedSearch.title}
setExpandedDoc={setExpandedDoc}
showTimeCol={showTimeCol}
settings={state.grid}
onAddColumn={onAddColumn}
onFilter={onAddFilter as DocViewFilterFn}
onRemoveColumn={onRemoveColumn}
onSetColumns={onSetColumns}
onSort={onSort}
onResize={onResize}
useNewFieldsApi={useNewFieldsApi}
rowHeightState={state.rowHeight}
onUpdateRowHeight={onUpdateRowHeight}
tour={discoverTour}
/>
</div>
</>
)}
</EuiFlexItem>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import React from 'react';
import { Subject, BehaviorSubject } from 'rxjs';
import { mountWithIntl } from '@kbn/test/jest';
import { setHeaderActionMenuMounter } from '../../../../kibana_services';
import { setHeaderActionMenuMounter, setServices } from '../../../../kibana_services';
import { DiscoverLayout, SIDEBAR_CLOSED_KEY } from './discover_layout';
import { esHits } from '../../../../__mocks__/es_hits';
import { indexPatternMock } from '../../../../__mocks__/index_pattern';
Expand All @@ -35,12 +35,21 @@ import { ElasticSearchHit } from '../../../../types';
import { KibanaContextProvider } from '../../../../../../kibana_react/public';
import { FieldFormatsStart } from '../../../../../../field_formats/public';
import { IUiSettingsClient } from 'kibana/public';
import { DiscoverServices } from 'src/plugins/discover/public/build_services';
import { LocalStorageMock } from 'src/plugins/discover/public/__mocks__/local_storage_mock';

setHeaderActionMenuMounter(jest.fn());

function mountComponent(indexPattern: DataView, prevSidebarClosed?: boolean) {
const searchSourceMock = createSearchSourceMock({});
const services = discoverServiceMock;
const services = {
...discoverServiceMock,
fieldFormats: {
getDefaultInstance: jest.fn(() => ({ convert: (value: unknown) => value })),
getFormatterForField: jest.fn(() => ({ convert: (value: unknown) => value })),
},
storage: new LocalStorageMock({ [SIDEBAR_CLOSED_KEY]: wasSidebarClosed }) as unknown as Storage,
} as unknown as DiscoverServices;
services.data.query.timefilter.timefilter.getAbsoluteTime = () => {
return { from: '2020-05-14T11:05:13.590', to: '2020-05-14T11:20:13.590' };
};
Expand Down Expand Up @@ -146,7 +155,7 @@ function mountComponent(indexPattern: DataView, prevSidebarClosed?: boolean) {
savedSearchRefetch$: new Subject(),
searchSource: searchSourceMock,
state: { columns: [] },
stateContainer: {} as GetStateReturn,
stateContainer: { setAppState: () => {} } as unknown as GetStateReturn,
setExpandedDoc: jest.fn(),
};

Expand Down
Loading