Skip to content
Closed
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