Co-authored-by: Byron Hulcher <byronhulcher@gmail.com>
This commit is contained in:
parent
654b7310d9
commit
a3e390f924
|
@ -68,7 +68,7 @@ const columns: Array<EuiBasicTableColumn<CurationSuggestion>> = [
|
|||
field: 'promoted',
|
||||
name: i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.curations.suggestionsTable.column.promotedDocumentsTableHeader',
|
||||
{ defaultMessage: 'Promoted documents' }
|
||||
{ defaultMessage: 'Promoted results' }
|
||||
),
|
||||
render: (promoted: string[]) => <span>{promoted.length}</span>,
|
||||
},
|
||||
|
|
|
@ -34,11 +34,11 @@ export const QUERY_INPUTS_PLACEHOLDER = i18n.translate(
|
|||
{ defaultMessage: 'Enter a query' }
|
||||
);
|
||||
|
||||
export const DELETE_MESSAGE = i18n.translate(
|
||||
export const DELETE_CONFIRMATION_MESSAGE = i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.curations.deleteConfirmation',
|
||||
{ defaultMessage: 'Are you sure you want to remove this curation?' }
|
||||
);
|
||||
export const SUCCESS_MESSAGE = i18n.translate(
|
||||
export const DELETE_SUCCESS_MESSAGE = i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.curations.deleteSuccessMessage',
|
||||
{ defaultMessage: 'Your curation was deleted' }
|
||||
);
|
||||
|
|
|
@ -14,7 +14,7 @@ import React from 'react';
|
|||
|
||||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
|
||||
import { EuiBadge, EuiTab } from '@elastic/eui';
|
||||
import { EuiBadge, EuiButton, EuiLoadingSpinner, EuiTab } from '@elastic/eui';
|
||||
|
||||
import { getPageHeaderActions, getPageHeaderTabs, getPageTitle } from '../../../../test_helpers';
|
||||
|
||||
|
@ -25,6 +25,7 @@ import { AppSearchPageTemplate } from '../../layout';
|
|||
import { AutomatedCuration } from './automated_curation';
|
||||
import { CurationLogic } from './curation_logic';
|
||||
|
||||
import { DeleteCurationButton } from './delete_curation_button';
|
||||
import { PromotedDocuments, OrganicDocuments } from './documents';
|
||||
|
||||
describe('AutomatedCuration', () => {
|
||||
|
@ -33,6 +34,8 @@ describe('AutomatedCuration', () => {
|
|||
queries: ['query A', 'query B'],
|
||||
isFlyoutOpen: false,
|
||||
curation: {
|
||||
promoted: [],
|
||||
hidden: [],
|
||||
suggestion: {
|
||||
status: 'applied',
|
||||
},
|
||||
|
@ -89,13 +92,29 @@ describe('AutomatedCuration', () => {
|
|||
expect(pageTitle.find(EuiBadge)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('displays a spinner in the title when loading', () => {
|
||||
setMockValues({ ...values, dataLoading: true });
|
||||
|
||||
const wrapper = shallow(<AutomatedCuration />);
|
||||
const pageTitle = shallow(<div>{getPageTitle(wrapper)}</div>);
|
||||
|
||||
expect(pageTitle.find(EuiLoadingSpinner)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('contains a button to delete the curation', () => {
|
||||
const wrapper = shallow(<AutomatedCuration />);
|
||||
const pageHeaderActions = getPageHeaderActions(wrapper);
|
||||
|
||||
expect(pageHeaderActions.find(DeleteCurationButton)).toHaveLength(1);
|
||||
});
|
||||
|
||||
describe('convert to manual button', () => {
|
||||
let convertToManualButton: ShallowWrapper;
|
||||
let confirmSpy: jest.SpyInstance;
|
||||
|
||||
beforeAll(() => {
|
||||
const wrapper = shallow(<AutomatedCuration />);
|
||||
convertToManualButton = getPageHeaderActions(wrapper).childAt(0);
|
||||
convertToManualButton = getPageHeaderActions(wrapper).find(EuiButton);
|
||||
|
||||
confirmSpy = jest.spyOn(window, 'confirm');
|
||||
});
|
||||
|
@ -107,12 +126,14 @@ describe('AutomatedCuration', () => {
|
|||
it('converts the curation upon user confirmation', () => {
|
||||
confirmSpy.mockReturnValueOnce(true);
|
||||
convertToManualButton.simulate('click');
|
||||
|
||||
expect(actions.convertToManual).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not convert the curation if the user cancels', () => {
|
||||
confirmSpy.mockReturnValueOnce(false);
|
||||
convertToManualButton.simulate('click');
|
||||
|
||||
expect(actions.convertToManual).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,7 +10,7 @@ import { useParams } from 'react-router-dom';
|
|||
|
||||
import { useValues, useActions } from 'kea';
|
||||
|
||||
import { EuiSpacer, EuiButton, EuiBadge } from '@elastic/eui';
|
||||
import { EuiButton, EuiBadge, EuiLoadingSpinner, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
|
||||
import { AppSearchPageTemplate } from '../../layout';
|
||||
import { AutomatedIcon } from '../components/automated_icon';
|
||||
|
@ -24,22 +24,25 @@ import { getCurationsBreadcrumbs } from '../utils';
|
|||
|
||||
import { HIDDEN_DOCUMENTS_TITLE, PROMOTED_DOCUMENTS_TITLE } from './constants';
|
||||
import { CurationLogic } from './curation_logic';
|
||||
import { DeleteCurationButton } from './delete_curation_button';
|
||||
import { PromotedDocuments, OrganicDocuments } from './documents';
|
||||
|
||||
export const AutomatedCuration: React.FC = () => {
|
||||
const { curationId } = useParams<{ curationId: string }>();
|
||||
const logic = CurationLogic({ curationId });
|
||||
const { convertToManual } = useActions(logic);
|
||||
const { activeQuery, dataLoading, queries } = useValues(logic);
|
||||
const { activeQuery, dataLoading, queries, curation } = useValues(logic);
|
||||
|
||||
// This tab group is meant to visually mirror the dynamic group of tags in the ManualCuration component
|
||||
const pageTabs = [
|
||||
{
|
||||
label: PROMOTED_DOCUMENTS_TITLE,
|
||||
append: <EuiBadge>{curation.promoted.length}</EuiBadge>,
|
||||
isSelected: true,
|
||||
},
|
||||
{
|
||||
label: HIDDEN_DOCUMENTS_TITLE,
|
||||
append: <EuiBadge isDisabled>0</EuiBadge>,
|
||||
isSelected: false,
|
||||
disabled: true,
|
||||
},
|
||||
|
@ -51,30 +54,36 @@ export const AutomatedCuration: React.FC = () => {
|
|||
pageHeader={{
|
||||
pageTitle: (
|
||||
<>
|
||||
{activeQuery}{' '}
|
||||
{dataLoading ? <EuiLoadingSpinner size="l" /> : activeQuery}{' '}
|
||||
<EuiBadge iconType={AutomatedIcon} color="accent">
|
||||
{AUTOMATED_LABEL}
|
||||
</EuiBadge>
|
||||
</>
|
||||
),
|
||||
rightSideItems: [
|
||||
<EuiButton
|
||||
color="primary"
|
||||
fill
|
||||
iconType="exportAction"
|
||||
onClick={() => {
|
||||
if (window.confirm(CONVERT_TO_MANUAL_CONFIRMATION)) convertToManual();
|
||||
}}
|
||||
>
|
||||
{COVERT_TO_MANUAL_BUTTON_LABEL}
|
||||
</EuiButton>,
|
||||
<EuiFlexGroup gutterSize="s" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<DeleteCurationButton />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
fill
|
||||
iconType="exportAction"
|
||||
onClick={() => {
|
||||
if (window.confirm(CONVERT_TO_MANUAL_CONFIRMATION)) convertToManual();
|
||||
}}
|
||||
>
|
||||
{COVERT_TO_MANUAL_BUTTON_LABEL}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>,
|
||||
],
|
||||
tabs: pageTabs,
|
||||
}}
|
||||
isLoading={dataLoading}
|
||||
>
|
||||
<PromotedDocuments />
|
||||
<EuiSpacer />
|
||||
<OrganicDocuments />
|
||||
</AppSearchPageTemplate>
|
||||
);
|
||||
|
|
|
@ -9,10 +9,10 @@ import { i18n } from '@kbn/i18n';
|
|||
|
||||
export const PROMOTED_DOCUMENTS_TITLE = i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.title',
|
||||
{ defaultMessage: 'Promoted documents' }
|
||||
{ defaultMessage: 'Promoted results' }
|
||||
);
|
||||
|
||||
export const HIDDEN_DOCUMENTS_TITLE = i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.title',
|
||||
{ defaultMessage: 'Hidden documents' }
|
||||
{ defaultMessage: 'Hidden results' }
|
||||
);
|
||||
|
|
|
@ -21,7 +21,7 @@ describe('CurationLogic', () => {
|
|||
const { mount } = new LogicMounter(CurationLogic);
|
||||
const { http } = mockHttpValues;
|
||||
const { navigateToUrl } = mockKibanaValues;
|
||||
const { clearFlashMessages, flashAPIErrors } = mockFlashMessageHelpers;
|
||||
const { clearFlashMessages, flashAPIErrors, flashSuccessToast } = mockFlashMessageHelpers;
|
||||
|
||||
const MOCK_CURATION_RESPONSE = {
|
||||
id: 'cur-123456789',
|
||||
|
@ -249,23 +249,6 @@ describe('CurationLogic', () => {
|
|||
});
|
||||
});
|
||||
|
||||
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('onSelectPageTab', () => {
|
||||
it('should set the selected page tab', () => {
|
||||
mount({
|
||||
|
@ -336,6 +319,33 @@ describe('CurationLogic', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('deleteCuration', () => {
|
||||
it('should make an API call and navigate to the curations page', async () => {
|
||||
http.delete.mockReturnValueOnce(Promise.resolve());
|
||||
mount({}, { curationId: 'cur-123456789' });
|
||||
jest.spyOn(CurationLogic.actions, 'onCurationLoad');
|
||||
|
||||
CurationLogic.actions.deleteCuration();
|
||||
await nextTick();
|
||||
|
||||
expect(http.delete).toHaveBeenCalledWith(
|
||||
'/internal/app_search/engines/some-engine/curations/cur-123456789'
|
||||
);
|
||||
expect(flashSuccessToast).toHaveBeenCalled();
|
||||
expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/curations');
|
||||
});
|
||||
|
||||
it('flashes any errors', async () => {
|
||||
http.delete.mockReturnValueOnce(Promise.reject('error'));
|
||||
mount({}, { curationId: 'cur-404' });
|
||||
|
||||
CurationLogic.actions.deleteCuration();
|
||||
await nextTick();
|
||||
|
||||
expect(flashAPIErrors).toHaveBeenCalledWith('error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadCuration', () => {
|
||||
it('should set dataLoading state', () => {
|
||||
mount({ dataLoading: false }, { curationId: 'cur-123456789' });
|
||||
|
|
|
@ -7,11 +7,16 @@
|
|||
|
||||
import { kea, MakeLogicType } from 'kea';
|
||||
|
||||
import { clearFlashMessages, flashAPIErrors } from '../../../../shared/flash_messages';
|
||||
import {
|
||||
clearFlashMessages,
|
||||
flashAPIErrors,
|
||||
flashSuccessToast,
|
||||
} from '../../../../shared/flash_messages';
|
||||
import { HttpLogic } from '../../../../shared/http';
|
||||
import { KibanaLogic } from '../../../../shared/kibana';
|
||||
import { ENGINE_CURATIONS_PATH } from '../../../routes';
|
||||
import { EngineLogic, generateEnginePath } from '../../engine';
|
||||
import { DELETE_SUCCESS_MESSAGE } from '../constants';
|
||||
|
||||
import { Curation } from '../types';
|
||||
import { addDocument, removeDocument } from '../utils';
|
||||
|
@ -35,6 +40,7 @@ interface CurationValues {
|
|||
|
||||
interface CurationActions {
|
||||
convertToManual(): void;
|
||||
deleteCuration(): void;
|
||||
loadCuration(): void;
|
||||
onCurationLoad(curation: Curation): { curation: Curation };
|
||||
updateCuration(): void;
|
||||
|
@ -48,7 +54,6 @@ interface CurationActions {
|
|||
addHiddenId(id: string): { id: string };
|
||||
removeHiddenId(id: string): { id: string };
|
||||
clearHiddenIds(): void;
|
||||
resetCuration(): void;
|
||||
onSelectPageTab(pageTab: CurationPageTabs): { pageTab: CurationPageTabs };
|
||||
}
|
||||
|
||||
|
@ -60,6 +65,7 @@ export const CurationLogic = kea<MakeLogicType<CurationValues, CurationActions,
|
|||
path: ['enterprise_search', 'app_search', 'curation_logic'],
|
||||
actions: () => ({
|
||||
convertToManual: true,
|
||||
deleteCuration: true,
|
||||
loadCuration: true,
|
||||
onCurationLoad: (curation) => ({ curation }),
|
||||
updateCuration: true,
|
||||
|
@ -73,7 +79,6 @@ export const CurationLogic = kea<MakeLogicType<CurationValues, CurationActions,
|
|||
addHiddenId: (id) => ({ id }),
|
||||
removeHiddenId: (id) => ({ id }),
|
||||
clearHiddenIds: true,
|
||||
resetCuration: true,
|
||||
onSelectPageTab: (pageTab) => ({ pageTab }),
|
||||
}),
|
||||
reducers: () => ({
|
||||
|
@ -81,7 +86,6 @@ export const CurationLogic = kea<MakeLogicType<CurationValues, CurationActions,
|
|||
true,
|
||||
{
|
||||
loadCuration: () => true,
|
||||
resetCuration: () => true,
|
||||
onCurationLoad: () => false,
|
||||
onCurationError: () => false,
|
||||
},
|
||||
|
@ -204,6 +208,21 @@ export const CurationLogic = kea<MakeLogicType<CurationValues, CurationActions,
|
|||
flashAPIErrors(e);
|
||||
}
|
||||
},
|
||||
deleteCuration: async () => {
|
||||
const { http } = HttpLogic.values;
|
||||
const { engineName } = EngineLogic.values;
|
||||
const { navigateToUrl } = KibanaLogic.values;
|
||||
|
||||
try {
|
||||
await http.delete(
|
||||
`/internal/app_search/engines/${engineName}/curations/${props.curationId}`
|
||||
);
|
||||
navigateToUrl(generateEnginePath(ENGINE_CURATIONS_PATH));
|
||||
flashSuccessToast(DELETE_SUCCESS_MESSAGE);
|
||||
} catch (e) {
|
||||
flashAPIErrors(e);
|
||||
}
|
||||
},
|
||||
loadCuration: async () => {
|
||||
const { http } = HttpLogic.values;
|
||||
const { engineName } = EngineLogic.values;
|
||||
|
@ -260,9 +279,5 @@ export const CurationLogic = kea<MakeLogicType<CurationValues, CurationActions,
|
|||
addHiddenId: () => actions.updateCuration(),
|
||||
removeHiddenId: () => actions.updateCuration(),
|
||||
clearHiddenIds: () => actions.updateCuration(),
|
||||
resetCuration: () => {
|
||||
actions.clearPromotedIds();
|
||||
actions.clearHiddenIds();
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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__/shallow_useeffect.mock';
|
||||
import { setMockActions } 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 { EuiButton } from '@elastic/eui';
|
||||
|
||||
jest.mock('./curation_logic', () => ({ CurationLogic: jest.fn() }));
|
||||
|
||||
import { DeleteCurationButton } from './delete_curation_button';
|
||||
|
||||
describe('DeleteCurationButton', () => {
|
||||
const actions = {
|
||||
deleteCuration: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
setMockActions(actions);
|
||||
mockUseParams.mockReturnValueOnce({ curationId: 'hello-world' });
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
const wrapper = shallow(<DeleteCurationButton />);
|
||||
|
||||
expect(wrapper.is(EuiButton)).toBe(true);
|
||||
});
|
||||
|
||||
describe('restore defaults button', () => {
|
||||
let wrapper: ShallowWrapper;
|
||||
let confirmSpy: jest.SpyInstance;
|
||||
|
||||
beforeAll(() => {
|
||||
wrapper = shallow(<DeleteCurationButton />);
|
||||
confirmSpy = jest.spyOn(window, 'confirm');
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
confirmSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('resets the curation upon user confirmation', () => {
|
||||
confirmSpy.mockReturnValueOnce(true);
|
||||
wrapper.simulate('click');
|
||||
expect(actions.deleteCuration).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not reset the curation if the user cancels', () => {
|
||||
confirmSpy.mockReturnValueOnce(false);
|
||||
wrapper.simulate('click');
|
||||
expect(actions.deleteCuration).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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 { useParams } from 'react-router-dom';
|
||||
|
||||
import { useActions } from 'kea';
|
||||
|
||||
import { EuiButton } from '@elastic/eui';
|
||||
|
||||
import { DELETE_BUTTON_LABEL } from '../../../../shared/constants';
|
||||
import { DELETE_CONFIRMATION_MESSAGE } from '../constants';
|
||||
|
||||
import { CurationLogic } from '.';
|
||||
|
||||
export const DeleteCurationButton: React.FC = () => {
|
||||
const { curationId } = useParams() as { curationId: string };
|
||||
const { deleteCuration } = useActions(CurationLogic({ curationId }));
|
||||
|
||||
return (
|
||||
<EuiButton
|
||||
color="danger"
|
||||
iconType="trash"
|
||||
onClick={() => {
|
||||
if (window.confirm(DELETE_CONFIRMATION_MESSAGE)) deleteCuration();
|
||||
}}
|
||||
>
|
||||
{DELETE_BUTTON_LABEL}
|
||||
</EuiButton>
|
||||
);
|
||||
};
|
|
@ -11,7 +11,7 @@ import React from 'react';
|
|||
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { EuiEmptyPrompt, EuiButtonEmpty } from '@elastic/eui';
|
||||
import { EuiEmptyPrompt, EuiButtonEmpty, EuiBadge } from '@elastic/eui';
|
||||
|
||||
import { DataPanel } from '../../../data_panel';
|
||||
import { CurationResult } from '../results';
|
||||
|
@ -48,6 +48,14 @@ describe('HiddenDocuments', () => {
|
|||
expect(wrapper.find(CurationResult)).toHaveLength(5);
|
||||
});
|
||||
|
||||
it('displays the number of documents in a badge', () => {
|
||||
const wrapper = shallow(<HiddenDocuments />);
|
||||
const Icon = wrapper.prop('iconType');
|
||||
const iconWrapper = shallow(<Icon />);
|
||||
|
||||
expect(iconWrapper.find(EuiBadge).prop('children')).toEqual(5);
|
||||
});
|
||||
|
||||
it('renders an empty state & hides the panel actions when empty', () => {
|
||||
setMockValues({ ...values, curation: { hidden: [] } });
|
||||
const wrapper = shallow(<HiddenDocuments />);
|
||||
|
|
|
@ -9,7 +9,7 @@ import React from 'react';
|
|||
|
||||
import { useValues, useActions } from 'kea';
|
||||
|
||||
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiEmptyPrompt } from '@elastic/eui';
|
||||
import { EuiBadge, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiEmptyPrompt } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { DataPanel } from '../../../data_panel';
|
||||
|
@ -26,39 +26,37 @@ export const HiddenDocuments: React.FC = () => {
|
|||
const documents = curation.hidden;
|
||||
const hasDocuments = documents.length > 0;
|
||||
|
||||
const CountBadge: React.FC = () => <EuiBadge color="accent">{documents.length}</EuiBadge>;
|
||||
|
||||
return (
|
||||
<DataPanel
|
||||
filled
|
||||
iconType="eyeClosed"
|
||||
iconType={CountBadge}
|
||||
title={<h2>{HIDDEN_DOCUMENTS_TITLE}</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" responsive={false} wrap>
|
||||
<EuiFlexItem>
|
||||
<AddResultButton />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiButtonEmpty onClick={clearHiddenIds} iconType="menuUp" size="s">
|
||||
<EuiButtonEmpty onClick={clearHiddenIds} size="s">
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.removeAllButtonLabel',
|
||||
{ defaultMessage: 'Restore all' }
|
||||
{ defaultMessage: 'Unhide all' }
|
||||
)}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<AddResultButton />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)
|
||||
}
|
||||
isLoading={hiddenDocumentsLoading}
|
||||
>
|
||||
{hasDocuments ? (
|
||||
documents.map((document) => (
|
||||
documents.map((document, index) => (
|
||||
<CurationResult
|
||||
key={document.id}
|
||||
result={convertToResultFormat(document)}
|
||||
index={index}
|
||||
actions={[
|
||||
{
|
||||
...SHOW_DOCUMENT_ACTION,
|
||||
|
|
|
@ -59,13 +59,6 @@ describe('OrganicDocuments', () => {
|
|||
expect(titleText).toEqual('Top organic documents for "world"');
|
||||
});
|
||||
|
||||
it('shows a title when the curation is manual', () => {
|
||||
setMockValues({ ...values, isAutomated: false });
|
||||
const wrapper = shallow(<OrganicDocuments />);
|
||||
|
||||
expect(wrapper.find(DataPanel).prop('subtitle')).toContain('Promote results');
|
||||
});
|
||||
|
||||
it('renders a loading state', () => {
|
||||
setMockValues({ ...values, organicDocumentsLoading: true });
|
||||
const wrapper = shallow(<OrganicDocuments />);
|
||||
|
@ -74,13 +67,20 @@ describe('OrganicDocuments', () => {
|
|||
});
|
||||
|
||||
describe('empty state', () => {
|
||||
it('renders', () => {
|
||||
it('renders when organic results is empty', () => {
|
||||
setMockValues({ ...values, curation: { organic: [] } });
|
||||
const wrapper = shallow(<OrganicDocuments />);
|
||||
|
||||
expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders when organic results is undefined', () => {
|
||||
setMockValues({ ...values, curation: { organic: undefined } });
|
||||
const wrapper = shallow(<OrganicDocuments />);
|
||||
|
||||
expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('tells the user to modify the query if the curation is manual', () => {
|
||||
setMockValues({ ...values, curation: { organic: [] }, isAutomated: false });
|
||||
const wrapper = shallow(<OrganicDocuments />);
|
||||
|
|
|
@ -13,14 +13,12 @@ import { EuiLoadingContent, EuiEmptyPrompt } from '@elastic/eui';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { LeafIcon } from '../../../../../shared/icons';
|
||||
|
||||
import { DataPanel } from '../../../data_panel';
|
||||
import { Result } from '../../../result/types';
|
||||
|
||||
import {
|
||||
RESULT_ACTIONS_DIRECTIONS,
|
||||
PROMOTE_DOCUMENT_ACTION,
|
||||
HIDE_DOCUMENT_ACTION,
|
||||
} from '../../constants';
|
||||
import { PROMOTE_DOCUMENT_ACTION, HIDE_DOCUMENT_ACTION } from '../../constants';
|
||||
import { CurationLogic } from '../curation_logic';
|
||||
import { CurationResult } from '../results';
|
||||
|
||||
|
@ -28,14 +26,13 @@ export const OrganicDocuments: React.FC = () => {
|
|||
const { addPromotedId, addHiddenId } = useActions(CurationLogic);
|
||||
const { curation, activeQuery, isAutomated, organicDocumentsLoading } = useValues(CurationLogic);
|
||||
|
||||
const documents = curation.organic;
|
||||
const documents = curation.organic || [];
|
||||
const hasDocuments = documents.length > 0 && !organicDocumentsLoading;
|
||||
const currentQuery = activeQuery;
|
||||
|
||||
return (
|
||||
<DataPanel
|
||||
filled
|
||||
iconType="search"
|
||||
iconType={LeafIcon}
|
||||
title={
|
||||
<h2>
|
||||
{i18n.translate(
|
||||
|
@ -47,12 +44,12 @@ export const OrganicDocuments: React.FC = () => {
|
|||
)}
|
||||
</h2>
|
||||
}
|
||||
subtitle={!isAutomated && RESULT_ACTIONS_DIRECTIONS}
|
||||
>
|
||||
{hasDocuments ? (
|
||||
documents.map((document: Result) => (
|
||||
documents.map((document: Result, index) => (
|
||||
<CurationResult
|
||||
result={document}
|
||||
index={index}
|
||||
key={document.id.raw}
|
||||
actions={
|
||||
isAutomated
|
||||
|
|
|
@ -10,7 +10,14 @@ import React from 'react';
|
|||
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { EuiDragDropContext, EuiDraggable, EuiEmptyPrompt, EuiButtonEmpty } from '@elastic/eui';
|
||||
import {
|
||||
EuiDragDropContext,
|
||||
EuiDraggable,
|
||||
EuiEmptyPrompt,
|
||||
EuiButtonEmpty,
|
||||
EuiBadge,
|
||||
EuiTextColor,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { mountWithIntl } from '../../../../../test_helpers';
|
||||
import { DataPanel } from '../../../data_panel';
|
||||
|
@ -47,6 +54,14 @@ describe('PromotedDocuments', () => {
|
|||
return draggableWrapper.renderProp('children')({}, {}, {});
|
||||
};
|
||||
|
||||
it('displays the number of documents in a badge', () => {
|
||||
const wrapper = shallow(<PromotedDocuments />);
|
||||
const Icon = wrapper.prop('iconType');
|
||||
const iconWrapper = shallow(<Icon />);
|
||||
|
||||
expect(iconWrapper.find(EuiBadge).prop('children')).toEqual(4);
|
||||
});
|
||||
|
||||
it('renders a list of draggable promoted documents', () => {
|
||||
const wrapper = shallow(<PromotedDocuments />);
|
||||
|
||||
|
@ -57,22 +72,6 @@ describe('PromotedDocuments', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('informs the user documents can be re-ordered if the curation is manual', () => {
|
||||
setMockValues({ ...values, isAutomated: false });
|
||||
const wrapper = shallow(<PromotedDocuments />);
|
||||
const subtitle = mountWithIntl(wrapper.prop('subtitle'));
|
||||
|
||||
expect(subtitle.text()).toContain('Documents can be re-ordered');
|
||||
});
|
||||
|
||||
it('informs the user the curation is managed if the curation is automated', () => {
|
||||
setMockValues({ ...values, isAutomated: true });
|
||||
const wrapper = shallow(<PromotedDocuments />);
|
||||
const subtitle = mountWithIntl(wrapper.prop('subtitle'));
|
||||
|
||||
expect(subtitle.text()).toContain('managed by App Search');
|
||||
});
|
||||
|
||||
describe('empty state', () => {
|
||||
it('renders', () => {
|
||||
setMockValues({ ...values, curation: { promoted: [] } });
|
||||
|
@ -90,18 +89,12 @@ describe('PromotedDocuments', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('hides the panel actions when empty', () => {
|
||||
setMockValues({ ...values, curation: { promoted: [] } });
|
||||
const wrapper = shallow(<PromotedDocuments />);
|
||||
|
||||
expect(wrapper.find(DataPanel).prop('action')).toBe(false);
|
||||
});
|
||||
|
||||
it('hides the panel actions when the curation is automated', () => {
|
||||
it('shows a message when the curation is automated', () => {
|
||||
setMockValues({ ...values, isAutomated: true });
|
||||
const wrapper = shallow(<PromotedDocuments />);
|
||||
const panelAction = shallow(wrapper.find(DataPanel).prop('action') as React.ReactElement);
|
||||
|
||||
expect(wrapper.find(DataPanel).prop('action')).toBe(false);
|
||||
expect(panelAction.find(EuiTextColor)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders a loading state', () => {
|
||||
|
@ -136,6 +129,13 @@ describe('PromotedDocuments', () => {
|
|||
expect(actions.clearPromotedIds).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('hides the demote all button when there are on promoted results', () => {
|
||||
setMockValues({ ...values, curation: { promoted: [] } });
|
||||
const wrapper = shallow(<PromotedDocuments />);
|
||||
|
||||
expect(wrapper.find(DataPanel).prop('action')).toEqual(false);
|
||||
});
|
||||
|
||||
describe('dragging', () => {
|
||||
it('calls setPromotedIds with the reordered list when users are done dragging', () => {
|
||||
const wrapper = shallow(<PromotedDocuments />);
|
||||
|
|
|
@ -19,9 +19,10 @@ import {
|
|||
EuiDroppable,
|
||||
EuiDraggable,
|
||||
euiDragDropReorder,
|
||||
EuiBadge,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { DataPanel } from '../../../data_panel';
|
||||
|
||||
|
@ -43,45 +44,45 @@ export const PromotedDocuments: React.FC = () => {
|
|||
}
|
||||
};
|
||||
|
||||
const CountBadge: React.FC = () => <EuiBadge color="accent">{documents.length}</EuiBadge>;
|
||||
|
||||
return (
|
||||
<DataPanel
|
||||
filled
|
||||
iconType="starFilled"
|
||||
iconType={CountBadge}
|
||||
title={<h2>{PROMOTED_DOCUMENTS_TITLE}</h2>}
|
||||
subtitle={
|
||||
isAutomated ? (
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.automatedDescription"
|
||||
defaultMessage="This curation is being managed by App Search"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.manualDescription"
|
||||
defaultMessage="Promoted results appear before organic results. Documents can be re-ordered."
|
||||
/>
|
||||
)
|
||||
}
|
||||
action={
|
||||
!isAutomated &&
|
||||
hasDocuments && (
|
||||
<EuiFlexGroup gutterSize="s" responsive={false} wrap>
|
||||
<EuiFlexItem>
|
||||
<AddResultButton />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiButtonEmpty
|
||||
onClick={clearPromotedIds}
|
||||
iconType="menuDown"
|
||||
size="s"
|
||||
disabled={isAutomated}
|
||||
>
|
||||
isAutomated ? (
|
||||
<EuiText color="subdued" size="s">
|
||||
<p>
|
||||
<em>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.removeAllButtonLabel',
|
||||
{ defaultMessage: 'Demote all' }
|
||||
'xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.managedByAppSearchDescription',
|
||||
{ defaultMessage: 'This curation is being automated by App Search' }
|
||||
)}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</em>
|
||||
</p>
|
||||
</EuiText>
|
||||
) : (
|
||||
hasDocuments && (
|
||||
<EuiFlexGroup gutterSize="s" responsive={false} wrap>
|
||||
<EuiFlexItem>
|
||||
<EuiButtonEmpty
|
||||
onClick={clearPromotedIds}
|
||||
color="danger"
|
||||
size="s"
|
||||
disabled={isAutomated}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.removeAllButtonLabel',
|
||||
{ defaultMessage: 'Demote all' }
|
||||
)}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<AddResultButton />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)
|
||||
)
|
||||
}
|
||||
isLoading={promotedDocumentsLoading}
|
||||
|
@ -89,9 +90,9 @@ export const PromotedDocuments: React.FC = () => {
|
|||
{hasDocuments ? (
|
||||
<EuiDragDropContext onDragEnd={reorderPromotedIds}>
|
||||
<EuiDroppable droppableId="PromotedDocuments" spacing="m">
|
||||
{documents.map((document, i: number) => (
|
||||
{documents.map((document, index) => (
|
||||
<EuiDraggable
|
||||
index={i}
|
||||
index={index}
|
||||
key={document.id}
|
||||
draggableId={document.id}
|
||||
customDragHandle
|
||||
|
@ -101,6 +102,7 @@ export const PromotedDocuments: React.FC = () => {
|
|||
{(provided) => (
|
||||
<CurationResult
|
||||
key={document.id}
|
||||
index={index}
|
||||
result={convertToResultFormat(document)}
|
||||
actions={
|
||||
isAutomated
|
||||
|
|
|
@ -21,8 +21,10 @@ import { getPageTitle, getPageHeaderActions, getPageHeaderTabs } from '../../../
|
|||
jest.mock('./curation_logic', () => ({ CurationLogic: jest.fn() }));
|
||||
import { CurationLogic } from './curation_logic';
|
||||
|
||||
import { DeleteCurationButton } from './delete_curation_button';
|
||||
import { PromotedDocuments, HiddenDocuments } from './documents';
|
||||
import { ManualCuration } from './manual_curation';
|
||||
import { ActiveQuerySelect, ManageQueriesModal } from './queries';
|
||||
import { AddResultFlyout } from './results';
|
||||
import { SuggestedDocumentsCallout } from './suggested_documents_callout';
|
||||
|
||||
|
@ -32,9 +34,13 @@ describe('ManualCuration', () => {
|
|||
queries: ['query A', 'query B'],
|
||||
isFlyoutOpen: false,
|
||||
selectedPageTab: 'promoted',
|
||||
curation: {
|
||||
promoted: [],
|
||||
hidden: [],
|
||||
},
|
||||
};
|
||||
const actions = {
|
||||
resetCuration: jest.fn(),
|
||||
deleteCuration: jest.fn(),
|
||||
onSelectPageTab: jest.fn(),
|
||||
};
|
||||
|
||||
|
@ -108,31 +114,26 @@ describe('ManualCuration', () => {
|
|||
expect(CurationLogic).toHaveBeenCalledWith({ curationId: 'hello-world' });
|
||||
});
|
||||
|
||||
describe('restore defaults button', () => {
|
||||
let restoreDefaultsButton: ShallowWrapper;
|
||||
let confirmSpy: jest.SpyInstance;
|
||||
describe('page header actions', () => {
|
||||
let pageHeaderActions: ShallowWrapper;
|
||||
|
||||
beforeAll(() => {
|
||||
const wrapper = shallow(<ManualCuration />);
|
||||
restoreDefaultsButton = getPageHeaderActions(wrapper).childAt(0);
|
||||
|
||||
confirmSpy = jest.spyOn(window, 'confirm');
|
||||
pageHeaderActions = getPageHeaderActions(wrapper);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
confirmSpy.mockRestore();
|
||||
it('contains a button to manage queries and an active query selector', () => {
|
||||
expect(pageHeaderActions.find(ManageQueriesModal)).toHaveLength(1);
|
||||
});
|
||||
|
||||
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();
|
||||
it('contains a button to delete the curation', () => {
|
||||
expect(pageHeaderActions.find(DeleteCurationButton)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('contains an active query selector', () => {
|
||||
const wrapper = shallow(<ManualCuration />);
|
||||
|
||||
expect(wrapper.find(ActiveQuerySelect)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,15 +10,15 @@ import { useParams } from 'react-router-dom';
|
|||
|
||||
import { useValues, useActions } from 'kea';
|
||||
|
||||
import { EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui';
|
||||
import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
|
||||
import { RESTORE_DEFAULTS_BUTTON_LABEL } from '../../../constants';
|
||||
import { AppSearchPageTemplate } from '../../layout';
|
||||
import { MANAGE_CURATION_TITLE, RESTORE_CONFIRMATION } from '../constants';
|
||||
import { MANAGE_CURATION_TITLE } from '../constants';
|
||||
import { getCurationsBreadcrumbs } from '../utils';
|
||||
|
||||
import { PROMOTED_DOCUMENTS_TITLE, HIDDEN_DOCUMENTS_TITLE } from './constants';
|
||||
import { CurationLogic } from './curation_logic';
|
||||
import { DeleteCurationButton } from './delete_curation_button';
|
||||
import { PromotedDocuments, OrganicDocuments, HiddenDocuments } from './documents';
|
||||
import { ActiveQuerySelect, ManageQueriesModal } from './queries';
|
||||
import { AddResultLogic, AddResultFlyout } from './results';
|
||||
|
@ -26,18 +26,22 @@ import { SuggestedDocumentsCallout } from './suggested_documents_callout';
|
|||
|
||||
export const ManualCuration: React.FC = () => {
|
||||
const { curationId } = useParams() as { curationId: string };
|
||||
const { onSelectPageTab, resetCuration } = useActions(CurationLogic({ curationId }));
|
||||
const { dataLoading, queries, selectedPageTab } = useValues(CurationLogic({ curationId }));
|
||||
const { onSelectPageTab } = useActions(CurationLogic({ curationId }));
|
||||
const { dataLoading, queries, selectedPageTab, curation } = useValues(
|
||||
CurationLogic({ curationId })
|
||||
);
|
||||
const { isFlyoutOpen } = useValues(AddResultLogic);
|
||||
|
||||
const pageTabs = [
|
||||
{
|
||||
label: PROMOTED_DOCUMENTS_TITLE,
|
||||
append: <EuiBadge>{curation.promoted.length}</EuiBadge>,
|
||||
isSelected: selectedPageTab === 'promoted',
|
||||
onClick: () => onSelectPageTab('promoted'),
|
||||
},
|
||||
{
|
||||
label: HIDDEN_DOCUMENTS_TITLE,
|
||||
append: <EuiBadge>{curation.hidden.length}</EuiBadge>,
|
||||
isSelected: selectedPageTab === 'hidden',
|
||||
onClick: () => onSelectPageTab('hidden'),
|
||||
},
|
||||
|
@ -49,32 +53,23 @@ export const ManualCuration: React.FC = () => {
|
|||
pageHeader={{
|
||||
pageTitle: MANAGE_CURATION_TITLE,
|
||||
rightSideItems: [
|
||||
<EuiButton
|
||||
color="danger"
|
||||
onClick={() => {
|
||||
if (window.confirm(RESTORE_CONFIRMATION)) resetCuration();
|
||||
}}
|
||||
>
|
||||
{RESTORE_DEFAULTS_BUTTON_LABEL}
|
||||
</EuiButton>,
|
||||
<EuiFlexGroup gutterSize="s" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<DeleteCurationButton />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ManageQueriesModal />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>,
|
||||
],
|
||||
tabs: pageTabs,
|
||||
}}
|
||||
isLoading={dataLoading}
|
||||
>
|
||||
<SuggestedDocumentsCallout />
|
||||
<EuiFlexGroup alignItems="flexEnd" gutterSize="xl" responsive={false}>
|
||||
<EuiFlexItem>
|
||||
<ActiveQuerySelect />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ManageQueriesModal />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="xl" />
|
||||
<ActiveQuerySelect />
|
||||
{selectedPageTab === 'promoted' && <SuggestedDocumentsCallout />}
|
||||
{selectedPageTab === 'promoted' && <PromotedDocuments />}
|
||||
{selectedPageTab === 'hidden' && <HiddenDocuments />}
|
||||
<EuiSpacer />
|
||||
<OrganicDocuments />
|
||||
|
||||
{isFlyoutOpen && <AddResultFlyout />}
|
||||
|
|
|
@ -23,6 +23,12 @@ export const ActiveQuerySelect: React.FC = () => {
|
|||
label={i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.activeQueryLabel', {
|
||||
defaultMessage: 'Active query',
|
||||
})}
|
||||
helpText={i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.curations.activeQueryHelpText',
|
||||
{
|
||||
defaultMessage: 'Select a query to view the organic search results for them',
|
||||
}
|
||||
)}
|
||||
fullWidth
|
||||
>
|
||||
<EuiSelect
|
||||
|
|
|
@ -36,7 +36,7 @@ export const ManageQueriesModal: React.FC = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<EuiButton onClick={showModal} isLoading={queriesLoading}>
|
||||
<EuiButton fill onClick={showModal} isLoading={queriesLoading}>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.curations.manageQueryButtonLabel',
|
||||
{ defaultMessage: 'Manage queries' }
|
||||
|
|
|
@ -21,7 +21,7 @@ export const AddResultButton: React.FC = () => {
|
|||
const { isAutomated } = useValues(CurationLogic);
|
||||
|
||||
return (
|
||||
<EuiButton onClick={openFlyout} iconType="plusInCircle" size="s" fill disabled={isAutomated}>
|
||||
<EuiButton onClick={openFlyout} iconType="plusInCircle" size="s" disabled={isAutomated}>
|
||||
{i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.addResult.buttonLabel', {
|
||||
defaultMessage: 'Add result manually',
|
||||
})}
|
||||
|
|
|
@ -51,4 +51,16 @@ describe('CurationResult', () => {
|
|||
expect(wrapper.find(Result).prop('actions')).toEqual(mockActions);
|
||||
expect(wrapper.find(Result).prop('dragHandleProps')).toEqual(mockDragging);
|
||||
});
|
||||
|
||||
it('increments the result index before passing it on', () => {
|
||||
wrapper = shallow(
|
||||
<CurationResult
|
||||
result={mockResult}
|
||||
index={5}
|
||||
actions={mockActions}
|
||||
dragHandleProps={mockDragging}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.find(Result).prop('resultPosition')).toEqual(6);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,12 +17,13 @@ import { Result } from '../../../result';
|
|||
import { Result as ResultType, ResultAction } from '../../../result/types';
|
||||
|
||||
interface Props {
|
||||
result: ResultType;
|
||||
actions: ResultAction[];
|
||||
dragHandleProps?: DraggableProvidedDragHandleProps;
|
||||
result: ResultType;
|
||||
index?: number;
|
||||
}
|
||||
|
||||
export const CurationResult: React.FC<Props> = ({ result, actions, dragHandleProps }) => {
|
||||
export const CurationResult: React.FC<Props> = ({ actions, dragHandleProps, result, index }) => {
|
||||
const {
|
||||
isMetaEngine,
|
||||
engine: { schema },
|
||||
|
@ -36,6 +37,7 @@ export const CurationResult: React.FC<Props> = ({ result, actions, dragHandlePro
|
|||
isMetaEngine={isMetaEngine}
|
||||
schemaForTypeHighlights={schema}
|
||||
dragHandleProps={dragHandleProps}
|
||||
resultPosition={typeof index === 'undefined' ? undefined : index + 1}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
|
|
|
@ -20,7 +20,7 @@ import { updateMetaPageIndex } from '../../../shared/table_pagination';
|
|||
import { ENGINE_CURATION_PATH } from '../../routes';
|
||||
import { EngineLogic, generateEnginePath } from '../engine';
|
||||
|
||||
import { DELETE_MESSAGE, SUCCESS_MESSAGE } from './constants';
|
||||
import { DELETE_CONFIRMATION_MESSAGE, DELETE_SUCCESS_MESSAGE } from './constants';
|
||||
import { Curation, CurationsAPIResponse } from './types';
|
||||
|
||||
type CurationsPageTabs = 'overview' | 'settings';
|
||||
|
@ -102,11 +102,11 @@ export const CurationsLogic = kea<MakeLogicType<CurationsValues, CurationsAction
|
|||
const { engineName } = EngineLogic.values;
|
||||
clearFlashMessages();
|
||||
|
||||
if (window.confirm(DELETE_MESSAGE)) {
|
||||
if (window.confirm(DELETE_CONFIRMATION_MESSAGE)) {
|
||||
try {
|
||||
await http.delete(`/internal/app_search/engines/${engineName}/curations/${id}`);
|
||||
actions.loadCurations();
|
||||
flashSuccessToast(SUCCESS_MESSAGE);
|
||||
flashSuccessToast(DELETE_SUCCESS_MESSAGE);
|
||||
} catch (e) {
|
||||
flashAPIErrors(e);
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ export interface Curation {
|
|||
queries: string[];
|
||||
promoted: CurationResult[];
|
||||
hidden: CurationResult[];
|
||||
organic: Result[];
|
||||
organic?: Result[]; // this field is missing if there are 0 results
|
||||
suggestion?: CurationSuggestion;
|
||||
}
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ describe('CurationResultPanel', () => {
|
|||
expect(wrapper.find(Result).length).toBe(2);
|
||||
expect(wrapper.find(Result).at(0).props()).toEqual({
|
||||
result: results[0],
|
||||
resultPosition: 1,
|
||||
isMetaEngine: true,
|
||||
schemaForTypeHighlights: values.engine.schema,
|
||||
});
|
||||
|
|
|
@ -72,12 +72,13 @@ export const CurationResultPanel: React.FC<Props> = ({ variant, results }) => {
|
|||
className={`curationResultPanel curationResultPanel--${variant}`}
|
||||
>
|
||||
{results.length > 0 ? (
|
||||
results.map((result) => (
|
||||
results.map((result, index) => (
|
||||
<EuiFlexItem key={result.id.raw} style={{ width: '100%' }}>
|
||||
<Result
|
||||
result={result}
|
||||
isMetaEngine={isMetaEngine}
|
||||
schemaForTypeHighlights={engine.schema}
|
||||
resultPosition={index + 1}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
))
|
||||
|
|
|
@ -129,12 +129,13 @@ export const CurationSuggestion: React.FC = () => {
|
|||
gutterSize="s"
|
||||
data-test-subj="currentOrganicResults"
|
||||
>
|
||||
{currentOrganicResults.map((result: ResultType) => (
|
||||
{currentOrganicResults.map((result: ResultType, index) => (
|
||||
<EuiFlexItem grow={false} key={result.id.raw}>
|
||||
<Result
|
||||
result={result}
|
||||
isMetaEngine={isMetaEngine}
|
||||
schemaForTypeHighlights={engine.schema}
|
||||
resultPosition={index + 1}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
|
@ -148,12 +149,13 @@ export const CurationSuggestion: React.FC = () => {
|
|||
gutterSize="s"
|
||||
data-test-subj="proposedOrganicResults"
|
||||
>
|
||||
{proposedOrganicResults.map((result: ResultType) => (
|
||||
{proposedOrganicResults.map((result: ResultType, index) => (
|
||||
<EuiFlexItem grow={false} key={result.id.raw}>
|
||||
<Result
|
||||
result={result}
|
||||
isMetaEngine={isMetaEngine}
|
||||
schemaForTypeHighlights={engine.schema}
|
||||
resultPosition={index + 1}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
|
|
|
@ -34,7 +34,11 @@ describe('DataPanel', () => {
|
|||
|
||||
wrapper.setProps({ children: 'hello world' });
|
||||
|
||||
expect(wrapper.find(EuiSpacer)).toHaveLength(1);
|
||||
expect(wrapper.find(EuiSpacer).prop('size')).toEqual('s');
|
||||
|
||||
wrapper.setProps({ filled: true });
|
||||
|
||||
expect(wrapper.find(EuiSpacer).prop('size')).toEqual('l');
|
||||
});
|
||||
|
||||
describe('components', () => {
|
||||
|
|
|
@ -87,7 +87,7 @@ export const DataPanel: React.FC<Props> = ({
|
|||
</EuiFlexGroup>
|
||||
{children && (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<EuiSpacer size={filled || subtitle ? 'l' : 's'} />
|
||||
{children}
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -9,6 +9,10 @@ import React from 'react';
|
|||
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { EuiBadge } from '@elastic/eui';
|
||||
|
||||
import { mountWithIntl } from '../../../test_helpers';
|
||||
|
||||
import { ResultActions } from './result_actions';
|
||||
import { ResultHeader } from './result_header';
|
||||
|
||||
|
@ -46,6 +50,13 @@ describe('ResultHeader', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('renders position if one is passed in', () => {
|
||||
const wrapper = mountWithIntl(<ResultHeader {...props} resultPosition={5} />);
|
||||
|
||||
const badge = wrapper.find(EuiBadge);
|
||||
expect(badge.text()).toContain('#5');
|
||||
});
|
||||
|
||||
describe('score', () => {
|
||||
it('renders score if showScore is true ', () => {
|
||||
const wrapper = shallow(<ResultHeader {...props} showScore />);
|
||||
|
|
|
@ -43,7 +43,7 @@ export const ResultHeader: React.FC<Props> = ({
|
|||
responsive={false}
|
||||
wrap
|
||||
>
|
||||
{resultPosition && (
|
||||
{typeof resultPosition !== 'undefined' && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge color="hollow">
|
||||
<FormattedMessage
|
||||
|
|
|
@ -6,3 +6,4 @@
|
|||
*/
|
||||
|
||||
export { LightbulbIcon } from './lightbulb_icon';
|
||||
export { LeafIcon } from './leaf_icon';
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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';
|
||||
|
||||
// TODO: This icon will be added to EUI soon - we should remove this custom SVG when once it's available in EUI
|
||||
export const LeafIcon: React.FC = ({ ...props }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="euiIcon"
|
||||
width={16}
|
||||
height={16}
|
||||
{...props}
|
||||
viewBox="0 0 16 16"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M15.0331 0.886954C15.2956 0.973255 15.5233 1.21266 15.5414 1.55005C15.6669 3.88393 15.361 7.34896 14.1321 10.0468C13.5157 11.3998 12.6491 12.5993 11.4483 13.3465C10.2327 14.1029 8.72706 14.3628 6.91355 13.9235C6.53012 13.8306 6.17184 13.7065 5.83884 13.5557C5.54781 13.4238 5.49404 13.047 5.72206 12.8232L5.77108 12.7753C5.92249 12.6281 6.15055 12.6009 6.34449 12.6844C6.59563 12.7926 6.86382 12.8825 7.14903 12.9516C8.73299 13.3354 9.95814 13.096 10.92 12.4974C11.8967 11.8897 12.6543 10.8786 13.222 9.63227C14.2675 7.33734 14.6079 4.37902 14.5638 2.17691C13.7629 2.93049 13.0215 3.38061 12.3014 3.63327C11.4623 3.92768 10.6938 3.93856 9.97619 3.90053C9.79372 3.89086 9.61467 3.87835 9.43764 3.86598C8.22713 3.78139 7.11038 3.70335 5.63121 4.58567C3.63527 5.77625 3.1 8.36625 4.00645 10.3814C5.40683 9.10928 7.13522 8.0625 9.00009 8.0625C9.27624 8.0625 9.50009 8.28636 9.50009 8.5625C9.50009 8.83864 9.27624 9.0625 9.00009 9.0625C7.39524 9.0625 5.79665 10.0415 4.41045 11.3724C3.03666 12.6914 1.95051 14.2809 1.37654 15.3332C1.24431 15.5756 0.940593 15.6649 0.698168 15.5327C0.455744 15.4005 0.366415 15.0967 0.498647 14.8543C1.04523 13.8523 2.01371 12.4126 3.25129 11.1188C1.94153 8.63223 2.53953 5.26546 5.11893 3.72686C6.88003 2.67636 8.2969 2.77931 9.52388 2.86847C9.69587 2.88096 9.86414 2.89319 10.0291 2.90193C10.6974 2.93734 11.3129 2.92032 11.9703 2.68967C12.6272 2.45919 13.3678 1.99999 14.2457 1.08421C14.4648 0.855623 14.776 0.802403 15.0331 0.886954Z" />
|
||||
</svg>
|
||||
);
|
|
@ -9485,7 +9485,6 @@
|
|||
"xpack.enterpriseSearch.appSearch.engine.curations.empty.buttonLabel": "キュレーションガイドを読む",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.empty.description": "キュレーションを使用して、ドキュメントを昇格させるか非表示にします。最も検出させたい内容をユーザーに検出させるように支援します。",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.empty.noCurationsTitle": "最初のキュレーションを作成",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.description": "非表示のドキュメントはオーガニック結果に表示されません。",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.emptyDescription": "上記のオーガニック結果の目アイコンをクリックしてドキュメントを非表示にするか、結果を手動で検索して非表示にします。",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.emptyTitle": "まだドキュメントを非表示にしていません",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.removeAllButtonLabel": "すべて復元",
|
||||
|
|
|
@ -9580,7 +9580,6 @@
|
|||
"xpack.enterpriseSearch.appSearch.engine.curations.empty.buttonLabel": "阅读策展指南",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.empty.description": "使用策展提升和隐藏文档。帮助人们发现最想让他们发现的内容。",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.empty.noCurationsTitle": "创建您的首个策展",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.description": "隐藏的文档将不显示在有机结果中。",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.emptyDescription": "通过单击上面有机结果上的眼睛图标,可隐藏文档,或手动搜索和隐藏结果。",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.emptyTitle": "您尚未隐藏任何文档",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.hiddenDocuments.removeAllButtonLabel": "全部还原",
|
||||
|
|
Loading…
Reference in a new issue