[8.18] [ObsUX] [APM] [OTel] Runtime metrics show dashboards with different ingest path (#211822) (#213534)

# Backport

This will backport the following commits from `main` to `8.18`:
- [[ObsUX] [APM] [OTel] Runtime metrics show dashboards with different
ingest path (#211822)](https://github.com/elastic/kibana/pull/211822)

<!--- Backport version: 9.6.6 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sorenlouv/backport)
This commit is contained in:
jennypavlova 2025-03-12 13:29:36 +01:00 committed by GitHub
parent a4f203f821
commit 836a2536c1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 587 additions and 60 deletions

View file

@ -32,6 +32,10 @@ export {
AGENT_NAMES,
} from './src/agent_names';
export { getIngestionPath } from './src/agent_ingestion_path';
export { getSdkNameAndLanguage } from './src/agent_sdk_name_and_language';
export type {
ElasticAgentName,
OpenTelemetryAgentName,

View file

@ -13,6 +13,7 @@ import {
isAndroidAgentName,
isAWSLambdaAgentName,
isAzureFunctionsAgentName,
isElasticAgentName,
isIosAgentName,
isJavaAgentName,
isJRubyAgentName,
@ -44,6 +45,17 @@ describe('Agents guards', () => {
expect(isOpenTelemetryAgentName('not-an-agent')).toBe(false);
});
it('isElasticAgentName should guard if the passed agent is an APM agent one.', () => {
expect(isElasticAgentName('nodejs')).toBe(true);
expect(isElasticAgentName('iOS/swift')).toBe(true);
expect(isElasticAgentName('java')).toBe(true);
expect(isElasticAgentName('rum-js')).toBe(true);
expect(isElasticAgentName('android/java')).toBe(true);
expect(isElasticAgentName('node-js')).toBe(false);
expect(isElasticAgentName('opentelemetry/nodejs/elastic')).toBe(false);
expect(isElasticAgentName('not-an-agent')).toBe(false);
});
it('isJavaAgentName should guard if the passed agent is an Java one.', () => {
expect(isJavaAgentName('java')).toBe(true);
expect(isJavaAgentName('otlp/java')).toBe(true);

View file

@ -9,6 +9,7 @@
import {
ANDROID_AGENT_NAMES,
ELASTIC_AGENT_NAMES,
IOS_AGENT_NAMES,
JAVA_AGENT_NAMES,
OPEN_TELEMETRY_AGENT_NAMES,
@ -17,6 +18,7 @@ import {
import type {
AndroidAgentName,
ElasticAgentName,
IOSAgentName,
JavaAgentName,
OpenTelemetryAgentName,
@ -24,6 +26,8 @@ import type {
ServerlessType,
} from './agent_names';
const ElasticAgentNamesSet = new Set(ELASTIC_AGENT_NAMES);
export function getAgentName(
agentName: string | null,
telemetryAgentName: string | null,
@ -57,6 +61,9 @@ export function isOpenTelemetryAgentName(agentName: string): agentName is OpenTe
);
}
export const isElasticAgentName = (agentName: string): agentName is ElasticAgentName =>
ElasticAgentNamesSet.has(agentName as ElasticAgentName);
export function isJavaAgentName(agentName?: string): agentName is JavaAgentName {
return (
hasOpenTelemetryPrefix(agentName, 'java') ||

View file

@ -0,0 +1,11 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
export const getIngestionPath = (hasOpenTelemetryFields: boolean) =>
hasOpenTelemetryFields ? 'otel_native' : 'classic_apm';

View file

@ -0,0 +1,61 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { getSdkNameAndLanguage } from './agent_sdk_name_and_language';
describe('getSdkNameAndLanguage', () => {
it.each([
{
agentName: 'java',
result: { sdkName: 'apm', language: 'java' },
},
{
agentName: 'iOS/swift',
result: { sdkName: 'apm', language: 'iOS/swift' },
},
{
agentName: 'android/java',
result: { sdkName: 'apm', language: 'android/java' },
},
{
agentName: 'opentelemetry/java/test/elastic',
result: { sdkName: 'edot', language: 'java' },
},
{
agentName: 'opentelemetry/java/elastic',
result: { sdkName: 'edot', language: 'java' },
},
{
agentName: 'otlp/nodejs',
result: { sdkName: 'otel_other', language: 'nodejs' },
},
{
agentName: 'otlp',
result: { sdkName: 'otel_other', language: undefined },
},
{
agentName: 'test/test/test/something-else/elastic',
result: { sdkName: undefined, language: undefined },
},
{
agentName: 'test/java/test/something-else/',
result: { sdkName: undefined, language: undefined },
},
{
agentName: 'elastic',
result: { sdkName: undefined, language: undefined },
},
{
agentName: 'my-awesome-agent/otel',
result: { sdkName: undefined, language: undefined },
},
])('for the agent name $agentName returns $result', ({ agentName, result }) => {
expect(getSdkNameAndLanguage(agentName)).toStrictEqual(result);
});
});

View file

@ -0,0 +1,36 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { isElasticAgentName, isOpenTelemetryAgentName } from './agent_guards';
interface SdkNameAndLanguage {
sdkName?: 'apm' | 'edot' | 'otel_other';
language?: string;
}
const LANGUAGE_INDEX = 1;
export const getSdkNameAndLanguage = (agentName: string): SdkNameAndLanguage => {
if (isElasticAgentName(agentName)) {
return { sdkName: 'apm', language: agentName };
}
const agentNameParts = agentName.split('/');
if (isOpenTelemetryAgentName(agentName)) {
if (agentNameParts[agentNameParts.length - 1] === 'elastic') {
return { sdkName: 'edot', language: agentNameParts[LANGUAGE_INDEX] };
}
return {
sdkName: 'otel_other',
language: agentNameParts[LANGUAGE_INDEX],
};
}
return { sdkName: undefined, language: undefined };
};

View file

@ -0,0 +1,155 @@
/*
* 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 type { CoreStart } from '@kbn/core/public';
import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public';
import { render } from '@testing-library/react';
import { createMemoryHistory } from 'history';
import React from 'react';
import type { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context';
import {
MockApmPluginContextWrapper,
mockApmPluginContextValue,
} from '../../../context/apm_plugin/mock_apm_plugin_context';
import * as useApmServiceContext from '../../../context/apm_service/use_apm_service_context';
import type { ServiceEntitySummary } from '../../../context/apm_service/use_service_entity_summary_fetcher';
import * as useApmDataViewHook from '../../../hooks/use_adhoc_apm_data_view';
import { FETCH_STATUS } from '../../../hooks/use_fetcher';
import { fromQuery } from '../../shared/links/url_helpers';
import { Metrics } from '.';
import type { DataView } from '@kbn/data-views-plugin/common';
const KibanaReactContext = createKibanaReactContext({
settings: { client: { get: () => {} } },
} as unknown as Partial<CoreStart>);
function MetricsWithWrapper() {
jest
.spyOn(useApmDataViewHook, 'useAdHocApmDataView')
.mockReturnValue({ dataView: { id: 'id-1', name: 'apm-data-view' } as DataView });
const history = createMemoryHistory();
history.replace({
pathname: '/services/testServiceName/metrics',
search: fromQuery({
rangeFrom: 'now-15m',
rangeTo: 'now',
}),
});
return (
<KibanaReactContext.Provider>
<MockApmPluginContextWrapper
history={history}
value={mockApmPluginContextValue as unknown as ApmPluginContextValue}
>
<Metrics />
</MockApmPluginContextWrapper>
</KibanaReactContext.Provider>
);
}
describe('Metrics', () => {
describe('render the correct metrics content for', () => {
describe('APM agent / server service', () => {
beforeEach(() => {
jest.spyOn(useApmServiceContext, 'useApmServiceContext').mockReturnValue({
agentName: 'java',
serviceName: 'testServiceName',
transactionTypeStatus: FETCH_STATUS.SUCCESS,
transactionTypes: [],
fallbackToTransactions: true,
serviceAgentStatus: FETCH_STATUS.SUCCESS,
serviceEntitySummaryStatus: FETCH_STATUS.SUCCESS,
serviceEntitySummary: {
dataStreamTypes: ['metrics'],
} as unknown as ServiceEntitySummary,
});
});
it('shows java dashboard content', () => {
const result = render(<MetricsWithWrapper />);
// Check that the other content is not rendered as we don't have test id in the dashboard rendering component
const loadingBar = result.queryByRole('progressbar');
expect(loadingBar).toBeNull();
expect(result.queryByTestId('apmMetricsNoDashboardFound')).toBeNull();
expect(result.queryByTestId('apmAddApmCallout')).toBeNull();
});
});
describe('APM agent / EDOT sdk with dashboard', () => {
beforeEach(() => {
jest.spyOn(useApmServiceContext, 'useApmServiceContext').mockReturnValue({
agentName: 'opentelemetry/nodejs/elastic',
serviceName: 'testServiceName',
transactionTypeStatus: FETCH_STATUS.SUCCESS,
transactionTypes: [],
fallbackToTransactions: true,
serviceAgentStatus: FETCH_STATUS.SUCCESS,
serviceEntitySummaryStatus: FETCH_STATUS.SUCCESS,
serviceEntitySummary: {
dataStreamTypes: ['metrics'],
} as unknown as ServiceEntitySummary,
});
});
it('shows nodejs dashboard content', () => {
const result = render(<MetricsWithWrapper />);
// Check that the other content is not rendered as we don't have test id in the dashboard rendering component
const loadingBar = result.queryByRole('progressbar');
expect(loadingBar).toBeNull();
expect(result.queryByTestId('apmMetricsNoDashboardFound')).toBeNull();
expect(result.queryByTestId('apmAddApmCallout')).toBeNull();
});
});
describe('APM agent / otel sdk with no dashboard', () => {
beforeEach(() => {
jest.spyOn(useApmServiceContext, 'useApmServiceContext').mockReturnValue({
agentName: 'opentelemetry/go',
serviceName: 'testServiceName',
transactionTypeStatus: FETCH_STATUS.SUCCESS,
transactionTypes: [],
fallbackToTransactions: true,
serviceAgentStatus: FETCH_STATUS.SUCCESS,
serviceEntitySummaryStatus: FETCH_STATUS.SUCCESS,
serviceEntitySummary: {
dataStreamTypes: ['metrics'],
} as unknown as ServiceEntitySummary,
});
});
it('shows "no dashboard found" message', () => {
const result = render(<MetricsWithWrapper />);
const apmMetricsNoDashboardFound = result.getByTestId('apmMetricsNoDashboardFound');
expect(apmMetricsNoDashboardFound).toBeInTheDocument();
});
});
describe('Logs signals', () => {
beforeEach(() => {
jest.spyOn(useApmServiceContext, 'useApmServiceContext').mockReturnValue({
agentName: 'java',
serviceName: 'testServiceName',
transactionTypeStatus: FETCH_STATUS.SUCCESS,
transactionTypes: [],
fallbackToTransactions: true,
serviceAgentStatus: FETCH_STATUS.SUCCESS,
serviceEntitySummaryStatus: FETCH_STATUS.SUCCESS,
serviceEntitySummary: {
dataStreamTypes: ['logs'],
} as unknown as ServiceEntitySummary,
});
});
it('shows service from logs metrics content', () => {
const result = render(<MetricsWithWrapper />);
const apmAddApmCallout = result.getByTestId('apmAddApmCallout');
expect(apmAddApmCallout).toBeInTheDocument();
});
});
});
});

View file

@ -6,27 +6,33 @@
*/
import React from 'react';
import {
isJavaAgentName,
isJRubyAgentName,
isAWSLambdaAgentName,
} from '../../../../common/agent_name';
import { isElasticAgentName, isJRubyAgentName } from '@kbn/elastic-agent-utils/src/agent_guards';
import { EuiCallOut } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { isAWSLambdaAgentName } from '../../../../common/agent_name';
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
import { ServerlessMetrics } from './serverless_metrics';
import { ServiceMetrics } from './service_metrics';
import { JvmMetricsOverview } from './jvm_metrics_overview';
import { JsonMetricsDashboard } from './static_dashboard';
import { hasDashboardFile } from './static_dashboard/helper';
import { hasDashboard } from './static_dashboard/helper';
import { useAdHocApmDataView } from '../../../hooks/use_adhoc_apm_data_view';
import { isLogsOnlySignal } from '../../../utils/get_signal_type';
import { ServiceTabEmptyState } from '../service_tab_empty_state';
import { JvmMetricsOverview } from './jvm_metrics_overview';
export function Metrics() {
const { agentName, runtimeName, serverlessType } = useApmServiceContext();
const {
agentName,
runtimeName,
serverlessType,
serviceEntitySummary,
telemetrySdkName,
telemetrySdkLanguage,
} = useApmServiceContext();
const isAWSLambda = isAWSLambdaAgentName(serverlessType);
const { dataView } = useAdHocApmDataView();
const { serviceEntitySummary } = useApmServiceContext();
const hasDashboardFile = hasDashboard({ agentName, telemetrySdkName, telemetrySdkLanguage });
const hasLogsOnlySignal =
serviceEntitySummary?.dataStreamTypes && isLogsOnlySignal(serviceEntitySummary.dataStreamTypes);
@ -38,13 +44,19 @@ export function Metrics() {
return <ServerlessMetrics />;
}
const hasStaticDashboard = hasDashboardFile({
agentName,
runtimeName,
serverlessType,
});
if (!hasDashboardFile && !isElasticAgentName(agentName ?? '')) {
return (
<EuiCallOut
title={i18n.translate('xpack.apm.metrics.emptyState.title', {
defaultMessage: 'Runtime metrics are not available for this Agent / SDK type.',
})}
iconType="iInCircle"
data-test-subj="apmMetricsNoDashboardFound"
/>
);
}
if (hasStaticDashboard && dataView) {
if (hasDashboardFile && dataView) {
return (
<JsonMetricsDashboard
agentName={agentName}
@ -55,7 +67,7 @@ export function Metrics() {
);
}
if (!isAWSLambda && (isJavaAgentName(agentName) || isJRubyAgentName(agentName, runtimeName))) {
if (!isAWSLambda && isJRubyAgentName(agentName, runtimeName)) {
return <JvmMetricsOverview />;
}

View file

@ -5,52 +5,69 @@
* 2.0.
*/
export const AGENT_NAME_DASHBOARD_FILE_MAPPING: Record<string, string> = {
nodejs: 'nodejs',
'opentelemetry/nodejs': 'opentelemetry_nodejs',
'opentelemetry/nodejs/elastic': 'opentelemetry_nodejs',
java: 'java',
'opentelemetry/java': 'opentelemetry_java',
'opentelemetry/java/opentelemetry-java-instrumentation': 'opentelemetry_java',
'opentelemetry/java/elastic': 'opentelemetry_java',
'opentelemetry/dotnet': 'opentelemetry_dotnet',
'opentelemetry/dotnet/opentelemetry-dotnet-instrumentation': 'opentelemetry_dotnet',
'opentelemetry/dotnet/elastic': 'opentelemetry_dotnet',
};
// The new dashboard file names should be added here
export const existingDashboardFileNames = new Set([
'classic_apm-apm-nodejs',
'classic_apm-apm-java',
'classic_apm-otel_other-nodejs',
'classic_apm-otel_other-java',
'classic_apm-otel_other-dotnet',
'classic_apm-edot-nodejs',
'classic_apm-edot-java',
'classic_apm-edot-dotnet',
]);
/**
* The specially formatted comment in the `import` expression causes the corresponding webpack chunk to be named. This aids us in debugging chunk size issues.
* See https://webpack.js.org/api/module-methods/#magic-comments
*/
export async function loadDashboardFile(filename: string): Promise<any> {
// The new dashboard files should be mapped here
// + changed with the new ones (following the naming convention)
// + similar mapping for edot needed
// - example: otel_native-edot-nodejs
export async function loadDashboardFile(filename: string) {
switch (filename) {
case 'nodejs': {
case 'classic_apm-apm-nodejs': {
return import(
/* webpackChunkName: "lazyNodeJsDashboard" */
/* webpackChunkName: "lazyNodeJsClassicApmDashboard" */
'./nodejs.json'
);
}
case 'opentelemetry_nodejs': {
case 'classic_apm-otel_other-nodejs': {
return import(
/* webpackChunkName: "lazyNodeJsDashboard" */
/* webpackChunkName: "lazyNodeJsApmOtelDashboard" */
'./opentelemetry_nodejs.json'
);
}
case 'java': {
case 'classic_apm-edot-nodejs': {
return import(
/* webpackChunkName: "lazyJavaDashboard" */
/* webpackChunkName: "lazyNodeJsOtelNativeDashboard" */
'./opentelemetry_nodejs.json'
);
}
case 'classic_apm-apm-java': {
return import(
/* webpackChunkName: "lazyJavaClassicApmDashboard" */
'./java.json'
);
}
case 'opentelemetry_java': {
case 'classic_apm-otel_other-java': {
return import(
/* webpackChunkName: "lazyJavaDashboard" */
/* webpackChunkName: "lazyJavaApmOtelDashboard" */
'./opentelemetry_java.json'
);
}
case 'opentelemetry_dotnet': {
case 'classic_apm-edot-java': {
return import(
/* webpackChunkName: "lazyOtelDotnetDashboard" */
/* webpackChunkName: "lazyJavaOtelNativeDashboard" */
'./opentelemetry_java.json'
);
}
case 'classic_apm-edot-dotnet': {
return import(
/* webpackChunkName: "lazyDotnetOtelNativeDashboard" */
'./opentelemetry_dotnet.json'
);
}
case 'classic_apm-otel_other-dotnet': {
return import(
/* webpackChunkName: "lazyDotnetApmOtelDashboard" */
'./opentelemetry_dotnet.json'
);
}

View file

@ -0,0 +1,152 @@
/*
* 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 { getDashboardFileName } from './get_dashboard_file_name';
const apmAgent = [
{
agentName: 'java',
telemetrySdkName: undefined,
telemetrySdkLanguage: undefined,
filename: 'classic_apm-apm-java',
},
{
agentName: 'iOS/swift',
telemetrySdkName: undefined,
telemetrySdkLanguage: undefined,
filename: 'classic_apm-apm-ios_swift',
},
{
agentName: 'java',
telemetrySdkName: 'opentelemetry',
filename: 'otel_native-apm-java',
},
];
const edotSdk = [
{
agentName: 'opentelemetry/java/test/elastic',
filename: 'classic_apm-edot-java',
},
{
agentName: 'opentelemetry/java/elastic',
filename: 'classic_apm-edot-java',
},
{
agentName: 'opentelemetry/java/test/elastic',
filename: 'classic_apm-edot-java',
},
{
agentName: 'opentelemetry/java/elastic',
filename: 'classic_apm-edot-java',
},
{
agentName: 'opentelemetry/java/elastic',
telemetrySdkName: 'opentelemetry',
telemetrySdkLanguage: 'java',
filename: 'otel_native-edot-java',
},
{
agentName: 'opentelemetry/nodejs/nodejs-agent/elastic',
telemetrySdkName: 'opentelemetry',
telemetrySdkLanguage: 'nodejs',
filename: 'otel_native-edot-nodejs',
},
];
const vanillaOtelSdk = [
{
agentName: 'opentelemetry/java',
filename: 'classic_apm-otel_other-java',
},
{
agentName: 'opentelemetry/nodejs/test/nodejs-agent',
telemetrySdkName: 'opentelemetry',
telemetrySdkLanguage: 'nodejs',
filename: 'otel_native-otel_other-nodejs',
},
{
agentName: 'opentelemetry/java/test/something-else/',
telemetrySdkName: 'opentelemetry',
telemetrySdkLanguage: 'java',
filename: 'otel_native-otel_other-java',
},
{
agentName: 'otlp/nodejs',
telemetrySdkName: 'opentelemetry',
telemetrySdkLanguage: 'nodejs',
filename: 'otel_native-otel_other-nodejs',
},
{
agentName: 'otlp/Android',
telemetrySdkName: 'opentelemetry',
telemetrySdkLanguage: 'android',
filename: 'otel_native-otel_other-android',
},
];
const noFilenameCases = [
{
agentName: 'test/java/test/something-else/',
telemetrySdkName: undefined,
telemetrySdkLanguage: undefined,
filename: undefined,
},
{
agentName: 'otlp',
filename: undefined,
},
{
agentName: 'elastic',
filename: undefined,
},
{
agentName: 'my-awesome-agent/otel',
telemetrySdkName: 'opentelemetry',
filename: undefined,
},
];
describe('getDashboardFileName', () => {
describe('apmAgent', () => {
it.each(apmAgent)(
'for the agent name $agentName and open telemetry sdk name: $telemetrySdkName returns $filename',
({ agentName, telemetrySdkName, telemetrySdkLanguage, filename }) => {
expect(
getDashboardFileName({ agentName, telemetrySdkName, telemetrySdkLanguage })
).toStrictEqual(filename);
}
);
});
describe('vanillaOtelSdk', () => {
it.each(vanillaOtelSdk)(
'for the agent name $agentName and open telemetry sdk name: $telemetrySdkName and language $telemetrySdkLanguage returns $filename',
({ agentName, telemetrySdkName, telemetrySdkLanguage, filename }) => {
expect(
getDashboardFileName({ agentName, telemetrySdkName, telemetrySdkLanguage })
).toStrictEqual(filename);
}
);
});
describe('edotSdk', () => {
it.each(edotSdk)(
'for the agent name $agentName and open telemetry sdk name: $telemetrySdkName and language $telemetrySdkLanguage returns $filename',
({ agentName, telemetrySdkName, telemetrySdkLanguage, filename }) => {
expect(
getDashboardFileName({ agentName, telemetrySdkName, telemetrySdkLanguage })
).toStrictEqual(filename);
}
);
});
describe('noFilenameCases', () => {
it.each(noFilenameCases)(
'for the agent name $agentName and open telemetry sdk name: $telemetrySdkName and language $telemetrySdkLanguage returns $filename',
({ agentName, telemetrySdkName, telemetrySdkLanguage, filename }) => {
expect(
getDashboardFileName({ agentName, telemetrySdkName, telemetrySdkLanguage })
).toStrictEqual(filename);
}
);
});
});

View file

@ -0,0 +1,33 @@
/*
* 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 { getSdkNameAndLanguage, getIngestionPath } from '@kbn/elastic-agent-utils';
interface DashboardFileNamePartsProps {
agentName: string;
telemetrySdkName?: string;
telemetrySdkLanguage?: string;
}
// We use the language name in the filename so we want to have a valid filename
// Example swift/iOS -> swift_ios : lowercased and '/' is replaces by '_'
const standardizeLanguageName = (languageName?: string) =>
languageName ? languageName.toLowerCase().replace('/', '_') : undefined;
export const getDashboardFileName = ({
agentName,
telemetrySdkName,
telemetrySdkLanguage,
}: DashboardFileNamePartsProps): string | undefined => {
const dataFormat = getIngestionPath(!!(telemetrySdkName ?? telemetrySdkLanguage));
const { sdkName, language } = getSdkNameAndLanguage(agentName);
const sdkLanguage = standardizeLanguageName(language);
if (!sdkName || !sdkLanguage) {
return undefined;
}
return `${dataFormat}-${sdkName}-${sdkLanguage}`;
};

View file

@ -7,28 +7,33 @@
import type { DataView } from '@kbn/data-views-plugin/common';
import type { DashboardPanelMap } from '@kbn/dashboard-plugin/common';
import {
AGENT_NAME_DASHBOARD_FILE_MAPPING,
loadDashboardFile,
} from './dashboards/dashboard_catalog';
import { existingDashboardFileNames, loadDashboardFile } from './dashboards/dashboard_catalog';
import { getDashboardFileName } from './dashboards/get_dashboard_file_name';
interface DashboardFileProps {
agentName?: string;
runtimeName?: string;
serverlessType?: string;
telemetrySdkName?: string;
telemetrySdkLanguage?: string;
}
export interface MetricsDashboardProps extends DashboardFileProps {
dataView: DataView;
}
export function hasDashboardFile(props: DashboardFileProps) {
return !!getDashboardFileName(props);
function getDashboardFileNameFromProps({
agentName,
telemetrySdkName,
telemetrySdkLanguage,
}: DashboardFileProps) {
const dashboardFile =
agentName && getDashboardFileName({ agentName, telemetrySdkName, telemetrySdkLanguage });
return dashboardFile;
}
function getDashboardFileName({ agentName }: DashboardFileProps) {
const dashboardFile = agentName && AGENT_NAME_DASHBOARD_FILE_MAPPING[agentName];
return dashboardFile;
export function hasDashboard(props: DashboardFileProps) {
const dashboardFilename = getDashboardFileNameFromProps(props);
return !!dashboardFilename && existingDashboardFileNames.has(dashboardFilename);
}
const getAdhocDataView = (dataView: DataView) => {
@ -43,10 +48,8 @@ export async function convertSavedDashboardToPanels(
props: MetricsDashboardProps,
dataView: DataView
): Promise<DashboardPanelMap | undefined> {
const dashboardFilename = getDashboardFileName(props);
const dashboardJSON = !!dashboardFilename
? await loadDashboardFile(dashboardFilename)
: undefined;
const dashboardFilename = getDashboardFileNameFromProps(props);
const dashboardJSON = !!dashboardFilename ? await loadDashboardFile(dashboardFilename) : false;
if (!dashboardFilename || !dashboardJSON) {
return undefined;

View file

@ -28,6 +28,8 @@ import {
export interface APMServiceContextValue {
serviceName: string;
agentName?: string;
telemetrySdkName?: string;
telemetrySdkLanguage?: string;
serverlessType?: ServerlessType;
transactionType?: string;
transactionTypeStatus: FETCH_STATUS;
@ -63,6 +65,8 @@ export function ApmServiceContextProvider({ children }: { children: ReactNode })
agentName,
runtimeName,
serverlessType,
telemetrySdkName,
telemetrySdkLanguage,
status: serviceAgentStatus,
} = useServiceAgentFetcher({
serviceName,
@ -108,6 +112,8 @@ export function ApmServiceContextProvider({ children }: { children: ReactNode })
serviceName,
agentName,
serverlessType,
telemetrySdkName,
telemetrySdkLanguage,
transactionType: currentTransactionType,
transactionTypeStatus,
transactionTypes,

View file

@ -11,6 +11,8 @@ const INITIAL_STATE = {
agentName: undefined,
runtimeName: undefined,
serverlessType: undefined,
telemetrySdkName: undefined,
telemetrySdkLanguage: undefined,
};
export function useServiceAgentFetcher({

View file

@ -15,6 +15,8 @@ import {
SERVICE_RUNTIME_NAME,
CLOUD_PROVIDER,
CLOUD_SERVICE_NAME,
TELEMETRY_SDK_NAME,
TELEMETRY_SDK_LANGUAGE,
} from '../../../common/es_fields/apm';
import type { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client';
import type { ServerlessType } from '../../../common/serverless';
@ -24,6 +26,8 @@ import { maybe } from '../../../common/utils/maybe';
export interface ServiceAgentResponse {
agentName?: string;
runtimeName?: string;
telemetrySdkName?: string;
telemetrySdkLanguage?: string;
serverlessType?: ServerlessType;
}
@ -40,6 +44,8 @@ export async function getServiceAgent({
}): Promise<ServiceAgentResponse> {
const fields = asMutableArray([
AGENT_NAME,
TELEMETRY_SDK_NAME,
TELEMETRY_SDK_LANGUAGE,
SERVICE_RUNTIME_NAME,
CLOUD_PROVIDER,
CLOUD_SERVICE_NAME,
@ -48,7 +54,12 @@ export async function getServiceAgent({
const params = {
terminate_after: 1,
apm: {
events: [ProcessorEvent.error, ProcessorEvent.transaction, ProcessorEvent.metric],
events: [
ProcessorEvent.span,
ProcessorEvent.error,
ProcessorEvent.transaction,
ProcessorEvent.metric,
],
},
body: {
track_total_hits: 1,
@ -99,11 +110,13 @@ export async function getServiceAgent({
const event = unflattenKnownApmEventFields(hit.fields);
const { agent, service, cloud } = event;
const { agent, service, cloud, telemetry } = event;
const serverlessType = getServerlessTypeFromCloudData(cloud?.provider, cloud?.service?.name);
return {
agentName: agent?.name,
telemetrySdkName: telemetry?.sdk?.name,
telemetrySdkLanguage: telemetry?.sdk?.language,
runtimeName: service?.runtime?.name,
serverlessType,
};

View file

@ -58,7 +58,10 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon
expect(response.status).to.be(200);
expect(response.body).to.eql({ agentName: 'nodejs', runtimeName: 'node' });
expect(response.body).to.eql({
agentName: 'nodejs',
runtimeName: 'node',
});
});
});
});