Skip to content

Commit

Permalink
[OneDiscover][DocViewer] Remember the last active tab (#189806)
Browse files Browse the repository at this point in the history
- Closes #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
  • Loading branch information
jughosta authored Aug 4, 2024
1 parent a2b3651 commit 36d0ae7
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 7 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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;
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
}
Expand All @@ -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
Expand All @@ -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.
Expand All @@ -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>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down

0 comments on commit 36d0ae7

Please sign in to comment.