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

[Curation] Add Result Manually flyout/search #94887

Merged
merged 6 commits into from
Mar 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import { Loading } from '../../../../shared/loading';
jest.mock('./curation_logic', () => ({ CurationLogic: jest.fn() }));
import { CurationLogic } from './curation_logic';

import { AddResultFlyout } from './results';

import { Curation } from './';

describe('Curation', () => {
Expand All @@ -31,6 +33,7 @@ describe('Curation', () => {
const values = {
dataLoading: false,
queries: ['query A', 'query B'],
isFlyoutOpen: false,
};
const actions = {
loadCuration: jest.fn(),
Expand Down Expand Up @@ -60,6 +63,13 @@ describe('Curation', () => {
expect(wrapper.find(Loading)).toHaveLength(1);
});

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

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

it('initializes CurationLogic with a curationId prop from URL param', () => {
(useParams as jest.Mock).mockReturnValueOnce({ curationId: 'hello-world' });
shallow(<Curation {...props} />);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { MANAGE_CURATION_TITLE, RESTORE_CONFIRMATION } from '../constants';
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;
Expand All @@ -32,6 +33,7 @@ export const Curation: React.FC<Props> = ({ curationsBreadcrumb }) => {
const { curationId } = useParams() as { curationId: string };
const { loadCuration, resetCuration } = useActions(CurationLogic({ curationId }));
const { dataLoading, queries } = useValues(CurationLogic({ curationId }));
const { isFlyoutOpen } = useValues(AddResultLogic);

useEffect(() => {
loadCuration();
Expand Down Expand Up @@ -77,7 +79,7 @@ export const Curation: React.FC<Props> = ({ curationsBreadcrumb }) => {
<EuiSpacer />
<HiddenDocuments />

{/* TODO: AddResult flyout */}
{isFlyoutOpen && <AddResultFlyout />}
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* 2.0.
*/

import { setMockActions } from '../../../../../__mocks__';

import React from 'react';

import { shallow, ShallowWrapper } from 'enzyme';
Expand All @@ -14,9 +16,14 @@ import { EuiButton } from '@elastic/eui';
import { AddResultButton } from './';

describe('AddResultButton', () => {
const actions = {
openFlyout: jest.fn(),
};

let wrapper: ShallowWrapper;

beforeAll(() => {
setMockActions(actions);
wrapper = shallow(<AddResultButton />);
});

Expand All @@ -26,6 +33,6 @@ describe('AddResultButton', () => {

it('opens the add result flyout on click', () => {
wrapper.find(EuiButton).simulate('click');
// TODO: assert on logic action
expect(actions.openFlyout).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@

import React from 'react';

import { useActions } from 'kea';

import { EuiButton } from '@elastic/eui';
import { i18n } from '@kbn/i18n';

import { AddResultLogic } from './';

export const AddResultButton: React.FC = () => {
const { openFlyout } = useActions(AddResultLogic);

return (
<EuiButton onClick={() => {} /* TODO */} iconType="plusInCircle" size="s" fill>
<EuiButton onClick={openFlyout} iconType="plusInCircle" size="s" fill>
{i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.addResult.buttonLabel', {
defaultMessage: 'Add result manually',
})}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* 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 { setMockActions, setMockValues } from '../../../../../__mocks__';

import React from 'react';

import { shallow } from 'enzyme';

import { EuiFlyout, EuiFieldSearch, EuiEmptyPrompt } from '@elastic/eui';

import { CurationResult, AddResultFlyout } from './';

describe('AddResultFlyout', () => {
const values = {
dataLoading: false,
searchQuery: '',
searchResults: [],
promotedIds: [],
hiddenIds: [],
};
const actions = {
search: jest.fn(),
closeFlyout: jest.fn(),
addPromotedId: jest.fn(),
removePromotedId: jest.fn(),
addHiddenId: jest.fn(),
removeHiddenId: jest.fn(),
};

beforeEach(() => {
jest.clearAllMocks();
setMockValues(values);
setMockActions(actions);
});

it('renders a closeable flyout', () => {
const wrapper = shallow(<AddResultFlyout />);
expect(wrapper.find(EuiFlyout)).toHaveLength(1);

wrapper.find(EuiFlyout).simulate('close');
expect(actions.closeFlyout).toHaveBeenCalled();
});

describe('search input', () => {
it('renders isLoading state correctly', () => {
setMockValues({ ...values, dataLoading: true });
const wrapper = shallow(<AddResultFlyout />);

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

it('renders value correctly', () => {
setMockValues({ ...values, searchQuery: 'hello world' });
const wrapper = shallow(<AddResultFlyout />);

expect(wrapper.find(EuiFieldSearch).prop('value')).toEqual('hello world');
});

it('calls search on input change', () => {
const wrapper = shallow(<AddResultFlyout />);
wrapper.find(EuiFieldSearch).simulate('change', { target: { value: 'lorem ipsum' } });

expect(actions.search).toHaveBeenCalledWith('lorem ipsum');
});
});

describe('search results', () => {
it('renders an empty state', () => {
setMockValues({ ...values, searchResults: [] });
const wrapper = shallow(<AddResultFlyout />);

expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1);
expect(wrapper.find(CurationResult)).toHaveLength(0);
});

it('renders a result component for each item in searchResults', () => {
setMockValues({
...values,
searchResults: [
{ id: { raw: 'doc-1' } },
{ id: { raw: 'doc-2' } },
{ id: { raw: 'doc-3' } },
],
});
const wrapper = shallow(<AddResultFlyout />);

expect(wrapper.find(CurationResult)).toHaveLength(3);
});

describe('actions', () => {
it('renders a hide result button if the document ID is not already in the hiddenIds list', () => {
setMockValues({
...values,
searchResults: [{ id: { raw: 'visible-document' } }],
hiddenIds: ['hidden-document'],
});
const wrapper = shallow(<AddResultFlyout />);
wrapper.find(CurationResult).prop('actions')[0].onClick();

expect(actions.addHiddenId).toHaveBeenCalledWith('visible-document');
});

it('renders a show result button if the document ID is already in the hiddenIds list', () => {
setMockValues({
...values,
searchResults: [{ id: { raw: 'hidden-document' } }],
hiddenIds: ['hidden-document'],
});
const wrapper = shallow(<AddResultFlyout />);
wrapper.find(CurationResult).prop('actions')[0].onClick();

expect(actions.removeHiddenId).toHaveBeenCalledWith('hidden-document');
});

it('renders a promote result button if the document ID is not already in the promotedIds list', () => {
setMockValues({
...values,
searchResults: [{ id: { raw: 'some-document' } }],
promotedIds: ['promoted-document'],
});
const wrapper = shallow(<AddResultFlyout />);
wrapper.find(CurationResult).prop('actions')[1].onClick();

expect(actions.addPromotedId).toHaveBeenCalledWith('some-document');
});

it('renders a demote result button if the document ID is already in the promotedIds list', () => {
setMockValues({
...values,
searchResults: [{ id: { raw: 'promoted-document' } }],
promotedIds: ['promoted-document'],
});
const wrapper = shallow(<AddResultFlyout />);
wrapper.find(CurationResult).prop('actions')[1].onClick();

expect(actions.removePromotedId).toHaveBeenCalledWith('promoted-document');
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* 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 {
EuiPortal,
EuiFlyout,
EuiFlyoutHeader,
EuiFlyoutBody,
EuiTitle,
EuiText,
EuiSpacer,
EuiFieldSearch,
EuiEmptyPrompt,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';

import { FlashMessages } from '../../../../../shared/flash_messages';

import {
RESULT_ACTIONS_DIRECTIONS,
PROMOTE_DOCUMENT_ACTION,
DEMOTE_DOCUMENT_ACTION,
HIDE_DOCUMENT_ACTION,
SHOW_DOCUMENT_ACTION,
} from '../../constants';
import { CurationLogic } from '../curation_logic';

import { AddResultLogic, CurationResult } from './';

export const AddResultFlyout: React.FC = () => {
const { searchQuery, searchResults, dataLoading } = useValues(AddResultLogic);
const { search, closeFlyout } = useActions(AddResultLogic);

const { promotedIds, hiddenIds } = useValues(CurationLogic);
const { addPromotedId, removePromotedId, addHiddenId, removeHiddenId } = useActions(
CurationLogic
);

return (
<EuiPortal>
<EuiFlyout ownFocus onClose={closeFlyout} aria-labelledby="addResultFlyout">
<EuiFlyoutHeader hasBorder>
<EuiTitle size="m">
<h2 id="addResultFlyout">
{i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.addResult.title', {
defaultMessage: 'Add result to curation',
})}
</h2>
</EuiTitle>
<EuiText color="subdued">
<p>{RESULT_ACTIONS_DIRECTIONS}</p>
</EuiText>
</EuiFlyoutHeader>
<EuiFlyoutBody banner={<FlashMessages />}>
<EuiFieldSearch
value={searchQuery}
onChange={(e) => search(e.target.value)}
isLoading={dataLoading}
placeholder={i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.curations.addResult.searchPlaceholder',
{ defaultMessage: 'Search engine documents' }
)}
fullWidth
autoFocus
/>
<EuiSpacer />

{searchResults.length > 0 ? (
searchResults.map((result) => {
const id = result.id.raw;
const isPromoted = promotedIds.includes(id);
const isHidden = hiddenIds.includes(id);

return (
<CurationResult
key={id}
result={result}
actions={[
isHidden
? {
...SHOW_DOCUMENT_ACTION,
onClick: () => removeHiddenId(id),
}
: {
...HIDE_DOCUMENT_ACTION,
onClick: () => addHiddenId(id),
},
isPromoted
? {
...DEMOTE_DOCUMENT_ACTION,
onClick: () => removePromotedId(id),
}
: {
...PROMOTE_DOCUMENT_ACTION,
onClick: () => addPromotedId(id),
},
]}
/>
);
})
) : (
<EuiEmptyPrompt
body={i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.curations.addResult.searchEmptyDescription',
{ defaultMessage: 'No matching content found.' }
)}
/>
)}
</EuiFlyoutBody>
</EuiFlyout>
</EuiPortal>
);
};
Loading