diff --git a/x-pack/legacy/plugins/maps/common/descriptor_types.d.ts b/x-pack/legacy/plugins/maps/common/descriptor_types.d.ts new file mode 100644 index 000000000000..05123c9c86b6 --- /dev/null +++ b/x-pack/legacy/plugins/maps/common/descriptor_types.d.ts @@ -0,0 +1,17 @@ +/* + * 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 { IFieldType } from '../../../../../src/plugins/data/common/index_patterns/fields'; + +export interface ISourceDescriptor { + id: string; + type: string; +} + +export interface ILayerDescriptor { + sourceDescriptor: ISourceDescriptor; + id: string; +} diff --git a/x-pack/legacy/plugins/maps/index.js b/x-pack/legacy/plugins/maps/index.js index 247dc8115c5c..5cd5a8731a70 100644 --- a/x-pack/legacy/plugins/maps/index.js +++ b/x-pack/legacy/plugins/maps/index.js @@ -9,7 +9,6 @@ import mappings from './mappings.json'; import { i18n } from '@kbn/i18n'; import { resolve } from 'path'; import { migrations } from './migrations'; -import { initTelemetryCollection } from './server/maps_telemetry'; import { getAppTitle } from './common/i18n_getters'; import { MapPlugin } from './server/plugin'; import { APP_ID, APP_ICON, createMapPath, MAP_SAVED_OBJECT_TYPE } from './common/constants'; @@ -92,12 +91,15 @@ export function maps(kibana) { init(server) { const mapsEnabled = server.config().get('xpack.maps.enabled'); - const { usageCollection } = server.newPlatform.setup.plugins; if (!mapsEnabled) { server.log(['info', 'maps'], 'Maps app disabled by configuration'); return; } - initTelemetryCollection(usageCollection, server); + + // Init saved objects client deps + const callCluster = server.plugins.elasticsearch.getCluster('admin').callWithInternalUser; + const { SavedObjectsClient, getSavedObjectsRepository } = server.savedObjects; + const internalRepository = getSavedObjectsRepository(callCluster); const coreSetup = server.newPlatform.setup.core; const newPlatformPlugins = server.newPlatform.setup.plugins; @@ -105,6 +107,7 @@ export function maps(kibana) { featuresPlugin: newPlatformPlugins.features, licensing: newPlatformPlugins.licensing, home: newPlatformPlugins.home, + usageCollection: newPlatformPlugins.usageCollection, }; // legacy dependencies @@ -118,6 +121,7 @@ export function maps(kibana) { elasticsearch: server.plugins.elasticsearch, }, savedObjects: { + savedObjectsClient: new SavedObjectsClient(internalRepository), getSavedObjectsRepository: server.savedObjects.getSavedObjectsRepository, }, injectUiAppVars: server.injectUiAppVars, diff --git a/x-pack/legacy/plugins/maps/server/maps_telemetry/collectors/register.ts b/x-pack/legacy/plugins/maps/server/maps_telemetry/collectors/register.ts new file mode 100644 index 000000000000..652bb83a0d78 --- /dev/null +++ b/x-pack/legacy/plugins/maps/server/maps_telemetry/collectors/register.ts @@ -0,0 +1,30 @@ +/* + * 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 { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +// @ts-ignore +import { SavedObjectsClientContract } from 'src/core/server'; +import { getMapsTelemetry } from '../maps_telemetry'; +// @ts-ignore +import { TELEMETRY_TYPE } from '../../../common/constants'; + +export function registerMapsUsageCollector( + usageCollection: UsageCollectionSetup, + savedObjectsClient: SavedObjectsClientContract, + config: Function +): void { + if (!usageCollection) { + return; + } + + const mapsUsageCollector = usageCollection.makeUsageCollector({ + type: TELEMETRY_TYPE, + isReady: () => true, + fetch: async () => await getMapsTelemetry(savedObjectsClient, config), + }); + + usageCollection.registerCollector(mapsUsageCollector); +} diff --git a/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_usage_collector.test.js b/x-pack/legacy/plugins/maps/server/maps_telemetry/collectors/register_collector.test.js similarity index 80% rename from x-pack/legacy/plugins/maps/server/maps_telemetry/maps_usage_collector.test.js rename to x-pack/legacy/plugins/maps/server/maps_telemetry/collectors/register_collector.test.js index c5a3fca89b56..33eb33100acd 100644 --- a/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_usage_collector.test.js +++ b/x-pack/legacy/plugins/maps/server/maps_telemetry/collectors/register_collector.test.js @@ -4,16 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import { initTelemetryCollection } from './maps_usage_collector'; +import { registerMapsUsageCollector } from './register'; describe('buildCollectorObj#fetch', () => { let makeUsageCollectorStub; + let savedObjectsClient; let registerStub; let usageCollection; + let config; beforeEach(() => { makeUsageCollectorStub = jest.fn(); + savedObjectsClient = jest.fn(); registerStub = jest.fn(); + config = jest.fn(); usageCollection = { makeUsageCollector: makeUsageCollectorStub, registerCollector: registerStub, @@ -21,8 +25,7 @@ describe('buildCollectorObj#fetch', () => { }); test('makes and registers maps usage collector', async () => { - const serverPlaceholder = {}; - initTelemetryCollection(usageCollection, serverPlaceholder); + registerMapsUsageCollector(usageCollection, savedObjectsClient, config); expect(registerStub).toHaveBeenCalledTimes(1); expect(makeUsageCollectorStub).toHaveBeenCalledTimes(1); diff --git a/x-pack/legacy/plugins/maps/server/maps_telemetry/index.js b/x-pack/legacy/plugins/maps/server/maps_telemetry/index.js deleted file mode 100644 index 513df3f76518..000000000000 --- a/x-pack/legacy/plugins/maps/server/maps_telemetry/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * 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/legacy/plugins/maps/server/maps_telemetry/maps_telemetry.js b/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_telemetry.ts similarity index 56% rename from x-pack/legacy/plugins/maps/server/maps_telemetry/maps_telemetry.js rename to x-pack/legacy/plugins/maps/server/maps_telemetry/maps_telemetry.ts index 848c964f4b6d..87642d9f8bea 100644 --- a/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_telemetry.js +++ b/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_telemetry.ts @@ -5,28 +5,73 @@ */ import _ from 'lodash'; +import { + SavedObjectsClientContract, + SavedObjectAttributes, + SavedObjectAttribute, +} from 'src/core/server'; +import { IFieldType, IIndexPattern } from 'src/plugins/data/public'; import { EMS_FILE, ES_GEO_FIELD_TYPE, MAP_SAVED_OBJECT_TYPE, TELEMETRY_TYPE, + // @ts-ignore } from '../../common/constants'; +import { ILayerDescriptor } from '../../common/descriptor_types'; + +interface IStats { + [key: string]: { + min: number; + max: number; + avg: number; + }; +} -function getSavedObjectsClient(server) { - const { SavedObjectsClient, getSavedObjectsRepository } = server.savedObjects; - const callCluster = server.plugins.elasticsearch.getCluster('admin').callWithInternalUser; - const internalRepository = getSavedObjectsRepository(callCluster); - return new SavedObjectsClient(internalRepository); +interface ILayerTypeCount { + [key: string]: number; +} + +interface IMapSavedObject { + [key: string]: any; + fields: IFieldType[]; + title: string; + id?: string; + type?: string; + timeFieldName?: string; + fieldFormatMap?: Record< + string, + { + id: string; + params: unknown; + } + >; + attributes?: { + title?: string; + description?: string; + mapStateJSON?: string; + layerListJSON?: string; + uiStateJSON?: string; + bounds?: { + type?: string; + coordinates?: []; + }; + }; } -function getUniqueLayerCounts(layerCountsList, mapsCount) { +function getUniqueLayerCounts(layerCountsList: ILayerTypeCount[], mapsCount: number) { 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; - }, []); + return uniqueLayerTypes.reduce((accu: IStats, type: string) => { + const typeCounts = layerCountsList.reduce( + (tCountsAccu: number[], tCounts: ILayerTypeCount): number[] => { + if (tCounts[type]) { + tCountsAccu.push(tCounts[type]); + } + return tCountsAccu; + }, + [] + ); const typeCountsSum = _.sum(typeCounts); accu[type] = { min: typeCounts.length ? _.min(typeCounts) : 0, @@ -37,25 +82,35 @@ function getUniqueLayerCounts(layerCountsList, mapsCount) { }, {}); } -function getIndexPatternsWithGeoFieldCount(indexPatterns) { +function getIndexPatternsWithGeoFieldCount(indexPatterns: IIndexPattern[]) { const fieldLists = indexPatterns.map(indexPattern => JSON.parse(indexPattern.attributes.fields)); - const fieldListsWithGeoFields = fieldLists.filter(fields => { - return fields.some( - field => + const fieldListsWithGeoFields = fieldLists.filter(fields => + fields.some( + (field: IFieldType) => field.type === ES_GEO_FIELD_TYPE.GEO_POINT || field.type === ES_GEO_FIELD_TYPE.GEO_SHAPE - ); - }); + ) + ); return fieldListsWithGeoFields.length; } -export function buildMapsTelemetry({ mapSavedObjects, indexPatternSavedObjects, settings }) { +export function buildMapsTelemetry({ + mapSavedObjects, + indexPatternSavedObjects, + settings, +}: { + mapSavedObjects: IMapSavedObject[]; + indexPatternSavedObjects: IIndexPattern[]; + settings: SavedObjectAttribute; +}): SavedObjectAttributes { const layerLists = mapSavedObjects.map(savedMapObject => - JSON.parse(savedMapObject.attributes.layerListJSON) + savedMapObject.attributes && savedMapObject.attributes.layerListJSON + ? JSON.parse(savedMapObject.attributes.layerListJSON) + : [] ); const mapsCount = layerLists.length; const dataSourcesCount = layerLists.map(lList => { - const sourceIdList = lList.map(layer => layer.sourceDescriptor.id); + const sourceIdList = lList.map((layer: ILayerDescriptor) => layer.sourceDescriptor.id); return _.uniq(sourceIdList).length; }); @@ -65,7 +120,7 @@ export function buildMapsTelemetry({ mapSavedObjects, indexPatternSavedObjects, // Count of EMS Vector layers used const emsLayersCount = layerLists.map(lList => _(lList) - .countBy(layer => { + .countBy((layer: ILayerDescriptor) => { const isEmsFile = _.get(layer, 'sourceDescriptor.type') === EMS_FILE; return isEmsFile && _.get(layer, 'sourceDescriptor.id'); }) @@ -110,23 +165,26 @@ export function buildMapsTelemetry({ mapSavedObjects, indexPatternSavedObjects, }, }; } - -async function getMapSavedObjects(savedObjectsClient) { +async function getMapSavedObjects(savedObjectsClient: SavedObjectsClientContract) { const mapsSavedObjects = await savedObjectsClient.find({ type: MAP_SAVED_OBJECT_TYPE }); return _.get(mapsSavedObjects, 'saved_objects', []); } -async function getIndexPatternSavedObjects(savedObjectsClient) { +async function getIndexPatternSavedObjects(savedObjectsClient: SavedObjectsClientContract) { const indexPatternSavedObjects = await savedObjectsClient.find({ type: 'index-pattern' }); return _.get(indexPatternSavedObjects, 'saved_objects', []); } -export async function getMapsTelemetry(server) { - const savedObjectsClient = getSavedObjectsClient(server); - const mapSavedObjects = await getMapSavedObjects(savedObjectsClient); - const indexPatternSavedObjects = await getIndexPatternSavedObjects(savedObjectsClient); - const settings = { - showMapVisualizationTypes: server.config().get('xpack.maps.showMapVisualizationTypes'), +export async function getMapsTelemetry( + savedObjectsClient: SavedObjectsClientContract, + config: Function +) { + const mapSavedObjects: IMapSavedObject[] = await getMapSavedObjects(savedObjectsClient); + const indexPatternSavedObjects: IIndexPattern[] = await getIndexPatternSavedObjects( + savedObjectsClient + ); + const settings: SavedObjectAttribute = { + showMapVisualizationTypes: config().get('xpack.maps.showMapVisualizationTypes'), }; const mapsTelemetry = buildMapsTelemetry({ mapSavedObjects, indexPatternSavedObjects, settings }); return await savedObjectsClient.create(TELEMETRY_TYPE, mapsTelemetry, { diff --git a/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_usage_collector.js b/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_usage_collector.js deleted file mode 100644 index 9c575e66f755..000000000000 --- a/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_usage_collector.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * 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'; -import { TELEMETRY_TYPE } from '../../common/constants'; - -export function initTelemetryCollection(usageCollection, server) { - if (!usageCollection) { - return; - } - - const mapsUsageCollector = usageCollection.makeUsageCollector({ - type: TELEMETRY_TYPE, - isReady: () => true, - fetch: async () => await getMapsTelemetry(server), - }); - - usageCollection.registerCollector(mapsUsageCollector); -} diff --git a/x-pack/legacy/plugins/maps/server/plugin.js b/x-pack/legacy/plugins/maps/server/plugin.js index d52a9f12ba63..02e38ff54b30 100644 --- a/x-pack/legacy/plugins/maps/server/plugin.js +++ b/x-pack/legacy/plugins/maps/server/plugin.js @@ -8,13 +8,14 @@ import { APP_ID, APP_ICON, createMapPath, MAP_SAVED_OBJECT_TYPE } from '../commo import { getEcommerceSavedObjects } from './sample_data/ecommerce_saved_objects'; import { getFlightsSavedObjects } from './sample_data/flights_saved_objects.js'; import { getWebLogsSavedObjects } from './sample_data/web_logs_saved_objects.js'; +import { registerMapsUsageCollector } from './maps_telemetry/collectors/register'; import { LICENSE_CHECK_STATE } from '../../../../plugins/licensing/server'; import { initRoutes } from './routes'; import { emsBoundariesSpecProvider } from './tutorials/ems'; export class MapPlugin { setup(core, plugins, __LEGACY) { - const { featuresPlugin, home, licensing } = plugins; + const { featuresPlugin, home, licensing, usageCollection } = plugins; let routesInitialized = false; featuresPlugin.registerFeature({ @@ -52,6 +53,10 @@ export class MapPlugin { } }); + // Init telemetry + const { savedObjectsClient } = __LEGACY.savedObjects; + registerMapsUsageCollector(usageCollection, savedObjectsClient, __LEGACY.config); + const sampleDataLinkLabel = i18n.translate('xpack.maps.sampleDataLinkLabel', { defaultMessage: 'Map', });