Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -81,11 +83,13 @@ export const RelevanceTuning: React.FC<Props> = ({ engineBreadcrumb }) => {
}

return (
<EuiFlexGroup>
<EuiFlexItem>
<EuiFlexGroup alignItems="flexStart">
<EuiFlexItem grow={3}>
<RelevanceTuningForm />
</EuiFlexItem>
<EuiFlexItem />
<EuiFlexItem grow={4}>
<RelevanceTuningPreview />
</EuiFlexItem>
</EuiFlexGroup>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import React from 'react';
import { useActions, useValues } from 'kea';

import {
EuiPageHeader,
EuiPageHeaderSection,
EuiTitle,
EuiFieldSearch,
EuiSpacer,
Expand Down Expand Up @@ -44,37 +42,36 @@ export const RelevanceTuningForm: React.FC = () => {
return (
<section className="relevanceTuningForm">
<form>
<EuiPageHeader>
<EuiPageHeaderSection>
<EuiTitle size="m">
<h2>
{i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.title',
{
defaultMessage: 'Manage fields',
}
)}
</h2>
</EuiTitle>
</EuiPageHeaderSection>
</EuiPageHeader>
{schemaFields.length > FIELD_FILTER_CUTOFF && (
<EuiFieldSearch
value={filterInputValue}
onChange={(e) => setFilterValue(e.target.value)}
placeholder={i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.filterPlaceholder',
<EuiTitle size="m">
<h2>
{i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.title',
{
defaultMessage: 'Filter {schemaFieldsLength} fields...',
values: {
schemaFieldsLength: schemaFields.length,
},
defaultMessage: 'Manage fields',
}
)}
fullWidth
/>
)}
</h2>
</EuiTitle>
<EuiSpacer />
{schemaFields.length > FIELD_FILTER_CUTOFF && (
<>
<EuiFieldSearch
value={filterInputValue}
onChange={(e) => setFilterValue(e.target.value)}
placeholder={i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.manageFields.filterPlaceholder',
{
defaultMessage: 'Filter {schemaFieldsLength} fields...',
values: {
schemaFieldsLength: schemaFields.length,
},
}
)}
fullWidth
/>
<EuiSpacer />
</>
)}
{filteredSchemaFields.map((fieldName) => (
<EuiPanel key={fieldName} className="relevanceTuningForm__panel">
<EuiAccordion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,7 @@ describe('RelevanceTuningLogic', () => {
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));
Expand Down
Original file line number Diff line number Diff line change
@@ -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(<RelevanceTuningPreview />);

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(<RelevanceTuningPreview />);

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(<RelevanceTuningPreview />);

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(<RelevanceTuningPreview />);

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(<RelevanceTuningPreview />);

expect(wrapper.find('[data-test-subj="EmptyQueryPrompt"]').exists()).toBe(false);
expect(wrapper.find('[data-test-subj="NoResultsPrompt"]').exists()).toBe(true);
});
});
Original file line number Diff line number Diff line change
@@ -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 = (
<EuiEmptyPrompt
data-test-subj="EmptyQueryPrompt"
body={i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.preview.enterQueryMessage',
{
defaultMessage: 'Enter a query to see search results',
}
)}
/>
);

const noResultsCallout = (
<EuiEmptyPrompt
data-test-subj="NoResultsPrompt"
body={i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.preview.noResultsMessage',
{
defaultMessage: 'No matching content found',
}
)}
/>
);

export const RelevanceTuningPreview: React.FC = () => {
const { updateSearchValue } = useActions(RelevanceTuningLogic);
const { searchResults, schema } = useValues(RelevanceTuningLogic);
const { engineName, isMetaEngine } = useValues(EngineLogic);

return (
<EuiPanel>
<EuiTitle size="m">
<h2>
{i18n.translate('xpack.enterpriseSearch.appSearch.engine.relevanceTuning.preview.title', {
defaultMessage: 'Preview',
})}
</h2>
</EuiTitle>
<EuiSpacer />
<EuiFieldSearch
onChange={(e) => 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 (
<React.Fragment key={result.id.raw}>
<EuiSpacer size="m" />
<Result
result={result}
showScore
isMetaEngine={isMetaEngine}
schemaForTypeHighlights={schema}
/>
</React.Fragment>
);
})}
</EuiPanel>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export function registerSearchSettingsRoutes({
engineName: schema.string(),
}),
body: schema.object({
boosts,
boosts: schema.maybe(boosts),
search_fields: searchFields,
}),
query: schema.object({
Expand Down