Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[App Search] Convert Curations pages to new page template #102835

Merged
merged 9 commits into from
Jun 23, 2021
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