Skip to content

Commit

Permalink
[APM] License prompt for service map (#52668)
Browse files Browse the repository at this point in the history
Display a link to platinum license upgrade on the service map.

Also add `useKibanaUrl` and `useLicense` hooks.

Make the `LicenseContext` (which is used in a couple class components and on page load) use the license observable from the NP plugin.

Add missing export of `useObservable` to kibana_react.
  • Loading branch information
smith committed Jan 10, 2020
1 parent 4636f10 commit bf9bf42
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,9 @@ export class ServiceIntegrations extends React.Component<Props, State> {
panels={[
{
id: 0,
items: this.getPanelItems(license.features.ml?.is_available)
items: this.getPanelItems(
license?.getFeature('ml').isAvailable
)
}
]}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* 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 React from 'react';
import {
EuiEmptyPrompt,
EuiButton,
EuiPanel,
EuiFlexGroup,
EuiFlexItem
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useKibanaUrl } from '../../../hooks/useKibanaUrl';

export function PlatinumLicensePrompt() {
// Set the height to give it some top margin
const style = { height: '60vh' };

const licensePageUrl = useKibanaUrl(
'/app/kibana',
'/management/elasticsearch/license_management/home'
);

return (
<EuiFlexGroup
alignItems="center"
justifyContent="spaceAround"
style={style}
>
<EuiFlexItem grow={false}>
<EuiPanel grow={false} hasShadow={true}>
<EuiEmptyPrompt
actions={[
<EuiButton fill={true} href={licensePageUrl}>
{i18n.translate(
'xpack.apm.serviceMap.licensePromptButtonText',
{
defaultMessage: 'Start 30-day Platinum trial'
}
)}
</EuiButton>
]}
body={
<p>
{i18n.translate('xpack.apm.serviceMap.licensePromptBody', {
defaultMessage:
"In order to access Service Maps, you must be subscribed to an Elastic Platinum license. With it, you'll have the ability to visualize your entire application stack along with your APM data."
})}
</p>
}
title={
<h2>
{i18n.translate('xpack.apm.serviceMap.licensePromptTitle', {
defaultMessage: 'Service maps is available in Platinum.'
})}
</h2>
}
/>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';
import theme from '@elastic/eui/dist/eui_theme_light.json';
import { useUrlParams } from '../../../hooks/useUrlParams';
import React from 'react';
import { useFetcher } from '../../../hooks/useFetcher';
import { Cytoscape } from './Cytoscape';
import { useLicense } from '../../../hooks/useLicense';
import { useUrlParams } from '../../../hooks/useUrlParams';
import { Controls } from './Controls';
import { Cytoscape } from './Cytoscape';
import { PlatinumLicensePrompt } from './PlatinumLicensePrompt';

interface ServiceMapProps {
serviceName?: string;
Expand Down Expand Up @@ -53,14 +55,19 @@ export function ServiceMap({ serviceName }: ServiceMapProps) {
);

const elements = Array.isArray(data) ? data : [];
const license = useLicense();
const isValidPlatinumLicense =
license?.isActive && license?.type === 'platinum';

return (
return isValidPlatinumLicense ? (
<Cytoscape
elements={elements}
serviceName={serviceName}
style={cytoscapeDivStyle}
>
<Controls />
</Cytoscape>
) : (
<PlatinumLicensePrompt />
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ export class TransactionCharts extends Component<TransactionChartProps> {
</EuiFlexItem>
<LicenseContext.Consumer>
{license =>
this.renderMLHeader(license.features.ml?.is_available)
this.renderMLHeader(license?.getFeature('ml').isAvailable)
}
</LicenseContext.Consumer>
</EuiFlexGroup>
Expand Down
34 changes: 14 additions & 20 deletions x-pack/legacy/plugins/apm/public/context/LicenseContext/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,27 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';
import { FETCH_STATUS, useFetcher } from '../../hooks/useFetcher';
import { loadLicense, LicenseApiResponse } from '../../services/rest/xpack';
import { InvalidLicenseNotification } from './InvalidLicenseNotification';
import { useObservable } from '../../../../../../../src/plugins/kibana_react/public';
import { ILicense } from '../../../../../../plugins/licensing/public';
import { useApmPluginContext } from '../../hooks/useApmPluginContext';
import { InvalidLicenseNotification } from './InvalidLicenseNotification';

const initialLicense: LicenseApiResponse = {
features: {},
license: {
is_active: false
}
};
export const LicenseContext = React.createContext(initialLicense);
export const LicenseContext = React.createContext<ILicense | undefined>(
undefined
);

export const LicenseProvider: React.FC = ({ children }) => {
const { http } = useApmPluginContext().core;
const { data = initialLicense, status } = useFetcher(
() => loadLicense(http),
[http]
);
const hasValidLicense = data.license.is_active;
export function LicenseProvider({ children }: { children: React.ReactChild }) {
const { license$ } = useApmPluginContext().plugins.licensing;
const license = useObservable(license$);
const hasInvalidLicense = !license?.isActive;

// if license is invalid show an error message
if (status === FETCH_STATUS.SUCCESS && !hasValidLicense) {
if (hasInvalidLicense) {
return <InvalidLicenseNotification />;
}

// render rest of application and pass down license via context
return <LicenseContext.Provider value={data} children={children} />;
};
return <LicenseContext.Provider value={license} children={children} />;
}
19 changes: 19 additions & 0 deletions x-pack/legacy/plugins/apm/public/hooks/useKibanaUrl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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 url from 'url';
import { useApmPluginContext } from './useApmPluginContext';

export function useKibanaUrl(
/** The path to the plugin */ path: string,
/** The hash path */ hash: string
) {
const { core } = useApmPluginContext();
return url.format({
pathname: core.http.basePath.prepend(path),
hash
});
}
12 changes: 12 additions & 0 deletions x-pack/legacy/plugins/apm/public/hooks/useLicense.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* 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 { useContext } from 'react';
import { LicenseContext } from '../context/LicenseContext';

export function useLicense() {
return useContext(LicenseContext);
}
32 changes: 17 additions & 15 deletions x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,36 @@

import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, Switch } from 'react-router-dom';
import { Route, Router, Switch } from 'react-router-dom';
import styled from 'styled-components';
import { metadata } from 'ui/metadata';
import { HomePublicPluginSetup } from '../../../../../../src/plugins/home/public';
import {
CoreSetup,
CoreStart,
PackageInfo,
Plugin,
CoreSetup,
PluginInitializerContext,
PackageInfo
PluginInitializerContext
} from '../../../../../../src/core/public';
import { DataPublicPluginSetup } from '../../../../../../src/plugins/data/public';
import { history } from '../utils/history';
import { LocationProvider } from '../context/LocationContext';
import { UrlParamsProvider } from '../context/UrlParamsContext';
import { px, unit, units } from '../style/variables';
import { LoadingIndicatorProvider } from '../context/LoadingIndicatorContext';
import { LicenseProvider } from '../context/LicenseContext';
import { UpdateBreadcrumbs } from '../components/app/Main/UpdateBreadcrumbs';
import { HomePublicPluginSetup } from '../../../../../../src/plugins/home/public';
import { LicensingPluginSetup } from '../../../../../plugins/licensing/public';
import { routes } from '../components/app/Main/route_config';
import { ScrollToTopOnPathChange } from '../components/app/Main/ScrollToTopOnPathChange';
import { UpdateBreadcrumbs } from '../components/app/Main/UpdateBreadcrumbs';
import { ApmPluginContext } from '../context/ApmPluginContext';
import { LicenseProvider } from '../context/LicenseContext';
import { LoadingIndicatorProvider } from '../context/LoadingIndicatorContext';
import { LocationProvider } from '../context/LocationContext';
import { MatchedRouteProvider } from '../context/MatchedRouteContext';
import { UrlParamsProvider } from '../context/UrlParamsContext';
import { createStaticIndexPattern } from '../services/rest/index_pattern';
import { setHelpExtension } from './setHelpExtension';
import { setReadonlyBadge } from './updateBadge';
import { px, unit, units } from '../style/variables';
import { history } from '../utils/history';
import { featureCatalogueEntry } from './featureCatalogueEntry';
import { getConfigFromInjectedMetadata } from './getConfigFromInjectedMetadata';
import { setHelpExtension } from './setHelpExtension';
import { toggleAppLinkInNav } from './toggleAppLinkInNav';
import { ApmPluginContext } from '../context/ApmPluginContext';
import { setReadonlyBadge } from './updateBadge';

export const REACT_APP_ROOT_ID = 'react-apm-root';

Expand Down Expand Up @@ -64,6 +65,7 @@ export type ApmPluginStart = void;
export interface ApmPluginSetupDeps {
data: DataPublicPluginSetup;
home: HomePublicPluginSetup;
licensing: LicensingPluginSetup;
}

export interface ConfigSchema {
Expand Down
45 changes: 0 additions & 45 deletions x-pack/legacy/plugins/apm/public/services/rest/xpack.ts

This file was deleted.

0 comments on commit bf9bf42

Please sign in to comment.