diff --git a/x-pack/solutions/observability/plugins/apm/public/components/app/service_map/__stories__/generate_elements.ts b/x-pack/solutions/observability/plugins/apm/public/components/app/service_map/__stories__/generate_elements.ts index 32ba3b64cd137..6445214a0143b 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/app/service_map/__stories__/generate_elements.ts +++ b/x-pack/solutions/observability/plugins/apm/public/components/app/service_map/__stories__/generate_elements.ts @@ -15,6 +15,7 @@ import type { GroupedNodeData, } from '../../../../../common/service_map'; import type { ServiceAnomalyStats } from '../../../../../common/anomaly_detection'; +import type { SloStatus } from '../../../../../common/service_inventory'; const AGENT_NAMES: AgentName[] = [ 'dotnet', @@ -92,6 +93,14 @@ function getRandomDependency(): (typeof DEPENDENCY_TYPES)[0] { return DEPENDENCY_TYPES[randn(DEPENDENCY_TYPES.length)]; } +const SLO_STATUSES: Array = [ + 'violated', + 'degrading', + 'healthy', + 'noData', + 'noSLOs', +]; + function createAnomalyStats(hasAnomalyData: boolean): ServiceAnomalyStats | undefined { if (!hasAnomalyData) { return undefined; @@ -107,6 +116,25 @@ function createAnomalyStats(hasAnomalyData: boolean): ServiceAnomalyStats | unde }; } +function createAlertData(hasAlerts: boolean): { alertsCount?: number } { + if (!hasAlerts || !probability(0.4)) { + return {}; + } + return { alertsCount: 1 + randn(10) }; +} + +function createSloData(hasSlos: boolean): { + sloStatus?: SloStatus | 'noSLOs'; + sloCount?: number; +} { + if (!hasSlos || !probability(0.5)) { + return {}; + } + const sloStatus = SLO_STATUSES[randn(SLO_STATUSES.length)]; + const sloCount = sloStatus === 'noSLOs' ? 0 : 1 + randn(5); + return { sloStatus, sloCount }; +} + function createDefaultEdgeStyle(color: string = '#c8c8c8') { return { type: 'default' as const, @@ -130,6 +158,8 @@ export interface GenerateOptions { groupedResourceCount: number; hasAnomalies: boolean; includeBidirectional: boolean; + hasAlerts?: boolean; + hasSlos?: boolean; } export function generateServiceMapElements(options: GenerateOptions): { @@ -143,6 +173,8 @@ export function generateServiceMapElements(options: GenerateOptions): { groupedResourceCount, hasAnomalies, includeBidirectional, + hasAlerts = false, + hasSlos = false, } = options; const nodes: ServiceMapNode[] = []; @@ -156,6 +188,8 @@ export function generateServiceMapElements(options: GenerateOptions): { isService: true, agentName: getRandomAgent(), serviceAnomalyStats: createAnomalyStats(hasAnomalies), + ...createAlertData(hasAlerts), + ...createSloData(hasSlos), }; nodes.push({ id, diff --git a/x-pack/solutions/observability/plugins/apm/public/components/app/service_map/__stories__/service_map.stories.tsx b/x-pack/solutions/observability/plugins/apm/public/components/app/service_map/__stories__/service_map.stories.tsx index 12cdd2bd683b2..89bfea10f74f4 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/app/service_map/__stories__/service_map.stories.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/app/service_map/__stories__/service_map.stories.tsx @@ -122,6 +122,8 @@ export const GenerateMap: StoryFn = () => { groupedResourceCount: 2, hasAnomalies: true, includeBidirectional: true, + hasAlerts: true, + hasSlos: true, }); const [json, setJson] = useState(''); const [{ nodes, edges }, setElements] = useState(() => generateServiceMapElements(options)); @@ -137,7 +139,7 @@ export const GenerateMap: StoryFn = () => { return (
- + { } /> + + setOptions((prev) => ({ ...prev, hasAlerts: e.target.checked }))} + /> + + + setOptions((prev) => ({ ...prev, hasSlos: e.target.checked }))} + /> + Generate diff --git a/x-pack/solutions/observability/plugins/apm/public/components/app/service_map/__stories__/service_node.stories.tsx b/x-pack/solutions/observability/plugins/apm/public/components/app/service_map/__stories__/service_node.stories.tsx index 8934fdf5ebfbc..c97204a3d37fe 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/app/service_map/__stories__/service_node.stories.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/app/service_map/__stories__/service_node.stories.tsx @@ -280,6 +280,173 @@ export const OpenTelemetryAgents: StoryObj = { }, }; +export const AlertBadges: StoryObj = { + render: () => ( +
+
+ + 1 active alert +
+
+ + 5 active alerts +
+
+ + 23 active alerts +
+
+ + 0 alerts (hidden) +
+
+ ), +}; + +export const SloBadges: StoryObj = { + render: () => ( +
+
+ + Violated (2 SLOs) +
+
+ + Degrading (3 SLOs) +
+
+ + Healthy (hidden on map) +
+
+ + No data (hidden on map) +
+
+ ), +}; + +export const AlertAndSloCombined: StoryObj = { + render: () => ( +
+
+ + Alerts + Violated SLO +
+
+ + Alerts + Degrading SLOs +
+
+ + Alerts + SLO + Critical anomaly +
+
+ ), +}; + export const HighlightStates: StoryObj = { render: () => (
diff --git a/x-pack/solutions/observability/plugins/apm/public/components/app/service_map/popover/popover.stories.tsx b/x-pack/solutions/observability/plugins/apm/public/components/app/service_map/popover/popover.stories.tsx index 415aa59f066d3..34282bd86890f 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/app/service_map/popover/popover.stories.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/app/service_map/popover/popover.stories.tsx @@ -142,6 +142,90 @@ export const Service: Story = { ), }; +const serviceNodeWithAlerts: ServiceMapNode = { + id: 'alerting-service', + type: 'service', + position: { x: 0, y: 0 }, + data: { + id: 'alerting-service', + label: 'alerting-service', + isService: true, + agentName: 'java', + alertsCount: 3, + }, +}; + +export const ServiceWithAlerts: Story = { + render: () => ( + + ), +}; + +const serviceNodeWithSlo: ServiceMapNode = { + id: 'slo-service', + type: 'service', + position: { x: 0, y: 0 }, + data: { + id: 'slo-service', + label: 'slo-service', + isService: true, + agentName: 'nodejs', + sloStatus: 'violated', + sloCount: 2, + }, +}; + +export const ServiceWithSlo: Story = { + render: () => ( + + ), +}; + +const serviceNodeWithAllBadges: ServiceMapNode = { + id: 'full-badges-service', + type: 'service', + position: { x: 0, y: 0 }, + data: { + id: 'full-badges-service', + label: 'full-badges-service', + isService: true, + agentName: 'python', + alertsCount: 5, + sloStatus: 'degrading', + sloCount: 3, + }, +}; + +export const ServiceWithAllBadges: Story = { + render: () => ( + + ), +}; + const edgeSelection: ServiceMapEdge = { id: 'e1', source: 'svc-a', diff --git a/x-pack/solutions/observability/plugins/apm/public/context/apm_plugin/mock_apm_plugin_storybook.tsx b/x-pack/solutions/observability/plugins/apm/public/context/apm_plugin/mock_apm_plugin_storybook.tsx index 3a9c31c4f147e..26fff7f07ce82 100644 --- a/x-pack/solutions/observability/plugins/apm/public/context/apm_plugin/mock_apm_plugin_storybook.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/context/apm_plugin/mock_apm_plugin_storybook.tsx @@ -87,6 +87,7 @@ const mockCore = { capabilities: { apm: {}, ml: {}, + slo: { read: true }, savedObjectsManagement: {}, }, currentAppId$: new Observable(),