Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Watcher] More robust handling of license at setup #55831

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import React from 'react';
import { of } from 'rxjs';
import { ComponentType } from 'enzyme';
import {
chromeServiceMock,
Expand All @@ -15,6 +16,7 @@ import {
} from '../../../../../../src/core/public/mocks';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { AppContextProvider } from '../../../public/application/app_context';
import { LicenseStatus } from '../../../common/types/license_status';

class MockTimeBuckets {
setBounds(_domain: any) {
Expand All @@ -27,9 +29,7 @@ class MockTimeBuckets {
}
}
export const mockContextValue = {
getLicenseStatus: () => ({
valid: true,
}),
licenseStatus$: of<LicenseStatus>({ valid: true }),
docLinks: docLinksServiceMock.createStartContract(),
chrome: chromeServiceMock.createStartContract(),
MANAGEMENT_BREADCRUMB: { text: 'test' },
Expand Down
12 changes: 9 additions & 3 deletions x-pack/plugins/watcher/public/application/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';
import React, { useEffect, useState } from 'react';
import { Observable } from 'rxjs';
import {
ChromeStart,
DocLinksStart,
Expand Down Expand Up @@ -47,12 +48,17 @@ export interface AppDeps {
uiSettings: IUiSettingsClient;
euiUtils: any;
createTimeBuckets: () => any;
getLicenseStatus: () => LicenseStatus;
licenseStatus$: Observable<LicenseStatus>;
MANAGEMENT_BREADCRUMB: any;
}

export const App = (deps: AppDeps) => {
const { valid, message } = deps.getLicenseStatus();
const [{ valid, message }, setLicenseStatus] = useState<LicenseStatus>({ valid: true });

useEffect(() => {
const s = deps.licenseStatus$.subscribe(setLicenseStatus);
return () => s.unsubscribe();
}, [deps.licenseStatus$]);

if (!valid) {
return (
Expand Down
124 changes: 67 additions & 57 deletions x-pack/plugins/watcher/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,80 +5,90 @@
*/
import { i18n } from '@kbn/i18n';
import { CoreSetup, Plugin, CoreStart } from 'kibana/public';
import { first, map, skip } from 'rxjs/operators';

import { FeatureCatalogueCategory } from '../../../../src/plugins/home/public';

import { LicenseStatus } from '../common/types/license_status';

import { LICENSE_CHECK_STATE } from '../../licensing/public';
import { ILicense, LICENSE_CHECK_STATE } from '../../licensing/public';
import { TimeBuckets, MANAGEMENT_BREADCRUMB } from './legacy';
import { PLUGIN } from '../common/constants';
import { Dependencies } from './types';

export class WatcherUIPlugin implements Plugin<void, void, Dependencies, any> {
// Reference for when `mount` gets called, we don't want to render if
// we don't have a valid license. Under certain conditions the Watcher app link
// may still be present so this is a final guard.
private licenseStatus: LicenseStatus = { valid: false };
private hasRegisteredESManagementSection = false;
const licenseToLicenseStatus = (license: ILicense): LicenseStatus => {
const { state, message } = license.check(PLUGIN.ID, PLUGIN.MINIMUM_LICENSE_REQUIRED);
return {
valid: state === LICENSE_CHECK_STATE.Valid && license.getFeature(PLUGIN.ID).isAvailable,
message,
};
};

export class WatcherUIPlugin implements Plugin<void, void, Dependencies, any> {
setup(
{ application, notifications, http, uiSettings, getStartServices }: CoreSetup,
{ licensing, management, data, home }: Dependencies
) {
licensing.license$.subscribe(license => {
const { state, message } = license.check(PLUGIN.ID, PLUGIN.MINIMUM_LICENSE_REQUIRED);
this.licenseStatus = {
valid: state === LICENSE_CHECK_STATE.Valid && license.getFeature(PLUGIN.ID).isAvailable,
message,
};
if (this.licenseStatus.valid) {
const esSection = management.sections.getSection('elasticsearch');
if (esSection && !this.hasRegisteredESManagementSection) {
esSection.registerApp({
id: 'watcher',
title: i18n.translate(
'xpack.watcher.sections.watchList.managementSection.watcherDisplayName',
{ defaultMessage: 'Watcher' }
),
mount: async ({ element }) => {
const [core, plugins] = await getStartServices();
const { chrome, i18n: i18nDep, docLinks, savedObjects } = core;
const { eui_utils } = plugins as any;
const { boot } = await import('./application/boot');
const esSection = management.sections.getSection('elasticsearch');

const watcherESApp = esSection!.registerApp({
id: 'watcher',
title: i18n.translate(
'xpack.watcher.sections.watchList.managementSection.watcherDisplayName',
{ defaultMessage: 'Watcher' }
),
mount: async ({ element }) => {
const [core, plugins] = await getStartServices();
const { chrome, i18n: i18nDep, docLinks, savedObjects } = core;
const { eui_utils } = plugins as any;
const { boot } = await import('./application/boot');

return boot({
// Skip the first license status, because that's already been used to determine
// whether to include Watcher.
licenseStatus$: licensing.license$.pipe(skip(1), map(licenseToLicenseStatus)),
jloleysens marked this conversation as resolved.
Show resolved Hide resolved
element,
toasts: notifications.toasts,
http,
uiSettings,
docLinks,
chrome,
euiUtils: eui_utils,
savedObjects: savedObjects.client,
I18nContext: i18nDep.Context,
createTimeBuckets: () => new TimeBuckets(uiSettings, data),
MANAGEMENT_BREADCRUMB,
});
},
});

watcherESApp.disable();

return boot({
getLicenseStatus: () => this.licenseStatus,
element,
toasts: notifications.toasts,
http,
uiSettings,
docLinks,
chrome,
euiUtils: eui_utils,
savedObjects: savedObjects.client,
I18nContext: i18nDep.Context,
createTimeBuckets: () => new TimeBuckets(uiSettings, data),
MANAGEMENT_BREADCRUMB,
});
},
});
// TODO: Fix the below dependency on `home` plugin inner workings
// Because the home feature catalogue does not have enable/disable functionality we pass
// the config in but keep a reference for enabling and disabling showing on home based on
// license updates.
const watcherHome = {
id: 'watcher',
title: 'Watcher', // This is a product name so we don't translate it.
category: FeatureCatalogueCategory.ADMIN,
description: i18n.translate('xpack.watcher.watcherDescription', {
defaultMessage: 'Detect changes in your data by creating, managing, and monitoring alerts.',
}),
icon: 'watchesApp',
path: '/app/kibana#/management/elasticsearch/watcher/watches',
showOnHomePage: true,
};

home.featureCatalogue.register({
id: 'watcher',
title: 'Watcher', // This is a product name so we don't translate it.
category: FeatureCatalogueCategory.ADMIN,
description: i18n.translate('xpack.watcher.watcherDescription', {
defaultMessage:
'Detect changes in your data by creating, managing, and monitoring alerts.',
}),
icon: 'watchesApp',
path: '/app/kibana#/management/elasticsearch/watcher/watches',
showOnHomePage: true,
});
home.featureCatalogue.register(watcherHome);

this.hasRegisteredESManagementSection = true;
}
licensing.license$.pipe(first(), map(licenseToLicenseStatus)).subscribe(({ valid }) => {
if (valid) {
watcherESApp.enable();
watcherHome.showOnHomePage = true;
} else {
watcherESApp.disable();
watcherHome.showOnHomePage = false;
}
});
}
Expand Down