[App Search] Add Automated Curations options to Curations Settings tab (#112766)

This commit is contained in:
Byron Hulcher 2021-09-30 13:25:27 -04:00 committed by GitHub
parent 575e22f070
commit 24147afdf2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 903 additions and 32 deletions

View file

@ -0,0 +1,17 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { shallow } from 'enzyme';
import { AutomatedIcon } from './automated_icon';
describe('AutomatedIcon', () => {
it('renders', () => {
expect(shallow(<AutomatedIcon />).is('svg')).toBe(true);
});
});

View file

@ -0,0 +1,26 @@
/*
* 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';
export const AutomatedIcon: React.FC = ({ ...props }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
className="euiIcon"
width={16}
height={16}
{...props}
viewBox="0 0 16 16"
aria-hidden="true"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M12.5 0.894547C12.7761 0.894547 13 1.1184 13 1.39455V3.64455C13 3.92069 12.7761 4.14455 12.5 4.14455H10.25C9.97386 4.14455 9.75 3.92069 9.75 3.64455C9.75 3.3684 9.97386 3.14455 10.25 3.14455H11.5634C10.6452 2.2954 9.42289 1.77022 8.07821 1.75058C8.02894 1.74985 7.97967 1.74981 7.93044 1.75046C5.063 1.7877 2.75 4.12374 2.75 7.00001C2.75 9.87611 5.06273 12.212 7.92992 12.2496C7.97659 12.2502 8.0233 12.2502 8.07004 12.2496C9.4485 12.2315 10.6988 11.6823 11.6256 10.7971C11.6764 10.7485 11.7264 10.6989 11.7754 10.6482C12.6071 9.7876 13.1474 8.64339 13.2368 7.37476C13.2514 7.16816 13.4179 7.00001 13.625 7.00001C13.8321 7.00001 14.0012 7.16806 13.9883 7.37477C13.9804 7.50044 13.9686 7.62558 13.953 7.75001C13.8077 8.90336 13.3294 9.9956 12.569 10.889C11.6453 11.9743 10.3654 12.6953 8.95854 12.9229C7.5517 13.1506 6.10975 12.8701 4.89087 12.1316C3.67199 11.3931 2.75577 10.2449 2.30627 8.89247C1.85676 7.54007 1.90332 6.07183 2.4376 4.75062C2.97189 3.42942 3.95901 2.34153 5.22223 1.68174C6.48545 1.02195 7.94227 0.833348 9.33187 1.1497C10.33 1.37693 11.2466 1.85402 12 2.52786V1.39455C12 1.1184 12.2239 0.894547 12.5 0.894547ZM7.29699 8.22388L7.0078 9.25H6.125L7.47013 4.75H8.53177L9.875 9.25H8.9922L8.70301 8.22388H7.29699ZM7.98573 5.77832L7.50628 7.4812H8.49562L8.01617 5.77832H7.98573Z"
/>
</svg>
);

View file

@ -1,27 +0,0 @@
/*
* 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__/react_router';
import '../../../__mocks__/engine_logic.mock';
import React from 'react';
import { shallow } from 'enzyme';
import { CurationsSettings } from './curations_settings';
describe('CurationsSettings', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('renders empty', () => {
const wrapper = shallow(<CurationsSettings />);
expect(wrapper.isEmptyRender()).toBe(true);
});
});

View file

@ -0,0 +1,233 @@
/*
* 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 '../../../../../__mocks__/react_router';
import '../../../../__mocks__/engine_logic.mock';
import { setMockActions, setMockValues } from '../../../../../__mocks__/kea_logic';
import React from 'react';
import { shallow, ShallowWrapper } from 'enzyme';
import { EuiButtonEmpty, EuiCallOut, EuiSwitch } from '@elastic/eui';
import { mountWithIntl } from '@kbn/test/jest';
import { Loading } from '../../../../../shared/loading';
import { EuiButtonTo } from '../../../../../shared/react_router_helpers';
import { DataPanel } from '../../../data_panel';
import { LogRetentionOptions } from '../../../log_retention';
import { CurationsSettings } from '.';
const MOCK_VALUES = {
// CurationsSettingsLogic
dataLoading: false,
curationsSettings: {
enabled: true,
mode: 'automatic',
},
// LogRetentionLogic
isLogRetentionUpdating: false,
logRetention: {
[LogRetentionOptions.Analytics]: {
enabled: true,
},
},
// LicensingLogic
hasPlatinumLicense: true,
};
const MOCK_ACTIONS = {
// CurationsSettingsLogic
loadCurationsSettings: jest.fn(),
onSkipLoadingCurationsSettings: jest.fn(),
toggleCurationsEnabled: jest.fn(),
toggleCurationsMode: jest.fn(),
// LogRetentionLogic
fetchLogRetention: jest.fn(),
};
describe('CurationsSettings', () => {
beforeEach(() => {
jest.clearAllMocks();
setMockActions(MOCK_ACTIONS);
});
it('loads curations and log retention settings on load', () => {
setMockValues(MOCK_VALUES);
mountWithIntl(<CurationsSettings />);
expect(MOCK_ACTIONS.loadCurationsSettings).toHaveBeenCalled();
expect(MOCK_ACTIONS.fetchLogRetention).toHaveBeenCalled();
});
it('contains a switch to toggle curations settings', () => {
let wrapper: ShallowWrapper;
setMockValues({
...MOCK_VALUES,
curationsSettings: { ...MOCK_VALUES.curationsSettings, enabled: true },
});
wrapper = shallow(<CurationsSettings />);
expect(wrapper.find(EuiSwitch).at(0).prop('checked')).toBe(true);
setMockValues({
...MOCK_VALUES,
curationsSettings: { ...MOCK_VALUES.curationsSettings, enabled: false },
});
wrapper = shallow(<CurationsSettings />);
expect(wrapper.find(EuiSwitch).at(0).prop('checked')).toBe(false);
wrapper.find(EuiSwitch).at(0).simulate('change');
expect(MOCK_ACTIONS.toggleCurationsEnabled).toHaveBeenCalled();
});
it('contains a switch to toggle the curations mode', () => {
let wrapper: ShallowWrapper;
setMockValues({
...MOCK_VALUES,
curationsSettings: { ...MOCK_VALUES.curationsSettings, mode: 'automatic' },
});
wrapper = shallow(<CurationsSettings />);
expect(wrapper.find(EuiSwitch).at(1).prop('checked')).toBe(true);
setMockValues({
...MOCK_VALUES,
curationsSettings: { ...MOCK_VALUES.curationsSettings, mode: 'manual' },
});
wrapper = shallow(<CurationsSettings />);
expect(wrapper.find(EuiSwitch).at(1).prop('checked')).toBe(false);
wrapper.find(EuiSwitch).at(1).simulate('change');
expect(MOCK_ACTIONS.toggleCurationsMode).toHaveBeenCalled();
});
it('enables form elements and hides the callout when analytics retention is enabled', () => {
setMockValues({
...MOCK_VALUES,
logRetention: {
[LogRetentionOptions.Analytics]: {
enabled: true,
},
},
});
const wrapper = shallow(<CurationsSettings />);
expect(wrapper.find(EuiSwitch).at(0).prop('disabled')).toBe(false);
expect(wrapper.find(EuiSwitch).at(1).prop('disabled')).toBe(false);
expect(wrapper.find(EuiCallOut)).toHaveLength(0);
});
it('display a callout and disables form elements when analytics retention is disabled', () => {
setMockValues({
...MOCK_VALUES,
logRetention: {
[LogRetentionOptions.Analytics]: {
enabled: false,
},
},
});
const wrapper = shallow(<CurationsSettings />);
expect(wrapper.find(EuiSwitch).at(0).prop('disabled')).toBe(true);
expect(wrapper.find(EuiSwitch).at(1).prop('disabled')).toBe(true);
expect(wrapper.find(EuiCallOut).dive().find(EuiButtonTo).prop('to')).toEqual('/settings');
});
it('returns a loading state when curations data is loading', () => {
setMockValues({
...MOCK_VALUES,
dataLoading: true,
});
const wrapper = shallow(<CurationsSettings />);
expect(wrapper.is(Loading)).toBe(true);
});
it('returns a loading state when log retention data is loading', () => {
setMockValues({
...MOCK_VALUES,
isLogRetentionUpdating: true,
});
const wrapper = shallow(<CurationsSettings />);
expect(wrapper.is(Loading)).toBe(true);
});
describe('loading curation settings based on log retention', () => {
it('loads curation settings when log retention is enabled', () => {
setMockValues({
...MOCK_VALUES,
logRetention: {
[LogRetentionOptions.Analytics]: {
enabled: true,
},
},
});
shallow(<CurationsSettings />);
expect(MOCK_ACTIONS.loadCurationsSettings).toHaveBeenCalledTimes(1);
});
it('skips loading curation settings when log retention is enabled', () => {
setMockValues({
...MOCK_VALUES,
logRetention: {
[LogRetentionOptions.Analytics]: {
enabled: false,
},
},
});
shallow(<CurationsSettings />);
expect(MOCK_ACTIONS.onSkipLoadingCurationsSettings).toHaveBeenCalledTimes(1);
});
it('takes no action if log retention has not yet been loaded', () => {
setMockValues({
...MOCK_VALUES,
logRetention: null,
});
shallow(<CurationsSettings />);
expect(MOCK_ACTIONS.loadCurationsSettings).toHaveBeenCalledTimes(0);
expect(MOCK_ACTIONS.onSkipLoadingCurationsSettings).toHaveBeenCalledTimes(0);
});
});
describe('when the user has no platinum license', () => {
beforeEach(() => {
setMockValues({
...MOCK_VALUES,
hasPlatinumLicense: false,
});
});
it('it does not fetch log retention', () => {
shallow(<CurationsSettings />);
expect(MOCK_ACTIONS.fetchLogRetention).toHaveBeenCalledTimes(0);
});
it('shows a CTA to upgrade your license when the user when the user', () => {
const wrapper = shallow(<CurationsSettings />);
expect(wrapper.is(DataPanel)).toBe(true);
expect(wrapper.prop('action').props.to).toEqual('/app/management/stack/license_management');
expect(wrapper.find(EuiButtonEmpty).prop('href')).toEqual('/license-management.html');
});
});
});

View file

@ -0,0 +1,216 @@
/*
* 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, { useEffect } from 'react';
import { useActions, useValues } from 'kea';
import {
EuiButtonEmpty,
EuiCallOut,
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiSpacer,
EuiSwitch,
EuiText,
EuiTitle,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { docLinks } from '../../../../../shared/doc_links';
import { LicensingLogic } from '../../../../../shared/licensing';
import { Loading } from '../../../../../shared/loading';
import { EuiButtonTo } from '../../../../../shared/react_router_helpers';
import { SETTINGS_PATH } from '../../../../routes';
import { DataPanel } from '../../../data_panel';
import { LogRetentionLogic, LogRetentionOptions } from '../../../log_retention';
import { AutomatedIcon } from '../../components/automated_icon';
import { CurationsSettingsLogic } from './curations_settings_logic';
export const CurationsSettings: React.FC = () => {
const { hasPlatinumLicense } = useValues(LicensingLogic);
const {
curationsSettings: { enabled, mode },
dataLoading,
} = useValues(CurationsSettingsLogic);
const {
loadCurationsSettings,
onSkipLoadingCurationsSettings,
toggleCurationsEnabled,
toggleCurationsMode,
} = useActions(CurationsSettingsLogic);
const { isLogRetentionUpdating, logRetention } = useValues(LogRetentionLogic);
const { fetchLogRetention } = useActions(LogRetentionLogic);
const analyticsDisabled = !logRetention?.[LogRetentionOptions.Analytics].enabled;
useEffect(() => {
if (hasPlatinumLicense) {
fetchLogRetention();
}
}, [hasPlatinumLicense]);
useEffect(() => {
if (logRetention) {
if (!analyticsDisabled) {
loadCurationsSettings();
} else {
onSkipLoadingCurationsSettings();
}
}
}, [logRetention]);
if (!hasPlatinumLicense)
return (
<DataPanel
title={
<h2>
{i18n.translate(
'xpack.enterpriseSearch.appSearch.curations.settings.licenseUpgradeCTATitle',
{
defaultMessage: 'Introducing automated curations',
}
)}
</h2>
}
subtitle={
<FormattedMessage
id="xpack.enterpriseSearch.appSearch.curations.settings.licenseUpgradeCTASubtitle"
defaultMessage="Upgrade to a {platinumLicenseName} license to harness the power of machine learning. By analyzing your engine's analytics, App Search is able to suggest new or updated curations. Effortlessly help your users find exactly what they're looking for. Start a free trial today."
values={{
platinumLicenseName: (
<strong>
{i18n.translate('xpack.enterpriseSearch.appSearch.curations.settings.platinum', {
defaultMessage: 'Platinum',
})}
</strong>
),
}}
/>
}
action={
<EuiButtonTo to="/app/management/stack/license_management" shouldNotCreateHref>
{i18n.translate(
'xpack.enterpriseSearch.curations.settings.start30DayTrialButtonLabel',
{
defaultMessage: 'Start a 30-day trial',
}
)}
</EuiButtonTo>
}
>
<EuiButtonEmpty
target="_blank"
iconType="popout"
href={`${docLinks.enterpriseSearchBase}/license-management.html`}
>
{i18n.translate('xpack.enterpriseSearch.curations.settings.licenseUpgradeLink', {
defaultMessage: 'Learn more about license upgrades',
})}
</EuiButtonEmpty>
</DataPanel>
);
if (dataLoading || isLogRetentionUpdating) return <Loading />;
return (
<>
<EuiFlexGroup gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false}>
<EuiIcon type={AutomatedIcon} size="l" />
</EuiFlexItem>
<EuiFlexItem>
<EuiTitle size="s">
<h2>
{i18n.translate(
'xpack.enterpriseSearch.appSearch.curations.settings.automaticCurationsTitle',
{
defaultMessage: 'Automated Curations',
}
)}
</h2>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="m" />
{analyticsDisabled && (
<>
<EuiCallOut
iconType="iInCircle"
title={i18n.translate(
'xpack.enterpriseSearch.appSearch.curations.settings.analyticsDisabledCalloutTitle',
{
defaultMessage: 'Analytics are disabled',
}
)}
>
<p>
{i18n.translate(
'xpack.enterpriseSearch.appSearch.curations.settings.analyticsDisabledCalloutDescription',
{
defaultMessage:
'Automated curations require analytics to be enabled on your account.',
}
)}
</p>
<EuiButtonTo fill size="s" to={SETTINGS_PATH}>
{i18n.translate(
'xpack.enterpriseSearch.appSearch.curations.settings.manageAnalyticsButtonLabel',
{ defaultMessage: 'Manage analytics' }
)}
</EuiButtonTo>
</EuiCallOut>
<EuiSpacer size="m" />
</>
)}
<EuiText color="subdued" size="s">
{i18n.translate(
'xpack.enterpriseSearch.appSearch.curations.settings.automaticCurationsDescription',
{
defaultMessage:
"Suggested curations will monitor your engine's analytics and make automatic suggestions to help you deliver the most relevant results. Each suggested curation can be accepted, rejected, or modified.",
}
)}
</EuiText>
<EuiSpacer size="m" />
<EuiFlexGroup direction="column">
<EuiFlexItem>
<EuiSwitch
label={i18n.translate(
'xpack.enterpriseSearch.appSearch.curations.settings.enableautomaticCurationsSwitchLabel',
{
defaultMessage: 'Enable automation suggestions',
}
)}
checked={enabled}
disabled={analyticsDisabled}
onChange={toggleCurationsEnabled}
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiSwitch
label={i18n.translate(
'xpack.enterpriseSearch.appSearch.curations.settings.acceptNewSuggesstionsSwitchLabel',
{
defaultMessage: 'Automatically accept new suggestions',
}
)}
checked={mode === 'automatic'}
disabled={analyticsDisabled}
onChange={toggleCurationsMode}
/>
</EuiFlexItem>
</EuiFlexGroup>
</>
);
};

View file

@ -0,0 +1,221 @@
/*
* 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 {
LogicMounter,
mockHttpValues,
mockFlashMessageHelpers,
} from '../../../../../__mocks__/kea_logic';
import '../../../../__mocks__/engine_logic.mock';
import { nextTick } from '@kbn/test/jest';
import { CurationsSettingsLogic } from './curations_settings_logic';
const DEFAULT_VALUES = {
dataLoading: true,
curationsSettings: {
enabled: false,
mode: 'manual',
},
};
describe('CurationsSettingsLogic', () => {
const { mount } = new LogicMounter(CurationsSettingsLogic);
const { http } = mockHttpValues;
const { flashAPIErrors } = mockFlashMessageHelpers;
beforeEach(() => {
jest.clearAllMocks();
});
it('has correct default values', () => {
mount();
expect(CurationsSettingsLogic.values).toEqual(DEFAULT_VALUES);
});
describe('actions', () => {
describe('onCurationsSettingsLoad', () => {
it('saves curation settings and that data has loaded', () => {
mount();
CurationsSettingsLogic.actions.onCurationsSettingsLoad({
enabled: true,
mode: 'automatic',
});
expect(CurationsSettingsLogic.values.dataLoading).toEqual(false);
expect(CurationsSettingsLogic.values.curationsSettings).toEqual({
enabled: true,
mode: 'automatic',
});
});
});
describe('onSkipCurationsSettingsLoad', () => {
it('saves that data has loaded', () => {
mount();
CurationsSettingsLogic.actions.onSkipLoadingCurationsSettings();
expect(CurationsSettingsLogic.values.dataLoading).toEqual(false);
});
});
});
describe('listeners', () => {
describe('loadCurationsSettings', () => {
it('calls the curations settings API and saves the returned settings', async () => {
http.get.mockReturnValueOnce(
Promise.resolve({
curation: {
enabled: true,
mode: 'automatic',
},
})
);
mount();
jest.spyOn(CurationsSettingsLogic.actions, 'onCurationsSettingsLoad');
CurationsSettingsLogic.actions.loadCurationsSettings();
await nextTick();
expect(http.get).toHaveBeenCalledWith(
'/internal/app_search/engines/some-engine/search_relevance_suggestions/settings'
);
expect(CurationsSettingsLogic.actions.onCurationsSettingsLoad).toHaveBeenCalledWith({
enabled: true,
mode: 'automatic',
});
});
it('presents any API errors to the user', async () => {
http.get.mockReturnValueOnce(Promise.reject('error'));
mount();
CurationsSettingsLogic.actions.loadCurationsSettings();
await nextTick();
expect(flashAPIErrors).toHaveBeenCalledWith('error');
});
});
describe('toggleCurationsEnabled', () => {
it('enables curations when they are currently disabled', () => {
mount({
curationsSettings: {
...DEFAULT_VALUES.curationsSettings,
enabled: false,
},
});
jest.spyOn(CurationsSettingsLogic.actions, 'updateCurationsSetting');
CurationsSettingsLogic.actions.toggleCurationsEnabled();
expect(CurationsSettingsLogic.actions.updateCurationsSetting).toHaveBeenCalledWith({
enabled: true,
});
});
it('disables curations when they are currently enabled', () => {
mount({
curationsSettings: {
...DEFAULT_VALUES.curationsSettings,
enabled: true,
},
});
jest.spyOn(CurationsSettingsLogic.actions, 'updateCurationsSetting');
CurationsSettingsLogic.actions.toggleCurationsEnabled();
expect(CurationsSettingsLogic.actions.updateCurationsSetting).toHaveBeenCalledWith({
enabled: false,
mode: 'manual',
});
});
});
describe('toggleCurationsMode', () => {
it('sets to manual mode when it is currently automatic', () => {
mount({
curationsSettings: {
...DEFAULT_VALUES.curationsSettings,
mode: 'automatic',
},
});
jest.spyOn(CurationsSettingsLogic.actions, 'updateCurationsSetting');
CurationsSettingsLogic.actions.toggleCurationsMode();
expect(CurationsSettingsLogic.actions.updateCurationsSetting).toHaveBeenCalledWith({
mode: 'manual',
});
});
it('sets to automatic mode when it is currently manual', () => {
mount({
curationsSettings: {
...DEFAULT_VALUES.curationsSettings,
mode: 'manual',
},
});
jest.spyOn(CurationsSettingsLogic.actions, 'updateCurationsSetting');
CurationsSettingsLogic.actions.toggleCurationsMode();
expect(CurationsSettingsLogic.actions.updateCurationsSetting).toHaveBeenCalledWith({
mode: 'automatic',
});
});
});
describe('updateCurationsSetting', () => {
it('calls the curations settings API and saves the returned settings', async () => {
http.put.mockReturnValueOnce(
Promise.resolve({
curation: {
enabled: true,
mode: 'automatic',
},
})
);
mount();
jest.spyOn(CurationsSettingsLogic.actions, 'onCurationsSettingsLoad');
CurationsSettingsLogic.actions.updateCurationsSetting({
enabled: true,
});
await nextTick();
expect(http.put).toHaveBeenCalledWith(
'/internal/app_search/engines/some-engine/search_relevance_suggestions/settings',
{
body: JSON.stringify({
curation: {
enabled: true,
},
}),
}
);
expect(CurationsSettingsLogic.actions.onCurationsSettingsLoad).toHaveBeenCalledWith({
enabled: true,
mode: 'automatic',
});
});
it('presents any API errors to the user', async () => {
http.put.mockReturnValueOnce(Promise.reject('error'));
mount();
CurationsSettingsLogic.actions.updateCurationsSetting({});
await nextTick();
expect(flashAPIErrors).toHaveBeenCalledWith('error');
});
});
});
});

View file

@ -0,0 +1,109 @@
/*
* 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 { kea, MakeLogicType } from 'kea';
import { flashAPIErrors } from '../../../../../shared/flash_messages';
import { HttpLogic } from '../../../../../shared/http';
import { EngineLogic } from '../../../engine';
export interface CurationsSettings {
enabled: boolean;
mode: 'automatic' | 'manual';
}
interface CurationsSettingsValues {
dataLoading: boolean;
curationsSettings: CurationsSettings;
}
interface CurationsSettingsActions {
loadCurationsSettings(): void;
onCurationsSettingsLoad(curationsSettings: CurationsSettings): {
curationsSettings: CurationsSettings;
};
onSkipLoadingCurationsSettings(): void;
toggleCurationsEnabled(): void;
toggleCurationsMode(): void;
updateCurationsSetting(currationsSetting: Partial<CurationsSettings>): {
currationsSetting: Partial<CurationsSettings>;
};
}
export const CurationsSettingsLogic = kea<
MakeLogicType<CurationsSettingsValues, CurationsSettingsActions>
>({
path: ['enterprise_search', 'app_search', 'curations', 'curations_settings_logic'],
actions: () => ({
loadCurationsSettings: true,
onCurationsSettingsLoad: (curationsSettings) => ({ curationsSettings }),
onSkipLoadingCurationsSettings: true,
toggleCurationsEnabled: true,
toggleCurationsMode: true,
updateCurationsSetting: (currationsSetting) => ({ currationsSetting }),
}),
reducers: () => ({
dataLoading: [
true,
{
onCurationsSettingsLoad: () => false,
onSkipLoadingCurationsSettings: () => false,
},
],
curationsSettings: [
{
enabled: false,
mode: 'manual',
},
{
onCurationsSettingsLoad: (_, { curationsSettings }) => curationsSettings,
},
],
}),
listeners: ({ actions, values }) => ({
loadCurationsSettings: async () => {
const { http } = HttpLogic.values;
const { engineName } = EngineLogic.values;
try {
const response = await http.get(
`/internal/app_search/engines/${engineName}/search_relevance_suggestions/settings`
);
actions.onCurationsSettingsLoad(response.curation);
} catch (e) {
flashAPIErrors(e);
}
},
toggleCurationsEnabled: async () => {
if (values.curationsSettings.enabled) {
actions.updateCurationsSetting({ enabled: false, mode: 'manual' });
} else {
actions.updateCurationsSetting({ enabled: true });
}
},
toggleCurationsMode: async () => {
actions.updateCurationsSetting({
mode: values.curationsSettings.mode === 'automatic' ? 'manual' : 'automatic',
});
},
updateCurationsSetting: async ({ currationsSetting }) => {
const { http } = HttpLogic.values;
const { engineName } = EngineLogic.values;
try {
const response = await http.put(
`/internal/app_search/engines/${engineName}/search_relevance_suggestions/settings`,
{
body: JSON.stringify({ curation: currationsSetting }),
}
);
actions.onCurationsSettingsLoad(response.curation);
} catch (e) {
flashAPIErrors(e);
}
},
}),
});

View file

@ -5,8 +5,5 @@
* 2.0.
*/
import React from 'react';
export const CurationsSettings: React.FC = () => {
return null;
};
export { CurationsSettings } from './curations_settings';
export { CurationsSettingsLogic } from './curations_settings_logic';

View file

@ -37,4 +37,53 @@ describe('search relevance insights routes', () => {
});
});
});
describe('GET /internal/app_search/engines/{name}/search_relevance_suggestions/settings', () => {
const mockRouter = new MockRouter({
method: 'get',
path: '/internal/app_search/engines/{engineName}/search_relevance_suggestions/settings',
});
beforeEach(() => {
registerSearchRelevanceSuggestionsRoutes({
...mockDependencies,
router: mockRouter.router,
});
});
it('creates a request to enterprise search', () => {
mockRouter.callRoute({
params: { engineName: 'some-engine' },
});
expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
path: '/api/as/v0/engines/:engineName/search_relevance_suggestions/settings',
});
});
});
describe('PUT /internal/app_search/engines/{name}/search_relevance_suggestions/settings', () => {
const mockRouter = new MockRouter({
method: 'put',
path: '/internal/app_search/engines/{engineName}/search_relevance_suggestions/settings',
});
beforeEach(() => {
registerSearchRelevanceSuggestionsRoutes({
...mockDependencies,
router: mockRouter.router,
});
});
it('creates a request to enterprise search', () => {
mockRouter.callRoute({
params: { engineName: 'some-engine' },
body: { curation: { enabled: true } },
});
expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
path: '/api/as/v0/engines/:engineName/search_relevance_suggestions/settings',
});
});
});
});

View file

@ -7,6 +7,8 @@
import { schema } from '@kbn/config-schema';
import { skipBodyValidation } from '../../lib/route_config_helpers';
import { RouteDependencies } from '../../plugin';
export function registerSearchRelevanceSuggestionsRoutes({
@ -36,4 +38,32 @@ export function registerSearchRelevanceSuggestionsRoutes({
path: '/api/as/v0/engines/:engineName/search_relevance_suggestions',
})
);
router.get(
{
path: '/internal/app_search/engines/{engineName}/search_relevance_suggestions/settings',
validate: {
params: schema.object({
engineName: schema.string(),
}),
},
},
enterpriseSearchRequestHandler.createRequest({
path: '/api/as/v0/engines/:engineName/search_relevance_suggestions/settings',
})
);
router.put(
skipBodyValidation({
path: '/internal/app_search/engines/{engineName}/search_relevance_suggestions/settings',
validate: {
params: schema.object({
engineName: schema.string(),
}),
},
}),
enterpriseSearchRequestHandler.createRequest({
path: '/api/as/v0/engines/:engineName/search_relevance_suggestions/settings',
})
);
}