Skip to content

Commit

Permalink
[App Search] Convert Curations pages to new page template (#102835)
Browse files Browse the repository at this point in the history
* Update CurationRouter

- Remove breadcrumbs set in router (will get set by page template)
- Set up a curation breadcrumb helper for DRYness
- Remove NotFound route - curation ID 404 handling will be used instead

* Convert Curations page to new page template
+ move Empty State from table to top level

* Convert Curation creation page to new page template

* Convert single Curation page to new page template

+ remove breadcrumb prop

* Update router

* [Polish] Copy changes from Davey

- see https://github.com/elastic/kibana/pull/101958/files

- Per https://elastic.github.io/eui/#/guidelines/writing we shouldn't be using "new", so I removed that also

* [UI polish] Add plus icon to create button

- To match other create buttons across app
  • Loading branch information
Constance committed Jun 23, 2021
1 parent cf12c03 commit e582549
Show file tree
Hide file tree
Showing 13 changed files with 110 additions and 114 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const CURATIONS_OVERVIEW_TITLE = i18n.translate(
);
export const CREATE_NEW_CURATION_TITLE = i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.curations.create.title',
{ defaultMessage: 'Create new curation' }
{ defaultMessage: 'Create a curation' }
);
export const MANAGE_CURATION_TITLE = i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.curations.manage.title',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,13 @@
import '../../../../__mocks__/shallow_useeffect.mock';
import { setMockActions, setMockValues } from '../../../../__mocks__/kea_logic';
import { mockUseParams } from '../../../../__mocks__/react_router';
import '../../../__mocks__/engine_logic.mock';

import React from 'react';

import { shallow, ShallowWrapper } from 'enzyme';

import { EuiPageHeader } from '@elastic/eui';

import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome';
import { Loading } from '../../../../shared/loading';
import { rerender } from '../../../../test_helpers';
import { rerender, getPageTitle, getPageHeaderActions } from '../../../../test_helpers';

jest.mock('./curation_logic', () => ({ CurationLogic: jest.fn() }));
import { CurationLogic } from './curation_logic';
Expand All @@ -27,9 +24,6 @@ import { AddResultFlyout } from './results';
import { Curation } from './';

describe('Curation', () => {
const props = {
curationsBreadcrumb: ['Engines', 'some-engine', 'Curations'],
};
const values = {
dataLoading: false,
queries: ['query A', 'query B'],
Expand All @@ -47,39 +41,34 @@ describe('Curation', () => {
});

it('renders', () => {
const wrapper = shallow(<Curation {...props} />);
const wrapper = shallow(<Curation />);

expect(wrapper.find(EuiPageHeader).prop('pageTitle')).toEqual('Manage curation');
expect(wrapper.find(SetPageChrome).prop('trail')).toEqual([
...props.curationsBreadcrumb,
expect(getPageTitle(wrapper)).toEqual('Manage curation');
expect(wrapper.prop('pageChrome')).toEqual([
'Engines',
'some-engine',
'Curations',
'query A, query B',
]);
});

it('renders a loading component on page load', () => {
setMockValues({ ...values, dataLoading: true });
const wrapper = shallow(<Curation {...props} />);

expect(wrapper.find(Loading)).toHaveLength(1);
});

it('renders the add result flyout when open', () => {
setMockValues({ ...values, isFlyoutOpen: true });
const wrapper = shallow(<Curation {...props} />);
const wrapper = shallow(<Curation />);

expect(wrapper.find(AddResultFlyout)).toHaveLength(1);
});

it('initializes CurationLogic with a curationId prop from URL param', () => {
mockUseParams.mockReturnValueOnce({ curationId: 'hello-world' });
shallow(<Curation {...props} />);
shallow(<Curation />);

expect(CurationLogic).toHaveBeenCalledWith({ curationId: 'hello-world' });
});

it('calls loadCuration on page load & whenever the curationId URL param changes', () => {
mockUseParams.mockReturnValueOnce({ curationId: 'cur-123456789' });
const wrapper = shallow(<Curation {...props} />);
const wrapper = shallow(<Curation />);
expect(actions.loadCuration).toHaveBeenCalledTimes(1);

mockUseParams.mockReturnValueOnce({ curationId: 'cur-987654321' });
Expand All @@ -92,9 +81,8 @@ describe('Curation', () => {
let confirmSpy: jest.SpyInstance;

beforeAll(() => {
const wrapper = shallow(<Curation {...props} />);
const headerActions = wrapper.find(EuiPageHeader).prop('rightSideItems');
restoreDefaultsButton = shallow(headerActions![0] as React.ReactElement);
const wrapper = shallow(<Curation />);
restoreDefaultsButton = getPageHeaderActions(wrapper).childAt(0);

confirmSpy = jest.spyOn(window, 'confirm');
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,19 @@ import { useParams } from 'react-router-dom';

import { useValues, useActions } from 'kea';

import { EuiPageHeader, EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui';

import { FlashMessages } from '../../../../shared/flash_messages';
import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome';
import { BreadcrumbTrail } from '../../../../shared/kibana_chrome/generate_breadcrumbs';
import { Loading } from '../../../../shared/loading';
import { EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui';

import { RESTORE_DEFAULTS_BUTTON_LABEL } from '../../../constants';
import { AppSearchPageTemplate } from '../../layout';
import { MANAGE_CURATION_TITLE, RESTORE_CONFIRMATION } from '../constants';
import { getCurationsBreadcrumbs } from '../utils';

import { CurationLogic } from './curation_logic';
import { PromotedDocuments, OrganicDocuments, HiddenDocuments } from './documents';
import { ActiveQuerySelect, ManageQueriesModal } from './queries';
import { AddResultLogic, AddResultFlyout } from './results';

interface Props {
curationsBreadcrumb: BreadcrumbTrail;
}

export const Curation: React.FC<Props> = ({ curationsBreadcrumb }) => {
export const Curation: React.FC = () => {
const { curationId } = useParams() as { curationId: string };
const { loadCuration, resetCuration } = useActions(CurationLogic({ curationId }));
const { dataLoading, queries } = useValues(CurationLogic({ curationId }));
Expand All @@ -39,14 +32,12 @@ export const Curation: React.FC<Props> = ({ curationsBreadcrumb }) => {
loadCuration();
}, [curationId]);

if (dataLoading) return <Loading />;

return (
<>
<SetPageChrome trail={[...curationsBreadcrumb, queries.join(', ')]} />
<EuiPageHeader
pageTitle={MANAGE_CURATION_TITLE}
rightSideItems={[
<AppSearchPageTemplate
pageChrome={getCurationsBreadcrumbs([queries.join(', ')])}
pageHeader={{
pageTitle: MANAGE_CURATION_TITLE,
rightSideItems: [
<EuiButton
color="danger"
onClick={() => {
Expand All @@ -55,10 +46,10 @@ export const Curation: React.FC<Props> = ({ curationsBreadcrumb }) => {
>
{RESTORE_DEFAULTS_BUTTON_LABEL}
</EuiButton>,
]}
responsive={false}
/>

],
}}
isLoading={dataLoading}
>
<EuiFlexGroup alignItems="flexEnd" gutterSize="xl" responsive={false}>
<EuiFlexItem>
<ActiveQuerySelect />
Expand All @@ -69,7 +60,6 @@ export const Curation: React.FC<Props> = ({ curationsBreadcrumb }) => {
</EuiFlexGroup>

<EuiSpacer size="xl" />
<FlashMessages />

<PromotedDocuments />
<EuiSpacer />
Expand All @@ -78,6 +68,6 @@ export const Curation: React.FC<Props> = ({ curationsBreadcrumb }) => {
<HiddenDocuments />

{isFlyoutOpen && <AddResultFlyout />}
</>
</AppSearchPageTemplate>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export const HiddenDocuments: React.FC = () => {
<h3>
{i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.emptyTitle',
{ defaultMessage: 'No documents are being hidden for this query' }
{ defaultMessage: "You haven't hidden any documents yet" }
)}
</h3>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ describe('CurationsRouter', () => {
const wrapper = shallow(<CurationsRouter />);

expect(wrapper.find(Switch)).toHaveLength(1);
expect(wrapper.find(Route)).toHaveLength(4);
expect(wrapper.find(Route)).toHaveLength(3);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,26 @@
import React from 'react';
import { Route, Switch } from 'react-router-dom';

import { APP_SEARCH_PLUGIN } from '../../../../../common/constants';
import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome';
import { NotFound } from '../../../shared/not_found';
import {
ENGINE_CURATIONS_PATH,
ENGINE_CURATIONS_NEW_PATH,
ENGINE_CURATION_PATH,
} from '../../routes';
import { getEngineBreadcrumbs } from '../engine';

import { CURATIONS_TITLE, CREATE_NEW_CURATION_TITLE } from './constants';
import { Curation } from './curation';
import { Curations, CurationCreation } from './views';

export const CurationsRouter: React.FC = () => {
const CURATIONS_BREADCRUMB = getEngineBreadcrumbs([CURATIONS_TITLE]);

return (
<Switch>
<Route exact path={ENGINE_CURATIONS_PATH}>
<SetPageChrome trail={CURATIONS_BREADCRUMB} />
<Curations />
</Route>
<Route exact path={ENGINE_CURATIONS_NEW_PATH}>
<SetPageChrome trail={[...CURATIONS_BREADCRUMB, CREATE_NEW_CURATION_TITLE]} />
<CurationCreation />
</Route>
<Route path={ENGINE_CURATION_PATH}>
<Curation curationsBreadcrumb={CURATIONS_BREADCRUMB} />
</Route>
<Route>
<NotFound breadcrumbs={CURATIONS_BREADCRUMB} product={APP_SEARCH_PLUGIN} />
<Curation />
</Route>
</Switch>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,21 @@
* 2.0.
*/

import { convertToDate, addDocument, removeDocument } from './utils';
import '../../__mocks__/engine_logic.mock';

import { getCurationsBreadcrumbs, convertToDate, addDocument, removeDocument } from './utils';

describe('getCurationsBreadcrumbs', () => {
it('generates curation-prefixed breadcrumbs', () => {
expect(getCurationsBreadcrumbs()).toEqual(['Engines', 'some-engine', 'Curations']);
expect(getCurationsBreadcrumbs(['Some page'])).toEqual([
'Engines',
'some-engine',
'Curations',
'Some page',
]);
});
});

describe('convertToDate', () => {
it('converts the English-only server timestamps to a parseable Date', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
* 2.0.
*/

import { BreadcrumbTrail } from '../../../shared/kibana_chrome/generate_breadcrumbs';
import { getEngineBreadcrumbs } from '../engine';

import { CURATIONS_TITLE } from './constants';

export const getCurationsBreadcrumbs = (breadcrumbs: BreadcrumbTrail = []) =>
getEngineBreadcrumbs([CURATIONS_TITLE, ...breadcrumbs]);

// The server API feels us an English datestring, but we want to convert
// it to an actual Date() instance so that we can localize date formats.
export const convertToDate = (serverDateString: string): Date => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import { setMockActions } from '../../../../__mocks__/kea_logic';
import '../../../__mocks__/engine_logic.mock';

import React from 'react';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import React from 'react';

import { useActions } from 'kea';

import { EuiPageHeader, EuiPageContent, EuiTitle, EuiText, EuiSpacer } from '@elastic/eui';
import { EuiPanel, EuiTitle, EuiText, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';

import { FlashMessages } from '../../../../shared/flash_messages';
import { AppSearchPageTemplate } from '../../layout';
import { MultiInputRows } from '../../multi_input_rows';

import {
Expand All @@ -21,15 +21,17 @@ import {
QUERY_INPUTS_PLACEHOLDER,
} from '../constants';
import { CurationsLogic } from '../index';
import { getCurationsBreadcrumbs } from '../utils';

export const CurationCreation: React.FC = () => {
const { createCuration } = useActions(CurationsLogic);

return (
<>
<EuiPageHeader pageTitle={CREATE_NEW_CURATION_TITLE} />
<FlashMessages />
<EuiPageContent hasBorder>
<AppSearchPageTemplate
pageChrome={getCurationsBreadcrumbs([CREATE_NEW_CURATION_TITLE])}
pageHeader={{ pageTitle: CREATE_NEW_CURATION_TITLE }}
>
<EuiPanel hasBorder>
<EuiTitle>
<h2>
{i18n.translate(
Expand All @@ -56,7 +58,7 @@ export const CurationCreation: React.FC = () => {
inputPlaceholder={QUERY_INPUTS_PLACEHOLDER}
onSubmit={(queries) => createCuration(queries)}
/>
</EuiPageContent>
</>
</EuiPanel>
</AppSearchPageTemplate>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,16 @@
*/

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 { EuiPageHeader, EuiBasicTable } from '@elastic/eui';
import { EuiBasicTable } from '@elastic/eui';

import { Loading } from '../../../../shared/loading';
import { mountWithIntl } from '../../../../test_helpers';
import { EmptyState } from '../components';
import { mountWithIntl, getPageTitle } from '../../../../test_helpers';

import { Curations, CurationsTable } from './curations';

Expand Down Expand Up @@ -61,32 +60,34 @@ describe('Curations', () => {
it('renders', () => {
const wrapper = shallow(<Curations />);

expect(wrapper.find(EuiPageHeader).prop('pageTitle')).toEqual('Curated results');
expect(getPageTitle(wrapper)).toEqual('Curated results');
expect(wrapper.find(CurationsTable)).toHaveLength(1);
});

it('renders a loading component on page load', () => {
setMockValues({ ...values, dataLoading: true, curations: [] });
const wrapper = shallow(<Curations />);
describe('loading state', () => {
it('renders a full-page loading state on initial page load', () => {
setMockValues({ ...values, dataLoading: true, curations: [] });
const wrapper = shallow(<Curations />);

expect(wrapper.prop('isLoading')).toEqual(true);
});

it('does not re-render a full-page loading state after initial page load (uses component-level loading state instead)', () => {
setMockValues({ ...values, dataLoading: true, curations: [{}] });
const wrapper = shallow(<Curations />);

expect(wrapper.find(Loading)).toHaveLength(1);
expect(wrapper.prop('isLoading')).toEqual(false);
});
});

it('calls loadCurations on page load', () => {
setMockValues({ ...values, myRole: {} }); // Required for AppSearchPageTemplate to load
mountWithIntl(<Curations />);

expect(actions.loadCurations).toHaveBeenCalledTimes(1);
});

describe('CurationsTable', () => {
it('renders an empty state', () => {
setMockValues({ ...values, curations: [] });
const table = shallow(<CurationsTable />).find(EuiBasicTable);
const noItemsMessage = table.prop('noItemsMessage') as React.ReactElement;

expect(noItemsMessage.type).toEqual(EmptyState);
});

it('passes loading prop based on dataLoading', () => {
setMockValues({ ...values, dataLoading: true });
const wrapper = shallow(<CurationsTable />);
Expand Down
Loading

0 comments on commit e582549

Please sign in to comment.