diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.tsx index 0ae3c8fd3b5dc..70adc91dd2b30 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.tsx @@ -19,6 +19,8 @@ import { DOCS_PREFIX } from '../../routes'; import { RelevanceTuningForm } from './relevance_tuning_form'; import { RelevanceTuningLayout } from './relevance_tuning_layout'; +import { RelevanceTuningPreview } from './relevance_tuning_preview'; + import { RelevanceTuningLogic } from '.'; interface Props { @@ -81,11 +83,13 @@ export const RelevanceTuning: React.FC = ({ engineBreadcrumb }) => { } return ( - - + + - + + + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.tsx index 87b9e1615774f..ab72f29a678c9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_form/relevance_tuning_form.tsx @@ -10,8 +10,6 @@ import React from 'react'; import { useActions, useValues } from 'kea'; import { - EuiPageHeader, - EuiPageHeaderSection, EuiTitle, EuiFieldSearch, EuiSpacer, @@ -44,37 +42,36 @@ export const RelevanceTuningForm: React.FC = () => { return (
- - - -

- {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.title', - { - defaultMessage: 'Manage fields', - } - )} -

-
-
-
- {schemaFields.length > FIELD_FILTER_CUTOFF && ( - setFilterValue(e.target.value)} - placeholder={i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.filterPlaceholder', + +

+ {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.title', { - defaultMessage: 'Filter {schemaFieldsLength} fields...', - values: { - schemaFieldsLength: schemaFields.length, - }, + defaultMessage: 'Manage fields', } )} - fullWidth - /> - )} +

+
+ {schemaFields.length > FIELD_FILTER_CUTOFF && ( + <> + setFilterValue(e.target.value)} + placeholder={i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.filterPlaceholder', + { + defaultMessage: 'Filter {schemaFieldsLength} fields...', + values: { + schemaFieldsLength: schemaFields.length, + }, + } + )} + fullWidth + /> + + + )} {filteredSchemaFields.map((fieldName) => ( { expect(mockEngineActions.initializeEngine).toHaveBeenCalled(); }); - it('will re-fetch the current engine after settings are updated if there were unconfirmed search fieldds', async () => { + it('will re-fetch the current engine after settings are updated if there were unconfirmed search fields', async () => { mockEngineValues.engine.unsearchedUnconfirmedFields = true; mount({}); http.put.mockReturnValueOnce(Promise.resolve(searchSettings)); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_preview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_preview.test.tsx new file mode 100644 index 0000000000000..ec6458a14b346 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_preview.test.tsx @@ -0,0 +1,110 @@ +/* + * 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, setMockValues } from '../../../__mocks__/kea.mock'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { EuiFieldSearch } from '@elastic/eui'; + +import { Result } from '../result/result'; + +import { RelevanceTuningPreview } from './relevance_tuning_preview'; + +describe('RelevanceTuningPreview', () => { + const result1 = { id: { raw: 1 } }; + const result2 = { id: { raw: 2 } }; + const result3 = { id: { raw: 3 } }; + + const actions = { + updateSearchValue: jest.fn(), + }; + + const values = { + searchResults: [result1, result2, result3], + engineName: 'foo', + isMetaEngine: false, + schema: {}, + }; + + beforeAll(() => { + setMockActions(actions); + setMockValues(values); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.find(EuiFieldSearch).prop('placeholder')).toBe('Search foo'); + + const results = wrapper.find(Result); + expect(results.length).toBe(3); + expect(results.at(0).prop('result')).toBe(result1); + expect(results.at(0).prop('isMetaEngine')).toBe(false); + expect(results.at(0).prop('showScore')).toBe(true); + expect(results.at(0).prop('schemaForTypeHighlights')).toBe(values.schema); + + expect(results.at(1).prop('result')).toBe(result2); + expect(results.at(2).prop('result')).toBe(result3); + + expect(wrapper.find('[data-test-subj="EmptyQueryPrompt"]').exists()).toBe(false); + expect(wrapper.find('[data-test-subj="NoResultsPrompt"]').exists()).toBe(false); + }); + + it('correctly indicates whether or not this is a meta engine in results', () => { + setMockValues({ + ...values, + isMetaEngine: true, + }); + + const wrapper = shallow(); + + const results = wrapper.find(Result); + expect(results.at(0).prop('isMetaEngine')).toBe(true); + expect(results.at(1).prop('isMetaEngine')).toBe(true); + expect(results.at(2).prop('isMetaEngine')).toBe(true); + }); + + it('renders a search box that will update search results whenever it is changed', () => { + const wrapper = shallow(); + + wrapper.find(EuiFieldSearch).simulate('change', { target: { value: 'some search text' } }); + + expect(actions.updateSearchValue).toHaveBeenCalledWith('some search text'); + }); + + it('will show user a prompt to enter a query if they have not entered one', () => { + setMockValues({ + ...values, + // Since `searchResults` is initialized as undefined, an undefined value indicates + // that no query has been performed, which means they have no yet entered a query + searchResults: undefined, + }); + + const wrapper = shallow(); + + expect(wrapper.find('[data-test-subj="EmptyQueryPrompt"]').exists()).toBe(true); + expect(wrapper.find('[data-test-subj="NoResultsPrompt"]').exists()).toBe(false); + }); + + it('will show user a no results message if their query returns no results', () => { + setMockValues({ + ...values, + searchResults: [], + }); + + const wrapper = shallow(); + + expect(wrapper.find('[data-test-subj="EmptyQueryPrompt"]').exists()).toBe(false); + expect(wrapper.find('[data-test-subj="NoResultsPrompt"]').exists()).toBe(true); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_preview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_preview.tsx new file mode 100644 index 0000000000000..298b692ac7b80 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_preview.tsx @@ -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 React from 'react'; + +import { useActions, useValues } from 'kea'; + +import { EuiEmptyPrompt, EuiFieldSearch, EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { EngineLogic } from '../engine'; +import { Result } from '../result/result'; + +import { RelevanceTuningLogic } from '.'; + +const emptyCallout = ( + +); + +const noResultsCallout = ( + +); + +export const RelevanceTuningPreview: React.FC = () => { + const { updateSearchValue } = useActions(RelevanceTuningLogic); + const { searchResults, schema } = useValues(RelevanceTuningLogic); + const { engineName, isMetaEngine } = useValues(EngineLogic); + + return ( + + +

+ {i18n.translate('xpack.enterpriseSearch.appSearch.engine.relevanceTuning.preview.title', { + defaultMessage: 'Preview', + })} +

+
+ + updateSearchValue(e.target.value)} + placeholder={i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.preview.searchPlaceholder', + { + defaultMessage: 'Search {engineName}', + values: { + engineName, + }, + } + )} + fullWidth + /> + {!searchResults && emptyCallout} + {searchResults && searchResults.length === 0 && noResultsCallout} + {searchResults && + searchResults.map((result) => { + return ( + + + + + ); + })} +
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.ts index 82b0497cd0946..d329c9b834b08 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.ts @@ -79,7 +79,7 @@ export function registerSearchSettingsRoutes({ engineName: schema.string(), }), body: schema.object({ - boosts, + boosts: schema.maybe(boosts), search_fields: searchFields, }), query: schema.object({