[App Search] Add Automated Curations options to Curations Settings tab (#112766)
This commit is contained in:
parent
575e22f070
commit
24147afdf2
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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>
|
||||
);
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
}),
|
||||
});
|
|
@ -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';
|
|
@ -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',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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',
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue