diff --git a/x-pack/plugins/maps/common/constants.js b/x-pack/plugins/maps/common/constants.js index 0f0f1bd3122b..01f5dab5268b 100644 --- a/x-pack/plugins/maps/common/constants.js +++ b/x-pack/plugins/maps/common/constants.js @@ -6,6 +6,8 @@ export const GIS_API_PATH = 'api/maps'; +export const EMS_FILE = 'EMS_FILE'; + export const DECIMAL_DEGREES_PRECISION = 5; // meters precision export const ZOOM_PRECISION = 2; diff --git a/x-pack/plugins/maps/index.js b/x-pack/plugins/maps/index.js index 5b6e47425d31..a6342975b797 100644 --- a/x-pack/plugins/maps/index.js +++ b/x-pack/plugins/maps/index.js @@ -13,11 +13,12 @@ import mappings from './mappings.json'; import { checkLicense } from './check_license'; import { watchStatusAndLicenseToInitialize } from '../../server/lib/watch_status_and_license_to_initialize'; +import { initTelemetryCollection } from './server/maps_telemetry'; export function maps(kibana) { return new kibana.Plugin({ - require: ['kibana', 'elasticsearch', 'xpack_main', 'tile_map'], + require: ['kibana', 'elasticsearch', 'xpack_main', 'tile_map', 'task_manager'], id: 'maps', configPrefix: 'xpack.maps', publicDir: resolve(__dirname, 'public'), @@ -41,6 +42,11 @@ export function maps(kibana) { ], home: ['plugins/maps/register_feature'], styleSheetPaths: `${__dirname}/public/index.scss`, + savedObjectSchemas: { + 'maps-telemetry': { + isNamespaceAgnostic: true + } + }, mappings }, config(Joi) { @@ -52,32 +58,33 @@ export function maps(kibana) { init(server) { const mapsEnabled = server.config().get('xpack.maps.enabled'); - if (mapsEnabled) { - const thisPlugin = this; - const xpackMainPlugin = server.plugins.xpack_main; - let routesInitialized = false; - - watchStatusAndLicenseToInitialize(xpackMainPlugin, thisPlugin, - async license => { - if (license && license.maps && !routesInitialized) { - routesInitialized = true; - initRoutes(server, license.uid); - } - }); + if (!mapsEnabled) { + server.log(['info', 'maps'], 'Maps app disabled by configuration'); + return; + } + initTelemetryCollection(server); - xpackMainPlugin.info - .feature(thisPlugin.id) - .registerLicenseCheckResultsGenerator(checkLicense); + const xpackMainPlugin = server.plugins.xpack_main; + let routesInitialized = false; - server.addSavedObjectsToSampleDataset('ecommerce', ecommerceSavedObjects); - server.addSavedObjectsToSampleDataset('flights', fligthsSavedObjects); - server.addSavedObjectsToSampleDataset('logs', webLogsSavedObjects); - server.injectUiAppVars('maps', async () => { - return await server.getInjectedUiAppVars('kibana'); + watchStatusAndLicenseToInitialize(xpackMainPlugin, this, + async license => { + if (license && license.maps && !routesInitialized) { + routesInitialized = true; + initRoutes(server, license.uid); + } }); - } else { - server.log(['info', 'maps'], 'Maps app disabled by configuration'); - } + + xpackMainPlugin.info + .feature(this.id) + .registerLicenseCheckResultsGenerator(checkLicense); + + server.addSavedObjectsToSampleDataset('ecommerce', ecommerceSavedObjects); + server.addSavedObjectsToSampleDataset('flights', fligthsSavedObjects); + server.addSavedObjectsToSampleDataset('logs', webLogsSavedObjects); + server.injectUiAppVars('maps', async () => { + return await server.getInjectedUiAppVars('kibana'); + }); } }); } diff --git a/x-pack/plugins/maps/mappings.json b/x-pack/plugins/maps/mappings.json index d9277dfe07fb..7f80512980f0 100644 --- a/x-pack/plugins/maps/mappings.json +++ b/x-pack/plugins/maps/mappings.json @@ -23,5 +23,53 @@ "type": "text" } } + }, + "maps-telemetry": { + "properties": { + "mapsTotalCount": { + "type": "long" + }, + "timeCaptured": { + "type": "date" + }, + "attributesPerMap": { + "properties": { + "dataSourcesCount": { + "properties": { + "min": { + "type": "long" + }, + "max": { + "type": "long" + }, + "avg": { + "type": "long" + } + } + }, + "layersCount": { + "properties": { + "min": { + "type": "long" + }, + "max": { + "type": "long" + }, + "avg": { + "type": "long" + } + } + }, + "layerTypesCount": { + "dynamic": "true", + "properties": {} + }, + "emsVectorLayersCount": { + "dynamic": "true", + "properties": {} + } + } + } + } } -} +} \ No newline at end of file diff --git a/x-pack/plugins/maps/public/shared/layers/sources/ems_file_source/ems_file_source.js b/x-pack/plugins/maps/public/shared/layers/sources/ems_file_source/ems_file_source.js index fbeba95d93ed..2cb7ede96bb9 100644 --- a/x-pack/plugins/maps/public/shared/layers/sources/ems_file_source/ems_file_source.js +++ b/x-pack/plugins/maps/public/shared/layers/sources/ems_file_source/ems_file_source.js @@ -6,14 +6,14 @@ import { AbstractVectorSource } from '../vector_source'; import React from 'react'; -import { GIS_API_PATH } from '../../../../../common/constants'; +import { GIS_API_PATH, EMS_FILE } from '../../../../../common/constants'; import { emsServiceSettings } from '../../../../kibana_services'; import { getEmsVectorFilesMeta } from '../../../../meta'; import { EMSFileCreateSourceEditor } from './create_source_editor'; export class EMSFileSource extends AbstractVectorSource { - static type = 'EMS_FILE'; + static type = EMS_FILE; static title = 'Elastic Maps Service vector shapes'; static description = 'Vector shapes of administrative boundaries from Elastic Maps Service'; static icon = 'emsApp'; diff --git a/x-pack/plugins/maps/server/maps_telemetry/index.js b/x-pack/plugins/maps/server/maps_telemetry/index.js new file mode 100644 index 000000000000..513df3f76518 --- /dev/null +++ b/x-pack/plugins/maps/server/maps_telemetry/index.js @@ -0,0 +1,7 @@ +/* + * 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. + */ + +export { initTelemetryCollection } from './maps_usage_collector'; diff --git a/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.js b/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.js new file mode 100644 index 000000000000..09e777298f13 --- /dev/null +++ b/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.js @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import _ from 'lodash'; +import { EMS_FILE } from '../../common/constants'; + +function getSavedObjectsClient(server, callCluster) { + const { SavedObjectsClient, getSavedObjectsRepository } = server.savedObjects; + const internalRepository = getSavedObjectsRepository(callCluster); + return new SavedObjectsClient(internalRepository); +} + +function getUniqueLayerCounts(layerCountsList, mapsCount) { + const uniqueLayerTypes = _.uniq(_.flatten( + layerCountsList.map(lTypes => Object.keys(lTypes)))); + + return uniqueLayerTypes.reduce((accu, type) => { + const typeCounts = layerCountsList.reduce((accu, tCounts) => { + tCounts[type] && accu.push(tCounts[type]); + return accu; + }, []); + const typeCountsSum = _.sum(typeCounts); + accu[type] = { + min: typeCounts.length ? _.min(typeCounts) : 0, + max: typeCounts.length ? _.max(typeCounts) : 0, + avg: typeCountsSum ? typeCountsSum / mapsCount : 0 + }; + return accu; + }, {}); +} + +export function buildMapsTelemetry(savedObjects) { + const layerLists = savedObjects + .map(savedMapObject => + JSON.parse(savedMapObject.attributes.layerListJSON)); + const mapsCount = layerLists.length; + + const dataSourcesCount = layerLists.map(lList => { + const sourceIdList = lList.map(layer => layer.sourceDescriptor.id); + return _.uniq(sourceIdList).length; + }); + + const layersCount = layerLists.map(lList => lList.length); + const layerTypesCount = layerLists.map(lList => _.countBy(lList, 'type')); + + // Count of EMS Vector layers used + const emsLayersCount = layerLists.map(lList => _(lList) + .countBy(layer => { + const isEmsFile = _.get(layer, 'sourceDescriptor.type') === EMS_FILE; + return isEmsFile && _.get(layer, 'sourceDescriptor.id'); + }) + .pick((val, key) => key !== 'false') + .value()); + + const dataSourcesCountSum = _.sum(dataSourcesCount); + const layersCountSum = _.sum(layersCount); + const mapsTelem = { + // Total count of maps + mapsTotalCount: mapsCount, + // Time of capture + timeCaptured: new Date(), + attributesPerMap: { + // Count of data sources per map + dataSourcesCount: { + min: dataSourcesCount.length ? _.min(dataSourcesCount) : 0, + max: dataSourcesCount.length ? _.max(dataSourcesCount) : 0, + avg: dataSourcesCountSum ? layersCountSum / mapsCount : 0 + }, + // Total count of layers per map + layersCount: { + min: layersCount.length ? _.min(layersCount) : 0, + max: layersCount.length ? _.max(layersCount) : 0, + avg: layersCountSum ? layersCountSum / mapsCount : 0 + }, + // Count of layers by type + layerTypesCount: { + ...getUniqueLayerCounts(layerTypesCount, mapsCount) + }, + // Count of layer by EMS region + emsVectorLayersCount: { + ...getUniqueLayerCounts(emsLayersCount, mapsCount) + } + } + }; + return mapsTelem; +} + +async function getSavedObjects(savedObjectsClient) { + const gisMapsSavedObject = await savedObjectsClient.find({ + type: 'map' + }); + return _.get(gisMapsSavedObject, 'saved_objects'); +} + +export async function getMapsTelemetry(server, callCluster) { + const savedObjectsClient = getSavedObjectsClient(server, callCluster); + const savedObjects = await getSavedObjects(savedObjectsClient); + const mapsTelemetry = buildMapsTelemetry(savedObjects); + + return await savedObjectsClient.create('maps-telemetry', + mapsTelemetry, { + id: 'maps-telemetry', + overwrite: true, + }); +} diff --git a/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.test.js b/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.test.js new file mode 100644 index 000000000000..1e6dcc0cf28a --- /dev/null +++ b/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.test.js @@ -0,0 +1,88 @@ +/* + * 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 * as savedObjectsPayload from + './test_resources/sample_saved_objects.json'; +import { buildMapsTelemetry } from './maps_telemetry'; + +describe('buildMapsTelemetry', () => { + + test('returns zeroed telemetry data when there are no saved objects', + async () => { + + const gisMaps = []; + const result = buildMapsTelemetry(gisMaps); + + expect(result).toMatchObject({ + "attributesPerMap": { + "dataSourcesCount": { + "avg": 0, + "max": 0, + "min": 0 + }, + "emsVectorLayersCount": {}, + "layerTypesCount": {}, + "layersCount": { + "avg": 0, + "max": 0, + "min": 0 + } + }, + "mapsTotalCount": 0 + }); + }); + + test('returns expected telemetry data from saved objects', async () => { + + const gisMaps = savedObjectsPayload.saved_objects; + const result = buildMapsTelemetry(gisMaps); + + expect(result).toMatchObject({ + "attributesPerMap": { + "dataSourcesCount": { + "avg": 2.6666666666666665, + "max": 3, + "min": 2 + }, + "emsVectorLayersCount": { + "canada_provinces": { + "avg": 0.3333333333333333, + "max": 1, + "min": 1 + }, + "france_departments": { + "avg": 0.3333333333333333, + "max": 1, + "min": 1 + }, + "italy_provinces": { + "avg": 0.3333333333333333, + "max": 1, + "min": 1 + } + }, + "layerTypesCount": { + "TILE": { + "avg": 1, + "max": 1, + "min": 1 + }, + "VECTOR": { + "avg": 1.6666666666666667, + "max": 2, + "min": 1 + } + }, + "layersCount": { + "avg": 2.6666666666666665, + "max": 3, + "min": 2 + } + }, + "mapsTotalCount": 3 + }); + }); +}); \ No newline at end of file diff --git a/x-pack/plugins/maps/server/maps_telemetry/maps_usage_collector.js b/x-pack/plugins/maps/server/maps_telemetry/maps_usage_collector.js new file mode 100644 index 000000000000..48c49542580c --- /dev/null +++ b/x-pack/plugins/maps/server/maps_telemetry/maps_usage_collector.js @@ -0,0 +1,57 @@ +/* + * 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 _ from 'lodash'; +import { TASK_ID, scheduleTask, registerMapsTelemetryTask } from './telemetry_task'; + +export function initTelemetryCollection(server) { + registerMapsTelemetryTask(server.taskManager); + scheduleTask(server, server.taskManager); + registerMapsUsageCollector(server); +} + +export function buildCollectorObj(server) { + return { + type: 'maps', + fetch: async () => { + let docs; + try { + ({ docs } = await server.taskManager.fetch({ + query: { + bool: { + filter: { + term: { + _id: TASK_ID + } + } + } + } + })); + } catch (err) { + const errMessage = err && err.message ? err.message : err.toString(); + /* + * The usage service WILL to try to fetch from this collector before the task manager has been initialized, because the task manager + * has to wait for all plugins to initialize first. + * It's fine to ignore it as next time around it will be initialized (or it will throw a different type of error) + */ + if (errMessage.indexOf('NotInitialized') >= 0) { + docs = {}; + } else { + throw err; + } + } + + return _.get(docs, '[0].state.stats'); + }, + }; +} + +export function registerMapsUsageCollector(server) { + const collectorObj = buildCollectorObj(server); + const mapsUsageCollector = server.usage.collectorSet + .makeUsageCollector(collectorObj); + server.usage.collectorSet.register(mapsUsageCollector); +} \ No newline at end of file diff --git a/x-pack/plugins/maps/server/maps_telemetry/maps_usage_collector.test.js b/x-pack/plugins/maps/server/maps_telemetry/maps_usage_collector.test.js new file mode 100644 index 000000000000..073927ffd5bb --- /dev/null +++ b/x-pack/plugins/maps/server/maps_telemetry/maps_usage_collector.test.js @@ -0,0 +1,69 @@ +/* + * 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 sinon from 'sinon'; +import { + getMockCallWithInternal, + getMockKbnServer, + getMockTaskFetch, +} from '../test_utils'; +import { buildCollectorObj } from './maps_usage_collector'; + +describe('buildCollectorObj#fetch', () => { + let mockKbnServer; + + beforeEach(() => { + mockKbnServer = getMockKbnServer(); + }); + + test('can return empty stats', async () => { + const { type, fetch } = buildCollectorObj(mockKbnServer); + expect(type).toBe('maps'); + const fetchResult = await fetch(); + expect(fetchResult).toEqual({}); + }); + + test('provides known stats', async () => { + const mockTaskFetch = getMockTaskFetch([ + { + state: { + runs: 2, + stats: { wombat_sightings: { total: 712, max: 84, min: 7, avg: 63 } }, + }, + }, + ]); + mockKbnServer = getMockKbnServer(getMockCallWithInternal(), mockTaskFetch); + + const { type, fetch } = buildCollectorObj(mockKbnServer); + expect(type).toBe('maps'); + const fetchResult = await fetch(); + expect(fetchResult).toEqual( + { wombat_sightings: { total: 712, max: 84, min: 7, avg: 63 } }, + ); + }); + + describe('Error handling', () => { + test('Silently handles Task Manager NotInitialized', async () => { + const mockTaskFetch = sinon.stub(); + mockTaskFetch.rejects( + new Error('NotInitialized taskManager is still waiting for plugins to load') + ); + mockKbnServer = getMockKbnServer(getMockCallWithInternal(), mockTaskFetch); + + const { fetch } = buildCollectorObj(mockKbnServer); + await expect(fetch()).resolves.toBe(undefined); + }); + // In real life, the CollectorSet calls fetch and handles errors + test('defers the errors', async () => { + const mockTaskFetch = sinon.stub(); + mockTaskFetch.rejects(new Error('Sad violin')); + mockKbnServer = getMockKbnServer(getMockCallWithInternal(), mockTaskFetch); + + const { fetch } = buildCollectorObj(mockKbnServer); + await expect(fetch()).rejects.toMatchObject(new Error('Sad violin')); + }); + }); +}); \ No newline at end of file diff --git a/x-pack/plugins/maps/server/maps_telemetry/telemetry_task.js b/x-pack/plugins/maps/server/maps_telemetry/telemetry_task.js new file mode 100644 index 000000000000..bb90f2a0a01f --- /dev/null +++ b/x-pack/plugins/maps/server/maps_telemetry/telemetry_task.js @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getMapsTelemetry } from './maps_telemetry'; + +const TELEMETRY_TASK_TYPE = 'maps_telemetry'; + +export const TASK_ID = `Maps-${TELEMETRY_TASK_TYPE}`; + +export function scheduleTask(server, taskManager) { + const { kbnServer } = server.plugins.xpack_main.status.plugin; + + kbnServer.afterPluginsInit(() => { + taskManager.schedule({ + id: TASK_ID, + taskType: TELEMETRY_TASK_TYPE, + state: { stats: {}, runs: 0 }, + }); + }); +} + +export function registerMapsTelemetryTask(taskManager) { + taskManager.registerTaskDefinitions({ + [TELEMETRY_TASK_TYPE]: { + title: 'Maps telemetry fetch task', + type: TELEMETRY_TASK_TYPE, + timeout: '1m', + numWorkers: 2, + createTaskRunner: telemetryTaskRunner(), + }, + }); +} + +export function telemetryTaskRunner() { + + return ({ kbnServer, taskInstance }) => { + const { state } = taskInstance; + const prevState = state; + const { server } = kbnServer; + let mapsTelemetry = {}; + + const callCluster = server.plugins.elasticsearch.getCluster('admin') + .callWithInternalUser; + + return { + async run() { + try { + mapsTelemetry = await getMapsTelemetry(server, callCluster); + } catch (err) { + server.log(['warning'], `Error loading maps telemetry: ${err}`); + } finally { + return { + state: { + runs: state.runs || 0 + 1, + stats: mapsTelemetry.attributes || prevState.stats || {}, + }, + runAt: getNextMidnight(), + }; + } + }, + }; + }; +} + +export function getNextMidnight() { + const nextMidnight = new Date(); + nextMidnight.setHours(0, 0, 0, 0); + nextMidnight.setDate(nextMidnight.getDate() + 1); + return nextMidnight.toISOString(); +} \ No newline at end of file diff --git a/x-pack/plugins/maps/server/maps_telemetry/telemetry_task.test.js b/x-pack/plugins/maps/server/maps_telemetry/telemetry_task.test.js new file mode 100644 index 000000000000..e8b04b15b1e2 --- /dev/null +++ b/x-pack/plugins/maps/server/maps_telemetry/telemetry_task.test.js @@ -0,0 +1,43 @@ +/* + * 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 moment from 'moment'; +import { + getMockKbnServer, + getMockTaskInstance, +} from '../test_utils'; +import { telemetryTaskRunner } from './telemetry_task'; + +describe('telemetryTaskRunner', () => { + let mockTaskInstance; + let mockKbnServer; + beforeEach(() => { + mockTaskInstance = getMockTaskInstance(); + mockKbnServer = getMockKbnServer(); + }); + + test('Returns empty stats on error', async () => { + const kbnServer = { server: mockKbnServer }; + const getNextMidnight = () => + moment() + .add(1, 'days') + .startOf('day') + .toISOString(); + + const getRunner = telemetryTaskRunner(); + const runResult = await getRunner( + { kbnServer, taskInstance: mockTaskInstance } + ).run(); + + expect(runResult).toMatchObject({ + runAt: getNextMidnight(), + state: { + runs: 1, + stats: {}, + }, + }); + }); +}); \ No newline at end of file diff --git a/x-pack/plugins/maps/server/maps_telemetry/test_resources/sample_saved_objects.json b/x-pack/plugins/maps/server/maps_telemetry/test_resources/sample_saved_objects.json new file mode 100644 index 000000000000..aca2a8d80e99 --- /dev/null +++ b/x-pack/plugins/maps/server/maps_telemetry/test_resources/sample_saved_objects.json @@ -0,0 +1,136 @@ +{ + "page":1, + "per_page":20, + "total":3, + "saved_objects":[ + { + "type":"gis-map", + "id":"37b08d60-25b0-11e9-9858-0f3a1e60d007", + "attributes":{ + "title":"Italy Map", + "description":"", + "mapStateJSON":"{\"zoom\":4.82,\"center\":{\"lon\":11.41545,\"lat\":42.0865},\"timeFilters\":{\"from\":\"now-15w\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":false,\"interval\":0},\"query\":{\"language\":\"lucene\",\"query\":\"\"}}", + "layerListJSON":"[{\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"temporary\":false,\"id\":\"csq5v\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.65,\"visible\":true,\"style\":{\"type\":\"TILE\",\"properties\":{},\"previousStyle\":null},\"type\":\"TILE\"},{\"sourceDescriptor\":{\"type\":\"EMS_FILE\",\"id\":\"italy_provinces\"},\"temporary\":false,\"id\":\"0oye8\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.75,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#0c1f70\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"VECTOR\"},{\"sourceDescriptor\":{\"type\":\"ES_GEO_GRID\",\"id\":\"053fe296-f5ae-4cb0-9e73-a5752cb9ba74\",\"indexPatternId\":\"d3d7af60-4c81-11e8-b3d7-01146121b73d\",\"geoField\":\"DestLocation\",\"requestType\":\"point\",\"resolution\":\"COARSE\"},\"temporary\":false,\"id\":\"1gx22\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.75,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"color\":\"Greens\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"label\":\"Count\",\"name\":\"doc_count\",\"origin\":\"source\"},\"minSize\":4,\"maxSize\":32}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"VECTOR\"}]", + "uiStateJSON":"{}", + "bounds":{ + "type":"polygon", + "coordinates":[ + [ + [ + -5.29778, + 51.54155 + ], + [ + -5.29778, + 30.98066 + ], + [ + 28.12868, + 30.98066 + ], + [ + 28.12868, + 51.54155 + ], + [ + -5.29778, + 51.54155 + ] + ] + ] + } + }, + "references":[ + + ], + "updated_at":"2019-01-31T23:30:39.030Z", + "version":1 + }, + { + "type":"gis-map", + "id":"5c061dc0-25af-11e9-9858-0f3a1e60d007", + "attributes":{ + "title":"France Map", + "description":"", + "mapStateJSON":"{\"zoom\":3.43,\"center\":{\"lon\":-16.30411,\"lat\":42.88411},\"timeFilters\":{\"from\":\"now-15w\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":false,\"interval\":0},\"query\":{\"query\":\"\",\"language\":\"lucene\"}}", + "layerListJSON":"[{\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"temporary\":false,\"id\":\"csq5v\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.65,\"visible\":true,\"style\":{\"type\":\"TILE\",\"properties\":{},\"previousStyle\":null},\"type\":\"TILE\"},{\"sourceDescriptor\":{\"type\":\"EMS_FILE\",\"id\":\"france_departments\"},\"temporary\":false,\"id\":\"65xbw\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.25,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#19c1e6\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"VECTOR\"},{\"sourceDescriptor\":{\"id\":\"240125db-e612-4001-b853-50107e55d984\",\"type\":\"ES_SEARCH\",\"indexPatternId\":\"ff959d40-b880-11e8-a6d9-e546fe2bba5f\",\"geoField\":\"geoip.location\",\"limit\":2048,\"filterByMapBounds\":true,\"tooltipProperties\":[]},\"temporary\":false,\"id\":\"mdae9\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.75,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#1ce619\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"VECTOR\"}]", + "uiStateJSON":"{}", + "bounds":{ + "type":"polygon", + "coordinates":[ + [ + [ + -59.97005, + 63.9123 + ], + [ + -59.97005, + 11.25616 + ], + [ + 27.36184, + 11.25616 + ], + [ + 27.36184, + 63.9123 + ], + [ + -59.97005, + 63.9123 + ] + ] + ] + } + }, + "references":[ + + ], + "updated_at":"2019-01-31T23:24:30.492Z", + "version":1 + }, + { + "type":"gis-map", + "id":"b853d5f0-25ae-11e9-9858-0f3a1e60d007", + "attributes":{ + "title":"Canada Map", + "description":"", + "mapStateJSON":"{\"zoom\":2.12,\"center\":{\"lon\":-88.67592,\"lat\":34.23257},\"timeFilters\":{\"from\":\"now-15m\",\"to\":\"now\",\"mode\":\"quick\"},\"refreshConfig\":{\"isPaused\":false,\"interval\":0},\"query\":{\"query\":\"\",\"language\":\"lucene\"}}", + "layerListJSON":"[{\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"temporary\":false,\"id\":\"csq5v\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.65,\"visible\":true,\"style\":{\"type\":\"TILE\",\"properties\":{},\"previousStyle\":null},\"type\":\"TILE\"},{\"sourceDescriptor\":{\"type\":\"EMS_FILE\",\"id\":\"canada_provinces\"},\"temporary\":false,\"id\":\"kt086\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":0.75,\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#60895e\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}}},\"temporary\":true,\"previousStyle\":null},\"type\":\"VECTOR\"}]", + "uiStateJSON":"{}", + "bounds":{ + "type":"polygon", + "coordinates":[ + [ + [ + 163.37506, + 77.35215 + ], + [ + 163.37506, + -46.80667 + ], + [ + 19.2731, + -46.80667 + ], + [ + 19.2731, + 77.35215 + ], + [ + 163.37506, + 77.35215 + ] + ] + ] + } + }, + "references":[ + + ], + "updated_at":"2019-01-31T23:19:55.855Z", + "version":1 + } + ] +} \ No newline at end of file diff --git a/x-pack/plugins/maps/server/test_utils/index.js b/x-pack/plugins/maps/server/test_utils/index.js new file mode 100644 index 000000000000..8a11d233f1d4 --- /dev/null +++ b/x-pack/plugins/maps/server/test_utils/index.js @@ -0,0 +1,51 @@ +/* + * 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. + */ + +const defaultMockSavedObjects = [{ + _id: 'milk:steak', + _source: { + type: 'food', + }, +}]; +const defaultMockTaskDocs = [{ state: { runs: 0, stats: {} } }]; + +export const getMockTaskInstance = () => ({ state: { runs: 0, stats: {} } }); + +export const getMockCallWithInternal = (hits = defaultMockSavedObjects) => { + return () => { + return Promise.resolve({ hits: { hits } }); + }; +}; + +export const getMockTaskFetch = (docs = defaultMockTaskDocs) => { + return () => Promise.resolve({ docs }); +}; + +export const getMockKbnServer = ( + mockCallWithInternal = getMockCallWithInternal(), + mockTaskFetch = getMockTaskFetch()) => ({ + taskManager: { + registerTaskDefinitions: () => undefined, + schedule: () => Promise.resolve(), + fetch: mockTaskFetch, + }, + plugins: { + elasticsearch: { + getCluster: () => ({ + callWithInternalUser: mockCallWithInternal, + }), + }, + xpack_main: {}, + }, + usage: { + collectorSet: { + makeUsageCollector: () => '', + register: () => undefined, + }, + }, + config: () => ({ get: () => '' }), + log: () => undefined +}); \ No newline at end of file