diff --git a/src/plugins/home/public/application/components/tutorial/instruction.js b/src/plugins/home/public/application/components/tutorial/instruction.js
index 88b28ea1bf1a9..5462263f0877b 100644
--- a/src/plugins/home/public/application/components/tutorial/instruction.js
+++ b/src/plugins/home/public/application/components/tutorial/instruction.js
@@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
-import React from 'react';
+import React, { Suspense, useMemo } from 'react';
import PropTypes from 'prop-types';
import { Content } from './content';
@@ -17,20 +17,16 @@ import {
EuiSpacer,
EuiCopy,
EuiButton,
+ EuiLoadingSpinner,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
-import { APMFleet } from '../apm_fleet';
+import { getServices } from '../../kibana_services';
+
+export function Instruction({ commands, paramValues, textPost, textPre, replaceTemplateStrings }) {
+ const { tutorialService, http, uiSettings, getBasePath } = getServices();
-export function Instruction({
- commands,
- paramValues,
- textPost,
- textPre,
- replaceTemplateStrings,
- customComponent,
-}) {
let pre;
if (textPre) {
pre = ;
@@ -45,10 +41,13 @@ export function Instruction({
);
}
- let custom;
- if (customComponent === 'apm_fleet') {
- custom = ;
- }
+ const customComponent = tutorialService.getCustomComponent();
+ //Memoize the custom component so it wont rerender everytime
+ const LazyCustomComponent = useMemo(() => {
+ if (customComponent) {
+ return React.lazy(() => customComponent());
+ }
+ }, [customComponent]);
let copyButton;
let commandBlock;
@@ -92,7 +91,15 @@ export function Instruction({
{post}
- {custom}
+ {LazyCustomComponent && (
+ }>
+
+
+ )}
diff --git a/src/plugins/home/public/application/components/tutorial/tutorial.js b/src/plugins/home/public/application/components/tutorial/tutorial.js
index 9ee9a92714a76..9296f72c8d44c 100644
--- a/src/plugins/home/public/application/components/tutorial/tutorial.js
+++ b/src/plugins/home/public/application/components/tutorial/tutorial.js
@@ -67,12 +67,12 @@ class TutorialUi extends React.Component {
async componentDidMount() {
const tutorial = await this.props.getTutorial(this.props.tutorialId);
-
if (!this._isMounted) {
return;
}
if (tutorial) {
+ getServices().tutorialService.setTutorial(tutorial);
// eslint-disable-next-line react/no-did-mount-set-state
this.setState(
{
@@ -172,12 +172,15 @@ class TutorialUi extends React.Component {
const instructionSet = this.getInstructionSets()[instructionSetIndex];
const esHitsCheckConfig = _.get(instructionSet, `statusCheck.esHitsCheck`);
- //Checks if the tutorial registered in the SERVER contains the customStatusCheckName property
- const { customStatusCheckName } = this.state.tutorial;
+ //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();
const [esHitsStatusCheck, customStatusCheck] = await Promise.all([
...(esHitsCheckConfig ? [this.fetchEsHitsStatus(esHitsCheckConfig)] : []),
- ...(customStatusCheckName ? [this.fetchCustomStatusCheck(customStatusCheckName)] : []),
+ ...(customStatusCheckCallback
+ ? [this.fetchCustomStatusCheck(customStatusCheckCallback)]
+ : []),
]);
const nextStatusCheckState =
@@ -194,17 +197,10 @@ class TutorialUi extends React.Component {
}));
};
- fetchCustomStatusCheck = async (customStatusCheckName) => {
+ fetchCustomStatusCheck = async (customStatusCheckCallback) => {
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;
- }
+ 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 b558f01997b59..7ab1d124adb00 100644
--- a/src/plugins/home/public/services/tutorials/tutorial_service.ts
+++ b/src/plugins/home/public/services/tutorials/tutorial_service.ts
@@ -23,7 +23,7 @@ export type TutorialModuleNoticeComponent = React.FC<{
}>;
type CustomStatusCheckCallback = () => Promise;
-type CustomStatusCheck = Record;
+type CustomComponent = () => Promise;
export class TutorialService {
private tutorialVariables: TutorialVariables = {};
@@ -32,7 +32,9 @@ export class TutorialService {
[key: string]: TutorialDirectoryHeaderLinkComponent;
} = {};
private tutorialModuleNotices: { [key: string]: TutorialModuleNoticeComponent } = {};
- private customStatusCheck: CustomStatusCheck = {};
+ private customStatusCheck: Record = {};
+ private customComponent: Record = {};
+ private tutorial: any;
public setup() {
return {
@@ -82,6 +84,10 @@ export class TutorialService {
registerCustomStatusCheck: (name: string, fnCallback: CustomStatusCheckCallback) => {
this.customStatusCheck[name] = fnCallback;
},
+
+ registerCustomComponent: (name: string, component: CustomComponent) => {
+ this.customComponent[name] = component;
+ },
};
}
@@ -101,8 +107,20 @@ export class TutorialService {
return Object.values(this.tutorialModuleNotices);
}
- public getCustomStatusCheck(name: string) {
- return this.customStatusCheck[name];
+ public getCustomStatusCheck() {
+ return this.customStatusCheck[this.tutorial?.customComponentName];
+ }
+
+ public getCustomComponent() {
+ return this.customComponent[this.tutorial?.customComponentName];
+ }
+
+ public setTutorial(tutorial: any) {
+ this.tutorial = tutorial;
+ }
+
+ public getTutorial() {
+ return this.tutorial;
}
}
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 375728e175f2c..6ea3c6aefa9ad 100644
--- a/src/plugins/home/server/services/tutorials/lib/tutorial_schema.ts
+++ b/src/plugins/home/server/services/tutorials/lib/tutorial_schema.ts
@@ -154,6 +154,7 @@ export const tutorialSchema = schema.object({
savedObjects: schema.maybe(schema.arrayOf(schema.any())),
savedObjectsInstallMsg: schema.maybe(schema.string()),
customStatusCheckName: schema.maybe(schema.string()),
+ customComponentName: schema.maybe(schema.string()),
});
export type TutorialSchema = TypeOf;
diff --git a/src/plugins/home/public/application/components/apm_fleet/index.tsx b/x-pack/plugins/apm/public/components/shared/tutorial_fleet_instructions/index.tsx
similarity index 54%
rename from src/plugins/home/public/application/components/apm_fleet/index.tsx
rename to x-pack/plugins/apm/public/components/shared/tutorial_fleet_instructions/index.tsx
index 1dc1b1e5de1b6..66aa149067acc 100644
--- a/src/plugins/home/public/application/components/apm_fleet/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/tutorial_fleet_instructions/index.tsx
@@ -1,23 +1,27 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
*/
-import {
- EuiButton,
- EuiCard,
- EuiFlexGroup,
- EuiFlexItem,
- EuiImage,
- EuiLoadingSpinner,
- EuiPanel,
-} from '@elastic/eui';
+import { EuiButton } from '@elastic/eui';
+import { EuiFlexItem } from '@elastic/eui';
+import { EuiFlexGroup } from '@elastic/eui';
+import { EuiPanel } from '@elastic/eui';
+import { EuiCard } from '@elastic/eui';
+import { EuiImage } from '@elastic/eui';
+import { EuiLoadingSpinner } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
+import { HttpStart } from 'kibana/public';
import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
-import { getServices } from '../../kibana_services';
+import { APIReturnType } from '../../../services/rest/createCallApmApi';
+
+interface Props {
+ http: HttpStart;
+ basePath: string;
+ isDarkTheme: boolean;
+}
const CentralizedContainer = styled.div`
display: flex;
@@ -25,15 +29,10 @@ const CentralizedContainer = styled.div`
align-items: center;
`;
-interface APIResponse {
- hasData: boolean;
-}
+type APIResponseType = APIReturnType<'GET /api/apm/fleet/has_data'>;
-export function APMFleet() {
- const { http, getBasePath, uiSettings } = getServices();
- const isDarkTheme = uiSettings.get('theme:darkMode');
- const basePath = getBasePath();
- const [data, setData] = useState();
+function TutorialFleetInstructions({ http, basePath, isDarkTheme }: Props) {
+ const [data, setData] = useState();
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
@@ -41,9 +40,8 @@ export function APMFleet() {
setIsLoading(true);
try {
const response = await http.get('/api/apm/fleet/has_data');
- setData(response as APIResponse);
+ setData(response as APIResponseType);
} catch (e) {
- // eslint-disable-next-line no-console
console.error('Error while fetching fleet details.', e);
}
setIsLoading(false);
@@ -58,13 +56,17 @@ export function APMFleet() {
);
}
+
// When APM integration is enable in Fleet
if (data?.hasData) {
return (
- {i18n.translate('home.apm.tutorial.apmServer.fleet.manageApmIntegration.button', {
- defaultMessage: 'Manage APM integration in Fleet',
- })}
+ {i18n.translate(
+ 'xpack.apm.tutorial.apmServer.fleet.manageApmIntegration.button',
+ {
+ defaultMessage: 'Manage APM integration in Fleet',
+ }
+ )}
);
}
@@ -76,22 +78,28 @@ export function APMFleet() {
- {i18n.translate('home.apm.tutorial.apmServer.fleet.apmIntegration.button', {
- defaultMessage: 'APM integration',
- })}
+ {i18n.translate(
+ 'xpack.apm.tutorial.apmServer.fleet.apmIntegration.button',
+ {
+ defaultMessage: 'APM integration',
+ }
+ )}
}
/>
@@ -110,3 +118,5 @@ export function APMFleet() {
);
}
+// eslint-disable-next-line import/no-default-export
+export default TutorialFleetInstructions;
diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts
index fcf34a916e809..6f332bf9ef26b 100644
--- a/x-pack/plugins/apm/public/plugin.ts
+++ b/x-pack/plugins/apm/public/plugin.ts
@@ -4,7 +4,6 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-
import { i18n } from '@kbn/i18n';
import { from } from 'rxjs';
import { map } from 'rxjs/operators';
@@ -165,6 +164,13 @@ export class ApmPlugin implements Plugin {
return hasFleetApmIntegrations();
}
);
+
+ // Registers custom component that is going to be render on fleet section
+ pluginSetupDeps.home?.tutorials.registerCustomComponent(
+ 'TutorialFleetInstructions',
+ () => import('./components/shared/tutorial_fleet_instructions')
+ );
+
plugins.observability.dashboard.register({
appName: 'apm',
hasData: async () => {
diff --git a/x-pack/plugins/apm/server/tutorial/index.ts b/x-pack/plugins/apm/server/tutorial/index.ts
index 9118c30b845d0..b4cca1bee94b0 100644
--- a/x-pack/plugins/apm/server/tutorial/index.ts
+++ b/x-pack/plugins/apm/server/tutorial/index.ts
@@ -104,6 +104,7 @@ It allows you to monitor the performance of thousands of applications in real ti
euiIconType: 'apmApp',
artifacts,
customStatusCheckName: 'apm_fleet_server_status_check',
+ customComponentName: 'TutorialFleetInstructions',
onPrem: onPremInstructions(indices),
elasticCloud: createElasticCloudInstructions(cloud),
previewImagePath: '/plugins/apm/assets/apm.png',