Add feature flag for service map api v2 (#214227)

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

### Summary

Added feature flag to enable the new service map api
Not specific telemetry for events was added, the advance settings have
telemetry already out of the box


https://github.com/user-attachments/assets/684bd369-c46d-4ac0-ab07-33b5395a7f71

<img width="778" alt="Screenshot 2025-03-13 at 14 20 37"
src="https://github.com/user-attachments/assets/6445e85f-1108-43d1-aeee-a340cdfe99b8"
/>

### How to test

- Inspect the response. v2 has a `spans` object, and the current version
has an `elements` object

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Miriam 2025-03-14 15:55:53 +01:00 committed by GitHub
parent f2b5f3fe48
commit 3ff1340c01
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 120 additions and 15 deletions

View file

@ -488,6 +488,10 @@ export const stackManagementSchema: MakeSchemaFrom<UsageStats> = {
type: 'integer',
_meta: { description: 'Non-default value of setting.' },
},
'observability:apmEnableServiceMapApiV2': {
type: 'boolean',
_meta: { description: 'Non-default value of setting.' },
},
'observability:aiAssistantSimulatedFunctionCalling': {
type: 'boolean',
_meta: { description: 'Non-default value of setting.' },

View file

@ -49,6 +49,7 @@ export interface UsageStats {
'observability:apmAgentExplorerView': boolean;
'observability:apmEnableTableSearchBar': boolean;
'observability:apmEnableServiceInventoryTableSearchBar': boolean;
'observability:apmEnableServiceMapApiV2': boolean;
'observability:logSources': string[];
'observability:newLogsOverview': boolean;
'observability:aiAssistantSimulatedFunctionCalling': boolean;

View file

@ -11030,6 +11030,12 @@
"description": "Non-default value of setting."
}
},
"observability:apmEnableServiceMapApiV2": {
"type": "boolean",
"_meta": {
"description": "Non-default value of setting."
}
},
"observability:aiAssistantSimulatedFunctionCalling": {
"type": "boolean",
"_meta": {

View file

@ -207,7 +207,6 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
'xpack.apm.serviceMapEnabled (boolean?)',
'xpack.apm.ui.enabled (boolean?)',
'xpack.apm.ui.maxTraceItems (number?)',
'xpack.apm.ui.serviceMapApiV2Enabled (boolean?)',
'xpack.apm.managedServiceUrl (string?|never)',
'xpack.apm.serverlessOnboarding (boolean?|never)',
'xpack.apm.latestAgentVersionsUrl (string?)',

View file

@ -6,9 +6,20 @@
*/
import { usePerformanceContext } from '@kbn/ebt-tools';
import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiPanel, useEuiTheme } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import {
EuiFlexGroup,
EuiFlexItem,
EuiLoadingSpinner,
EuiPanel,
EuiSpacer,
useEuiTheme,
} from '@elastic/eui';
import type { ReactNode } from 'react';
import React from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { apmEnableServiceMapApiV2 } from '@kbn/observability-plugin/common';
import { useEditableSettings } from '@kbn/observability-shared-plugin/public';
import { Subscription } from 'rxjs';
import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
import { isActivePlatinumLicense } from '../../../../common/license_check';
import { invalidLicenseMessage, SERVICE_MAP_TIMEOUT_ERROR } from '../../../../common/service_map';
@ -33,6 +44,7 @@ import { useApmServiceContext } from '../../../context/apm_service/use_apm_servi
import { isLogsOnlySignal } from '../../../utils/get_signal_type';
import { ServiceTabEmptyState } from '../service_tab_empty_state';
import { useServiceMap } from './use_service_map';
import { TryItButton } from '../../shared/try_it_button';
function PromptContainer({ children }: { children: ReactNode }) {
return (
@ -108,8 +120,34 @@ export function ServiceMap({
const license = useLicenseContext();
const serviceName = useServiceName();
const { config } = useApmPluginContext();
const { config, core } = useApmPluginContext();
const { onPageReady } = usePerformanceContext();
const subscriptions = useRef<Subscription>(new Subscription());
const [isServiceMapApiV2Enabled, setIsServiceMapApiV2Enabled] = useState<boolean>(
core.settings.client.get(apmEnableServiceMapApiV2)
);
useEffect(() => {
subscriptions.current.add(
core.settings.client.get$<boolean>(apmEnableServiceMapApiV2).subscribe((value) => {
setIsServiceMapApiV2Enabled(value);
})
);
}, [core.settings]);
useEffect(() => {
const currentSubscriptions = subscriptions.current;
return () => {
currentSubscriptions.unsubscribe();
};
}, []);
const { fields, isSaving, saveSingleSetting } = useEditableSettings([apmEnableServiceMapApiV2]);
const settingsField = fields[apmEnableServiceMapApiV2];
const isServiceMapV2Enabled = Boolean(settingsField?.savedValue ?? settingsField?.defaultValue);
const { data, status, error } = useServiceMap({
environment,
kuery,
@ -117,6 +155,7 @@ export function ServiceMap({
end,
serviceGroupId,
serviceName,
isServiceMapApiV2Enabled,
});
const { ref, height } = useRefDimensions();
@ -182,6 +221,39 @@ export function ServiceMap({
return (
<>
<SearchBar showTimeComparison />
<EuiFlexGroup alignItems="center" justifyContent="flexStart" gutterSize="s">
<TryItButton
isFeatureEnabled={isServiceMapV2Enabled}
linkLabel={
isServiceMapV2Enabled
? i18n.translate('xpack.apm.serviceMap.disableServiceMapApiV2', {
defaultMessage: 'Disable the new service map API',
})
: i18n.translate('xpack.apm.serviceMap.enableServiceMapApiV2', {
defaultMessage: 'Enable the new service map API',
})
}
onClick={() => {
saveSingleSetting(apmEnableServiceMapApiV2, !isServiceMapV2Enabled);
}}
isLoading={isSaving}
popoverContent={
<EuiFlexGroup direction="column" gutterSize="s">
<EuiFlexItem grow={false}>
{i18n.translate('xpack.apm.serviceMap.serviceMapApiV2PopoverContent', {
defaultMessage: 'The new service map API is faster, try it out!',
})}
</EuiFlexItem>
</EuiFlexGroup>
}
hideThisContent={i18n.translate('xpack.apm.serviceMap.hideThisContent', {
defaultMessage:
'Hide this. The setting can be enabled or disabled in Advanced Settings.',
})}
calloutId="showServiceMapV2Callout"
/>
</EuiFlexGroup>
<EuiSpacer size="s" />
<EuiPanel hasBorder={true} paddingSize="none">
<div data-test-subj="serviceMap" style={{ height: heightWithPadding }} ref={ref}>
<Cytoscape

View file

@ -31,6 +31,7 @@ export const useServiceMap = ({
serviceName,
serviceGroupId,
kuery,
isServiceMapApiV2Enabled,
}: {
environment: Environment;
kuery: string;
@ -38,6 +39,7 @@ export const useServiceMap = ({
end: string;
serviceGroupId?: string;
serviceName?: string;
isServiceMapApiV2Enabled: boolean;
}) => {
const license = useLicenseContext();
const { config } = useApmPluginContext();
@ -67,7 +69,7 @@ export const useServiceMap = ({
serviceName,
serviceGroup: serviceGroupId,
kuery,
useV2: config.ui.serviceMapApiV2Enabled,
useV2: isServiceMapApiV2Enabled,
},
},
});
@ -81,7 +83,7 @@ export const useServiceMap = ({
serviceGroupId,
kuery,
config.serviceMapEnabled,
config.ui.serviceMapApiV2Enabled,
isServiceMapApiV2Enabled,
],
{ preservePreviousData: false }
);

View file

@ -23,6 +23,7 @@ import {
apmEnableTableSearchBar,
apmEnableTransactionProfiling,
apmEnableServiceInventoryTableSearchBar,
apmEnableServiceMapApiV2,
} from '@kbn/observability-plugin/common';
import { isEmpty } from 'lodash';
import React from 'react';
@ -56,6 +57,7 @@ function getApmSettingsKeys(isProfilingIntegrationEnabled: boolean) {
enableAgentExplorerView,
apmEnableTableSearchBar,
apmEnableServiceInventoryTableSearchBar,
apmEnableServiceMapApiV2,
];
if (isProfilingIntegrationEnabled) {

View file

@ -29,6 +29,8 @@ interface Props {
onClick: () => void;
popoverContent?: React.ReactElement;
isLoading: boolean;
calloutId: string;
hideThisContent: string;
}
export function TryItButton({
@ -38,16 +40,15 @@ export function TryItButton({
popoverContent,
promoLabel,
isLoading,
calloutId,
hideThisContent,
}: Props) {
const [showFastFilterTryCallout, setShowFastFilterTryCallout] = useLocalStorage(
'apm.showFastFilterTryCallout',
true
);
const [showTryCallout, setShowFastFilterTryCallout] = useLocalStorage(`apm.${calloutId}`, true);
const { core } = useApmPluginContext();
const canEditAdvancedSettings = core.application.capabilities.advancedSettings?.save;
const [isPopoverOpen, togglePopover] = useToggle(false);
if (!showFastFilterTryCallout) {
if (!showTryCallout) {
return null;
}
@ -169,7 +170,7 @@ export function TryItButton({
function HideThisButton() {
return (
<EuiFlexItem grow={false}>
<EuiToolTip content="Hide this">
<EuiToolTip content={hideThisContent}>
<EuiButtonIcon
data-test-subj="apmHideThisButtonButton"
iconType="cross"

View file

@ -92,7 +92,6 @@ const mockCore = merge({}, coreStart, {
const mockConfig: ConfigSchema = {
serviceMapEnabled: true,
ui: {
serviceMapApiV2Enabled: false,
enabled: false,
},
latestAgentVersionsUrl: '',

View file

@ -12,7 +12,6 @@ import { ApmPlugin } from './plugin';
export interface ConfigSchema {
serviceMapEnabled: boolean;
ui: {
serviceMapApiV2Enabled: boolean;
enabled: boolean;
};
latestAgentVersionsUrl: string;

View file

@ -36,7 +36,6 @@ const configSchema = schema.object({
ui: schema.object({
enabled: schema.boolean({ defaultValue: true }),
maxTraceItems: schema.number({ defaultValue: 5000 }),
serviceMapApiV2Enabled: schema.boolean({ defaultValue: false }),
}),
searchAggregatedTransactions: schema.oneOf(
[

View file

@ -53,6 +53,7 @@ export {
profilingAzureCostDiscountRate,
apmEnableTransactionProfiling,
apmEnableServiceInventoryTableSearchBar,
apmEnableServiceMapApiV2,
profilingFetchTopNFunctionsFromStacktraces,
} from './ui_settings_keys';

View file

@ -46,3 +46,4 @@ export const apmEnableTransactionProfiling = 'observability:apmEnableTransaction
export const profilingFetchTopNFunctionsFromStacktraces =
'observability:profilingFetchTopNFunctionsFromStacktraces';
export const searchExcludedDataTiers = 'observability:searchExcludedDataTiers';
export const apmEnableServiceMapApiV2 = 'observability:apmEnableServiceMapApiV2';

View file

@ -41,6 +41,7 @@ export {
apmServiceGroupMaxNumberOfServices,
enableAgentExplorerView,
apmEnableTableSearchBar,
apmEnableServiceMapApiV2,
} from '../common/ui_settings_keys';
export {
alertsLocatorID,

View file

@ -45,6 +45,7 @@ import {
apmEnableServiceInventoryTableSearchBar,
profilingFetchTopNFunctionsFromStacktraces,
searchExcludedDataTiers,
apmEnableServiceMapApiV2,
} from '../common/ui_settings_keys';
const betaLabel = i18n.translate('xpack.observability.uiSettings.betaLabel', {
@ -365,6 +366,23 @@ export const uiSettings: Record<string, UiSettings> = {
type: 'boolean',
solution: 'oblt',
},
[apmEnableServiceMapApiV2]: {
category: [observabilityFeatureId],
name: i18n.translate('xpack.observability.apmEnableServiceMapApiV2', {
defaultMessage: 'Service Map API v2',
}),
description: i18n.translate('xpack.observability.apmEnableServiceMapApiV2Description', {
defaultMessage: '{technicalPreviewLabel} Enables the usage of the new Service Map API v2.',
values: {
technicalPreviewLabel: `<em>[${technicalPreviewLabel}]</em>`,
},
}),
schema: schema.boolean(),
value: false,
requiresPageReload: false,
type: 'boolean',
solution: 'oblt',
},
[apmAWSLambdaPriceFactor]: {
category: [observabilityFeatureId],
name: i18n.translate('xpack.observability.apmAWSLambdaPricePerGbSeconds', {