[App Search] Remaining Result Settings logic and routes (#94947)
This commit is contained in:
parent
b4ff1f65dc
commit
19aeb99dc3
|
@ -9,6 +9,8 @@ import { i18n } from '@kbn/i18n';
|
|||
|
||||
import { FieldResultSetting } from './types';
|
||||
|
||||
export const DEFAULT_SNIPPET_SIZE = 100;
|
||||
|
||||
export const RESULT_SETTINGS_TITLE = i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.resultSettings.title',
|
||||
{ defaultMessage: 'Result Settings' }
|
||||
|
|
|
@ -5,7 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { LogicMounter } from '../../../__mocks__';
|
||||
import { LogicMounter, mockFlashMessageHelpers, mockHttpValues } from '../../../__mocks__';
|
||||
|
||||
import { mockEngineValues } from '../../__mocks__';
|
||||
|
||||
import { omit } from 'lodash';
|
||||
|
||||
import { nextTick } from '@kbn/test/jest';
|
||||
|
||||
import { Schema, SchemaConflicts, SchemaTypes } from '../../../shared/types';
|
||||
|
||||
|
@ -29,13 +35,27 @@ describe('ResultSettingsLogic', () => {
|
|||
schemaConflicts: {},
|
||||
};
|
||||
|
||||
const SELECTORS = {
|
||||
reducedServerResultFields: {},
|
||||
resultFieldsAtDefaultSettings: true,
|
||||
resultFieldsEmpty: true,
|
||||
stagedUpdates: false,
|
||||
};
|
||||
|
||||
// Values without selectors
|
||||
const resultSettingLogicValues = () => omit(ResultSettingsLogic.values, Object.keys(SELECTORS));
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockEngineValues.engineName = 'test-engine';
|
||||
});
|
||||
|
||||
it('has expected default values', () => {
|
||||
mount();
|
||||
expect(ResultSettingsLogic.values).toEqual(DEFAULT_VALUES);
|
||||
expect(ResultSettingsLogic.values).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
...SELECTORS,
|
||||
});
|
||||
});
|
||||
|
||||
describe('actions', () => {
|
||||
|
@ -71,7 +91,7 @@ describe('ResultSettingsLogic', () => {
|
|||
schemaConflicts
|
||||
);
|
||||
|
||||
expect(ResultSettingsLogic.values).toEqual({
|
||||
expect(resultSettingLogicValues()).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
dataLoading: false,
|
||||
saving: false,
|
||||
|
@ -173,7 +193,7 @@ describe('ResultSettingsLogic', () => {
|
|||
|
||||
ResultSettingsLogic.actions.openConfirmSaveModal();
|
||||
|
||||
expect(ResultSettingsLogic.values).toEqual({
|
||||
expect(resultSettingLogicValues()).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
openModal: OpenModal.ConfirmSaveModal,
|
||||
});
|
||||
|
@ -186,7 +206,7 @@ describe('ResultSettingsLogic', () => {
|
|||
|
||||
ResultSettingsLogic.actions.openConfirmResetModal();
|
||||
|
||||
expect(ResultSettingsLogic.values).toEqual({
|
||||
expect(resultSettingLogicValues()).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
openModal: OpenModal.ConfirmResetModal,
|
||||
});
|
||||
|
@ -200,7 +220,7 @@ describe('ResultSettingsLogic', () => {
|
|||
|
||||
ResultSettingsLogic.actions.closeModals();
|
||||
|
||||
expect(ResultSettingsLogic.values).toEqual({
|
||||
expect(resultSettingLogicValues()).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
openModal: OpenModal.None,
|
||||
});
|
||||
|
@ -230,7 +250,7 @@ describe('ResultSettingsLogic', () => {
|
|||
|
||||
ResultSettingsLogic.actions.clearAllFields();
|
||||
|
||||
expect(ResultSettingsLogic.values).toEqual({
|
||||
expect(resultSettingLogicValues()).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
nonTextResultFields: {
|
||||
foo: {},
|
||||
|
@ -275,7 +295,7 @@ describe('ResultSettingsLogic', () => {
|
|||
|
||||
ResultSettingsLogic.actions.resetAllFields();
|
||||
|
||||
expect(ResultSettingsLogic.values).toEqual({
|
||||
expect(resultSettingLogicValues()).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
nonTextResultFields: {
|
||||
bar: { raw: true, snippet: false, snippetFallback: false },
|
||||
|
@ -303,7 +323,7 @@ describe('ResultSettingsLogic', () => {
|
|||
|
||||
ResultSettingsLogic.actions.resetAllFields();
|
||||
|
||||
expect(ResultSettingsLogic.values).toEqual({
|
||||
expect(resultSettingLogicValues()).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
openModal: OpenModal.None,
|
||||
});
|
||||
|
@ -339,7 +359,7 @@ describe('ResultSettingsLogic', () => {
|
|||
snippetFallback: false,
|
||||
});
|
||||
|
||||
expect(ResultSettingsLogic.values).toEqual({
|
||||
expect(resultSettingLogicValues()).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
// the settings for foo are updated below for any *ResultFields state in which they appear
|
||||
nonTextResultFields: {
|
||||
|
@ -372,7 +392,7 @@ describe('ResultSettingsLogic', () => {
|
|||
});
|
||||
|
||||
// 'baz' does not exist in state, so nothing is updated
|
||||
expect(ResultSettingsLogic.values).toEqual({
|
||||
expect(resultSettingLogicValues()).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
...initialValues,
|
||||
});
|
||||
|
@ -387,7 +407,7 @@ describe('ResultSettingsLogic', () => {
|
|||
|
||||
ResultSettingsLogic.actions.saving();
|
||||
|
||||
expect(ResultSettingsLogic.values).toEqual({
|
||||
expect(resultSettingLogicValues()).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
saving: true,
|
||||
openModal: OpenModal.None,
|
||||
|
@ -395,4 +415,453 @@ describe('ResultSettingsLogic', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('selectors', () => {
|
||||
describe('resultFieldsAtDefaultSettings', () => {
|
||||
it('should return true if all fields are at their default settings', () => {
|
||||
mount({
|
||||
resultFields: {
|
||||
foo: { raw: true, snippet: false, snippetFallback: false },
|
||||
bar: { raw: true, snippet: false, snippetFallback: false },
|
||||
},
|
||||
});
|
||||
|
||||
expect(ResultSettingsLogic.values.resultFieldsAtDefaultSettings).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return false otherwise', () => {
|
||||
mount({
|
||||
resultFields: {
|
||||
foo: { raw: true, snippet: false, snippetFallback: false },
|
||||
bar: { raw: true, snippet: true, snippetFallback: false },
|
||||
},
|
||||
});
|
||||
|
||||
expect(ResultSettingsLogic.values.resultFieldsAtDefaultSettings).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resultFieldsEmpty', () => {
|
||||
it('should return true if all fields are empty', () => {
|
||||
mount({
|
||||
resultFields: {
|
||||
foo: {},
|
||||
bar: {},
|
||||
},
|
||||
});
|
||||
|
||||
expect(ResultSettingsLogic.values.resultFieldsEmpty).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return false otherwise', () => {
|
||||
mount({
|
||||
resultFields: {
|
||||
foo: {},
|
||||
bar: { raw: true, snippet: true, snippetFallback: false },
|
||||
},
|
||||
});
|
||||
|
||||
expect(ResultSettingsLogic.values.resultFieldsEmpty).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('stagedUpdates', () => {
|
||||
it('should return true if changes have been made since the last save', () => {
|
||||
mount({
|
||||
lastSavedResultFields: {
|
||||
foo: {},
|
||||
bar: { raw: true, snippet: true, snippetFallback: false },
|
||||
},
|
||||
resultFields: {
|
||||
foo: { raw: false, snippet: true, snippetFallback: true },
|
||||
bar: { raw: true, snippet: true, snippetFallback: false },
|
||||
},
|
||||
});
|
||||
|
||||
// resultFields is different than lastSavedResultsFields, which happens if changes
|
||||
// have been made since the last save, which is represented by lastSavedResultFields
|
||||
expect(ResultSettingsLogic.values.stagedUpdates).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return false otherwise', () => {
|
||||
mount({
|
||||
lastSavedResultFields: {
|
||||
foo: { raw: false, snippet: true, snippetFallback: true },
|
||||
bar: { raw: true, snippet: true, snippetFallback: false },
|
||||
},
|
||||
resultFields: {
|
||||
foo: { raw: false, snippet: true, snippetFallback: true },
|
||||
bar: { raw: true, snippet: true, snippetFallback: false },
|
||||
},
|
||||
});
|
||||
|
||||
expect(ResultSettingsLogic.values.stagedUpdates).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('reducedServerResultFields', () => {
|
||||
it('filters out fields that do not have any settings', () => {
|
||||
mount({
|
||||
serverResultFields: {
|
||||
foo: { raw: { size: 5 } },
|
||||
bar: {},
|
||||
},
|
||||
});
|
||||
|
||||
expect(ResultSettingsLogic.values.reducedServerResultFields).toEqual({
|
||||
// bar was filtered out because it has neither raw nor snippet data set
|
||||
foo: { raw: { size: 5 } },
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('listeners', () => {
|
||||
const { http } = mockHttpValues;
|
||||
const { flashAPIErrors } = mockFlashMessageHelpers;
|
||||
|
||||
const serverFieldResultSettings = {
|
||||
foo: {
|
||||
raw: {},
|
||||
},
|
||||
bar: {
|
||||
raw: {},
|
||||
},
|
||||
};
|
||||
const schema = {
|
||||
foo: 'text',
|
||||
bar: 'number',
|
||||
};
|
||||
const schemaConflicts = {
|
||||
baz: {
|
||||
text: ['test'],
|
||||
number: ['test2'],
|
||||
},
|
||||
};
|
||||
|
||||
describe('clearRawSizeForField', () => {
|
||||
it('should remove the raw size set on a field', () => {
|
||||
mount({
|
||||
resultFields: {
|
||||
foo: { raw: true, rawSize: 5, snippet: false },
|
||||
bar: { raw: true, rawSize: 5, snippet: false },
|
||||
},
|
||||
});
|
||||
jest.spyOn(ResultSettingsLogic.actions, 'updateField');
|
||||
|
||||
ResultSettingsLogic.actions.clearRawSizeForField('foo');
|
||||
|
||||
expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('foo', {
|
||||
raw: true,
|
||||
snippet: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('clearSnippetSizeForField', () => {
|
||||
it('should remove the snippet size set on a field', () => {
|
||||
mount({
|
||||
resultFields: {
|
||||
foo: { raw: false, snippet: true, snippetSize: 5 },
|
||||
bar: { raw: true, rawSize: 5, snippet: false },
|
||||
},
|
||||
});
|
||||
jest.spyOn(ResultSettingsLogic.actions, 'updateField');
|
||||
|
||||
ResultSettingsLogic.actions.clearSnippetSizeForField('foo');
|
||||
|
||||
expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('foo', {
|
||||
raw: false,
|
||||
snippet: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleRawForField', () => {
|
||||
it('should toggle the raw value on for a field', () => {
|
||||
mount({
|
||||
resultFields: {
|
||||
foo: { raw: false, snippet: true, snippetSize: 5 },
|
||||
bar: { raw: false, snippet: false },
|
||||
},
|
||||
});
|
||||
jest.spyOn(ResultSettingsLogic.actions, 'updateField');
|
||||
|
||||
ResultSettingsLogic.actions.toggleRawForField('bar');
|
||||
|
||||
expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('bar', {
|
||||
raw: true,
|
||||
snippet: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should maintain rawSize if it was set prior', () => {
|
||||
mount({
|
||||
resultFields: {
|
||||
foo: { raw: false, snippet: true, snippetSize: 5 },
|
||||
bar: { raw: false, rawSize: 10, snippet: false },
|
||||
},
|
||||
});
|
||||
jest.spyOn(ResultSettingsLogic.actions, 'updateField');
|
||||
|
||||
ResultSettingsLogic.actions.toggleRawForField('bar');
|
||||
|
||||
expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('bar', {
|
||||
raw: true,
|
||||
rawSize: 10,
|
||||
snippet: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove rawSize value when toggling off', () => {
|
||||
mount({
|
||||
resultFields: {
|
||||
foo: { raw: false, snippet: true, snippetSize: 5 },
|
||||
bar: { raw: true, rawSize: 5, snippet: false },
|
||||
},
|
||||
});
|
||||
jest.spyOn(ResultSettingsLogic.actions, 'updateField');
|
||||
|
||||
ResultSettingsLogic.actions.toggleRawForField('bar');
|
||||
|
||||
expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('bar', {
|
||||
raw: false,
|
||||
snippet: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should still work if the object is empty', () => {
|
||||
mount({
|
||||
resultFields: {
|
||||
foo: { raw: false, snippet: true, snippetSize: 5 },
|
||||
bar: {},
|
||||
},
|
||||
});
|
||||
jest.spyOn(ResultSettingsLogic.actions, 'updateField');
|
||||
|
||||
ResultSettingsLogic.actions.toggleRawForField('bar');
|
||||
|
||||
expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('bar', {
|
||||
raw: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleSnippetForField', () => {
|
||||
it('should toggle the raw value on for a field, always setting the snippet size to 100', () => {
|
||||
mount({
|
||||
resultFields: {
|
||||
foo: { raw: false, snippet: true, snippetSize: 5 },
|
||||
bar: { raw: false, snippet: false },
|
||||
},
|
||||
});
|
||||
jest.spyOn(ResultSettingsLogic.actions, 'updateField');
|
||||
|
||||
ResultSettingsLogic.actions.toggleSnippetForField('bar');
|
||||
|
||||
expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('bar', {
|
||||
raw: false,
|
||||
snippet: true,
|
||||
snippetSize: 100,
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove rawSize value when toggling off', () => {
|
||||
mount({
|
||||
resultFields: {
|
||||
foo: { raw: false, snippet: true, snippetSize: 5 },
|
||||
bar: { raw: false, snippet: true, snippetSize: 5 },
|
||||
},
|
||||
});
|
||||
jest.spyOn(ResultSettingsLogic.actions, 'updateField');
|
||||
|
||||
ResultSettingsLogic.actions.toggleSnippetForField('bar');
|
||||
|
||||
expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('bar', {
|
||||
raw: false,
|
||||
snippet: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should still work if the object is empty', () => {
|
||||
mount({
|
||||
resultFields: {
|
||||
foo: { raw: false, snippet: true, snippetSize: 5 },
|
||||
bar: {},
|
||||
},
|
||||
});
|
||||
jest.spyOn(ResultSettingsLogic.actions, 'updateField');
|
||||
|
||||
ResultSettingsLogic.actions.toggleSnippetForField('bar');
|
||||
|
||||
expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('bar', {
|
||||
snippet: true,
|
||||
snippetSize: 100,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleSnippetFallbackForField', () => {
|
||||
it('should toggle the snippetFallback value for a field', () => {
|
||||
mount({
|
||||
resultFields: {
|
||||
foo: { raw: false, snippet: true, snippetSize: 5, snippetFallback: true },
|
||||
bar: { raw: false, snippet: false },
|
||||
},
|
||||
});
|
||||
jest.spyOn(ResultSettingsLogic.actions, 'updateField');
|
||||
|
||||
ResultSettingsLogic.actions.toggleSnippetFallbackForField('foo');
|
||||
|
||||
expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('foo', {
|
||||
raw: false,
|
||||
snippet: true,
|
||||
snippetSize: 5,
|
||||
snippetFallback: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateRawSizeForField', () => {
|
||||
it('should update the rawSize value for a field', () => {
|
||||
mount({
|
||||
resultFields: {
|
||||
foo: { raw: false, snippet: true, snippetSize: 5, snippetFallback: true },
|
||||
bar: { raw: true, rawSize: 5, snippet: false },
|
||||
},
|
||||
});
|
||||
jest.spyOn(ResultSettingsLogic.actions, 'updateField');
|
||||
|
||||
ResultSettingsLogic.actions.updateRawSizeForField('bar', 7);
|
||||
|
||||
expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('bar', {
|
||||
raw: true,
|
||||
rawSize: 7,
|
||||
snippet: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateSnippetSizeForField', () => {
|
||||
it('should update the snippetSize value for a field', () => {
|
||||
mount({
|
||||
resultFields: {
|
||||
foo: { raw: false, snippet: true, snippetSize: 5, snippetFallback: true },
|
||||
bar: { raw: true, rawSize: 5, snippet: false },
|
||||
},
|
||||
});
|
||||
jest.spyOn(ResultSettingsLogic.actions, 'updateField');
|
||||
|
||||
ResultSettingsLogic.actions.updateSnippetSizeForField('foo', 7);
|
||||
|
||||
expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('foo', {
|
||||
raw: false,
|
||||
snippet: true,
|
||||
snippetSize: 7,
|
||||
snippetFallback: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('initializeResultSettingsData', () => {
|
||||
it('should remove the snippet size set on a field', () => {
|
||||
mount({
|
||||
resultFields: {
|
||||
foo: { raw: false, snippet: true, snippetSize: 5 },
|
||||
bar: { raw: true, rawSize: 5, snippet: false },
|
||||
},
|
||||
});
|
||||
jest.spyOn(ResultSettingsLogic.actions, 'updateField');
|
||||
|
||||
ResultSettingsLogic.actions.clearSnippetSizeForField('foo');
|
||||
|
||||
expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('foo', {
|
||||
raw: false,
|
||||
snippet: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('initializeResultFields', () => {
|
||||
it('should make an API call and set state based on the response', async () => {
|
||||
mount();
|
||||
http.get.mockReturnValueOnce(
|
||||
Promise.resolve({
|
||||
searchSettings: {
|
||||
result_fields: serverFieldResultSettings,
|
||||
},
|
||||
schema,
|
||||
schemaConflicts,
|
||||
})
|
||||
);
|
||||
jest.spyOn(ResultSettingsLogic.actions, 'initializeResultFields');
|
||||
|
||||
ResultSettingsLogic.actions.initializeResultSettingsData();
|
||||
await nextTick();
|
||||
|
||||
expect(http.get).toHaveBeenCalledWith(
|
||||
'/api/app_search/engines/test-engine/result_settings/details'
|
||||
);
|
||||
expect(ResultSettingsLogic.actions.initializeResultFields).toHaveBeenCalledWith(
|
||||
serverFieldResultSettings,
|
||||
schema,
|
||||
schemaConflicts
|
||||
);
|
||||
});
|
||||
|
||||
it('handles errors', async () => {
|
||||
mount();
|
||||
http.get.mockReturnValueOnce(Promise.reject('error'));
|
||||
|
||||
ResultSettingsLogic.actions.initializeResultSettingsData();
|
||||
await nextTick();
|
||||
|
||||
expect(flashAPIErrors).toHaveBeenCalledWith('error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('saveResultSettings', () => {
|
||||
it('should make an API call to update result settings and update state accordingly', async () => {
|
||||
mount({
|
||||
schema,
|
||||
});
|
||||
http.put.mockReturnValueOnce(
|
||||
Promise.resolve({
|
||||
result_fields: serverFieldResultSettings,
|
||||
})
|
||||
);
|
||||
jest.spyOn(ResultSettingsLogic.actions, 'saving');
|
||||
jest.spyOn(ResultSettingsLogic.actions, 'initializeResultFields');
|
||||
|
||||
ResultSettingsLogic.actions.saveResultSettings(serverFieldResultSettings);
|
||||
|
||||
expect(ResultSettingsLogic.actions.saving).toHaveBeenCalled();
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(http.put).toHaveBeenCalledWith(
|
||||
'/api/app_search/engines/test-engine/result_settings',
|
||||
{
|
||||
body: JSON.stringify({
|
||||
result_fields: serverFieldResultSettings,
|
||||
}),
|
||||
}
|
||||
);
|
||||
expect(ResultSettingsLogic.actions.initializeResultFields).toHaveBeenCalledWith(
|
||||
serverFieldResultSettings,
|
||||
schema
|
||||
);
|
||||
});
|
||||
|
||||
it('handles errors', async () => {
|
||||
mount();
|
||||
http.put.mockReturnValueOnce(Promise.reject('error'));
|
||||
|
||||
ResultSettingsLogic.actions.saveResultSettings(serverFieldResultSettings);
|
||||
await nextTick();
|
||||
|
||||
expect(flashAPIErrors).toHaveBeenCalledWith('error');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,9 +6,16 @@
|
|||
*/
|
||||
|
||||
import { kea, MakeLogicType } from 'kea';
|
||||
import { omit, isEqual } from 'lodash';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { flashAPIErrors, setSuccessMessage } from '../../../shared/flash_messages';
|
||||
import { HttpLogic } from '../../../shared/http';
|
||||
import { Schema, SchemaConflicts } from '../../../shared/types';
|
||||
import { EngineLogic } from '../engine';
|
||||
|
||||
import { DEFAULT_SNIPPET_SIZE } from './constants';
|
||||
import {
|
||||
FieldResultSetting,
|
||||
FieldResultSettingObject,
|
||||
|
@ -17,6 +24,8 @@ import {
|
|||
} from './types';
|
||||
|
||||
import {
|
||||
areFieldsAtDefaultSettings,
|
||||
areFieldsEmpty,
|
||||
clearAllFields,
|
||||
clearAllServerFields,
|
||||
convertServerResultFieldsToResultFields,
|
||||
|
@ -46,9 +55,21 @@ interface ResultSettingsActions {
|
|||
resetAllFields(): void;
|
||||
updateField(
|
||||
fieldName: string,
|
||||
settings: FieldResultSetting
|
||||
settings: FieldResultSetting | {}
|
||||
): { fieldName: string; settings: FieldResultSetting };
|
||||
saving(): void;
|
||||
// Listeners
|
||||
clearRawSizeForField(fieldName: string): { fieldName: string };
|
||||
clearSnippetSizeForField(fieldName: string): { fieldName: string };
|
||||
toggleRawForField(fieldName: string): { fieldName: string };
|
||||
toggleSnippetForField(fieldName: string): { fieldName: string };
|
||||
toggleSnippetFallbackForField(fieldName: string): { fieldName: string };
|
||||
updateRawSizeForField(fieldName: string, size: number): { fieldName: string; size: number };
|
||||
updateSnippetSizeForField(fieldName: string, size: number): { fieldName: string; size: number };
|
||||
initializeResultSettingsData(): void;
|
||||
saveResultSettings(
|
||||
resultFields: ServerFieldResultSettingObject
|
||||
): { resultFields: ServerFieldResultSettingObject };
|
||||
}
|
||||
|
||||
interface ResultSettingsValues {
|
||||
|
@ -62,6 +83,11 @@ interface ResultSettingsValues {
|
|||
lastSavedResultFields: FieldResultSettingObject;
|
||||
schema: Schema;
|
||||
schemaConflicts: SchemaConflicts;
|
||||
// Selectors
|
||||
resultFieldsAtDefaultSettings: boolean;
|
||||
resultFieldsEmpty: boolean;
|
||||
stagedUpdates: true;
|
||||
reducedServerResultFields: ServerFieldResultSettingObject;
|
||||
}
|
||||
|
||||
export const ResultSettingsLogic = kea<MakeLogicType<ResultSettingsValues, ResultSettingsActions>>({
|
||||
|
@ -90,6 +116,15 @@ export const ResultSettingsLogic = kea<MakeLogicType<ResultSettingsValues, Resul
|
|||
resetAllFields: () => true,
|
||||
updateField: (fieldName, settings) => ({ fieldName, settings }),
|
||||
saving: () => true,
|
||||
clearRawSizeForField: (fieldName) => ({ fieldName }),
|
||||
clearSnippetSizeForField: (fieldName) => ({ fieldName }),
|
||||
toggleRawForField: (fieldName) => ({ fieldName }),
|
||||
toggleSnippetForField: (fieldName) => ({ fieldName }),
|
||||
toggleSnippetFallbackForField: (fieldName) => ({ fieldName }),
|
||||
updateRawSizeForField: (fieldName, size) => ({ fieldName, size }),
|
||||
updateSnippetSizeForField: (fieldName, size) => ({ fieldName, size }),
|
||||
initializeResultSettingsData: () => true,
|
||||
saveResultSettings: (resultFields) => ({ resultFields }),
|
||||
}),
|
||||
reducers: () => ({
|
||||
dataLoading: [
|
||||
|
@ -187,4 +222,122 @@ export const ResultSettingsLogic = kea<MakeLogicType<ResultSettingsValues, Resul
|
|||
},
|
||||
],
|
||||
}),
|
||||
selectors: ({ selectors }) => ({
|
||||
resultFieldsAtDefaultSettings: [
|
||||
() => [selectors.resultFields],
|
||||
(resultFields) => areFieldsAtDefaultSettings(resultFields),
|
||||
],
|
||||
resultFieldsEmpty: [
|
||||
() => [selectors.resultFields],
|
||||
(resultFields) => areFieldsEmpty(resultFields),
|
||||
],
|
||||
stagedUpdates: [
|
||||
() => [selectors.lastSavedResultFields, selectors.resultFields],
|
||||
(lastSavedResultFields, resultFields) => !isEqual(lastSavedResultFields, resultFields),
|
||||
],
|
||||
reducedServerResultFields: [
|
||||
() => [selectors.serverResultFields],
|
||||
(serverResultFields: ServerFieldResultSettingObject) =>
|
||||
Object.entries(serverResultFields).reduce(
|
||||
(acc: ServerFieldResultSettingObject, [fieldName, resultSetting]) => {
|
||||
if (resultSetting.raw || resultSetting.snippet) {
|
||||
acc[fieldName] = resultSetting;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
),
|
||||
],
|
||||
}),
|
||||
listeners: ({ actions, values }) => ({
|
||||
clearRawSizeForField: ({ fieldName }) => {
|
||||
actions.updateField(fieldName, omit(values.resultFields[fieldName], ['rawSize']));
|
||||
},
|
||||
clearSnippetSizeForField: ({ fieldName }) => {
|
||||
actions.updateField(fieldName, omit(values.resultFields[fieldName], ['snippetSize']));
|
||||
},
|
||||
toggleRawForField: ({ fieldName }) => {
|
||||
// We cast this because it could be an empty object, which we can still treat as a FieldResultSetting safely
|
||||
const field = values.resultFields[fieldName] as FieldResultSetting;
|
||||
const raw = !field.raw;
|
||||
actions.updateField(fieldName, {
|
||||
...omit(field, ['rawSize']),
|
||||
raw,
|
||||
...(raw ? { rawSize: field.rawSize } : {}),
|
||||
});
|
||||
},
|
||||
toggleSnippetForField: ({ fieldName }) => {
|
||||
// We cast this because it could be an empty object, which we can still treat as a FieldResultSetting safely
|
||||
const field = values.resultFields[fieldName] as FieldResultSetting;
|
||||
const snippet = !field.snippet;
|
||||
|
||||
actions.updateField(fieldName, {
|
||||
...omit(field, ['snippetSize']),
|
||||
snippet,
|
||||
...(snippet ? { snippetSize: DEFAULT_SNIPPET_SIZE } : {}),
|
||||
});
|
||||
},
|
||||
toggleSnippetFallbackForField: ({ fieldName }) => {
|
||||
// We cast this because it could be an empty object, which we can still treat as a FieldResultSetting safely
|
||||
const field = values.resultFields[fieldName] as FieldResultSetting;
|
||||
actions.updateField(fieldName, {
|
||||
...field,
|
||||
snippetFallback: !field.snippetFallback,
|
||||
});
|
||||
},
|
||||
updateRawSizeForField: ({ fieldName, size }) => {
|
||||
actions.updateField(fieldName, { ...values.resultFields[fieldName], rawSize: size });
|
||||
},
|
||||
updateSnippetSizeForField: ({ fieldName, size }) => {
|
||||
actions.updateField(fieldName, { ...values.resultFields[fieldName], snippetSize: size });
|
||||
},
|
||||
initializeResultSettingsData: async () => {
|
||||
const { http } = HttpLogic.values;
|
||||
const { engineName } = EngineLogic.values;
|
||||
|
||||
const url = `/api/app_search/engines/${engineName}/result_settings/details`;
|
||||
|
||||
try {
|
||||
const {
|
||||
schema,
|
||||
schemaConflicts,
|
||||
searchSettings: { result_fields: serverFieldResultSettings },
|
||||
} = await http.get(url);
|
||||
|
||||
actions.initializeResultFields(serverFieldResultSettings, schema, schemaConflicts);
|
||||
} catch (e) {
|
||||
flashAPIErrors(e);
|
||||
}
|
||||
},
|
||||
saveResultSettings: async ({ resultFields }) => {
|
||||
actions.saving();
|
||||
|
||||
const { http } = HttpLogic.values;
|
||||
const { engineName } = EngineLogic.values;
|
||||
const url = `/api/app_search/engines/${engineName}/result_settings`;
|
||||
|
||||
actions.saving();
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await http.put(url, {
|
||||
body: JSON.stringify({
|
||||
result_fields: resultFields,
|
||||
}),
|
||||
});
|
||||
} catch (e) {
|
||||
flashAPIErrors(e);
|
||||
}
|
||||
|
||||
actions.initializeResultFields(response.result_fields, values.schema);
|
||||
setSuccessMessage(
|
||||
i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.resultSettings.saveSuccessMessage',
|
||||
{
|
||||
defaultMessage: 'Result settings have been saved successfully.',
|
||||
}
|
||||
)
|
||||
);
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -34,4 +34,4 @@ export interface FieldResultSetting {
|
|||
snippetFallback: boolean;
|
||||
}
|
||||
|
||||
export type FieldResultSettingObject = Record<string, FieldResultSetting>;
|
||||
export type FieldResultSettingObject = Record<string, FieldResultSetting | {}>;
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
import { SchemaTypes } from '../../../shared/types';
|
||||
|
||||
import {
|
||||
areFieldsAtDefaultSettings,
|
||||
areFieldsEmpty,
|
||||
convertServerResultFieldsToResultFields,
|
||||
convertToServerFieldResultSetting,
|
||||
clearAllServerFields,
|
||||
|
@ -172,3 +174,68 @@ describe('splitResultFields', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('areFieldsEmpty', () => {
|
||||
it('should return true if all fields are empty objects', () => {
|
||||
expect(
|
||||
areFieldsEmpty({
|
||||
foo: {},
|
||||
bar: {},
|
||||
})
|
||||
).toBe(true);
|
||||
});
|
||||
it('should return false otherwise', () => {
|
||||
expect(
|
||||
areFieldsEmpty({
|
||||
foo: {
|
||||
raw: true,
|
||||
rawSize: 5,
|
||||
snippet: false,
|
||||
snippetFallback: false,
|
||||
},
|
||||
bar: {
|
||||
raw: true,
|
||||
rawSize: 5,
|
||||
snippet: false,
|
||||
snippetFallback: false,
|
||||
},
|
||||
})
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('areFieldsAtDefaultSettings', () => {
|
||||
it('will return true if all settings for all fields are at their defaults', () => {
|
||||
expect(
|
||||
areFieldsAtDefaultSettings({
|
||||
foo: {
|
||||
raw: true,
|
||||
snippet: false,
|
||||
snippetFallback: false,
|
||||
},
|
||||
bar: {
|
||||
raw: true,
|
||||
snippet: false,
|
||||
snippetFallback: false,
|
||||
},
|
||||
})
|
||||
).toEqual(true);
|
||||
});
|
||||
|
||||
it('will return false otherwise', () => {
|
||||
expect(
|
||||
areFieldsAtDefaultSettings({
|
||||
foo: {
|
||||
raw: true,
|
||||
snippet: false,
|
||||
snippetFallback: false,
|
||||
},
|
||||
bar: {
|
||||
raw: false,
|
||||
snippet: true,
|
||||
snippetFallback: true,
|
||||
},
|
||||
})
|
||||
).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { isEqual, isEmpty } from 'lodash';
|
||||
|
||||
import { Schema } from '../../../shared/types';
|
||||
|
||||
import { DEFAULT_FIELD_SETTINGS, DISABLED_FIELD_SETTINGS } from './constants';
|
||||
|
@ -115,3 +117,17 @@ export const splitResultFields = (resultFields: FieldResultSettingObject, schema
|
|||
|
||||
return { textResultFields, nonTextResultFields };
|
||||
};
|
||||
|
||||
export const areFieldsEmpty = (fields: FieldResultSettingObject) => {
|
||||
const anyNonEmptyField = Object.values(fields).find((resultSettings) => {
|
||||
return !isEmpty(resultSettings);
|
||||
});
|
||||
return !anyNonEmptyField;
|
||||
};
|
||||
|
||||
export const areFieldsAtDefaultSettings = (fields: FieldResultSettingObject) => {
|
||||
const anyNonDefaultSettingsValue = Object.values(fields).find((resultSettings) => {
|
||||
return !isEqual(resultSettings, DEFAULT_FIELD_SETTINGS);
|
||||
});
|
||||
return !anyNonDefaultSettingsValue;
|
||||
};
|
||||
|
|
|
@ -12,6 +12,7 @@ import { registerCredentialsRoutes } from './credentials';
|
|||
import { registerCurationsRoutes } from './curations';
|
||||
import { registerDocumentsRoutes, registerDocumentRoutes } from './documents';
|
||||
import { registerEnginesRoutes } from './engines';
|
||||
import { registerResultSettingsRoutes } from './result_settings';
|
||||
import { registerRoleMappingsRoutes } from './role_mappings';
|
||||
import { registerSearchSettingsRoutes } from './search_settings';
|
||||
import { registerSettingsRoutes } from './settings';
|
||||
|
@ -26,4 +27,5 @@ export const registerAppSearchRoutes = (dependencies: RouteDependencies) => {
|
|||
registerCurationsRoutes(dependencies);
|
||||
registerSearchSettingsRoutes(dependencies);
|
||||
registerRoleMappingsRoutes(dependencies);
|
||||
registerResultSettingsRoutes(dependencies);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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 { mockDependencies, mockRequestHandler, MockRouter } from '../../__mocks__';
|
||||
|
||||
import { registerResultSettingsRoutes } from './result_settings';
|
||||
|
||||
const resultFields = {
|
||||
id: {
|
||||
raw: {},
|
||||
},
|
||||
hp: {
|
||||
raw: {},
|
||||
},
|
||||
name: {
|
||||
raw: {},
|
||||
},
|
||||
};
|
||||
|
||||
describe('result settings routes', () => {
|
||||
describe('GET /api/app_search/engines/{name}/result_settings/details', () => {
|
||||
const mockRouter = new MockRouter({
|
||||
method: 'get',
|
||||
path: '/api/app_search/engines/{engineName}/result_settings/details',
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
registerResultSettingsRoutes({
|
||||
...mockDependencies,
|
||||
router: mockRouter.router,
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a request to enterprise search', () => {
|
||||
mockRouter.callRoute({
|
||||
params: { engineName: 'some-engine' },
|
||||
});
|
||||
|
||||
expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
|
||||
path: '/as/engines/:engineName/result_settings/details',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('PUT /api/app_search/engines/{name}/result_settings', () => {
|
||||
const mockRouter = new MockRouter({
|
||||
method: 'put',
|
||||
path: '/api/app_search/engines/{engineName}/result_settings',
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
registerResultSettingsRoutes({
|
||||
...mockDependencies,
|
||||
router: mockRouter.router,
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a request to enterprise search', () => {
|
||||
mockRouter.callRoute({
|
||||
params: { engineName: 'some-engine' },
|
||||
body: {
|
||||
result_settings: resultFields,
|
||||
},
|
||||
});
|
||||
|
||||
expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
|
||||
path: '/as/engines/:engineName/result_settings',
|
||||
});
|
||||
});
|
||||
|
||||
describe('validates', () => {
|
||||
it('correctly', () => {
|
||||
const request = {
|
||||
body: {
|
||||
result_fields: resultFields,
|
||||
},
|
||||
};
|
||||
mockRouter.shouldValidate(request);
|
||||
});
|
||||
|
||||
it('missing required fields', () => {
|
||||
const request = { body: {} };
|
||||
mockRouter.shouldThrow(request);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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 { schema } from '@kbn/config-schema';
|
||||
|
||||
import { RouteDependencies } from '../../plugin';
|
||||
|
||||
const resultFields = schema.recordOf(schema.string(), schema.object({}, { unknowns: 'allow' }));
|
||||
|
||||
export function registerResultSettingsRoutes({
|
||||
router,
|
||||
enterpriseSearchRequestHandler,
|
||||
}: RouteDependencies) {
|
||||
router.get(
|
||||
{
|
||||
path: '/api/app_search/engines/{engineName}/result_settings/details',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
engineName: schema.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
enterpriseSearchRequestHandler.createRequest({
|
||||
path: '/as/engines/:engineName/result_settings/details',
|
||||
})
|
||||
);
|
||||
|
||||
router.put(
|
||||
{
|
||||
path: '/api/app_search/engines/{engineName}/result_settings',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
engineName: schema.string(),
|
||||
}),
|
||||
body: schema.object({
|
||||
result_fields: resultFields,
|
||||
}),
|
||||
},
|
||||
},
|
||||
enterpriseSearchRequestHandler.createRequest({
|
||||
path: '/as/engines/:engineName/result_settings',
|
||||
})
|
||||
);
|
||||
}
|
Loading…
Reference in a new issue