diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.test.tsx
index 5afbce3661da3..bf64101527fd2 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.test.tsx
@@ -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',
},
],
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.tsx
index 9352bdab51edd..074d2114ee8cb 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.tsx
@@ -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;
}
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/constants.ts
index f8c3e3efdbc1d..01ca80776ae85 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/constants.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/constants.ts
@@ -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' }
+);
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation.test.tsx
new file mode 100644
index 0000000000000..3139d62863729
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation.test.tsx
@@ -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();
+
+ 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();
+
+ expect(CurationLogic).toHaveBeenCalledWith({ curationId: 'hello-world' });
+ });
+
+ it('displays the query in the title with a badge', () => {
+ const wrapper = shallow();
+ const pageTitle = shallow(
{getPageTitle(wrapper)}
);
+
+ 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();
+ 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();
+ });
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation.tsx
new file mode 100644
index 0000000000000..1415537e42d6e
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/automated_curation.tsx
@@ -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 (
+
+ {activeQuery}{' '}
+
+ {AUTOMATED_LABEL}
+
+ >
+ ),
+ rightSideItems: [
+ {
+ if (window.confirm(CONVERT_TO_MANUAL_CONFIRMATION)) convertToManual();
+ }}
+ >
+ {COVERT_TO_MANUAL_BUTTON_LABEL}
+ ,
+ ],
+ }}
+ isLoading={dataLoading}
+ >
+
+
+
+
+ );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.test.tsx
index 2efe1f2ffe86f..62c3a6c7d4578 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.test.tsx
@@ -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();
-
- 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();
-
- expect(wrapper.find(AddResultFlyout)).toHaveLength(1);
- });
-
- it('initializes CurationLogic with a curationId prop from URL param', () => {
- mockUseParams.mockReturnValueOnce({ curationId: 'hello-world' });
- shallow();
-
- 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();
@@ -76,31 +49,17 @@ describe('Curation', () => {
expect(actions.loadCuration).toHaveBeenCalledTimes(2);
});
- describe('restore defaults button', () => {
- let restoreDefaultsButton: ShallowWrapper;
- let confirmSpy: jest.SpyInstance;
-
- beforeAll(() => {
- const wrapper = shallow();
- restoreDefaultsButton = getPageHeaderActions(wrapper).childAt(0);
-
- confirmSpy = jest.spyOn(window, 'confirm');
- });
+ it('renders a view for automated curations', () => {
+ setMockValues({ isAutomated: true });
+ const wrapper = shallow();
- afterAll(() => {
- confirmSpy.mockRestore();
- });
+ expect(wrapper.is(AutomatedCuration)).toBe(true);
+ });
- it('resets the curation upon user confirmation', () => {
- confirmSpy.mockReturnValueOnce(true);
- restoreDefaultsButton.simulate('click');
- expect(actions.resetCuration).toHaveBeenCalled();
- });
+ it('renders a view for manual curations', () => {
+ setMockValues({ isAutomated: false });
+ const wrapper = shallow();
- 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);
});
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.tsx
index 2a01c0db049ab..19b6542e96c4b 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation.tsx
@@ -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 (
- {
- if (window.confirm(RESTORE_CONFIRMATION)) resetCuration();
- }}
- >
- {RESTORE_DEFAULTS_BUTTON_LABEL}
- ,
- ],
- }}
- isLoading={dataLoading}
- >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {isFlyoutOpen && }
-
- );
+ return isAutomated ? : ;
};
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts
index 8fa57e52a26a1..941fd0bf28f96 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts
@@ -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' });
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.ts
index c49fc76d06874..a9fa5ab8c1048 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.ts
@@ -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>({
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 ({
+ 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;
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.test.tsx
index 0624d0063e57d..b7955cf514079 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.test.tsx
@@ -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();
+
+ expect(wrapper.find(DataPanel).prop('subtitle')).toContain('Promote results');
+ });
+
it('renders a loading state', () => {
setMockValues({ ...values, organicDocumentsLoading: true });
const wrapper = shallow();
@@ -63,11 +73,21 @@ describe('OrganicDocuments', () => {
expect(wrapper.find(EuiLoadingContent)).toHaveLength(1);
});
- it('renders an empty state', () => {
- setMockValues({ ...values, curation: { organic: [] } });
- const wrapper = shallow();
+ describe('empty state', () => {
+ it('renders', () => {
+ setMockValues({ ...values, curation: { organic: [] } });
+ const wrapper = shallow();
+
+ 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();
+ 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();
+ const result = wrapper.find(CurationResult).first();
+
+ expect(result.prop('actions')).toEqual([]);
+ });
});
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.tsx
index a3a761feefcd2..7314376a4a7ab 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.tsx
@@ -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 = () => {
)}
}
- subtitle={RESULT_ACTIONS_DIRECTIONS}
+ subtitle={!isAutomated && RESULT_ACTIONS_DIRECTIONS}
>
{hasDocuments ? (
documents.map((document: Result) => (
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 ? (
) : (
+ {' '}
+
+ >
+ ),
+ }}
+ />
+ }
/>
)}
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.test.tsx
index e0c6de973666c..a66b33a47f35c 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.test.tsx
@@ -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();
+ 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();
+ 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();
+
+ 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();
+ 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();
- 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();
+
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();
+ 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();
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();
wrapper.find(EuiDragDropContext).simulate('dragEnd', {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.tsx
index 6b0a02aa2af58..e9d9136a45ac6 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/promoted_documents.tsx
@@ -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 = () => {
)}
}
- subtitle={i18n.translate(
- 'xpack.enterpriseSearch.appSearch.engine.curations.promotedDocuments.description',
- {
- defaultMessage:
- 'Promoted results appear before organic results. Documents can be re-ordered.',
- }
- )}
+ subtitle={
+ isAutomated ? (
+
+ ) : (
+
+ )
+ }
action={
+ !isAutomated &&
hasDocuments && (
-
+
{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) => (
removePromotedId(document.id),
- },
- ]}
+ actions={
+ isAutomated
+ ? []
+ : [
+ {
+ ...DEMOTE_DOCUMENT_ACTION,
+ onClick: () => removePromotedId(document.id),
+ },
+ ]
+ }
dragHandleProps={provided.dragHandleProps}
/>
)}
@@ -109,13 +127,22 @@ export const PromotedDocuments: React.FC = () => {
) : (
}
/>
)}
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/manual_curation.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/manual_curation.test.tsx
new file mode 100644
index 0000000000000..ad9f3bcd64e3e
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/manual_curation.test.tsx
@@ -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();
+
+ 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();
+
+ expect(wrapper.find(AddResultFlyout)).toHaveLength(1);
+ });
+
+ it('initializes CurationLogic with a curationId prop from URL param', () => {
+ mockUseParams.mockReturnValueOnce({ curationId: 'hello-world' });
+ shallow();
+
+ expect(CurationLogic).toHaveBeenCalledWith({ curationId: 'hello-world' });
+ });
+
+ describe('restore defaults button', () => {
+ let restoreDefaultsButton: ShallowWrapper;
+ let confirmSpy: jest.SpyInstance;
+
+ beforeAll(() => {
+ const wrapper = shallow();
+ 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();
+ });
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/manual_curation.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/manual_curation.tsx
new file mode 100644
index 0000000000000..d50575535bf21
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/manual_curation.tsx
@@ -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 (
+ {
+ if (window.confirm(RESTORE_CONFIRMATION)) resetCuration();
+ }}
+ >
+ {RESTORE_DEFAULTS_BUTTON_LABEL}
+ ,
+ ],
+ }}
+ isLoading={dataLoading}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {isFlyoutOpen && }
+
+ );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_button.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_button.test.tsx
index 53cefdd00c670..5b5c814a24c5b 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_button.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_button.test.tsx
@@ -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();
- });
-
it('renders', () => {
- expect(wrapper.find(EuiButton)).toHaveLength(1);
+ const wrapper = shallow();
+
+ expect(wrapper.is(EuiButton)).toBe(true);
});
it('opens the add result flyout on click', () => {
+ setMockActions(actions);
+ const wrapper = shallow();
+
wrapper.find(EuiButton).simulate('click');
expect(actions.openFlyout).toHaveBeenCalled();
});
+
+ it('is disbled when the curation is automated', () => {
+ setMockValues({ ...values, isAutomated: true });
+ const wrapper = shallow();
+
+ expect(wrapper.find(EuiButton).prop('disabled')).toBe(true);
+ });
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_button.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_button.tsx
index 025dda65f4fb8..f2285064da307 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_button.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/add_result_button.tsx
@@ -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 (
-
+
{i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.addResult.buttonLabel', {
defaultMessage: 'Add result manually',
})}
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/types.ts
index 866bf6490ebe8..09c8a487b1b9b 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/types.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/types.ts
@@ -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 {
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.test.ts
index 6e616dcd9452c..9edeab4b658ef 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.test.ts
@@ -15,6 +15,8 @@ import '../../../../__mocks__/engine_logic.mock';
import { nextTick } from '@kbn/test/jest';
+import { CurationSuggestion } from '../../types';
+
import { CurationSuggestionLogic } from './curation_suggestion_logic';
const DEFAULT_VALUES = {
@@ -23,10 +25,11 @@ const DEFAULT_VALUES = {
suggestedPromotedDocuments: [],
};
-const suggestion = {
+const suggestion: CurationSuggestion = {
query: 'foo',
updated_at: '2021-07-08T14:35:50Z',
promoted: ['1', '2', '3'],
+ status: 'applied',
};
const suggestedPromotedDocuments = [
@@ -186,6 +189,7 @@ describe('CurationSuggestionLogic', () => {
query: 'foo',
updated_at: '2021-07-08T14:35:50Z',
promoted: ['1', '2', '3'],
+ status: 'applied',
},
// Note that these were re-ordered to match the 'promoted' list above, and since document
// 3 was not found it is not included in this list
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_actions.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_actions.tsx
index 52fbee90fe31a..5eac38b88937c 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_actions.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_actions.tsx
@@ -18,7 +18,7 @@ interface Props {
export const ResultActions: React.FC = ({ actions }) => {
return (
- {actions.map(({ onClick, title, iconType, iconColor }) => (
+ {actions.map(({ onClick, title, iconType, iconColor, disabled }) => (
= ({ actions }) => {
color={iconColor ? iconColor : 'primary'}
aria-label={title}
title={title}
+ disabled={disabled}
/>
))}
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/types.ts
index 4be3eb137177b..d9f1bb394778e 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/types.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/types.ts
@@ -41,4 +41,5 @@ export interface ResultAction {
title: string;
iconType: string;
iconColor?: EuiButtonIconColor;
+ disabled?: boolean;
}
diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/search_relevance_suggestions.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/search_relevance_suggestions.test.ts
index e6bfaa4a9cca2..2bdcfb9fe9d58 100644
--- a/x-pack/plugins/enterprise_search/server/routes/app_search/search_relevance_suggestions.test.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/app_search/search_relevance_suggestions.test.ts
@@ -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',
diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/search_relevance_suggestions.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/search_relevance_suggestions.ts
index c6fa108a5629e..8b3b204c24d70 100644
--- a/x-pack/plugins/enterprise_search/server/routes/app_search/search_relevance_suggestions.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/app_search/search_relevance_suggestions.ts
@@ -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',
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index dcbb8ce26ee4a..62e35bf2aae5f 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -9346,11 +9346,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": "昇格されたドキュメント",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 87c96d1efe48d..2f58cf9c10abf 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -9438,11 +9438,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": "提升文档",