mirror of
https://github.com/elastic/kibana.git
synced 2025-04-25 02:09:32 -04:00
[App Search] Automated Curations: Split Curations view into "Overview" and "Settings" tabs (#112488) (#112587)
Co-authored-by: Byron Hulcher <byronhulcher@gmail.com>
This commit is contained in:
parent
21bf2818ae
commit
cd89cabf6a
13 changed files with 491 additions and 207 deletions
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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)}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -5,4 +5,5 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export { CurationsTable } from './curations_table';
|
||||
export { EmptyState } from './empty_state';
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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,
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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 />
|
||||
);
|
||||
};
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
};
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ export {
|
|||
getPageDescription,
|
||||
getPageHeaderActions,
|
||||
getPageHeaderChildren,
|
||||
getPageHeaderTabs,
|
||||
} from './get_page_header';
|
||||
|
||||
// Misc
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue