[APM] Add license check for significant terms (#88209)

* only shows significant terms under platinum license

* addressing PR comments

* addressing PR comments

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Cauê Marcondes 2021-01-15 09:48:57 +01:00 committed by GitHub
parent a0da8bda04
commit 6beef61f93
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 277 additions and 127 deletions

View file

@ -0,0 +1,217 @@
/*
* 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 { License } from '../../licensing/common/license';
import { isActiveGoldLicense, isActivePlatinumLicense } from './license_check';
describe('License check', () => {
describe('isActivePlatinumLicense', () => {
describe('with an expired license', () => {
it('returns false', () => {
const license = new License({
license: {
uid: 'test uid',
expiryDateInMillis: 0,
mode: 'platinum',
type: 'platinum',
status: 'expired',
},
signature: 'test signature',
});
expect(isActivePlatinumLicense(license)).toEqual(false);
});
});
describe('with a basic license', () => {
it('returns false', () => {
const license = new License({
license: {
uid: 'test uid',
expiryDateInMillis: 0,
mode: 'basic',
type: 'basic',
status: 'active',
},
signature: 'test signature',
});
expect(isActivePlatinumLicense(license)).toEqual(false);
});
});
describe('with a gold license', () => {
it('returns true', () => {
const license = new License({
license: {
uid: 'test uid',
expiryDateInMillis: 0,
mode: 'gold',
type: 'gold',
status: 'active',
},
signature: 'test signature',
});
expect(isActivePlatinumLicense(license)).toEqual(false);
});
});
describe('with a platinum license', () => {
it('returns true', () => {
const license = new License({
license: {
uid: 'test uid',
expiryDateInMillis: 0,
mode: 'platinum',
type: 'platinum',
status: 'active',
},
signature: 'test signature',
});
expect(isActivePlatinumLicense(license)).toEqual(true);
});
});
describe('with an enterprise license', () => {
it('returns true', () => {
const license = new License({
license: {
uid: 'test uid',
expiryDateInMillis: 0,
mode: 'enterprise',
type: 'enterprise',
status: 'active',
},
signature: 'test signature',
});
expect(isActivePlatinumLicense(license)).toEqual(true);
});
});
describe('with a trial license', () => {
it('returns true', () => {
const license = new License({
license: {
uid: 'test uid',
expiryDateInMillis: 0,
mode: 'trial',
type: 'trial',
status: 'active',
},
signature: 'test signature',
});
expect(isActivePlatinumLicense(license)).toEqual(true);
});
});
});
describe('isActiveGoldLicense', () => {
describe('with an expired license', () => {
it('returns false', () => {
const license = new License({
license: {
uid: 'test uid',
expiryDateInMillis: 0,
mode: 'gold',
type: 'gold',
status: 'expired',
},
signature: 'test signature',
});
expect(isActiveGoldLicense(license)).toEqual(false);
});
});
describe('with a basic license', () => {
it('returns false', () => {
const license = new License({
license: {
uid: 'test uid',
expiryDateInMillis: 0,
mode: 'basic',
type: 'basic',
status: 'active',
},
signature: 'test signature',
});
expect(isActiveGoldLicense(license)).toEqual(false);
});
});
describe('with a gold license', () => {
it('returns true', () => {
const license = new License({
license: {
uid: 'test uid',
expiryDateInMillis: 0,
mode: 'gold',
type: 'gold',
status: 'active',
},
signature: 'test signature',
});
expect(isActiveGoldLicense(license)).toEqual(true);
});
});
describe('with a platinum license', () => {
it('returns true', () => {
const license = new License({
license: {
uid: 'test uid',
expiryDateInMillis: 0,
mode: 'platinum',
type: 'platinum',
status: 'active',
},
signature: 'test signature',
});
expect(isActiveGoldLicense(license)).toEqual(true);
});
});
describe('with an enterprise license', () => {
it('returns true', () => {
const license = new License({
license: {
uid: 'test uid',
expiryDateInMillis: 0,
mode: 'enterprise',
type: 'enterprise',
status: 'active',
},
signature: 'test signature',
});
expect(isActiveGoldLicense(license)).toEqual(true);
});
});
describe('with a trial license', () => {
it('returns true', () => {
const license = new License({
license: {
uid: 'test uid',
expiryDateInMillis: 0,
mode: 'trial',
type: 'trial',
status: 'active',
},
signature: 'test signature',
});
expect(isActiveGoldLicense(license)).toEqual(true);
});
});
});
});

View file

@ -0,0 +1,18 @@
/*
* 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';
function isActiveLicense(licenseType: LicenseType, license?: ILicense) {
return license && license.isActive && license.hasAtLeast(licenseType);
}
export function isActivePlatinumLicense(license?: ILicense) {
return isActiveLicense('platinum', license);
}
export function isActiveGoldLicense(license?: ILicense) {
return isActiveLicense('gold', license);
}

View file

@ -1,97 +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 { License } from '../../licensing/common/license';
import * as serviceMap from './service_map';
describe('service map helpers', () => {
describe('isActivePlatinumLicense', () => {
describe('with an expired license', () => {
it('returns false', () => {
const license = new License({
license: {
uid: 'test uid',
expiryDateInMillis: 0,
mode: 'platinum',
type: 'platinum',
status: 'expired',
},
signature: 'test signature',
});
expect(serviceMap.isActivePlatinumLicense(license)).toEqual(false);
});
});
describe('with a basic license', () => {
it('returns false', () => {
const license = new License({
license: {
uid: 'test uid',
expiryDateInMillis: 0,
mode: 'basic',
type: 'basic',
status: 'active',
},
signature: 'test signature',
});
expect(serviceMap.isActivePlatinumLicense(license)).toEqual(false);
});
});
describe('with a platinum license', () => {
it('returns true', () => {
const license = new License({
license: {
uid: 'test uid',
expiryDateInMillis: 0,
mode: 'platinum',
type: 'platinum',
status: 'active',
},
signature: 'test signature',
});
expect(serviceMap.isActivePlatinumLicense(license)).toEqual(true);
});
});
describe('with an enterprise license', () => {
it('returns true', () => {
const license = new License({
license: {
uid: 'test uid',
expiryDateInMillis: 0,
mode: 'enterprise',
type: 'enterprise',
status: 'active',
},
signature: 'test signature',
});
expect(serviceMap.isActivePlatinumLicense(license)).toEqual(true);
});
});
describe('with a trial license', () => {
it('returns true', () => {
const license = new License({
license: {
uid: 'test uid',
expiryDateInMillis: 0,
mode: 'trial',
type: 'trial',
status: 'active',
},
signature: 'test signature',
});
expect(serviceMap.isActivePlatinumLicense(license)).toEqual(true);
});
});
});
});

View file

@ -6,7 +6,6 @@
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import cytoscape from 'cytoscape'; import cytoscape from 'cytoscape';
import { ILicense } from '../../licensing/common/types';
import { import {
AGENT_NAME, AGENT_NAME,
SERVICE_ENVIRONMENT, SERVICE_ENVIRONMENT,
@ -61,10 +60,6 @@ export interface ServiceNodeStats {
avgErrorRate: number | null; avgErrorRate: number | null;
} }
export function isActivePlatinumLicense(license: ILicense) {
return license.isActive && license.hasAtLeast('platinum');
}
export const invalidLicenseMessage = i18n.translate( export const invalidLicenseMessage = i18n.translate(
'xpack.apm.serviceMap.invalidLicenseMessage', 'xpack.apm.serviceMap.invalidLicenseMessage',
{ {

View file

@ -4,5 +4,5 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
export const enableCorrelations = 'apm:enableCorrelations'; export const enableSignificantTerms = 'apm:enableSignificantTerms';
export const enableServiceOverview = 'apm:enableServiceOverview'; export const enableServiceOverview = 'apm:enableServiceOverview';

View file

@ -19,19 +19,25 @@ import {
} from '@elastic/eui'; } from '@elastic/eui';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { EuiSpacer } from '@elastic/eui'; import { EuiSpacer } from '@elastic/eui';
import { enableCorrelations } from '../../../../common/ui_settings_keys'; import { isActivePlatinumLicense } from '../../../../common/license_check';
import { enableSignificantTerms } from '../../../../common/ui_settings_keys';
import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
import { LatencyCorrelations } from './LatencyCorrelations'; import { LatencyCorrelations } from './LatencyCorrelations';
import { ErrorCorrelations } from './ErrorCorrelations'; import { ErrorCorrelations } from './ErrorCorrelations';
import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { useUrlParams } from '../../../context/url_params_context/use_url_params';
import { createHref } from '../../shared/Links/url_helpers'; import { createHref } from '../../shared/Links/url_helpers';
import { useLicenseContext } from '../../../context/license/use_license_context';
export function Correlations() { export function Correlations() {
const { uiSettings } = useApmPluginContext().core; const { uiSettings } = useApmPluginContext().core;
const { urlParams } = useUrlParams(); const { urlParams } = useUrlParams();
const license = useLicenseContext();
const history = useHistory(); const history = useHistory();
const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); const [isFlyoutVisible, setIsFlyoutVisible] = useState(false);
if (!uiSettings.get(enableCorrelations)) { if (
!uiSettings.get(enableSignificantTerms) ||
!isActivePlatinumLicense(license)
) {
return null; return null;
} }

View file

@ -7,10 +7,10 @@
import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui';
import React, { PropsWithChildren, ReactNode } from 'react'; import React, { PropsWithChildren, ReactNode } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { isActivePlatinumLicense } from '../../../../common/license_check';
import { useTrackPageview } from '../../../../../observability/public'; import { useTrackPageview } from '../../../../../observability/public';
import { import {
invalidLicenseMessage, invalidLicenseMessage,
isActivePlatinumLicense,
SERVICE_MAP_TIMEOUT_ERROR, SERVICE_MAP_TIMEOUT_ERROR,
} from '../../../../common/service_map'; } from '../../../../common/service_map';
import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher';

View file

@ -6,11 +6,11 @@
import { Logger } from 'kibana/server'; import { Logger } from 'kibana/server';
import moment from 'moment'; import moment from 'moment';
import { isActivePlatinumLicense } from '../../../common/license_check';
import { APMConfig } from '../..'; import { APMConfig } from '../..';
import { KibanaRequest } from '../../../../../../src/core/server'; import { KibanaRequest } from '../../../../../../src/core/server';
import { UI_SETTINGS } from '../../../../../../src/plugins/data/common'; import { UI_SETTINGS } from '../../../../../../src/plugins/data/common';
import { ESFilter } from '../../../../../typings/elasticsearch'; import { ESFilter } from '../../../../../typings/elasticsearch';
import { isActivePlatinumLicense } from '../../../common/service_map';
import { UIFilters } from '../../../typings/ui_filters'; import { UIFilters } from '../../../typings/ui_filters';
import { APMRequestHandlerContext } from '../../routes/typings'; import { APMRequestHandlerContext } from '../../routes/typings';
import { import {

View file

@ -4,12 +4,23 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import Boom from '@hapi/boom';
import { i18n } from '@kbn/i18n';
import * as t from 'io-ts'; import * as t from 'io-ts';
import { rangeRt } from './default_api_types'; import { isActivePlatinumLicense } from '../../common/license_check';
import { getCorrelationsForSlowTransactions } from '../lib/correlations/get_correlations_for_slow_transactions';
import { getCorrelationsForFailedTransactions } from '../lib/correlations/get_correlations_for_failed_transactions'; import { getCorrelationsForFailedTransactions } from '../lib/correlations/get_correlations_for_failed_transactions';
import { createRoute } from './create_route'; import { getCorrelationsForSlowTransactions } from '../lib/correlations/get_correlations_for_slow_transactions';
import { setupRequest } from '../lib/helpers/setup_request'; import { setupRequest } from '../lib/helpers/setup_request';
import { createRoute } from './create_route';
import { rangeRt } from './default_api_types';
const INVALID_LICENSE = i18n.translate(
'xpack.apm.significanTerms.license.text',
{
defaultMessage:
'To use the correlations API, you must be subscribed to an Elastic Platinum license.',
}
);
export const correlationsForSlowTransactionsRoute = createRoute({ export const correlationsForSlowTransactionsRoute = createRoute({
endpoint: 'GET /api/apm/correlations/slow_transactions', endpoint: 'GET /api/apm/correlations/slow_transactions',
@ -30,6 +41,9 @@ export const correlationsForSlowTransactionsRoute = createRoute({
}), }),
options: { tags: ['access:apm'] }, options: { tags: ['access:apm'] },
handler: async ({ context, request }) => { handler: async ({ context, request }) => {
if (!isActivePlatinumLicense(context.licensing.license)) {
throw Boom.forbidden(INVALID_LICENSE);
}
const setup = await setupRequest(context, request); const setup = await setupRequest(context, request);
const { const {
serviceName, serviceName,
@ -68,6 +82,9 @@ export const correlationsForFailedTransactionsRoute = createRoute({
}), }),
options: { tags: ['access:apm'] }, options: { tags: ['access:apm'] },
handler: async ({ context, request }) => { handler: async ({ context, request }) => {
if (!isActivePlatinumLicense(context.licensing.license)) {
throw Boom.forbidden(INVALID_LICENSE);
}
const setup = await setupRequest(context, request); const setup = await setupRequest(context, request);
const { const {
serviceName, serviceName,

View file

@ -6,10 +6,7 @@
import Boom from '@hapi/boom'; import Boom from '@hapi/boom';
import * as t from 'io-ts'; import * as t from 'io-ts';
import { import { invalidLicenseMessage } from '../../common/service_map';
invalidLicenseMessage,
isActivePlatinumLicense,
} from '../../common/service_map';
import { setupRequest } from '../lib/helpers/setup_request'; import { setupRequest } from '../lib/helpers/setup_request';
import { getServiceMap } from '../lib/service_map/get_service_map'; import { getServiceMap } from '../lib/service_map/get_service_map';
import { getServiceMapServiceNodeInfo } from '../lib/service_map/get_service_map_service_node_info'; import { getServiceMapServiceNodeInfo } from '../lib/service_map/get_service_map_service_node_info';
@ -17,6 +14,7 @@ import { createRoute } from './create_route';
import { rangeRt, uiFiltersRt } from './default_api_types'; import { rangeRt, uiFiltersRt } from './default_api_types';
import { notifyFeatureUsage } from '../feature'; import { notifyFeatureUsage } from '../feature';
import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions'; import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions';
import { isActivePlatinumLicense } from '../../common/license_check';
export const serviceMapRoute = createRoute({ export const serviceMapRoute = createRoute({
endpoint: 'GET /api/apm/service-map', endpoint: 'GET /api/apm/service-map',

View file

@ -6,7 +6,7 @@
import * as t from 'io-ts'; import * as t from 'io-ts';
import Boom from '@hapi/boom'; import Boom from '@hapi/boom';
import { isActivePlatinumLicense } from '../../../common/service_map'; import { isActivePlatinumLicense } from '../../../common/license_check';
import { ML_ERRORS } from '../../../common/anomaly_detection'; import { ML_ERRORS } from '../../../common/anomaly_detection';
import { createRoute } from '../create_route'; import { createRoute } from '../create_route';
import { getAnomalyDetectionJobs } from '../../lib/anomaly_detection/get_anomaly_detection_jobs'; import { getAnomalyDetectionJobs } from '../../lib/anomaly_detection/get_anomaly_detection_jobs';

View file

@ -7,8 +7,8 @@
import Boom from '@hapi/boom'; import Boom from '@hapi/boom';
import * as t from 'io-ts'; import * as t from 'io-ts';
import { pick } from 'lodash'; import { pick } from 'lodash';
import { isActiveGoldLicense } from '../../../common/license_check';
import { INVALID_LICENSE } from '../../../common/custom_link'; import { INVALID_LICENSE } from '../../../common/custom_link';
import { ILicense } from '../../../../licensing/common/types';
import { FILTER_OPTIONS } from '../../../common/custom_link/custom_link_filter_options'; import { FILTER_OPTIONS } from '../../../common/custom_link/custom_link_filter_options';
import { notifyFeatureUsage } from '../../feature'; import { notifyFeatureUsage } from '../../feature';
import { setupRequest } from '../../lib/helpers/setup_request'; import { setupRequest } from '../../lib/helpers/setup_request';
@ -22,10 +22,6 @@ import { getTransaction } from '../../lib/settings/custom_link/get_transaction';
import { listCustomLinks } from '../../lib/settings/custom_link/list_custom_links'; import { listCustomLinks } from '../../lib/settings/custom_link/list_custom_links';
import { createRoute } from '../create_route'; import { createRoute } from '../create_route';
function isActiveGoldLicense(license: ILicense) {
return license.isActive && license.hasAtLeast('gold');
}
export const customLinkTransactionRoute = createRoute({ export const customLinkTransactionRoute = createRoute({
endpoint: 'GET /api/apm/settings/custom_links/transaction', endpoint: 'GET /api/apm/settings/custom_links/transaction',
options: { tags: ['access:apm'] }, options: { tags: ['access:apm'] },

View file

@ -8,7 +8,7 @@ import { schema } from '@kbn/config-schema';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { UiSettingsParams } from '../../../../src/core/types'; import { UiSettingsParams } from '../../../../src/core/types';
import { import {
enableCorrelations, enableSignificantTerms,
enableServiceOverview, enableServiceOverview,
} from '../common/ui_settings_keys'; } from '../common/ui_settings_keys';
@ -16,10 +16,10 @@ import {
* uiSettings definitions for APM. * uiSettings definitions for APM.
*/ */
export const uiSettings: Record<string, UiSettingsParams<boolean>> = { export const uiSettings: Record<string, UiSettingsParams<boolean>> = {
[enableCorrelations]: { [enableSignificantTerms]: {
category: ['observability'], category: ['observability'],
name: i18n.translate('xpack.apm.enableCorrelationsExperimentName', { name: i18n.translate('xpack.apm.enableCorrelationsExperimentName', {
defaultMessage: 'APM Significant terms', defaultMessage: 'APM Significant terms (Platinum required)',
}), }),
value: false, value: false,
description: i18n.translate( description: i18n.translate(

View file

@ -68,9 +68,5 @@ export default function apmApiIntegrationTests({ loadTestFile }: FtrProviderCont
describe('Metrics', function () { describe('Metrics', function () {
loadTestFile(require.resolve('./metrics_charts/metrics_charts')); loadTestFile(require.resolve('./metrics_charts/metrics_charts'));
}); });
describe('Correlations', function () {
loadTestFile(require.resolve('./correlations/slow_transactions'));
});
}); });
} }

View file

@ -42,5 +42,9 @@ export default function observabilityApiIntegrationTests({ loadTestFile }: FtrPr
loadTestFile(require.resolve('./csm/has_rum_data.ts')); loadTestFile(require.resolve('./csm/has_rum_data.ts'));
loadTestFile(require.resolve('./csm/page_load_dist.ts')); loadTestFile(require.resolve('./csm/page_load_dist.ts'));
}); });
describe('Correlations', function () {
loadTestFile(require.resolve('./correlations/slow_transactions'));
});
}); });
} }