mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[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:
parent
3a1d4ad0ac
commit
55e7b183d7
13 changed files with 717 additions and 28 deletions
|
@ -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;
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
|
|
7
x-pack/plugins/maps/server/maps_telemetry/index.js
Normal file
7
x-pack/plugins/maps/server/maps_telemetry/index.js
Normal 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';
|
108
x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.js
Normal file
108
x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.js
Normal 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,
|
||||
});
|
||||
}
|
|
@ -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
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
}
|
|
@ -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'));
|
||||
});
|
||||
});
|
||||
});
|
73
x-pack/plugins/maps/server/maps_telemetry/telemetry_task.js
Normal file
73
x-pack/plugins/maps/server/maps_telemetry/telemetry_task.js
Normal 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();
|
||||
}
|
|
@ -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: {},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
51
x-pack/plugins/maps/server/test_utils/index.js
Normal file
51
x-pack/plugins/maps/server/test_utils/index.js
Normal 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
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue