[App Search] Automated Curations: Split Curations view into "Overview" and "Settings" tabs (#112488)

This commit is contained in:
Byron Hulcher 2021-09-20 13:21:33 -04:00 committed by GitHub
parent d3b44c41cd
commit b34c54e934
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 491 additions and 207 deletions

View file

@ -0,0 +1,143 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { mockKibanaValues, setMockActions, setMockValues } from '../../../../__mocks__/kea_logic';
import '../../../../__mocks__/react_router';
import '../../../__mocks__/engine_logic.mock';
import React from 'react';
import { shallow, ReactWrapper } from 'enzyme';
import { EuiBasicTable } from '@elastic/eui';
import { mountWithIntl } from '../../../../test_helpers';
import { CurationsTable } from './curations_table';
describe('CurationsTable', () => {
const { navigateToUrl } = mockKibanaValues;
const values = {
dataLoading: false,
curations: [
{
id: 'cur-id-1',
last_updated: 'January 1, 1970 at 12:00PM',
queries: ['hiking'],
},
{
id: 'cur-id-2',
last_updated: 'January 2, 1970 at 12:00PM',
queries: ['mountains', 'valleys'],
},
],
meta: {
page: {
current: 1,
size: 10,
total_results: 2,
},
},
};
const actions = {
deleteCuration: jest.fn(),
onPaginate: jest.fn(),
};
beforeEach(() => {
jest.clearAllMocks();
setMockValues(values);
setMockActions(actions);
});
it('passes loading prop based on dataLoading', () => {
setMockValues({ ...values, dataLoading: true });
const wrapper = shallow(<CurationsTable />);
expect(wrapper.find(EuiBasicTable).prop('loading')).toEqual(true);
});
describe('populated table render', () => {
let wrapper: ReactWrapper;
beforeAll(() => {
wrapper = mountWithIntl(<CurationsTable />);
});
it('renders queries and last updated columns', () => {
const tableContent = wrapper.find(EuiBasicTable).text();
expect(tableContent).toContain('Queries');
expect(tableContent).toContain('hiking');
expect(tableContent).toContain('mountains, valleys');
expect(tableContent).toContain('Last updated');
expect(tableContent).toContain('Jan 1, 1970 12:00 PM');
expect(tableContent).toContain('Jan 2, 1970 12:00 PM');
});
it('renders queries with curation links', () => {
expect(
wrapper.find('EuiLinkTo[data-test-subj="CurationsTableQueriesLink"]').first().prop('to')
).toEqual('/engines/some-engine/curations/cur-id-1');
expect(
wrapper.find('EuiLinkTo[data-test-subj="CurationsTableQueriesLink"]').last().prop('to')
).toEqual('/engines/some-engine/curations/cur-id-2');
});
describe('action column', () => {
it('edit action navigates to curation link', () => {
wrapper.find('[data-test-subj="CurationsTableEditButton"]').first().simulate('click');
expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/curations/cur-id-1');
wrapper.find('[data-test-subj="CurationsTableEditButton"]').last().simulate('click');
expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/curations/cur-id-2');
});
it('delete action calls deleteCuration', () => {
wrapper.find('[data-test-subj="CurationsTableDeleteButton"]').first().simulate('click');
expect(actions.deleteCuration).toHaveBeenCalledWith('cur-id-1');
wrapper.find('[data-test-subj="CurationsTableDeleteButton"]').last().simulate('click');
expect(actions.deleteCuration).toHaveBeenCalledWith('cur-id-2');
});
});
});
describe('pagination', () => {
it('passes pagination props from meta.page', () => {
setMockValues({
...values,
meta: {
page: {
current: 5,
size: 10,
total_results: 50,
},
},
});
const wrapper = shallow(<CurationsTable />);
expect(wrapper.find(EuiBasicTable).prop('pagination')).toEqual({
pageIndex: 4,
pageSize: 10,
totalItemCount: 50,
hidePerPageOptions: true,
});
});
it('calls onPaginate on pagination change', () => {
const wrapper = shallow(<CurationsTable />);
wrapper.find(EuiBasicTable).simulate('change', { page: { index: 0 } });
expect(actions.onPaginate).toHaveBeenCalledWith(1);
});
});
});

View file

@ -0,0 +1,117 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { useValues, useActions } from 'kea';
import { EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { EDIT_BUTTON_LABEL, DELETE_BUTTON_LABEL } from '../../../../shared/constants';
import { KibanaLogic } from '../../../../shared/kibana';
import { EuiLinkTo } from '../../../../shared/react_router_helpers';
import { convertMetaToPagination, handlePageChange } from '../../../../shared/table_pagination';
import { ENGINE_CURATION_PATH } from '../../../routes';
import { FormattedDateTime } from '../../../utils/formatted_date_time';
import { generateEnginePath } from '../../engine';
import { CurationsLogic } from '../curations_logic';
import { Curation } from '../types';
import { convertToDate } from '../utils';
export const CurationsTable: React.FC = () => {
const { dataLoading, curations, meta } = useValues(CurationsLogic);
const { onPaginate, deleteCuration } = useActions(CurationsLogic);
const columns: Array<EuiBasicTableColumn<Curation>> = [
{
field: 'queries',
name: i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.curations.table.column.queries',
{ defaultMessage: 'Queries' }
),
render: (queries: Curation['queries'], curation: Curation) => (
<EuiLinkTo
data-test-subj="CurationsTableQueriesLink"
to={generateEnginePath(ENGINE_CURATION_PATH, { curationId: curation.id })}
>
{queries.join(', ')}
</EuiLinkTo>
),
width: '40%',
truncateText: true,
mobileOptions: {
header: true,
// Note: the below props are valid props per https://elastic.github.io/eui/#/tabular-content/tables (Responsive tables), but EUI's types have a bug reporting it as an error
// @ts-ignore
enlarge: true,
width: '100%',
truncateText: false,
},
},
{
field: 'last_updated',
name: i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.curations.table.column.lastUpdated',
{ defaultMessage: 'Last updated' }
),
width: '30%',
dataType: 'string',
render: (dateString: string) => <FormattedDateTime date={convertToDate(dateString)} />,
},
{
width: '120px',
actions: [
{
name: EDIT_BUTTON_LABEL,
description: i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.curations.table.editTooltip',
{ defaultMessage: 'Edit curation' }
),
type: 'icon',
icon: 'pencil',
color: 'primary',
onClick: (curation: Curation) => {
const { navigateToUrl } = KibanaLogic.values;
const url = generateEnginePath(ENGINE_CURATION_PATH, { curationId: curation.id });
navigateToUrl(url);
},
'data-test-subj': 'CurationsTableEditButton',
},
{
name: DELETE_BUTTON_LABEL,
description: i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.curations.table.deleteTooltip',
{ defaultMessage: 'Delete curation' }
),
type: 'icon',
icon: 'trash',
color: 'danger',
onClick: (curation: Curation) => deleteCuration(curation.id),
'data-test-subj': 'CurationsTableDeleteButton',
},
],
},
];
return (
<EuiBasicTable
columns={columns}
items={curations}
responsive
hasActions
loading={dataLoading}
pagination={{
...convertMetaToPagination(meta),
hidePerPageOptions: true,
}}
onChange={handlePageChange(onPaginate)}
/>
);
};

View file

@ -5,4 +5,5 @@
* 2.0.
*/
export { CurationsTable } from './curations_table';
export { EmptyState } from './empty_state';

View file

@ -50,6 +50,7 @@ describe('CurationsLogic', () => {
dataLoading: true,
curations: [],
meta: DEFAULT_META,
selectedPageTab: 'overview',
};
beforeEach(() => {
@ -89,6 +90,19 @@ describe('CurationsLogic', () => {
});
});
});
describe('onSelectPageTab', () => {
it('should set the selected page tab', () => {
mount();
CurationsLogic.actions.onSelectPageTab('settings');
expect(CurationsLogic.values).toEqual({
...DEFAULT_VALUES,
selectedPageTab: 'settings',
});
});
});
});
describe('listeners', () => {

View file

@ -23,10 +23,13 @@ import { EngineLogic, generateEnginePath } from '../engine';
import { DELETE_MESSAGE, SUCCESS_MESSAGE } from './constants';
import { Curation, CurationsAPIResponse } from './types';
type CurationsPageTabs = 'overview' | 'settings';
interface CurationsValues {
dataLoading: boolean;
curations: Curation[];
meta: Meta;
selectedPageTab: CurationsPageTabs;
}
interface CurationsActions {
@ -35,6 +38,7 @@ interface CurationsActions {
loadCurations(): void;
deleteCuration(id: string): string;
createCuration(queries: Curation['queries']): Curation['queries'];
onSelectPageTab(pageTab: CurationsPageTabs): { pageTab: CurationsPageTabs };
}
export const CurationsLogic = kea<MakeLogicType<CurationsValues, CurationsActions>>({
@ -45,8 +49,15 @@ export const CurationsLogic = kea<MakeLogicType<CurationsValues, CurationsAction
loadCurations: true,
deleteCuration: (id) => id,
createCuration: (queries) => queries,
onSelectPageTab: (pageTab) => ({ pageTab }),
}),
reducers: () => ({
selectedPageTab: [
'overview',
{
onSelectPageTab: (_, { pageTab }) => pageTab,
},
],
dataLoading: [
true,
{

View file

@ -5,23 +5,23 @@
* 2.0.
*/
import { mockKibanaValues, setMockActions, setMockValues } from '../../../../__mocks__/kea_logic';
import { setMockActions, setMockValues } from '../../../../__mocks__/kea_logic';
import '../../../../__mocks__/react_router';
import '../../../__mocks__/engine_logic.mock';
import React from 'react';
import { shallow, ReactWrapper } from 'enzyme';
import { shallow } from 'enzyme';
import { EuiBasicTable } from '@elastic/eui';
import { EuiTab } from '@elastic/eui';
import { mountWithIntl, getPageTitle } from '../../../../test_helpers';
import { mountWithIntl, getPageHeaderTabs, getPageTitle } from '../../../../test_helpers';
import { Curations, CurationsTable } from './curations';
import { Curations } from './curations';
import { CurationsOverview } from './curations_overview';
import { CurationsSettings } from './curations_settings';
describe('Curations', () => {
const { navigateToUrl } = mockKibanaValues;
const values = {
dataLoading: false,
curations: [
@ -43,12 +43,13 @@ describe('Curations', () => {
total_results: 2,
},
},
selectedPageTab: 'overview',
};
const actions = {
loadCurations: jest.fn(),
deleteCuration: jest.fn(),
onPaginate: jest.fn(),
onSelectPageTab: jest.fn(),
};
beforeEach(() => {
@ -57,11 +58,38 @@ describe('Curations', () => {
setMockActions(actions);
});
it('renders', () => {
it('renders with a set of tabs in the page header', () => {
const wrapper = shallow(<Curations />);
expect(getPageTitle(wrapper)).toEqual('Curated results');
expect(wrapper.find(CurationsTable)).toHaveLength(1);
const tabs = getPageHeaderTabs(wrapper).find(EuiTab);
tabs.at(0).simulate('click');
expect(actions.onSelectPageTab).toHaveBeenNthCalledWith(1, 'overview');
tabs.at(1).simulate('click');
expect(actions.onSelectPageTab).toHaveBeenNthCalledWith(2, 'settings');
});
it('renders an overview view', () => {
setMockValues({ ...values, selectedPageTab: 'overview' });
const wrapper = shallow(<Curations />);
const tabs = getPageHeaderTabs(wrapper).find(EuiTab);
expect(tabs.at(0).prop('isSelected')).toEqual(true);
expect(wrapper.find(CurationsOverview)).toHaveLength(1);
});
it('renders a settings view', () => {
setMockValues({ ...values, selectedPageTab: 'settings' });
const wrapper = shallow(<Curations />);
const tabs = getPageHeaderTabs(wrapper).find(EuiTab);
expect(tabs.at(1).prop('isSelected')).toEqual(true);
expect(wrapper.find(CurationsSettings)).toHaveLength(1);
});
describe('loading state', () => {
@ -86,91 +114,4 @@ describe('Curations', () => {
expect(actions.loadCurations).toHaveBeenCalledTimes(1);
});
describe('CurationsTable', () => {
it('passes loading prop based on dataLoading', () => {
setMockValues({ ...values, dataLoading: true });
const wrapper = shallow(<CurationsTable />);
expect(wrapper.find(EuiBasicTable).prop('loading')).toEqual(true);
});
describe('populated table render', () => {
let wrapper: ReactWrapper;
beforeAll(() => {
wrapper = mountWithIntl(<CurationsTable />);
});
it('renders queries and last updated columns', () => {
const tableContent = wrapper.find(EuiBasicTable).text();
expect(tableContent).toContain('Queries');
expect(tableContent).toContain('hiking');
expect(tableContent).toContain('mountains, valleys');
expect(tableContent).toContain('Last updated');
expect(tableContent).toContain('Jan 1, 1970 12:00 PM');
expect(tableContent).toContain('Jan 2, 1970 12:00 PM');
});
it('renders queries with curation links', () => {
expect(
wrapper.find('EuiLinkTo[data-test-subj="CurationsTableQueriesLink"]').first().prop('to')
).toEqual('/engines/some-engine/curations/cur-id-1');
expect(
wrapper.find('EuiLinkTo[data-test-subj="CurationsTableQueriesLink"]').last().prop('to')
).toEqual('/engines/some-engine/curations/cur-id-2');
});
describe('action column', () => {
it('edit action navigates to curation link', () => {
wrapper.find('[data-test-subj="CurationsTableEditButton"]').first().simulate('click');
expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/curations/cur-id-1');
wrapper.find('[data-test-subj="CurationsTableEditButton"]').last().simulate('click');
expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/curations/cur-id-2');
});
it('delete action calls deleteCuration', () => {
wrapper.find('[data-test-subj="CurationsTableDeleteButton"]').first().simulate('click');
expect(actions.deleteCuration).toHaveBeenCalledWith('cur-id-1');
wrapper.find('[data-test-subj="CurationsTableDeleteButton"]').last().simulate('click');
expect(actions.deleteCuration).toHaveBeenCalledWith('cur-id-2');
});
});
});
describe('pagination', () => {
it('passes pagination props from meta.page', () => {
setMockValues({
...values,
meta: {
page: {
current: 5,
size: 10,
total_results: 50,
},
},
});
const wrapper = shallow(<CurationsTable />);
expect(wrapper.find(EuiBasicTable).prop('pagination')).toEqual({
pageIndex: 4,
pageSize: 10,
totalItemCount: 50,
hidePerPageOptions: true,
});
});
it('calls onPaginate on pagination change', () => {
const wrapper = shallow(<CurationsTable />);
wrapper.find(EuiBasicTable).simulate('change', { page: { index: 0 } });
expect(actions.onPaginate).toHaveBeenCalledWith(1);
});
});
});
});

View file

@ -9,28 +9,47 @@ import React, { useEffect } from 'react';
import { useValues, useActions } from 'kea';
import { EuiBasicTable, EuiBasicTableColumn, EuiPanel } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { EDIT_BUTTON_LABEL, DELETE_BUTTON_LABEL } from '../../../../shared/constants';
import { KibanaLogic } from '../../../../shared/kibana';
import { EuiButtonTo, EuiLinkTo } from '../../../../shared/react_router_helpers';
import { convertMetaToPagination, handlePageChange } from '../../../../shared/table_pagination';
import { EuiButtonTo } from '../../../../shared/react_router_helpers';
import { ENGINE_CURATIONS_NEW_PATH, ENGINE_CURATION_PATH } from '../../../routes';
import { FormattedDateTime } from '../../../utils/formatted_date_time';
import { ENGINE_CURATIONS_NEW_PATH } from '../../../routes';
import { generateEnginePath } from '../../engine';
import { AppSearchPageTemplate } from '../../layout';
import { EmptyState } from '../components';
import { CURATIONS_OVERVIEW_TITLE, CREATE_NEW_CURATION_TITLE } from '../constants';
import { CurationsLogic } from '../curations_logic';
import { Curation } from '../types';
import { getCurationsBreadcrumbs, convertToDate } from '../utils';
import { getCurationsBreadcrumbs } from '../utils';
import { CurationsOverview } from './curations_overview';
import { CurationsSettings } from './curations_settings';
export const Curations: React.FC = () => {
const { dataLoading, curations, meta } = useValues(CurationsLogic);
const { loadCurations } = useActions(CurationsLogic);
const { dataLoading, curations, meta, selectedPageTab } = useValues(CurationsLogic);
const { loadCurations, onSelectPageTab } = useActions(CurationsLogic);
const pageTabs = [
{
label: i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.curations.overviewPageTabLabel',
{
defaultMessage: 'Overview',
}
),
isSelected: selectedPageTab === 'overview',
onClick: () => onSelectPageTab('overview'),
},
{
label: i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.curations.settingsPageTabLabel',
{
defaultMessage: 'Settings',
}
),
isSelected: selectedPageTab === 'settings',
onClick: () => onSelectPageTab('settings'),
},
];
useEffect(() => {
loadCurations();
@ -50,105 +69,12 @@ export const Curations: React.FC = () => {
{CREATE_NEW_CURATION_TITLE}
</EuiButtonTo>,
],
tabs: pageTabs,
}}
isLoading={dataLoading && !curations.length}
isEmptyState={!curations.length}
emptyState={<EmptyState />}
>
<EuiPanel hasBorder>
<CurationsTable />
</EuiPanel>
{selectedPageTab === 'overview' && <CurationsOverview />}
{selectedPageTab === 'settings' && <CurationsSettings />}
</AppSearchPageTemplate>
);
};
export const CurationsTable: React.FC = () => {
const { dataLoading, curations, meta } = useValues(CurationsLogic);
const { onPaginate, deleteCuration } = useActions(CurationsLogic);
const columns: Array<EuiBasicTableColumn<Curation>> = [
{
field: 'queries',
name: i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.curations.table.column.queries',
{ defaultMessage: 'Queries' }
),
render: (queries: Curation['queries'], curation: Curation) => (
<EuiLinkTo
data-test-subj="CurationsTableQueriesLink"
to={generateEnginePath(ENGINE_CURATION_PATH, { curationId: curation.id })}
>
{queries.join(', ')}
</EuiLinkTo>
),
width: '40%',
truncateText: true,
mobileOptions: {
header: true,
// Note: the below props are valid props per https://elastic.github.io/eui/#/tabular-content/tables (Responsive tables), but EUI's types have a bug reporting it as an error
// @ts-ignore
enlarge: true,
width: '100%',
truncateText: false,
},
},
{
field: 'last_updated',
name: i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.curations.table.column.lastUpdated',
{ defaultMessage: 'Last updated' }
),
width: '30%',
dataType: 'string',
render: (dateString: string) => <FormattedDateTime date={convertToDate(dateString)} />,
},
{
width: '120px',
actions: [
{
name: EDIT_BUTTON_LABEL,
description: i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.curations.table.editTooltip',
{ defaultMessage: 'Edit curation' }
),
type: 'icon',
icon: 'pencil',
color: 'primary',
onClick: (curation: Curation) => {
const { navigateToUrl } = KibanaLogic.values;
const url = generateEnginePath(ENGINE_CURATION_PATH, { curationId: curation.id });
navigateToUrl(url);
},
'data-test-subj': 'CurationsTableEditButton',
},
{
name: DELETE_BUTTON_LABEL,
description: i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.curations.table.deleteTooltip',
{ defaultMessage: 'Delete curation' }
),
type: 'icon',
icon: 'trash',
color: 'danger',
onClick: (curation: Curation) => deleteCuration(curation.id),
'data-test-subj': 'CurationsTableDeleteButton',
},
],
},
];
return (
<EuiBasicTable
columns={columns}
items={curations}
responsive
hasActions
loading={dataLoading}
pagination={{
...convertMetaToPagination(meta),
hidePerPageOptions: true,
}}
onChange={handlePageChange(onPaginate)}
/>
);
};

View file

@ -0,0 +1,47 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { setMockValues } from '../../../../__mocks__/kea_logic';
import '../../../../__mocks__/react_router';
import '../../../__mocks__/engine_logic.mock';
import React from 'react';
import { shallow } from 'enzyme';
import { CurationsTable, EmptyState } from '../components';
import { CurationsOverview } from './curations_overview';
describe('CurationsOverview', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('renders an empty message when there are no curations', () => {
setMockValues({ curations: [] });
const wrapper = shallow(<CurationsOverview />);
expect(wrapper.is(EmptyState)).toBe(true);
});
it('renders a curations table when there are curations present', () => {
setMockValues({
curations: [
{
id: 'cur-id-1',
},
{
id: 'cur-id-2',
},
],
});
const wrapper = shallow(<CurationsOverview />);
expect(wrapper.find(CurationsTable)).toHaveLength(1);
});
});

View file

@ -0,0 +1,27 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { useValues } from 'kea';
import { EuiPanel } from '@elastic/eui';
import { CurationsTable, EmptyState } from '../components';
import { CurationsLogic } from '../curations_logic';
export const CurationsOverview: React.FC = () => {
const { curations } = useValues(CurationsLogic);
return curations.length ? (
<EuiPanel hasBorder>
<CurationsTable />
</EuiPanel>
) : (
<EmptyState />
);
};

View file

@ -0,0 +1,27 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import '../../../../__mocks__/react_router';
import '../../../__mocks__/engine_logic.mock';
import React from 'react';
import { shallow } from 'enzyme';
import { CurationsSettings } from './curations_settings';
describe('CurationsSettings', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('renders empty', () => {
const wrapper = shallow(<CurationsSettings />);
expect(wrapper.isEmptyRender()).toBe(true);
});
});

View file

@ -0,0 +1,12 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
export const CurationsSettings: React.FC = () => {
return null;
};

View file

@ -9,7 +9,7 @@ import React, { Fragment } from 'react';
import { shallow, ShallowWrapper } from 'enzyme';
import { EuiPageHeaderProps } from '@elastic/eui';
import { EuiPageHeaderProps, EuiTab } from '@elastic/eui';
/*
* Given an AppSearchPageTemplate or WorkplaceSearchPageTemplate, these
@ -35,13 +35,30 @@ export const getPageHeaderActions = (wrapper: ShallowWrapper) => {
return shallow(
<div>
{actions.map((action: React.ReactNode, i) => (
{actions.map((action, i) => (
<Fragment key={i}>{action}</Fragment>
))}
</div>
);
};
export const getPageHeaderTabs = (wrapper: ShallowWrapper) => {
// The tabs prop of EuiPageHeader takes an `Array<EuiTabProps>`
// instead of an array of EuiTab jsx components
// These are then rendered inside of EuiPageHeader as EuiTabs
// See https://elastic.github.io/eui/#/layout/page-header#tabs-in-the-page-header
const tabs = getPageHeader(wrapper).tabs || [];
return shallow(
<div>
{tabs.map((tabProps, i) => (
<EuiTab {...tabProps} key={i} />
))}
</div>
);
};
export const getPageHeaderChildren = (wrapper: ShallowWrapper) => {
const children = getPageHeader(wrapper).children || null;

View file

@ -16,6 +16,7 @@ export {
getPageDescription,
getPageHeaderActions,
getPageHeaderChildren,
getPageHeaderTabs,
} from './get_page_header';
// Misc