Skip to content

Commit

Permalink
[App Search] Added relevance tuning search preview (elastic#93054)
Browse files Browse the repository at this point in the history
  • Loading branch information
JasonStoltz authored and kibanamachine committed Mar 4, 2021
1 parent d7ddd67 commit 21f9762
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 34 deletions.
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

0 comments on commit 21f9762

Please sign in to comment.