Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
966605a
initial setup
jennypavlova Jan 8, 2026
2010546
Add nodes with icons and connection
jennypavlova Jan 8, 2026
6191fc2
Add connection edges highlighting
jennypavlova Jan 8, 2026
9141dcf
Add feature flag
jennypavlova Jan 9, 2026
206e189
Cleanup and separate components
jennypavlova Jan 9, 2026
97baea1
Extract layout logic and add missing dep
jennypavlova Jan 9, 2026
1e0dec6
Initial horizontal/vertical switcher
jennypavlova Jan 13, 2026
61f3cee
Remove unused LR (left to right) or BT (bottom to top) definitions
jennypavlova Jan 13, 2026
be50a5b
Persist hightlight color when switching the layout orientation
jennypavlova Jan 14, 2026
eef987c
Missing arrows in vertical mode fix and add comments
jennypavlova Jan 14, 2026
e8aa3aa
Fix edges position
jennypavlova Jan 14, 2026
d76052e
added popover
MiriamAparicio Jan 15, 2026
fccd13d
Fix issues with node highlighting and moving
jennypavlova Jan 15, 2026
238d2ac
clean up, fix reload when espace press on popover
MiriamAparicio Jan 15, 2026
855b88e
fix navigation from the popover
MiriamAparicio Jan 15, 2026
6e590bb
fix i18n
MiriamAparicio Jan 15, 2026
205e9e0
fix popover overlap diagnostics flyout
MiriamAparicio Jan 15, 2026
3c5b284
Arrow size fix
jennypavlova Jan 15, 2026
6e7cd1d
Merge branch 'react-flow-service-map' of https://github.com/jennypavl…
jennypavlova Jan 15, 2026
9172adb
[APM] Add Service map PoC tests
jennypavlova Jan 19, 2026
7c88e3c
A11y improvements and review
MiriamAparicio Jan 20, 2026
5fa9361
Merge pull request #23 from jennypavlova/react-flow-service-map-test
jennypavlova Jan 20, 2026
1ae1840
added span grouping
MiriamAparicio Jan 20, 2026
63f9375
arrow key navigation
MiriamAparicio Jan 20, 2026
57bd129
Services expand/collapse init
jennypavlova Jan 20, 2026
65640cc
Add collapsable groups in the react flow service map by service group
jennypavlova Jan 20, 2026
00d2810
Cleanup
jennypavlova Jan 20, 2026
b429770
Merge branch 'react-flow-service-map' into react-flow-service-map-exp…
jennypavlova Jan 21, 2026
5b284aa
Merge pull request #24 from jennypavlova/react-flow-service-map-expan…
jennypavlova Jan 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ export const defaultConfig: ScoutServerConfig = {
`--permissionsPolicy.report_to=${JSON.stringify(['violations-endpoint'])}`,
// Allow dynamic config overrides in tests
`--coreApp.allowDynamicConfigOverrides=true`,
// APM feature flags
'--xpack.apm.featureFlags.serviceMapUseReactFlow=true',
`--xpack.fleet.fleetServerHosts=${JSON.stringify([
{
id: 'default-fleet-server',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ export const defaultConfig: ScoutServerConfig = {
'--xpack.ruleRegistry.write.cache.enabled=false',
'--monitoring_collection.opentelemetry.metrics.prometheus.enabled=true',
'--xpack.profiling.enabled=true',
// APM feature flags
'--xpack.apm.featureFlags.serviceMapUseReactFlow=true',
// Fleet configuration
`--xpack.fleet.fleetServerHosts=${JSON.stringify([
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
'xpack.apm.featureFlags.infrastructureTabAvailable (boolean?|true?)',
'xpack.apm.featureFlags.infraUiAvailable (boolean?|true?)',
'xpack.apm.featureFlags.migrationToFleetAvailable (boolean?|true?)',
'xpack.apm.featureFlags.serviceMapUseReactFlow (boolean?)',
'xpack.apm.featureFlags.sourcemapApiAvailable (boolean?|true?)',
'xpack.apm.featureFlags.storageExplorerAvailable (boolean?|true?)',
// to be removed in https://github.com/elastic/kibana/issues/221904
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export enum ApmFeatureFlagName {
InfrastructureTabAvailable = 'infrastructureTabAvailable',
InfraUiAvailable = 'infraUiAvailable',
MigrationToFleetAvailable = 'migrationToFleetAvailable',
ServiceMapUseReactFlow = 'serviceMapUseReactFlow',
SourcemapApiAvailable = 'sourcemapApiAvailable',
StorageExplorerAvailable = 'storageExplorerAvailable',
RuleFormV2Enabled = 'ruleFormV2Enabled',
Expand All @@ -40,6 +41,10 @@ const apmFeatureFlagMap = {
default: true,
type: t.boolean,
},
[ApmFeatureFlagName.ServiceMapUseReactFlow]: {
default: false,
type: t.boolean,
},
[ApmFeatureFlagName.SourcemapApiAvailable]: {
default: true,
type: t.boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export function ServiceGroupSaveButton() {

const {
query: { serviceGroup },
} = useAnyOfApmParams('/service-groups', '/services', '/service-map');
} = useAnyOfApmParams('/service-groups', '/services', '/service-map', '/react-flow-service-map');

const isGroupEditMode = !!serviceGroup;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export function DiagnosticConfigurationForm({
} = useAnyOfApmParams(
'/services/{serviceName}/service-map',
'/service-map',
'/react-flow-service-map',
'/mobile-services/{serviceName}/service-map'
);
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export function DiagnosticFlyout({ onClose, isOpen, selectedNode }: DiagnosticFl
} = useAnyOfApmParams(
'/services/{serviceName}/service-map',
'/service-map',
'/react-flow-service-map',
'/mobile-services/{serviceName}/service-map'
);
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
Expand Down Expand Up @@ -114,10 +115,10 @@ export function DiagnosticFlyout({ onClose, isOpen, selectedNode }: DiagnosticFl
}
} catch (error) {
notifications?.toasts.addDanger({
title: i18n.translate('xpack.apm.diagnosticFlyout.errorTitle', {
title: i18n.translate('xpack.apm.serviceMap.diagnosticFlyout.errorTitle', {
defaultMessage: 'Failed to run diagnostic',
}),
text: i18n.translate('xpack.apm.diagnosticFlyout.errorMessage', {
text: i18n.translate('xpack.apm.serviceMap.diagnosticFlyout.errorMessage', {
defaultMessage: 'An error occurred while running the diagnostic. Please try again.',
}),
});
Expand Down Expand Up @@ -224,7 +225,7 @@ export function DiagnosticFlyout({ onClose, isOpen, selectedNode }: DiagnosticFl
data-test-subj="apmDiagnosticRunButton"
onClick={handleRunDiagnostic}
>
{i18n.translate('xpack.apm.diagnosticFlyout.closeButtonLabel', {
{i18n.translate('xpack.apm.serviceMap.diagnosticFlyout.closeButtonLabel', {
defaultMessage: 'Run diagnostic',
})}
</EuiButton>
Expand Down Expand Up @@ -252,7 +253,7 @@ export function DiagnosticFlyout({ onClose, isOpen, selectedNode }: DiagnosticFl
}
fill
>
{i18n.translate('xpack.apm.storageExplorer.downloadReport', {
{i18n.translate('xpack.apm.serviceMap.diagnosticFlyout.downloadReport', {
defaultMessage: 'Download report',
})}
</EuiButton>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import type { Environment } from '../../../../common/environment_rt';
import { useTimeRange } from '../../../hooks/use_time_range';
import { DisabledPrompt } from './disabled_prompt';
import { useServiceMap } from './use_service_map';
import { ReactFlowServiceMap } from './react_flow_service_map';

function PromptContainer({ children }: { children: ReactNode }) {
return (
Expand Down Expand Up @@ -70,6 +71,22 @@ export function ServiceMapHome() {
);
}

export function ReactFlowServiceMapHome() {
const {
query: { environment, kuery, rangeFrom, rangeTo, serviceGroup },
} = useApmParams('/react-flow-service-map');
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
return (
<ServiceMapWithReactFlow
environment={environment}
kuery={kuery}
start={start}
end={end}
serviceGroupId={serviceGroup}
/>
);
}

export function ServiceMapServiceDetail() {
const {
query: { environment, kuery, rangeFrom, rangeTo },
Expand All @@ -82,19 +99,15 @@ export function ServiceMapServiceDetail() {
return <ServiceMap environment={environment} kuery={kuery} start={start} end={end} />;
}

export function ServiceMap({
environment,
kuery,
start,
end,
serviceGroupId,
}: {
interface ServiceMapProps {
environment: Environment;
kuery: string;
start: string;
end: string;
serviceGroupId?: string;
}) {
}

export function ServiceMap({ environment, kuery, start, end, serviceGroupId }: ServiceMapProps) {
const { euiTheme } = useEuiTheme();
const license = useLicenseContext();
const serviceName = useServiceName();
Expand Down Expand Up @@ -207,3 +220,119 @@ export function ServiceMap({
</>
);
}

/**
* React Flow Service Map implementation (POC)
* Uses the same data as the Cytoscape version but renders with React Flow
*/
export function ServiceMapWithReactFlow({
environment,
kuery,
start,
end,
serviceGroupId,
}: ServiceMapProps) {
const license = useLicenseContext();
const serviceName = useServiceName();

const { config } = useApmPluginContext();
const { onPageReady } = usePerformanceContext();

const subscriptions = useRef<Subscription>(new Subscription());

useEffect(() => {
const currentSubscriptions = subscriptions.current;
return () => {
currentSubscriptions.unsubscribe();
};
}, []);

const { data, status, error } = useServiceMap({
environment,
kuery,
start,
end,
serviceGroupId,
serviceName,
});

const { ref, height } = useRefDimensions();

// Temporary hack to work around bottom padding introduced by EuiPage
const PADDING_BOTTOM = 24;
const heightWithPadding = height - PADDING_BOTTOM;

if (!license) {
return null;
}

if (!isActivePlatinumLicense(license)) {
return (
<PromptContainer>
<LicensePrompt text={invalidLicenseMessage} />
</PromptContainer>
);
}

if (!config.serviceMapEnabled) {
return (
<PromptContainer>
<DisabledPrompt />
</PromptContainer>
);
}

if (status === FETCH_STATUS.SUCCESS && data.elements.length === 0) {
return (
<PromptContainer>
<EmptyPrompt />
</PromptContainer>
);
}

if (
status === FETCH_STATUS.FAILURE &&
error &&
'body' in error &&
error.body?.statusCode === 500 &&
error.body?.message === SERVICE_MAP_TIMEOUT_ERROR
) {
return (
<PromptContainer>
<TimeoutPrompt isGlobalServiceMap={!serviceName} />
</PromptContainer>
);
}

if (status === FETCH_STATUS.SUCCESS) {
onPageReady({
customMetrics: {
key1: 'num_of_nodes',
value1: data.nodesCount,
key2: 'num_of_traces',
value2: data.tracesCount,
},
meta: { rangeFrom: start, rangeTo: end },
});
}

return (
<>
<SearchBar showTimeComparison />
<EuiPanel hasBorder={true} paddingSize="none">
<div data-test-subj="reactFlowServiceMap" style={{ height: heightWithPadding }} ref={ref}>
<ReactFlowServiceMap
elements={data.elements}
height={heightWithPadding}
serviceName={serviceName}
status={status}
environment={environment}
kuery={kuery}
start={start}
end={end}
/>
</div>
</EuiPanel>
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export function DependencyContents({ elementData, environment, start, end }: Con

const { query } = useAnyOfApmParams(
'/service-map',
'/react-flow-service-map',
'/services/{serviceName}/service-map',
'/mobile-services/{serviceName}/service-map'
);
Expand Down Expand Up @@ -94,7 +95,7 @@ export function DependencyContents({ elementData, environment, start, end }: Con
});
}}
>
{i18n.translate('xpack.apm.serviceMap.dependencyDetailsButtonText', {
{i18n.translate('xpack.actions.serviceMap.dependencyDetailsButtonText', {
defaultMessage: 'Dependency Details',
})}
</EuiButton>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export function ServiceContents({ onFocusClick, elementData, environment, kuery

const { query } = useAnyOfApmParams(
'/service-map',
'/react-flow-service-map',
'/services/{serviceName}/service-map',
'/mobile-services/{serviceName}/service-map'
);
Expand Down
Loading