From 07f91b519b4b563aedb46d1013a8465761aeefed Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Mon, 31 May 2021 15:12:00 -0400 Subject: [PATCH] adding fleet information on APM tutorial --- .../components/apm_fleet/index.tsx | 101 ++++++++++++++++++ .../components/tutorial/instruction.js | 18 +++- .../components/tutorial/instruction_set.js | 1 + .../services/tutorials/lib/tutorial_schema.ts | 1 + x-pack/plugins/apm/kibana.json | 5 +- x-pack/plugins/apm/server/plugin.ts | 45 ++++---- x-pack/plugins/apm/server/routes/fleet.ts | 29 +++++ .../get_global_apm_server_route_repository.ts | 4 +- .../apm/server/tutorial/envs/elastic_cloud.ts | 56 ++++++---- .../apm/server/tutorial/envs/on_prem.ts | 56 +++------- x-pack/plugins/apm/server/tutorial/index.ts | 10 +- x-pack/plugins/apm/server/types.ts | 9 ++ 12 files changed, 242 insertions(+), 93 deletions(-) create mode 100644 src/plugins/home/public/application/components/apm_fleet/index.tsx create mode 100644 x-pack/plugins/apm/server/routes/fleet.ts diff --git a/src/plugins/home/public/application/components/apm_fleet/index.tsx b/src/plugins/home/public/application/components/apm_fleet/index.tsx new file mode 100644 index 0000000000000..43a487047b19e --- /dev/null +++ b/src/plugins/home/public/application/components/apm_fleet/index.tsx @@ -0,0 +1,101 @@ +/* + * 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. + */ +import { + EuiButton, + EuiCard, + EuiFlexGroup, + EuiFlexItem, + EuiLoadingSpinner, + EuiPanel, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useEffect, useState } from 'react'; +import styled from 'styled-components'; +import { getServices } from '../../kibana_services'; + +const CentralizedContainer = styled.div` + display: flex; + justify-content: center; + align-items: center; +`; + +interface APIResponse { + hasData: boolean; +} + +export function APMFleet() { + const { getBasePath } = getServices(); + const basePath = getBasePath(); + const [data, setData] = useState(); + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + async function fetchData() { + setIsLoading(true); + try { + const response = await fetch(`${basePath}/api/apm/fleet/hasData`); + setData((await response.json()) as APIResponse); + } catch (e) { + // eslint-disable-next-line no-console + console.error('Error while fetching fleet details.', e); + } + setIsLoading(false); + } + fetchData(); + }, [basePath]); + + if (isLoading) { + return ( + + + + ); + } + // When APM integration is enable in Fleet + if (data?.hasData) { + return ( + + {i18n.translate('xpack.apm.tutorial.apmServer.fleet.manageApmIntegration.button', { + defaultMessage: 'Manage APM integration in Fleet', + })} + + ); + } + // When APM integration is not installed in Fleet or for some reason the API didn't work out + return ( + + + + + {i18n.translate('xpack.apm.tutorial.apmServer.fleet.apmIntegration.button', { + defaultMessage: 'APM integration', + })} + + } + /> + + + + + ); +} diff --git a/src/plugins/home/public/application/components/tutorial/instruction.js b/src/plugins/home/public/application/components/tutorial/instruction.js index 5f1a81efba7f7..f96ef3d71c517 100644 --- a/src/plugins/home/public/application/components/tutorial/instruction.js +++ b/src/plugins/home/public/application/components/tutorial/instruction.js @@ -21,7 +21,16 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; -export function Instruction({ commands, paramValues, textPost, textPre, replaceTemplateStrings }) { +import { APMFleet } from '../apm_fleet'; + +export function Instruction({ + commands, + paramValues, + textPost, + textPre, + replaceTemplateStrings, + customComponent, +}) { let pre; if (textPre) { pre = ; @@ -36,6 +45,10 @@ export function Instruction({ commands, paramValues, textPost, textPre, replaceT ); } + let custom; + if (customComponent === 'apm_fleet') { + custom = ; + } let copyButton; let commandBlock; @@ -79,6 +92,8 @@ export function Instruction({ commands, paramValues, textPost, textPre, replaceT {post} + {custom} + ); @@ -90,4 +105,5 @@ Instruction.propTypes = { textPost: PropTypes.string, textPre: PropTypes.string, replaceTemplateStrings: PropTypes.func.isRequired, + customComponent: PropTypes.string, }; diff --git a/src/plugins/home/public/application/components/tutorial/instruction_set.js b/src/plugins/home/public/application/components/tutorial/instruction_set.js index f16e276ed4c56..ada9b63b06b9d 100644 --- a/src/plugins/home/public/application/components/tutorial/instruction_set.js +++ b/src/plugins/home/public/application/components/tutorial/instruction_set.js @@ -186,6 +186,7 @@ class InstructionSetUi extends React.Component { textPre={instruction.textPre} textPost={instruction.textPost} replaceTemplateStrings={this.props.replaceTemplateStrings} + customComponent={instruction.customComponent} /> ); return { 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 5efbe067f6ece..89c5c78e82490 100644 --- a/src/plugins/home/server/services/tutorials/lib/tutorial_schema.ts +++ b/src/plugins/home/server/services/tutorials/lib/tutorial_schema.ts @@ -56,6 +56,7 @@ const instructionSchema = schema.object({ textPre: schema.maybe(schema.string()), commands: schema.maybe(schema.arrayOf(schema.string())), textPost: schema.maybe(schema.string()), + customComponent: schema.maybe(schema.string()), }); export type Instruction = TypeOf; diff --git a/x-pack/plugins/apm/kibana.json b/x-pack/plugins/apm/kibana.json index 76d544c3bc6f5..23ae90a204594 100644 --- a/x-pack/plugins/apm/kibana.json +++ b/x-pack/plugins/apm/kibana.json @@ -11,7 +11,8 @@ "embeddable", "infra", "observability", - "ruleRegistry" + "ruleRegistry", + "fleet" ], "optionalPlugins": [ "spaces", @@ -42,4 +43,4 @@ "ml", "observability" ] -} +} \ No newline at end of file diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index ddfd1368385d3..706ca1b5a0d7c 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -101,24 +101,38 @@ export class APMPlugin kibanaVersion: this.initContext.env.packageInfo.version, }); } + + const resourcePlugins = mapValues(plugins, (value, key) => { + return { + setup: value, + start: () => + core.getStartServices().then((services) => { + const [, pluginsStartContracts] = services; + return pluginsStartContracts[ + key as keyof APMPluginStartDependencies + ]; + }), + }; + }) as APMRouteHandlerResources['plugins']; + + plugins.features.registerKibanaFeature(APM_FEATURE); + plugins.home?.tutorials.registerTutorial( tutorialProvider({ - isEnabled: this.currentConfig['xpack.apm.ui.enabled'], - indexPatternTitle: this.currentConfig['apm_oss.indexPattern'], + isEnabled: currentConfig['xpack.apm.ui.enabled'], + indexPatternTitle: currentConfig['apm_oss.indexPattern'], cloud: plugins.cloud, indices: { - errorIndices: this.currentConfig['apm_oss.errorIndices'], - metricsIndices: this.currentConfig['apm_oss.metricsIndices'], - onboardingIndices: this.currentConfig['apm_oss.onboardingIndices'], - sourcemapIndices: this.currentConfig['apm_oss.sourcemapIndices'], - transactionIndices: this.currentConfig['apm_oss.transactionIndices'], + errorIndices: currentConfig['apm_oss.errorIndices'], + metricsIndices: currentConfig['apm_oss.metricsIndices'], + onboardingIndices: currentConfig['apm_oss.onboardingIndices'], + sourcemapIndices: currentConfig['apm_oss.sourcemapIndices'], + transactionIndices: currentConfig['apm_oss.transactionIndices'], }, basePath: core.http.basePath, }) ); - plugins.features.registerKibanaFeature(APM_FEATURE); - registerFeaturesUsage({ licensingPlugin: plugins.licensing }); const { ruleDataService } = plugins.ruleRegistry; @@ -195,18 +209,7 @@ export class APMPlugin config: currentConfig, repository: getGlobalApmServerRouteRepository(), ruleDataClient, - plugins: mapValues(plugins, (value, key) => { - return { - setup: value, - start: () => - core.getStartServices().then((services) => { - const [, pluginsStartContracts] = services; - return pluginsStartContracts[ - key as keyof APMPluginStartDependencies - ]; - }), - }; - }) as APMRouteHandlerResources['plugins'], + plugins: resourcePlugins, }); const boundGetApmIndices = async () => diff --git a/x-pack/plugins/apm/server/routes/fleet.ts b/x-pack/plugins/apm/server/routes/fleet.ts new file mode 100644 index 0000000000000..e9f8ccee9666f --- /dev/null +++ b/x-pack/plugins/apm/server/routes/fleet.ts @@ -0,0 +1,29 @@ +/* + * 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 { createApmServerRoute } from './create_apm_server_route'; +import { createApmServerRouteRepository } from './create_apm_server_route_repository'; + +const hasFleetDataRoute = createApmServerRoute({ + endpoint: 'GET /api/apm/fleet/hasData', + options: { tags: [] }, + handler: async (resources) => { + const { core } = resources.context; + const savedObjectsClient = core.savedObjects.client; + const fleetPluginStart = await resources.plugins.fleet.start(); + + const packagePolicies = await fleetPluginStart.packagePolicyService.list( + savedObjectsClient, + { kuery: 'ingest-package-policies.package.name:apm' } + ); + return { hasData: packagePolicies.total > 0 }; + }, +}); + +export const ApmFleetRouteRepository = createApmServerRouteRepository().add( + hasFleetDataRoute +); diff --git a/x-pack/plugins/apm/server/routes/get_global_apm_server_route_repository.ts b/x-pack/plugins/apm/server/routes/get_global_apm_server_route_repository.ts index c151752b4b6e0..3618d151076a0 100644 --- a/x-pack/plugins/apm/server/routes/get_global_apm_server_route_repository.ts +++ b/x-pack/plugins/apm/server/routes/get_global_apm_server_route_repository.ts @@ -29,6 +29,7 @@ import { customLinkRouteRepository } from './settings/custom_link'; import { traceRouteRepository } from './traces'; import { transactionRouteRepository } from './transactions'; import { APMRouteHandlerResources } from './typings'; +import { ApmFleetRouteRepository } from './fleet'; const getTypedGlobalApmServerRouteRepository = () => { const repository = createApmServerRouteRepository() @@ -48,7 +49,8 @@ const getTypedGlobalApmServerRouteRepository = () => { .merge(agentConfigurationRouteRepository) .merge(anomalyDetectionRouteRepository) .merge(apmIndicesRouteRepository) - .merge(customLinkRouteRepository); + .merge(customLinkRouteRepository) + .merge(ApmFleetRouteRepository); return repository; }; diff --git a/x-pack/plugins/apm/server/tutorial/envs/elastic_cloud.ts b/x-pack/plugins/apm/server/tutorial/envs/elastic_cloud.ts index 55adc756f31af..e6aa59b708436 100644 --- a/x-pack/plugins/apm/server/tutorial/envs/elastic_cloud.ts +++ b/x-pack/plugins/apm/server/tutorial/envs/elastic_cloud.ts @@ -32,9 +32,9 @@ export function createElasticCloudInstructions( const apmServerUrl = cloudSetup?.apm.url; const instructionSets = []; - if (!apmServerUrl) { - instructionSets.push(getApmServerInstructionSet(cloudSetup)); - } + instructionSets.push( + getApmServerInstructionSet({ cloudSetup, hasApmServerUrl: !!apmServerUrl }) + ); instructionSets.push(getApmAgentInstructionSet(cloudSetup)); @@ -43,29 +43,41 @@ export function createElasticCloudInstructions( }; } -function getApmServerInstructionSet( - cloudSetup?: CloudSetup -): InstructionSetSchema { - const cloudId = cloudSetup?.cloudId; +function getApmServerInstructionSet({ + cloudSetup, + hasApmServerUrl, +}: { + cloudSetup?: CloudSetup; + hasApmServerUrl: boolean; +}): InstructionSetSchema { + const instructionVariants: InstructionSetSchema['instructionVariants'] = [ + { + id: INSTRUCTION_VARIANT.FLEET, + instructions: [{ customComponent: 'apm_fleet' }], + }, + ]; + + if (!hasApmServerUrl) { + instructionVariants.push({ + id: INSTRUCTION_VARIANT.ESC, + instructions: [ + { + title: 'Enable the APM Server in the ESS console', + textPre: i18n.translate('xpack.apm.tutorial.elasticCloud.textPre', { + defaultMessage: + 'To enable the APM Server go to [the Elastic Cloud console](https://cloud.elastic.co/deployments?q={cloudId}) and enable APM in the deployment settings. Once enabled, refresh this page.', + values: { cloudId: cloudSetup?.cloudId }, + }), + }, + ], + }); + } + return { title: i18n.translate('xpack.apm.tutorial.apmServer.title', { defaultMessage: 'APM Server', }), - instructionVariants: [ - { - id: INSTRUCTION_VARIANT.ESC, - instructions: [ - { - title: 'Enable the APM Server in the ESS console', - textPre: i18n.translate('xpack.apm.tutorial.elasticCloud.textPre', { - defaultMessage: - 'To enable the APM Server go to [the Elastic Cloud console](https://cloud.elastic.co/deployments?q={cloudId}) and enable APM in the deployment settings. Once enabled, refresh this page.', - values: { cloudId }, - }), - }, - ], - }, - ], + instructionVariants, }; } 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 a3fc13b7ad656..3b1367e64bd06 100644 --- a/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts +++ b/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts @@ -6,33 +6,31 @@ */ import { i18n } from '@kbn/i18n'; -import { IBasePath } from 'kibana/server'; -import url from 'url'; import { INSTRUCTION_VARIANT, TutorialSchema, } from '../../../../../../src/plugins/home/server'; import { - createWindowsServerInstructions, - createEditConfig, - createStartServerUnixSysv, - createStartServerUnix, - createDownloadServerRpm, - createDownloadServerDeb, - createDownloadServerOsx, -} from '../instructions/apm_server_instructions'; -import { - createNodeAgentInstructions, createDjangoAgentInstructions, + createDotNetAgentInstructions, createFlaskAgentInstructions, - createRailsAgentInstructions, - createRackAgentInstructions, - createJsAgentInstructions, createGoAgentInstructions, createJavaAgentInstructions, - createDotNetAgentInstructions, + createJsAgentInstructions, + createNodeAgentInstructions, createPhpAgentInstructions, + createRackAgentInstructions, + createRailsAgentInstructions, } from '../instructions/apm_agent_instructions'; +import { + createDownloadServerDeb, + createDownloadServerOsx, + createDownloadServerRpm, + createEditConfig, + createStartServerUnix, + createStartServerUnixSysv, + createWindowsServerInstructions, +} from '../instructions/apm_server_instructions'; export function onPremInstructions({ errorIndices, @@ -40,14 +38,12 @@ export function onPremInstructions({ metricsIndices, sourcemapIndices, onboardingIndices, - basePath, }: { errorIndices: string; transactionIndices: string; metricsIndices: string; sourcemapIndices: string; onboardingIndices: string; - basePath: IBasePath; }): TutorialSchema['onPrem'] { const EDIT_CONFIG = createEditConfig(); const START_SERVER_UNIX = createStartServerUnix(); @@ -75,29 +71,7 @@ export function onPremInstructions({ instructionVariants: [ { id: INSTRUCTION_VARIANT.FLEET, - instructions: [ - { - title: i18n.translate( - 'xpack.apm.tutorial.apmServer.fleet.title', - { - defaultMessage: - 'Elastic APM (beta) now available in Fleet!', - } - ), - textPre: i18n.translate( - 'xpack.apm.tutorial.apmServer.fleet.message', - { - defaultMessage: - 'The [APM integration]({apmIntegrationTest}) installs Elasticsearch templates and Ingest Node pipelines for APM data.', - values: { - apmIntegrationTest: `${url.format( - basePath.prepend('/app/fleet#/policies') - )}`, - }, - } - ), - }, - ], + instructions: [{ customComponent: 'apm_fleet' }], }, { id: INSTRUCTION_VARIANT.OSX, diff --git a/x-pack/plugins/apm/server/tutorial/index.ts b/x-pack/plugins/apm/server/tutorial/index.ts index 47e1e3901581b..a9a1af905bf63 100644 --- a/x-pack/plugins/apm/server/tutorial/index.ts +++ b/x-pack/plugins/apm/server/tutorial/index.ts @@ -7,16 +7,16 @@ import { i18n } from '@kbn/i18n'; import { IBasePath } from 'kibana/server'; -import { onPremInstructions } from './envs/on_prem'; -import { createElasticCloudInstructions } from './envs/elastic_cloud'; -import apmIndexPattern from './index_pattern.json'; -import { CloudSetup } from '../../../cloud/server'; import { ArtifactsSchema, TutorialsCategory, TutorialSchema, } from '../../../../../src/plugins/home/server'; +import { CloudSetup } from '../../../cloud/server'; import { APM_STATIC_INDEX_PATTERN_ID } from '../../common/index_pattern_constants'; +import { createElasticCloudInstructions } from './envs/elastic_cloud'; +import { onPremInstructions } from './envs/on_prem'; +import apmIndexPattern from './index_pattern.json'; const apmIntro = i18n.translate('xpack.apm.tutorial.introduction', { defaultMessage: @@ -106,7 +106,7 @@ It allows you to monitor the performance of thousands of applications in real ti ), euiIconType: 'apmApp', artifacts, - onPrem: onPremInstructions({ ...indices, basePath }), + onPrem: onPremInstructions(indices), elasticCloud: createElasticCloudInstructions(cloud), previewImagePath: '/plugins/apm/assets/apm.png', savedObjects, diff --git a/x-pack/plugins/apm/server/types.ts b/x-pack/plugins/apm/server/types.ts index a5ba4f39b32b3..341d9bc8d8604 100644 --- a/x-pack/plugins/apm/server/types.ts +++ b/x-pack/plugins/apm/server/types.ts @@ -43,6 +43,10 @@ import { TaskManagerSetupContract, TaskManagerStartContract, } from '../../task_manager/server'; +import { + FleetSetupContract as FleetPluginSetup, + FleetStartContract as FleetPluginStart, +} from '../../fleet/server'; import { APMConfig } from '.'; import { getApmIndices } from './lib/settings/apm_indices/get_apm_indices'; import { createApmEventClient } from './lib/helpers/create_es_client/create_apm_event_client'; @@ -123,6 +127,10 @@ interface DependencyMap { setup: RuleRegistryPluginSetupContract; start: RuleRegistryPluginStartContract; }; + fleet: { + setup: FleetPluginSetup; + start: FleetPluginStart; + }; } const requiredDependencies = [ @@ -135,6 +143,7 @@ const requiredDependencies = [ 'infra', 'observability', 'ruleRegistry', + 'fleet', ] as const; const optionalDependencies = [