mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[License Management] Add URL locator (#153792)
## Summary Fixes https://github.com/elastic/kibana/issues/153104 Fixes https://github.com/elastic/kibana/issues/153037 This PR adds a URL locator to the License Management plugin that can be used by other plugins to safely render links to that plugin. An example of that is implemented in the Watcher plugin. ### How to test Since there is some logic in the code that disables Watcher if the license is not valid for that feature, it is actually not very probable to run into an issue with an invalid license in the UI. But we still can mock the license status and test the changes of this PR: 1. Start ES with the trial license `yarn es snapshot --license=trial` and start Kibana `yarn start`. 2. Update this [file](https://github.com/elastic/kibana/blob/main/x-pack/plugins/watcher/public/application/app.tsx) to mock an invalid license status: change line 61 to `if (true) {`. 3. Navigate to Watcher to see that there is a link to License Management. 4. Update this [file](https://github.com/elastic/kibana/blob/main/x-pack/plugins/watcher/public/application/app.tsx) to mock an undefined License Management URL locator (imitates that the License Management plugin is disabled): change line 63 to `<LicensePrompt message={message} />`. 5. Navigate to Watcher to see that there is not link to License Management. #### Screenshots When the license status is invalid and the License Management plugin is enabled (not changed) <img width="1259" alt="Screenshot 2023-03-28 at 14 55 54" src="https://user-images.githubusercontent.com/6585477/228833667-a96337cc-52c4-40fc-9209-37f7c0adab33.png"> When the license status is invalid and the License Management plugin is disabled <img width="1258" alt="Screenshot 2023-03-28 at 14 55 27" src="https://user-images.githubusercontent.com/6585477/228834043-7aa270ba-92c4-48cd-9c75-3a060e3924eb.png"> ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
546ceabc15
commit
a77ece24f5
14 changed files with 293 additions and 32 deletions
|
@ -14,7 +14,8 @@
|
|||
"home",
|
||||
"licensing",
|
||||
"management",
|
||||
"features"
|
||||
"features",
|
||||
"share"
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"telemetry"
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
EuiPageBody,
|
||||
EuiEmptyPrompt,
|
||||
} from '@elastic/eui';
|
||||
import { UPLOAD_LICENSE_ROUTE } from '../locator';
|
||||
|
||||
export const App = ({
|
||||
hasPermission,
|
||||
|
@ -102,7 +103,7 @@ export const App = ({
|
|||
return (
|
||||
<EuiPageBody>
|
||||
<Switch>
|
||||
<Route path={`/upload_license`} component={withTelemetry(UploadLicense)} />
|
||||
<Route path={`/${UPLOAD_LICENSE_ROUTE}`} component={withTelemetry(UploadLicense)} />
|
||||
<Route path={['/']} component={withTelemetry(LicenseDashboard)} />
|
||||
</Switch>
|
||||
</EuiPageBody>
|
||||
|
|
34
x-pack/plugins/license_management/public/locator.test.ts
Normal file
34
x-pack/plugins/license_management/public/locator.test.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { sharePluginMock } from '@kbn/share-plugin/public/mocks';
|
||||
import { ManagementAppLocatorDefinition } from '@kbn/management-plugin/common/locator';
|
||||
import { LicenseManagementLocatorDefinition, LICENSE_MANAGEMENT_LOCATOR_ID } from './locator';
|
||||
describe('License Management URL locator', () => {
|
||||
let locator: LicenseManagementLocatorDefinition;
|
||||
beforeEach(() => {
|
||||
const managementDefinition = new ManagementAppLocatorDefinition();
|
||||
locator = new LicenseManagementLocatorDefinition({
|
||||
managementAppLocator: {
|
||||
...sharePluginMock.createLocator(),
|
||||
getLocation: (params) => managementDefinition.getLocation(params),
|
||||
},
|
||||
});
|
||||
});
|
||||
test('locator has the right ID', () => {
|
||||
expect(locator.id).toBe(LICENSE_MANAGEMENT_LOCATOR_ID);
|
||||
});
|
||||
|
||||
test('locator returns the correct url for dashboard page', async () => {
|
||||
const { path } = await locator.getLocation({ page: 'dashboard' });
|
||||
expect(path).toBe('/stack/license_management');
|
||||
});
|
||||
test('locator returns the correct url for upload license page', async () => {
|
||||
const { path } = await locator.getLocation({ page: 'upload_license' });
|
||||
expect(path).toBe('/stack/license_management/upload_license');
|
||||
});
|
||||
});
|
51
x-pack/plugins/license_management/public/locator.ts
Normal file
51
x-pack/plugins/license_management/public/locator.ts
Normal 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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { SerializableRecord } from '@kbn/utility-types';
|
||||
import { ManagementAppLocator } from '@kbn/management-plugin/common';
|
||||
import { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public';
|
||||
import { PLUGIN } from '../common/constants';
|
||||
|
||||
export const LICENSE_MANAGEMENT_LOCATOR_ID = 'LICENSE_MANAGEMENT_LOCATOR';
|
||||
export const UPLOAD_LICENSE_ROUTE = 'upload_license';
|
||||
|
||||
export interface LicenseManagementLocatorParams extends SerializableRecord {
|
||||
page: 'dashboard' | 'upload_license';
|
||||
}
|
||||
|
||||
export type LicenseManagementLocator = LocatorPublic<LicenseManagementLocatorParams>;
|
||||
|
||||
export interface LicenseManagementLocatorDefinitionDependencies {
|
||||
managementAppLocator: ManagementAppLocator;
|
||||
}
|
||||
|
||||
export class LicenseManagementLocatorDefinition
|
||||
implements LocatorDefinition<LicenseManagementLocatorParams>
|
||||
{
|
||||
constructor(protected readonly deps: LicenseManagementLocatorDefinitionDependencies) {}
|
||||
|
||||
public readonly id = LICENSE_MANAGEMENT_LOCATOR_ID;
|
||||
|
||||
public readonly getLocation = async (params: LicenseManagementLocatorParams) => {
|
||||
const location = await this.deps.managementAppLocator.getLocation({
|
||||
sectionId: 'stack',
|
||||
appId: PLUGIN.id,
|
||||
});
|
||||
|
||||
switch (params.page) {
|
||||
case 'upload_license': {
|
||||
return {
|
||||
...location,
|
||||
path: `${location.path}/${UPLOAD_LICENSE_ROUTE}`,
|
||||
};
|
||||
}
|
||||
case 'dashboard': {
|
||||
return location;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
|
@ -11,14 +11,17 @@ import { CoreSetup, Plugin, PluginInitializerContext } from '@kbn/core/public';
|
|||
import { TelemetryPluginStart } from '@kbn/telemetry-plugin/public';
|
||||
import { ManagementSetup } from '@kbn/management-plugin/public';
|
||||
import { LicensingPluginSetup } from '@kbn/licensing-plugin/public';
|
||||
import { SharePluginSetup } from '@kbn/share-plugin/public';
|
||||
import { PLUGIN } from '../common/constants';
|
||||
import { ClientConfigType } from './types';
|
||||
import { AppDependencies } from './application';
|
||||
import { BreadcrumbService } from './application/breadcrumbs';
|
||||
import { LicenseManagementLocator, LicenseManagementLocatorDefinition } from './locator';
|
||||
|
||||
interface PluginsDependenciesSetup {
|
||||
management: ManagementSetup;
|
||||
licensing: LicensingPluginSetup;
|
||||
share: SharePluginSetup;
|
||||
}
|
||||
|
||||
interface PluginsDependenciesStart {
|
||||
|
@ -27,6 +30,7 @@ interface PluginsDependenciesStart {
|
|||
|
||||
export interface LicenseManagementUIPluginSetup {
|
||||
enabled: boolean;
|
||||
locator: undefined | LicenseManagementLocator;
|
||||
}
|
||||
export type LicenseManagementUIPluginStart = void;
|
||||
|
||||
|
@ -34,6 +38,7 @@ export class LicenseManagementUIPlugin
|
|||
implements Plugin<LicenseManagementUIPluginSetup, LicenseManagementUIPluginStart, any, any>
|
||||
{
|
||||
private breadcrumbService = new BreadcrumbService();
|
||||
private locator?: LicenseManagementLocator;
|
||||
|
||||
constructor(private readonly initializerContext: PluginInitializerContext) {}
|
||||
|
||||
|
@ -47,11 +52,18 @@ export class LicenseManagementUIPlugin
|
|||
// No need to go any further
|
||||
return {
|
||||
enabled: false,
|
||||
locator: this.locator,
|
||||
};
|
||||
}
|
||||
|
||||
const { getStartServices } = coreSetup;
|
||||
const { management, licensing } = plugins;
|
||||
const { management, licensing, share } = plugins;
|
||||
|
||||
this.locator = share.url.locators.create(
|
||||
new LicenseManagementLocatorDefinition({
|
||||
managementAppLocator: management.locator,
|
||||
})
|
||||
);
|
||||
|
||||
management.sections.section.stack.registerApp({
|
||||
id: PLUGIN.id,
|
||||
|
@ -105,6 +117,7 @@ export class LicenseManagementUIPlugin
|
|||
|
||||
return {
|
||||
enabled: true,
|
||||
locator: this.locator,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
"@kbn/config-schema",
|
||||
"@kbn/test-jest-helpers",
|
||||
"@kbn/shared-ux-router",
|
||||
"@kbn/utility-types",
|
||||
"@kbn/share-plugin",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
80
x-pack/plugins/watcher/__jest__/__snapshots__/license_prompt.test.tsx.snap
generated
Normal file
80
x-pack/plugins/watcher/__jest__/__snapshots__/license_prompt.test.tsx.snap
generated
Normal file
|
@ -0,0 +1,80 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`License prompt renders a prompt with a link to License Management 1`] = `
|
||||
<EuiPageContent_Deprecated
|
||||
color="danger"
|
||||
horizontalPosition="center"
|
||||
verticalPosition="center"
|
||||
>
|
||||
<EuiEmptyPrompt
|
||||
actions={
|
||||
Array [
|
||||
<EuiLink
|
||||
href="/license_management"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Manage your license"
|
||||
id="xpack.watcher.app.licenseErrorLinkText"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiLink>,
|
||||
]
|
||||
}
|
||||
body={
|
||||
<p>
|
||||
License error
|
||||
</p>
|
||||
}
|
||||
iconType="warning"
|
||||
title={
|
||||
<h1>
|
||||
<FormattedMessage
|
||||
defaultMessage="License error"
|
||||
id="xpack.watcher.app.licenseErrorTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</h1>
|
||||
}
|
||||
/>
|
||||
</EuiPageContent_Deprecated>
|
||||
`;
|
||||
|
||||
exports[`License prompt renders a prompt without a link to License Management 1`] = `
|
||||
<EuiPageContent_Deprecated
|
||||
color="danger"
|
||||
horizontalPosition="center"
|
||||
verticalPosition="center"
|
||||
>
|
||||
<EuiEmptyPrompt
|
||||
actions={
|
||||
Array [
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
body={
|
||||
<React.Fragment>
|
||||
<p>
|
||||
License error
|
||||
</p>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="Contact your administrator to change your license."
|
||||
id="xpack.watcher.app.licenseErrorBody"
|
||||
values={Object {}}
|
||||
/>
|
||||
</p>
|
||||
</React.Fragment>
|
||||
}
|
||||
iconType="warning"
|
||||
title={
|
||||
<h1>
|
||||
<FormattedMessage
|
||||
defaultMessage="License error"
|
||||
id="xpack.watcher.app.licenseErrorTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</h1>
|
||||
}
|
||||
/>
|
||||
</EuiPageContent_Deprecated>
|
||||
`;
|
35
x-pack/plugins/watcher/__jest__/license_prompt.test.tsx
Normal file
35
x-pack/plugins/watcher/__jest__/license_prompt.test.tsx
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { sharePluginMock } from '@kbn/share-plugin/public/mocks';
|
||||
import {
|
||||
LicenseManagementLocator,
|
||||
LicenseManagementLocatorParams,
|
||||
} from '@kbn/license-management-plugin/public/locator';
|
||||
import { LicensePrompt } from '../public/application/license_prompt';
|
||||
|
||||
describe('License prompt', () => {
|
||||
test('renders a prompt with a link to License Management', () => {
|
||||
const locator = {
|
||||
...sharePluginMock.createLocator(),
|
||||
useUrl: (params: LicenseManagementLocatorParams) => '/license_management',
|
||||
} as LicenseManagementLocator;
|
||||
const component = shallow(
|
||||
<LicensePrompt message="License error" licenseManagementLocator={locator} />
|
||||
);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('renders a prompt without a link to License Management', () => {
|
||||
const component = shallow(<LicensePrompt message="License error" />);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -19,6 +19,9 @@
|
|||
"data",
|
||||
"features"
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"licenseManagement"
|
||||
],
|
||||
"requiredBundles": [
|
||||
"esUiShared",
|
||||
"kibanaReact",
|
||||
|
|
|
@ -20,17 +20,15 @@ import { Router, Switch, Redirect, withRouter, RouteComponentProps } from 'react
|
|||
|
||||
import { Route } from '@kbn/shared-ux-router';
|
||||
|
||||
import { EuiPageContent_Deprecated as EuiPageContent, EuiEmptyPrompt, EuiLink } from '@elastic/eui';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import { RegisterManagementAppArgs, ManagementAppMountParams } from '@kbn/management-plugin/public';
|
||||
|
||||
import { ChartsPluginSetup } from '@kbn/charts-plugin/public';
|
||||
import { LicenseManagementLocator } from '@kbn/license-management-plugin/public/locator';
|
||||
import { LicenseStatus } from '../../common/types/license_status';
|
||||
import { WatchListPage, WatchEditPage, WatchStatusPage } from './sections';
|
||||
import { registerRouter } from './lib/navigation';
|
||||
import { AppContextProvider } from './app_context';
|
||||
import { LicensePrompt } from './license_prompt';
|
||||
|
||||
const ShareRouter = withRouter(({ children, history }: RouteComponentProps & { children: any }) => {
|
||||
registerRouter({ history });
|
||||
|
@ -49,6 +47,7 @@ export interface AppDeps {
|
|||
history: ManagementAppMountParams['history'];
|
||||
getUrlForApp: ApplicationStart['getUrlForApp'];
|
||||
executionContext: ExecutionContextStart;
|
||||
licenseManagementLocator?: LicenseManagementLocator;
|
||||
}
|
||||
|
||||
export const App = (deps: AppDeps) => {
|
||||
|
@ -61,30 +60,7 @@ export const App = (deps: AppDeps) => {
|
|||
|
||||
if (!valid) {
|
||||
return (
|
||||
<EuiPageContent verticalPosition="center" horizontalPosition="center" color="danger">
|
||||
<EuiEmptyPrompt
|
||||
iconType="warning"
|
||||
title={
|
||||
<h1>
|
||||
<FormattedMessage
|
||||
id="xpack.watcher.app.licenseErrorTitle"
|
||||
defaultMessage="License error"
|
||||
/>
|
||||
</h1>
|
||||
}
|
||||
body={<p>{message}</p>}
|
||||
actions={[
|
||||
<EuiLink
|
||||
href={deps.getUrlForApp('management', { path: 'stack/license_management/home' })}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.watcher.app.licenseErrorLinkText"
|
||||
defaultMessage="Manage your license"
|
||||
/>
|
||||
</EuiLink>,
|
||||
]}
|
||||
/>
|
||||
</EuiPageContent>
|
||||
<LicensePrompt licenseManagementLocator={deps.licenseManagementLocator} message={message} />
|
||||
);
|
||||
}
|
||||
return (
|
||||
|
|
60
x-pack/plugins/watcher/public/application/license_prompt.tsx
Normal file
60
x-pack/plugins/watcher/public/application/license_prompt.tsx
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiEmptyPrompt, EuiLink, EuiPageContent_Deprecated as EuiPageContent } from '@elastic/eui';
|
||||
import { LicenseManagementLocator } from '@kbn/license-management-plugin/public/locator';
|
||||
|
||||
export const LicensePrompt = ({
|
||||
message,
|
||||
licenseManagementLocator,
|
||||
}: {
|
||||
message: string | undefined;
|
||||
licenseManagementLocator?: LicenseManagementLocator;
|
||||
}) => {
|
||||
const licenseManagementUrl = licenseManagementLocator?.useUrl({ page: 'dashboard' });
|
||||
// if there is no licenseManagementUrl, the license management plugin might be disabled
|
||||
const promptAction = licenseManagementUrl ? (
|
||||
<EuiLink href={licenseManagementUrl}>
|
||||
<FormattedMessage
|
||||
id="xpack.watcher.app.licenseErrorLinkText"
|
||||
defaultMessage="Manage your license"
|
||||
/>
|
||||
</EuiLink>
|
||||
) : undefined;
|
||||
const promptBody = licenseManagementUrl ? (
|
||||
<p>{message}</p>
|
||||
) : (
|
||||
<>
|
||||
<p>{message}</p>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.watcher.app.licenseErrorBody"
|
||||
defaultMessage="Contact your administrator to change your license."
|
||||
/>
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<EuiPageContent verticalPosition="center" horizontalPosition="center" color="danger">
|
||||
<EuiEmptyPrompt
|
||||
iconType="warning"
|
||||
title={
|
||||
<h1>
|
||||
<FormattedMessage
|
||||
id="xpack.watcher.app.licenseErrorTitle"
|
||||
defaultMessage="License error"
|
||||
/>
|
||||
</h1>
|
||||
}
|
||||
body={promptBody}
|
||||
actions={[promptAction]}
|
||||
/>
|
||||
</EuiPageContent>
|
||||
);
|
||||
};
|
|
@ -29,7 +29,7 @@ export class WatcherUIPlugin implements Plugin<void, void, Dependencies, any> {
|
|||
|
||||
setup(
|
||||
{ notifications, http, uiSettings, getStartServices }: CoreSetup,
|
||||
{ licensing, management, data, home, charts }: Dependencies
|
||||
{ licensing, management, data, home, charts, licenseManagement }: Dependencies
|
||||
) {
|
||||
const esSection = management.sections.section.insightsAndAlerting;
|
||||
|
||||
|
@ -75,6 +75,7 @@ export class WatcherUIPlugin implements Plugin<void, void, Dependencies, any> {
|
|||
history,
|
||||
getUrlForApp: application.getUrlForApp,
|
||||
theme$,
|
||||
licenseManagementLocator: licenseManagement?.locator,
|
||||
executionContext,
|
||||
});
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import { ChartsPluginStart } from '@kbn/charts-plugin/public';
|
|||
import { LicensingPluginSetup } from '@kbn/licensing-plugin/public';
|
||||
import { DataPublicPluginSetup } from '@kbn/data-plugin/public';
|
||||
import { HomePublicPluginSetup } from '@kbn/home-plugin/public';
|
||||
import { LicenseManagementUIPluginSetup } from '@kbn/license-management-plugin/public';
|
||||
|
||||
export interface Dependencies {
|
||||
home: HomePublicPluginSetup;
|
||||
|
@ -17,4 +18,5 @@ export interface Dependencies {
|
|||
licensing: LicensingPluginSetup;
|
||||
charts: ChartsPluginStart;
|
||||
data: DataPublicPluginSetup;
|
||||
licenseManagement?: LicenseManagementUIPluginSetup;
|
||||
}
|
||||
|
|
|
@ -33,6 +33,8 @@
|
|||
"@kbn/i18n-react",
|
||||
"@kbn/ace",
|
||||
"@kbn/shared-ux-router",
|
||||
"@kbn/license-management-plugin",
|
||||
"@kbn/share-plugin",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue