[Discover] Add document explorer callout (#123814)

* [Discover] add document explorer callout

* [Discover] fix mobile view

* [Discover] fix lint

* [Discover] apply suggestions

* Update src/plugins/discover/public/application/main/components/document_explorer_callout/document_explorer_callout.tsx

Co-authored-by: Matthias Wilhelm <ankertal@gmail.com>

* [Discover] add unit tests, fix read only advanced settings case

* [Discover] apply suggestions

Co-authored-by: Matthias Wilhelm <ankertal@gmail.com>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Dmitry Tomashevich 2022-02-01 11:50:35 +03:00 committed by GitHub
parent 9f239dc0a7
commit 41e756c989
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 206 additions and 16 deletions

View file

@ -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];
}
}

View file

@ -0,0 +1,5 @@
.dscDocumentExplorerCallout {
.euiCallOutHeader__title {
width: 100%;
}
}

View file

@ -0,0 +1,90 @@
/*
* 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, { useCallback, useState } from 'react';
import './document_explorer_callout.scss';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiButton, EuiButtonIcon, EuiCallOut, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { useDiscoverServices } from '../../../../utils/use_discover_services';
import { DOC_TABLE_LEGACY } from '../../../../../common';
import { Storage } from '../../../../../../kibana_utils/public';
export const CALLOUT_STATE_KEY = 'discover:docExplorerCalloutClosed';
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 DocumentExplorerCallout = () => {
const { storage, capabilities, addBasePath } = useDiscoverServices();
const [calloutClosed, setCalloutClosed] = useState(getStoredCalloutState(storage));
const onCloseCallout = useCallback(() => {
updateStoredCalloutState(true, storage);
setCalloutClosed(true);
}, [storage]);
if (calloutClosed || !capabilities.advancedSettings.save) {
return null;
}
return (
<EuiCallOut
className="dscDocumentExplorerCallout"
title={<CalloutTitle onCloseCallout={onCloseCallout} />}
iconType="search"
>
<p>
<FormattedMessage
id="discover.docExplorerCallout.bodyMessage"
defaultMessage="Quickly sort, select, and compare data, resize columns, and view documents in fullscreen with the Document Explorer."
/>
</p>
<p>
<EuiButton
iconType="tableDensityNormal"
size="s"
href={addBasePath(`/app/management/kibana/settings?query=${DOC_TABLE_LEGACY}`)}
>
<FormattedMessage
id="discover.docExplorerCallout.tryDocumentExplorer"
defaultMessage="Try Document Explorer"
/>
</EuiButton>
</p>
</EuiCallOut>
);
};
function CalloutTitle({ onCloseCallout }: { onCloseCallout: () => void }) {
return (
<EuiFlexGroup justifyContent="spaceBetween" gutterSize="none" responsive={false}>
<EuiFlexItem grow={false}>
<FormattedMessage
id="discover.docExplorerCallout.headerMessage"
defaultMessage="A better way to explore"
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonIcon
aria-label={i18n.translate('discover.docExplorerCallout.closeButtonAriaLabel', {
defaultMessage: 'Close',
})}
onClick={onCloseCallout}
type="button"
iconType="cross"
/>
</EuiFlexItem>
</EuiFlexGroup>
);
}

View file

@ -0,0 +1,56 @@
/*
* 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 from 'react';
import { mountWithIntl } from '@kbn/test/jest';
import { KibanaContextProvider } from '../../../../../../kibana_react/public';
import { CALLOUT_STATE_KEY, DocumentExplorerCallout } from './document_explorer_callout';
import { LocalStorageMock } from '../../../../__mocks__/local_storage_mock';
import { DiscoverServices } from '../../../../build_services';
const defaultServices = {
addBasePath: () => '',
capabilities: { advancedSettings: { save: true } },
storage: new LocalStorageMock({ [CALLOUT_STATE_KEY]: false }),
} as unknown as DiscoverServices;
const mount = (services: DiscoverServices) => {
return mountWithIntl(
<KibanaContextProvider services={services}>
<DocumentExplorerCallout />
</KibanaContextProvider>
);
};
describe('Document Explorer callout', () => {
it('should render callout', () => {
const result = mount(defaultServices);
expect(result.find('.dscDocumentExplorerCallout').exists()).toBeTruthy();
});
it('should not render callout for user without permissions', () => {
const services = {
...defaultServices,
capabilities: { advancedSettings: { save: false } },
} as unknown as DiscoverServices;
const result = mount(services);
expect(result.find('.dscDocumentExplorerCallout').exists()).toBeFalsy();
});
it('should not render callout of it was closed', () => {
const services = {
...defaultServices,
storage: new LocalStorageMock({ [CALLOUT_STATE_KEY]: true }),
} as unknown as DiscoverServices;
const result = mount(services);
expect(result.find('.dscDocumentExplorerCallout').exists()).toBeFalsy();
});
});

View file

@ -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 { DocumentExplorerCallout } from './document_explorer_callout';

View file

@ -33,6 +33,7 @@ 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 { DocumentExplorerCallout } from '../document_explorer_callout';
const DocTableInfiniteMemoized = React.memo(DocTableInfinite);
const DataGridMemoized = React.memo(DiscoverGrid);
@ -126,22 +127,25 @@ function DiscoverDocumentsComponent({
</h2>
</EuiScreenReaderOnly>
{isLegacy && rows && rows.length && (
<DocTableInfiniteMemoized
columns={columns}
indexPattern={indexPattern}
rows={rows}
sort={state.sort || []}
isLoading={isLoading}
searchDescription={savedSearch.description}
sharedItemTitle={savedSearch.title}
onAddColumn={onAddColumn}
onFilter={onAddFilter as DocViewFilterFn}
onMoveColumn={onMoveColumn}
onRemoveColumn={onRemoveColumn}
onSort={onSort}
useNewFieldsApi={useNewFieldsApi}
dataTestSubj="discoverDocTable"
/>
<>
<DocumentExplorerCallout />
<DocTableInfiniteMemoized
columns={columns}
indexPattern={indexPattern}
rows={rows}
sort={state.sort || []}
isLoading={isLoading}
searchDescription={savedSearch.description}
sharedItemTitle={savedSearch.title}
onAddColumn={onAddColumn}
onFilter={onAddFilter as DocViewFilterFn}
onMoveColumn={onMoveColumn}
onRemoveColumn={onRemoveColumn}
onSort={onSort}
useNewFieldsApi={useNewFieldsApi}
dataTestSubj="discoverDocTable"
/>
</>
)}
{!isLegacy && (
<div className="dscDiscoverGrid">