[7.x] Migrates ES Fields Route to NP (#54398) (#54609)

* Migrated es fields route to NP and added tests

* Removed extraneous import

* Removed check for index query

* Fixed broken test
This commit is contained in:
Catherine Liu 2020-01-13 12:11:37 -07:00 committed by GitHub
parent f89299ada4
commit 41120ae84a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 236 additions and 75 deletions

View file

@ -1,33 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { mapValues, keys } from 'lodash';
import { normalizeType } from '../../lib/normalize_type';
export function getESFieldTypes(index, fields, elasticsearchClient) {
const config = {
index: index,
fields: fields || '*',
};
if (fields && fields.length === 0) {
return Promise.resolve({});
}
return elasticsearchClient('fieldCaps', config).then(resp => {
return mapValues(resp.fields, types => {
if (keys(types).length > 1) {
return 'conflict';
}
try {
return normalizeType(keys(types)[0]);
} catch (e) {
return 'unsupported';
}
});
});
}

View file

@ -1,40 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { partial } from 'lodash';
import { API_ROUTE } from '../../../common/lib/constants';
import { CoreSetup } from '../../shim';
// @ts-ignore untyped local
import { getESFieldTypes } from './get_es_field_types';
// TODO: Error handling, note: esErrors
interface ESFieldsRequest {
query: {
index: string;
fields: string[];
};
}
export function esFields(
route: CoreSetup['http']['route'],
elasticsearch: CoreSetup['elasticsearch']
) {
const { callWithRequest } = elasticsearch.getCluster('data');
route({
method: 'GET',
path: `${API_ROUTE}/es_fields`,
handler(request: ESFieldsRequest, h: any) {
const { index, fields } = request.query;
if (!index) {
return h.response({ error: '"index" query is required' }).code(400);
}
return getESFieldTypes(index, fields, partial(callWithRequest, request));
},
});
}

View file

@ -4,11 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { esFields } from './es_fields';
import { shareableWorkpads } from './shareables';
import { CoreSetup } from '../shim';
export function routes(setup: CoreSetup): void {
esFields(setup.http.route, setup.elasticsearch);
shareableWorkpads(setup.http.route);
}

View file

@ -0,0 +1,164 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { initializeESFieldsRoute } from './es_fields';
import {
IRouter,
kibanaResponseFactory,
RequestHandlerContext,
RequestHandler,
} from 'src/core/server';
import {
httpServiceMock,
httpServerMock,
loggingServiceMock,
elasticsearchServiceMock,
} from 'src/core/server/mocks';
const mockRouteContext = ({
core: {
elasticsearch: { dataClient: elasticsearchServiceMock.createScopedClusterClient() },
},
} as unknown) as RequestHandlerContext;
const path = `api/canvas/workpad/find`;
describe('Retrieve ES Fields', () => {
let routeHandler: RequestHandler<any, any, any>;
beforeEach(() => {
const httpService = httpServiceMock.createSetupContract();
const router = httpService.createRouter('') as jest.Mocked<IRouter>;
initializeESFieldsRoute({
router,
logger: loggingServiceMock.create().get(),
});
routeHandler = router.get.mock.calls[0][1];
});
it(`returns 200 with fields from existing index/index pattern`, async () => {
const index = 'test';
const mockResults = {
indices: ['test'],
fields: {
'@timestamp': {
date: {
type: 'date',
searchable: true,
aggregatable: true,
},
},
name: {
text: {
type: 'text',
searchable: true,
aggregatable: false,
},
},
products: {
object: {
type: 'object',
searchable: false,
aggregatable: false,
},
},
},
};
const request = httpServerMock.createKibanaRequest({
method: 'get',
path,
query: {
index,
},
});
const callAsCurrentUserMock = mockRouteContext.core.elasticsearch.dataClient
.callAsCurrentUser as jest.Mock;
callAsCurrentUserMock.mockResolvedValueOnce(mockResults);
const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory);
expect(response.status).toBe(200);
expect(response.payload).toMatchInlineSnapshot(`
Object {
"@timestamp": "date",
"name": "string",
"products": "unsupported",
}
`);
});
it(`returns 200 with empty object when index/index pattern has no fields`, async () => {
const index = 'test';
const mockResults = { indices: [index], fields: {} };
const request = httpServerMock.createKibanaRequest({
method: 'get',
path,
query: {
index,
},
});
const callAsCurrentUserMock = mockRouteContext.core.elasticsearch.dataClient
.callAsCurrentUser as jest.Mock;
callAsCurrentUserMock.mockResolvedValueOnce(mockResults);
const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory);
expect(response.status).toBe(200);
expect(response.payload).toMatchInlineSnapshot('Object {}');
});
it(`returns 200 with empty object when index/index pattern does not have specified field(s)`, async () => {
const index = 'test';
const mockResults = {
indices: [index],
fields: {},
};
const request = httpServerMock.createKibanaRequest({
method: 'get',
path,
query: {
index,
fields: ['foo', 'bar'],
},
});
const callAsCurrentUserMock = mockRouteContext.core.elasticsearch.dataClient
.callAsCurrentUser as jest.Mock;
callAsCurrentUserMock.mockResolvedValueOnce(mockResults);
const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory);
expect(response.status).toBe(200);
expect(response.payload).toMatchInlineSnapshot(`Object {}`);
});
it(`returns 500 when index does not exist`, async () => {
const request = httpServerMock.createKibanaRequest({
method: 'get',
path,
query: {
index: 'foo',
},
});
const callAsCurrentUserMock = mockRouteContext.core.elasticsearch.dataClient
.callAsCurrentUser as jest.Mock;
callAsCurrentUserMock.mockRejectedValueOnce(new Error('Index not found'));
const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory);
expect(response.status).toBe(500);
});
});

View file

@ -0,0 +1,58 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { mapValues, keys } from 'lodash';
import { schema } from '@kbn/config-schema';
import { API_ROUTE } from '../../../../../legacy/plugins/canvas/common/lib';
import { catchErrorHandler } from '../catch_error_handler';
// @ts-ignore unconverted lib
import { normalizeType } from '../../../../../legacy/plugins/canvas/server/lib/normalize_type';
import { RouteInitializerDeps } from '..';
const ESFieldsRequestSchema = schema.object({
index: schema.string(),
fields: schema.maybe(schema.arrayOf(schema.string())),
});
export function initializeESFieldsRoute(deps: RouteInitializerDeps) {
const { router } = deps;
router.get(
{
path: `${API_ROUTE}/es_fields`,
validate: {
query: ESFieldsRequestSchema,
},
},
catchErrorHandler(async (context, request, response) => {
const { callAsCurrentUser } = context.core.elasticsearch.dataClient;
const { index, fields } = request.query;
const config = {
index,
fields: fields || '*',
};
const esFields = await callAsCurrentUser('fieldCaps', config).then(resp => {
return mapValues(resp.fields, types => {
if (keys(types).length > 1) {
return 'conflict';
}
try {
return normalizeType(keys(types)[0]);
} catch (e) {
return 'unsupported';
}
});
});
return response.ok({
body: esFields,
});
})
);
}

View file

@ -0,0 +1,12 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { initializeESFieldsRoute } from './es_fields';
import { RouteInitializerDeps } from '..';
export function initESFieldsRoutes(deps: RouteInitializerDeps) {
initializeESFieldsRoute(deps);
}

View file

@ -7,6 +7,7 @@
import { IRouter, Logger } from 'src/core/server';
import { initWorkpadRoutes } from './workpad';
import { initCustomElementsRoutes } from './custom_elements';
import { initESFieldsRoutes } from './es_fields';
export interface RouteInitializerDeps {
router: IRouter;
@ -16,4 +17,5 @@ export interface RouteInitializerDeps {
export function initRoutes(deps: RouteInitializerDeps) {
initWorkpadRoutes(deps);
initCustomElementsRoutes(deps);
initESFieldsRoutes(deps);
}