[Curation] Add promoted/hidden documents section & logic + Restore defaults button (#94769)
* Set up promoted & hidden documents logic * Set up result utility for converting CurationResult to Result * Set up AddResultButton in documents sections - not hooked up to anything right now, but will be in the next PR * Add HiddenDocuments section * Add PromotedDocuments section w/ draggable results * Update OrganicDocuments results with promote/hide actions * Add the Restore Defaults button+logic * PR feedback: key ID Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
ad18739de7
commit
f4da06349d
|
@ -32,8 +32,43 @@ export const SUCCESS_MESSAGE = i18n.translate(
|
|||
'xpack.enterpriseSearch.appSearch.engine.curations.deleteSuccessMessage',
|
||||
{ defaultMessage: 'Successfully removed curation.' }
|
||||
);
|
||||
export const RESTORE_CONFIRMATION = i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.curations.restoreConfirmation',
|
||||
{
|
||||
defaultMessage:
|
||||
'Are you sure you want to clear your changes and return to your default results?',
|
||||
}
|
||||
);
|
||||
|
||||
export const RESULT_ACTIONS_DIRECTIONS = i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.curations.resultActionsDescription',
|
||||
{ defaultMessage: 'Promote results by clicking the star, hide them by clicking the eye.' }
|
||||
);
|
||||
export const PROMOTE_DOCUMENT_ACTION = {
|
||||
title: i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.promoteButtonLabel', {
|
||||
defaultMessage: 'Promote this result',
|
||||
}),
|
||||
iconType: 'starPlusEmpty',
|
||||
iconColor: 'primary',
|
||||
};
|
||||
export const DEMOTE_DOCUMENT_ACTION = {
|
||||
title: i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.demoteButtonLabel', {
|
||||
defaultMessage: 'Demote this result',
|
||||
}),
|
||||
iconType: 'starMinusFilled',
|
||||
iconColor: 'primary',
|
||||
};
|
||||
export const HIDE_DOCUMENT_ACTION = {
|
||||
title: i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.hideButtonLabel', {
|
||||
defaultMessage: 'Hide this result',
|
||||
}),
|
||||
iconType: 'eyeClosed',
|
||||
iconColor: 'danger',
|
||||
};
|
||||
export const SHOW_DOCUMENT_ACTION = {
|
||||
title: i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.showButtonLabel', {
|
||||
defaultMessage: 'Show this result',
|
||||
}),
|
||||
iconType: 'eye',
|
||||
iconColor: 'primary',
|
||||
};
|
||||
|
|
|
@ -12,7 +12,7 @@ import { setMockActions, setMockValues, rerender } from '../../../../__mocks__';
|
|||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
|
||||
import { EuiPageHeader } from '@elastic/eui';
|
||||
|
||||
|
@ -34,6 +34,7 @@ describe('Curation', () => {
|
|||
};
|
||||
const actions = {
|
||||
loadCuration: jest.fn(),
|
||||
resetCuration: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -75,4 +76,33 @@ describe('Curation', () => {
|
|||
rerender(wrapper);
|
||||
expect(actions.loadCuration).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
describe('restore defaults button', () => {
|
||||
let restoreDefaultsButton: ShallowWrapper;
|
||||
let confirmSpy: jest.SpyInstance;
|
||||
|
||||
beforeAll(() => {
|
||||
const wrapper = shallow(<Curation {...props} />);
|
||||
const headerActions = wrapper.find(EuiPageHeader).prop('rightSideItems');
|
||||
restoreDefaultsButton = shallow(headerActions![0] as React.ReactElement);
|
||||
|
||||
confirmSpy = jest.spyOn(window, 'confirm');
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
confirmSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('resets the curation upon user confirmation', () => {
|
||||
confirmSpy.mockReturnValueOnce(true);
|
||||
restoreDefaultsButton.simulate('click');
|
||||
expect(actions.resetCuration).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not reset the curation if the user cancels', () => {
|
||||
confirmSpy.mockReturnValueOnce(false);
|
||||
restoreDefaultsButton.simulate('click');
|
||||
expect(actions.resetCuration).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,17 +10,18 @@ import { useParams } from 'react-router-dom';
|
|||
|
||||
import { useValues, useActions } from 'kea';
|
||||
|
||||
import { EuiPageHeader, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { EuiPageHeader, EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
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 { MANAGE_CURATION_TITLE } from '../constants';
|
||||
import { MANAGE_CURATION_TITLE, RESTORE_CONFIRMATION } from '../constants';
|
||||
|
||||
import { CurationLogic } from './curation_logic';
|
||||
import { OrganicDocuments } from './documents';
|
||||
import { PromotedDocuments, OrganicDocuments, HiddenDocuments } from './documents';
|
||||
import { ActiveQuerySelect, ManageQueriesModal } from './queries';
|
||||
|
||||
interface Props {
|
||||
|
@ -29,7 +30,7 @@ interface Props {
|
|||
|
||||
export const Curation: React.FC<Props> = ({ curationsBreadcrumb }) => {
|
||||
const { curationId } = useParams() as { curationId: string };
|
||||
const { loadCuration } = useActions(CurationLogic({ curationId }));
|
||||
const { loadCuration, resetCuration } = useActions(CurationLogic({ curationId }));
|
||||
const { dataLoading, queries } = useValues(CurationLogic({ curationId }));
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -43,7 +44,18 @@ export const Curation: React.FC<Props> = ({ curationsBreadcrumb }) => {
|
|||
<SetPageChrome trail={[...curationsBreadcrumb, queries.join(', ')]} />
|
||||
<EuiPageHeader
|
||||
pageTitle={MANAGE_CURATION_TITLE}
|
||||
/* TODO: Restore defaults button */
|
||||
rightSideItems={[
|
||||
<EuiButton
|
||||
color="danger"
|
||||
onClick={() => {
|
||||
if (window.confirm(RESTORE_CONFIRMATION)) resetCuration();
|
||||
}}
|
||||
>
|
||||
{i18n.translate('xpack.enterpriseSearch.appSearch.actions.restoreDefaults', {
|
||||
defaultMessage: 'Restore defaults',
|
||||
})}
|
||||
</EuiButton>,
|
||||
]}
|
||||
responsive={false}
|
||||
/>
|
||||
|
||||
|
@ -59,9 +71,11 @@ export const Curation: React.FC<Props> = ({ curationsBreadcrumb }) => {
|
|||
<EuiSpacer size="xl" />
|
||||
<FlashMessages />
|
||||
|
||||
{/* TODO: PromotedDocuments section */}
|
||||
<PromotedDocuments />
|
||||
<EuiSpacer />
|
||||
<OrganicDocuments />
|
||||
{/* TODO: HiddenDocuments section */}
|
||||
<EuiSpacer />
|
||||
<HiddenDocuments />
|
||||
|
||||
{/* TODO: AddResult flyout */}
|
||||
</>
|
||||
|
|
|
@ -51,6 +51,10 @@ describe('CurationLogic', () => {
|
|||
queriesLoading: false,
|
||||
activeQuery: '',
|
||||
organicDocumentsLoading: false,
|
||||
promotedIds: [],
|
||||
promotedDocumentsLoading: false,
|
||||
hiddenIds: [],
|
||||
hiddenDocumentsLoading: false,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -64,7 +68,7 @@ describe('CurationLogic', () => {
|
|||
|
||||
describe('actions', () => {
|
||||
describe('onCurationLoad', () => {
|
||||
it('should set curation, queries, activeQuery, & all loading states to false', () => {
|
||||
it('should set curation, queries, activeQuery, promotedIds, hiddenIds, & all loading states to false', () => {
|
||||
mount();
|
||||
|
||||
CurationLogic.actions.onCurationLoad(MOCK_CURATION_RESPONSE);
|
||||
|
@ -74,9 +78,13 @@ describe('CurationLogic', () => {
|
|||
curation: MOCK_CURATION_RESPONSE,
|
||||
queries: ['some search'],
|
||||
activeQuery: 'some search',
|
||||
promotedIds: ['some-promoted-document'],
|
||||
hiddenIds: ['some-hidden-document'],
|
||||
dataLoading: false,
|
||||
queriesLoading: false,
|
||||
organicDocumentsLoading: false,
|
||||
promotedDocumentsLoading: false,
|
||||
hiddenDocumentsLoading: false,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -95,6 +103,8 @@ describe('CurationLogic', () => {
|
|||
dataLoading: true,
|
||||
queriesLoading: true,
|
||||
organicDocumentsLoading: true,
|
||||
promotedDocumentsLoading: true,
|
||||
hiddenDocumentsLoading: true,
|
||||
});
|
||||
|
||||
CurationLogic.actions.onCurationError();
|
||||
|
@ -104,6 +114,8 @@ describe('CurationLogic', () => {
|
|||
dataLoading: false,
|
||||
queriesLoading: false,
|
||||
organicDocumentsLoading: false,
|
||||
promotedDocumentsLoading: false,
|
||||
hiddenDocumentsLoading: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -136,6 +148,121 @@ describe('CurationLogic', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setPromotedIds', () => {
|
||||
it('should set promotedIds state & promotedDocumentsLoading to true', () => {
|
||||
mount();
|
||||
|
||||
CurationLogic.actions.setPromotedIds(['hello', 'world']);
|
||||
|
||||
expect(CurationLogic.values).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
promotedIds: ['hello', 'world'],
|
||||
promotedDocumentsLoading: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('addPromotedId', () => {
|
||||
it('should set promotedIds state & promotedDocumentsLoading to true', () => {
|
||||
mount({ promotedIds: ['hello'] });
|
||||
|
||||
CurationLogic.actions.addPromotedId('world');
|
||||
|
||||
expect(CurationLogic.values).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
promotedIds: ['hello', 'world'],
|
||||
promotedDocumentsLoading: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('removePromotedId', () => {
|
||||
it('should set promotedIds state & promotedDocumentsLoading to true', () => {
|
||||
mount({ promotedIds: ['hello', 'deleteme', 'world'] });
|
||||
|
||||
CurationLogic.actions.removePromotedId('deleteme');
|
||||
|
||||
expect(CurationLogic.values).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
promotedIds: ['hello', 'world'],
|
||||
promotedDocumentsLoading: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('clearPromotedId', () => {
|
||||
it('should reset promotedIds state & set promotedDocumentsLoading to true', () => {
|
||||
mount({ promotedIds: ['hello', 'world'] });
|
||||
|
||||
CurationLogic.actions.clearPromotedIds();
|
||||
|
||||
expect(CurationLogic.values).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
promotedIds: [],
|
||||
promotedDocumentsLoading: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('addHiddenId', () => {
|
||||
it('should set hiddenIds state & hiddenDocumentsLoading to true', () => {
|
||||
mount({ hiddenIds: ['hello'] });
|
||||
|
||||
CurationLogic.actions.addHiddenId('world');
|
||||
|
||||
expect(CurationLogic.values).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
hiddenIds: ['hello', 'world'],
|
||||
hiddenDocumentsLoading: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeHiddenId', () => {
|
||||
it('should set hiddenIds state & hiddenDocumentsLoading to true', () => {
|
||||
mount({ hiddenIds: ['hello', 'deleteme', 'world'] });
|
||||
|
||||
CurationLogic.actions.removeHiddenId('deleteme');
|
||||
|
||||
expect(CurationLogic.values).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
hiddenIds: ['hello', 'world'],
|
||||
hiddenDocumentsLoading: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('clearHiddenId', () => {
|
||||
it('should reset hiddenIds state & set hiddenDocumentsLoading to true', () => {
|
||||
mount({ hiddenIds: ['hello', 'world'] });
|
||||
|
||||
CurationLogic.actions.clearHiddenIds();
|
||||
|
||||
expect(CurationLogic.values).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
hiddenIds: [],
|
||||
hiddenDocumentsLoading: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('resetCuration', () => {
|
||||
it('should clear promotedIds & hiddenIds & set dataLoading to true', () => {
|
||||
mount({ promotedIds: ['hello'], hiddenIds: ['world'] });
|
||||
|
||||
CurationLogic.actions.resetCuration();
|
||||
|
||||
expect(CurationLogic.values).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
dataLoading: true,
|
||||
promotedIds: [],
|
||||
promotedDocumentsLoading: true,
|
||||
hiddenIds: [],
|
||||
hiddenDocumentsLoading: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('listeners', () => {
|
||||
|
@ -187,6 +314,8 @@ describe('CurationLogic', () => {
|
|||
{
|
||||
queries: ['a', 'b', 'c'],
|
||||
activeQuery: 'b',
|
||||
promotedIds: ['d', 'e', 'f'],
|
||||
hiddenIds: ['g'],
|
||||
},
|
||||
{ curationId: 'cur-123456789' }
|
||||
);
|
||||
|
@ -199,7 +328,7 @@ describe('CurationLogic', () => {
|
|||
expect(http.put).toHaveBeenCalledWith(
|
||||
'/api/app_search/engines/some-engine/curations/cur-123456789',
|
||||
{
|
||||
body: '{"queries":["a","b","c"],"query":"b","promoted":[],"hidden":[]}', // Uses state currently in CurationLogic
|
||||
body: '{"queries":["a","b","c"],"query":"b","promoted":["d","e","f"],"hidden":["g"]}', // Uses state currently in CurationLogic
|
||||
}
|
||||
);
|
||||
expect(CurationLogic.actions.onCurationLoad).toHaveBeenCalledWith(MOCK_CURATION_RESPONSE);
|
||||
|
@ -249,6 +378,34 @@ describe('CurationLogic', () => {
|
|||
it('setActiveQuery', () => {
|
||||
CurationLogic.actions.setActiveQuery('test');
|
||||
});
|
||||
|
||||
it('setPromotedIds', () => {
|
||||
CurationLogic.actions.setPromotedIds(['test']);
|
||||
});
|
||||
|
||||
it('addPromotedId', () => {
|
||||
CurationLogic.actions.addPromotedId('test');
|
||||
});
|
||||
|
||||
it('removePromotedId', () => {
|
||||
CurationLogic.actions.removePromotedId('test');
|
||||
});
|
||||
|
||||
it('clearPromotedIds', () => {
|
||||
CurationLogic.actions.clearPromotedIds();
|
||||
});
|
||||
|
||||
it('addHiddenId', () => {
|
||||
CurationLogic.actions.addHiddenId('test');
|
||||
});
|
||||
|
||||
it('removeHiddenId', () => {
|
||||
CurationLogic.actions.removeHiddenId('test');
|
||||
});
|
||||
|
||||
it('clearHiddenIds', () => {
|
||||
CurationLogic.actions.clearHiddenIds();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,6 +14,7 @@ import { ENGINE_CURATIONS_PATH } from '../../../routes';
|
|||
import { EngineLogic, generateEnginePath } from '../../engine';
|
||||
|
||||
import { Curation } from '../types';
|
||||
import { addDocument, removeDocument } from '../utils';
|
||||
|
||||
interface CurationValues {
|
||||
dataLoading: boolean;
|
||||
|
@ -22,6 +23,10 @@ interface CurationValues {
|
|||
queriesLoading: boolean;
|
||||
activeQuery: string;
|
||||
organicDocumentsLoading: boolean;
|
||||
promotedIds: string[];
|
||||
promotedDocumentsLoading: boolean;
|
||||
hiddenIds: string[];
|
||||
hiddenDocumentsLoading: boolean;
|
||||
}
|
||||
|
||||
interface CurationActions {
|
||||
|
@ -31,6 +36,14 @@ interface CurationActions {
|
|||
onCurationError(): void;
|
||||
updateQueries(queries: Curation['queries']): { queries: Curation['queries'] };
|
||||
setActiveQuery(query: string): { query: string };
|
||||
setPromotedIds(promotedIds: string[]): { promotedIds: string[] };
|
||||
addPromotedId(id: string): { id: string };
|
||||
removePromotedId(id: string): { id: string };
|
||||
clearPromotedIds(): void;
|
||||
addHiddenId(id: string): { id: string };
|
||||
removeHiddenId(id: string): { id: string };
|
||||
clearHiddenIds(): void;
|
||||
resetCuration(): void;
|
||||
}
|
||||
|
||||
interface CurationProps {
|
||||
|
@ -46,12 +59,21 @@ export const CurationLogic = kea<MakeLogicType<CurationValues, CurationActions,
|
|||
onCurationError: true,
|
||||
updateQueries: (queries) => ({ queries }),
|
||||
setActiveQuery: (query) => ({ query }),
|
||||
setPromotedIds: (promotedIds) => ({ promotedIds }),
|
||||
addPromotedId: (id) => ({ id }),
|
||||
removePromotedId: (id) => ({ id }),
|
||||
clearPromotedIds: true,
|
||||
addHiddenId: (id) => ({ id }),
|
||||
removeHiddenId: (id) => ({ id }),
|
||||
clearHiddenIds: true,
|
||||
resetCuration: true,
|
||||
}),
|
||||
reducers: () => ({
|
||||
dataLoading: [
|
||||
true,
|
||||
{
|
||||
loadCuration: () => true,
|
||||
resetCuration: () => true,
|
||||
onCurationLoad: () => false,
|
||||
onCurationError: () => false,
|
||||
},
|
||||
|
@ -99,6 +121,46 @@ export const CurationLogic = kea<MakeLogicType<CurationValues, CurationActions,
|
|||
onCurationError: () => false,
|
||||
},
|
||||
],
|
||||
promotedIds: [
|
||||
[],
|
||||
{
|
||||
onCurationLoad: (_, { curation }) => curation.promoted.map((document) => document.id),
|
||||
setPromotedIds: (_, { promotedIds }) => promotedIds,
|
||||
addPromotedId: (promotedIds, { id }) => addDocument(promotedIds, id),
|
||||
removePromotedId: (promotedIds, { id }) => removeDocument(promotedIds, id),
|
||||
clearPromotedIds: () => [],
|
||||
},
|
||||
],
|
||||
promotedDocumentsLoading: [
|
||||
false,
|
||||
{
|
||||
setPromotedIds: () => true,
|
||||
addPromotedId: () => true,
|
||||
removePromotedId: () => true,
|
||||
clearPromotedIds: () => true,
|
||||
onCurationLoad: () => false,
|
||||
onCurationError: () => false,
|
||||
},
|
||||
],
|
||||
hiddenIds: [
|
||||
[],
|
||||
{
|
||||
onCurationLoad: (_, { curation }) => curation.hidden.map((document) => document.id),
|
||||
addHiddenId: (hiddenIds, { id }) => addDocument(hiddenIds, id),
|
||||
removeHiddenId: (hiddenIds, { id }) => removeDocument(hiddenIds, id),
|
||||
clearHiddenIds: () => [],
|
||||
},
|
||||
],
|
||||
hiddenDocumentsLoading: [
|
||||
false,
|
||||
{
|
||||
addHiddenId: () => true,
|
||||
removeHiddenId: () => true,
|
||||
clearHiddenIds: () => true,
|
||||
onCurationLoad: () => false,
|
||||
onCurationError: () => false,
|
||||
},
|
||||
],
|
||||
}),
|
||||
listeners: ({ actions, values, props }) => ({
|
||||
loadCuration: async () => {
|
||||
|
@ -131,8 +193,8 @@ export const CurationLogic = kea<MakeLogicType<CurationValues, CurationActions,
|
|||
body: JSON.stringify({
|
||||
queries: values.queries,
|
||||
query: values.activeQuery,
|
||||
promoted: [], // TODO: promotedIds state
|
||||
hidden: [], // TODO: hiddenIds state
|
||||
promoted: values.promotedIds,
|
||||
hidden: values.hiddenIds,
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
@ -149,5 +211,16 @@ export const CurationLogic = kea<MakeLogicType<CurationValues, CurationActions,
|
|||
actions.updateCuration();
|
||||
},
|
||||
setActiveQuery: () => actions.updateCuration(),
|
||||
setPromotedIds: () => actions.updateCuration(),
|
||||
addPromotedId: () => actions.updateCuration(),
|
||||
removePromotedId: () => actions.updateCuration(),
|
||||
clearPromotedIds: () => actions.updateCuration(),
|
||||
addHiddenId: () => actions.updateCuration(),
|
||||
removeHiddenId: () => actions.updateCuration(),
|
||||
clearHiddenIds: () => actions.updateCuration(),
|
||||
resetCuration: () => {
|
||||
actions.clearPromotedIds();
|
||||
actions.clearHiddenIds();
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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, setMockActions } from '../../../../../__mocks__';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { EuiEmptyPrompt, EuiButtonEmpty } from '@elastic/eui';
|
||||
|
||||
import { DataPanel } from '../../../data_panel';
|
||||
import { CurationResult } from '../results';
|
||||
|
||||
import { HiddenDocuments } from './';
|
||||
|
||||
describe('HiddenDocuments', () => {
|
||||
const values = {
|
||||
curation: {
|
||||
hidden: [
|
||||
{ id: 'mock-document-1' },
|
||||
{ id: 'mock-document-2' },
|
||||
{ id: 'mock-document-3' },
|
||||
{ id: 'mock-document-4' },
|
||||
{ id: 'mock-document-5' },
|
||||
],
|
||||
},
|
||||
hiddenDocumentsLoading: false,
|
||||
};
|
||||
const actions = {
|
||||
removeHiddenId: jest.fn(),
|
||||
clearHiddenIds: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
setMockValues(values);
|
||||
setMockActions(actions);
|
||||
});
|
||||
|
||||
it('renders a list of hidden documents', () => {
|
||||
const wrapper = shallow(<HiddenDocuments />);
|
||||
|
||||
expect(wrapper.find(CurationResult)).toHaveLength(5);
|
||||
});
|
||||
|
||||
it('renders an empty state & hides the panel actions when empty', () => {
|
||||
setMockValues({ ...values, curation: { hidden: [] } });
|
||||
const wrapper = shallow(<HiddenDocuments />);
|
||||
|
||||
expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1);
|
||||
expect(wrapper.find(DataPanel).prop('action')).toBe(false);
|
||||
});
|
||||
|
||||
it('renders a loading state', () => {
|
||||
setMockValues({ ...values, hiddenDocumentsLoading: true });
|
||||
const wrapper = shallow(<HiddenDocuments />);
|
||||
|
||||
expect(wrapper.find(DataPanel).prop('isLoading')).toEqual(true);
|
||||
});
|
||||
|
||||
describe('actions', () => {
|
||||
it('renders results with an action button that un-hides the result', () => {
|
||||
const wrapper = shallow(<HiddenDocuments />);
|
||||
const result = wrapper.find(CurationResult).last();
|
||||
result.prop('actions')[0].onClick();
|
||||
|
||||
expect(actions.removeHiddenId).toHaveBeenCalledWith('mock-document-5');
|
||||
});
|
||||
|
||||
it('renders a restore all button that un-hides all hidden results', () => {
|
||||
const wrapper = shallow(<HiddenDocuments />);
|
||||
const panelActions = shallow(wrapper.find(DataPanel).prop('action') as React.ReactElement);
|
||||
|
||||
panelActions.find(EuiButtonEmpty).simulate('click');
|
||||
expect(actions.clearHiddenIds).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* 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 { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiEmptyPrompt } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { DataPanel } from '../../../data_panel';
|
||||
|
||||
import { SHOW_DOCUMENT_ACTION } from '../../constants';
|
||||
import { CurationLogic } from '../curation_logic';
|
||||
import { AddResultButton, CurationResult, convertToResultFormat } from '../results';
|
||||
|
||||
export const HiddenDocuments: React.FC = () => {
|
||||
const { clearHiddenIds, removeHiddenId } = useActions(CurationLogic);
|
||||
const { curation, hiddenDocumentsLoading } = useValues(CurationLogic);
|
||||
|
||||
const documents = curation.hidden;
|
||||
const hasDocuments = documents.length > 0;
|
||||
|
||||
return (
|
||||
<DataPanel
|
||||
filled
|
||||
iconType="eyeClosed"
|
||||
title={
|
||||
<h2>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.title',
|
||||
{ defaultMessage: 'Hidden documents' }
|
||||
)}
|
||||
</h2>
|
||||
}
|
||||
subtitle={i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.description',
|
||||
{ defaultMessage: 'Hidden documents will not appear in organic results.' }
|
||||
)}
|
||||
action={
|
||||
hasDocuments && (
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<AddResultButton />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiButtonEmpty onClick={clearHiddenIds} iconType="menuUp" size="s">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.removeAllButtonLabel',
|
||||
{ defaultMessage: 'Restore all' }
|
||||
)}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)
|
||||
}
|
||||
isLoading={hiddenDocumentsLoading}
|
||||
>
|
||||
{hasDocuments ? (
|
||||
documents.map((document) => (
|
||||
<CurationResult
|
||||
key={document.id}
|
||||
result={convertToResultFormat(document)}
|
||||
actions={[
|
||||
{
|
||||
...SHOW_DOCUMENT_ACTION,
|
||||
onClick: () => removeHiddenId(document.id),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<EuiEmptyPrompt
|
||||
titleSize="s"
|
||||
title={
|
||||
<h3>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.emptyTitle',
|
||||
{ defaultMessage: 'No documents are being hidden for this query' }
|
||||
)}
|
||||
</h3>
|
||||
}
|
||||
body={i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.emptyDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Hide documents by clicking the eye icon on the organic results above, or search and hide a result manually.',
|
||||
}
|
||||
)}
|
||||
actions={<AddResultButton />}
|
||||
/>
|
||||
)}
|
||||
</DataPanel>
|
||||
);
|
||||
};
|
|
@ -5,4 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export { PromotedDocuments } from './promoted_documents';
|
||||
export { OrganicDocuments } from './organic_documents';
|
||||
export { HiddenDocuments } from './hidden_documents';
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { setMockValues } from '../../../../../__mocks__';
|
||||
import { setMockValues, setMockActions } from '../../../../../__mocks__';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
|
@ -31,10 +31,15 @@ describe('OrganicDocuments', () => {
|
|||
activeQuery: 'world',
|
||||
organicDocumentsLoading: false,
|
||||
};
|
||||
const actions = {
|
||||
addPromotedId: jest.fn(),
|
||||
addHiddenId: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
setMockValues(values);
|
||||
setMockActions(actions);
|
||||
});
|
||||
|
||||
it('renders a list of organic results', () => {
|
||||
|
@ -64,4 +69,22 @@ describe('OrganicDocuments', () => {
|
|||
|
||||
expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1);
|
||||
});
|
||||
|
||||
describe('actions', () => {
|
||||
it('renders results with an action button that promotes the result', () => {
|
||||
const wrapper = shallow(<OrganicDocuments />);
|
||||
const result = wrapper.find(CurationResult).first();
|
||||
result.prop('actions')[1].onClick();
|
||||
|
||||
expect(actions.addPromotedId).toHaveBeenCalledWith('mock-document-1');
|
||||
});
|
||||
|
||||
it('renders results with an action button that hides the result', () => {
|
||||
const wrapper = shallow(<OrganicDocuments />);
|
||||
const result = wrapper.find(CurationResult).last();
|
||||
result.prop('actions')[0].onClick();
|
||||
|
||||
expect(actions.addHiddenId).toHaveBeenCalledWith('mock-document-3');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { useValues } from 'kea';
|
||||
import { useValues, useActions } from 'kea';
|
||||
|
||||
import { EuiLoadingContent, EuiEmptyPrompt } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -15,11 +15,16 @@ import { i18n } from '@kbn/i18n';
|
|||
import { DataPanel } from '../../../data_panel';
|
||||
import { Result } from '../../../result/types';
|
||||
|
||||
import { RESULT_ACTIONS_DIRECTIONS } from '../../constants';
|
||||
import {
|
||||
RESULT_ACTIONS_DIRECTIONS,
|
||||
PROMOTE_DOCUMENT_ACTION,
|
||||
HIDE_DOCUMENT_ACTION,
|
||||
} from '../../constants';
|
||||
import { CurationLogic } from '../curation_logic';
|
||||
import { CurationResult } from '../results';
|
||||
|
||||
export const OrganicDocuments: React.FC = () => {
|
||||
const { addPromotedId, addHiddenId } = useActions(CurationLogic);
|
||||
const { curation, activeQuery, organicDocumentsLoading } = useValues(CurationLogic);
|
||||
|
||||
const documents = curation.organic;
|
||||
|
@ -48,7 +53,16 @@ export const OrganicDocuments: React.FC = () => {
|
|||
<CurationResult
|
||||
result={document}
|
||||
key={document.id.raw}
|
||||
actions={[]} // TODO: Next Curation PR
|
||||
actions={[
|
||||
{
|
||||
...HIDE_DOCUMENT_ACTION,
|
||||
onClick: () => addHiddenId(document.id.raw),
|
||||
},
|
||||
{
|
||||
...PROMOTE_DOCUMENT_ACTION,
|
||||
onClick: () => addPromotedId(document.id.raw),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
))
|
||||
) : organicDocumentsLoading ? (
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* 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, setMockActions } from '../../../../../__mocks__';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { EuiDragDropContext, EuiDraggable, EuiEmptyPrompt, EuiButtonEmpty } from '@elastic/eui';
|
||||
|
||||
import { DataPanel } from '../../../data_panel';
|
||||
import { CurationResult } from '../results';
|
||||
|
||||
import { PromotedDocuments } from './';
|
||||
|
||||
describe('PromotedDocuments', () => {
|
||||
const values = {
|
||||
curation: {
|
||||
promoted: [
|
||||
{ id: 'mock-document-1' },
|
||||
{ id: 'mock-document-2' },
|
||||
{ id: 'mock-document-3' },
|
||||
{ id: 'mock-document-4' },
|
||||
],
|
||||
},
|
||||
promotedIds: ['mock-document-1', 'mock-document-2', 'mock-document-3', 'mock-document-4'],
|
||||
promotedDocumentsLoading: false,
|
||||
};
|
||||
const actions = {
|
||||
setPromotedIds: jest.fn(),
|
||||
clearPromotedIds: jest.fn(),
|
||||
removePromotedId: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
setMockValues(values);
|
||||
setMockActions(actions);
|
||||
});
|
||||
|
||||
const getDraggableChildren = (draggableWrapper: any) => {
|
||||
return draggableWrapper.renderProp('children')({}, {}, {});
|
||||
};
|
||||
|
||||
it('renders a list of draggable promoted documents', () => {
|
||||
const wrapper = shallow(<PromotedDocuments />);
|
||||
|
||||
expect(wrapper.find(EuiDraggable)).toHaveLength(4);
|
||||
|
||||
wrapper.find(EuiDraggable).forEach((draggableWrapper) => {
|
||||
expect(getDraggableChildren(draggableWrapper).find(CurationResult).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders an empty state & hides the panel actions when empty', () => {
|
||||
setMockValues({ ...values, curation: { promoted: [] } });
|
||||
const wrapper = shallow(<PromotedDocuments />);
|
||||
|
||||
expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1);
|
||||
expect(wrapper.find(DataPanel).prop('action')).toBe(false);
|
||||
});
|
||||
|
||||
it('renders a loading state', () => {
|
||||
setMockValues({ ...values, promotedDocumentsLoading: true });
|
||||
const wrapper = shallow(<PromotedDocuments />);
|
||||
|
||||
expect(wrapper.find(DataPanel).prop('isLoading')).toEqual(true);
|
||||
});
|
||||
|
||||
describe('actions', () => {
|
||||
it('renders results with an action button that demotes the result', () => {
|
||||
const wrapper = shallow(<PromotedDocuments />);
|
||||
const result = getDraggableChildren(wrapper.find(EuiDraggable).last());
|
||||
result.prop('actions')[0].onClick();
|
||||
|
||||
expect(actions.removePromotedId).toHaveBeenCalledWith('mock-document-4');
|
||||
});
|
||||
|
||||
it('renders a demote all button that demotes all hidden results', () => {
|
||||
const wrapper = shallow(<PromotedDocuments />);
|
||||
const panelActions = shallow(wrapper.find(DataPanel).prop('action') as React.ReactElement);
|
||||
|
||||
panelActions.find(EuiButtonEmpty).simulate('click');
|
||||
expect(actions.clearPromotedIds).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('draggging', () => {
|
||||
it('calls setPromotedIds with the reordered list when users are done dragging', () => {
|
||||
const wrapper = shallow(<PromotedDocuments />);
|
||||
wrapper.find(EuiDragDropContext).simulate('dragEnd', {
|
||||
source: { index: 3 },
|
||||
destination: { index: 0 },
|
||||
});
|
||||
|
||||
expect(actions.setPromotedIds).toHaveBeenCalledWith([
|
||||
'mock-document-4',
|
||||
'mock-document-1',
|
||||
'mock-document-2',
|
||||
'mock-document-3',
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not error if source/destination are unavailable on drag end', () => {
|
||||
const wrapper = shallow(<PromotedDocuments />);
|
||||
wrapper.find(EuiDragDropContext).simulate('dragEnd', {});
|
||||
|
||||
expect(actions.setPromotedIds).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* 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 {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiEmptyPrompt,
|
||||
EuiButtonEmpty,
|
||||
EuiDragDropContext,
|
||||
DropResult,
|
||||
EuiDroppable,
|
||||
EuiDraggable,
|
||||
euiDragDropReorder,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { DataPanel } from '../../../data_panel';
|
||||
|
||||
import { DEMOTE_DOCUMENT_ACTION } from '../../constants';
|
||||
import { CurationLogic } from '../curation_logic';
|
||||
import { AddResultButton, CurationResult, convertToResultFormat } from '../results';
|
||||
|
||||
export const PromotedDocuments: React.FC = () => {
|
||||
const { curation, promotedIds, promotedDocumentsLoading } = useValues(CurationLogic);
|
||||
const documents = curation.promoted;
|
||||
const hasDocuments = documents.length > 0;
|
||||
|
||||
const { setPromotedIds, clearPromotedIds, removePromotedId } = useActions(CurationLogic);
|
||||
const reorderPromotedIds = ({ source, destination }: DropResult) => {
|
||||
if (source && destination) {
|
||||
const reorderedIds = euiDragDropReorder(promotedIds, source.index, destination.index);
|
||||
setPromotedIds(reorderedIds);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DataPanel
|
||||
filled
|
||||
iconType="starFilled"
|
||||
title={
|
||||
<h2>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.title',
|
||||
{ defaultMessage: 'Promoted documents' }
|
||||
)}
|
||||
</h2>
|
||||
}
|
||||
subtitle={i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'Promoted results appear before organic results. Documents can be re-ordered.',
|
||||
}
|
||||
)}
|
||||
action={
|
||||
hasDocuments && (
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<AddResultButton />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiButtonEmpty onClick={clearPromotedIds} iconType="menuDown" size="s">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.removeAllButtonLabel',
|
||||
{ defaultMessage: 'Demote all' }
|
||||
)}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)
|
||||
}
|
||||
isLoading={promotedDocumentsLoading}
|
||||
>
|
||||
{hasDocuments ? (
|
||||
<EuiDragDropContext onDragEnd={reorderPromotedIds}>
|
||||
<EuiDroppable droppableId="PromotedDocuments" spacing="m">
|
||||
{documents.map((document, i: number) => (
|
||||
<EuiDraggable
|
||||
index={i}
|
||||
key={document.id}
|
||||
draggableId={document.id}
|
||||
customDragHandle
|
||||
spacing="none"
|
||||
>
|
||||
{(provided) => (
|
||||
<CurationResult
|
||||
key={document.id}
|
||||
result={convertToResultFormat(document)}
|
||||
actions={[
|
||||
{
|
||||
...DEMOTE_DOCUMENT_ACTION,
|
||||
onClick: () => removePromotedId(document.id),
|
||||
},
|
||||
]}
|
||||
dragHandleProps={provided.dragHandleProps}
|
||||
/>
|
||||
)}
|
||||
</EuiDraggable>
|
||||
))}
|
||||
</EuiDroppable>
|
||||
</EuiDragDropContext>
|
||||
) : (
|
||||
<EuiEmptyPrompt
|
||||
body={i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.emptyDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Star documents from the organic results below, or search and promote a result manually.',
|
||||
}
|
||||
)}
|
||||
actions={<AddResultButton />}
|
||||
/>
|
||||
)}
|
||||
</DataPanel>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 { shallow, ShallowWrapper } from 'enzyme';
|
||||
|
||||
import { EuiButton } from '@elastic/eui';
|
||||
|
||||
import { AddResultButton } from './';
|
||||
|
||||
describe('AddResultButton', () => {
|
||||
let wrapper: ShallowWrapper;
|
||||
|
||||
beforeAll(() => {
|
||||
wrapper = shallow(<AddResultButton />);
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
expect(wrapper.find(EuiButton)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('opens the add result flyout on click', () => {
|
||||
wrapper.find(EuiButton).simulate('click');
|
||||
// TODO: assert on logic action
|
||||
});
|
||||
});
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 { EuiButton } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const AddResultButton: React.FC = () => {
|
||||
return (
|
||||
<EuiButton onClick={() => {} /* TODO */} iconType="plusInCircle" size="s" fill>
|
||||
{i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.addResult.buttonLabel', {
|
||||
defaultMessage: 'Add result manually',
|
||||
})}
|
||||
</EuiButton>
|
||||
);
|
||||
};
|
|
@ -5,4 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export { AddResultButton } from './add_result_button';
|
||||
export { CurationResult } from './curation_result';
|
||||
export { convertToResultFormat } from './utils';
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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 { convertToResultFormat, convertIdToMeta } from './utils';
|
||||
|
||||
describe('convertToResultFormat', () => {
|
||||
it('converts curation results to a format that the Result component can use', () => {
|
||||
expect(
|
||||
convertToResultFormat({
|
||||
id: 'some-id',
|
||||
someField: 'some flat string',
|
||||
anotherField: '123456',
|
||||
})
|
||||
).toEqual({
|
||||
_meta: {
|
||||
id: 'some-id',
|
||||
},
|
||||
id: {
|
||||
raw: 'some-id',
|
||||
snippet: null,
|
||||
},
|
||||
someField: {
|
||||
raw: 'some flat string',
|
||||
snippet: null,
|
||||
},
|
||||
anotherField: {
|
||||
raw: '123456',
|
||||
snippet: null,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertIdToMeta', () => {
|
||||
it('creates an approximate _meta object based on the curation result ID', () => {
|
||||
expect(convertIdToMeta('some-id')).toEqual({ id: 'some-id' });
|
||||
expect(convertIdToMeta('some-engine|some-id')).toEqual({
|
||||
id: 'some-id',
|
||||
engine: 'some-engine',
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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 { Result } from '../../../result/types';
|
||||
import { CurationResult } from '../../types';
|
||||
|
||||
/**
|
||||
* The `promoted` and `hidden` keys from the internal curations endpoints
|
||||
* currently return a document data structure that our Result component can't
|
||||
* correctly parse - we need to attempt to naively transform the data in order
|
||||
* to display it in a Result
|
||||
*
|
||||
* TODO: Ideally someday we can update our internal curations endpoint to return
|
||||
* the same Result-ready data structure that the `organic` endpoint uses, and
|
||||
* remove this file when that happens
|
||||
*/
|
||||
|
||||
export const convertToResultFormat = (document: CurationResult): Result => {
|
||||
const result = {} as Result;
|
||||
|
||||
// Convert `key: 'value'` into `key: { raw: 'value' }`
|
||||
Object.entries(document).forEach(([key, value]) => {
|
||||
result[key] = {
|
||||
raw: value,
|
||||
snippet: null, // Don't try to provide a snippet, we can't really guesstimate it
|
||||
};
|
||||
});
|
||||
|
||||
// Add the _meta obj needed by Result
|
||||
result._meta = convertIdToMeta(document.id);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const convertIdToMeta = (id: string): Result['_meta'] => {
|
||||
const splitId = id.split('|');
|
||||
const isMetaEngine = splitId.length > 1;
|
||||
|
||||
return isMetaEngine
|
||||
? {
|
||||
engine: splitId[0],
|
||||
id: splitId[1],
|
||||
}
|
||||
: ({ id } as Result['_meta']);
|
||||
// Note: We're casting this as _meta even though `engine` is missing,
|
||||
// since for source engines the engine shouldn't matter / be displayed,
|
||||
// but if needed we could likely populate this from EngineLogic.values
|
||||
};
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { convertToDate } from './utils';
|
||||
import { convertToDate, addDocument, removeDocument } from './utils';
|
||||
|
||||
describe('convertToDate', () => {
|
||||
it('converts the English-only server timestamps to a parseable Date', () => {
|
||||
|
@ -16,3 +16,23 @@ describe('convertToDate', () => {
|
|||
expect(date.getFullYear()).toEqual(1970);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addDocument', () => {
|
||||
it('adds a new document to the end of the document array without mutating the original array', () => {
|
||||
const originalDocuments = ['hello'];
|
||||
const newDocuments = addDocument(originalDocuments, 'world');
|
||||
|
||||
expect(newDocuments).toEqual(['hello', 'world']);
|
||||
expect(newDocuments).not.toBe(originalDocuments); // Would fail if we had mutated the array
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeDocument', () => {
|
||||
it('removes a specific document from the array without mutating the original array', () => {
|
||||
const originalDocuments = ['lorem', 'ipsum', 'dolor', 'sit', 'amet'];
|
||||
const newDocuments = removeDocument(originalDocuments, 'dolor');
|
||||
|
||||
expect(newDocuments).toEqual(['lorem', 'ipsum', 'sit', 'amet']);
|
||||
expect(newDocuments).not.toBe(originalDocuments); // Would fail if we had mutated the array
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,3 +14,14 @@ export const convertToDate = (serverDateString: string): Date => {
|
|||
.replace('AM', ' AM');
|
||||
return new Date(readableDateString);
|
||||
};
|
||||
|
||||
export const addDocument = (documentArray: string[], newDocument: string) => {
|
||||
return [...documentArray, newDocument];
|
||||
};
|
||||
|
||||
export const removeDocument = (documentArray: string[], deletedDocument: string) => {
|
||||
const newArray = [...documentArray];
|
||||
const indexToDelete = newArray.indexOf(deletedDocument);
|
||||
newArray.splice(indexToDelete, 1);
|
||||
return newArray;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue