diff --git a/src/plugins/home/public/application/components/tutorial/tutorial.js b/src/plugins/home/public/application/components/tutorial/tutorial.js index bb8c92fb4f80c..9ee9a92714a76 100644 --- a/src/plugins/home/public/application/components/tutorial/tutorial.js +++ b/src/plugins/home/public/application/components/tutorial/tutorial.js @@ -171,16 +171,18 @@ class TutorialUi extends React.Component { checkInstructionSetStatus = async (instructionSetIndex) => { const instructionSet = this.getInstructionSets()[instructionSetIndex]; const esHitsCheckConfig = _.get(instructionSet, `statusCheck.esHitsCheck`); - const customStatusCheck = _.get(instructionSet, `statusCheck.customStatusCheck`); - const [esHitsStatusCheck, apmFleetStatusCheck] = await Promise.all([ + //Checks if the tutorial registered in the SERVER contains the customStatusCheckName property + const { customStatusCheckName } = this.state.tutorial; + + const [esHitsStatusCheck, customStatusCheck] = await Promise.all([ ...(esHitsCheckConfig ? [this.fetchEsHitsStatus(esHitsCheckConfig)] : []), - ...(customStatusCheck === 'apm-fleet-check' ? [this.fetchApmFleetStatus()] : []), + ...(customStatusCheckName ? [this.fetchCustomStatusCheck(customStatusCheckName)] : []), ]); const nextStatusCheckState = esHitsStatusCheck === StatusCheckStates.HAS_DATA || - apmFleetStatusCheck === StatusCheckStates.HAS_DATA + customStatusCheck === StatusCheckStates.HAS_DATA ? StatusCheckStates.HAS_DATA : StatusCheckStates.NO_DATA; @@ -192,10 +194,20 @@ class TutorialUi extends React.Component { })); }; - fetchApmFleetStatus = async () => { - const { http } = getServices(); - const response = await http.get('/api/apm/fleet/has_data'); - return response?.hasData === true ? StatusCheckStates.HAS_DATA : StatusCheckStates.NO_DATA; + fetchCustomStatusCheck = async (customStatusCheckName) => { + try { + //Checks if a custom status check callback was registered in the CLIENT + //that matches the same name registered in the SERVER (customStatusCheckName) + const customStatusCheckCallback = getServices().tutorialService.getCustomStatusCheck( + customStatusCheckName + ); + if (customStatusCheckCallback) { + const response = await customStatusCheckCallback(); + return response ? StatusCheckStates.HAS_DATA : StatusCheckStates.NO_DATA; + } + } catch (e) { + return StatusCheckStates.ERROR; + } }; /** diff --git a/src/plugins/home/public/services/tutorials/tutorial_service.ts b/src/plugins/home/public/services/tutorials/tutorial_service.ts index 8ba766d34da53..b558f01997b59 100644 --- a/src/plugins/home/public/services/tutorials/tutorial_service.ts +++ b/src/plugins/home/public/services/tutorials/tutorial_service.ts @@ -22,6 +22,9 @@ export type TutorialModuleNoticeComponent = React.FC<{ moduleName: string; }>; +type CustomStatusCheckCallback = () => Promise; +type CustomStatusCheck = Record; + export class TutorialService { private tutorialVariables: TutorialVariables = {}; private tutorialDirectoryNotices: { [key: string]: TutorialDirectoryNoticeComponent } = {}; @@ -29,6 +32,7 @@ export class TutorialService { [key: string]: TutorialDirectoryHeaderLinkComponent; } = {}; private tutorialModuleNotices: { [key: string]: TutorialModuleNoticeComponent } = {}; + private customStatusCheck: CustomStatusCheck = {}; public setup() { return { @@ -74,6 +78,10 @@ export class TutorialService { } this.tutorialModuleNotices[id] = component; }, + + registerCustomStatusCheck: (name: string, fnCallback: CustomStatusCheckCallback) => { + this.customStatusCheck[name] = fnCallback; + }, }; } @@ -92,6 +100,10 @@ export class TutorialService { public getModuleNotices() { return Object.values(this.tutorialModuleNotices); } + + public getCustomStatusCheck(name: string) { + return this.customStatusCheck[name]; + } } export type TutorialServiceSetup = ReturnType; diff --git a/src/plugins/home/server/services/tutorials/lib/tutorial_schema.ts b/src/plugins/home/server/services/tutorials/lib/tutorial_schema.ts index 86f5fb014f74f..375728e175f2c 100644 --- a/src/plugins/home/server/services/tutorials/lib/tutorial_schema.ts +++ b/src/plugins/home/server/services/tutorials/lib/tutorial_schema.ts @@ -49,7 +49,6 @@ const statusCheckSchema = schema.object({ index: schema.oneOf([schema.string(), schema.arrayOf(schema.string())]), query: schema.recordOf(schema.string(), schema.any()), }), - customStatusCheck: schema.maybe(schema.string()), }); const instructionSchema = schema.object({ @@ -154,6 +153,7 @@ export const tutorialSchema = schema.object({ // saved objects used by data module. savedObjects: schema.maybe(schema.arrayOf(schema.any())), savedObjectsInstallMsg: schema.maybe(schema.string()), + customStatusCheckName: schema.maybe(schema.string()), }); export type TutorialSchema = TypeOf; diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts index bfc0a3daf6f0e..fcf34a916e809 100644 --- a/x-pack/plugins/apm/public/plugin.ts +++ b/x-pack/plugins/apm/public/plugin.ts @@ -44,6 +44,7 @@ import type { } from '../../triggers_actions_ui/public'; import { registerApmAlerts } from './components/alerting/register_apm_alerts'; import { featureCatalogueEntry } from './featureCatalogueEntry'; +import { createCallApmApi } from './services/rest/createCallApmApi'; export type ApmPluginSetup = ReturnType; @@ -140,16 +141,30 @@ export class ApmPlugin implements Plugin { ); const getApmDataHelper = async () => { - const { - fetchObservabilityOverviewPageData, - getHasData, - createCallApmApi, - } = await import('./services/rest/apm_observability_overview_fetchers'); + const { fetchObservabilityOverviewPageData, getHasData } = await import( + './services/rest/apm_observability_overview_fetchers' + ); + const { hasFleetApmIntegrations } = await import( + './services/rest/tutorial_apm_fleet_check' + ); // have to do this here as well in case app isn't mounted yet createCallApmApi(core); - return { fetchObservabilityOverviewPageData, getHasData }; + return { + fetchObservabilityOverviewPageData, + getHasData, + hasFleetApmIntegrations, + }; }; + + // Registers a status check callback for the tutorial to call and verify if the APM integration is installed on fleet. + pluginSetupDeps.home?.tutorials.registerCustomStatusCheck( + 'apm_fleet_server_status_check', + async () => { + const { hasFleetApmIntegrations } = await getApmDataHelper(); + return hasFleetApmIntegrations(); + } + ); plugins.observability.dashboard.register({ appName: 'apm', hasData: async () => { @@ -163,11 +178,9 @@ export class ApmPlugin implements Plugin { }); const getUxDataHelper = async () => { - const { - fetchUxOverviewDate, - hasRumData, - createCallApmApi, - } = await import('./components/app/RumDashboard/ux_overview_fetchers'); + const { fetchUxOverviewDate, hasRumData } = await import( + './components/app/RumDashboard/ux_overview_fetchers' + ); // have to do this here as well in case app isn't mounted yet createCallApmApi(core); diff --git a/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts b/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts index 3a02efd05e5a5..ec5022bae8daa 100644 --- a/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts +++ b/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts @@ -11,8 +11,6 @@ import { } from '../../../../observability/public'; import { callApmApi } from './createCallApmApi'; -export { createCallApmApi } from './createCallApmApi'; - export const fetchObservabilityOverviewPageData = async ({ absoluteTime, relativeTime, diff --git a/x-pack/plugins/apm/public/services/rest/tutorial_apm_fleet_check.ts b/x-pack/plugins/apm/public/services/rest/tutorial_apm_fleet_check.ts new file mode 100644 index 0000000000000..d85cd6b3c8b48 --- /dev/null +++ b/x-pack/plugins/apm/public/services/rest/tutorial_apm_fleet_check.ts @@ -0,0 +1,20 @@ +/* + * 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 { callApmApi } from './createCallApmApi'; + +export async function hasFleetApmIntegrations() { + try { + const { hasData = false } = await callApmApi({ + endpoint: 'GET /api/apm/fleet/has_data', + signal: null, + }); + return hasData; + } catch (e) { + console.error('Something went wrong while fetching apm fleet data', e); + return false; + } +} diff --git a/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts b/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts index 711b1e96d3f3d..e886f2cc4029a 100644 --- a/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts +++ b/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts @@ -135,7 +135,6 @@ export function onPremInstructions({ 'No APM Server detected. Please make sure it is running and you have updated to 7.0 or higher.', } ), - customStatusCheck: 'apm-fleet-check', esHitsCheck: { index: onboardingIndices, query: { diff --git a/x-pack/plugins/apm/server/tutorial/index.ts b/x-pack/plugins/apm/server/tutorial/index.ts index 138efd111d63c..9118c30b845d0 100644 --- a/x-pack/plugins/apm/server/tutorial/index.ts +++ b/x-pack/plugins/apm/server/tutorial/index.ts @@ -103,6 +103,7 @@ It allows you to monitor the performance of thousands of applications in real ti ), euiIconType: 'apmApp', artifacts, + customStatusCheckName: 'apm_fleet_server_status_check', onPrem: onPremInstructions(indices), elasticCloud: createElasticCloudInstructions(cloud), previewImagePath: '/plugins/apm/assets/apm.png',