Skip to content

Commit

Permalink
[Maps] Enh/gis telemetry (#29346)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
kindsun authored Feb 5, 2019
1 parent 3a1d4ad commit 55e7b18
Show file tree
Hide file tree
Showing 13 changed files with 716 additions and 27 deletions.
2 changes: 2 additions & 0 deletions x-pack/plugins/maps/common/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
55 changes: 31 additions & 24 deletions x-pack/plugins/maps/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand All @@ -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) {
Expand All @@ -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');
});
}
});
}
50 changes: 49 additions & 1 deletion x-pack/plugins/maps/mappings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {}
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
7 changes: 7 additions & 0 deletions x-pack/plugins/maps/server/maps_telemetry/index.js
Original file line number Diff line number Diff line change
@@ -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 changes: 108 additions & 0 deletions x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.js
Original file line number Diff line number Diff line change
@@ -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,
});
}
88 changes: 88 additions & 0 deletions x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.test.js
Original file line number Diff line number Diff line change
@@ -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
});
});
});
Loading

0 comments on commit 55e7b18

Please sign in to comment.