diff --git a/x-pack/plugins/apm/common/license_check.test.ts b/x-pack/plugins/apm/common/license_check.test.ts new file mode 100644 index 000000000000..2cad197719be --- /dev/null +++ b/x-pack/plugins/apm/common/license_check.test.ts @@ -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); + }); + }); + }); +}); diff --git a/x-pack/plugins/apm/common/license_check.ts b/x-pack/plugins/apm/common/license_check.ts new file mode 100644 index 000000000000..81b9196173cf --- /dev/null +++ b/x-pack/plugins/apm/common/license_check.ts @@ -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); +} diff --git a/x-pack/plugins/apm/common/service_map.test.ts b/x-pack/plugins/apm/common/service_map.test.ts deleted file mode 100644 index 31f439a7aaec..000000000000 --- a/x-pack/plugins/apm/common/service_map.test.ts +++ /dev/null @@ -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); - }); - }); - }); -}); diff --git a/x-pack/plugins/apm/common/service_map.ts b/x-pack/plugins/apm/common/service_map.ts index 6edf56fb9a1a..17f0d110cf43 100644 --- a/x-pack/plugins/apm/common/service_map.ts +++ b/x-pack/plugins/apm/common/service_map.ts @@ -6,7 +6,6 @@ import { i18n } from '@kbn/i18n'; import cytoscape from 'cytoscape'; -import { ILicense } from '../../licensing/common/types'; import { AGENT_NAME, SERVICE_ENVIRONMENT, @@ -61,10 +60,6 @@ export interface ServiceNodeStats { avgErrorRate: number | null; } -export function isActivePlatinumLicense(license: ILicense) { - return license.isActive && license.hasAtLeast('platinum'); -} - export const invalidLicenseMessage = i18n.translate( 'xpack.apm.serviceMap.invalidLicenseMessage', { diff --git a/x-pack/plugins/apm/common/ui_settings_keys.ts b/x-pack/plugins/apm/common/ui_settings_keys.ts index 38922fa445a4..ffc2a2ef21fe 100644 --- a/x-pack/plugins/apm/common/ui_settings_keys.ts +++ b/x-pack/plugins/apm/common/ui_settings_keys.ts @@ -4,5 +4,5 @@ * 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'; diff --git a/x-pack/plugins/apm/public/components/app/Correlations/index.tsx b/x-pack/plugins/apm/public/components/app/Correlations/index.tsx index 217400bbcc0f..fada90039aec 100644 --- a/x-pack/plugins/apm/public/components/app/Correlations/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Correlations/index.tsx @@ -19,19 +19,25 @@ import { } from '@elastic/eui'; import { useHistory } from 'react-router-dom'; 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 { LatencyCorrelations } from './LatencyCorrelations'; import { ErrorCorrelations } from './ErrorCorrelations'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { createHref } from '../../shared/Links/url_helpers'; +import { useLicenseContext } from '../../../context/license/use_license_context'; export function Correlations() { const { uiSettings } = useApmPluginContext().core; const { urlParams } = useUrlParams(); + const license = useLicenseContext(); const history = useHistory(); const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); - if (!uiSettings.get(enableCorrelations)) { + if ( + !uiSettings.get(enableSignificantTerms) || + !isActivePlatinumLicense(license) + ) { return null; } diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx index da4a8596970e..6f8d05890318 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx @@ -7,10 +7,10 @@ import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; import React, { PropsWithChildren, ReactNode } from 'react'; import styled from 'styled-components'; +import { isActivePlatinumLicense } from '../../../../common/license_check'; import { useTrackPageview } from '../../../../../observability/public'; import { invalidLicenseMessage, - isActivePlatinumLicense, SERVICE_MAP_TIMEOUT_ERROR, } from '../../../../common/service_map'; import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; diff --git a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts index 7e128493c873..47529de1042a 100644 --- a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts +++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts @@ -6,11 +6,11 @@ import { Logger } from 'kibana/server'; import moment from 'moment'; +import { isActivePlatinumLicense } from '../../../common/license_check'; import { APMConfig } from '../..'; import { KibanaRequest } from '../../../../../../src/core/server'; import { UI_SETTINGS } from '../../../../../../src/plugins/data/common'; import { ESFilter } from '../../../../../typings/elasticsearch'; -import { isActivePlatinumLicense } from '../../../common/service_map'; import { UIFilters } from '../../../typings/ui_filters'; import { APMRequestHandlerContext } from '../../routes/typings'; import { diff --git a/x-pack/plugins/apm/server/routes/correlations.ts b/x-pack/plugins/apm/server/routes/correlations.ts index 6d1aead9292e..d9431b6f8c0b 100644 --- a/x-pack/plugins/apm/server/routes/correlations.ts +++ b/x-pack/plugins/apm/server/routes/correlations.ts @@ -4,12 +4,23 @@ * 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 { rangeRt } from './default_api_types'; -import { getCorrelationsForSlowTransactions } from '../lib/correlations/get_correlations_for_slow_transactions'; +import { isActivePlatinumLicense } from '../../common/license_check'; 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 { 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({ endpoint: 'GET /api/apm/correlations/slow_transactions', @@ -30,6 +41,9 @@ export const correlationsForSlowTransactionsRoute = createRoute({ }), options: { tags: ['access:apm'] }, handler: async ({ context, request }) => { + if (!isActivePlatinumLicense(context.licensing.license)) { + throw Boom.forbidden(INVALID_LICENSE); + } const setup = await setupRequest(context, request); const { serviceName, @@ -68,6 +82,9 @@ export const correlationsForFailedTransactionsRoute = createRoute({ }), options: { tags: ['access:apm'] }, handler: async ({ context, request }) => { + if (!isActivePlatinumLicense(context.licensing.license)) { + throw Boom.forbidden(INVALID_LICENSE); + } const setup = await setupRequest(context, request); const { serviceName, diff --git a/x-pack/plugins/apm/server/routes/service_map.ts b/x-pack/plugins/apm/server/routes/service_map.ts index 452b00a7ae32..1192291de7b5 100644 --- a/x-pack/plugins/apm/server/routes/service_map.ts +++ b/x-pack/plugins/apm/server/routes/service_map.ts @@ -6,10 +6,7 @@ import Boom from '@hapi/boom'; import * as t from 'io-ts'; -import { - invalidLicenseMessage, - isActivePlatinumLicense, -} from '../../common/service_map'; +import { invalidLicenseMessage } from '../../common/service_map'; import { setupRequest } from '../lib/helpers/setup_request'; import { getServiceMap } from '../lib/service_map/get_service_map'; 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 { notifyFeatureUsage } from '../feature'; import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions'; +import { isActivePlatinumLicense } from '../../common/license_check'; export const serviceMapRoute = createRoute({ endpoint: 'GET /api/apm/service-map', diff --git a/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts b/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts index 49708e4edb73..b5727862a898 100644 --- a/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts +++ b/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts @@ -6,7 +6,7 @@ import * as t from 'io-ts'; 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 { createRoute } from '../create_route'; import { getAnomalyDetectionJobs } from '../../lib/anomaly_detection/get_anomaly_detection_jobs'; diff --git a/x-pack/plugins/apm/server/routes/settings/custom_link.ts b/x-pack/plugins/apm/server/routes/settings/custom_link.ts index 70755540721d..89ac698e9208 100644 --- a/x-pack/plugins/apm/server/routes/settings/custom_link.ts +++ b/x-pack/plugins/apm/server/routes/settings/custom_link.ts @@ -7,8 +7,8 @@ import Boom from '@hapi/boom'; import * as t from 'io-ts'; import { pick } from 'lodash'; +import { isActiveGoldLicense } from '../../../common/license_check'; 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 { notifyFeatureUsage } from '../../feature'; 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 { createRoute } from '../create_route'; -function isActiveGoldLicense(license: ILicense) { - return license.isActive && license.hasAtLeast('gold'); -} - export const customLinkTransactionRoute = createRoute({ endpoint: 'GET /api/apm/settings/custom_links/transaction', options: { tags: ['access:apm'] }, diff --git a/x-pack/plugins/apm/server/ui_settings.ts b/x-pack/plugins/apm/server/ui_settings.ts index 80225de5195f..c86fb636b5a1 100644 --- a/x-pack/plugins/apm/server/ui_settings.ts +++ b/x-pack/plugins/apm/server/ui_settings.ts @@ -8,7 +8,7 @@ import { schema } from '@kbn/config-schema'; import { i18n } from '@kbn/i18n'; import { UiSettingsParams } from '../../../../src/core/types'; import { - enableCorrelations, + enableSignificantTerms, enableServiceOverview, } from '../common/ui_settings_keys'; @@ -16,10 +16,10 @@ import { * uiSettings definitions for APM. */ export const uiSettings: Record> = { - [enableCorrelations]: { + [enableSignificantTerms]: { category: ['observability'], name: i18n.translate('xpack.apm.enableCorrelationsExperimentName', { - defaultMessage: 'APM Significant terms', + defaultMessage: 'APM Significant terms (Platinum required)', }), value: false, description: i18n.translate( diff --git a/x-pack/test/apm_api_integration/basic/tests/index.ts b/x-pack/test/apm_api_integration/basic/tests/index.ts index 645d4078f933..4e66c6e6f76c 100644 --- a/x-pack/test/apm_api_integration/basic/tests/index.ts +++ b/x-pack/test/apm_api_integration/basic/tests/index.ts @@ -68,9 +68,5 @@ export default function apmApiIntegrationTests({ loadTestFile }: FtrProviderCont describe('Metrics', function () { loadTestFile(require.resolve('./metrics_charts/metrics_charts')); }); - - describe('Correlations', function () { - loadTestFile(require.resolve('./correlations/slow_transactions')); - }); }); } diff --git a/x-pack/test/apm_api_integration/basic/tests/correlations/slow_transactions.ts b/x-pack/test/apm_api_integration/trial/tests/correlations/slow_transactions.ts similarity index 100% rename from x-pack/test/apm_api_integration/basic/tests/correlations/slow_transactions.ts rename to x-pack/test/apm_api_integration/trial/tests/correlations/slow_transactions.ts diff --git a/x-pack/test/apm_api_integration/trial/tests/index.ts b/x-pack/test/apm_api_integration/trial/tests/index.ts index 262b0f2b0daa..c8ee858d9ceb 100644 --- a/x-pack/test/apm_api_integration/trial/tests/index.ts +++ b/x-pack/test/apm_api_integration/trial/tests/index.ts @@ -42,5 +42,9 @@ export default function observabilityApiIntegrationTests({ loadTestFile }: FtrPr loadTestFile(require.resolve('./csm/has_rum_data.ts')); loadTestFile(require.resolve('./csm/page_load_dist.ts')); }); + + describe('Correlations', function () { + loadTestFile(require.resolve('./correlations/slow_transactions')); + }); }); }