Reverse dependency of home plugin and apm/ml/cloud (#52883) (#54889)

This commit is contained in:
Joe Reuter 2020-01-15 11:44:27 +01:00 committed by GitHub
parent 40dfc2bdbb
commit dc0dfe7b1d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 313 additions and 52 deletions

View file

@ -73,6 +73,6 @@ let copiedLegacyCatalogue = false;
},
});
instance.start(npStart.core, {
data: npStart.plugins.data,
...npStart.plugins,
});
})();

View file

@ -29,7 +29,7 @@ import {
UiSettingsState,
} from 'kibana/public';
import { UiStatsMetricType } from '@kbn/analytics';
import { FeatureCatalogueEntry } from '../../../../../plugins/home/public';
import { Environment, FeatureCatalogueEntry } from '../../../../../plugins/home/public';
export interface HomeKibanaServices {
indexPatternService: any;
@ -61,6 +61,7 @@ export interface HomeKibanaServices {
shouldShowTelemetryOptIn: boolean;
docLinks: DocLinksStart;
addBasePath: (url: string) => string;
environment: Environment;
}
let services: HomeKibanaServices | null = null;

View file

@ -28,22 +28,19 @@ import { HashRouter as Router, Switch, Route, Redirect } from 'react-router-dom'
import { getTutorial } from '../load_tutorials';
import { replaceTemplateStrings } from './tutorial/replace_template_strings';
import { getServices } from '../../kibana_services';
// TODO This is going to be refactored soon
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { npSetup } from 'ui/new_platform';
export function HomeApp({ directories }) {
const {
getInjected,
savedObjectsClient,
getBasePath,
addBasePath,
environment,
telemetryOptInProvider: { setOptInNoticeSeen, getOptIn },
} = getServices();
const { cloud } = npSetup.plugins;
const isCloudEnabled = !!(cloud && cloud.isCloudEnabled);
const isCloudEnabled = environment.cloud;
const mlEnabled = environment.ml;
const apmUiEnabled = environment.apmUi;
const apmUiEnabled = getInjected('apmUiEnabled', true);
const mlEnabled = getInjected('mlEnabled', false);
const defaultAppId = getInjected('kbnDefaultAppId', 'discover');
const renderTutorialDirectory = props => {

View file

@ -23,7 +23,11 @@ import { UiStatsMetricType } from '@kbn/analytics';
import { DataPublicPluginStart } from 'src/plugins/data/public';
import { setServices } from './kibana_services';
import { KibanaLegacySetup } from '../../../../../plugins/kibana_legacy/public';
import { FeatureCatalogueEntry } from '../../../../../plugins/home/public';
import {
Environment,
FeatureCatalogueEntry,
HomePublicPluginStart,
} from '../../../../../plugins/home/public';
export interface LegacyAngularInjectedDependencies {
telemetryOptInProvider: any;
@ -32,6 +36,7 @@ export interface LegacyAngularInjectedDependencies {
export interface HomePluginStartDependencies {
data: DataPublicPluginStart;
home: HomePublicPluginStart;
}
export interface HomePluginSetupDependencies {
@ -60,6 +65,7 @@ export interface HomePluginSetupDependencies {
export class HomePlugin implements Plugin {
private dataStart: DataPublicPluginStart | null = null;
private savedObjectsClient: any = null;
private environment: Environment | null = null;
setup(
core: CoreSetup,
@ -86,6 +92,7 @@ export class HomePlugin implements Plugin {
addBasePath: core.http.basePath.prepend,
getBasePath: core.http.basePath.get,
indexPatternService: this.dataStart!.indexPatterns,
environment: this.environment!,
...angularDependencies,
});
const { renderApp } = await import('./np_ready/application');
@ -94,8 +101,8 @@ export class HomePlugin implements Plugin {
});
}
start(core: CoreStart, { data }: HomePluginStartDependencies) {
// TODO is this really the right way? I though the app context would give us those
start(core: CoreStart, { data, home }: HomePluginStartDependencies) {
this.environment = home.environment.get();
this.dataStart = data;
this.savedObjectsClient = core.savedObjects.client;
}

View file

@ -23,7 +23,6 @@ import 'uiExports/docViews';
import 'uiExports/embeddableActions';
import 'uiExports/fieldFormatEditors';
import 'uiExports/fieldFormats';
import 'uiExports/home';
import 'uiExports/indexManagement';
import 'uiExports/inspectorViews';
import 'uiExports/savedObjectTypes';

View file

@ -131,6 +131,9 @@ export const npSetup = {
featureCatalogue: {
register: sinon.fake(),
},
environment: {
update: sinon.fake(),
},
},
},
};

View file

@ -23,7 +23,7 @@ export {
HomePublicPluginSetup,
HomePublicPluginStart,
} from './plugin';
export { FeatureCatalogueEntry, FeatureCatalogueCategory } from './services';
export { FeatureCatalogueEntry, FeatureCatalogueCategory, Environment } from './services';
import { HomePublicPlugin } from './plugin';
export const plugin = () => new HomePublicPlugin();

View file

@ -18,8 +18,11 @@
*/
import { featureCatalogueRegistryMock } from './services/feature_catalogue/feature_catalogue_registry.mock';
import { environmentServiceMock } from './services/environment/environment.mock';
export const registryMock = featureCatalogueRegistryMock.create();
export const environmentMock = environmentServiceMock.create();
jest.doMock('./services', () => ({
FeatureCatalogueRegistry: jest.fn(() => registryMock),
EnvironmentService: jest.fn(() => environmentMock),
}));

View file

@ -17,13 +17,15 @@
* under the License.
*/
import { registryMock } from './plugin.test.mocks';
import { registryMock, environmentMock } from './plugin.test.mocks';
import { HomePublicPlugin } from './plugin';
describe('HomePublicPlugin', () => {
beforeEach(() => {
registryMock.setup.mockClear();
registryMock.start.mockClear();
environmentMock.setup.mockClear();
environmentMock.start.mockClear();
});
describe('setup', () => {
@ -32,6 +34,12 @@ describe('HomePublicPlugin', () => {
expect(setup).toHaveProperty('featureCatalogue');
expect(setup.featureCatalogue).toHaveProperty('register');
});
test('wires up and returns environment service', async () => {
const setup = await new HomePublicPlugin().setup();
expect(setup).toHaveProperty('environment');
expect(setup.environment).toHaveProperty('update');
});
});
describe('start', () => {
@ -45,5 +53,15 @@ describe('HomePublicPlugin', () => {
});
expect(start.featureCatalogue.get).toBeDefined();
});
test('wires up and returns environment service', async () => {
const service = new HomePublicPlugin();
await service.setup();
const start = await service.start({
application: { capabilities: { catalogue: {} } },
} as any);
expect(environmentMock.start).toHaveBeenCalled();
expect(start.environment.get).toBeDefined();
});
});
});

View file

@ -19,6 +19,9 @@
import { CoreStart, Plugin } from 'src/core/public';
import {
EnvironmentService,
EnvironmentServiceSetup,
EnvironmentServiceStart,
FeatureCatalogueRegistry,
FeatureCatalogueRegistrySetup,
FeatureCatalogueRegistryStart,
@ -26,10 +29,12 @@ import {
export class HomePublicPlugin implements Plugin<HomePublicPluginSetup, HomePublicPluginStart> {
private readonly featuresCatalogueRegistry = new FeatureCatalogueRegistry();
private readonly environmentService = new EnvironmentService();
public async setup() {
return {
featureCatalogue: { ...this.featuresCatalogueRegistry.setup() },
environment: { ...this.environmentService.setup() },
};
}
@ -40,6 +45,7 @@ export class HomePublicPlugin implements Plugin<HomePublicPluginSetup, HomePubli
capabilities: core.application.capabilities,
}),
},
environment: { ...this.environmentService.start() },
};
}
}
@ -50,12 +56,25 @@ export type FeatureCatalogueSetup = FeatureCatalogueRegistrySetup;
/** @public */
export type FeatureCatalogueStart = FeatureCatalogueRegistryStart;
/** @public */
export type EnvironmentSetup = EnvironmentServiceSetup;
/** @public */
export type EnvironmentStart = EnvironmentServiceStart;
/** @public */
export interface HomePublicPluginSetup {
featureCatalogue: FeatureCatalogueSetup;
/**
* The environment service is only available for a transition period and will
* be replaced by display specific extension points.
* @deprecated
*/
environment: EnvironmentSetup;
}
/** @public */
export interface HomePublicPluginStart {
featureCatalogue: FeatureCatalogueStart;
environment: EnvironmentStart;
}

View file

@ -0,0 +1,54 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import {
EnvironmentService,
EnvironmentServiceSetup,
EnvironmentServiceStart,
} from './environment';
const createSetupMock = (): jest.Mocked<EnvironmentServiceSetup> => {
const setup = {
update: jest.fn(),
};
return setup;
};
const createStartMock = (): jest.Mocked<EnvironmentServiceStart> => {
const start = {
get: jest.fn(),
};
return start;
};
const createMock = (): jest.Mocked<PublicMethodsOf<EnvironmentService>> => {
const service = {
setup: jest.fn(),
start: jest.fn(),
};
service.setup.mockImplementation(createSetupMock);
service.start.mockImplementation(createStartMock);
return service;
};
export const environmentServiceMock = {
createSetup: createSetupMock,
createStart: createStartMock,
create: createMock,
};

View file

@ -0,0 +1,47 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { EnvironmentService } from './environment';
describe('EnvironmentService', () => {
describe('setup', () => {
test('allows multiple update calls', () => {
const setup = new EnvironmentService().setup();
expect(() => {
setup.update({ ml: true });
setup.update({ apmUi: true });
}).not.toThrow();
});
});
describe('start', () => {
test('returns default values', () => {
const service = new EnvironmentService();
expect(service.start().get()).toEqual({ ml: false, cloud: false, apmUi: false });
});
test('returns last state of update calls', () => {
const service = new EnvironmentService();
const setup = service.setup();
setup.update({ ml: true, cloud: true });
setup.update({ ml: false, apmUi: true });
expect(service.start().get()).toEqual({ ml: false, cloud: true, apmUi: true });
});
});
});

View file

@ -0,0 +1,71 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/** @public */
export interface Environment {
/**
* Flag whether the home app should advertize cloud features
*/
readonly cloud: boolean;
/**
* Flag whether the home app should advertize apm features
*/
readonly apmUi: boolean;
/**
* Flag whether the home app should advertize ml features
*/
readonly ml: boolean;
}
export class EnvironmentService {
private environment = {
cloud: false,
apmUi: false,
ml: false,
};
public setup() {
return {
/**
* Update the environment to influence how the home app is presenting available features.
* This API should not be extended for new features and will be removed in future versions
* in favor of display specific extension apis.
* @deprecated
* @param update
*/
update: (update: Partial<Environment>) => {
this.environment = Object.assign({}, this.environment, update);
},
};
}
public start() {
return {
/**
* Retrieve the current environment home is running in. This API is only intended for internal
* use and is only exposed during a transition period of migrating the home app to the new platform.
* @deprecated
*/
get: (): Environment => this.environment,
};
}
}
export type EnvironmentServiceSetup = ReturnType<EnvironmentService['setup']>;
export type EnvironmentServiceStart = ReturnType<EnvironmentService['start']>;

View file

@ -0,0 +1,25 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export {
EnvironmentService,
Environment,
EnvironmentServiceSetup,
EnvironmentServiceStart,
} from './environment';

View file

@ -18,3 +18,4 @@
*/
export * from './feature_catalogue';
export * from './environment';

View file

@ -4,13 +4,21 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { npStart } from 'ui/new_platform';
import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue';
import { npSetup } from 'ui/new_platform';
import { featureCatalogueEntry } from './new-platform/featureCatalogueEntry';
const { core } = npStart;
const apmUiEnabled = core.injectedMetadata.getInjectedVar('apmUiEnabled');
const {
core,
plugins: { home }
} = npSetup;
const apmUiEnabled = core.injectedMetadata.getInjectedVar(
'apmUiEnabled'
) as boolean;
if (apmUiEnabled) {
FeatureCatalogueRegistryProvider.register(() => featureCatalogueEntry);
home.featureCatalogue.register(featureCatalogueEntry);
}
home.environment.update({
apmUi: apmUiEnabled
});

View file

@ -16,6 +16,7 @@ import {
Plugin,
PluginInitializerContext
} from '../../../../../../src/core/public';
import { featureCatalogueEntry } from './featureCatalogueEntry';
import { DataPublicPluginSetup } from '../../../../../../src/plugins/data/public';
import { HomePublicPluginSetup } from '../../../../../../src/plugins/home/public';
import { LicensingPluginSetup } from '../../../../../plugins/licensing/public';
@ -31,7 +32,6 @@ import { UrlParamsProvider } from '../context/UrlParamsContext';
import { createStaticIndexPattern } from '../services/rest/index_pattern';
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';

View file

@ -1,29 +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 {
FeatureCatalogueRegistryProvider,
FeatureCatalogueCategory,
} from 'ui/registry/feature_catalogue';
import { i18n } from '@kbn/i18n';
FeatureCatalogueRegistryProvider.register(() => {
return {
id: 'ml',
title: i18n.translate('xpack.ml.machineLearningTitle', {
defaultMessage: 'Machine Learning',
}),
description: i18n.translate('xpack.ml.machineLearningDescription', {
defaultMessage:
'Automatically model the normal behavior of your time series data to detect anomalies.',
}),
icon: 'machineLearningApp',
path: '/app/ml',
showOnHomePage: true,
category: FeatureCatalogueCategory.DATA,
};
});

View file

@ -0,0 +1,28 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { npSetup } from 'ui/new_platform';
import { FeatureCatalogueCategory } from '../../../../../src/plugins/home/public';
npSetup.plugins.home.featureCatalogue.register({
id: 'ml',
title: i18n.translate('xpack.ml.machineLearningTitle', {
defaultMessage: 'Machine Learning',
}),
description: i18n.translate('xpack.ml.machineLearningDescription', {
defaultMessage:
'Automatically model the normal behavior of your time series data to detect anomalies.',
}),
icon: 'machineLearningApp',
path: '/app/ml',
showOnHomePage: true,
category: FeatureCatalogueCategory.DATA,
});
npSetup.plugins.home.environment.update({
ml: npSetup.core.injectedMetadata.getInjectedVar('mlEnabled') as boolean,
});

View file

@ -3,7 +3,7 @@
"version": "8.0.0",
"kibanaVersion": "kibana",
"configPath": ["xpack", "cloud"],
"optionalPlugins": ["usageCollection"],
"optionalPlugins": ["usageCollection", "home"],
"server": true,
"ui": true
}

View file

@ -7,11 +7,16 @@
import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/public';
import { getIsCloudEnabled } from '../common/is_cloud_enabled';
import { ELASTIC_SUPPORT_LINK } from '../common/constants';
import { HomePublicPluginSetup } from '../../../../src/plugins/home/public';
interface CloudConfigType {
id?: string;
}
interface CloudSetupDependencies {
home?: HomePublicPluginSetup;
}
export interface CloudSetup {
cloudId?: string;
isCloudEnabled: boolean;
@ -20,10 +25,14 @@ export interface CloudSetup {
export class CloudPlugin implements Plugin<CloudSetup> {
constructor(private readonly initializerContext: PluginInitializerContext) {}
public async setup(core: CoreSetup) {
public async setup(core: CoreSetup, { home }: CloudSetupDependencies) {
const { id } = this.initializerContext.config.get<CloudConfigType>();
const isCloudEnabled = getIsCloudEnabled(id);
if (home) {
home.environment.update({ cloud: isCloudEnabled });
}
return {
cloudId: id,
isCloudEnabled,