mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[App Search] Added Sample Response section to Result Settings (#95971)
This commit is contained in:
parent
1fad3175f9
commit
ad5f83a362
11 changed files with 556 additions and 1 deletions
|
@ -17,6 +17,8 @@ import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chro
|
|||
import { RESULT_SETTINGS_TITLE } from './constants';
|
||||
import { ResultSettingsTable } from './result_settings_table';
|
||||
|
||||
import { SampleResponse } from './sample_response';
|
||||
|
||||
import { ResultSettingsLogic } from '.';
|
||||
|
||||
interface Props {
|
||||
|
@ -40,7 +42,7 @@ export const ResultSettings: React.FC<Props> = ({ engineBreadcrumb }) => {
|
|||
<ResultSettingsTable />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={3}>
|
||||
<div>TODO</div>
|
||||
<SampleResponse />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
|
|
|
@ -10,6 +10,7 @@ import React, { useMemo } from 'react';
|
|||
import { useValues, useActions } from 'kea';
|
||||
|
||||
import { EuiTableRow, EuiTableRowCell, EuiCheckbox, EuiTableRowCellCheckbox } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { ResultSettingsLogic } from '..';
|
||||
import { FieldResultSetting } from '../types';
|
||||
|
@ -33,6 +34,10 @@ export const NonTextFieldsBody: React.FC = () => {
|
|||
</EuiTableRowCell>
|
||||
<EuiTableRowCellCheckbox>
|
||||
<EuiCheckbox
|
||||
aria-label={i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.rawAriaLabel',
|
||||
{ defaultMessage: 'Toggle raw field' }
|
||||
)}
|
||||
data-test-subj="ResultSettingRawCheckBox"
|
||||
id={`${fieldName}-raw}`}
|
||||
checked={!!fieldSettings.raw}
|
||||
|
|
|
@ -10,6 +10,7 @@ import React, { useMemo } from 'react';
|
|||
import { useValues, useActions } from 'kea';
|
||||
|
||||
import { EuiTableRow, EuiTableRowCell, EuiTableRowCellCheckbox, EuiCheckbox } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { ResultSettingsLogic } from '../result_settings_logic';
|
||||
import { FieldResultSetting } from '../types';
|
||||
|
@ -43,6 +44,10 @@ export const TextFieldsBody: React.FC = () => {
|
|||
</EuiTableRowCell>
|
||||
<EuiTableRowCellCheckbox>
|
||||
<EuiCheckbox
|
||||
aria-label={i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.rawAriaLabel',
|
||||
{ defaultMessage: 'Toggle raw field' }
|
||||
)}
|
||||
data-test-subj="ResultSettingRawCheckBox"
|
||||
id={`${fieldName}-raw}`}
|
||||
checked={!!fieldSettings.raw}
|
||||
|
@ -63,6 +68,10 @@ export const TextFieldsBody: React.FC = () => {
|
|||
</EuiTableRowCell>
|
||||
<EuiTableRowCellCheckbox>
|
||||
<EuiCheckbox
|
||||
aria-label={i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.snippetAriaLabel',
|
||||
{ defaultMessage: 'Toggle text snippet' }
|
||||
)}
|
||||
data-test-subj="ResultSettingSnippetTextBox"
|
||||
id={`${fieldName}-snippet}`}
|
||||
checked={!!fieldSettings.snippet}
|
||||
|
@ -73,6 +82,10 @@ export const TextFieldsBody: React.FC = () => {
|
|||
</EuiTableRowCellCheckbox>
|
||||
<EuiTableRowCellCheckbox>
|
||||
<EuiCheckbox
|
||||
aria-label={i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.resultSettings.table.snippetFallbackAriaLabel',
|
||||
{ defaultMessage: 'Toggle snippet fallback' }
|
||||
)}
|
||||
data-test-subj="ResultSettingFallbackTextBox"
|
||||
id={`${fieldName}-snippetFallback}`}
|
||||
checked={fieldSettings.snippetFallback}
|
||||
|
|
|
@ -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 { SampleResponse } from './sample_response';
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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 '../../../../__mocks__/shallow_useeffect.mock';
|
||||
import { setMockActions, setMockValues } from '../../../../__mocks__';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { EuiCodeBlock, EuiFieldSearch } from '@elastic/eui';
|
||||
|
||||
import { SampleResponse } from './sample_response';
|
||||
|
||||
describe('SampleResponse', () => {
|
||||
const actions = {
|
||||
queryChanged: jest.fn(),
|
||||
getSearchResults: jest.fn(),
|
||||
};
|
||||
|
||||
const values = {
|
||||
reducedServerResultFields: {},
|
||||
query: 'foo',
|
||||
response: {
|
||||
bar: 'baz',
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
setMockActions(actions);
|
||||
setMockValues(values);
|
||||
});
|
||||
|
||||
it('renders a text box with the current user "query" value from state', () => {
|
||||
const wrapper = shallow(<SampleResponse />);
|
||||
expect(wrapper.find(EuiFieldSearch).prop('value')).toEqual('foo');
|
||||
});
|
||||
|
||||
it('updates the "query" value in state when a user updates the text in the text box', () => {
|
||||
const wrapper = shallow(<SampleResponse />);
|
||||
wrapper.find(EuiFieldSearch).simulate('change', { target: { value: 'bar' } });
|
||||
expect(actions.queryChanged).toHaveBeenCalledWith('bar');
|
||||
});
|
||||
|
||||
it('will call getSearchResults with the current value of query and reducedServerResultFields in a useEffect, which updates the displayed response', () => {
|
||||
const wrapper = shallow(<SampleResponse />);
|
||||
expect(wrapper.find(EuiFieldSearch).prop('value')).toEqual('foo');
|
||||
});
|
||||
|
||||
it('renders the response from the given user "query" in a code block', () => {
|
||||
const wrapper = shallow(<SampleResponse />);
|
||||
expect(wrapper.find(EuiCodeBlock).prop('children')).toEqual('{\n "bar": "baz"\n}');
|
||||
});
|
||||
|
||||
it('renders a plain old string in the code block if the response is a string', () => {
|
||||
setMockValues({
|
||||
response: 'No results.',
|
||||
});
|
||||
const wrapper = shallow(<SampleResponse />);
|
||||
expect(wrapper.find(EuiCodeBlock).prop('children')).toEqual('No results.');
|
||||
});
|
||||
|
||||
it('will not render a code block at all if there is no response yet', () => {
|
||||
setMockValues({
|
||||
response: null,
|
||||
});
|
||||
const wrapper = shallow(<SampleResponse />);
|
||||
expect(wrapper.find(EuiCodeBlock).exists()).toEqual(false);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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, { useEffect } from 'react';
|
||||
|
||||
import { useActions, useValues } from 'kea';
|
||||
|
||||
import {
|
||||
EuiCodeBlock,
|
||||
EuiFieldSearch,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { ResultSettingsLogic } from '../result_settings_logic';
|
||||
|
||||
import { SampleResponseLogic } from './sample_response_logic';
|
||||
|
||||
export const SampleResponse: React.FC = () => {
|
||||
const { reducedServerResultFields } = useValues(ResultSettingsLogic);
|
||||
|
||||
const { query, response } = useValues(SampleResponseLogic);
|
||||
const { queryChanged, getSearchResults } = useActions(SampleResponseLogic);
|
||||
|
||||
useEffect(() => {
|
||||
getSearchResults(query, reducedServerResultFields);
|
||||
}, [query, reducedServerResultFields]);
|
||||
|
||||
return (
|
||||
<EuiPanel hasBorder>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="s">
|
||||
<h2>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.resultSettings.sampleResponseTitle',
|
||||
{ defaultMessage: 'Sample response' }
|
||||
)}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
{/* TODO <QueryPerformance queryPerformanceRating={queryPerformanceRating} /> */}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
<EuiFieldSearch
|
||||
value={query}
|
||||
onChange={(e) => queryChanged(e.target.value)}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.resultSettings.sampleResponse.inputPlaceholder',
|
||||
{ defaultMessage: 'Type a search query to test a response...' }
|
||||
)}
|
||||
data-test-subj="ResultSettingsQuerySampleResponse"
|
||||
/>
|
||||
<EuiSpacer />
|
||||
{!!response && (
|
||||
<EuiCodeBlock language="json" whiteSpace="pre-wrap">
|
||||
{typeof response === 'string' ? response : JSON.stringify(response, null, 2)}
|
||||
</EuiCodeBlock>
|
||||
)}
|
||||
</EuiPanel>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,214 @@
|
|||
/*
|
||||
* 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 { LogicMounter, mockHttpValues } from '../../../../__mocks__';
|
||||
import '../../../__mocks__/engine_logic.mock';
|
||||
|
||||
import { nextTick } from '@kbn/test/jest';
|
||||
|
||||
import { flashAPIErrors } from '../../../../shared/flash_messages';
|
||||
|
||||
import { SampleResponseLogic } from './sample_response_logic';
|
||||
|
||||
describe('SampleResponseLogic', () => {
|
||||
const { mount } = new LogicMounter(SampleResponseLogic);
|
||||
const { http } = mockHttpValues;
|
||||
|
||||
const DEFAULT_VALUES = {
|
||||
query: '',
|
||||
response: null,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('has expected default values', () => {
|
||||
mount();
|
||||
expect(SampleResponseLogic.values).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
});
|
||||
});
|
||||
|
||||
describe('actions', () => {
|
||||
describe('queryChanged', () => {
|
||||
it('updates the query', () => {
|
||||
mount({
|
||||
query: '',
|
||||
});
|
||||
|
||||
SampleResponseLogic.actions.queryChanged('foo');
|
||||
|
||||
expect(SampleResponseLogic.values).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
query: 'foo',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSearchResultsSuccess', () => {
|
||||
it('sets the response from a search API request', () => {
|
||||
mount({
|
||||
response: null,
|
||||
});
|
||||
|
||||
SampleResponseLogic.actions.getSearchResultsSuccess({});
|
||||
|
||||
expect(SampleResponseLogic.values).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
response: {},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSearchResultsFailure', () => {
|
||||
it('sets a string response from a search API request', () => {
|
||||
mount({
|
||||
response: null,
|
||||
});
|
||||
|
||||
SampleResponseLogic.actions.getSearchResultsFailure('An error occured.');
|
||||
|
||||
expect(SampleResponseLogic.values).toEqual({
|
||||
...DEFAULT_VALUES,
|
||||
response: 'An error occured.',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('listeners', () => {
|
||||
describe('getSearchResults', () => {
|
||||
beforeAll(() => jest.useFakeTimers());
|
||||
afterAll(() => jest.useRealTimers());
|
||||
|
||||
it('makes a search API request and calls getSearchResultsSuccess with the first result of the response', async () => {
|
||||
mount();
|
||||
jest.spyOn(SampleResponseLogic.actions, 'getSearchResultsSuccess');
|
||||
|
||||
http.post.mockReturnValue(
|
||||
Promise.resolve({
|
||||
results: [
|
||||
{ id: { raw: 'foo' }, _meta: {} },
|
||||
{ id: { raw: 'bar' }, _meta: {} },
|
||||
{ id: { raw: 'baz' }, _meta: {} },
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
SampleResponseLogic.actions.getSearchResults('foo', { foo: { raw: true } });
|
||||
jest.runAllTimers();
|
||||
await nextTick();
|
||||
|
||||
expect(SampleResponseLogic.actions.getSearchResultsSuccess).toHaveBeenCalledWith({
|
||||
// Note that the _meta field was stripped from the result
|
||||
id: { raw: 'foo' },
|
||||
});
|
||||
});
|
||||
|
||||
it('calls getSearchResultsSuccess with a "No Results." message if there are no results', async () => {
|
||||
mount();
|
||||
jest.spyOn(SampleResponseLogic.actions, 'getSearchResultsSuccess');
|
||||
|
||||
http.post.mockReturnValue(
|
||||
Promise.resolve({
|
||||
results: [],
|
||||
})
|
||||
);
|
||||
|
||||
SampleResponseLogic.actions.getSearchResults('foo', { foo: { raw: true } });
|
||||
jest.runAllTimers();
|
||||
await nextTick();
|
||||
|
||||
expect(SampleResponseLogic.actions.getSearchResultsSuccess).toHaveBeenCalledWith(
|
||||
'No results.'
|
||||
);
|
||||
});
|
||||
|
||||
it('handles 500 errors by setting a generic error response and showing a flash message error', async () => {
|
||||
mount();
|
||||
jest.spyOn(SampleResponseLogic.actions, 'getSearchResultsFailure');
|
||||
|
||||
const error = {
|
||||
response: {
|
||||
status: 500,
|
||||
},
|
||||
};
|
||||
|
||||
http.post.mockReturnValueOnce(Promise.reject(error));
|
||||
|
||||
SampleResponseLogic.actions.getSearchResults('foo', { foo: { raw: true } });
|
||||
jest.runAllTimers();
|
||||
await nextTick();
|
||||
|
||||
expect(flashAPIErrors).toHaveBeenCalledWith(error);
|
||||
expect(SampleResponseLogic.actions.getSearchResultsFailure).toHaveBeenCalledWith(
|
||||
'An error occured.'
|
||||
);
|
||||
});
|
||||
|
||||
it('handles 400 errors by setting the response, but does not show a flash error message', async () => {
|
||||
mount();
|
||||
jest.spyOn(SampleResponseLogic.actions, 'getSearchResultsFailure');
|
||||
|
||||
http.post.mockReturnValueOnce(
|
||||
Promise.reject({
|
||||
response: {
|
||||
status: 400,
|
||||
},
|
||||
body: {
|
||||
attributes: {
|
||||
errors: ['A validation error occurred.'],
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
SampleResponseLogic.actions.getSearchResults('foo', { foo: { raw: true } });
|
||||
jest.runAllTimers();
|
||||
await nextTick();
|
||||
|
||||
expect(SampleResponseLogic.actions.getSearchResultsFailure).toHaveBeenCalledWith({
|
||||
errors: ['A validation error occurred.'],
|
||||
});
|
||||
});
|
||||
|
||||
it('sets a generic message on a 400 error if no custom message is provided in the response', async () => {
|
||||
mount();
|
||||
jest.spyOn(SampleResponseLogic.actions, 'getSearchResultsFailure');
|
||||
|
||||
http.post.mockReturnValueOnce(
|
||||
Promise.reject({
|
||||
response: {
|
||||
status: 400,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
SampleResponseLogic.actions.getSearchResults('foo', { foo: { raw: true } });
|
||||
jest.runAllTimers();
|
||||
await nextTick();
|
||||
|
||||
expect(SampleResponseLogic.actions.getSearchResultsFailure).toHaveBeenCalledWith(
|
||||
'An error occured.'
|
||||
);
|
||||
});
|
||||
|
||||
it('does nothing if an empty object is passed for the resultFields parameter', async () => {
|
||||
mount();
|
||||
jest.spyOn(SampleResponseLogic.actions, 'getSearchResultsSuccess');
|
||||
|
||||
SampleResponseLogic.actions.getSearchResults('foo', {});
|
||||
|
||||
jest.runAllTimers();
|
||||
await nextTick();
|
||||
|
||||
expect(SampleResponseLogic.actions.getSearchResultsSuccess).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* 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 { kea, MakeLogicType } from 'kea';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { flashAPIErrors } from '../../../../shared/flash_messages';
|
||||
|
||||
import { HttpLogic } from '../../../../shared/http';
|
||||
import { EngineLogic } from '../../engine';
|
||||
|
||||
import { SampleSearchResponse, ServerFieldResultSettingObject } from '../types';
|
||||
|
||||
const NO_RESULTS_MESSAGE = i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.resultSettings.sampleResponse.noResultsMessage',
|
||||
{ defaultMessage: 'No results.' }
|
||||
);
|
||||
|
||||
const ERROR_MESSAGE = i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.engine.resultSettings.sampleResponse.errorMessage',
|
||||
{ defaultMessage: 'An error occured.' }
|
||||
);
|
||||
|
||||
interface SampleResponseValues {
|
||||
query: string;
|
||||
response: SampleSearchResponse | string | null;
|
||||
}
|
||||
|
||||
interface SampleResponseActions {
|
||||
queryChanged: (query: string) => { query: string };
|
||||
getSearchResultsSuccess: (
|
||||
response: SampleSearchResponse | string
|
||||
) => { response: SampleSearchResponse | string };
|
||||
getSearchResultsFailure: (response: string) => { response: string };
|
||||
getSearchResults: (
|
||||
query: string,
|
||||
resultFields: ServerFieldResultSettingObject
|
||||
) => { query: string; resultFields: ServerFieldResultSettingObject };
|
||||
}
|
||||
|
||||
export const SampleResponseLogic = kea<MakeLogicType<SampleResponseValues, SampleResponseActions>>({
|
||||
path: ['enterprise_search', 'app_search', 'sample_response_logic'],
|
||||
actions: {
|
||||
queryChanged: (query) => ({ query }),
|
||||
getSearchResultsSuccess: (response) => ({ response }),
|
||||
getSearchResultsFailure: (response) => ({ response }),
|
||||
getSearchResults: (query, resultFields) => ({ query, resultFields }),
|
||||
},
|
||||
reducers: {
|
||||
query: ['', { queryChanged: (_, { query }) => query }],
|
||||
response: [
|
||||
null,
|
||||
{
|
||||
getSearchResultsSuccess: (_, { response }) => response,
|
||||
getSearchResultsFailure: (_, { response }) => response,
|
||||
},
|
||||
],
|
||||
},
|
||||
listeners: ({ actions }) => ({
|
||||
getSearchResults: async ({ query, resultFields }, breakpoint) => {
|
||||
if (Object.keys(resultFields).length < 1) return;
|
||||
await breakpoint(250);
|
||||
|
||||
const { http } = HttpLogic.values;
|
||||
const { engineName } = EngineLogic.values;
|
||||
|
||||
const url = `/api/app_search/engines/${engineName}/sample_response_search`;
|
||||
|
||||
try {
|
||||
const response = await http.post(url, {
|
||||
body: JSON.stringify({
|
||||
query,
|
||||
result_fields: resultFields,
|
||||
}),
|
||||
});
|
||||
|
||||
const result = response.results?.[0];
|
||||
actions.getSearchResultsSuccess(
|
||||
result ? { ...result, _meta: undefined } : NO_RESULTS_MESSAGE
|
||||
);
|
||||
} catch (e) {
|
||||
if (e.response.status >= 500) {
|
||||
// 4XX Validation errors are expected, as a user could enter something like 2 as a size, which is out of valid range.
|
||||
// In this case, we simply render the message from the server as the response.
|
||||
//
|
||||
// 5xx Server errors are unexpected, and need to be reported in a flash message.
|
||||
flashAPIErrors(e);
|
||||
actions.getSearchResultsFailure(ERROR_MESSAGE);
|
||||
} else {
|
||||
actions.getSearchResultsFailure(e.body?.attributes || ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
});
|
|
@ -5,6 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { FieldValue } from '../result/types';
|
||||
|
||||
export enum OpenModal {
|
||||
None,
|
||||
ConfirmResetModal,
|
||||
|
@ -35,3 +37,5 @@ export interface FieldResultSetting {
|
|||
}
|
||||
|
||||
export type FieldResultSettingObject = Record<string, FieldResultSetting | {}>;
|
||||
|
||||
export type SampleSearchResponse = Record<string, FieldValue>;
|
||||
|
|
|
@ -88,4 +88,48 @@ describe('result settings routes', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /api/app_search/engines/{name}/sample_response_search', () => {
|
||||
const mockRouter = new MockRouter({
|
||||
method: 'post',
|
||||
path: '/api/app_search/engines/{engineName}/sample_response_search',
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
registerResultSettingsRoutes({
|
||||
...mockDependencies,
|
||||
router: mockRouter.router,
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a request to enterprise search', () => {
|
||||
mockRouter.callRoute({
|
||||
params: { engineName: 'some-engine' },
|
||||
body: {
|
||||
query: 'test',
|
||||
result_fields: resultFields,
|
||||
},
|
||||
});
|
||||
|
||||
expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
|
||||
path: '/as/engines/:engineName/sample_response_search',
|
||||
});
|
||||
});
|
||||
|
||||
describe('validates', () => {
|
||||
it('correctly', () => {
|
||||
const request = {
|
||||
body: {
|
||||
query: 'test',
|
||||
result_fields: resultFields,
|
||||
},
|
||||
};
|
||||
mockRouter.shouldValidate(request);
|
||||
});
|
||||
it('missing required fields', () => {
|
||||
const request = { body: {} };
|
||||
mockRouter.shouldThrow(request);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -45,4 +45,22 @@ export function registerResultSettingsRoutes({
|
|||
path: '/as/engines/:engineName/result_settings',
|
||||
})
|
||||
);
|
||||
|
||||
router.post(
|
||||
{
|
||||
path: '/api/app_search/engines/{engineName}/sample_response_search',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
engineName: schema.string(),
|
||||
}),
|
||||
body: schema.object({
|
||||
query: schema.string(),
|
||||
result_fields: schema.recordOf(schema.string(), schema.object({}, { unknowns: 'allow' })),
|
||||
}),
|
||||
},
|
||||
},
|
||||
enterpriseSearchRequestHandler.createRequest({
|
||||
path: '/as/engines/:engineName/sample_response_search',
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue