mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Co-authored-by: Byron Hulcher <byronhulcher@gmail.com>
This commit is contained in:
parent
2111eddde0
commit
85ef6207ba
24 changed files with 697 additions and 177 deletions
|
@ -16,7 +16,7 @@ import { nextTick } from '@kbn/test/jest';
|
|||
|
||||
import { DEFAULT_META } from '../../../../shared/constants';
|
||||
|
||||
import { SuggestionsLogic } from './suggestions_logic';
|
||||
import { SuggestionsAPIResponse, SuggestionsLogic } from './suggestions_logic';
|
||||
|
||||
const DEFAULT_VALUES = {
|
||||
dataLoading: true,
|
||||
|
@ -30,7 +30,7 @@ const DEFAULT_VALUES = {
|
|||
},
|
||||
};
|
||||
|
||||
const MOCK_RESPONSE = {
|
||||
const MOCK_RESPONSE: SuggestionsAPIResponse = {
|
||||
meta: {
|
||||
page: {
|
||||
current: 1,
|
||||
|
@ -44,6 +44,7 @@ const MOCK_RESPONSE = {
|
|||
query: 'foo',
|
||||
updated_at: '2021-07-08T14:35:50Z',
|
||||
promoted: ['1', '2'],
|
||||
status: 'applied',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -15,7 +15,7 @@ import { updateMetaPageIndex } from '../../../../shared/table_pagination';
|
|||
import { EngineLogic } from '../../engine';
|
||||
import { CurationSuggestion } from '../types';
|
||||
|
||||
interface SuggestionsAPIResponse {
|
||||
export interface SuggestionsAPIResponse {
|
||||
results: CurationSuggestion[];
|
||||
meta: Meta;
|
||||
}
|
||||
|
|
|
@ -50,6 +50,13 @@ export const RESTORE_CONFIRMATION = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const CONVERT_TO_MANUAL_CONFIRMATION = i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.curations.convertToManualCurationConfirmation',
|
||||
{
|
||||
defaultMessage: 'Are you sure you want to convert this to a manual curation?',
|
||||
}
|
||||
);
|
||||
|
||||
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.' }
|
||||
|
@ -82,3 +89,13 @@ export const SHOW_DOCUMENT_ACTION = {
|
|||
iconType: 'eye',
|
||||
iconColor: 'primary' as EuiButtonIconColor,
|
||||
};
|
||||
|
||||
export const AUTOMATED_LABEL = i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.curation.automatedLabel',
|
||||
{ defaultMessage: 'Automated' }
|
||||
);
|
||||
|
||||
export const COVERT_TO_MANUAL_BUTTON_LABEL = i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.curation.convertToManualCurationButtonLabel',
|
||||
{ defaultMessage: 'Convert to manual curation' }
|
||||
);
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* 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, setMockValues } from '../../../../__mocks__/kea_logic';
|
||||
import { mockUseParams } from '../../../../__mocks__/react_router';
|
||||
import '../../../__mocks__/engine_logic.mock';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
|
||||
import { EuiBadge } from '@elastic/eui';
|
||||
|
||||
import { getPageHeaderActions, getPageTitle } from '../../../../test_helpers';
|
||||
|
||||
jest.mock('./curation_logic', () => ({ CurationLogic: jest.fn() }));
|
||||
|
||||
import { AppSearchPageTemplate } from '../../layout';
|
||||
|
||||
import { AutomatedCuration } from './automated_curation';
|
||||
import { CurationLogic } from './curation_logic';
|
||||
|
||||
import { PromotedDocuments, OrganicDocuments } from './documents';
|
||||
|
||||
describe('AutomatedCuration', () => {
|
||||
const values = {
|
||||
dataLoading: false,
|
||||
queries: ['query A', 'query B'],
|
||||
isFlyoutOpen: false,
|
||||
curation: {
|
||||
suggestion: {
|
||||
status: 'applied',
|
||||
},
|
||||
},
|
||||
activeQuery: 'query A',
|
||||
isAutomated: true,
|
||||
};
|
||||
|
||||
const actions = {
|
||||
convertToManual: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
setMockValues(values);
|
||||
setMockActions(actions);
|
||||
mockUseParams.mockReturnValue({ curationId: 'test' });
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
const wrapper = shallow(<AutomatedCuration />);
|
||||
|
||||
expect(wrapper.is(AppSearchPageTemplate));
|
||||
expect(wrapper.find(PromotedDocuments)).toHaveLength(1);
|
||||
expect(wrapper.find(OrganicDocuments)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('initializes CurationLogic with a curationId prop from URL param', () => {
|
||||
mockUseParams.mockReturnValueOnce({ curationId: 'hello-world' });
|
||||
shallow(<AutomatedCuration />);
|
||||
|
||||
expect(CurationLogic).toHaveBeenCalledWith({ curationId: 'hello-world' });
|
||||
});
|
||||
|
||||
it('displays the query in the title with a badge', () => {
|
||||
const wrapper = shallow(<AutomatedCuration />);
|
||||
const pageTitle = shallow(<div>{getPageTitle(wrapper)}</div>);
|
||||
|
||||
expect(pageTitle.text()).toContain('query A');
|
||||
expect(pageTitle.find(EuiBadge)).toHaveLength(1);
|
||||
});
|
||||
|
||||
describe('convert to manual button', () => {
|
||||
let convertToManualButton: ShallowWrapper;
|
||||
let confirmSpy: jest.SpyInstance;
|
||||
|
||||
beforeAll(() => {
|
||||
const wrapper = shallow(<AutomatedCuration />);
|
||||
convertToManualButton = getPageHeaderActions(wrapper).childAt(0);
|
||||
|
||||
confirmSpy = jest.spyOn(window, 'confirm');
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
confirmSpy.mockRestore();
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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 React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { useValues, useActions } from 'kea';
|
||||
|
||||
import { EuiSpacer, EuiButton, EuiBadge } from '@elastic/eui';
|
||||
|
||||
import { AppSearchPageTemplate } from '../../layout';
|
||||
import { AutomatedIcon } from '../components/automated_icon';
|
||||
import {
|
||||
AUTOMATED_LABEL,
|
||||
COVERT_TO_MANUAL_BUTTON_LABEL,
|
||||
CONVERT_TO_MANUAL_CONFIRMATION,
|
||||
} from '../constants';
|
||||
import { getCurationsBreadcrumbs } from '../utils';
|
||||
|
||||
import { CurationLogic } from './curation_logic';
|
||||
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);
|
||||
|
||||
return (
|
||||
<AppSearchPageTemplate
|
||||
pageChrome={getCurationsBreadcrumbs([queries.join(', ')])}
|
||||
pageHeader={{
|
||||
pageTitle: (
|
||||
<>
|
||||
{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>,
|
||||
],
|
||||
}}
|
||||
isLoading={dataLoading}
|
||||
>
|
||||
<PromotedDocuments />
|
||||
<EuiSpacer />
|
||||
<OrganicDocuments />
|
||||
</AppSearchPageTemplate>
|
||||
);
|
||||
};
|
|
@ -12,26 +12,25 @@ import '../../../__mocks__/engine_logic.mock';
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { rerender, getPageTitle, getPageHeaderActions } from '../../../../test_helpers';
|
||||
import { rerender } from '../../../../test_helpers';
|
||||
|
||||
jest.mock('./curation_logic', () => ({ CurationLogic: jest.fn() }));
|
||||
import { CurationLogic } from './curation_logic';
|
||||
|
||||
import { AddResultFlyout } from './results';
|
||||
import { AutomatedCuration } from './automated_curation';
|
||||
|
||||
import { ManualCuration } from './manual_curation';
|
||||
|
||||
import { Curation } from './';
|
||||
|
||||
describe('Curation', () => {
|
||||
const values = {
|
||||
dataLoading: false,
|
||||
queries: ['query A', 'query B'],
|
||||
isFlyoutOpen: false,
|
||||
isAutomated: true,
|
||||
};
|
||||
|
||||
const actions = {
|
||||
loadCuration: jest.fn(),
|
||||
resetCuration: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -40,32 +39,6 @@ describe('Curation', () => {
|
|||
setMockActions(actions);
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
const wrapper = shallow(<Curation />);
|
||||
|
||||
expect(getPageTitle(wrapper)).toEqual('Manage curation');
|
||||
expect(wrapper.prop('pageChrome')).toEqual([
|
||||
'Engines',
|
||||
'some-engine',
|
||||
'Curations',
|
||||
'query A, query B',
|
||||
]);
|
||||
});
|
||||
|
||||
it('renders the add result flyout when open', () => {
|
||||
setMockValues({ ...values, isFlyoutOpen: true });
|
||||
const wrapper = shallow(<Curation />);
|
||||
|
||||
expect(wrapper.find(AddResultFlyout)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('initializes CurationLogic with a curationId prop from URL param', () => {
|
||||
mockUseParams.mockReturnValueOnce({ curationId: 'hello-world' });
|
||||
shallow(<Curation />);
|
||||
|
||||
expect(CurationLogic).toHaveBeenCalledWith({ curationId: 'hello-world' });
|
||||
});
|
||||
|
||||
it('calls loadCuration on page load & whenever the curationId URL param changes', () => {
|
||||
mockUseParams.mockReturnValueOnce({ curationId: 'cur-123456789' });
|
||||
const wrapper = shallow(<Curation />);
|
||||
|
@ -76,31 +49,17 @@ describe('Curation', () => {
|
|||
expect(actions.loadCuration).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
describe('restore defaults button', () => {
|
||||
let restoreDefaultsButton: ShallowWrapper;
|
||||
let confirmSpy: jest.SpyInstance;
|
||||
it('renders a view for automated curations', () => {
|
||||
setMockValues({ isAutomated: true });
|
||||
const wrapper = shallow(<Curation />);
|
||||
|
||||
beforeAll(() => {
|
||||
const wrapper = shallow(<Curation />);
|
||||
restoreDefaultsButton = getPageHeaderActions(wrapper).childAt(0);
|
||||
expect(wrapper.is(AutomatedCuration)).toBe(true);
|
||||
});
|
||||
|
||||
confirmSpy = jest.spyOn(window, 'confirm');
|
||||
});
|
||||
it('renders a view for manual curations', () => {
|
||||
setMockValues({ isAutomated: false });
|
||||
const wrapper = shallow(<Curation />);
|
||||
|
||||
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();
|
||||
});
|
||||
expect(wrapper.is(ManualCuration)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,64 +10,18 @@ import { useParams } from 'react-router-dom';
|
|||
|
||||
import { useValues, useActions } from 'kea';
|
||||
|
||||
import { EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui';
|
||||
|
||||
import { RESTORE_DEFAULTS_BUTTON_LABEL } from '../../../constants';
|
||||
import { AppSearchPageTemplate } from '../../layout';
|
||||
import { MANAGE_CURATION_TITLE, RESTORE_CONFIRMATION } from '../constants';
|
||||
import { getCurationsBreadcrumbs } from '../utils';
|
||||
|
||||
import { AutomatedCuration } from './automated_curation';
|
||||
import { CurationLogic } from './curation_logic';
|
||||
import { PromotedDocuments, OrganicDocuments, HiddenDocuments } from './documents';
|
||||
import { ActiveQuerySelect, ManageQueriesModal } from './queries';
|
||||
import { AddResultLogic, AddResultFlyout } from './results';
|
||||
import { ManualCuration } from './manual_curation';
|
||||
|
||||
export const Curation: React.FC = () => {
|
||||
const { curationId } = useParams() as { curationId: string };
|
||||
const { loadCuration, resetCuration } = useActions(CurationLogic({ curationId }));
|
||||
const { dataLoading, queries } = useValues(CurationLogic({ curationId }));
|
||||
const { isFlyoutOpen } = useValues(AddResultLogic);
|
||||
const { loadCuration } = useActions(CurationLogic({ curationId }));
|
||||
const { isAutomated } = useValues(CurationLogic({ curationId }));
|
||||
|
||||
useEffect(() => {
|
||||
loadCuration();
|
||||
}, [curationId]);
|
||||
|
||||
return (
|
||||
<AppSearchPageTemplate
|
||||
pageChrome={getCurationsBreadcrumbs([queries.join(', ')])}
|
||||
pageHeader={{
|
||||
pageTitle: MANAGE_CURATION_TITLE,
|
||||
rightSideItems: [
|
||||
<EuiButton
|
||||
color="danger"
|
||||
onClick={() => {
|
||||
if (window.confirm(RESTORE_CONFIRMATION)) resetCuration();
|
||||
}}
|
||||
>
|
||||
{RESTORE_DEFAULTS_BUTTON_LABEL}
|
||||
</EuiButton>,
|
||||
],
|
||||
}}
|
||||
isLoading={dataLoading}
|
||||
>
|
||||
<EuiFlexGroup alignItems="flexEnd" gutterSize="xl" responsive={false}>
|
||||
<EuiFlexItem>
|
||||
<ActiveQuerySelect />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ManageQueriesModal />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="xl" />
|
||||
|
||||
<PromotedDocuments />
|
||||
<EuiSpacer />
|
||||
<OrganicDocuments />
|
||||
<EuiSpacer />
|
||||
<HiddenDocuments />
|
||||
|
||||
{isFlyoutOpen && <AddResultFlyout />}
|
||||
</AppSearchPageTemplate>
|
||||
);
|
||||
return isAutomated ? <AutomatedCuration /> : <ManualCuration />;
|
||||
};
|
||||
|
|
|
@ -55,6 +55,7 @@ describe('CurationLogic', () => {
|
|||
promotedDocumentsLoading: false,
|
||||
hiddenIds: [],
|
||||
hiddenDocumentsLoading: false,
|
||||
isAutomated: false,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -265,7 +266,60 @@ describe('CurationLogic', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('selectors', () => {
|
||||
describe('isAutomated', () => {
|
||||
it('is true when suggestion status is automated', () => {
|
||||
mount({ curation: { suggestion: { status: 'automated' } } });
|
||||
|
||||
expect(CurationLogic.values.isAutomated).toBe(true);
|
||||
});
|
||||
|
||||
it('is false when suggestion status is not automated', () => {
|
||||
for (status of ['pending', 'applied', 'rejected', 'disabled']) {
|
||||
mount({ curation: { suggestion: { status } } });
|
||||
|
||||
expect(CurationLogic.values.isAutomated).toBe(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('listeners', () => {
|
||||
describe('convertToManual', () => {
|
||||
it('should make an API call and re-load the curation on success', async () => {
|
||||
http.put.mockReturnValueOnce(Promise.resolve());
|
||||
mount({ activeQuery: 'some query' });
|
||||
jest.spyOn(CurationLogic.actions, 'loadCuration');
|
||||
|
||||
CurationLogic.actions.convertToManual();
|
||||
await nextTick();
|
||||
|
||||
expect(http.put).toHaveBeenCalledWith(
|
||||
'/internal/app_search/engines/some-engine/search_relevance_suggestions',
|
||||
{
|
||||
body: JSON.stringify([
|
||||
{
|
||||
query: 'some query',
|
||||
type: 'curation',
|
||||
status: 'applied',
|
||||
},
|
||||
]),
|
||||
}
|
||||
);
|
||||
expect(CurationLogic.actions.loadCuration).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('flashes any error messages', async () => {
|
||||
http.put.mockReturnValueOnce(Promise.reject('error'));
|
||||
mount({ activeQuery: 'some query' });
|
||||
|
||||
CurationLogic.actions.convertToManual();
|
||||
await nextTick();
|
||||
|
||||
expect(flashAPIErrors).toHaveBeenCalledWith('error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadCuration', () => {
|
||||
it('should set dataLoading state', () => {
|
||||
mount({ dataLoading: false }, { curationId: 'cur-123456789' });
|
||||
|
|
|
@ -27,9 +27,11 @@ interface CurationValues {
|
|||
promotedDocumentsLoading: boolean;
|
||||
hiddenIds: string[];
|
||||
hiddenDocumentsLoading: boolean;
|
||||
isAutomated: boolean;
|
||||
}
|
||||
|
||||
interface CurationActions {
|
||||
convertToManual(): void;
|
||||
loadCuration(): void;
|
||||
onCurationLoad(curation: Curation): { curation: Curation };
|
||||
updateCuration(): void;
|
||||
|
@ -53,6 +55,7 @@ interface CurationProps {
|
|||
export const CurationLogic = kea<MakeLogicType<CurationValues, CurationActions, CurationProps>>({
|
||||
path: ['enterprise_search', 'app_search', 'curation_logic'],
|
||||
actions: () => ({
|
||||
convertToManual: true,
|
||||
loadCuration: true,
|
||||
onCurationLoad: (curation) => ({ curation }),
|
||||
updateCuration: true,
|
||||
|
@ -162,7 +165,34 @@ export const CurationLogic = kea<MakeLogicType<CurationValues, CurationActions,
|
|||
},
|
||||
],
|
||||
}),
|
||||
selectors: ({ selectors }) => ({
|
||||
isAutomated: [
|
||||
() => [selectors.curation],
|
||||
(curation: CurationValues['curation']) => {
|
||||
return curation.suggestion?.status === 'automated';
|
||||
},
|
||||
],
|
||||
}),
|
||||
listeners: ({ actions, values, props }) => ({
|
||||
convertToManual: async () => {
|
||||
const { http } = HttpLogic.values;
|
||||
const { engineName } = EngineLogic.values;
|
||||
|
||||
try {
|
||||
await http.put(`/internal/app_search/engines/${engineName}/search_relevance_suggestions`, {
|
||||
body: JSON.stringify([
|
||||
{
|
||||
query: values.activeQuery,
|
||||
type: 'curation',
|
||||
status: 'applied',
|
||||
},
|
||||
]),
|
||||
});
|
||||
actions.loadCuration();
|
||||
} catch (e) {
|
||||
flashAPIErrors(e);
|
||||
}
|
||||
},
|
||||
loadCuration: async () => {
|
||||
const { http } = HttpLogic.values;
|
||||
const { engineName } = EngineLogic.values;
|
||||
|
|
|
@ -13,6 +13,8 @@ import { shallow } from 'enzyme';
|
|||
|
||||
import { EuiLoadingContent, EuiEmptyPrompt } from '@elastic/eui';
|
||||
|
||||
import { mountWithIntl } from '../../../../../test_helpers';
|
||||
|
||||
import { DataPanel } from '../../../data_panel';
|
||||
import { CurationResult } from '../results';
|
||||
|
||||
|
@ -30,6 +32,7 @@ describe('OrganicDocuments', () => {
|
|||
},
|
||||
activeQuery: 'world',
|
||||
organicDocumentsLoading: false,
|
||||
isAutomated: false,
|
||||
};
|
||||
const actions = {
|
||||
addPromotedId: jest.fn(),
|
||||
|
@ -56,6 +59,13 @@ 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 />);
|
||||
|
@ -63,11 +73,21 @@ describe('OrganicDocuments', () => {
|
|||
expect(wrapper.find(EuiLoadingContent)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders an empty state', () => {
|
||||
setMockValues({ ...values, curation: { organic: [] } });
|
||||
const wrapper = shallow(<OrganicDocuments />);
|
||||
describe('empty state', () => {
|
||||
it('renders', () => {
|
||||
setMockValues({ ...values, curation: { organic: [] } });
|
||||
const wrapper = shallow(<OrganicDocuments />);
|
||||
|
||||
expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1);
|
||||
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 />);
|
||||
const emptyPromptBody = mountWithIntl(<>{wrapper.find(EuiEmptyPrompt).prop('body')}</>);
|
||||
|
||||
expect(emptyPromptBody.text()).toContain('Add or change');
|
||||
});
|
||||
});
|
||||
|
||||
describe('actions', () => {
|
||||
|
@ -86,5 +106,13 @@ describe('OrganicDocuments', () => {
|
|||
|
||||
expect(actions.addHiddenId).toHaveBeenCalledWith('mock-document-3');
|
||||
});
|
||||
|
||||
it('hides actions when the curation is automated', () => {
|
||||
setMockValues({ ...values, isAutomated: true });
|
||||
const wrapper = shallow(<OrganicDocuments />);
|
||||
const result = wrapper.find(CurationResult).first();
|
||||
|
||||
expect(result.prop('actions')).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,6 +11,7 @@ import { useValues, useActions } from 'kea';
|
|||
|
||||
import { EuiLoadingContent, EuiEmptyPrompt } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { DataPanel } from '../../../data_panel';
|
||||
import { Result } from '../../../result/types';
|
||||
|
@ -25,7 +26,7 @@ import { CurationResult } from '../results';
|
|||
|
||||
export const OrganicDocuments: React.FC = () => {
|
||||
const { addPromotedId, addHiddenId } = useActions(CurationLogic);
|
||||
const { curation, activeQuery, organicDocumentsLoading } = useValues(CurationLogic);
|
||||
const { curation, activeQuery, isAutomated, organicDocumentsLoading } = useValues(CurationLogic);
|
||||
|
||||
const documents = curation.organic;
|
||||
const hasDocuments = documents.length > 0 && !organicDocumentsLoading;
|
||||
|
@ -46,36 +47,50 @@ export const OrganicDocuments: React.FC = () => {
|
|||
)}
|
||||
</h2>
|
||||
}
|
||||
subtitle={RESULT_ACTIONS_DIRECTIONS}
|
||||
subtitle={!isAutomated && RESULT_ACTIONS_DIRECTIONS}
|
||||
>
|
||||
{hasDocuments ? (
|
||||
documents.map((document: Result) => (
|
||||
<CurationResult
|
||||
result={document}
|
||||
key={document.id.raw}
|
||||
actions={[
|
||||
{
|
||||
...HIDE_DOCUMENT_ACTION,
|
||||
onClick: () => addHiddenId(document.id.raw),
|
||||
},
|
||||
{
|
||||
...PROMOTE_DOCUMENT_ACTION,
|
||||
onClick: () => addPromotedId(document.id.raw),
|
||||
},
|
||||
]}
|
||||
actions={
|
||||
isAutomated
|
||||
? []
|
||||
: [
|
||||
{
|
||||
...HIDE_DOCUMENT_ACTION,
|
||||
onClick: () => addHiddenId(document.id.raw),
|
||||
},
|
||||
{
|
||||
...PROMOTE_DOCUMENT_ACTION,
|
||||
onClick: () => addPromotedId(document.id.raw),
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
))
|
||||
) : organicDocumentsLoading ? (
|
||||
<EuiLoadingContent lines={5} />
|
||||
) : (
|
||||
<EuiEmptyPrompt
|
||||
body={i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.curations.organicDocuments.emptyDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'No organic results to display. Add or change the active query above.',
|
||||
}
|
||||
)}
|
||||
body={
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.appSearch.engine.curations.organicDocuments.description"
|
||||
defaultMessage="No organic results to display.{manualDescription}"
|
||||
values={{
|
||||
manualDescription: !isAutomated && (
|
||||
<>
|
||||
{' '}
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.appSearch.engine.curations.organicDocuments.manualDescription"
|
||||
defaultMessage="Add or change the active query above."
|
||||
/>
|
||||
</>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</DataPanel>
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { setMockValues, setMockActions } from '../../../../../__mocks__/kea_logic';
|
||||
|
||||
import React from 'react';
|
||||
|
@ -13,6 +12,7 @@ import { shallow } from 'enzyme';
|
|||
|
||||
import { EuiDragDropContext, EuiDraggable, EuiEmptyPrompt, EuiButtonEmpty } from '@elastic/eui';
|
||||
|
||||
import { mountWithIntl } from '../../../../../test_helpers';
|
||||
import { DataPanel } from '../../../data_panel';
|
||||
import { CurationResult } from '../results';
|
||||
|
||||
|
@ -57,11 +57,50 @@ describe('PromotedDocuments', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('renders an empty state & hides the panel actions when empty', () => {
|
||||
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: [] } });
|
||||
const wrapper = shallow(<PromotedDocuments />);
|
||||
|
||||
expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('hide information about starring documents if the curation is automated', () => {
|
||||
setMockValues({ ...values, curation: { promoted: [] }, isAutomated: true });
|
||||
const wrapper = shallow(<PromotedDocuments />);
|
||||
const emptyPromptBody = mountWithIntl(<>{wrapper.find(EuiEmptyPrompt).prop('body')}</>);
|
||||
|
||||
expect(emptyPromptBody.text()).not.toContain('Star documents');
|
||||
});
|
||||
});
|
||||
|
||||
it('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('hides the panel actions when the curation is automated', () => {
|
||||
setMockValues({ ...values, isAutomated: true });
|
||||
const wrapper = shallow(<PromotedDocuments />);
|
||||
|
||||
expect(wrapper.find(DataPanel).prop('action')).toBe(false);
|
||||
});
|
||||
|
||||
|
@ -81,6 +120,14 @@ describe('PromotedDocuments', () => {
|
|||
expect(actions.removePromotedId).toHaveBeenCalledWith('mock-document-4');
|
||||
});
|
||||
|
||||
it('hides demote button for results when the curation is automated', () => {
|
||||
setMockValues({ ...values, isAutomated: true });
|
||||
const wrapper = shallow(<PromotedDocuments />);
|
||||
const result = getDraggableChildren(wrapper.find(EuiDraggable).last());
|
||||
|
||||
expect(result.prop('actions')).toEqual([]);
|
||||
});
|
||||
|
||||
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);
|
||||
|
@ -89,7 +136,7 @@ describe('PromotedDocuments', () => {
|
|||
expect(actions.clearPromotedIds).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('draggging', () => {
|
||||
describe('dragging', () => {
|
||||
it('calls setPromotedIds with the reordered list when users are done dragging', () => {
|
||||
const wrapper = shallow(<PromotedDocuments />);
|
||||
wrapper.find(EuiDragDropContext).simulate('dragEnd', {
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
euiDragDropReorder,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { DataPanel } from '../../../data_panel';
|
||||
|
||||
|
@ -29,7 +30,7 @@ import { CurationLogic } from '../curation_logic';
|
|||
import { AddResultButton, CurationResult, convertToResultFormat } from '../results';
|
||||
|
||||
export const PromotedDocuments: React.FC = () => {
|
||||
const { curation, promotedIds, promotedDocumentsLoading } = useValues(CurationLogic);
|
||||
const { curation, isAutomated, promotedIds, promotedDocumentsLoading } = useValues(CurationLogic);
|
||||
const documents = curation.promoted;
|
||||
const hasDocuments = documents.length > 0;
|
||||
|
||||
|
@ -53,21 +54,33 @@ export const PromotedDocuments: React.FC = () => {
|
|||
)}
|
||||
</h2>
|
||||
}
|
||||
subtitle={i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'Promoted results appear before organic results. Documents can be re-ordered.',
|
||||
}
|
||||
)}
|
||||
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">
|
||||
<EuiButtonEmpty
|
||||
onClick={clearPromotedIds}
|
||||
iconType="menuDown"
|
||||
size="s"
|
||||
disabled={isAutomated}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.removeAllButtonLabel',
|
||||
{ defaultMessage: 'Demote all' }
|
||||
|
@ -89,17 +102,22 @@ export const PromotedDocuments: React.FC = () => {
|
|||
draggableId={document.id}
|
||||
customDragHandle
|
||||
spacing="none"
|
||||
isDragDisabled={isAutomated}
|
||||
>
|
||||
{(provided) => (
|
||||
<CurationResult
|
||||
key={document.id}
|
||||
result={convertToResultFormat(document)}
|
||||
actions={[
|
||||
{
|
||||
...DEMOTE_DOCUMENT_ACTION,
|
||||
onClick: () => removePromotedId(document.id),
|
||||
},
|
||||
]}
|
||||
actions={
|
||||
isAutomated
|
||||
? []
|
||||
: [
|
||||
{
|
||||
...DEMOTE_DOCUMENT_ACTION,
|
||||
onClick: () => removePromotedId(document.id),
|
||||
},
|
||||
]
|
||||
}
|
||||
dragHandleProps={provided.dragHandleProps}
|
||||
/>
|
||||
)}
|
||||
|
@ -109,13 +127,22 @@ export const PromotedDocuments: React.FC = () => {
|
|||
</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.',
|
||||
}
|
||||
)}
|
||||
body={
|
||||
isAutomated
|
||||
? i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.automatedEmptyDescription',
|
||||
{
|
||||
defaultMessage: "We haven't identified any documents to promote",
|
||||
}
|
||||
)
|
||||
: 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 />}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* 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, setMockValues } from '../../../../__mocks__/kea_logic';
|
||||
import { mockUseParams } from '../../../../__mocks__/react_router';
|
||||
import '../../../__mocks__/engine_logic.mock';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
|
||||
import { getPageTitle, getPageHeaderActions } from '../../../../test_helpers';
|
||||
|
||||
jest.mock('./curation_logic', () => ({ CurationLogic: jest.fn() }));
|
||||
import { CurationLogic } from './curation_logic';
|
||||
|
||||
import { ManualCuration } from './manual_curation';
|
||||
import { AddResultFlyout } from './results';
|
||||
|
||||
describe('ManualCuration', () => {
|
||||
const values = {
|
||||
dataLoading: false,
|
||||
queries: ['query A', 'query B'],
|
||||
isFlyoutOpen: false,
|
||||
};
|
||||
const actions = {
|
||||
resetCuration: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
setMockValues(values);
|
||||
setMockActions(actions);
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
const wrapper = shallow(<ManualCuration />);
|
||||
|
||||
expect(getPageTitle(wrapper)).toEqual('Manage curation');
|
||||
expect(wrapper.prop('pageChrome')).toEqual([
|
||||
'Engines',
|
||||
'some-engine',
|
||||
'Curations',
|
||||
'query A, query B',
|
||||
]);
|
||||
});
|
||||
|
||||
it('renders the add result flyout when open', () => {
|
||||
setMockValues({ ...values, isFlyoutOpen: true });
|
||||
const wrapper = shallow(<ManualCuration />);
|
||||
|
||||
expect(wrapper.find(AddResultFlyout)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('initializes CurationLogic with a curationId prop from URL param', () => {
|
||||
mockUseParams.mockReturnValueOnce({ curationId: 'hello-world' });
|
||||
shallow(<ManualCuration />);
|
||||
|
||||
expect(CurationLogic).toHaveBeenCalledWith({ curationId: 'hello-world' });
|
||||
});
|
||||
|
||||
describe('restore defaults button', () => {
|
||||
let restoreDefaultsButton: ShallowWrapper;
|
||||
let confirmSpy: jest.SpyInstance;
|
||||
|
||||
beforeAll(() => {
|
||||
const wrapper = shallow(<ManualCuration />);
|
||||
restoreDefaultsButton = getPageHeaderActions(wrapper).childAt(0);
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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 { useValues, useActions } from 'kea';
|
||||
|
||||
import { EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui';
|
||||
|
||||
import { RESTORE_DEFAULTS_BUTTON_LABEL } from '../../../constants';
|
||||
import { AppSearchPageTemplate } from '../../layout';
|
||||
import { MANAGE_CURATION_TITLE, RESTORE_CONFIRMATION } from '../constants';
|
||||
import { getCurationsBreadcrumbs } from '../utils';
|
||||
|
||||
import { CurationLogic } from './curation_logic';
|
||||
import { PromotedDocuments, OrganicDocuments, HiddenDocuments } from './documents';
|
||||
import { ActiveQuerySelect, ManageQueriesModal } from './queries';
|
||||
import { AddResultLogic, AddResultFlyout } from './results';
|
||||
|
||||
export const ManualCuration: React.FC = () => {
|
||||
const { curationId } = useParams() as { curationId: string };
|
||||
const { resetCuration } = useActions(CurationLogic({ curationId }));
|
||||
const { dataLoading, queries } = useValues(CurationLogic({ curationId }));
|
||||
const { isFlyoutOpen } = useValues(AddResultLogic);
|
||||
|
||||
return (
|
||||
<AppSearchPageTemplate
|
||||
pageChrome={getCurationsBreadcrumbs([queries.join(', ')])}
|
||||
pageHeader={{
|
||||
pageTitle: MANAGE_CURATION_TITLE,
|
||||
rightSideItems: [
|
||||
<EuiButton
|
||||
color="danger"
|
||||
onClick={() => {
|
||||
if (window.confirm(RESTORE_CONFIRMATION)) resetCuration();
|
||||
}}
|
||||
>
|
||||
{RESTORE_DEFAULTS_BUTTON_LABEL}
|
||||
</EuiButton>,
|
||||
],
|
||||
}}
|
||||
isLoading={dataLoading}
|
||||
>
|
||||
<EuiFlexGroup alignItems="flexEnd" gutterSize="xl" responsive={false}>
|
||||
<EuiFlexItem>
|
||||
<ActiveQuerySelect />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ManageQueriesModal />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="xl" />
|
||||
|
||||
<PromotedDocuments />
|
||||
<EuiSpacer />
|
||||
<OrganicDocuments />
|
||||
<EuiSpacer />
|
||||
<HiddenDocuments />
|
||||
|
||||
{isFlyoutOpen && <AddResultFlyout />}
|
||||
</AppSearchPageTemplate>
|
||||
);
|
||||
};
|
|
@ -5,34 +5,43 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { setMockActions } from '../../../../../__mocks__/kea_logic';
|
||||
import { setMockActions, setMockValues } from '../../../../../__mocks__/kea_logic';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { EuiButton } from '@elastic/eui';
|
||||
|
||||
import { AddResultButton } from './';
|
||||
|
||||
describe('AddResultButton', () => {
|
||||
const values = {
|
||||
isAutomated: false,
|
||||
};
|
||||
|
||||
const actions = {
|
||||
openFlyout: jest.fn(),
|
||||
};
|
||||
|
||||
let wrapper: ShallowWrapper;
|
||||
|
||||
beforeAll(() => {
|
||||
setMockActions(actions);
|
||||
wrapper = shallow(<AddResultButton />);
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
expect(wrapper.find(EuiButton)).toHaveLength(1);
|
||||
const wrapper = shallow(<AddResultButton />);
|
||||
|
||||
expect(wrapper.is(EuiButton)).toBe(true);
|
||||
});
|
||||
|
||||
it('opens the add result flyout on click', () => {
|
||||
setMockActions(actions);
|
||||
const wrapper = shallow(<AddResultButton />);
|
||||
|
||||
wrapper.find(EuiButton).simulate('click');
|
||||
expect(actions.openFlyout).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('is disbled when the curation is automated', () => {
|
||||
setMockValues({ ...values, isAutomated: true });
|
||||
const wrapper = shallow(<AddResultButton />);
|
||||
|
||||
expect(wrapper.find(EuiButton).prop('disabled')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,18 +7,21 @@
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { useActions } from 'kea';
|
||||
import { useActions, useValues } from 'kea';
|
||||
|
||||
import { EuiButton } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { CurationLogic } from '..';
|
||||
|
||||
import { AddResultLogic } from './';
|
||||
|
||||
export const AddResultButton: React.FC = () => {
|
||||
const { openFlyout } = useActions(AddResultLogic);
|
||||
const { isAutomated } = useValues(CurationLogic);
|
||||
|
||||
return (
|
||||
<EuiButton onClick={openFlyout} iconType="plusInCircle" size="s" fill>
|
||||
<EuiButton onClick={openFlyout} iconType="plusInCircle" size="s" fill disabled={isAutomated}>
|
||||
{i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.addResult.buttonLabel', {
|
||||
defaultMessage: 'Add result manually',
|
||||
})}
|
||||
|
|
|
@ -12,7 +12,9 @@ export interface CurationSuggestion {
|
|||
query: string;
|
||||
updated_at: string;
|
||||
promoted: string[];
|
||||
status: 'pending' | 'applied' | 'automated' | 'rejected' | 'disabled';
|
||||
}
|
||||
|
||||
export interface Curation {
|
||||
id: string;
|
||||
last_updated: string;
|
||||
|
@ -20,6 +22,7 @@ export interface Curation {
|
|||
promoted: CurationResult[];
|
||||
hidden: CurationResult[];
|
||||
organic: Result[];
|
||||
suggestion?: CurationSuggestion;
|
||||
}
|
||||
|
||||
export interface CurationsAPIResponse {
|
||||
|
|
|
@ -18,7 +18,7 @@ interface Props {
|
|||
export const ResultActions: React.FC<Props> = ({ actions }) => {
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="s" responsive={false}>
|
||||
{actions.map(({ onClick, title, iconType, iconColor }) => (
|
||||
{actions.map(({ onClick, title, iconType, iconColor, disabled }) => (
|
||||
<EuiFlexItem key={title} grow={false}>
|
||||
<EuiButtonIcon
|
||||
iconType={iconType}
|
||||
|
@ -26,6 +26,7 @@ export const ResultActions: React.FC<Props> = ({ actions }) => {
|
|||
color={iconColor ? iconColor : 'primary'}
|
||||
aria-label={title}
|
||||
title={title}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
|
|
|
@ -41,4 +41,5 @@ export interface ResultAction {
|
|||
title: string;
|
||||
iconType: string;
|
||||
iconColor?: EuiButtonIconColor;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
|
|
@ -38,6 +38,35 @@ describe('search relevance insights routes', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('PUT /internal/app_search/engines/{name}/search_relevance_suggestions', () => {
|
||||
const mockRouter = new MockRouter({
|
||||
method: 'put',
|
||||
path: '/internal/app_search/engines/{engineName}/search_relevance_suggestions',
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
registerSearchRelevanceSuggestionsRoutes({
|
||||
...mockDependencies,
|
||||
router: mockRouter.router,
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a request to enterprise search', () => {
|
||||
mockRouter.callRoute({
|
||||
params: { engineName: 'some-engine' },
|
||||
body: {
|
||||
query: 'some query',
|
||||
type: 'curation',
|
||||
status: 'applied',
|
||||
},
|
||||
});
|
||||
|
||||
expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
|
||||
path: '/api/as/v0/engines/:engineName/search_relevance_suggestions',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /internal/app_search/engines/{name}/search_relevance_suggestions/settings', () => {
|
||||
const mockRouter = new MockRouter({
|
||||
method: 'get',
|
||||
|
|
|
@ -39,6 +39,20 @@ export function registerSearchRelevanceSuggestionsRoutes({
|
|||
})
|
||||
);
|
||||
|
||||
router.put(
|
||||
skipBodyValidation({
|
||||
path: '/internal/app_search/engines/{engineName}/search_relevance_suggestions',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
engineName: schema.string(),
|
||||
}),
|
||||
},
|
||||
}),
|
||||
enterpriseSearchRequestHandler.createRequest({
|
||||
path: '/api/as/v0/engines/:engineName/search_relevance_suggestions',
|
||||
})
|
||||
);
|
||||
|
||||
router.get(
|
||||
{
|
||||
path: '/internal/app_search/engines/{engineName}/search_relevance_suggestions/settings',
|
||||
|
|
|
@ -9505,11 +9505,9 @@
|
|||
"xpack.enterpriseSearch.appSearch.engine.curations.manageQueryButtonLabel": "クエリを管理",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.manageQueryDescription": "このキュレーションのクエリを編集、追加、削除します。",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.manageQueryTitle": "クエリを管理",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.organicDocuments.emptyDescription": "表示するオーガニック結果はありません。上記のアクティブなクエリを追加または変更します。",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.organicDocuments.title": "\"{currentQuery}\"の上位のオーガニックドキュメント",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.overview.title": "キュレーションされた結果",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.promoteButtonLabel": "この結果を昇格",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.description": "昇格された結果はオーガニック結果の前に表示されます。ドキュメントを並べ替えることができます。",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.emptyDescription": "以下のオーガニック結果からドキュメントにスターを付けるか、手動で結果を検索して昇格します。",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.removeAllButtonLabel": "すべて降格",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.title": "昇格されたドキュメント",
|
||||
|
|
|
@ -9600,11 +9600,9 @@
|
|||
"xpack.enterpriseSearch.appSearch.engine.curations.manageQueryButtonLabel": "管理查询",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.manageQueryDescription": "编辑、添加或移除此策展的查询。",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.manageQueryTitle": "管理查询",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.organicDocuments.emptyDescription": "没有要显示的有机结果。在上面添加或更改活动查询。",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.organicDocuments.title": "“{currentQuery}”的排名靠前有机文档",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.overview.title": "已策展结果",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.promoteButtonLabel": "提升此结果",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.description": "提升结果显示在有机结果之前。可以重新排列文档。",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.emptyDescription": "使用星号标记来自下面有机结果的文档或手动搜索或提升结果。",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.removeAllButtonLabel": "全部降低",
|
||||
"xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.title": "提升文档",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue