[APM] Display latest agent version in agent explorer (#153643)

Closes https://github.com/elastic/kibana/issues/152326.

### Changes
- `fetchWithTimeout` function was added, so we can fetch the external
bucket where the versions are with a timeout. This is mostly useful for
air-gapped environments.
- `fetchAgentsLatestVersion` was introduced an it's in charge of
fetching the bucket and handling the errors accordingly.
- `getAgentsItems` now returns `latestVersion` property for each agent.
- New column was created in the UI to list the latestVersion per agent.

When no timing out


https://user-images.githubusercontent.com/1313018/227519796-e5569475-451d-4c04-8243-d18c8e7126c3.mov

When timing out


https://user-images.githubusercontent.com/1313018/227520011-ae616a07-e87b-4d0f-bd29-4b3338aa5df2.mov

### Pending

- [ ] Replace bucket URL with production bucket url

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Yngrid Coello 2023-04-11 14:31:13 +02:00 committed by GitHub
parent e29265e51c
commit 628db34d8a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 486 additions and 20 deletions

View file

@ -101,4 +101,7 @@ Matcher for all source map indices. Defaults to `apm-*`.
`xpack.apm.autoCreateApmDataView` {ess-icon}::
Set to `false` to disable the automatic creation of the APM data view when the APM app is opened. Defaults to `true`.
`xpack.apm.latestAgentVersionsUrl` {ess-icon}::
Specifies the URL of a self hosted file that contains latest agent versions. Defaults to `https://apm-agent-versions.elastic.co/versions.json`. Set to `''` to disable requesting latest agent versions.
// end::general-apm-settings[]

View file

@ -165,6 +165,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
'xpack.apm.serviceMapEnabled (boolean)',
'xpack.apm.ui.enabled (boolean)',
'xpack.apm.ui.maxTraceItems (number)',
'xpack.apm.latestAgentVersionsUrl (string)',
'xpack.cases.files.allowedMimeTypes (array)',
'xpack.cases.files.maxSize (number)',
'xpack.cases.markdownPlugins.lens (boolean)',

View file

@ -14,3 +14,12 @@ export enum AgentExplorerFieldName {
AgentDocsPageUrl = 'agentDocsPageUrl',
Instances = 'instances',
}
export interface ElasticApmAgentLatestVersion {
latest_version: string;
}
export interface OtelAgentLatestVersion {
sdk_latest_version: string;
auto_latest_version?: string;
}

View file

@ -8,6 +8,7 @@
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { TypeOf } from '@kbn/typed-react-router-config';
import { isEmpty } from 'lodash';
import React from 'react';
import { AgentName } from '../../../../../../../typings/es_schemas/ui/fields/agent';
import { useApmPluginContext } from '../../../../../../context/apm_plugin/use_apm_plugin_context';
@ -18,6 +19,7 @@ import { StickyProperties } from '../../../../../shared/sticky_properties';
import { getComparisonEnabled } from '../../../../../shared/time_comparison/get_comparison_enabled';
import { TruncateWithTooltip } from '../../../../../shared/truncate_with_tooltip';
import { AgentExplorerDocsLink } from '../../agent_explorer_docs_link';
import { AgentLatestVersion } from '../../agent_latest_version';
const serviceLabel = i18n.translate(
'xpack.apm.agentInstancesDetails.serviceLabel',
@ -40,6 +42,13 @@ const instancesLabel = i18n.translate(
}
);
const latestVersionLabel = i18n.translate(
'xpack.apm.agentInstancesDetails.latestVersionLabel',
{
defaultMessage: 'Latest agent version',
}
);
const agentDocsLabel = i18n.translate(
'xpack.apm.agentInstancesDetails.agentDocsUrlLabel',
{
@ -52,17 +61,25 @@ export function AgentContextualInformation({
serviceName,
agentDocsPageUrl,
instances,
latestVersion,
query,
isLatestVersionsLoading,
latestVersionsFailed,
}: {
agentName: AgentName;
serviceName: string;
agentDocsPageUrl?: string;
instances: number;
latestVersion?: string;
query: TypeOf<ApmRoutes, '/settings/agent-explorer'>['query'];
isLatestVersionsLoading: boolean;
latestVersionsFailed: boolean;
}) {
const { core } = useApmPluginContext();
const { core, config } = useApmPluginContext();
const latestAgentVersionEnabled = !isEmpty(config.latestAgentVersionsUrl);
const comparisonEnabled = getComparisonEnabled({ core });
const { rangeFrom, rangeTo } = useDefaultTimeRange();
const width = latestAgentVersionEnabled ? '20%' : '25%';
const stickyProperties = [
{
@ -88,7 +105,7 @@ export function AgentContextualInformation({
}
/>
),
width: '25%',
width,
},
{
label: agentNameLabel,
@ -100,7 +117,7 @@ export function AgentContextualInformation({
</EuiFlexItem>
</EuiFlexGroup>
),
width: '25%',
width,
},
{
label: instancesLabel,
@ -112,8 +129,25 @@ export function AgentContextualInformation({
</EuiFlexItem>
</EuiFlexGroup>
),
width: '25%',
width,
},
...(latestAgentVersionEnabled
? [
{
label: latestVersionLabel,
fieldName: latestVersionLabel,
val: (
<AgentLatestVersion
agentName={agentName}
isLoading={isLatestVersionsLoading}
latestVersion={latestVersion}
failed={latestVersionsFailed}
/>
),
width,
},
]
: []),
{
label: agentDocsLabel,
fieldName: agentDocsLabel,
@ -129,7 +163,7 @@ export function AgentContextualInformation({
}
/>
),
width: '25%',
width,
},
];

View file

@ -58,10 +58,17 @@ function useAgentInstancesFetcher({ serviceName }: { serviceName: string }) {
interface Props {
agent: AgentExplorerItem;
isLatestVersionsLoading: boolean;
latestVersionsFailed: boolean;
onClose: () => void;
}
export function AgentInstances({ agent, onClose }: Props) {
export function AgentInstances({
agent,
isLatestVersionsLoading,
latestVersionsFailed,
onClose,
}: Props) {
const { query } = useApmParams('/settings/agent-explorer');
const instances = useAgentInstancesFetcher({
@ -95,7 +102,10 @@ export function AgentInstances({ agent, onClose }: Props) {
serviceName={agent.serviceName}
agentDocsPageUrl={agent.agentDocsPageUrl}
instances={agent.instances}
latestVersion={agent.latestVersion}
query={query}
isLatestVersionsLoading={isLatestVersionsLoading}
latestVersionsFailed={latestVersionsFailed}
/>
<EuiHorizontalRule margin="m" />
<EuiSpacer size="m" />

View file

@ -0,0 +1,58 @@
/*
* 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 { EuiSkeletonRectangle, EuiToolTip, useEuiTheme } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { NOT_AVAILABLE_LABEL } from '../../../../../../common/i18n';
import { AgentName } from '../../../../../../typings/es_schemas/ui/fields/agent';
export function AgentLatestVersion({
agentName,
isLoading,
latestVersion,
failed,
}: {
agentName: AgentName;
isLoading: boolean;
latestVersion?: string;
failed: boolean;
}) {
const { euiTheme } = useEuiTheme();
const latestVersionElement = latestVersion ? (
<>{latestVersion}</>
) : (
<>{NOT_AVAILABLE_LABEL}</>
);
const failedLatestVersionsElement = (
<EuiToolTip
content={i18n.translate(
'xpack.apm.agentExplorer.agentLatestVersion.airGappedMessage',
{
defaultMessage:
'The latest version of {agentName} agent could not be fetched from the repository. Please contact your administrator to check the server logs.',
values: { agentName },
}
)}
>
<>{NOT_AVAILABLE_LABEL}</>
</EuiToolTip>
);
return (
<EuiSkeletonRectangle
width="60px"
height={euiTheme.size.l}
borderRadius="m"
isLoading={isLoading}
>
{!failed ? latestVersionElement : failedLatestVersionsElement}
</EuiSkeletonRectangle>
);
}

View file

@ -9,13 +9,16 @@ import {
EuiButtonIcon,
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiToolTip,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { isEmpty } from 'lodash';
import React, { useMemo, useState } from 'react';
import { ValuesType } from 'utility-types';
import { AgentExplorerFieldName } from '../../../../../../common/agent_explorer';
import { AgentName } from '../../../../../../typings/es_schemas/ui/fields/agent';
import { useApmPluginContext } from '../../../../../context/apm_plugin/use_apm_plugin_context';
import { APIReturnType } from '../../../../../services/rest/create_call_apm_api';
import { AgentIcon } from '../../../../shared/agent_icon';
import { EnvironmentBadge } from '../../../../shared/environment_badge';
@ -24,6 +27,7 @@ import { ITableColumn, ManagedTable } from '../../../../shared/managed_table';
import { TruncateWithTooltip } from '../../../../shared/truncate_with_tooltip';
import { AgentExplorerDocsLink } from '../agent_explorer_docs_link';
import { AgentInstances } from '../agent_instances';
import { AgentLatestVersion } from '../agent_latest_version';
export type AgentExplorerItem = ValuesType<
APIReturnType<'GET /internal/apm/get_agents_per_service'>['items']
@ -31,9 +35,15 @@ export type AgentExplorerItem = ValuesType<
export function getAgentsColumns({
selectedAgent,
isLatestVersionsLoading,
latestAgentVersionEnabled,
latestVersionsFailed,
onAgentSelected,
}: {
selectedAgent?: AgentExplorerItem;
isLatestVersionsLoading: boolean;
latestAgentVersionEnabled: boolean;
latestVersionsFailed: boolean;
onAgentSelected: (agent: AgentExplorerItem) => void;
}): Array<ITableColumn<AgentExplorerItem>> {
return [
@ -153,6 +163,51 @@ export function getAgentsColumns({
/>
),
},
...(latestAgentVersionEnabled
? [
{
field: AgentExplorerFieldName.AgentLastVersion,
name: (
<EuiToolTip
content={i18n.translate(
'xpack.apm.agentExplorerTable.agentLatestVersionColumnTooltip',
{
defaultMessage: 'The latest released version of the agent.',
}
)}
>
<>
{i18n.translate(
'xpack.apm.agentExplorerTable.agentLatestVersionColumnLabel',
{ defaultMessage: 'Latest Agent Version' }
)}
&nbsp;
<EuiIcon
size="s"
color="subdued"
type="questionInCircle"
className="eui-alignCenter"
/>
</>
</EuiToolTip>
),
width: '10%',
align: 'center',
truncateText: true,
render: (
_: any,
{ agentName, latestVersion }: AgentExplorerItem
) => (
<AgentLatestVersion
agentName={agentName}
isLoading={isLatestVersionsLoading}
latestVersion={latestVersion}
failed={latestVersionsFailed}
/>
),
},
]
: []),
{
field: AgentExplorerFieldName.AgentDocsPageUrl,
name: i18n.translate(
@ -177,9 +232,20 @@ interface Props {
items: AgentExplorerItem[];
noItemsMessage: React.ReactNode;
isLoading: boolean;
isLatestVersionsLoading: boolean;
latestVersionsFailed: boolean;
}
export function AgentList({ items, noItemsMessage, isLoading }: Props) {
export function AgentList({
items,
noItemsMessage,
isLoading,
isLatestVersionsLoading,
latestVersionsFailed,
}: Props) {
const { config } = useApmPluginContext();
const latestAgentVersionEnabled = !isEmpty(config.latestAgentVersionsUrl);
const [selectedAgent, setSelectedAgent] = useState<AgentExplorerItem>();
const onAgentSelected = (agent: AgentExplorerItem) => {
@ -191,14 +257,31 @@ export function AgentList({ items, noItemsMessage, isLoading }: Props) {
};
const agentColumns = useMemo(
() => getAgentsColumns({ selectedAgent, onAgentSelected }),
[selectedAgent]
() =>
getAgentsColumns({
selectedAgent,
isLatestVersionsLoading,
latestAgentVersionEnabled,
latestVersionsFailed,
onAgentSelected,
}),
[
selectedAgent,
latestAgentVersionEnabled,
isLatestVersionsLoading,
latestVersionsFailed,
]
);
return (
<>
{selectedAgent && (
<AgentInstances agent={selectedAgent} onClose={onCloseFlyout} />
<AgentInstances
agent={selectedAgent}
isLatestVersionsLoading={isLatestVersionsLoading}
latestVersionsFailed={latestVersionsFailed}
onClose={onCloseFlyout}
/>
)}
<ManagedTable
columns={agentColumns}

View file

@ -17,13 +17,20 @@ import {
import { i18n } from '@kbn/i18n';
import React from 'react';
import { useHistory } from 'react-router-dom';
import { isEmpty } from 'lodash';
import {
ElasticApmAgentLatestVersion,
OtelAgentLatestVersion,
} from '../../../../../common/agent_explorer';
import { isOpenTelemetryAgentName } from '../../../../../common/agent_name';
import {
SERVICE_LANGUAGE_NAME,
SERVICE_NAME,
} from '../../../../../common/es_fields/apm';
import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
import { EnvironmentsContextProvider } from '../../../../context/environments_context/environments_context';
import { useApmParams } from '../../../../hooks/use_apm_params';
import { FETCH_STATUS } from '../../../../hooks/use_fetcher';
import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher';
import { useProgressiveFetcher } from '../../../../hooks/use_progressive_fetcher';
import { useTimeRange } from '../../../../hooks/use_time_range';
import { ApmEnvironmentFilter } from '../../../shared/environment_filter';
@ -33,6 +40,15 @@ import { SuggestionsSelect } from '../../../shared/suggestions_select';
import { TechnicalPreviewBadge } from '../../../shared/technical_preview_badge';
import { AgentList } from './agent_list';
const getOtelLatestAgentVersion = (
agentTelemetryAutoVersion: string[],
otelLatestVersion?: OtelAgentLatestVersion
) => {
return agentTelemetryAutoVersion.length > 0
? otelLatestVersion?.auto_latest_version
: otelLatestVersion?.sdk_latest_version;
};
function useAgentExplorerFetcher({
start,
end,
@ -63,6 +79,17 @@ function useAgentExplorerFetcher({
);
}
function useLatestAgentVersionsFetcher(latestAgentVersionEnabled: boolean) {
return useFetcher(
(callApmApi) => {
if (latestAgentVersionEnabled) {
return callApmApi('GET /internal/apm/get_latest_agent_versions');
}
},
[latestAgentVersionEnabled]
);
}
export function AgentExplorer() {
const history = useHistory();
@ -74,9 +101,30 @@ export function AgentExplorer() {
const rangeTo = 'now';
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
const { config } = useApmPluginContext();
const latestAgentVersionEnabled = !isEmpty(config.latestAgentVersionsUrl);
const agents = useAgentExplorerFetcher({ start, end });
const { data: latestAgentVersions, status: latestAgentVersionsStatus } =
useLatestAgentVersionsFetcher(latestAgentVersionEnabled);
const isLoading = agents.status === FETCH_STATUS.LOADING;
const isLatestAgentVersionsLoading =
latestAgentVersionsStatus === FETCH_STATUS.LOADING;
const agentItems = (agents.data?.items ?? []).map((agent) => ({
...agent,
latestVersion: isOpenTelemetryAgentName(agent.agentName)
? getOtelLatestAgentVersion(
agent.agentTelemetryAutoVersion,
latestAgentVersions?.data?.[agent.agentName] as OtelAgentLatestVersion
)
: (
latestAgentVersions?.data?.[
agent.agentName
] as ElasticApmAgentLatestVersion
)?.latest_version,
}));
const noItemsMessage = (
<EuiEmptyPrompt
@ -199,8 +247,10 @@ export function AgentExplorer() {
<EuiFlexItem>
<AgentList
isLoading={isLoading}
items={agents.data?.items ?? []}
items={agentItems}
noItemsMessage={noItemsMessage}
isLatestVersionsLoading={isLatestAgentVersionsLoading}
latestVersionsFailed={!!latestAgentVersions?.error}
/>
</EuiFlexItem>
</EuiFlexGroup>

View file

@ -66,6 +66,7 @@ const mockConfig: ConfigSchema = {
ui: {
enabled: false,
},
latestAgentVersionsUrl: '',
};
const urlService = new UrlService({

View file

@ -13,6 +13,7 @@ export interface ConfigSchema {
ui: {
enabled: boolean;
};
latestAgentVersionsUrl: string;
}
export const plugin: PluginInitializer<ApmPluginSetup, ApmPluginStart> = (

View file

@ -53,6 +53,9 @@ const configSchema = schema.object({
onboarding: schema.string({ defaultValue: 'apm-*' }),
}),
forceSyntheticSource: schema.boolean({ defaultValue: false }),
latestAgentVersionsUrl: schema.string({
defaultValue: 'https://apm-agent-versions.elastic.co/versions.json',
}),
});
// plugin config
@ -110,6 +113,7 @@ export const config: PluginConfigDescriptor<APMConfig> = {
exposeToBrowser: {
serviceMapEnabled: true,
ui: true,
latestAgentVersionsUrl: true,
},
schema: configSchema,
};

View file

@ -0,0 +1,12 @@
/*
* 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 class ErrorWithStatusCode extends Error {
constructor(message: string, public readonly statusCode: string) {
super(message);
}
}

View file

@ -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.
*/
jest.mock('node-fetch');
import Boom from '@hapi/boom';
import { loggerMock } from '@kbn/logging-mocks';
import { fetchAgentsLatestVersion } from './fetch_agents_latest_version';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const fetchMock = require('node-fetch') as jest.Mock;
const logger = loggerMock.create();
describe('ApmFetchAgentslatestsVersion', () => {
beforeEach(() => {
jest.resetAllMocks();
});
it('when url is empty should not fetch latest versions', async () => {
const boom = Boom.notImplemented(
'To use latest agent versions you must set xpack.apm.latestAgentVersionsUrl.'
);
await expect(fetchAgentsLatestVersion(logger, '')).rejects.toThrow(boom);
expect(fetchMock).toBeCalledTimes(0);
});
describe('when url is defined', () => {
it('should handle errors gracefully', async () => {
fetchMock.mockResolvedValue({
text: () => 'Request Timeout',
status: 408,
ok: false,
});
const { data, error } = await fetchAgentsLatestVersion(logger, 'my-url');
expect(fetchMock).toBeCalledTimes(1);
expect(data).toEqual({});
expect(error?.statusCode).toEqual('408');
});
it('should return latest agents version', async () => {
fetchMock.mockResolvedValue({
json: () => ({
java: '1.1.0',
}),
status: 200,
ok: true,
});
const { data, error } = await fetchAgentsLatestVersion(logger, 'my-url');
expect(fetchMock).toBeCalledTimes(1);
expect(data).toEqual({ java: '1.1.0' });
expect(error).toBeFalsy();
});
});
});

View file

@ -0,0 +1,67 @@
/*
* 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 Boom from '@hapi/boom';
import { Logger } from '@kbn/core/server';
import { i18n } from '@kbn/i18n';
import { isEmpty } from 'lodash';
import fetch from 'node-fetch';
import {
ElasticApmAgentLatestVersion,
OtelAgentLatestVersion,
} from '../../../common/agent_explorer';
import { AgentName } from '../../../typings/es_schemas/ui/fields/agent';
import { ErrorWithStatusCode } from './error_with_status_code';
const MISSING_CONFIGURATION = i18n.translate(
'xpack.apm.agent_explorer.error.missing_configuration',
{
defaultMessage:
'To use latest agent versions you must set xpack.apm.latestAgentVersionsUrl.',
}
);
export interface AgentLatestVersionsResponse {
data: AgentLatestVersions;
error?: { message: string; type?: string; statusCode?: string };
}
type AgentLatestVersions = Record<
AgentName,
ElasticApmAgentLatestVersion | OtelAgentLatestVersion
>;
export const fetchAgentsLatestVersion = async (
logger: Logger,
latestAgentVersionsUrl: string
): Promise<AgentLatestVersionsResponse> => {
if (isEmpty(latestAgentVersionsUrl)) {
throw Boom.notImplemented(MISSING_CONFIGURATION);
}
try {
const response = await fetch(latestAgentVersionsUrl);
if (response.status !== 200) {
throw new ErrorWithStatusCode(
`${response.status} - ${await response.text()}`,
`${response.status}`
);
}
const data = await response.json();
return { data };
} catch (error) {
const message = `Failed to retrieve latest APM Agent versions due to ${error}`;
logger.warn(message);
return {
data: {} as AgentLatestVersions,
error,
};
}
};

View file

@ -9,8 +9,8 @@ import { isOpenTelemetryAgentName } from '../../../common/agent_name';
import { AgentName } from '../../../typings/es_schemas/ui/fields/agent';
import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client';
import { RandomSampler } from '../../lib/helpers/get_random_sampler';
import { getAgentsItems } from './get_agents_items';
import { getAgentDocsPageUrl } from './get_agent_url_repository';
import { getAgentsItems } from './get_agents_items';
const getOtelAgentVersion = (item: {
agentTelemetryAutoVersion: string[];
@ -29,7 +29,9 @@ export interface AgentExplorerAgentsResponse {
environments: string[];
agentName: AgentName;
agentVersion: string[];
agentTelemetryAutoVersion: string[];
instances: number;
latestVersion?: string;
}>;
}
@ -65,16 +67,19 @@ export async function getAgents({
return {
items: items.map((item) => {
const agentVersion = isOpenTelemetryAgentName(item.agentName)
? getOtelAgentVersion(item)
: item.agentVersion;
const agentDocsPageUrl = getAgentDocsPageUrl(item.agentName);
const { agentTelemetryAutoVersion, ...rest } = item;
if (isOpenTelemetryAgentName(item.agentName)) {
return {
...item,
agentVersion: getOtelAgentVersion(item),
agentDocsPageUrl,
};
}
return {
...rest,
agentVersion,
agentDocsPageUrl: getAgentDocsPageUrl(item.agentName as AgentName),
...item,
agentDocsPageUrl,
};
}),
};

View file

@ -20,6 +20,10 @@ import {
AgentExplorerAgentInstancesResponse,
getAgentInstances,
} from './get_agent_instances';
import {
AgentLatestVersionsResponse,
fetchAgentsLatestVersion,
} from './fetch_agents_latest_version';
const agentExplorerRoute = createApmServerRoute({
endpoint: 'GET /internal/apm/get_agents_per_service',
@ -71,6 +75,16 @@ const agentExplorerRoute = createApmServerRoute({
},
});
const latestAgentVersionsRoute = createApmServerRoute({
endpoint: 'GET /internal/apm/get_latest_agent_versions',
options: { tags: ['access:apm'] },
async handler(resources): Promise<AgentLatestVersionsResponse> {
const { logger, config } = resources;
return fetchAgentsLatestVersion(logger, config.latestAgentVersionsUrl);
},
});
const agentExplorerInstanceRoute = createApmServerRoute({
endpoint: 'GET /internal/apm/services/{serviceName}/agent_instances',
options: { tags: ['access:apm'] },
@ -104,5 +118,6 @@ const agentExplorerInstanceRoute = createApmServerRoute({
export const agentExplorerRouteRepository = {
...agentExplorerRoute,
...latestAgentVersionsRoute,
...agentExplorerInstanceRoute,
};

View file

@ -82,6 +82,7 @@
"@kbn/shared-ux-router",
"@kbn/alerts-as-data-utils",
"@kbn/exploratory-view-plugin",
"@kbn/logging-mocks",
],
"exclude": [
"target/**/*",

View file

@ -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 { ElasticApmAgentLatestVersion } from '@kbn/apm-plugin/common/agent_explorer';
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../common/ftr_provider_context';
export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const apmApiClient = getService('apmApiClient');
const nodeAgentName = 'nodejs';
const unlistedAgentName = 'unlistedAgent';
async function callApi() {
return await apmApiClient.readUser({
endpoint: 'GET /internal/apm/get_latest_agent_versions',
});
}
registry.when(
'Agent latest versions when configuration is defined',
{ config: 'basic', archives: [] },
() => {
it('returns a version when agent is listed in the file', async () => {
const { status, body } = await callApi();
expect(status).to.be(200);
const agents = body.data;
const nodeAgent = agents[nodeAgentName] as ElasticApmAgentLatestVersion;
expect(nodeAgent?.latest_version).not.to.be(undefined);
});
it('returns undefined when agent is not listed in the file', async () => {
const { status, body } = await callApi();
expect(status).to.be(200);
const agents = body.data;
// @ts-ignore
const unlistedAgent = agents[unlistedAgentName] as ElasticApmAgentLatestVersion;
expect(unlistedAgent?.latest_version).to.be(undefined);
});
}
);
}