diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boost_item.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item.tsx similarity index 73% rename from x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boost_item.tsx rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item.tsx index e5a76bc586b80..641628c32659c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boost_item.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item.tsx @@ -9,26 +9,29 @@ import React, { useMemo } from 'react'; import { EuiFlexItem, EuiAccordion, EuiFlexGroup, EuiHideFor } from '@elastic/eui'; -import { BoostIcon } from '../../../boost_icon'; -import { BOOST_TYPE_TO_DISPLAY_MAP } from '../../../constants'; -import { Boost } from '../../../types'; -import { ValueBadge } from '../../value_badge'; +import { BoostIcon } from '../boost_icon'; +import { BOOST_TYPE_TO_DISPLAY_MAP } from '../constants'; +import { Boost } from '../types'; +import { ValueBadge } from '../value_badge'; +import { BoostItemContent } from './boost_item_content'; import { getBoostSummary } from './get_boost_summary'; interface Props { boost: Boost; id: string; + index: number; + name: string; } -export const BoostItem: React.FC = ({ id, boost }) => { +export const BoostItem: React.FC = ({ id, boost, index, name }) => { const summary = useMemo(() => getBoostSummary(boost), [boost]); return ( @@ -48,6 +51,8 @@ export const BoostItem: React.FC = ({ id, boost }) => { } paddingSize="s" - /> + > + + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/boost_item_content.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/boost_item_content.test.tsx new file mode 100644 index 0000000000000..3296155fdce5d --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/boost_item_content.test.tsx @@ -0,0 +1,99 @@ +/* + * 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 { setMockActions } from '../../../../../__mocks__/kea.mock'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { EuiButton, EuiRange } from '@elastic/eui'; + +import { BoostType } from '../../types'; + +import { BoostItemContent } from './boost_item_content'; +import { FunctionalBoostForm } from './functional_boost_form'; +import { ProximityBoostForm } from './proximity_boost_form'; +import { ValueBoostForm } from './value_boost_form'; + +describe('BoostItemContent', () => { + const actions = { + updateBoostFactor: jest.fn(), + deleteBoost: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + setMockActions(actions); + }); + + it('renders a value boost form if the provided boost is "value" boost', () => { + const boost = { + factor: 2, + type: 'value' as BoostType, + }; + + const wrapper = shallow(); + + expect(wrapper.find(ValueBoostForm).exists()).toBe(true); + expect(wrapper.find(FunctionalBoostForm).exists()).toBe(false); + expect(wrapper.find(ProximityBoostForm).exists()).toBe(false); + }); + + it('renders a functional boost form if the provided boost is "functional" boost', () => { + const boost = { + factor: 10, + type: 'functional' as BoostType, + }; + + const wrapper = shallow(); + + expect(wrapper.find(ValueBoostForm).exists()).toBe(false); + expect(wrapper.find(FunctionalBoostForm).exists()).toBe(true); + expect(wrapper.find(ProximityBoostForm).exists()).toBe(false); + }); + + it('renders a proximity boost form if the provided boost is "proximity" boost', () => { + const boost = { + factor: 8, + type: 'proximity' as BoostType, + }; + + const wrapper = shallow(); + + expect(wrapper.find(ValueBoostForm).exists()).toBe(false); + expect(wrapper.find(FunctionalBoostForm).exists()).toBe(false); + expect(wrapper.find(ProximityBoostForm).exists()).toBe(true); + }); + + it("renders an impact slider that can be used to update the boost's 'factor'", () => { + const boost = { + factor: 8, + type: 'proximity' as BoostType, + }; + + const wrapper = shallow(); + const impactSlider = wrapper.find(EuiRange); + expect(impactSlider.prop('value')).toBe(8); + + impactSlider.simulate('change', { target: { value: '2' } }); + + expect(actions.updateBoostFactor).toHaveBeenCalledWith('foo', 3, 2); + }); + + it("will delete the current boost if the 'Delete Boost' button is clicked", () => { + const boost = { + factor: 8, + type: 'proximity' as BoostType, + }; + + const wrapper = shallow(); + wrapper.find(EuiButton).simulate('click'); + + expect(actions.deleteBoost).toHaveBeenCalledWith('foo', 3); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/boost_item_content.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/boost_item_content.tsx new file mode 100644 index 0000000000000..7a19564543c81 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/boost_item_content.tsx @@ -0,0 +1,83 @@ +/* + * 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 { useActions } from 'kea'; + +import { EuiButton, EuiFormRow, EuiPanel, EuiRange, EuiSpacer } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { RelevanceTuningLogic } from '../..'; +import { Boost, BoostType } from '../../types'; + +import { FunctionalBoostForm } from './functional_boost_form'; +import { ProximityBoostForm } from './proximity_boost_form'; +import { ValueBoostForm } from './value_boost_form'; + +interface Props { + boost: Boost; + index: number; + name: string; +} + +export const BoostItemContent: React.FC = ({ boost, index, name }) => { + const { deleteBoost, updateBoostFactor } = useActions(RelevanceTuningLogic); + const { type } = boost; + + const getBoostForm = () => { + switch (type) { + case BoostType.Value: + return ; + case BoostType.Functional: + return ; + case BoostType.Proximity: + return ; + } + }; + + return ( + + {getBoostForm()} + + + + updateBoostFactor( + name, + index, + parseFloat((e as React.ChangeEvent).target.value) + ) + } + showInput + compressed + fullWidth + /> + + deleteBoost(name, index)}> + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.deleteBoostButtonLabel', + { + defaultMessage: 'Delete Boost', + } + )} + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/functional_boost_form.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/functional_boost_form.test.tsx new file mode 100644 index 0000000000000..11a224a71d7f8 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/functional_boost_form.test.tsx @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { setMockActions } from '../../../../../__mocks__/kea.mock'; + +import React from 'react'; + +import { shallow, ShallowWrapper } from 'enzyme'; + +import { EuiSelect } from '@elastic/eui'; + +import { Boost, BoostOperation, BoostType, FunctionalBoostFunction } from '../../types'; + +import { FunctionalBoostForm } from './functional_boost_form'; + +describe('FunctionalBoostForm', () => { + const boost: Boost = { + factor: 2, + type: 'functional' as BoostType, + function: 'logarithmic' as FunctionalBoostFunction, + operation: 'multiply' as BoostOperation, + }; + + const actions = { + updateBoostSelectOption: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + setMockActions(actions); + }); + + const functionSelect = (wrapper: ShallowWrapper) => wrapper.find(EuiSelect).at(0); + const operationSelect = (wrapper: ShallowWrapper) => wrapper.find(EuiSelect).at(1); + + it('renders select boxes with values from the provided boost selected', () => { + const wrapper = shallow(); + expect(functionSelect(wrapper).prop('value')).toEqual('logarithmic'); + expect(operationSelect(wrapper).prop('value')).toEqual('multiply'); + }); + + it('will update state when a user makes a selection', () => { + const wrapper = shallow(); + + functionSelect(wrapper).simulate('change', { + target: { + value: 'exponential', + }, + }); + expect(actions.updateBoostSelectOption).toHaveBeenCalledWith( + 'foo', + 3, + 'function', + 'exponential' + ); + + operationSelect(wrapper).simulate('change', { + target: { + value: 'add', + }, + }); + expect(actions.updateBoostSelectOption).toHaveBeenCalledWith('foo', 3, 'operation', 'add'); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/functional_boost_form.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/functional_boost_form.tsx new file mode 100644 index 0000000000000..d677fe5cbc069 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/functional_boost_form.tsx @@ -0,0 +1,89 @@ +/* + * 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 { useActions } from 'kea'; + +import { EuiFormRow, EuiSelect } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { RelevanceTuningLogic } from '../..'; +import { + BOOST_OPERATION_DISPLAY_MAP, + FUNCTIONAL_BOOST_FUNCTION_DISPLAY_MAP, +} from '../../constants'; +import { + Boost, + BoostFunction, + BoostOperation, + BoostType, + FunctionalBoostFunction, +} from '../../types'; + +interface Props { + boost: Boost; + index: number; + name: string; +} + +const functionOptions = Object.values(FunctionalBoostFunction).map((boostFunction) => ({ + value: boostFunction, + text: FUNCTIONAL_BOOST_FUNCTION_DISPLAY_MAP[boostFunction as FunctionalBoostFunction], +})); + +const operationOptions = Object.values(BoostOperation).map((boostOperation) => ({ + value: boostOperation, + text: BOOST_OPERATION_DISPLAY_MAP[boostOperation as BoostOperation], +})); + +export const FunctionalBoostForm: React.FC = ({ boost, index, name }) => { + const { updateBoostSelectOption } = useActions(RelevanceTuningLogic); + return ( + <> + + + updateBoostSelectOption(name, index, 'function', e.target.value as BoostFunction) + } + fullWidth + /> + + + + updateBoostSelectOption(name, index, 'operation', e.target.value as BoostOperation) + } + fullWidth + /> + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/index.ts new file mode 100644 index 0000000000000..1a13c486ca523 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export { BoostItemContent } from './boost_item_content'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/proximity_boost_form.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/proximity_boost_form.test.tsx new file mode 100644 index 0000000000000..6abbcc3d98862 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/proximity_boost_form.test.tsx @@ -0,0 +1,102 @@ +/* + * 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 { setMockActions } from '../../../../../__mocks__/kea.mock'; + +import React from 'react'; + +import { shallow, ShallowWrapper } from 'enzyme'; + +import { EuiFieldText, EuiSelect } from '@elastic/eui'; + +import { Boost, BoostType, ProximityBoostFunction } from '../../types'; + +import { ProximityBoostForm } from './proximity_boost_form'; + +describe('ProximityBoostForm', () => { + const boost: Boost = { + factor: 2, + type: 'proximity' as BoostType, + function: 'linear' as ProximityBoostFunction, + center: '2', + }; + + const actions = { + updateBoostSelectOption: jest.fn(), + updateBoostCenter: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + setMockActions(actions); + }); + + const functionSelect = (wrapper: ShallowWrapper) => wrapper.find(EuiSelect); + const centerInput = (wrapper: ShallowWrapper) => wrapper.find(EuiFieldText); + + it('renders input with values from the provided boost', () => { + const wrapper = shallow(); + expect(functionSelect(wrapper).prop('value')).toEqual('linear'); + expect(centerInput(wrapper).prop('defaultValue')).toEqual('2'); + }); + + describe('various boost values', () => { + const renderWithBoostValues = (boostValues: { + center?: Boost['center']; + function?: Boost['function']; + }) => { + return shallow( + + ); + }; + + it('will set the center value as a string if the value is a number', () => { + const wrapper = renderWithBoostValues({ center: 0 }); + expect(centerInput(wrapper).prop('defaultValue')).toEqual('0'); + }); + + it('will set the center value as an empty string if the value is undefined', () => { + const wrapper = renderWithBoostValues({ center: undefined }); + expect(centerInput(wrapper).prop('defaultValue')).toEqual(''); + }); + + it('will set the function to Guaussian if it is not already set', () => { + const wrapper = renderWithBoostValues({ function: undefined }); + expect(functionSelect(wrapper).prop('value')).toEqual('gaussian'); + }); + }); + + it('will update state when a user enters input', () => { + const wrapper = shallow(); + + functionSelect(wrapper).simulate('change', { + target: { + value: 'exponential', + }, + }); + expect(actions.updateBoostSelectOption).toHaveBeenCalledWith( + 'foo', + 3, + 'function', + 'exponential' + ); + + centerInput(wrapper).simulate('change', { + target: { + value: '5', + }, + }); + expect(actions.updateBoostCenter).toHaveBeenCalledWith('foo', 3, '5'); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/proximity_boost_form.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/proximity_boost_form.tsx new file mode 100644 index 0000000000000..f01f060bfcee6 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/proximity_boost_form.tsx @@ -0,0 +1,79 @@ +/* + * 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 { useActions } from 'kea'; + +import { EuiFieldText, EuiFormRow, EuiSelect } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { RelevanceTuningLogic } from '../..'; +import { PROXIMITY_BOOST_FUNCTION_DISPLAY_MAP } from '../../constants'; +import { Boost, BoostType, ProximityBoostFunction } from '../../types'; + +interface Props { + boost: Boost; + index: number; + name: string; +} + +export const ProximityBoostForm: React.FC = ({ boost, index, name }) => { + const { updateBoostSelectOption, updateBoostCenter } = useActions(RelevanceTuningLogic); + + const currentBoostCenter = boost.center !== undefined ? boost.center.toString() : ''; + const currentBoostFunction = boost.function || ProximityBoostFunction.Gaussian; + + const functionOptions = Object.values(ProximityBoostFunction).map((boostFunction) => ({ + value: boostFunction, + text: PROXIMITY_BOOST_FUNCTION_DISPLAY_MAP[boostFunction as ProximityBoostFunction], + })); + + return ( + <> + + + updateBoostSelectOption( + name, + index, + 'function', + e.target.value as ProximityBoostFunction + ) + } + fullWidth + /> + + + updateBoostCenter(name, index, e.target.value)} + fullWidth + /> + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/value_boost_form.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/value_boost_form.test.tsx new file mode 100644 index 0000000000000..447ca8e178349 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/value_boost_form.test.tsx @@ -0,0 +1,88 @@ +/* + * 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 { setMockActions } from '../../../../../__mocks__/kea.mock'; + +import React from 'react'; + +import { shallow, ShallowWrapper } from 'enzyme'; + +import { EuiButton, EuiButtonIcon, EuiFieldText } from '@elastic/eui'; + +import { Boost, BoostType } from '../../types'; + +import { ValueBoostForm } from './value_boost_form'; + +describe('ValueBoostForm', () => { + const boost: Boost = { + factor: 2, + type: 'value' as BoostType, + value: ['bar', '', 'baz'], + }; + + const actions = { + removeBoostValue: jest.fn(), + updateBoostValue: jest.fn(), + addBoostValue: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + setMockActions(actions); + }); + + const valueInput = (wrapper: ShallowWrapper, index: number) => + wrapper.find(EuiFieldText).at(index); + const removeButton = (wrapper: ShallowWrapper, index: number) => + wrapper.find(EuiButtonIcon).at(index); + const addButton = (wrapper: ShallowWrapper) => wrapper.find(EuiButton); + + it('renders a text input for each value from the boost', () => { + const wrapper = shallow(); + expect(valueInput(wrapper, 0).prop('value')).toEqual('bar'); + expect(valueInput(wrapper, 1).prop('value')).toEqual(''); + expect(valueInput(wrapper, 2).prop('value')).toEqual('baz'); + }); + + it('renders a single empty text box if the boost has no value', () => { + const wrapper = shallow( + + ); + expect(valueInput(wrapper, 0).prop('value')).toEqual(''); + }); + + it('updates the corresponding value in state whenever a user changes the value in a text input', () => { + const wrapper = shallow(); + + valueInput(wrapper, 2).simulate('change', { target: { value: 'new value' } }); + + expect(actions.updateBoostValue).toHaveBeenCalledWith('foo', 3, 2, 'new value'); + }); + + it('deletes a boost value when the Remove Value button is clicked', () => { + const wrapper = shallow(); + + removeButton(wrapper, 2).simulate('click'); + + expect(actions.removeBoostValue).toHaveBeenCalledWith('foo', 3, 2); + }); + + it('adds a new boost value when the Add Value is button clicked', () => { + const wrapper = shallow(); + + addButton(wrapper).simulate('click'); + + expect(actions.addBoostValue).toHaveBeenCalledWith('foo', 3); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/value_boost_form.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/value_boost_form.tsx new file mode 100644 index 0000000000000..15d19a9741d0a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boost_item_content/value_boost_form.tsx @@ -0,0 +1,79 @@ +/* + * 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 { useActions } from 'kea'; + +import { + EuiButton, + EuiButtonIcon, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { RelevanceTuningLogic } from '../..'; +import { Boost } from '../../types'; + +interface Props { + boost: Boost; + index: number; + name: string; +} + +export const ValueBoostForm: React.FC = ({ boost, index, name }) => { + const { updateBoostValue, removeBoostValue, addBoostValue } = useActions(RelevanceTuningLogic); + const values = boost.value || ['']; + + return ( + <> + {values.map((value, valueIndex) => ( + + + updateBoostValue(name, index, valueIndex, e.target.value)} + aria-label={i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.value.valueNameAriaLabel', + { + defaultMessage: 'Value name', + } + )} + autoFocus + /> + + + removeBoostValue(name, index, valueIndex)} + aria-label={i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.value.removeValueAriaLabel', + { + defaultMessage: 'Remove value', + } + )} + /> + + + ))} + + addBoostValue(name, index)}> + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.value.addValueButtonLabel', + { + defaultMessage: 'Add Value', + } + )} + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boosts.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boosts.scss similarity index 94% rename from x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boosts.scss rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boosts.scss index 53b3c233301b0..0e9b2b1035b36 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boosts.scss +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boosts.scss @@ -3,7 +3,7 @@ min-width: $euiSizeXXL * 4; } - &__itemContent { + &__itemButton { width: 100%; } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boosts.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boosts.test.tsx similarity index 65% rename from x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boosts.test.tsx rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boosts.test.tsx index b313e16c0bda1..75c22d2ae9473 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boosts.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boosts.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { setMockActions } from '../../../../../../__mocks__/kea.mock'; +import { setMockActions } from '../../../../__mocks__/kea.mock'; import React from 'react'; @@ -13,8 +13,11 @@ import { shallow } from 'enzyme'; import { EuiSuperSelect } from '@elastic/eui'; -import { SchemaTypes } from '../../../../../../shared/types'; +import { SchemaTypes } from '../../../../shared/types'; +import { BoostType } from '../types'; + +import { BoostItem } from './boost_item'; import { Boosts } from './boosts'; describe('Boosts', () => { @@ -68,4 +71,33 @@ describe('Boosts', () => { expect(actions.addBoost).toHaveBeenCalledWith('foo', 'functional'); }); + + it('will render a list of boosts', () => { + const boost1 = { + factor: 2, + type: 'value' as BoostType, + }; + const boost2 = { + factor: 10, + type: 'functional' as BoostType, + }; + const boost3 = { + factor: 8, + type: 'proximity' as BoostType, + }; + + const wrapper = shallow( + + ); + + const boostItems = wrapper.find(BoostItem); + expect(boostItems.at(0).prop('boost')).toEqual(boost1); + expect(boostItems.at(1).prop('boost')).toEqual(boost2); + expect(boostItems.at(2).prop('boost')).toEqual(boost3); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boosts.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boosts.tsx similarity index 86% rename from x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boosts.tsx rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boosts.tsx index 1ad27346d2630..d6d43ea7beab0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/boosts.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/boosts.tsx @@ -13,13 +13,13 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle, EuiSuperSelect } from '@ import { i18n } from '@kbn/i18n'; -import { TEXT } from '../../../../../../shared/constants/field_types'; -import { SchemaTypes } from '../../../../../../shared/types'; +import { TEXT } from '../../../../shared/constants/field_types'; +import { SchemaTypes } from '../../../../shared/types'; -import { BoostIcon } from '../../../boost_icon'; -import { FUNCTIONAL_DISPLAY, PROXIMITY_DISPLAY, VALUE_DISPLAY } from '../../../constants'; -import { RelevanceTuningLogic } from '../../../relevance_tuning_logic'; -import { Boost, BoostType } from '../../../types'; +import { BoostIcon } from '../boost_icon'; +import { FUNCTIONAL_DISPLAY, PROXIMITY_DISPLAY, VALUE_DISPLAY } from '../constants'; +import { RelevanceTuningLogic } from '../relevance_tuning_logic'; +import { Boost, BoostType } from '../types'; import { BoostItem } from './boost_item'; @@ -111,7 +111,13 @@ export const Boosts: React.FC = ({ name, type, boosts = [] }) => { {boosts.map((boost, index) => ( - + ))} ); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/get_boost_summary.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/get_boost_summary.test.ts similarity index 80% rename from x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/get_boost_summary.test.ts rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/get_boost_summary.test.ts index f6852569213a6..4d78fe8f06739 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/get_boost_summary.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/get_boost_summary.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { Boost, BoostFunction, BoostType, BoostOperation } from '../../../types'; +import { Boost, BoostFunction, BoostType, BoostOperation, FunctionalBoostFunction } from '../types'; import { getBoostSummary } from './get_boost_summary'; @@ -29,6 +29,15 @@ describe('getBoostSummary', () => { }) ).toEqual(''); }); + + it('filters out empty values', () => { + expect( + getBoostSummary({ + ...boost, + value: [' ', '', 'foo', '', 'bar'], + }) + ).toEqual('foo,bar'); + }); }); describe('when the boost type is "proximity"', () => { @@ -55,18 +64,20 @@ describe('getBoostSummary', () => { describe('when the boost type is "functional"', () => { const boost: Boost = { type: BoostType.Functional, - function: BoostFunction.Gaussian, + function: FunctionalBoostFunction.Logarithmic, operation: BoostOperation.Add, factor: 5, }; it('creates a summary that is name of the function and operation', () => { - expect(getBoostSummary(boost)).toEqual('gaussian add'); + expect(getBoostSummary(boost)).toEqual('logarithmic add'); }); it('prints empty if function or operation is missing', () => { expect(getBoostSummary({ ...boost, function: undefined })).toEqual(BoostOperation.Add); - expect(getBoostSummary({ ...boost, operation: undefined })).toEqual(BoostFunction.Gaussian); + expect(getBoostSummary({ ...boost, operation: undefined })).toEqual( + FunctionalBoostFunction.Logarithmic + ); expect(getBoostSummary({ ...boost, function: undefined, operation: undefined })).toEqual(''); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/get_boost_summary.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/get_boost_summary.ts similarity index 80% rename from x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/get_boost_summary.ts rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/get_boost_summary.ts index f3922ebb0fffe..71b1a6136cf65 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/get_boost_summary.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/get_boost_summary.ts @@ -5,11 +5,11 @@ * 2.0. */ -import { Boost, BoostType } from '../../../types'; +import { Boost, BoostType } from '../types'; export const getBoostSummary = (boost: Boost): string => { if (boost.type === BoostType.Value) { - return !boost.value ? '' : boost.value.join(','); + return !boost.value ? '' : boost.value.filter((v) => v.trim() !== '').join(','); } else if (boost.type === BoostType.Proximity) { return boost.function || ''; } else { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/index.ts similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/boosts/index.ts rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/boosts/index.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/constants.ts index 9fdbb8e979b31..8131a6a3a57c6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/constants.ts @@ -7,7 +7,12 @@ import { i18n } from '@kbn/i18n'; -import { BoostType } from './types'; +import { + BoostOperation, + BoostType, + FunctionalBoostFunction, + ProximityBoostFunction, +} from './types'; export const FIELD_FILTER_CUTOFF = 10; @@ -59,6 +64,7 @@ export const VALUE_DISPLAY = i18n.translate( defaultMessage: 'Value', } ); + export const BOOST_TYPE_TO_DISPLAY_MAP = { [BoostType.Proximity]: PROXIMITY_DISPLAY, [BoostType.Functional]: FUNCTIONAL_DISPLAY, @@ -70,3 +76,62 @@ export const BOOST_TYPE_TO_ICON_MAP = { [BoostType.Functional]: 'tokenFunction', [BoostType.Proximity]: 'tokenGeo', }; + +export const ADD_DISPLAY = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.addOperationDropDownOptionLabel', + { + defaultMessage: 'Add', + } +); + +export const MULTIPLY_DISPLAY = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.multiplyOperationDropDownOptionLabel', + { + defaultMessage: 'Multiply', + } +); + +export const BOOST_OPERATION_DISPLAY_MAP = { + [BoostOperation.Add]: ADD_DISPLAY, + [BoostOperation.Multiply]: MULTIPLY_DISPLAY, +}; + +export const LOGARITHMIC_DISPLAY = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.logarithmicBoostFunctionDropDownOptionLabel', + { + defaultMessage: 'Logarithmic', + } +); + +export const GAUSSIAN_DISPLAY = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.gaussianFunctionDropDownOptionLabel', + { + defaultMessage: 'Gaussian', + } +); + +export const EXPONENTIAL_DISPLAY = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.exponentialFunctionDropDownOptionLabel', + { + defaultMessage: 'Exponential', + } +); + +export const LINEAR_DISPLAY = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.boosts.linearFunctionDropDownOptionLabel', + { + defaultMessage: 'Linear', + } +); + +export const PROXIMITY_BOOST_FUNCTION_DISPLAY_MAP = { + [ProximityBoostFunction.Gaussian]: GAUSSIAN_DISPLAY, + [ProximityBoostFunction.Exponential]: EXPONENTIAL_DISPLAY, + [ProximityBoostFunction.Linear]: LINEAR_DISPLAY, +}; + +export const FUNCTIONAL_BOOST_FUNCTION_DISPLAY_MAP = { + [FunctionalBoostFunction.Logarithmic]: LOGARITHMIC_DISPLAY, + [FunctionalBoostFunction.Exponential]: EXPONENTIAL_DISPLAY, + [FunctionalBoostFunction.Linear]: LINEAR_DISPLAY, +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.scss index 749fca6f79811..9795564da04d5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.scss +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.scss @@ -17,4 +17,10 @@ } } } + + .relevanceTuningAccordionItem { + border: none; + border-top: $euiBorderThin; + border-radius: 0; + } } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.test.tsx index 6043e7ae65b26..674bb91929a76 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.test.tsx @@ -13,9 +13,9 @@ import { SchemaTypes } from '../../../../shared/types'; import { BoostIcon } from '../boost_icon'; import { Boost, BoostType, SearchField } from '../types'; +import { ValueBadge } from '../value_badge'; import { RelevanceTuningItem } from './relevance_tuning_item'; -import { ValueBadge } from './value_badge'; describe('RelevanceTuningItem', () => { const props = { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.tsx index 38cec4825cfe7..f7f4c64622fa6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item.tsx @@ -13,8 +13,7 @@ import { SchemaTypes } from '../../../../shared/types'; import { BoostIcon } from '../boost_icon'; import { Boost, SearchField } from '../types'; - -import { ValueBadge } from './value_badge'; +import { ValueBadge } from '../value_badge'; interface Props { name: string; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.scss deleted file mode 100644 index 63718a95551fa..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.scss +++ /dev/null @@ -1,6 +0,0 @@ -.relevanceTuningForm { - &__itemContent { - border: none; - border-top: $euiBorderThin; - } -} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.tsx index 29ab559485d77..e780a4de07252 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_item_content/relevance_tuning_item_content.tsx @@ -11,14 +11,12 @@ import { EuiPanel } from '@elastic/eui'; import { SchemaTypes } from '../../../../../shared/types'; +import { Boosts } from '../../boosts'; import { Boost, SearchField } from '../../types'; -import { Boosts } from './boosts'; import { TextSearchToggle } from './text_search_toggle'; import { WeightSlider } from './weight_slider'; -import './relevance_tuning_item_content.scss'; - interface Props { name: string; type: SchemaTypes; @@ -29,7 +27,7 @@ interface Props { export const RelevanceTuningItemContent: React.FC = ({ name, type, boosts, field }) => { return ( <> - + {field && } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts index a7ee6f9755fc4..8ce07dc699cbb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts @@ -9,7 +9,7 @@ import { LogicMounter, mockFlashMessageHelpers, mockHttpValues } from '../../../ import { nextTick } from '@kbn/test/jest'; -import { Boost, BoostFunction, BoostOperation, BoostType } from './types'; +import { Boost, BoostOperation, BoostType, FunctionalBoostFunction } from './types'; import { RelevanceTuningLogic } from './'; @@ -1053,14 +1053,14 @@ describe('RelevanceTuningLogic', () => { 'foo', 1, 'function', - BoostFunction.Exponential + FunctionalBoostFunction.Exponential ); expect(RelevanceTuningLogic.actions.setSearchSettings).toHaveBeenCalledWith( searchSettingsWithBoost({ factor: 1, type: BoostType.Functional, - function: BoostFunction.Exponential, + function: FunctionalBoostFunction.Exponential, }) ); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/types.ts index 95bd33aac5b9f..16da5868da681 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/types.ts @@ -11,15 +11,23 @@ export enum BoostType { Proximity = 'proximity', } -export enum BoostFunction { +export enum FunctionalBoostFunction { + Logarithmic = 'logarithmic', + Exponential = 'exponential', + Linear = 'linear', +} + +export enum ProximityBoostFunction { Gaussian = 'gaussian', Exponential = 'exponential', Linear = 'linear', } +export type BoostFunction = FunctionalBoostFunction | ProximityBoostFunction; + export enum BoostOperation { Add = 'add', - Multiple = 'multiply', + Multiply = 'multiply', } export interface BaseBoost { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/value_badge.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/value_badge.scss similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/value_badge.scss rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/value_badge.scss diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/value_badge.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/value_badge.tsx similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/value_badge.tsx rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/value_badge.tsx