mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Enterprise Search] Adds an endpoint to query documents from an index (#135345)
* WIP: Add internal enterprise search route to fetch a index mapping This is still WIP because I need to learn how to properly mock things in Kibana. * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * Update return value for API based on the designs. * Add request handler context to router mock constructor * WIP: simple index query for docs. * Fix and add specs * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
c3327e4fad
commit
b06eee9355
5 changed files with 268 additions and 0 deletions
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* 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 { SearchResponseBody } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { IScopedClusterClient } from '@kbn/core/server';
|
||||
|
||||
import { fetchSearchResults } from './fetch_search_results';
|
||||
|
||||
describe('fetchSearchResults lib function', () => {
|
||||
const mockClient = {
|
||||
asCurrentUser: {
|
||||
search: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
const indexName = 'search-regular-index';
|
||||
const query = 'banana';
|
||||
|
||||
const regularSearchResultsResponse = {
|
||||
took: 4,
|
||||
timed_out: false,
|
||||
_shards: {
|
||||
total: 2,
|
||||
successful: 2,
|
||||
skipped: 0,
|
||||
failed: 0,
|
||||
},
|
||||
hits: {
|
||||
total: {
|
||||
value: 1,
|
||||
relation: 'eq',
|
||||
},
|
||||
max_score: null,
|
||||
hits: [
|
||||
{
|
||||
_index: 'search-regular-index',
|
||||
_id: '5a12292a0f5ae10021650d7e',
|
||||
_score: 4.437291,
|
||||
_source: {
|
||||
name: 'banana',
|
||||
id: '5a12292a0f5ae10021650d7e',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const emptySearchResultsResponse = {
|
||||
took: 4,
|
||||
timed_out: false,
|
||||
_shards: {
|
||||
total: 2,
|
||||
successful: 2,
|
||||
skipped: 0,
|
||||
failed: 0,
|
||||
},
|
||||
hits: {
|
||||
total: {
|
||||
value: 0,
|
||||
relation: 'eq',
|
||||
},
|
||||
max_score: null,
|
||||
hits: [],
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return search results with hits', async () => {
|
||||
mockClient.asCurrentUser.search.mockImplementation(
|
||||
() => regularSearchResultsResponse as SearchResponseBody
|
||||
);
|
||||
|
||||
await expect(
|
||||
fetchSearchResults(mockClient as unknown as IScopedClusterClient, indexName, query)
|
||||
).resolves.toEqual(regularSearchResultsResponse);
|
||||
|
||||
expect(mockClient.asCurrentUser.search).toHaveBeenCalledWith({
|
||||
index: indexName,
|
||||
q: query,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return empty search results', async () => {
|
||||
mockClient.asCurrentUser.search.mockImplementationOnce(
|
||||
() => emptySearchResultsResponse as SearchResponseBody
|
||||
);
|
||||
|
||||
await expect(
|
||||
fetchSearchResults(mockClient as unknown as IScopedClusterClient, indexName, query)
|
||||
).resolves.toEqual(emptySearchResultsResponse);
|
||||
|
||||
expect(mockClient.asCurrentUser.search).toHaveBeenCalledWith({
|
||||
index: indexName,
|
||||
q: query,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 { SearchResponseBody } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { IScopedClusterClient } from '@kbn/core/server';
|
||||
|
||||
export const fetchSearchResults = async (
|
||||
client: IScopedClusterClient,
|
||||
indexName: string,
|
||||
query: string
|
||||
): Promise<SearchResponseBody> => {
|
||||
const results = await client.asCurrentUser.search({
|
||||
index: indexName,
|
||||
q: query,
|
||||
});
|
||||
return results;
|
||||
};
|
|
@ -9,8 +9,10 @@ import { RouteDependencies } from '../../plugin';
|
|||
|
||||
import { registerIndexRoutes } from './indices';
|
||||
import { registerMappingRoute } from './mapping';
|
||||
import { registerSearchRoute } from './search';
|
||||
|
||||
export const registerEnterpriseSearchRoutes = (dependencies: RouteDependencies) => {
|
||||
registerIndexRoutes(dependencies);
|
||||
registerMappingRoute(dependencies);
|
||||
registerSearchRoute(dependencies);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* 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, mockDependencies } from '../../__mocks__';
|
||||
|
||||
import { RequestHandlerContext } from '@kbn/core/server';
|
||||
|
||||
jest.mock('../../lib/fetch_search_results', () => ({
|
||||
fetchSearchResults: jest.fn(),
|
||||
}));
|
||||
import { fetchSearchResults } from '../../lib/fetch_search_results';
|
||||
|
||||
import { registerSearchRoute } from './search';
|
||||
|
||||
describe('Elasticsearch Index Mapping', () => {
|
||||
let mockRouter: MockRouter;
|
||||
const mockClient = {};
|
||||
|
||||
beforeEach(() => {
|
||||
const context = {
|
||||
core: Promise.resolve({ elasticsearch: { client: mockClient } }),
|
||||
} as jest.Mocked<RequestHandlerContext>;
|
||||
|
||||
mockRouter = new MockRouter({
|
||||
context,
|
||||
method: 'get',
|
||||
path: '/internal/enterprise_search/{index_name}/search/{query}',
|
||||
});
|
||||
|
||||
registerSearchRoute({
|
||||
...mockDependencies,
|
||||
router: mockRouter.router,
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /internal/enterprise_search/{index_name}/search/{query}', () => {
|
||||
it('fails validation without index_name', () => {
|
||||
const request = { params: { query: 'banana' } };
|
||||
mockRouter.shouldThrow(request);
|
||||
});
|
||||
|
||||
it('fails validation without query', () => {
|
||||
const request = { params: { index_name: 'search-banana' } };
|
||||
mockRouter.shouldThrow(request);
|
||||
});
|
||||
|
||||
it('returns search results for a query', async () => {
|
||||
const mockData = {
|
||||
took: 4,
|
||||
timed_out: false,
|
||||
_shards: {
|
||||
total: 2,
|
||||
successful: 2,
|
||||
skipped: 0,
|
||||
failed: 0,
|
||||
},
|
||||
hits: {
|
||||
total: {
|
||||
value: 1,
|
||||
relation: 'eq',
|
||||
},
|
||||
max_score: null,
|
||||
hits: [
|
||||
{
|
||||
_index: 'search-regular-index',
|
||||
_id: '5a12292a0f5ae10021650d7e',
|
||||
_score: 4.437291,
|
||||
_source: {
|
||||
name: 'banana',
|
||||
id: '5a12292a0f5ae10021650d7e',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
(fetchSearchResults as jest.Mock).mockImplementationOnce(() => {
|
||||
return Promise.resolve(mockData);
|
||||
});
|
||||
|
||||
await mockRouter.callRoute({
|
||||
params: { index_name: 'search-index-name', query: 'banana' },
|
||||
});
|
||||
|
||||
expect(fetchSearchResults).toHaveBeenCalledWith(mockClient, 'search-index-name', 'banana');
|
||||
|
||||
expect(mockRouter.response.ok).toHaveBeenCalledWith({
|
||||
body: mockData,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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 { fetchSearchResults } from '../../lib/fetch_search_results';
|
||||
import { RouteDependencies } from '../../plugin';
|
||||
|
||||
export function registerSearchRoute({ router }: RouteDependencies) {
|
||||
router.get(
|
||||
{
|
||||
path: '/internal/enterprise_search/{index_name}/search/{query}',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
index_name: schema.string(),
|
||||
query: schema.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const { client } = (await context.core).elasticsearch;
|
||||
try {
|
||||
const searchResults = await fetchSearchResults(
|
||||
client,
|
||||
request.params.index_name,
|
||||
request.params.query
|
||||
);
|
||||
return response.ok({
|
||||
body: searchResults,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
});
|
||||
} catch (error) {
|
||||
return response.customError({
|
||||
statusCode: 502,
|
||||
body: 'Error fetching data from Enterprise Search',
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue