From d89ede9834dd4cf41d9c53ad2e107c40fe2d4165 Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Fri, 26 Mar 2021 14:29:16 -0400 Subject: [PATCH] [Maps] Add drawing index data endpoint (#94728) --- x-pack/plugins/maps/common/constants.ts | 2 + x-pack/plugins/maps/common/types.ts | 6 + .../{ => data_indexing}/create_doc_source.ts | 13 +-- .../maps/server/data_indexing/index_data.ts | 45 ++++++++ .../server/data_indexing/indexing_routes.ts | 104 ++++++++++++++++++ x-pack/plugins/maps/server/routes.js | 49 +-------- .../test/api_integration/apis/maps/index.js | 1 + .../api_integration/apis/maps/index_data.js | 98 +++++++++++++++++ 8 files changed, 265 insertions(+), 53 deletions(-) rename x-pack/plugins/maps/server/{ => data_indexing}/create_doc_source.ts (84%) create mode 100644 x-pack/plugins/maps/server/data_indexing/index_data.ts create mode 100644 x-pack/plugins/maps/server/data_indexing/indexing_routes.ts create mode 100644 x-pack/test/api_integration/apis/maps/index_data.js diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts index 070ad6ee98f00d..ecdf94a076809c 100644 --- a/x-pack/plugins/maps/common/constants.ts +++ b/x-pack/plugins/maps/common/constants.ts @@ -298,3 +298,5 @@ export type RawValue = string | number | boolean | undefined | null; export type FieldFormatter = (value: RawValue) => string | number; export const INDEX_META_DATA_CREATED_BY = 'maps-drawing-data-ingest'; + +export const MAX_DRAWING_SIZE_BYTES = 10485760; // 10MB diff --git a/x-pack/plugins/maps/common/types.ts b/x-pack/plugins/maps/common/types.ts index 806eac597ac57b..6f2bd72c808967 100644 --- a/x-pack/plugins/maps/common/types.ts +++ b/x-pack/plugins/maps/common/types.ts @@ -22,3 +22,9 @@ export interface IndexSourceMappings { export interface BodySettings { [key: string]: any; } + +export interface WriteSettings { + index: string; + body: object; + [key: string]: any; +} diff --git a/x-pack/plugins/maps/server/create_doc_source.ts b/x-pack/plugins/maps/server/data_indexing/create_doc_source.ts similarity index 84% rename from x-pack/plugins/maps/server/create_doc_source.ts rename to x-pack/plugins/maps/server/data_indexing/create_doc_source.ts index 641a2acf42384e..2b8984aa1534a1 100644 --- a/x-pack/plugins/maps/server/create_doc_source.ts +++ b/x-pack/plugins/maps/server/data_indexing/create_doc_source.ts @@ -11,8 +11,8 @@ import { CreateDocSourceResp, IndexSourceMappings, BodySettings, -} from '../common'; -import { IndexPatternsService } from '../../../../src/plugins/data/common'; +} from '../../common'; +import { IndexPatternsCommonService } from '../../../../../src/plugins/data/server'; const DEFAULT_SETTINGS = { number_of_shards: 1 }; const DEFAULT_MAPPINGS = { @@ -25,16 +25,11 @@ export async function createDocSource( index: string, mappings: IndexSourceMappings, { asCurrentUser }: IScopedClusterClient, - indexPatternsService: IndexPatternsService + indexPatternsService: IndexPatternsCommonService ): Promise { try { await createIndex(index, mappings, asCurrentUser); - await indexPatternsService.createAndSave( - { - title: index, - }, - true - ); + await indexPatternsService.createAndSave({ title: index }, true); return { success: true, diff --git a/x-pack/plugins/maps/server/data_indexing/index_data.ts b/x-pack/plugins/maps/server/data_indexing/index_data.ts new file mode 100644 index 00000000000000..b87cd53a3dfd21 --- /dev/null +++ b/x-pack/plugins/maps/server/data_indexing/index_data.ts @@ -0,0 +1,45 @@ +/* + * 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'; +import { ElasticsearchClient } from 'kibana/server'; +import { WriteSettings } from '../../common'; + +export async function writeDataToIndex( + index: string, + data: object, + asCurrentUser: ElasticsearchClient +) { + try { + const { body: indexExists } = await asCurrentUser.indices.exists({ index }); + if (!indexExists) { + throw new Error( + i18n.translate('xpack.maps.indexData.indexExists', { + defaultMessage: `Index: '{index}' not found. A valid index must be provided`, + values: { + index, + }, + }) + ); + } + const settings: WriteSettings = { index, body: data }; + const { body: resp } = await asCurrentUser.index(settings); + if (resp.result === 'Error') { + throw resp; + } else { + return { + success: true, + data, + }; + } + } catch (error) { + return { + success: false, + error, + }; + } +} diff --git a/x-pack/plugins/maps/server/data_indexing/indexing_routes.ts b/x-pack/plugins/maps/server/data_indexing/indexing_routes.ts new file mode 100644 index 00000000000000..e6e6471ff9af61 --- /dev/null +++ b/x-pack/plugins/maps/server/data_indexing/indexing_routes.ts @@ -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 { schema } from '@kbn/config-schema'; +import { Logger } from 'src/core/server'; +import { IRouter } from 'src/core/server'; +import type { DataRequestHandlerContext } from 'src/plugins/data/server'; +import { + INDEX_SOURCE_API_PATH, + GIS_API_PATH, + MAX_DRAWING_SIZE_BYTES, +} from '../../common/constants'; +import { createDocSource } from './create_doc_source'; +import { writeDataToIndex } from './index_data'; +import { PluginStart as DataPluginStart } from '../../../../../src/plugins/data/server'; + +export function initIndexingRoutes({ + router, + logger, + dataPlugin, +}: { + router: IRouter; + logger: Logger; + dataPlugin: DataPluginStart; +}) { + router.post( + { + path: `/${INDEX_SOURCE_API_PATH}`, + validate: { + body: schema.object({ + index: schema.string(), + mappings: schema.any(), + }), + }, + options: { + body: { + accepts: ['application/json'], + }, + }, + }, + async (context, request, response) => { + const { index, mappings } = request.body; + const indexPatternsService = await dataPlugin.indexPatterns.indexPatternsServiceFactory( + context.core.savedObjects.client, + context.core.elasticsearch.client.asCurrentUser + ); + const result = await createDocSource( + index, + mappings, + context.core.elasticsearch.client, + indexPatternsService + ); + if (result.success) { + return response.ok({ body: result }); + } else { + if (result.error) { + logger.error(result.error); + } + return response.custom({ + body: result?.error?.message, + statusCode: 500, + }); + } + } + ); + + router.post( + { + path: `/${GIS_API_PATH}/feature`, + validate: { + body: schema.object({ + index: schema.string(), + data: schema.any(), + }), + }, + options: { + body: { + accepts: ['application/json'], + maxBytes: MAX_DRAWING_SIZE_BYTES, + }, + }, + }, + async (context, request, response) => { + const result = await writeDataToIndex( + request.body.index, + request.body.data, + context.core.elasticsearch.client.asCurrentUser + ); + if (result.success) { + return response.ok({ body: result }); + } else { + logger.error(result.error); + return response.custom({ + body: result.error.message, + statusCode: 500, + }); + } + } + ); +} diff --git a/x-pack/plugins/maps/server/routes.js b/x-pack/plugins/maps/server/routes.js index f18bb29ed453d5..39ce9979870c5b 100644 --- a/x-pack/plugins/maps/server/routes.js +++ b/x-pack/plugins/maps/server/routes.js @@ -24,8 +24,7 @@ import { INDEX_SETTINGS_API_PATH, FONTS_API_PATH, API_ROOT_PATH, - INDEX_SOURCE_API_PATH, -} from '../common/constants'; +} from '../common'; import { EMSClient } from '@elastic/ems-client'; import fetch from 'node-fetch'; import { i18n } from '@kbn/i18n'; @@ -34,7 +33,7 @@ import { schema } from '@kbn/config-schema'; import fs from 'fs'; import path from 'path'; import { initMVTRoutes } from './mvt/mvt_routes'; -import { createDocSource } from './create_doc_source'; +import { initIndexingRoutes } from './data_indexing/indexing_routes'; const EMPTY_EMS_CLIENT = { async getFileLayers() { @@ -594,47 +593,6 @@ export async function initRoutes( } ); - if (drawingFeatureEnabled) { - router.post( - { - path: `/${INDEX_SOURCE_API_PATH}`, - validate: { - body: schema.object({ - index: schema.string(), - mappings: schema.any(), - }), - }, - options: { - body: { - accepts: ['application/json'], - }, - }, - }, - async (context, request, response) => { - const { index, mappings } = request.body; - const indexPatternsService = await dataPlugin.indexPatterns.indexPatternsServiceFactory( - context.core.savedObjects.client, - context.core.elasticsearch.client.asCurrentUser - ); - const result = await createDocSource( - index, - mappings, - context.core.elasticsearch.client, - indexPatternsService - ); - if (result.success) { - return response.ok({ body: result }); - } else { - logger.error(result.error); - return response.custom({ - body: result.error.message, - statusCode: 500, - }); - } - } - ); - } - function checkEMSProxyEnabled() { const proxyEMSInMaps = emsSettings.isProxyElasticMapsServiceInMaps(); if (!proxyEMSInMaps) { @@ -666,4 +624,7 @@ export async function initRoutes( } initMVTRoutes({ router, logger }); + if (drawingFeatureEnabled) { + initIndexingRoutes({ router, logger, dataPlugin }); + } } diff --git a/x-pack/test/api_integration/apis/maps/index.js b/x-pack/test/api_integration/apis/maps/index.js index afbe201a18b0ed..db954a3dc03967 100644 --- a/x-pack/test/api_integration/apis/maps/index.js +++ b/x-pack/test/api_integration/apis/maps/index.js @@ -16,6 +16,7 @@ export default function ({ loadTestFile, getService }) { describe('', () => { loadTestFile(require.resolve('./create_doc_source')); + loadTestFile(require.resolve('./index_data')); loadTestFile(require.resolve('./fonts_api')); loadTestFile(require.resolve('./index_settings')); loadTestFile(require.resolve('./migrations')); diff --git a/x-pack/test/api_integration/apis/maps/index_data.js b/x-pack/test/api_integration/apis/maps/index_data.js new file mode 100644 index 00000000000000..f7c595537f6123 --- /dev/null +++ b/x-pack/test/api_integration/apis/maps/index_data.js @@ -0,0 +1,98 @@ +/* + * 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 expect from '@kbn/expect'; + +export default function ({ getService }) { + const supertest = getService('supertest'); + + describe('index feature data', () => { + it('should add point data to an existing index', async () => { + await supertest + .post(`/api/maps/docSource`) + .set('kbn-xsrf', 'kibana') + .send({ + index: 'new-point-feature-index', + mappings: { properties: { coordinates: { type: 'geo_point' } } }, + }); + + const resp = await supertest + .post(`/api/maps/feature`) + .set('kbn-xsrf', 'kibana') + .send({ + index: 'new-point-feature-index', + data: { coordinates: [125.6, 10.1], name: 'Dinagat Islands' }, + }) + .expect(200); + + expect(resp.body.success).to.be(true); + }); + + it('should add shape data to an existing index', async () => { + await supertest + .post(`/api/maps/docSource`) + .set('kbn-xsrf', 'kibana') + .send({ + index: 'new-shape-feature-index', + mappings: { properties: { coordinates: { type: 'geo_shape' } } }, + }); + + const resp = await supertest + .post(`/api/maps/feature`) + .set('kbn-xsrf', 'kibana') + .send({ + index: 'new-shape-feature-index', + data: { + coordinates: { + type: 'Polygon', + coordinates: [ + [ + [-20.91796875, 25.64152637306577], + [-13.0517578125, 25.64152637306577], + [-13.0517578125, 31.203404950917395], + [-20.91796875, 31.203404950917395], + [-20.91796875, 25.64152637306577], + ], + ], + }, + }, + }) + .expect(200); + + expect(resp.body.success).to.be(true); + }); + + it('should fail if data is invalid', async () => { + await supertest + .post(`/api/maps/docSource`) + .set('kbn-xsrf', 'kibana') + .send({ + index: 'new-feature-index2', + mappings: { properties: { coordinates: { type: 'geo_point' } } }, + }); + await supertest + .post(`/api/maps/feature`) + .set('kbn-xsrf', 'kibana') + .send({ + index: 'new-feature-index2', + data: { coordinates: [600, 800], name: 'Never Gonna Happen Islands' }, + }) + .expect(500); + }); + + it('should fail if index does not exist', async () => { + await supertest + .post(`/api/maps/feature`) + .set('kbn-xsrf', 'kibana') + .send({ + index: 'not-an-index', + data: { coordinates: [125.6, 10.1], name: 'Dinagat Islands' }, + }) + .expect(500); + }); + }); +}