[Maps] Enh/gis telemetry (#29346)

* Add basic framework for maps telemetry gathering

* Update mappings to cover required maps usage metrics

* Update telemetry to harvest from saved maps

* Consolidate. Clean up

* Harvest telemetry daily at midnight using task manager. Retrieve task mgr state in usage collector

* Organize, clean up

* Reorg

* Add usage collector tests. Add test utils

* Add maps telemetry tests to verify values. Multiple structure changes to support testing

* Review feedback

* More review feedback

* Add capture date/time to telemetry

* Change date to 'type: date'

* Review feedback

* Fix merge issue
This commit is contained in:
Aaron Caldwell 2019-02-05 09:45:05 -07:00 committed by GitHub
parent 3a1d4ad0ac
commit 55e7b183d7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 717 additions and 28 deletions

View file

@ -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;

View file

@ -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);
}
});
xpackMainPlugin.info
.feature(thisPlugin.id)
.registerLicenseCheckResultsGenerator(checkLicense);
server.addSavedObjectsToSampleDataset('ecommerce', ecommerceSavedObjects);
server.addSavedObjectsToSampleDataset('flights', fligthsSavedObjects);
server.addSavedObjectsToSampleDataset('logs', webLogsSavedObjects);
server.injectUiAppVars('maps', async () => {
return await server.getInjectedUiAppVars('kibana');
});
} else {
if (!mapsEnabled) {
server.log(['info', 'maps'], 'Maps app disabled by configuration');
return;
}
initTelemetryCollection(server);
const xpackMainPlugin = server.plugins.xpack_main;
let routesInitialized = false;
watchStatusAndLicenseToInitialize(xpackMainPlugin, this,
async license => {
if (license && license.maps && !routesInitialized) {
routesInitialized = true;
initRoutes(server, license.uid);
}
});
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');
});
}
});
}

View file

@ -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": {}
}
}
}
}
}
}
}

View file

@ -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';

View file

@ -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';

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;
* 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,
});
}

View file

@ -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
});
});
});

View file

@ -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);
}

View file

@ -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'));
});
});
});

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;
* 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();
}

View file

@ -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: {},
},
});
});
});

View file

@ -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
}
]
}

View file

@ -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
});