Skip to content

Commit

Permalink
addin custom component registration function
Browse files Browse the repository at this point in the history
  • Loading branch information
cauemarcondes committed Jun 10, 2021
1 parent 35c5672 commit 7dadd85
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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 = <Content text={replaceTemplateStrings(textPre)} />;
Expand All @@ -45,10 +41,13 @@ export function Instruction({
</div>
);
}
let custom;
if (customComponent === 'apm_fleet') {
custom = <APMFleet />;
}
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;
Expand Down Expand Up @@ -92,7 +91,15 @@ export function Instruction({

{post}

{custom}
{LazyCustomComponent && (
<Suspense fallback={<EuiLoadingSpinner />}>
<LazyCustomComponent
basePath={getBasePath()}
isDarkTheme={uiSettings.get('theme:darkMode')}
http={http}
/>
</Suspense>
)}

<EuiSpacer />
</div>
Expand Down
24 changes: 10 additions & 14 deletions src/plugins/home/public/application/components/tutorial/tutorial.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
{
Expand Down Expand Up @@ -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 =
Expand All @@ -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;
}
Expand Down
26 changes: 22 additions & 4 deletions src/plugins/home/public/services/tutorials/tutorial_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export type TutorialModuleNoticeComponent = React.FC<{
}>;

type CustomStatusCheckCallback = () => Promise<boolean>;
type CustomStatusCheck = Record<string, CustomStatusCheckCallback>;
type CustomComponent = () => Promise<React.ReactNode>;

export class TutorialService {
private tutorialVariables: TutorialVariables = {};
Expand All @@ -32,7 +32,9 @@ export class TutorialService {
[key: string]: TutorialDirectoryHeaderLinkComponent;
} = {};
private tutorialModuleNotices: { [key: string]: TutorialModuleNoticeComponent } = {};
private customStatusCheck: CustomStatusCheck = {};
private customStatusCheck: Record<string, CustomStatusCheckCallback> = {};
private customComponent: Record<string, CustomComponent> = {};
private tutorial: any;

public setup() {
return {
Expand Down Expand Up @@ -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;
},
};
}

Expand All @@ -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;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof tutorialSchema>;
Original file line number Diff line number Diff line change
@@ -1,49 +1,47 @@
/*
* 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;
justify-content: center;
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<APIResponse | undefined>();
function TutorialFleetInstructions({ http, basePath, isDarkTheme }: Props) {
const [data, setData] = useState<APIResponseType | undefined>();
const [isLoading, setIsLoading] = useState(false);

useEffect(() => {
async function fetchData() {
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);
Expand All @@ -58,13 +56,17 @@ export function APMFleet() {
</CentralizedContainer>
);
}

// When APM integration is enable in Fleet
if (data?.hasData) {
return (
<EuiButton iconType="gear" fill href={`${basePath}/app/fleet#/policies`}>
{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',
}
)}
</EuiButton>
);
}
Expand All @@ -76,22 +78,28 @@ export function APMFleet() {
<EuiCard
display="plain"
textAlign="left"
title={i18n.translate('home.apm.tutorial.apmServer.fleet.title', {
title={i18n.translate('xpack.apm.tutorial.apmServer.fleet.title', {
defaultMessage: 'Elastic APM (beta) now available in Fleet!',
})}
description={i18n.translate('home.apm.tutorial.apmServer.fleet.message', {
defaultMessage:
'The APM integration installs Elasticsearch templates and Ingest Node pipelines for APM data.',
})}
description={i18n.translate(
'xpack.apm.tutorial.apmServer.fleet.message',
{
defaultMessage:
'The APM integration installs Elasticsearch templates and Ingest Node pipelines for APM data.',
}
)}
footer={
<EuiButton
iconType="analyzeEvent"
color="secondary"
href={`${basePath}/app/fleet#/integrations/detail/apm-0.2.0/overview`}
>
{i18n.translate('home.apm.tutorial.apmServer.fleet.apmIntegration.button', {
defaultMessage: 'APM integration',
})}
{i18n.translate(
'xpack.apm.tutorial.apmServer.fleet.apmIntegration.button',
{
defaultMessage: 'APM integration',
}
)}
</EuiButton>
}
/>
Expand All @@ -110,3 +118,5 @@ export function APMFleet() {
</EuiPanel>
);
}
// eslint-disable-next-line import/no-default-export
export default TutorialFleetInstructions;
8 changes: 7 additions & 1 deletion x-pack/plugins/apm/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -165,6 +164,13 @@ export class ApmPlugin implements Plugin<ApmPluginSetup, ApmPluginStart> {
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 () => {
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/apm/server/tutorial/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down

0 comments on commit 7dadd85

Please sign in to comment.