Skip to content

Commit

Permalink
[APM] Serverless Onboarding with Custom Tutorials (#158228)
Browse files Browse the repository at this point in the history
Closes #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 <[email protected]>
  • Loading branch information
achyutjhunjhunwala and kibanamachine authored Jun 7, 2023
1 parent 177914b commit ac2fc4c
Show file tree
Hide file tree
Showing 50 changed files with 3,637 additions and 34 deletions.
8 changes: 8 additions & 0 deletions config/serverless.oblt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 2 additions & 0 deletions test/plugin_functional/test_suites/core_plugins/rendering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)',
Expand Down
32 changes: 32 additions & 0 deletions x-pack/plugins/apm/common/tutorial/tutorials.ts
Original file line number Diff line number Diff line change
@@ -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<CustomIntegration, 'type'> = {
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,
};
1 change: 1 addition & 0 deletions x-pack/plugins/apm/kibana.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"spaces",
"taskManager",
"usageCollection",
"customIntegrations", // Move this to requiredPlugins after completely migrating from the Tutorials Home App
"licenseManagement"
],
"requiredBundles": [
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<>
<EuiSpacer />
<AgentConfigurationTable
variables={variables}
data={{ apmServerUrl, secretToken, apiKey }}
createApiKey={createApiKey}
createApiKeyLoading={createApiKeyLoading}
/>
<EuiSpacer />

<EuiCodeBlock
isCopyable
language={highlightLang || 'bash'}
data-test-subj="commands"
lineNumbers={lineNumbers}
whiteSpace="pre"
>
{commands}
</EuiCodeBlock>
</>
);
}
Original file line number Diff line number Diff line change
@@ -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 (
<EuiButton
data-test-subj="createApiKeyAndId"
fill
onClick={createApiKey}
isLoading={createApiKeyLoading}
>
{i18n.translate('xpack.apm.tutorial.apiKey.create', {
defaultMessage: 'Create API Key',
})}
</EuiButton>
);
}

return (
<EuiText size="s" color="accent">
{value}
</EuiText>
);
}

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<EuiBasicTableColumn<ValuesType<typeof items>>> = [
{
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 }) => (
<ConfigurationValueColumn
columnKey={key}
value={value}
createApiKey={createApiKey}
createApiKeyLoading={createApiKeyLoading}
/>
),
},
];

const items = Object.entries(variables).map(([key, value]) => ({
setting: value,
value: get({ ...data, ...defaultValues }, key),
key,
}));
return <EuiBasicTable items={items} columns={columns} />;
}
Original file line number Diff line number Diff line change
@@ -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',
#...
)`;
Original file line number Diff line number Diff line change
@@ -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",
}
}`;
Loading

0 comments on commit ac2fc4c

Please sign in to comment.