[App Search] Added a persistent query tester flyout (#101071)

This commit is contained in:
Jason Stoltzfus 2021-06-03 11:00:12 -04:00 committed by GitHub
parent 58b1416f84
commit 9618fd7dfe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 558 additions and 179 deletions

View file

@ -17,7 +17,7 @@ import { CurationResult, AddResultFlyout } from './';
describe('AddResultFlyout', () => {
const values = {
dataLoading: false,
searchDataLoading: false,
searchQuery: '',
searchResults: [],
promotedIds: [],
@ -48,7 +48,7 @@ describe('AddResultFlyout', () => {
describe('search input', () => {
it('renders isLoading state correctly', () => {
setMockValues({ ...values, dataLoading: true });
setMockValues({ ...values, searchDataLoading: true });
const wrapper = shallow(<AddResultFlyout />);
expect(wrapper.find(EuiFieldSearch).prop('isLoading')).toEqual(true);

View file

@ -24,6 +24,7 @@ import { i18n } from '@kbn/i18n';
import { FlashMessages } from '../../../../../shared/flash_messages';
import { SearchLogic } from '../../../search';
import {
RESULT_ACTIONS_DIRECTIONS,
PROMOTE_DOCUMENT_ACTION,
@ -36,8 +37,10 @@ import { CurationLogic } from '../curation_logic';
import { AddResultLogic, CurationResult } from './';
export const AddResultFlyout: React.FC = () => {
const { searchQuery, searchResults, dataLoading } = useValues(AddResultLogic);
const { search, closeFlyout } = useActions(AddResultLogic);
const searchLogic = SearchLogic({ id: 'add-results-flyout' });
const { searchQuery, searchResults, searchDataLoading } = useValues(searchLogic);
const { closeFlyout } = useActions(AddResultLogic);
const { search } = useActions(searchLogic);
const { promotedIds, hiddenIds } = useValues(CurationLogic);
const { addPromotedId, removePromotedId, addHiddenId, removeHiddenId } = useActions(
@ -63,7 +66,7 @@ export const AddResultFlyout: React.FC = () => {
<EuiFieldSearch
value={searchQuery}
onChange={(e) => search(e.target.value)}
isLoading={dataLoading}
isLoading={searchDataLoading}
placeholder={i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.curations.addResult.searchPlaceholder',
{ defaultMessage: 'Search engine documents' }

View file

@ -5,31 +5,16 @@
* 2.0.
*/
import { LogicMounter, mockHttpValues, mockFlashMessageHelpers } from '../../../../../__mocks__';
import { LogicMounter } from '../../../../../__mocks__';
import '../../../../__mocks__/engine_logic.mock';
import { nextTick } from '@kbn/test/jest';
import { AddResultLogic } from './';
describe('AddResultLogic', () => {
const { mount } = new LogicMounter(AddResultLogic);
const { http } = mockHttpValues;
const { flashAPIErrors } = mockFlashMessageHelpers;
const MOCK_SEARCH_RESPONSE = {
results: [
{ id: { raw: 'document-1' }, _meta: { id: 'document-1', engine: 'some-engine' } },
{ id: { raw: 'document-2' }, _meta: { id: 'document-2', engine: 'some-engine' } },
{ id: { raw: 'document-3' }, _meta: { id: 'document-3', engine: 'some-engine' } },
],
};
const DEFAULT_VALUES = {
isFlyoutOpen: false,
dataLoading: false,
searchQuery: '',
searchResults: [],
};
beforeEach(() => {
@ -51,7 +36,6 @@ describe('AddResultLogic', () => {
expect(AddResultLogic.values).toEqual({
...DEFAULT_VALUES,
isFlyoutOpen: true,
searchQuery: '',
});
});
});
@ -68,67 +52,5 @@ describe('AddResultLogic', () => {
});
});
});
describe('search', () => {
it('sets searchQuery & dataLoading to true', () => {
mount({ searchQuery: '', dataLoading: false });
AddResultLogic.actions.search('hello world');
expect(AddResultLogic.values).toEqual({
...DEFAULT_VALUES,
searchQuery: 'hello world',
dataLoading: true,
});
});
});
describe('onSearch', () => {
it('sets searchResults & dataLoading to false', () => {
mount({ searchResults: [], dataLoading: true });
AddResultLogic.actions.onSearch(MOCK_SEARCH_RESPONSE);
expect(AddResultLogic.values).toEqual({
...DEFAULT_VALUES,
searchResults: MOCK_SEARCH_RESPONSE.results,
dataLoading: false,
});
});
});
});
describe('listeners', () => {
describe('search', () => {
beforeAll(() => jest.useFakeTimers());
afterAll(() => jest.useRealTimers());
it('should make a GET API call with a search query', async () => {
http.get.mockReturnValueOnce(Promise.resolve(MOCK_SEARCH_RESPONSE));
mount();
jest.spyOn(AddResultLogic.actions, 'onSearch');
AddResultLogic.actions.search('hello world');
jest.runAllTimers();
await nextTick();
expect(http.get).toHaveBeenCalledWith(
'/api/app_search/engines/some-engine/curation_search',
{ query: { query: 'hello world' } }
);
expect(AddResultLogic.actions.onSearch).toHaveBeenCalledWith(MOCK_SEARCH_RESPONSE);
});
it('handles errors', async () => {
http.get.mockReturnValueOnce(Promise.reject('error'));
mount();
AddResultLogic.actions.search('test');
jest.runAllTimers();
await nextTick();
expect(flashAPIErrors).toHaveBeenCalledWith('error');
});
});
});
});

View file

@ -7,24 +7,13 @@
import { kea, MakeLogicType } from 'kea';
import { flashAPIErrors } from '../../../../../shared/flash_messages';
import { HttpLogic } from '../../../../../shared/http';
import { EngineLogic } from '../../../engine';
import { Result } from '../../../result/types';
interface AddResultValues {
isFlyoutOpen: boolean;
dataLoading: boolean;
searchQuery: string;
searchResults: Result[];
}
interface AddResultActions {
openFlyout(): void;
closeFlyout(): void;
search(query: string): { query: string };
onSearch({ results }: { results: Result[] }): { results: Result[] };
}
export const AddResultLogic = kea<MakeLogicType<AddResultValues, AddResultActions>>({
@ -32,8 +21,6 @@ export const AddResultLogic = kea<MakeLogicType<AddResultValues, AddResultAction
actions: () => ({
openFlyout: true,
closeFlyout: true,
search: (query) => ({ query }),
onSearch: ({ results }) => ({ results }),
}),
reducers: () => ({
isFlyoutOpen: [
@ -43,42 +30,5 @@ export const AddResultLogic = kea<MakeLogicType<AddResultValues, AddResultAction
closeFlyout: () => false,
},
],
dataLoading: [
false,
{
search: () => true,
onSearch: () => false,
},
],
searchQuery: [
'',
{
search: (_, { query }) => query,
openFlyout: () => '',
},
],
searchResults: [
[],
{
onSearch: (_, { results }) => results,
},
],
}),
listeners: ({ actions }) => ({
search: async ({ query }, breakpoint) => {
await breakpoint(250);
const { http } = HttpLogic.values;
const { engineName } = EngineLogic.values;
try {
const response = await http.get(`/api/app_search/engines/${engineName}/curation_search`, {
query: { query },
});
actions.onSearch(response);
} catch (e) {
flashAPIErrors(e);
}
},
}),
});

View file

@ -11,7 +11,7 @@ import React from 'react';
import { shallow } from 'enzyme';
import { EuiButtonEmpty } from '@elastic/eui';
import { QueryTesterButton } from '../query_tester';
import { KibanaHeaderActions } from './kibana_header_actions';
@ -27,7 +27,7 @@ describe('KibanaHeaderActions', () => {
it('renders', () => {
const wrapper = shallow(<KibanaHeaderActions />);
expect(wrapper.find(EuiButtonEmpty).exists()).toBe(true);
expect(wrapper.find(QueryTesterButton).exists()).toBe(true);
});
it('does not render a "Query Tester" button if there is no engine available', () => {
@ -35,6 +35,6 @@ describe('KibanaHeaderActions', () => {
engineName: '',
});
const wrapper = shallow(<KibanaHeaderActions />);
expect(wrapper.find(EuiButtonEmpty).exists()).toBe(false);
expect(wrapper.find(QueryTesterButton).exists()).toBe(false);
});
});

View file

@ -9,10 +9,10 @@ import React from 'react';
import { useValues } from 'kea';
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { EngineLogic } from '../engine';
import { QueryTesterButton } from '../query_tester';
export const KibanaHeaderActions: React.FC = () => {
const { engineName } = useValues(EngineLogic);
@ -21,11 +21,7 @@ export const KibanaHeaderActions: React.FC = () => {
<EuiFlexGroup gutterSize="s">
{engineName && (
<EuiFlexItem>
<EuiButtonEmpty iconType="beaker" size="s">
{i18n.translate('xpack.enterpriseSearch.appSearch.engine.queryTesterButtonLabel', {
defaultMessage: 'Query tester',
})}
</EuiButtonEmpty>
<QueryTesterButton />
</EuiFlexItem>
)}
</EuiFlexGroup>

View file

@ -0,0 +1,15 @@
/*
* 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 { i18n } from '@kbn/i18n';
export const QUERY_TESTER_TITLE = i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.queryTesterTitle',
{
defaultMessage: 'Query tester',
}
);

View file

@ -0,0 +1,9 @@
/*
* 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 { QueryTesterFlyout } from './query_tester_flyout';
export { QueryTesterButton } from './query_tester_button';

View file

@ -0,0 +1,66 @@
/*
* 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 { setMockValues, setMockActions } from '../../../__mocks__/kea.mock';
import React from 'react';
import { shallow } from 'enzyme';
import { EuiEmptyPrompt, EuiFieldSearch } from '@elastic/eui';
import { SchemaType } from '../../../shared/schema/types';
import { Result } from '../result';
import { QueryTester } from './query_tester';
describe('QueryTester', () => {
const values = {
searchQuery: 'foo',
searchResults: [{ id: { raw: '1' } }, { id: { raw: '2' } }, { id: { raw: '3' } }],
searchDataLoading: false,
engine: {
schema: {
foo: SchemaType.Text,
},
},
};
const actions = {
search: jest.fn(),
};
beforeEach(() => {
jest.clearAllMocks();
setMockValues(values);
setMockActions(actions);
});
it('renders with a search box and results', () => {
const wrapper = shallow(<QueryTester />);
expect(wrapper.find(EuiFieldSearch).prop('value')).toBe('foo');
expect(wrapper.find(EuiFieldSearch).prop('isLoading')).toBe(false);
expect(wrapper.find(Result)).toHaveLength(3);
});
it('will update the search term in state when the user updates the search box', () => {
const wrapper = shallow(<QueryTester />);
wrapper.find(EuiFieldSearch).simulate('change', { target: { value: 'bar' } });
expect(actions.search).toHaveBeenCalledWith('bar');
});
it('will render an empty prompt when there are no results', () => {
setMockValues({
...values,
searchResults: [],
});
const wrapper = shallow(<QueryTester />);
wrapper.find(EuiFieldSearch).simulate('change', { target: { value: 'bar' } });
expect(wrapper.find(Result)).toHaveLength(0);
expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1);
});
});

View file

@ -0,0 +1,66 @@
/*
* 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, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { EngineLogic } from '../engine';
import { Result } from '../result';
import { SearchLogic } from '../search';
export const QueryTester: React.FC = () => {
const logic = SearchLogic({ id: 'query-tester' });
const { searchQuery, searchResults, searchDataLoading } = useValues(logic);
const { search } = useActions(logic);
const { engine } = useValues(EngineLogic);
return (
<>
<EuiFieldSearch
value={searchQuery}
onChange={(e) => search(e.target.value)}
isLoading={searchDataLoading}
placeholder={i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.queryTester.searchPlaceholder',
{ defaultMessage: 'Search engine documents' }
)}
fullWidth
autoFocus
/>
<EuiSpacer />
{searchResults.length > 0 ? (
searchResults.map((result) => {
const id = result.id.raw;
return (
<React.Fragment key={id}>
<Result
isMetaEngine={false}
key={id}
result={result}
schemaForTypeHighlights={engine.schema}
showScore
/>
<EuiSpacer size="m" />
</React.Fragment>
);
})
) : (
<EuiEmptyPrompt
body={i18n.translate(
'xpack.enterpriseSearch.appSearch.engine.curations.addResult.searchEmptyDescription',
{ defaultMessage: 'No matching content found.' }
)}
/>
)}
</>
);
};

View file

@ -0,0 +1,35 @@
/*
* 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 { shallow } from 'enzyme';
import { EuiButtonEmpty } from '@elastic/eui';
import { QueryTesterFlyout, QueryTesterButton } from '.';
describe('QueryTesterButton', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('renders', () => {
const wrapper = shallow(<QueryTesterButton />);
expect(wrapper.find(EuiButtonEmpty).exists()).toBe(true);
expect(wrapper.find(QueryTesterFlyout).exists()).toBe(false);
});
it('will render a QueryTesterFlyout when pressed and close on QueryTesterFlyout close', () => {
const wrapper = shallow(<QueryTesterButton />);
wrapper.find(EuiButtonEmpty).simulate('click');
expect(wrapper.find(QueryTesterFlyout).exists()).toBe(true);
wrapper.find(QueryTesterFlyout).simulate('close');
expect(wrapper.find(QueryTesterFlyout).exists()).toBe(false);
});
});

View file

@ -0,0 +1,30 @@
/*
* 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, { useState } from 'react';
import { EuiButtonEmpty } from '@elastic/eui';
import { QUERY_TESTER_TITLE } from './i18n';
import { QueryTesterFlyout } from '.';
export const QueryTesterButton: React.FC = () => {
const [isQueryTesterOpen, setIsQueryTesterOpen] = useState(false);
return (
<>
<EuiButtonEmpty
iconType="beaker"
size="s"
onClick={() => setIsQueryTesterOpen(!isQueryTesterOpen)}
>
{QUERY_TESTER_TITLE}
</EuiButtonEmpty>
{isQueryTesterOpen && <QueryTesterFlyout onClose={() => setIsQueryTesterOpen(false)} />}
</>
);
};

View file

@ -0,0 +1,25 @@
/*
* 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 { shallow } from 'enzyme';
import { EuiFlyout } from '@elastic/eui';
import { QueryTester } from './query_tester';
import { QueryTesterFlyout } from './query_tester_flyout';
describe('QueryTesterFlyout', () => {
const onClose = jest.fn();
it('renders', () => {
const wrapper = shallow(<QueryTesterFlyout onClose={onClose} />);
expect(wrapper.find(QueryTester).exists()).toBe(true);
expect(wrapper.find(EuiFlyout).prop('onClose')).toEqual(onClose);
});
});

View file

@ -0,0 +1,32 @@
/*
* 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 { EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader, EuiTitle } from '@elastic/eui';
import { QUERY_TESTER_TITLE } from './i18n';
import { QueryTester } from './query_tester';
interface Props {
onClose: () => void;
}
export const QueryTesterFlyout: React.FC<Props> = ({ onClose }) => {
return (
<EuiFlyout onClose={onClose} aria-labelledby="queryTesterFlyoutTitle" ownFocus>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="m">
<h2 id="queryTesterFlyoutTitle">{QUERY_TESTER_TITLE}</h2>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<QueryTester />
</EuiFlyoutBody>
</EuiFlyout>
);
};

View file

@ -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 { SearchLogic } from './search_logic';

View file

@ -0,0 +1,108 @@
/*
* 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__/engine_logic.mock';
import { LogicMounter, mockHttpValues, mockFlashMessageHelpers } from '../../../__mocks__';
import { nextTick } from '@kbn/test/jest';
import { SearchLogic } from './search_logic';
describe('SearchLogic', () => {
const { mount } = new LogicMounter(SearchLogic);
const { http } = mockHttpValues;
const { flashAPIErrors } = mockFlashMessageHelpers;
const MOCK_SEARCH_RESPONSE = {
results: [
{ id: { raw: 'document-1' }, _meta: { id: 'document-1', engine: 'some-engine' } },
{ id: { raw: 'document-2' }, _meta: { id: 'document-2', engine: 'some-engine' } },
{ id: { raw: 'document-3' }, _meta: { id: 'document-3', engine: 'some-engine' } },
],
};
const DEFAULT_VALUES = {
searchDataLoading: false,
searchQuery: '',
searchResults: [],
};
beforeEach(() => {
jest.clearAllMocks();
});
const mountLogic = (values: object = {}) => mount(values, { id: '1' });
it('has expected default values', () => {
const logic = mountLogic();
expect(logic.values).toEqual(DEFAULT_VALUES);
});
describe('actions', () => {
describe('search', () => {
it('sets searchQuery & searchDataLoading to true', () => {
const logic = mountLogic({ searchQuery: '', searchDataLoading: false });
logic.actions.search('hello world');
expect(logic.values).toEqual({
...DEFAULT_VALUES,
searchQuery: 'hello world',
searchDataLoading: true,
});
});
});
describe('onSearch', () => {
it('sets searchResults & searchDataLoading to false', () => {
const logic = mountLogic({ searchResults: [], searchDataLoading: true });
logic.actions.onSearch(MOCK_SEARCH_RESPONSE);
expect(logic.values).toEqual({
...DEFAULT_VALUES,
searchResults: MOCK_SEARCH_RESPONSE.results,
searchDataLoading: false,
});
});
});
});
describe('listeners', () => {
describe('search', () => {
beforeAll(() => jest.useFakeTimers());
afterAll(() => jest.useRealTimers());
it('should make a GET API call with a search query', async () => {
http.get.mockReturnValueOnce(Promise.resolve(MOCK_SEARCH_RESPONSE));
const logic = mountLogic();
jest.spyOn(logic.actions, 'onSearch');
logic.actions.search('hello world');
jest.runAllTimers();
await nextTick();
expect(http.get).toHaveBeenCalledWith('/api/app_search/engines/some-engine/search', {
query: { query: 'hello world' },
});
expect(logic.actions.onSearch).toHaveBeenCalledWith(MOCK_SEARCH_RESPONSE);
});
it('handles errors', async () => {
http.get.mockReturnValueOnce(Promise.reject('error'));
const logic = mountLogic();
logic.actions.search('test');
jest.runAllTimers();
await nextTick();
expect(flashAPIErrors).toHaveBeenCalledWith('error');
});
});
});
});

View file

@ -0,0 +1,73 @@
/*
* 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 { flashAPIErrors } from '../../../shared/flash_messages';
import { HttpLogic } from '../../../shared/http';
import { EngineLogic } from '../engine';
import { Result } from '../result/types';
interface SearchValues {
searchDataLoading: boolean;
searchQuery: string;
searchResults: Result[];
}
interface SearchActions {
search(query: string): { query: string };
onSearch({ results }: { results: Result[] }): { results: Result[] };
}
export const SearchLogic = kea<MakeLogicType<SearchValues, SearchActions>>({
key: (props) => props.id,
path: (key: string) => ['enterprise_search', 'app_search', 'search_logic', key],
actions: () => ({
search: (query) => ({ query }),
onSearch: ({ results }) => ({ results }),
}),
reducers: () => ({
searchDataLoading: [
false,
{
search: () => true,
onSearch: () => false,
},
],
searchQuery: [
'',
{
search: (_, { query }) => query,
},
],
searchResults: [
[],
{
onSearch: (_, { results }) => results,
},
],
}),
listeners: ({ actions }) => ({
search: async ({ query }, breakpoint) => {
await breakpoint(250);
const { http } = HttpLogic.values;
const { engineName } = EngineLogic.values;
try {
const response = await http.get(`/api/app_search/engines/${engineName}/search`, {
query: { query },
});
actions.onSearch(response);
} catch (e) {
flashAPIErrors(e);
}
},
}),
});

View file

@ -229,39 +229,4 @@ describe('curations routes', () => {
});
});
});
describe('GET /api/app_search/engines/{engineName}/curation_search', () => {
let mockRouter: MockRouter;
beforeEach(() => {
jest.clearAllMocks();
mockRouter = new MockRouter({
method: 'get',
path: '/api/app_search/engines/{engineName}/curation_search',
});
registerCurationsRoutes({
...mockDependencies,
router: mockRouter.router,
});
});
it('creates a request handler', () => {
expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
path: '/api/as/v1/engines/:engineName/search.json',
});
});
describe('validates', () => {
it('required query param', () => {
const request = { query: { query: 'some query' } };
mockRouter.shouldValidate(request);
});
it('missing query', () => {
const request = { query: {} };
mockRouter.shouldThrow(request);
});
});
});
});

View file

@ -17,6 +17,7 @@ import { registerOnboardingRoutes } from './onboarding';
import { registerResultSettingsRoutes } from './result_settings';
import { registerRoleMappingsRoutes } from './role_mappings';
import { registerSchemaRoutes } from './schema';
import { registerSearchRoutes } from './search';
import { registerSearchSettingsRoutes } from './search_settings';
import { registerSearchUIRoutes } from './search_ui';
import { registerSettingsRoutes } from './settings';
@ -31,6 +32,7 @@ export const registerAppSearchRoutes = (dependencies: RouteDependencies) => {
registerDocumentsRoutes(dependencies);
registerDocumentRoutes(dependencies);
registerSchemaRoutes(dependencies);
registerSearchRoutes(dependencies);
registerSourceEnginesRoutes(dependencies);
registerCurationsRoutes(dependencies);
registerSynonymsRoutes(dependencies);

View file

@ -0,0 +1,35 @@
/*
* 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 { MockRouter, mockRequestHandler, mockDependencies } from '../../__mocks__';
import { registerSearchRoutes } from './search';
describe('search routes', () => {
describe('GET /api/app_search/engines/{engineName}/search', () => {
let mockRouter: MockRouter;
beforeEach(() => {
jest.clearAllMocks();
mockRouter = new MockRouter({
method: 'get',
path: '/api/app_search/engines/{engineName}/schema',
});
registerSearchRoutes({
...mockDependencies,
router: mockRouter.router,
});
});
it('creates a request handler', () => {
expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
path: '/api/as/v1/engines/:engineName/search.json',
});
});
});
});

View file

@ -0,0 +1,39 @@
/*
* 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.
*/
/*
* 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 { schema } from '@kbn/config-schema';
import { RouteDependencies } from '../../plugin';
export function registerSearchRoutes({
router,
enterpriseSearchRequestHandler,
}: RouteDependencies) {
router.get(
{
path: '/api/app_search/engines/{engineName}/search',
validate: {
params: schema.object({
engineName: schema.string(),
}),
query: schema.object({
query: schema.string(),
}),
},
},
enterpriseSearchRequestHandler.createRequest({
path: '/api/as/v1/engines/:engineName/search.json',
})
);
}