diff --git a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts index 4b09997fc6244..a4fbe83f60e84 100644 --- a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts @@ -31,6 +31,7 @@ import { TelemetryPluginStart } from '../../../../../plugins/telemetry/public'; import { Environment, HomePublicPluginSetup, + TutorialStart, HomePublicPluginStart, } from '../../../../../plugins/home/public'; import { KibanaLegacySetup } from '../../../../../plugins/kibana_legacy/public'; @@ -38,7 +39,6 @@ import { KibanaLegacySetup } from '../../../../../plugins/kibana_legacy/public'; export interface HomeKibanaServices { indexPatternService: any; kibanaVersion: string; - getInjected: (name: string, defaultValue?: any) => unknown; chrome: ChromeStart; uiSettings: IUiSettingsClient; config: KibanaLegacySetup['config']; @@ -54,6 +54,7 @@ export interface HomeKibanaServices { addBasePath: (url: string) => string; environment: Environment; telemetry?: TelemetryPluginStart; + tutorialVariables: TutorialStart['get']; } let services: HomeKibanaServices | null = null; diff --git a/src/legacy/core_plugins/kibana/public/home/np_ready/components/home.test.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/home.test.js index d25a1f81dae5a..b0d94711be7b6 100644 --- a/src/legacy/core_plugins/kibana/public/home/np_ready/components/home.test.js +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/home.test.js @@ -29,7 +29,7 @@ import { FeatureCatalogueCategory } from '../../../../../../../plugins/home/publ jest.mock('../../kibana_services', () => ({ getServices: () => ({ getBasePath: () => 'path', - getInjected: () => '', + tutorialVariables: () => ({}), homeConfig: { disableWelcomeScreen: false }, }), })); diff --git a/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/replace_template_strings.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/replace_template_strings.js index c7e623657bf71..f9fa662e6d507 100644 --- a/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/replace_template_strings.js +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/tutorial/replace_template_strings.js @@ -33,7 +33,7 @@ mustacheWriter.escapedValue = function escapedValue(token, context) { }; export function replaceTemplateStrings(text, params = {}) { - const { getInjected, kibanaVersion, docLinks } = getServices(); + const { tutorialVariables, kibanaVersion, docLinks } = getServices(); const variables = { // '{' and '}' can not be used in template since they are used as template tags. @@ -41,9 +41,7 @@ export function replaceTemplateStrings(text, params = {}) { curlyOpen: '{', curlyClose: '}', config: { - cloud: { - id: getInjected('cloudId'), - }, + ...tutorialVariables(), docs: { base_url: docLinks.ELASTIC_WEBSITE_URL, beats: { diff --git a/src/legacy/core_plugins/kibana/public/home/plugin.ts b/src/legacy/core_plugins/kibana/public/home/plugin.ts index 1f4b5fe7cacef..f8c750cc80283 100644 --- a/src/legacy/core_plugins/kibana/public/home/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/home/plugin.ts @@ -57,20 +57,22 @@ export class HomePlugin implements Plugin { constructor(private initializerContext: PluginInitializerContext) {} - setup(core: CoreSetup, { home, kibanaLegacy, usageCollection }: HomePluginSetupDependencies) { + setup( + core: CoreSetup, + { home, kibanaLegacy, usageCollection }: HomePluginSetupDependencies + ) { kibanaLegacy.registerLegacyApp({ id: 'home', title: 'Home', mount: async (params: AppMountParameters) => { const trackUiMetric = usageCollection.reportUiStats.bind(usageCollection, 'Kibana_home'); - const [coreStart] = await core.getStartServices(); + const [coreStart, { home: homeStart }] = await core.getStartServices(); setServices({ trackUiMetric, kibanaVersion: this.initializerContext.env.packageInfo.version, http: coreStart.http, toastNotifications: core.notifications.toasts, banners: coreStart.overlays.banners, - getInjected: core.injectedMetadata.getInjectedVar, docLinks: coreStart.docLinks, savedObjectsClient: this.savedObjectsClient!, chrome: coreStart.chrome, @@ -82,6 +84,7 @@ export class HomePlugin implements Plugin { environment: this.environment!, config: kibanaLegacy.config, homeConfig: home.config, + tutorialVariables: homeStart.tutorials.get, featureCatalogue: this.featureCatalogue!, }); const { renderApp } = await import('./np_ready/application'); diff --git a/src/plugins/home/public/index.ts b/src/plugins/home/public/index.ts index 2a445cf242729..7738990bba0d0 100644 --- a/src/plugins/home/public/index.ts +++ b/src/plugins/home/public/index.ts @@ -22,10 +22,19 @@ import { PluginInitializerContext } from 'kibana/public'; export { FeatureCatalogueSetup, FeatureCatalogueStart, + EnvironmentSetup, + EnvironmentStart, + TutorialSetup, + TutorialStart, HomePublicPluginSetup, HomePublicPluginStart, } from './plugin'; -export { FeatureCatalogueEntry, FeatureCatalogueCategory, Environment } from './services'; +export { + FeatureCatalogueEntry, + FeatureCatalogueCategory, + Environment, + TutorialVariables, +} from './services'; export * from '../common/instruction_variant'; import { HomePublicPlugin } from './plugin'; diff --git a/src/plugins/home/public/mocks/index.ts b/src/plugins/home/public/mocks/index.ts index dead50230ec85..42c61fe847250 100644 --- a/src/plugins/home/public/mocks/index.ts +++ b/src/plugins/home/public/mocks/index.ts @@ -20,16 +20,19 @@ import { featureCatalogueRegistryMock } from '../services/feature_catalogue/feature_catalogue_registry.mock'; import { environmentServiceMock } from '../services/environment/environment.mock'; import { configSchema } from '../../config'; +import { tutorialServiceMock } from '../services/tutorials/tutorial_service.mock'; const createSetupContract = () => ({ featureCatalogue: featureCatalogueRegistryMock.createSetup(), environment: environmentServiceMock.createSetup(), + tutorials: tutorialServiceMock.createSetup(), config: configSchema.validate({}), }); const createStartContract = () => ({ featureCatalogue: featureCatalogueRegistryMock.createStart(), environment: environmentServiceMock.createStart(), + tutorials: tutorialServiceMock.createStart(), }); export const homePluginMock = { diff --git a/src/plugins/home/public/plugin.test.mocks.ts b/src/plugins/home/public/plugin.test.mocks.ts index 461930ddfb80f..d047311968361 100644 --- a/src/plugins/home/public/plugin.test.mocks.ts +++ b/src/plugins/home/public/plugin.test.mocks.ts @@ -19,10 +19,13 @@ import { featureCatalogueRegistryMock } from './services/feature_catalogue/feature_catalogue_registry.mock'; import { environmentServiceMock } from './services/environment/environment.mock'; +import { tutorialServiceMock } from './services/tutorials/tutorial_service.mock'; export const registryMock = featureCatalogueRegistryMock.create(); export const environmentMock = environmentServiceMock.create(); +export const tutorialMock = tutorialServiceMock.create(); jest.doMock('./services', () => ({ FeatureCatalogueRegistry: jest.fn(() => registryMock), EnvironmentService: jest.fn(() => environmentMock), + TutorialService: jest.fn(() => tutorialMock), })); diff --git a/src/plugins/home/public/plugin.test.ts b/src/plugins/home/public/plugin.test.ts index fa44a110c63b7..0423ad3dd99f5 100644 --- a/src/plugins/home/public/plugin.test.ts +++ b/src/plugins/home/public/plugin.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { registryMock, environmentMock } from './plugin.test.mocks'; +import { registryMock, environmentMock, tutorialMock } from './plugin.test.mocks'; import { HomePublicPlugin } from './plugin'; import { coreMock } from '../../../core/public/mocks'; @@ -27,8 +27,10 @@ describe('HomePublicPlugin', () => { beforeEach(() => { registryMock.setup.mockClear(); registryMock.start.mockClear(); + tutorialMock.setup.mockClear(); environmentMock.setup.mockClear(); environmentMock.start.mockClear(); + tutorialMock.start.mockClear(); }); describe('setup', () => { @@ -43,6 +45,12 @@ describe('HomePublicPlugin', () => { expect(setup).toHaveProperty('environment'); expect(setup.environment).toHaveProperty('update'); }); + + test('wires up and returns tutorial service', async () => { + const setup = await new HomePublicPlugin(mockInitializerContext).setup(); + expect(setup).toHaveProperty('tutorials'); + expect(setup.tutorials).toHaveProperty('setVariable'); + }); }); describe('start', () => { @@ -66,5 +74,13 @@ describe('HomePublicPlugin', () => { expect(environmentMock.start).toHaveBeenCalled(); expect(start.environment.get).toBeDefined(); }); + + test('wires up and returns tutorial service', async () => { + const service = new HomePublicPlugin(mockInitializerContext); + await service.setup(); + const start = await service.start(coreMock.createStart()); + expect(tutorialMock.start).toHaveBeenCalled(); + expect(start.tutorials.get).toBeDefined(); + }); }); }); diff --git a/src/plugins/home/public/plugin.ts b/src/plugins/home/public/plugin.ts index fe68dbc3e7e49..975fd7bfb23c0 100644 --- a/src/plugins/home/public/plugin.ts +++ b/src/plugins/home/public/plugin.ts @@ -26,12 +26,16 @@ import { FeatureCatalogueRegistry, FeatureCatalogueRegistrySetup, FeatureCatalogueRegistryStart, + TutorialService, + TutorialServiceSetup, + TutorialServiceStart, } from './services'; import { ConfigSchema } from '../config'; export class HomePublicPlugin implements Plugin { private readonly featuresCatalogueRegistry = new FeatureCatalogueRegistry(); private readonly environmentService = new EnvironmentService(); + private readonly tutorialService = new TutorialService(); constructor(private readonly initializerContext: PluginInitializerContext) {} @@ -39,6 +43,7 @@ export class HomePublicPlugin implements Plugin => { + const setup = { + setVariable: jest.fn(), + }; + return setup; +}; + +const createStartMock = (): jest.Mocked => { + const start = { + get: jest.fn(), + }; + return start; +}; + +const createMock = (): jest.Mocked> => { + const service = { + setup: jest.fn(), + start: jest.fn(), + }; + service.setup.mockImplementation(createSetupMock); + service.start.mockImplementation(createStartMock); + return service; +}; + +export const tutorialServiceMock = { + createSetup: createSetupMock, + createStart: createStartMock, + create: createMock, +}; diff --git a/src/plugins/home/public/services/tutorials/tutorial_service.test.ts b/src/plugins/home/public/services/tutorials/tutorial_service.test.ts new file mode 100644 index 0000000000000..04f6bce9b7caa --- /dev/null +++ b/src/plugins/home/public/services/tutorials/tutorial_service.test.ts @@ -0,0 +1,55 @@ +/* + * 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 { TutorialService } from './tutorial_service'; + +describe('TutorialService', () => { + describe('setup', () => { + test('allows multiple set calls', () => { + const setup = new TutorialService().setup(); + expect(() => { + setup.setVariable('abc', 123); + setup.setVariable('def', 456); + }).not.toThrow(); + }); + + test('throws when same variable is set twice', () => { + const setup = new TutorialService().setup(); + expect(() => { + setup.setVariable('abc', 123); + setup.setVariable('abc', 456); + }).toThrow(); + }); + }); + + describe('start', () => { + test('returns empty object', () => { + const service = new TutorialService(); + expect(service.start().get()).toEqual({}); + }); + + test('returns last state of update calls', () => { + const service = new TutorialService(); + const setup = service.setup(); + setup.setVariable('abc', 123); + setup.setVariable('def', { subKey: 456 }); + expect(service.start().get()).toEqual({ abc: 123, def: { subKey: 456 } }); + }); + }); +}); diff --git a/src/plugins/home/public/services/tutorials/tutorial_service.ts b/src/plugins/home/public/services/tutorials/tutorial_service.ts new file mode 100644 index 0000000000000..824c3d46a76a3 --- /dev/null +++ b/src/plugins/home/public/services/tutorials/tutorial_service.ts @@ -0,0 +1,53 @@ +/* + * 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 type TutorialVariables = Partial>; + +export class TutorialService { + private tutorialVariables: TutorialVariables = {}; + + public setup() { + return { + /** + * Set a variable usable in tutorial templates. Access with `{config.}`. + */ + setVariable: (key: string, value: unknown) => { + if (this.tutorialVariables[key]) { + throw new Error('variable already set'); + } + this.tutorialVariables[key] = value; + }, + }; + } + + public start() { + return { + /** + * Retrieve the variables for substitution in tutorials. 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: (): TutorialVariables => this.tutorialVariables, + }; + } +} + +export type TutorialServiceSetup = ReturnType; +export type TutorialServiceStart = ReturnType; diff --git a/x-pack/plugins/cloud/public/plugin.ts b/x-pack/plugins/cloud/public/plugin.ts index f6408afb31493..2b8247066bfc3 100644 --- a/x-pack/plugins/cloud/public/plugin.ts +++ b/x-pack/plugins/cloud/public/plugin.ts @@ -31,6 +31,9 @@ export class CloudPlugin implements Plugin { if (home) { home.environment.update({ cloud: isCloudEnabled }); + if (isCloudEnabled) { + home.tutorials.setVariable('cloud', { id }); + } } return {