From ac2fc4c3beca7b677910815566db55b93d8406a7 Mon Sep 17 00:00:00 2001 From: Achyut Jhunjhunwala Date: Wed, 7 Jun 2023 16:20:50 +0200 Subject: [PATCH] [APM] Serverless Onboarding with Custom Tutorials (#158228) Closes https://github.com/elastic/kibana/issues/155371 ## Summary PR adds Serverless Onboarding flow using Custom Integration. This would also lay the foundation for us to complete get rid of Home Tutorial App and move the remaining `onPrem` and `cloud` tutorials which are currently still loaded using Home Tutorial App. 1. Adds new Custom Integration for Serverless Onboarding (Toggling Home AApp Tutorial Integration) 2. Since we are migrating away from the Home App Tutorials, lot of existing code has been duplicated and refactored for the custom implementation. Home App Tutorial would require the Server to register all the steps and the client to only register a custom component which then would be loaded by Home App Tutorial component. We don't need to follow this approach any more. All the UX logic has now been moved to the Public folder with only Custom Integration done on the `server/plugin.ts`. 3. As we are not sure how the solutions will be informed about being running on Serverless or not, I have introduced a new variable in `serverless.oblt.yml` file called `xpack.apm.serverlessOnboarding: true`. With this the development has been done. This can be changed to actual logic once we know more. 4. A new configuration `xpack.apm.managedServiceUrl` for accessing Managed Service URL is also being added by Control Plane team as part of https://elasticco.atlassian.net/browse/CP-2403. Hence this PR expects this property to be present for Serverless. 5. Unit tests to toggle between `secret_token` and `api_key` depending on availability has been added. No API Tests were added as no new API created. Cypress Tests cannot be added due to Serverless ## Need help reviewing the PR ? 1. `config/serverless.oblt.yml` - Adds the new flag which would enable this flow 2. `x-pack/plugins/apm/common/tutorial/tutorials.ts` - Defines the configuration required to register the APM's Tutorial Custom Integration 3. `x-pack/plugins/apm/public/components/app/tutorials/commands` - This directory contains all the agent specific data required to load the TABLE with settings required for configuring APM MIS. 4. `x-pack/plugins/apm/public/components/app/tutorials/instructions` - This folder contains all the individual agent specific instructions in the format used by [EuiSteps](https://eui.elastic.co/#/navigation/steps#complex-steps) 5. `x-pack/plugins/apm/public/components/routing` - Here we register our custom route 6. Changes on the server side a quite small and they only register the custom integration. 7. `x-pack/plugins/apm/public/components/app/tutorials/serverless_instructions.tsx` - This file currently defines all the logic for registering Serverless instructions. We will soon have similar files for `onPrem` and `cloud` instructions ### Risk Matrix | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | The flow depends on presence of a flag in `kibana.yml` file. | Low | High | By default this flow will be disabled and would fallback to traditional onboarding in absence of the flag. | ### Demo https://github.com/elastic/kibana/assets/7416358/d60f0610-1fea-4540-86f5-2d72ab97f640 ### Updated Demo with Create API Button inside the table https://github.com/elastic/kibana/assets/7416358/e84d8d6c-a048-4638-9b63-45080feca90b --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- config/serverless.oblt.yml | 8 + .../test_suites/core_plugins/rendering.ts | 2 + .../plugins/apm/common/tutorial/tutorials.ts | 32 + x-pack/plugins/apm/kibana.jsonc | 1 + .../onboarding/agent_config_instructions.tsx | 66 ++ .../app/onboarding/agent_config_table.tsx | 106 +++ .../app/onboarding/commands/django.ts | 65 ++ .../app/onboarding/commands/dotnet.ts | 47 ++ .../app/onboarding/commands/flask.ts | 62 ++ .../commands/get_apm_agent_commands.test.ts | 696 ++++++++++++++++++ .../commands/get_apm_agent_commands.ts | 161 ++++ .../components/app/onboarding/commands/go.ts | 54 ++ .../app/onboarding/commands/java.ts | 47 ++ .../app/onboarding/commands/node.ts | 55 ++ .../components/app/onboarding/commands/php.ts | 38 + .../app/onboarding/commands/rack.ts | 48 ++ .../app/onboarding/commands/rails.ts | 48 ++ .../app/onboarding/commands/shared_hints.ts | 47 ++ .../components/app/onboarding/footer.tsx | 50 ++ .../components/app/onboarding/index.tsx | 107 +++ .../app/onboarding/instruction_variants.ts | 70 ++ .../instructions/api_key_callout.tsx | 88 +++ .../onboarding/instructions/django_agent.tsx | 90 +++ .../onboarding/instructions/dotnet_agent.tsx | 151 ++++ .../onboarding/instructions/flask_agent.tsx | 90 +++ .../app/onboarding/instructions/go_agent.tsx | 134 ++++ .../app/onboarding/instructions/index.ts | 17 + .../onboarding/instructions/java_agent.tsx | 104 +++ .../onboarding/instructions/node_agent.tsx | 93 +++ .../onboarding/instructions/otel_agent.tsx | 290 ++++++++ .../app/onboarding/instructions/php_agent.tsx | 123 ++++ .../onboarding/instructions/rack_agent.tsx | 134 ++++ .../onboarding/instructions/rails_agent.tsx | 89 +++ .../app/onboarding/instructions_set.tsx | 101 +++ .../app/onboarding/introduction.tsx | 85 +++ .../onboarding/serverless_instructions.tsx | 102 +++ .../components/routing/apm_route_config.tsx | 2 + .../components/routing/onboarding/index.tsx | 15 + .../routing/templates/apm_main_template.tsx | 3 + .../routing/templates/no_data_config.ts | 78 +- .../apm_plugin/mock_apm_plugin_context.tsx | 2 + x-pack/plugins/apm/public/index.ts | 2 + x-pack/plugins/apm/server/index.ts | 14 + x-pack/plugins/apm/server/plugin.ts | 30 +- .../routes/agent_keys/create_agent_key.ts | 2 +- .../register_apm_server_routes.test.ts | 2 +- .../apm_routes/register_apm_server_routes.ts | 2 + x-pack/plugins/apm/server/types.ts | 7 +- x-pack/plugins/apm/tsconfig.json | 1 + .../settings/agent_keys/agent_keys.spec.ts | 10 +- 50 files changed, 3637 insertions(+), 34 deletions(-) create mode 100644 x-pack/plugins/apm/common/tutorial/tutorials.ts create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/agent_config_instructions.tsx create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/agent_config_table.tsx create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/commands/django.ts create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/commands/dotnet.ts create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/commands/flask.ts create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/commands/get_apm_agent_commands.test.ts create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/commands/get_apm_agent_commands.ts create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/commands/go.ts create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/commands/java.ts create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/commands/node.ts create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/commands/php.ts create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/commands/rack.ts create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/commands/rails.ts create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/commands/shared_hints.ts create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/footer.tsx create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/index.tsx create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/instruction_variants.ts create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/instructions/api_key_callout.tsx create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/instructions/django_agent.tsx create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/instructions/dotnet_agent.tsx create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/instructions/flask_agent.tsx create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/instructions/go_agent.tsx create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/instructions/index.ts create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/instructions/java_agent.tsx create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/instructions/node_agent.tsx create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/instructions/otel_agent.tsx create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/instructions/php_agent.tsx create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/instructions/rack_agent.tsx create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/instructions/rails_agent.tsx create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/instructions_set.tsx create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/introduction.tsx create mode 100644 x-pack/plugins/apm/public/components/app/onboarding/serverless_instructions.tsx create mode 100644 x-pack/plugins/apm/public/components/routing/onboarding/index.tsx diff --git a/config/serverless.oblt.yml b/config/serverless.oblt.yml index 7687537296771..df251857c9575 100644 --- a/config/serverless.oblt.yml +++ b/config/serverless.oblt.yml @@ -19,3 +19,11 @@ xpack.serverless.plugin.developer.projectSwitcher.currentType: 'observability' ## Disable adding the component template `.fleet_agent_id_verification-1` to every index template for each datastream for each integration xpack.fleet.agentIdVerificationEnabled: false + +## APM Serverless Onboarding flow +xpack.apm.serverlessOnboarding: true + +## Required for force installation of APM Package +xpack.fleet.packages: + - name: apm + version: latest diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts index 272a53eb7b283..fc5be23eba61d 100644 --- a/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -177,6 +177,8 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'xpack.apm.serviceMapEnabled (boolean)', 'xpack.apm.ui.enabled (boolean)', 'xpack.apm.ui.maxTraceItems (number)', + 'xpack.apm.managedServiceUrl (any)', + 'xpack.apm.serverlessOnboarding (any)', 'xpack.apm.latestAgentVersionsUrl (string)', 'xpack.cases.files.allowedMimeTypes (array)', 'xpack.cases.files.maxSize (number)', diff --git a/x-pack/plugins/apm/common/tutorial/tutorials.ts b/x-pack/plugins/apm/common/tutorial/tutorials.ts new file mode 100644 index 0000000000000..a401ec4bb15f3 --- /dev/null +++ b/x-pack/plugins/apm/common/tutorial/tutorials.ts @@ -0,0 +1,32 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { CustomIntegration } from '@kbn/custom-integrations-plugin/common'; + +const APM_INTEGRATION_CATEGORIES = ['observability', 'apm']; + +export const apmTutorialCustomIntegration: Omit = { + id: 'apm', + title: i18n.translate('xpack.apm.tutorial.specProvider.name', { + defaultMessage: 'APM', + }), + categories: APM_INTEGRATION_CATEGORIES, + uiInternalPath: '/app/apm/onboarding', + description: i18n.translate('xpack.apm.tutorial.introduction', { + defaultMessage: + 'Collect performance metrics from your applications with Elastic APM.', + }), + icons: [ + { + type: 'eui', + src: 'apmApp', + }, + ], + shipper: 'tutorial', + isBeta: false, +}; diff --git a/x-pack/plugins/apm/kibana.jsonc b/x-pack/plugins/apm/kibana.jsonc index 391f8481e59c7..c00c90afb4852 100644 --- a/x-pack/plugins/apm/kibana.jsonc +++ b/x-pack/plugins/apm/kibana.jsonc @@ -45,6 +45,7 @@ "spaces", "taskManager", "usageCollection", + "customIntegrations", // Move this to requiredPlugins after completely migrating from the Tutorials Home App "licenseManagement" ], "requiredBundles": [ diff --git a/x-pack/plugins/apm/public/components/app/onboarding/agent_config_instructions.tsx b/x-pack/plugins/apm/public/components/app/onboarding/agent_config_instructions.tsx new file mode 100644 index 0000000000000..c975d5af42271 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/onboarding/agent_config_instructions.tsx @@ -0,0 +1,66 @@ +/* + * 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 React from 'react'; +import { EuiCodeBlock, EuiSpacer } from '@elastic/eui'; +import { + getApmAgentCommands, + getApmAgentVariables, + getApmAgentLineNumbers, + getApmAgentHighlightLang, +} from './commands/get_apm_agent_commands'; +import { AgentConfigurationTable } from './agent_config_table'; + +export function AgentConfigInstructions({ + variantId, + apmServerUrl, + secretToken, + apiKey, + createApiKey, + createApiKeyLoading, +}: { + variantId: string; + apmServerUrl: string; + secretToken?: string; + apiKey?: string | null; + createApiKey?: () => void; + createApiKeyLoading?: boolean; +}) { + const commands = getApmAgentCommands({ + variantId, + apmServerUrl, + secretToken, + apiKey, + }); + + const variables = getApmAgentVariables(variantId, secretToken); + const lineNumbers = getApmAgentLineNumbers(variantId, apiKey); + const highlightLang = getApmAgentHighlightLang(variantId); + + return ( + <> + + + + + + {commands} + + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/onboarding/agent_config_table.tsx b/x-pack/plugins/apm/public/components/app/onboarding/agent_config_table.tsx new file mode 100644 index 0000000000000..33359b1713ab5 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/onboarding/agent_config_table.tsx @@ -0,0 +1,106 @@ +/* + * 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 React from 'react'; +import type { ValuesType } from 'utility-types'; +import { get } from 'lodash'; +import { + EuiBasicTable, + EuiText, + EuiBasicTableColumn, + EuiButton, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +function ConfigurationValueColumn({ + columnKey, + value, + createApiKey, + createApiKeyLoading, +}: { + columnKey: string; + value: string | null; + createApiKey?: () => void; + createApiKeyLoading?: boolean; +}) { + const shouldRenderCreateApiKeyButton = + columnKey === 'apiKey' && value === null; + + if (shouldRenderCreateApiKeyButton) { + return ( + + {i18n.translate('xpack.apm.tutorial.apiKey.create', { + defaultMessage: 'Create API Key', + })} + + ); + } + + return ( + + {value} + + ); +} + +export function AgentConfigurationTable({ + variables, + data, + createApiKey, + createApiKeyLoading, +}: { + variables: { [key: string]: string }; + data: { + apmServerUrl?: string; + secretToken?: string; + apiKey?: string | null; + }; + createApiKey?: () => void; + createApiKeyLoading?: boolean; +}) { + if (!variables) return null; + + const defaultValues = { + apmServiceName: 'my-service-name', + apmEnvironment: 'my-environment', + }; + + const columns: Array>> = [ + { + field: 'setting', + name: i18n.translate('xpack.apm.onboarding.agent.column.configSettings', { + defaultMessage: 'Configuration setting', + }), + }, + { + field: 'value', + name: i18n.translate('xpack.apm.onboarding.agent.column.configValue', { + defaultMessage: 'Configuration value', + }), + render: (_, { value, key }) => ( + + ), + }, + ]; + + const items = Object.entries(variables).map(([key, value]) => ({ + setting: value, + value: get({ ...data, ...defaultValues }, key), + key, + })); + return ; +} diff --git a/x-pack/plugins/apm/public/components/app/onboarding/commands/django.ts b/x-pack/plugins/apm/public/components/app/onboarding/commands/django.ts new file mode 100644 index 0000000000000..993138c69110e --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/onboarding/commands/django.ts @@ -0,0 +1,65 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const djangoVariables = (secretToken?: string) => ({ + apmServiceName: 'SERVICE_NAME', + ...(secretToken && { secretToken: 'SECRET_TOKEN' }), + ...(!secretToken && { apiKey: 'API_KEY' }), + apmServerUrl: 'SERVER_URL', + apmEnvironment: 'ENVIRONMENT', +}); + +export const djangoHighlightLang = 'py'; + +export const djangoLineNumbers = () => ({ + start: 1, + highlight: '1, 3, 5, 7, 9, 12, 15, 18-19, 21, 23, 25', +}); + +export const django = `INSTALLED_APPS = ( + # ${i18n.translate( + 'xpack.apm.onboarding.djangoClient.configure.commands.addAgentComment', + { + defaultMessage: 'Add the agent to installed apps', + } + )} + 'elasticapm.contrib.django', + # ... +) + +ELASTIC_APM = { + # {{serviceNameHint}} + 'SERVICE_NAME': 'my-service-name', + + {{^secretToken}} + # {{apiKeyHint}} + 'API_KEY': '{{{apiKey}}}', + {{/secretToken}} + {{#secretToken}} + # {{secretTokenHint}} + 'SECRET_TOKEN': '{{{secretToken}}}', + {{/secretToken}} + + # {{{serverUrlHint}}} + 'SERVER_URL': '{{{apmServerUrl}}}', + + # {{{serviceEnvironmentHint}}} + 'ENVIRONMENT': 'my-environment', +} + +MIDDLEWARE = ( + # ${i18n.translate( + 'xpack.apm.onboarding.djangoClient.configure.commands.addTracingMiddlewareComment', + { + defaultMessage: 'Add our tracing middleware to send performance metrics', + } + )} + 'elasticapm.contrib.django.middleware.TracingMiddleware', + #... +)`; diff --git a/x-pack/plugins/apm/public/components/app/onboarding/commands/dotnet.ts b/x-pack/plugins/apm/public/components/app/onboarding/commands/dotnet.ts new file mode 100644 index 0000000000000..84c1b2a3f2603 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/onboarding/commands/dotnet.ts @@ -0,0 +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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const dotnetVariables = (secretToken?: string) => ({ + apmServiceName: 'ServiceName', + ...(secretToken && { secretToken: 'SecretToken' }), + ...(!secretToken && { apiKey: 'ApiKey' }), + apmServerUrl: 'ServerUrl', + apmEnvironment: 'Environment', +}); + +export const dotnetHighlightLang = 'dotnet'; + +export const dotnetLineNumbers = () => ({ + start: 1, + highlight: '1-2, 4, 6, 8, 10-12', +}); + +export const dotnet = `{ + "ElasticApm": { + /// {{serviceNameHint}} ${i18n.translate( + 'xpack.apm.onboarding.dotnetClient.createConfig.commands.defaultServiceName', + { + defaultMessage: 'Default is the entry assembly of the application.', + } + )} + "ServiceName": "my-service-name", + {{^secretToken}} + /// {{apiKeyHint}} + "ApiKey": "{{{apiKey}}}", + {{/secretToken}} + {{#secretToken}} + /// {{secretTokenHint}} + "SecretToken": "{{{secretToken}}}", + {{/secretToken}} + /// {{{serverUrlHint}}} + "ServerUrl": "{{{apmServerUrl}}}", + /// {{{serviceEnvironmentHint}}} + "Environment": "my-environment", + } +}`; diff --git a/x-pack/plugins/apm/public/components/app/onboarding/commands/flask.ts b/x-pack/plugins/apm/public/components/app/onboarding/commands/flask.ts new file mode 100644 index 0000000000000..a95d64a11a318 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/onboarding/commands/flask.ts @@ -0,0 +1,62 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const flaskVariables = (secretToken?: string) => ({ + apmServiceName: 'SERVICE_NAME', + ...(secretToken && { secretToken: 'SECRET_TOKEN' }), + ...(!secretToken && { apiKey: 'API_KEY' }), + apmServerUrl: 'SERVER_URL', + apmEnvironment: 'ENVIRONMENT', +}); + +export const flaskHighlightLang = 'py'; + +export const flaskLineNumbers = () => ({ + start: 1, + highlight: '2-4, 7-8, 10, 13, 16, 19-22', +}); + +export const flask = `# ${i18n.translate( + 'xpack.apm.onboarding.flaskClient.configure.commands.initializeUsingEnvironmentVariablesComment', + { + defaultMessage: 'Initialize using environment variables', + } +)} +from elasticapm.contrib.flask import ElasticAPM +app = Flask(__name__) +apm = ElasticAPM(app) + +# ${i18n.translate( + 'xpack.apm.onboarding.flaskClient.configure.commands.configureElasticApmComment', + { + defaultMessage: "Or use ELASTIC_APM in your application's settings", + } +)} +from elasticapm.contrib.flask import ElasticAPM +app.config['ELASTIC_APM'] = { + # {{serviceNameHint}} + 'SERVICE_NAME': 'my-service-name', + + {{^secretToken}} + # {{apiKeyHint}} + 'API_KEY': '{{{apiKey}}}', + {{/secretToken}} + {{#secretToken}} + # {{secretTokenHint}} + 'SECRET_TOKEN': '{{{secretToken}}}', + {{/secretToken}} + + # {{{serverUrlHint}}} + 'SERVER_URL': '{{{apmServerUrl}}}', + + {{{serviceEnvironmentHint}}} + 'ENVIRONMENT': 'my-environment', +} + +apm = ElasticAPM(app)`; diff --git a/x-pack/plugins/apm/public/components/app/onboarding/commands/get_apm_agent_commands.test.ts b/x-pack/plugins/apm/public/components/app/onboarding/commands/get_apm_agent_commands.test.ts new file mode 100644 index 0000000000000..2ff78a0321e93 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/onboarding/commands/get_apm_agent_commands.test.ts @@ -0,0 +1,696 @@ +/* + * 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 { getApmAgentCommands } from './get_apm_agent_commands'; + +describe('getCommands', () => { + describe('Unknown agent', () => { + it('renders empty command', () => { + const commands = getApmAgentCommands({ + variantId: 'foo', + apmServerUrl: 'localhost:8220', + secretToken: 'foobar', + }); + expect(commands).toBe(''); + }); + }); + describe('Java agent', () => { + it('renders empty commands', () => { + const commands = getApmAgentCommands({ + variantId: 'java', + }); + expect(commands).toMatchInlineSnapshot(` + "java -javaagent:/path/to/elastic-apm-agent-.jar \\\\ + -Delastic.apm.service_name=my-service-name \\\\ + -Delastic.apm.api_key= \\\\ + -Delastic.apm.server_url= \\\\ + -Delastic.apm.environment=my-environment \\\\ + -Delastic.apm.application_packages=org.example \\\\ + -jar my-service-name.jar" + `); + }); + it('renders with secret token and url', () => { + const commands = getApmAgentCommands({ + variantId: 'java', + apmServerUrl: 'localhost:8220', + secretToken: 'foobar', + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "java -javaagent:/path/to/elastic-apm-agent-.jar \\\\ + -Delastic.apm.service_name=my-service-name \\\\ + -Delastic.apm.secret_token=foobar \\\\ + -Delastic.apm.server_url=localhost:8220 \\\\ + -Delastic.apm.environment=my-environment \\\\ + -Delastic.apm.application_packages=org.example \\\\ + -jar my-service-name.jar" + `); + }); + it('renders with api key even though secret token is present', () => { + const commands = getApmAgentCommands({ + variantId: 'java', + apmServerUrl: 'localhost:8220', + secretToken: 'foobar', + apiKey: 'myApiKey', + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "java -javaagent:/path/to/elastic-apm-agent-.jar \\\\ + -Delastic.apm.service_name=my-service-name \\\\ + -Delastic.apm.secret_token=foobar \\\\ + -Delastic.apm.server_url=localhost:8220 \\\\ + -Delastic.apm.environment=my-environment \\\\ + -Delastic.apm.application_packages=org.example \\\\ + -jar my-service-name.jar" + `); + }); + }); + + describe('Node.js agent', () => { + it('renders empty commands', () => { + const commands = getApmAgentCommands({ + variantId: 'node', + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "// Add this to the very top of the first file loaded in your app + var apm = require('elastic-apm-node').start({ + + // The service name is the primary filter in the APM UI and is used to group errors and trace data together. Allowed characters are a-z, A-Z, 0-9, -, _, and space. Overrides the service name in package.json. + serviceName: 'my-service-name', + + // Use if APM Server requires an API Key. This is used to ensure that only your agents can send data to your APM server. Agents can use API keys as a replacement of secret token, APM server can have multiple API keys. When both secret token and API key are used, API key has priority and secret token is ignored. + apiKey: '', + + // Set the custom APM Server URL (default: http://localhost:8200). The URL must be fully qualified, including protocol (http or https) and port. + serverUrl: '', + + // The name of the environment this service is deployed in, e.g., \\"production\\" or \\"staging\\". Environments allow you to easily filter data on a global level in the APM UI. It's important to be consistent when naming environments across agents. + environment: 'my-environment' + })" + `); + }); + it('renders with secret token and url', () => { + const commands = getApmAgentCommands({ + variantId: 'node', + apmServerUrl: 'localhost:8220', + secretToken: 'foobar', + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "// Add this to the very top of the first file loaded in your app + var apm = require('elastic-apm-node').start({ + + // The service name is the primary filter in the APM UI and is used to group errors and trace data together. Allowed characters are a-z, A-Z, 0-9, -, _, and space. Overrides the service name in package.json. + serviceName: 'my-service-name', + + // Use if APM Server requires a secret token. Both the agent and APM Server must be configured with the same token. This ensures that only your agents can send data to your APM server. + secretToken: 'foobar', + + // Set the custom APM Server URL (default: http://localhost:8200). The URL must be fully qualified, including protocol (http or https) and port. + serverUrl: 'localhost:8220', + + // The name of the environment this service is deployed in, e.g., \\"production\\" or \\"staging\\". Environments allow you to easily filter data on a global level in the APM UI. It's important to be consistent when naming environments across agents. + environment: 'my-environment' + })" + `); + }); + it('renders with api key even though secret token is present', () => { + const commands = getApmAgentCommands({ + variantId: 'node', + apmServerUrl: 'localhost:8220', + secretToken: 'foobar', + apiKey: 'myApiKey', + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "// Add this to the very top of the first file loaded in your app + var apm = require('elastic-apm-node').start({ + + // The service name is the primary filter in the APM UI and is used to group errors and trace data together. Allowed characters are a-z, A-Z, 0-9, -, _, and space. Overrides the service name in package.json. + serviceName: 'my-service-name', + + // Use if APM Server requires a secret token. Both the agent and APM Server must be configured with the same token. This ensures that only your agents can send data to your APM server. + secretToken: 'foobar', + + // Set the custom APM Server URL (default: http://localhost:8200). The URL must be fully qualified, including protocol (http or https) and port. + serverUrl: 'localhost:8220', + + // The name of the environment this service is deployed in, e.g., \\"production\\" or \\"staging\\". Environments allow you to easily filter data on a global level in the APM UI. It's important to be consistent when naming environments across agents. + environment: 'my-environment' + })" + `); + }); + }); + describe('Django agent', () => { + it('renders empty commands', () => { + const commands = getApmAgentCommands({ + variantId: 'django', + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "INSTALLED_APPS = ( + # Add the agent to installed apps + 'elasticapm.contrib.django', + # ... + ) + + ELASTIC_APM = { + # The service name is the primary filter in the APM UI and is used to group errors and trace data together. Allowed characters are a-z, A-Z, 0-9, -, _, and space. + 'SERVICE_NAME': 'my-service-name', + + # Use if APM Server requires an API Key. This is used to ensure that only your agents can send data to your APM server. Agents can use API keys as a replacement of secret token, APM server can have multiple API keys. When both secret token and API key are used, API key has priority and secret token is ignored. + 'API_KEY': '', + + # Set the custom APM Server URL (default: http://localhost:8200). The URL must be fully qualified, including protocol (http or https) and port. + 'SERVER_URL': '', + + # The name of the environment this service is deployed in, e.g., \\"production\\" or \\"staging\\". Environments allow you to easily filter data on a global level in the APM UI. It's important to be consistent when naming environments across agents. + 'ENVIRONMENT': 'my-environment', + } + + MIDDLEWARE = ( + # Add our tracing middleware to send performance metrics + 'elasticapm.contrib.django.middleware.TracingMiddleware', + #... + )" + `); + }); + it('renders with secret token and url', () => { + const commands = getApmAgentCommands({ + variantId: 'django', + apmServerUrl: 'localhost:8220', + secretToken: 'foobar', + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "INSTALLED_APPS = ( + # Add the agent to installed apps + 'elasticapm.contrib.django', + # ... + ) + + ELASTIC_APM = { + # The service name is the primary filter in the APM UI and is used to group errors and trace data together. Allowed characters are a-z, A-Z, 0-9, -, _, and space. + 'SERVICE_NAME': 'my-service-name', + + # Use if APM Server requires a secret token. Both the agent and APM Server must be configured with the same token. This ensures that only your agents can send data to your APM server. + 'SECRET_TOKEN': 'foobar', + + # Set the custom APM Server URL (default: http://localhost:8200). The URL must be fully qualified, including protocol (http or https) and port. + 'SERVER_URL': 'localhost:8220', + + # The name of the environment this service is deployed in, e.g., \\"production\\" or \\"staging\\". Environments allow you to easily filter data on a global level in the APM UI. It's important to be consistent when naming environments across agents. + 'ENVIRONMENT': 'my-environment', + } + + MIDDLEWARE = ( + # Add our tracing middleware to send performance metrics + 'elasticapm.contrib.django.middleware.TracingMiddleware', + #... + )" + `); + }); + it('renders with api key even though secret token is present', () => { + const commands = getApmAgentCommands({ + variantId: 'django', + apmServerUrl: 'localhost:8220', + secretToken: 'foobar', + apiKey: 'myApiKey', + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "INSTALLED_APPS = ( + # Add the agent to installed apps + 'elasticapm.contrib.django', + # ... + ) + + ELASTIC_APM = { + # The service name is the primary filter in the APM UI and is used to group errors and trace data together. Allowed characters are a-z, A-Z, 0-9, -, _, and space. + 'SERVICE_NAME': 'my-service-name', + + # Use if APM Server requires a secret token. Both the agent and APM Server must be configured with the same token. This ensures that only your agents can send data to your APM server. + 'SECRET_TOKEN': 'foobar', + + # Set the custom APM Server URL (default: http://localhost:8200). The URL must be fully qualified, including protocol (http or https) and port. + 'SERVER_URL': 'localhost:8220', + + # The name of the environment this service is deployed in, e.g., \\"production\\" or \\"staging\\". Environments allow you to easily filter data on a global level in the APM UI. It's important to be consistent when naming environments across agents. + 'ENVIRONMENT': 'my-environment', + } + + MIDDLEWARE = ( + # Add our tracing middleware to send performance metrics + 'elasticapm.contrib.django.middleware.TracingMiddleware', + #... + )" + `); + }); + }); + describe('Flask agent', () => { + it('renders empty commands', () => { + const commands = getApmAgentCommands({ + variantId: 'flask', + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "# Initialize using environment variables + from elasticapm.contrib.flask import ElasticAPM + app = Flask(__name__) + apm = ElasticAPM(app) + + # Or use ELASTIC_APM in your application's settings + from elasticapm.contrib.flask import ElasticAPM + app.config['ELASTIC_APM'] = { + # The service name is the primary filter in the APM UI and is used to group errors and trace data together. Allowed characters are a-z, A-Z, 0-9, -, _, and space. + 'SERVICE_NAME': 'my-service-name', + + # Use if APM Server requires an API Key. This is used to ensure that only your agents can send data to your APM server. Agents can use API keys as a replacement of secret token, APM server can have multiple API keys. When both secret token and API key are used, API key has priority and secret token is ignored. + 'API_KEY': '', + + # Set the custom APM Server URL (default: http://localhost:8200). The URL must be fully qualified, including protocol (http or https) and port. + 'SERVER_URL': '', + + The name of the environment this service is deployed in, e.g., \\"production\\" or \\"staging\\". Environments allow you to easily filter data on a global level in the APM UI. It's important to be consistent when naming environments across agents. + 'ENVIRONMENT': 'my-environment', + } + + apm = ElasticAPM(app)" + `); + }); + it('renders with secret token and url', () => { + const commands = getApmAgentCommands({ + variantId: 'flask', + apmServerUrl: 'localhost:8220', + secretToken: 'foobar', + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "# Initialize using environment variables + from elasticapm.contrib.flask import ElasticAPM + app = Flask(__name__) + apm = ElasticAPM(app) + + # Or use ELASTIC_APM in your application's settings + from elasticapm.contrib.flask import ElasticAPM + app.config['ELASTIC_APM'] = { + # The service name is the primary filter in the APM UI and is used to group errors and trace data together. Allowed characters are a-z, A-Z, 0-9, -, _, and space. + 'SERVICE_NAME': 'my-service-name', + + # Use if APM Server requires a secret token. Both the agent and APM Server must be configured with the same token. This ensures that only your agents can send data to your APM server. + 'SECRET_TOKEN': 'foobar', + + # Set the custom APM Server URL (default: http://localhost:8200). The URL must be fully qualified, including protocol (http or https) and port. + 'SERVER_URL': 'localhost:8220', + + The name of the environment this service is deployed in, e.g., \\"production\\" or \\"staging\\". Environments allow you to easily filter data on a global level in the APM UI. It's important to be consistent when naming environments across agents. + 'ENVIRONMENT': 'my-environment', + } + + apm = ElasticAPM(app)" + `); + }); + it('renders with api key even though secret token is present', () => { + const commands = getApmAgentCommands({ + variantId: 'flask', + apmServerUrl: 'localhost:8220', + secretToken: 'foobar', + apiKey: 'myApiKey', + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "# Initialize using environment variables + from elasticapm.contrib.flask import ElasticAPM + app = Flask(__name__) + apm = ElasticAPM(app) + + # Or use ELASTIC_APM in your application's settings + from elasticapm.contrib.flask import ElasticAPM + app.config['ELASTIC_APM'] = { + # The service name is the primary filter in the APM UI and is used to group errors and trace data together. Allowed characters are a-z, A-Z, 0-9, -, _, and space. + 'SERVICE_NAME': 'my-service-name', + + # Use if APM Server requires a secret token. Both the agent and APM Server must be configured with the same token. This ensures that only your agents can send data to your APM server. + 'SECRET_TOKEN': 'foobar', + + # Set the custom APM Server URL (default: http://localhost:8200). The URL must be fully qualified, including protocol (http or https) and port. + 'SERVER_URL': 'localhost:8220', + + The name of the environment this service is deployed in, e.g., \\"production\\" or \\"staging\\". Environments allow you to easily filter data on a global level in the APM UI. It's important to be consistent when naming environments across agents. + 'ENVIRONMENT': 'my-environment', + } + + apm = ElasticAPM(app)" + `); + }); + }); + describe('Ruby on Rails agent', () => { + it('renders empty commands', () => { + const commands = getApmAgentCommands({ + variantId: 'rails', + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "# config/elastic_apm.yml: + + # The service name is the primary filter in the APM UI and is used to group errors and trace data together. Allowed characters are a-z, A-Z, 0-9, -, _, and space. Defaults to the name of your Rails app. + service_name: 'my-service-name' + + # Use if APM Server requires an API Key. This is used to ensure that only your agents can send data to your APM server. Agents can use API keys as a replacement of secret token, APM server can have multiple API keys. When both secret token and API key are used, API key has priority and secret token is ignored. + api_key: '' + + # Set the custom APM Server URL (default: http://localhost:8200). The URL must be fully qualified, including protocol (http or https) and port. + server_url: '' + + # The name of the environment this service is deployed in, e.g., \\"production\\" or \\"staging\\". Environments allow you to easily filter data on a global level in the APM UI. It's important to be consistent when naming environments across agents. + environment: 'my-environment'" + `); + }); + it('renders with secret token and url', () => { + const commands = getApmAgentCommands({ + variantId: 'rails', + apmServerUrl: 'localhost:8220', + secretToken: 'foobar', + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "# config/elastic_apm.yml: + + # The service name is the primary filter in the APM UI and is used to group errors and trace data together. Allowed characters are a-z, A-Z, 0-9, -, _, and space. Defaults to the name of your Rails app. + service_name: 'my-service-name' + + # Use if APM Server requires a secret token. Both the agent and APM Server must be configured with the same token. This ensures that only your agents can send data to your APM server. + secret_token: 'foobar' + + # Set the custom APM Server URL (default: http://localhost:8200). The URL must be fully qualified, including protocol (http or https) and port. + server_url: 'localhost:8220' + + # The name of the environment this service is deployed in, e.g., \\"production\\" or \\"staging\\". Environments allow you to easily filter data on a global level in the APM UI. It's important to be consistent when naming environments across agents. + environment: 'my-environment'" + `); + }); + it('renders with api key even though secret token is present', () => { + const commands = getApmAgentCommands({ + variantId: 'rails', + apmServerUrl: 'localhost:8220', + secretToken: 'foobar', + apiKey: 'myApiKey', + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "# config/elastic_apm.yml: + + # The service name is the primary filter in the APM UI and is used to group errors and trace data together. Allowed characters are a-z, A-Z, 0-9, -, _, and space. Defaults to the name of your Rails app. + service_name: 'my-service-name' + + # Use if APM Server requires a secret token. Both the agent and APM Server must be configured with the same token. This ensures that only your agents can send data to your APM server. + secret_token: 'foobar' + + # Set the custom APM Server URL (default: http://localhost:8200). The URL must be fully qualified, including protocol (http or https) and port. + server_url: 'localhost:8220' + + # The name of the environment this service is deployed in, e.g., \\"production\\" or \\"staging\\". Environments allow you to easily filter data on a global level in the APM UI. It's important to be consistent when naming environments across agents. + environment: 'my-environment'" + `); + }); + }); + describe('Rack agent', () => { + it('renders empty commands', () => { + const commands = getApmAgentCommands({ + variantId: 'rack', + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "# config/elastic_apm.yml: + + # The service name is the primary filter in the APM UI and is used to group errors and trace data together. Allowed characters are a-z, A-Z, 0-9, -, _, and space. Defaults to the name of your Rack app's class. + service_name: 'my-service-name' + + # Use if APM Server requires an API Key. This is used to ensure that only your agents can send data to your APM server. Agents can use API keys as a replacement of secret token, APM server can have multiple API keys. When both secret token and API key are used, API key has priority and secret token is ignored. + api_key: '' + + # Set the custom APM Server URL (default: http://localhost:8200). The URL must be fully qualified, including protocol (http or https) and port. + server_url: '' + + # The name of the environment this service is deployed in, e.g., \\"production\\" or \\"staging\\". Environments allow you to easily filter data on a global level in the APM UI. It's important to be consistent when naming environments across agents. + environment: 'my-environment'" + `); + }); + it('renders with secret token and url', () => { + const commands = getApmAgentCommands({ + variantId: 'rack', + apmServerUrl: 'localhost:8220', + secretToken: 'foobar', + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "# config/elastic_apm.yml: + + # The service name is the primary filter in the APM UI and is used to group errors and trace data together. Allowed characters are a-z, A-Z, 0-9, -, _, and space. Defaults to the name of your Rack app's class. + service_name: 'my-service-name' + + # Use if APM Server requires a secret token. Both the agent and APM Server must be configured with the same token. This ensures that only your agents can send data to your APM server. + secret_token: 'foobar' + + # Set the custom APM Server URL (default: http://localhost:8200). The URL must be fully qualified, including protocol (http or https) and port. + server_url: 'localhost:8220' + + # The name of the environment this service is deployed in, e.g., \\"production\\" or \\"staging\\". Environments allow you to easily filter data on a global level in the APM UI. It's important to be consistent when naming environments across agents. + environment: 'my-environment'" + `); + }); + it('renders with api key even though secret token is present', () => { + const commands = getApmAgentCommands({ + variantId: 'rack', + apmServerUrl: 'localhost:8220', + secretToken: 'foobar', + apiKey: 'myApiKey', + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "# config/elastic_apm.yml: + + # The service name is the primary filter in the APM UI and is used to group errors and trace data together. Allowed characters are a-z, A-Z, 0-9, -, _, and space. Defaults to the name of your Rack app's class. + service_name: 'my-service-name' + + # Use if APM Server requires a secret token. Both the agent and APM Server must be configured with the same token. This ensures that only your agents can send data to your APM server. + secret_token: 'foobar' + + # Set the custom APM Server URL (default: http://localhost:8200). The URL must be fully qualified, including protocol (http or https) and port. + server_url: 'localhost:8220' + + # The name of the environment this service is deployed in, e.g., \\"production\\" or \\"staging\\". Environments allow you to easily filter data on a global level in the APM UI. It's important to be consistent when naming environments across agents. + environment: 'my-environment'" + `); + }); + }); + describe('Go agent', () => { + it('renders empty commands', () => { + const commands = getApmAgentCommands({ + variantId: 'go', + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "# Initialize using environment variables: + + # The service name is the primary filter in the APM UI and is used to group errors and trace data together. Allowed characters are a-z, A-Z, 0-9, -, _, and space. If not specified, the executable name will be used. + export ELASTIC_APM_SERVICE_NAME=my-service-name + + # Use if APM Server requires an API Key. This is used to ensure that only your agents can send data to your APM server. Agents can use API keys as a replacement of secret token, APM server can have multiple API keys. When both secret token and API key are used, API key has priority and secret token is ignored. + export ELASTIC_APM_API_KEY= + + # Set the custom APM Server URL (default: http://localhost:8200). The URL must be fully qualified, including protocol (http or https) and port. + export ELASTIC_APM_SERVER_URL= + + # The name of the environment this service is deployed in, e.g., \\"production\\" or \\"staging\\". Environments allow you to easily filter data on a global level in the APM UI. It's important to be consistent when naming environments across agents. + export ELASTIC_APM_ENVIRONMENT=my-environment + " + `); + }); + it('renders with secret token and url', () => { + const commands = getApmAgentCommands({ + variantId: 'go', + apmServerUrl: 'localhost:8220', + secretToken: 'foobar', + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "# Initialize using environment variables: + + # The service name is the primary filter in the APM UI and is used to group errors and trace data together. Allowed characters are a-z, A-Z, 0-9, -, _, and space. If not specified, the executable name will be used. + export ELASTIC_APM_SERVICE_NAME=my-service-name + + # Use if APM Server requires a secret token. Both the agent and APM Server must be configured with the same token. This ensures that only your agents can send data to your APM server. + export ELASTIC_APM_SECRET_TOKEN=foobar + + # Set the custom APM Server URL (default: http://localhost:8200). The URL must be fully qualified, including protocol (http or https) and port. + export ELASTIC_APM_SERVER_URL=localhost:8220 + + # The name of the environment this service is deployed in, e.g., \\"production\\" or \\"staging\\". Environments allow you to easily filter data on a global level in the APM UI. It's important to be consistent when naming environments across agents. + export ELASTIC_APM_ENVIRONMENT=my-environment + " + `); + }); + it('renders with api key even though secret token is present', () => { + const commands = getApmAgentCommands({ + variantId: 'go', + apmServerUrl: 'localhost:8220', + secretToken: 'foobar', + apiKey: 'myApiKey', + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "# Initialize using environment variables: + + # The service name is the primary filter in the APM UI and is used to group errors and trace data together. Allowed characters are a-z, A-Z, 0-9, -, _, and space. If not specified, the executable name will be used. + export ELASTIC_APM_SERVICE_NAME=my-service-name + + # Use if APM Server requires a secret token. Both the agent and APM Server must be configured with the same token. This ensures that only your agents can send data to your APM server. + export ELASTIC_APM_SECRET_TOKEN=foobar + + # Set the custom APM Server URL (default: http://localhost:8200). The URL must be fully qualified, including protocol (http or https) and port. + export ELASTIC_APM_SERVER_URL=localhost:8220 + + # The name of the environment this service is deployed in, e.g., \\"production\\" or \\"staging\\". Environments allow you to easily filter data on a global level in the APM UI. It's important to be consistent when naming environments across agents. + export ELASTIC_APM_ENVIRONMENT=my-environment + " + `); + }); + }); + describe('DotNet agent', () => { + it('renders empty commands', () => { + const commands = getApmAgentCommands({ + variantId: 'dotnet', + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "{ + \\"ElasticApm\\": { + /// The service name is the primary filter in the APM UI and is used to group errors and trace data together. Allowed characters are a-z, A-Z, 0-9, -, _, and space. Default is the entry assembly of the application. + \\"ServiceName\\": \\"my-service-name\\", + /// Use if APM Server requires an API Key. This is used to ensure that only your agents can send data to your APM server. Agents can use API keys as a replacement of secret token, APM server can have multiple API keys. When both secret token and API key are used, API key has priority and secret token is ignored. + \\"ApiKey\\": \\"\\", + /// Set the custom APM Server URL (default: http://localhost:8200). The URL must be fully qualified, including protocol (http or https) and port. + \\"ServerUrl\\": \\"\\", + /// The name of the environment this service is deployed in, e.g., \\"production\\" or \\"staging\\". Environments allow you to easily filter data on a global level in the APM UI. It's important to be consistent when naming environments across agents. + \\"Environment\\": \\"my-environment\\", + } + }" + `); + }); + it('renders with secret token and url', () => { + const commands = getApmAgentCommands({ + variantId: 'dotnet', + apmServerUrl: 'localhost:8220', + secretToken: 'foobar', + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "{ + \\"ElasticApm\\": { + /// The service name is the primary filter in the APM UI and is used to group errors and trace data together. Allowed characters are a-z, A-Z, 0-9, -, _, and space. Default is the entry assembly of the application. + \\"ServiceName\\": \\"my-service-name\\", + /// Use if APM Server requires a secret token. Both the agent and APM Server must be configured with the same token. This ensures that only your agents can send data to your APM server. + \\"SecretToken\\": \\"foobar\\", + /// Set the custom APM Server URL (default: http://localhost:8200). The URL must be fully qualified, including protocol (http or https) and port. + \\"ServerUrl\\": \\"localhost:8220\\", + /// The name of the environment this service is deployed in, e.g., \\"production\\" or \\"staging\\". Environments allow you to easily filter data on a global level in the APM UI. It's important to be consistent when naming environments across agents. + \\"Environment\\": \\"my-environment\\", + } + }" + `); + }); + it('renders with api key even though secret token is present', () => { + const commands = getApmAgentCommands({ + variantId: 'dotnet', + apmServerUrl: 'localhost:8220', + secretToken: 'foobar', + apiKey: 'myApiKey', + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "{ + \\"ElasticApm\\": { + /// The service name is the primary filter in the APM UI and is used to group errors and trace data together. Allowed characters are a-z, A-Z, 0-9, -, _, and space. Default is the entry assembly of the application. + \\"ServiceName\\": \\"my-service-name\\", + /// Use if APM Server requires a secret token. Both the agent and APM Server must be configured with the same token. This ensures that only your agents can send data to your APM server. + \\"SecretToken\\": \\"foobar\\", + /// Set the custom APM Server URL (default: http://localhost:8200). The URL must be fully qualified, including protocol (http or https) and port. + \\"ServerUrl\\": \\"localhost:8220\\", + /// The name of the environment this service is deployed in, e.g., \\"production\\" or \\"staging\\". Environments allow you to easily filter data on a global level in the APM UI. It's important to be consistent when naming environments across agents. + \\"Environment\\": \\"my-environment\\", + } + }" + `); + }); + }); + describe('PHP agent', () => { + it('renders empty commands', () => { + const commands = getApmAgentCommands({ + variantId: 'php', + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "# The service name is the primary filter in the APM UI and is used to group errors and trace data together. Allowed characters are a-z, A-Z, 0-9, -, _, and space. + elastic_apm.service_name=\\"my-service-name\\" + + # Use if APM Server requires an API Key. This is used to ensure that only your agents can send data to your APM server. Agents can use API keys as a replacement of secret token, APM server can have multiple API keys. When both secret token and API key are used, API key has priority and secret token is ignored. + elastic_apm.api_key=\\"\\" + + # Set the custom APM Server URL (default: http://localhost:8200). The URL must be fully qualified, including protocol (http or https) and port. + elastic_apm.server_url=\\"\\" + + # The name of the environment this service is deployed in, e.g., \\"production\\" or \\"staging\\". Environments allow you to easily filter data on a global level in the APM UI. It's important to be consistent when naming environments across agents. + elastic_apm.environment=\\"my-environment\\"" + `); + }); + it('renders with secret token and url', () => { + const commands = getApmAgentCommands({ + variantId: 'php', + apmServerUrl: 'localhost:8220', + secretToken: 'foobar', + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "# The service name is the primary filter in the APM UI and is used to group errors and trace data together. Allowed characters are a-z, A-Z, 0-9, -, _, and space. + elastic_apm.service_name=\\"my-service-name\\" + + # Use if APM Server requires a secret token. Both the agent and APM Server must be configured with the same token. This ensures that only your agents can send data to your APM server. + elastic_apm.secret_token=\\"foobar\\" + + # Set the custom APM Server URL (default: http://localhost:8200). The URL must be fully qualified, including protocol (http or https) and port. + elastic_apm.server_url=\\"localhost:8220\\" + + # The name of the environment this service is deployed in, e.g., \\"production\\" or \\"staging\\". Environments allow you to easily filter data on a global level in the APM UI. It's important to be consistent when naming environments across agents. + elastic_apm.environment=\\"my-environment\\"" + `); + }); + it('renders with api key even though secret token is present', () => { + const commands = getApmAgentCommands({ + variantId: 'php', + apmServerUrl: 'localhost:8220', + secretToken: 'foobar', + apiKey: 'myApiKey', + }); + expect(commands).not.toBe(''); + expect(commands).toMatchInlineSnapshot(` + "# The service name is the primary filter in the APM UI and is used to group errors and trace data together. Allowed characters are a-z, A-Z, 0-9, -, _, and space. + elastic_apm.service_name=\\"my-service-name\\" + + # Use if APM Server requires a secret token. Both the agent and APM Server must be configured with the same token. This ensures that only your agents can send data to your APM server. + elastic_apm.secret_token=\\"foobar\\" + + # Set the custom APM Server URL (default: http://localhost:8200). The URL must be fully qualified, including protocol (http or https) and port. + elastic_apm.server_url=\\"localhost:8220\\" + + # The name of the environment this service is deployed in, e.g., \\"production\\" or \\"staging\\". Environments allow you to easily filter data on a global level in the APM UI. It's important to be consistent when naming environments across agents. + elastic_apm.environment=\\"my-environment\\"" + `); + }); + }); +}); diff --git a/x-pack/plugins/apm/public/components/app/onboarding/commands/get_apm_agent_commands.ts b/x-pack/plugins/apm/public/components/app/onboarding/commands/get_apm_agent_commands.ts new file mode 100644 index 0000000000000..e2eb92e196cce --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/onboarding/commands/get_apm_agent_commands.ts @@ -0,0 +1,161 @@ +/* + * 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 Mustache from 'mustache'; +import { + java, + javaVariables, + javaLineNumbers, + javaHighlightLang, +} from './java'; +import { + node, + nodeVariables, + nodeLineNumbers, + nodeHighlightLang, +} from './node'; +import { + django, + djangoVariables, + djangoLineNumbers, + djangoHighlightLang, +} from './django'; +import { + flask, + flaskVariables, + flaskLineNumbers, + flaskHighlightLang, +} from './flask'; +import { + rails, + railsVariables, + railsLineNumbers, + railsHighlightLang, +} from './rails'; +import { + rack, + rackVariables, + rackLineNumbers, + rackHighlightLang, +} from './rack'; +import { go, goVariables, goLineNumbers, goHighlightLang } from './go'; +import { + dotnet, + dotnetVariables, + dotnetLineNumbers, + dotnetHighlightLang, +} from './dotnet'; +import { php, phpVariables, phpLineNumbers, phpHighlightLang } from './php'; +import { + serviceNameHint, + serviceEnvironmentHint, + serverUrlHint, + secretTokenHint, + apiKeyHint, +} from './shared_hints'; + +const apmAgentCommandsMap: Record = { + java, + node, + django, + flask, + rails, + rack, + go, + dotnet, + php, +}; + +interface Variables { + [key: string]: string; +} + +const apmAgentVariablesMap: ( + secretToken?: string +) => Record = (secretToken?: string) => ({ + java: javaVariables(secretToken), + node: nodeVariables(secretToken), + django: djangoVariables(secretToken), + flask: flaskVariables(secretToken), + rails: railsVariables(secretToken), + rack: rackVariables(secretToken), + go: goVariables(secretToken), + dotnet: dotnetVariables(secretToken), + php: phpVariables(secretToken), +}); + +interface LineNumbers { + [key: string]: string | number | object; +} + +const apmAgentLineNumbersMap: ( + apiKey?: string | null +) => Record = (apiKey?: string | null) => ({ + java: javaLineNumbers(apiKey), + node: nodeLineNumbers(), + django: djangoLineNumbers(), + flask: flaskLineNumbers(), + rails: railsLineNumbers(), + rack: rackLineNumbers(), + go: goLineNumbers(), + dotnet: dotnetLineNumbers(), + php: phpLineNumbers(), +}); + +const apmAgentHighlightLangMap: Record = { + java: javaHighlightLang, + node: nodeHighlightLang, + django: djangoHighlightLang, + flask: flaskHighlightLang, + rails: railsHighlightLang, + rack: rackHighlightLang, + go: goHighlightLang, + dotnet: dotnetHighlightLang, + php: phpHighlightLang, +}; + +export function getApmAgentCommands({ + variantId, + apmServerUrl, + secretToken, + apiKey, +}: { + variantId: string; + apmServerUrl?: string; + secretToken?: string; + apiKey?: string | null; +}) { + const commands = apmAgentCommandsMap[variantId]; + if (!commands) { + return ''; + } + + return Mustache.render(commands, { + apmServerUrl, + secretToken, + apiKey, + serviceNameHint, + serviceEnvironmentHint, + serverUrlHint, + secretTokenHint, + apiKeyHint, + }); +} + +export function getApmAgentVariables(variantId: string, secretToken?: string) { + return apmAgentVariablesMap(secretToken)[variantId]; +} + +export function getApmAgentLineNumbers( + variantId: string, + apiKey?: string | null +) { + return apmAgentLineNumbersMap(apiKey)[variantId]; +} + +export function getApmAgentHighlightLang(variantId: string) { + return apmAgentHighlightLangMap[variantId]; +} diff --git a/x-pack/plugins/apm/public/components/app/onboarding/commands/go.ts b/x-pack/plugins/apm/public/components/app/onboarding/commands/go.ts new file mode 100644 index 0000000000000..c95cb30606c85 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/onboarding/commands/go.ts @@ -0,0 +1,54 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const goVariables = (secretToken?: string) => ({ + apmServiceName: 'ELASTIC_APM_SERVICE_NAME', + ...(secretToken && { secretToken: 'ELASTIC_APM_SECRET_TOKEN' }), + ...(!secretToken && { apiKey: 'ELASTIC_APM_API_KEY' }), + apmServerUrl: 'ELASTIC_APM_SERVER_URL', + apmEnvironment: 'ELASTIC_APM_ENVIRONMENT', +}); + +export const goHighlightLang = 'go'; + +export const goLineNumbers = () => ({ + start: 1, + highlight: '4, 7, 10, 13', +}); + +export const go = `# ${i18n.translate( + 'xpack.apm.onboarding.goClient.configure.commands.initializeUsingEnvironmentVariablesComment', + { + defaultMessage: 'Initialize using environment variables:', + } +)} + +# {{serviceNameHint}} ${i18n.translate( + 'xpack.apm.onboarding.goClient.configure.commands.usedExecutableNameComment', + { + defaultMessage: 'If not specified, the executable name will be used.', + } +)} +export ELASTIC_APM_SERVICE_NAME=my-service-name + +{{^secretToken}} +# {{apiKeyHint}} +export ELASTIC_APM_API_KEY={{{apiKey}}} +{{/secretToken}} +{{#secretToken}} +# {{secretTokenHint}} +export ELASTIC_APM_SECRET_TOKEN={{{secretToken}}} +{{/secretToken}} + +# {{{serverUrlHint}}} +export ELASTIC_APM_SERVER_URL={{{apmServerUrl}}} + +# {{{serviceEnvironmentHint}}} +export ELASTIC_APM_ENVIRONMENT=my-environment +`; diff --git a/x-pack/plugins/apm/public/components/app/onboarding/commands/java.ts b/x-pack/plugins/apm/public/components/app/onboarding/commands/java.ts new file mode 100644 index 0000000000000..f093b0b241af0 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/onboarding/commands/java.ts @@ -0,0 +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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + serviceNameHint, + secretTokenHint, + serverUrlHint, + serviceEnvironmentHint, + apiKeyHint, +} from './shared_hints'; + +export const javaVariables = (secretToken?: string) => ({ + apmServiceName: 'Delastic.apm.service_name', + ...(secretToken && { secretToken: 'Delastic.apm.secret_token' }), + ...(!secretToken && { apiKey: 'Delastic.apm.api_key' }), + apmServerUrl: 'Delastic.apm.server_url', + apmEnvironment: 'Delastic.apm.environment', +}); + +export const javaHighlightLang = 'java'; + +export const javaLineNumbers = (apiKey?: string | null) => ({ + start: 1, + highlight: '', + annotations: { + 2: serviceNameHint, + 3: apiKey ? apiKeyHint : secretTokenHint, + 4: serverUrlHint, + 5: serviceEnvironmentHint, + }, +}); +export const java = `java -javaagent:/path/to/elastic-apm-agent-.jar \\ +-Delastic.apm.service_name=my-service-name \\ +{{^secretToken}} +-Delastic.apm.api_key={{{apiKey}}} \\ +{{/secretToken}} +{{#secretToken}} +-Delastic.apm.secret_token={{{secretToken}}} \\ +{{/secretToken}} +-Delastic.apm.server_url={{{apmServerUrl}}} \\ +-Delastic.apm.environment=my-environment \\ +-Delastic.apm.application_packages=org.example \\ +-jar my-service-name.jar`; diff --git a/x-pack/plugins/apm/public/components/app/onboarding/commands/node.ts b/x-pack/plugins/apm/public/components/app/onboarding/commands/node.ts new file mode 100644 index 0000000000000..97e2ab0b43809 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/onboarding/commands/node.ts @@ -0,0 +1,55 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const nodeVariables = (secretToken?: string) => ({ + apmServiceName: 'serviceName', + ...(secretToken && { secretToken: 'secretToken' }), + ...(!secretToken && { apiKey: 'apiKey' }), + apmServerUrl: 'serverUrl', + apmEnvironment: 'environment', +}); + +export const nodeHighlightLang = 'js'; + +export const nodeLineNumbers = () => ({ + start: 1, + highlight: '2, 5, 8, 11, 14-15', +}); + +export const node = `// ${i18n.translate( + 'xpack.apm.onboarding.nodeClient.configure.commands.addThisToTheFileTopComment', + { + defaultMessage: + 'Add this to the very top of the first file loaded in your app', + } +)} +var apm = require('elastic-apm-node').start({ + + // {{serviceNameHint}} ${i18n.translate( + 'xpack.apm.onboarding.nodeClient.createConfig.commands.serviceName', + { + defaultMessage: 'Overrides the service name in package.json.', + } + )} + serviceName: 'my-service-name', + + {{^secretToken}} + // {{apiKeyHint}} + apiKey: '{{{apiKey}}}', + {{/secretToken}} + {{#secretToken}} + // {{secretTokenHint}} + secretToken: '{{{secretToken}}}', + {{/secretToken}} + + // {{{serverUrlHint}}} + serverUrl: '{{{apmServerUrl}}}', + + // {{{serviceEnvironmentHint}}} + environment: 'my-environment' +})`; diff --git a/x-pack/plugins/apm/public/components/app/onboarding/commands/php.ts b/x-pack/plugins/apm/public/components/app/onboarding/commands/php.ts new file mode 100644 index 0000000000000..c00fea44dc998 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/onboarding/commands/php.ts @@ -0,0 +1,38 @@ +/* + * 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. + */ +export const phpVariables = (secretToken?: string) => ({ + apmServiceName: 'elastic_apm.service_name', + ...(secretToken && { secretToken: 'elastic_apm.secret_token' }), + ...(!secretToken && { apiKey: 'elastic_apm.api_key' }), + apmServerUrl: 'elastic_apm.server_url', + apmEnvironment: 'elastic_apm.environment', +}); + +export const phpHighlightLang = 'php'; + +export const phpLineNumbers = () => ({ + start: 1, + highlight: '2, 5, 8, 11', +}); + +export const php = `# {{serviceNameHint}} +elastic_apm.service_name="my-service-name" + +{{^secretToken}} +# {{apiKeyHint}} +elastic_apm.api_key="{{{apiKey}}}" +{{/secretToken}} +{{#secretToken}} +# {{secretTokenHint}} +elastic_apm.secret_token="{{{secretToken}}}" +{{/secretToken}} + +# {{serverUrlHint}} +elastic_apm.server_url="{{{apmServerUrl}}}" + +# {{{serviceEnvironmentHint}}} +elastic_apm.environment="my-environment"`; diff --git a/x-pack/plugins/apm/public/components/app/onboarding/commands/rack.ts b/x-pack/plugins/apm/public/components/app/onboarding/commands/rack.ts new file mode 100644 index 0000000000000..54d484d92c513 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/onboarding/commands/rack.ts @@ -0,0 +1,48 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const rackVariables = (secretToken?: string) => ({ + apmServiceName: 'service_name', + ...(secretToken && { secretToken: 'secret_token' }), + ...(!secretToken && { apiKey: 'api_key' }), + apmServerUrl: 'server_url', + apmEnvironment: 'environment', +}); + +export const rackHighlightLang = 'rb'; + +export const rackLineNumbers = () => ({ + start: 1, + highlight: '4, 7, 10, 13', +}); + +export const rack = `# config/elastic_apm.yml: + +# {{serviceNameHint}} ${i18n.translate( + 'xpack.apm.onboarding.rackClient.createConfig.commands.defaultsToTheNameOfRackAppClassComment', + { + defaultMessage: "Defaults to the name of your Rack app's class.", + } +)} +service_name: 'my-service-name' + +{{^secretToken}} +# {{apiKeyHint}} +api_key: '{{{apiKey}}}' +{{/secretToken}} +{{#secretToken}} +# {{secretTokenHint}} +secret_token: '{{{secretToken}}}' +{{/secretToken}} + +# {{{serverUrlHint}}} +server_url: '{{{apmServerUrl}}}' + +# {{{serviceEnvironmentHint}}} +environment: 'my-environment'`; diff --git a/x-pack/plugins/apm/public/components/app/onboarding/commands/rails.ts b/x-pack/plugins/apm/public/components/app/onboarding/commands/rails.ts new file mode 100644 index 0000000000000..9f7bb313400eb --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/onboarding/commands/rails.ts @@ -0,0 +1,48 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const railsVariables = (secretToken?: string) => ({ + apmServiceName: 'service_name', + ...(secretToken && { secretToken: 'secret_token' }), + ...(!secretToken && { apiKey: 'api_key' }), + apmServerUrl: 'server_url', + apmEnvironment: 'environment', +}); + +export const railsHighlightLang = 'rb'; + +export const railsLineNumbers = () => ({ + start: 1, + highlight: '4, 7, 10, 13', +}); + +export const rails = `# config/elastic_apm.yml: + +# {{serviceNameHint}} ${i18n.translate( + 'xpack.apm.onboarding.railsClient.createConfig.commands.defaultServiceName', + { + defaultMessage: 'Defaults to the name of your Rails app.', + } +)} +service_name: 'my-service-name' + +{{^secretToken}} +# {{apiKeyHint}} +api_key: '{{{apiKey}}}' +{{/secretToken}} +{{#secretToken}} +# {{secretTokenHint}} +secret_token: '{{{secretToken}}}' +{{/secretToken}} + +# {{{serverUrlHint}}} +server_url: '{{{apmServerUrl}}}' + +# {{{serviceEnvironmentHint}}} +environment: 'my-environment'`; diff --git a/x-pack/plugins/apm/public/components/app/onboarding/commands/shared_hints.ts b/x-pack/plugins/apm/public/components/app/onboarding/commands/shared_hints.ts new file mode 100644 index 0000000000000..bb497d7777498 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/onboarding/commands/shared_hints.ts @@ -0,0 +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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const serviceNameHint = i18n.translate( + 'xpack.apm.onboarding.shared_clients.configure.commands.serviceNameHint', + { + defaultMessage: + 'The service name is the primary filter in the APM UI and is used to group errors and trace data together. Allowed characters are a-z, A-Z, 0-9, -, _, and space.', + } +); + +export const secretTokenHint = i18n.translate( + 'xpack.apm.onboarding.shared_clients.configure.commands.secretTokenHint', + { + defaultMessage: + 'Use if APM Server requires a secret token. Both the agent and APM Server must be configured with the same token. This ensures that only your agents can send data to your APM server.', + } +); + +export const apiKeyHint = i18n.translate( + 'xpack.apm.onboarding.shared_clients.configure.commands.apiKeyHint', + { + defaultMessage: + 'Use if APM Server requires an API Key. This is used to ensure that only your agents can send data to your APM server. Agents can use API keys as a replacement of secret token, APM server can have multiple API keys. When both secret token and API key are used, API key has priority and secret token is ignored.', + } +); +export const serverUrlHint = i18n.translate( + 'xpack.apm.onboarding.shared_clients.configure.commands.serverUrlHint', + { + defaultMessage: + 'Set the custom APM Server URL (default: {defaultApmServerUrl}). The URL must be fully qualified, including protocol (http or https) and port.', + values: { defaultApmServerUrl: 'http://localhost:8200' }, + } +); + +export const serviceEnvironmentHint = i18n.translate( + 'xpack.apm.onboarding.shared_clients.configure.commands.serviceEnvironmentHint', + { + defaultMessage: `The name of the environment this service is deployed in, e.g., "production" or "staging". Environments allow you to easily filter data on a global level in the APM UI. It's important to be consistent when naming environments across agents.`, + } +); diff --git a/x-pack/plugins/apm/public/components/app/onboarding/footer.tsx b/x-pack/plugins/apm/public/components/app/onboarding/footer.tsx new file mode 100644 index 0000000000000..0886e5b5ab008 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/onboarding/footer.tsx @@ -0,0 +1,50 @@ +/* + * 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 { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiText, +} from '@elastic/eui'; +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { i18n } from '@kbn/i18n'; +import { useKibanaUrl } from '../../../hooks/use_kibana_url'; + +export function Footer() { + const apmLink = useKibanaUrl('/app/apm'); + return ( + + + + +

+ +

+
+
+ + + + {i18n.translate('xpack.apm.onboarding.footer.cta', { + defaultMessage: 'Launch APM', + })} + + +
+
+ ); +} diff --git a/x-pack/plugins/apm/public/components/app/onboarding/index.tsx b/x-pack/plugins/apm/public/components/app/onboarding/index.tsx new file mode 100644 index 0000000000000..30c065e10b349 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/onboarding/index.tsx @@ -0,0 +1,107 @@ +/* + * 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 React, { useCallback, useEffect, useState } from 'react'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { EuiSpacer } from '@elastic/eui'; +import { callApmApi } from '../../../services/rest/create_call_apm_api'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; +import { ApmPluginStartDeps } from '../../../plugin'; +import { Introduction } from './introduction'; +import { InstructionsSet } from './instructions_set'; +import { serverlessInstructions } from './serverless_instructions'; +import { Footer } from './footer'; +import { PrivilegeType } from '../../../../common/privilege_type'; +import { AgentApiKey, InstructionSet } from './instruction_variants'; + +export function Onboarding() { + const [instructions, setInstructions] = useState([]); + const [agentApiKey, setAgentApiKey] = useState({ + apiKey: null, + error: false, + }); + const [apiKeyLoading, setApiKeyLoading] = useState(false); + const { services } = useKibana(); + const { config } = useApmPluginContext(); + const { docLinks, observabilityShared } = services; + const guideLink = + docLinks?.links.kibana.guide || + 'https://www.elastic.co/guide/en/kibana/current/index.html'; + + const baseUrl = docLinks?.ELASTIC_WEBSITE_URL || 'https://www.elastic.co/'; + + const createAgentKey = useCallback(async () => { + try { + setApiKeyLoading(true); + const privileges: PrivilegeType[] = [PrivilegeType.EVENT]; + + const { agentKey } = await callApmApi( + 'POST /api/apm/agent_keys 2023-05-22', + { + signal: null, + params: { + body: { + name: `onboarding-${(Math.random() + 1) + .toString(36) + .substring(7)}`, + privileges, + }, + }, + } + ); + + setAgentApiKey({ + apiKey: agentKey.api_key, + encodedKey: agentKey.encoded, + id: agentKey.id, + error: false, + }); + } catch (error) { + setAgentApiKey({ + apiKey: null, + error: true, + errorMessage: error.body?.message || error.message, + }); + } finally { + setApiKeyLoading(false); + } + }, []); + + const instructionsExists = instructions.length > 0; + + useEffect(() => { + // Here setInstructions will be called based on the condition for serverless, cloud or onPrem + // right now we will only call the ServerlessInstruction directly + setInstructions([ + serverlessInstructions( + { + baseUrl, + config, + }, + apiKeyLoading, + agentApiKey, + createAgentKey + ), + ]); + }, [agentApiKey, baseUrl, config, createAgentKey, apiKeyLoading]); + + const ObservabilityPageTemplate = observabilityShared.navigation.PageTemplate; + return ( + + + + {instructionsExists && + instructions.map((instruction) => ( +
+ + +
+ ))} +