[Maps] Register gold+ feature use (#79011)

This commit is contained in:
Thomas Neirynck 2020-10-02 14:04:03 -04:00 committed by GitHub
parent e9fd3902c5
commit 85528d0ecd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 197 additions and 35 deletions

View file

@ -5,6 +5,7 @@
*/
import { i18n } from '@kbn/i18n';
import { FeatureCollection } from 'geojson';
export const EMS_APP_NAME = 'kibana';
export const EMS_CATALOGUE_PATH = 'ems/catalogue';

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.
*/
import { addLayer } from './layer_actions';
import { LayerDescriptor } from '../../common/descriptor_types';
import { LICENSED_FEATURES } from '../licensed_features';
const getStoreMock = jest.fn();
const dispatchMock = jest.fn();
describe('layer_actions', () => {
afterEach(() => {
jest.resetAllMocks();
});
describe('addLayer', () => {
const notifyLicensedFeatureUsageMock = jest.fn();
beforeEach(() => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('../licensed_features').notifyLicensedFeatureUsage = (feature: LICENSED_FEATURES) => {
notifyLicensedFeatureUsageMock(feature);
};
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('../selectors/map_selectors').getMapReady = () => {
return true;
};
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('../selectors/map_selectors').createLayerInstance = () => {
return {
getLicensedFeatures() {
return [LICENSED_FEATURES.GEO_SHAPE_AGGS_GEO_TILE];
},
};
};
});
it('should register feature-use', async () => {
const action = addLayer(({} as unknown) as LayerDescriptor);
await action(dispatchMock, getStoreMock);
expect(notifyLicensedFeatureUsageMock).toHaveBeenCalledWith(
LICENSED_FEATURES.GEO_SHAPE_AGGS_GEO_TILE
);
});
});
});

View file

@ -14,6 +14,7 @@ import {
getSelectedLayerId,
getMapReady,
getMapColors,
createLayerInstance,
} from '../selectors/map_selectors';
import { FLYOUT_STATE } from '../reducers/ui';
import { cancelRequest } from '../reducers/non_serializable_instances';
@ -42,6 +43,7 @@ import { ILayer } from '../classes/layers/layer';
import { IVectorLayer } from '../classes/layers/vector_layer/vector_layer';
import { LAYER_STYLE_TYPE, LAYER_TYPE } from '../../common/constants';
import { IVectorStyle } from '../classes/styles/vector/vector_style';
import { notifyLicensedFeatureUsage } from '../licensed_features';
export function trackCurrentLayerState(layerId: string) {
return {
@ -108,7 +110,7 @@ export function cloneLayer(layerId: string) {
}
export function addLayer(layerDescriptor: LayerDescriptor) {
return (dispatch: Dispatch, getState: () => MapStoreState) => {
return async (dispatch: Dispatch, getState: () => MapStoreState) => {
const isMapReady = getMapReady(getState());
if (!isMapReady) {
dispatch({
@ -123,6 +125,10 @@ export function addLayer(layerDescriptor: LayerDescriptor) {
layer: layerDescriptor,
});
dispatch<any>(syncDataForLayerId(layerDescriptor.id));
const layer = createLayerInstance(layerDescriptor);
const features = await layer.getLicensedFeatures();
features.forEach(notifyLicensedFeatureUsage);
};
}

View file

@ -38,6 +38,7 @@ import {
VectorLayerDescriptor,
} from '../../../../common/descriptor_types';
import { IVectorSource } from '../../sources/vector_source';
import { LICENSED_FEATURES } from '../../../licensed_features';
const ACTIVE_COUNT_DATA_ID = 'ACTIVE_COUNT_DATA_ID';
@ -327,4 +328,11 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer {
super._syncData(syncContext, activeSource, activeStyle);
}
async getLicensedFeatures(): Promise<LICENSED_FEATURES[]> {
return [
...(await this._clusterSource.getLicensedFeatures()),
...(await this._documentSource.getLicensedFeatures()),
];
}
}

View file

@ -34,6 +34,7 @@ import { Attribution, ImmutableSourceProperty, ISource, SourceEditorArgs } from
import { DataRequestContext } from '../../actions';
import { IStyle } from '../styles/style';
import { getJoinAggKey } from '../../../common/get_agg_key';
import { LICENSED_FEATURES } from '../../licensed_features';
export interface ILayer {
getBounds(dataRequestContext: DataRequestContext): Promise<MapExtent | null>;
@ -91,6 +92,7 @@ export interface ILayer {
showJoinEditor(): boolean;
getJoinsDisabledReason(): string | null;
isFittable(): Promise<boolean>;
getLicensedFeatures(): Promise<LICENSED_FEATURES[]>;
}
export type Footnote = {
icon: ReactElement<any>;
@ -538,4 +540,8 @@ export class AbstractLayer implements ILayer {
supportsLabelsOnTop(): boolean {
return false;
}
async getLicensedFeatures(): Promise<LICENSED_FEATURES[]> {
return [];
}
}

View file

@ -1090,4 +1090,8 @@ export class VectorLayer extends AbstractLayer {
});
return targetFeature ? targetFeature : null;
}
async getLicensedFeatures() {
return await this._source.getLicensedFeatures();
}
}

View file

@ -24,12 +24,14 @@ import {
MVT_GETGRIDTILE_API_PATH,
GEOTILE_GRID_AGG_NAME,
GEOCENTROID_AGG_NAME,
ES_GEO_FIELD_TYPE,
} from '../../../../common/constants';
import { i18n } from '@kbn/i18n';
import { getDataSourceLabel } from '../../../../common/i18n_getters';
import { AbstractESAggSource, DEFAULT_METRIC } from '../es_agg_source';
import { DataRequestAbortError } from '../../util/data_request';
import { registerSource } from '../source_registry';
import { LICENSED_FEATURES } from '../../../licensed_features';
import rison from 'rison-node';
import { getHttp } from '../../../kibana_services';
@ -399,6 +401,13 @@ export class ESGeoGridSource extends AbstractESAggSource {
return [VECTOR_SHAPE_TYPE.POINT];
}
async getLicensedFeatures() {
const geoField = await this._getGeoField();
return geoField.type === ES_GEO_FIELD_TYPE.GEO_SHAPE
? [LICENSED_FEATURES.GEO_SHAPE_AGGS_GEO_TILE]
: [];
}
}
registerSource({

View file

@ -4,10 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { MapExtent, VectorSourceRequestMeta } from '../../../../common/descriptor_types';
jest.mock('../../../kibana_services');
import { getIndexPatternService, getSearchService, getHttp } from '../../../kibana_services';
import { getHttp, getIndexPatternService, getSearchService } from '../../../kibana_services';
import { ESGeoGridSource } from './es_geo_grid_source';
import {
ES_GEO_FIELD_TYPE,
@ -16,6 +13,9 @@ import {
SOURCE_TYPES,
} from '../../../../common/constants';
import { SearchSource } from 'src/plugins/data/public';
import { LICENSED_FEATURES } from '../../../licensed_features';
jest.mock('../../../kibana_services');
export class MockSearchSource {
setField = jest.fn();
@ -27,6 +27,8 @@ export class MockSearchSource {
describe('ESGeoGridSource', () => {
const geoFieldName = 'bar';
let esGeoFieldType = ES_GEO_FIELD_TYPE.GEO_POINT;
const mockIndexPatternService = {
get() {
return {
@ -34,7 +36,7 @@ describe('ESGeoGridSource', () => {
getByName() {
return {
name: geoFieldName,
type: ES_GEO_FIELD_TYPE.GEO_POINT,
type: esGeoFieldType,
};
},
},
@ -127,6 +129,11 @@ describe('ESGeoGridSource', () => {
});
});
afterEach(() => {
esGeoFieldType = ES_GEO_FIELD_TYPE.GEO_POINT;
jest.resetAllMocks();
});
const extent: MapExtent = {
minLon: -160,
minLat: -80,
@ -271,4 +278,17 @@ describe('ESGeoGridSource', () => {
);
});
});
describe('Gold+ usage', () => {
it('Should have none for points', async () => {
expect(await geogridSource.getLicensedFeatures()).toEqual([]);
});
it('Should have shape-aggs for geo_shape', async () => {
esGeoFieldType = ES_GEO_FIELD_TYPE.GEO_SHAPE;
expect(await geogridSource.getLicensedFeatures()).toEqual([
LICENSED_FEATURES.GEO_SHAPE_AGGS_GEO_TILE,
]);
});
});
});

View file

@ -15,6 +15,7 @@ import { IField } from '../fields/field';
import { FieldFormatter, MAX_ZOOM, MIN_ZOOM } from '../../../common/constants';
import { AbstractSourceDescriptor } from '../../../common/descriptor_types';
import { OnSourceChangeArgs } from '../../connected_components/layer_panel/view';
import { LICENSED_FEATURES } from '../../licensed_features';
export type SourceEditorArgs = {
onChange: (...args: OnSourceChangeArgs[]) => void;
@ -66,6 +67,7 @@ export interface ISource {
getValueSuggestions(field: IField, query: string): Promise<string[]>;
getMinZoom(): number;
getMaxZoom(): number;
getLicensedFeatures(): Promise<LICENSED_FEATURES[]>;
}
export class AbstractSource implements ISource {
@ -188,4 +190,8 @@ export class AbstractSource implements ISource {
getMaxZoom() {
return MAX_ZOOM;
}
async getLicensedFeatures(): Promise<LICENSED_FEATURES[]> {
return [];
}
}

View file

@ -5,6 +5,7 @@
*/
jest.mock('./kibana_services', () => ({}));
jest.mock('./licensed_features', () => ({}));
import {
getSourceFields,
@ -69,7 +70,7 @@ describe('Gold+ licensing', () => {
describe('basic license', () => {
beforeEach(() => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('./kibana_services').getIsGoldPlus = () => false;
require('./licensed_features').getIsGoldPlus = () => false;
});
describe('getAggregatableGeoFieldTypes', () => {
@ -92,7 +93,7 @@ describe('Gold+ licensing', () => {
describe('gold license', () => {
beforeEach(() => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('./kibana_services').getIsGoldPlus = () => true;
require('./licensed_features').getIsGoldPlus = () => true;
});
describe('getAggregatableGeoFieldTypes', () => {
test('Should add geo_shape field', () => {

View file

@ -6,9 +6,10 @@
import { IFieldType, IndexPattern } from 'src/plugins/data/public';
import { i18n } from '@kbn/i18n';
import { getIndexPatternService, getIsGoldPlus } from './kibana_services';
import { getIndexPatternService } from './kibana_services';
import { indexPatterns } from '../../../../src/plugins/data/public';
import { ES_GEO_FIELD_TYPE, ES_GEO_FIELD_TYPES } from '../common/constants';
import { getIsGoldPlus } from './licensed_features';
export function getGeoTileAggNotSupportedReason(field: IFieldType): string | null {
if (!field.aggregatable) {

View file

@ -5,17 +5,10 @@
*/
import _ from 'lodash';
import { CoreStart } from 'kibana/public';
import { MapsLegacyConfig } from '../../../../src/plugins/maps_legacy/config';
import { MapsConfigType } from '../config';
import { MapsPluginStartDependencies } from './plugin';
import { CoreStart } from '../../../../src/core/public';
let licenseId: string | undefined;
export const setLicenseId = (latestLicenseId: string | undefined) => (licenseId = latestLicenseId);
export const getLicenseId = () => licenseId;
let isGoldPlus: boolean = false;
export const setIsGoldPlus = (igp: boolean) => (isGoldPlus = igp);
export const getIsGoldPlus = () => isGoldPlus;
let kibanaVersion: string;
export const setKibanaVersion = (version: string) => (kibanaVersion = version);

View file

@ -0,0 +1,61 @@
/*
* 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 { ILicense, LicenseType } from '../../licensing/common/types';
import { LicensingPluginSetup, LicensingPluginStart } from '../../licensing/public';
import { APP_ID } from '../common/constants';
export enum LICENSED_FEATURES {
GEO_SHAPE_AGGS_GEO_TILE = 'GEO_SHAPE_AGGS_GEO_TILE',
}
export interface LicensedFeatureDetail {
name: string;
license: LicenseType;
}
export const LICENCED_FEATURES_DETAILS: Record<LICENSED_FEATURES, LicensedFeatureDetail> = {
[LICENSED_FEATURES.GEO_SHAPE_AGGS_GEO_TILE]: {
name: 'geo_tile aggregation on geo_shape field-type',
license: 'gold',
},
};
let licenseId: string | undefined;
let isGoldPlus: boolean = false;
export const getLicenseId = () => licenseId;
export const getIsGoldPlus = () => isGoldPlus;
export function registerLicensedFeatures(licensingPlugin: LicensingPluginSetup) {
for (const licensedFeature of Object.values(LICENSED_FEATURES)) {
licensingPlugin.featureUsage.register(
LICENCED_FEATURES_DETAILS[licensedFeature].name,
LICENCED_FEATURES_DETAILS[licensedFeature].license
);
}
}
let licensingPluginStart: LicensingPluginStart;
export function setLicensingPluginStart(licensingPlugin: LicensingPluginStart) {
licensingPluginStart = licensingPlugin;
licensingPluginStart.license$.subscribe((license: ILicense) => {
const gold = license.check(APP_ID, 'gold');
isGoldPlus = gold.state === 'valid';
licenseId = license.uid;
});
}
export function notifyLicensedFeatureUsage(licensedFeature: LICENSED_FEATURES) {
if (!licensingPluginStart) {
// eslint-disable-next-line no-console
console.error('May not call notifyLicensedFeatureUsage before plugin start');
return;
}
licensingPluginStart.featureUsage.notifyUsage(
LICENCED_FEATURES_DETAILS[LICENSED_FEATURES[licensedFeature]].name
);
}

View file

@ -12,14 +12,14 @@ jest.mock('@elastic/ems-client');
describe('default use without proxy', () => {
beforeEach(() => {
require('./kibana_services').getProxyElasticMapsServiceInMaps = () => false;
require('./kibana_services').getLicenseId = () => {
return 'foobarlicenseid';
};
require('./kibana_services').getIsEmsEnabled = () => true;
require('./kibana_services').getEmsTileLayerId = () => '123';
require('./kibana_services').getEmsFileApiUrl = () => 'https://file-api';
require('./kibana_services').getEmsTileApiUrl = () => 'https://tile-api';
require('./kibana_services').getEmsLandingPageUrl = () => 'http://test.com';
require('./licensed_features').getLicenseId = () => {
return 'foobarlicenseid';
};
});
test('should construct EMSClient with absolute file and tile API urls', async () => {

View file

@ -18,7 +18,6 @@ import {
} from '../common/constants';
import {
getHttp,
getLicenseId,
getIsEmsEnabled,
getRegionmapLayers,
getTilemap,
@ -29,6 +28,7 @@ import {
getProxyElasticMapsServiceInMaps,
getKibanaVersion,
} from './kibana_services';
import { getLicenseId } from './licensed_features';
export function getKibanaRegionList(): unknown[] {
return getRegionmapLayers();

View file

@ -18,10 +18,8 @@ import {
// @ts-ignore
import { MapView } from './inspector/views/map_view';
import {
setIsGoldPlus,
setKibanaCommonConfig,
setKibanaVersion,
setLicenseId,
setMapAppConfig,
setStartServices,
} from './kibana_services';
@ -42,7 +40,6 @@ import { MapEmbeddableFactory } from './embeddable/map_embeddable_factory';
import { EmbeddableSetup } from '../../../../src/plugins/embeddable/public';
import { MapsXPackConfig, MapsConfigType } from '../config';
import { getAppTitle } from '../common/i18n_getters';
import { ILicense } from '../../licensing/common/types';
import { lazyLoadMapModules } from './lazy_load_bundle';
import { MapsStartApi } from './api';
import { createSecurityLayerDescriptors, registerLayerWizard, registerSource } from './api';
@ -50,8 +47,9 @@ import { SharePluginSetup, SharePluginStart } from '../../../../src/plugins/shar
import { EmbeddableStart } from '../../../../src/plugins/embeddable/public';
import { MapsLegacyConfig } from '../../../../src/plugins/maps_legacy/config';
import { DataPublicPluginStart } from '../../../../src/plugins/data/public';
import { LicensingPluginStart } from '../../licensing/public';
import { LicensingPluginSetup, LicensingPluginStart } from '../../licensing/public';
import { StartContract as FileUploadStartContract } from '../../file_upload/public';
import { registerLicensedFeatures, setLicensingPluginStart } from './licensed_features';
export interface MapsPluginSetupDependencies {
inspector: InspectorSetupContract;
@ -60,6 +58,7 @@ export interface MapsPluginSetupDependencies {
embeddable: EmbeddableSetup;
mapsLegacy: { config: MapsLegacyConfig };
share: SharePluginSetup;
licensing: LicensingPluginSetup;
}
export interface MapsPluginStartDependencies {
@ -97,6 +96,8 @@ export class MapsPlugin
}
public setup(core: CoreSetup, plugins: MapsPluginSetupDependencies) {
registerLicensedFeatures(plugins.licensing);
const config = this._initializerContext.config.get<MapsConfigType>();
setKibanaCommonConfig(plugins.mapsLegacy.config);
setMapAppConfig(config);
@ -138,13 +139,7 @@ export class MapsPlugin
}
public start(core: CoreStart, plugins: MapsPluginStartDependencies): MapsStartApi {
if (plugins.licensing) {
plugins.licensing.license$.subscribe((license: ILicense) => {
const gold = license.check(APP_ID, 'gold');
setIsGoldPlus(gold.state === 'valid');
setLicenseId(license.uid);
});
}
setLicensingPluginStart(plugins.licensing);
plugins.uiActions.addTriggerAction(VISUALIZE_GEO_FIELD_TRIGGER, visualizeGeoFieldAction);
setStartServices(core, plugins);

View file

@ -52,9 +52,9 @@ import { ITMSSource } from '../classes/sources/tms_source';
import { IVectorSource } from '../classes/sources/vector_source';
import { ILayer } from '../classes/layers/layer';
function createLayerInstance(
export function createLayerInstance(
layerDescriptor: LayerDescriptor,
inspectorAdapters: Adapters
inspectorAdapters?: Adapters
): ILayer {
const source: ISource = createSourceInstance(layerDescriptor.sourceDescriptor, inspectorAdapters);
@ -94,7 +94,7 @@ function createLayerInstance(
}
}
function createSourceInstance(sourceDescriptor: any, inspectorAdapters: Adapters): ISource {
function createSourceInstance(sourceDescriptor: any, inspectorAdapters?: Adapters): ISource {
const source = getSourceByType(sourceDescriptor.type);
if (!source) {
throw new Error(`Unrecognized sourceType ${sourceDescriptor.type}`);