[OneDiscover][DocViewer] Remember the last active tab (#189806)

- Closes https://github.com/elastic/kibana/issues/188717

## Summary

With this PR the last active DocViewer tab is going to be stored in
local storage under `unifiedDocViewer:initialTab` and restored when
opening the flyout next time.

### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
This commit is contained in:
Julia Rechkunova 2024-08-04 09:28:25 +02:00 committed by GitHub
parent a2b365168e
commit 36d0ae7121
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 96 additions and 7 deletions

View file

@ -7,6 +7,7 @@ exports[`<DocViewer /> Render <DocViewer/> with 3 different tabs 1`] = `
>
<EuiTabbedContent
autoFocus="initial"
onTabClick={[Function]}
size="s"
tabs={
Array [

View file

@ -8,11 +8,28 @@
import React from 'react';
import { mount, shallow } from 'enzyme';
import { DocViewer } from './doc_viewer';
import { render, screen } from '@testing-library/react';
import { findTestSubject } from '@elastic/eui/lib/test';
import type { DocViewRenderProps } from '../../types';
import { buildDataTableRecord } from '@kbn/discover-utils';
import { DocViewer, INITIAL_TAB } from './doc_viewer';
import type { DocViewRenderProps } from '../../types';
import { DocViewsRegistry } from '../..';
import { dataViewMock, esHitsMockWithSort } from '@kbn/discover-utils/src/__mocks__';
const records = esHitsMockWithSort.map((hit) => buildDataTableRecord(hit, dataViewMock));
const mockSetLocalStorage = jest.fn();
const mockLocalStorageKey = INITIAL_TAB;
let mockTestInitialLocalStorageValue: string | undefined;
jest.mock('react-use/lib/useLocalStorage', () => {
return jest.fn((key: string, initialValue: number) => {
if (key !== mockLocalStorageKey) {
throw new Error(`Unexpected key: ${key}`);
}
return [mockTestInitialLocalStorageValue ?? initialValue, mockSetLocalStorage];
});
});
describe('<DocViewer />', () => {
test('Render <DocViewer/> with 3 different tabs', () => {
@ -59,4 +76,57 @@ describe('<DocViewer />', () => {
const errorMsgComponent = findTestSubject(wrapper, 'docViewerError');
expect(errorMsgComponent.text()).toMatch(new RegExp(`${errorMsg}`));
});
test('should save active tab to local storage', () => {
const registry = new DocViewsRegistry();
registry.add({ id: 'test1', order: 10, title: 'Render function', render: jest.fn() });
registry.add({ id: 'test2', order: 20, title: 'Render function', render: jest.fn() });
render(<DocViewer docViews={registry.getAll()} hit={records[0]} dataView={dataViewMock} />);
expect(screen.getByTestId('docViewerTab-test1').getAttribute('aria-selected')).toBe('true');
expect(screen.getByTestId('docViewerTab-test2').getAttribute('aria-selected')).toBe('false');
screen.getByTestId('docViewerTab-test2').click();
expect(screen.getByTestId('docViewerTab-test1').getAttribute('aria-selected')).toBe('false');
expect(screen.getByTestId('docViewerTab-test2').getAttribute('aria-selected')).toBe('true');
expect(mockSetLocalStorage).toHaveBeenCalledWith('kbn_doc_viewer_tab_test2');
screen.getByTestId('docViewerTab-test1').click();
expect(screen.getByTestId('docViewerTab-test1').getAttribute('aria-selected')).toBe('true');
expect(screen.getByTestId('docViewerTab-test2').getAttribute('aria-selected')).toBe('false');
expect(mockSetLocalStorage).toHaveBeenCalledWith('kbn_doc_viewer_tab_test1');
});
test('should restore active tab from local storage', () => {
const registry = new DocViewsRegistry();
registry.add({ id: 'test1', order: 10, title: 'Render function', render: jest.fn() });
registry.add({ id: 'test2', order: 20, title: 'Render function', render: jest.fn() });
mockTestInitialLocalStorageValue = 'kbn_doc_viewer_tab_test2';
render(<DocViewer docViews={registry.getAll()} hit={records[0]} dataView={dataViewMock} />);
expect(screen.getByTestId('docViewerTab-test1').getAttribute('aria-selected')).toBe('false');
expect(screen.getByTestId('docViewerTab-test2').getAttribute('aria-selected')).toBe('true');
mockTestInitialLocalStorageValue = undefined;
});
test('should not restore a tab from local storage if unavailable', () => {
const registry = new DocViewsRegistry();
registry.add({ id: 'test1', order: 10, title: 'Render function', render: jest.fn() });
registry.add({ id: 'test2', order: 20, title: 'Render function', render: jest.fn() });
mockTestInitialLocalStorageValue = 'kbn_doc_viewer_tab_test3';
render(<DocViewer docViews={registry.getAll()} hit={records[0]} dataView={dataViewMock} />);
expect(screen.getByTestId('docViewerTab-test1').getAttribute('aria-selected')).toBe('true');
expect(screen.getByTestId('docViewerTab-test2').getAttribute('aria-selected')).toBe('false');
mockTestInitialLocalStorageValue = undefined;
});
});

View file

@ -6,11 +6,14 @@
* Side Public License, v 1.
*/
import React from 'react';
import { EuiTabbedContent } from '@elastic/eui';
import React, { useCallback } from 'react';
import { EuiTabbedContent, EuiTabbedContentTab } from '@elastic/eui';
import useLocalStorage from 'react-use/lib/useLocalStorage';
import { DocViewerTab } from './doc_viewer_tab';
import type { DocView, DocViewRenderProps } from '../../types';
export const INITIAL_TAB = 'unifiedDocViewer:initialTab';
export interface DocViewerProps extends DocViewRenderProps {
docViews: DocView[];
}
@ -26,7 +29,7 @@ export function DocViewer({ docViews, ...renderProps }: DocViewerProps) {
.filter(({ enabled }) => enabled) // Filter out disabled doc views
.map(({ id, title, render, component }: DocView) => {
return {
id: `kbn_doc_viewer_tab_${id}`,
id: `kbn_doc_viewer_tab_${id}`, // `id` value is used to persist the selected tab in localStorage
name: title,
content: (
<DocViewerTab
@ -41,6 +44,16 @@ export function DocViewer({ docViews, ...renderProps }: DocViewerProps) {
};
});
const [initialTabId, setInitialTabId] = useLocalStorage<string>(INITIAL_TAB);
const initialSelectedTab = initialTabId ? tabs.find(({ id }) => id === initialTabId) : undefined;
const onTabClick = useCallback(
(tab: EuiTabbedContentTab) => {
setInitialTabId(tab.id);
},
[setInitialTabId]
);
if (!tabs.length) {
// There's a minimum of 2 tabs active in Discover.
// This condition takes care of unit tests with 0 tabs.
@ -49,7 +62,12 @@ export function DocViewer({ docViews, ...renderProps }: DocViewerProps) {
return (
<div className="kbnDocViewer" data-test-subj="kbnDocViewer">
<EuiTabbedContent size="s" tabs={tabs} />
<EuiTabbedContent
size="s"
tabs={tabs}
initialSelectedTab={initialSelectedTab}
onTabClick={onTabClick}
/>
</div>
);
}

View file

@ -75,7 +75,7 @@ const DEFAULT_PAGE_SIZE = 25;
const PINNED_FIELDS_KEY = 'discover:pinnedFields';
const PAGE_SIZE = 'discover:pageSize';
const SEARCH_TEXT = 'discover:searchText';
const HIDE_NULL_VALUES = 'discover:hideNullValues';
const HIDE_NULL_VALUES = 'unifiedDocViewer:hideNullValues';
const GRID_COLUMN_FIELD_NAME = 'name';
const GRID_COLUMN_FIELD_VALUE = 'value';