Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -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',
Expand Down Expand Up @@ -92,6 +93,14 @@ function getRandomDependency(): (typeof DEPENDENCY_TYPES)[0] {
return DEPENDENCY_TYPES[randn(DEPENDENCY_TYPES.length)];
}

const SLO_STATUSES: Array<SloStatus | 'noSLOs'> = [
'violated',
'degrading',
'healthy',
'noData',
'noSLOs',
];

function createAnomalyStats(hasAnomalyData: boolean): ServiceAnomalyStats | undefined {
if (!hasAnomalyData) {
return undefined;
Expand All @@ -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,
Expand All @@ -130,6 +158,8 @@ export interface GenerateOptions {
groupedResourceCount: number;
hasAnomalies: boolean;
includeBidirectional: boolean;
hasAlerts?: boolean;
hasSlos?: boolean;
}

export function generateServiceMapElements(options: GenerateOptions): {
Expand All @@ -143,6 +173,8 @@ export function generateServiceMapElements(options: GenerateOptions): {
groupedResourceCount,
hasAnomalies,
includeBidirectional,
hasAlerts = false,
hasSlos = false,
} = options;

const nodes: ServiceMapNode[] = [];
Expand All @@ -156,6 +188,8 @@ export function generateServiceMapElements(options: GenerateOptions): {
isService: true,
agentName: getRandomAgent(),
serviceAnomalyStats: createAnomalyStats(hasAnomalies),
...createAlertData(hasAlerts),
...createSloData(hasSlos),
};
nodes.push({
id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ export const GenerateMap: StoryFn = () => {
groupedResourceCount: 2,
hasAnomalies: true,
includeBidirectional: true,
hasAlerts: true,
hasSlos: true,
});
const [json, setJson] = useState<string>('');
const [{ nodes, edges }, setElements] = useState(() => generateServiceMapElements(options));
Expand All @@ -137,7 +139,7 @@ export const GenerateMap: StoryFn = () => {

return (
<div style={{ padding: 16 }}>
<EuiFlexGroup wrap>
<EuiFlexGroup wrap alignItems="center">
<EuiFlexItem grow={false}>
<EuiToolTip content="Number of service nodes">
<EuiFieldNumber
Expand Down Expand Up @@ -191,6 +193,20 @@ export const GenerateMap: StoryFn = () => {
}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiSwitch
label="Alert Badges"
checked={options.hasAlerts ?? false}
onChange={(e) => setOptions((prev) => ({ ...prev, hasAlerts: e.target.checked }))}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiSwitch
label="SLO Badges"
checked={options.hasSlos ?? false}
onChange={(e) => setOptions((prev) => ({ ...prev, hasSlos: e.target.checked }))}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton data-test-subj="generateMapButton" onClick={handleGenerate} iconType="refresh">
Generate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,173 @@ export const OpenTelemetryAgents: StoryObj = {
},
};

export const AlertBadges: StoryObj = {
render: () => (
<div style={{ display: 'flex', gap: 48, flexWrap: 'wrap' }}>
<div style={{ textAlign: 'center' }}>
<ServiceNode
{...createNodeProps({
id: 'single-alert',
label: 'single-alert',
isService: true,
agentName: 'java',
alertsCount: 1,
})}
/>
<LabelText>1 active alert</LabelText>
</div>
<div style={{ textAlign: 'center' }}>
<ServiceNode
{...createNodeProps({
id: 'multiple-alerts',
label: 'multiple-alerts',
isService: true,
agentName: 'nodejs',
alertsCount: 5,
})}
/>
<LabelText>5 active alerts</LabelText>
</div>
<div style={{ textAlign: 'center' }}>
<ServiceNode
{...createNodeProps({
id: 'many-alerts',
label: 'many-alerts',
isService: true,
agentName: 'python',
alertsCount: 23,
})}
/>
<LabelText>23 active alerts</LabelText>
</div>
<div style={{ textAlign: 'center' }}>
<ServiceNode
{...createNodeProps({
id: 'no-alerts',
label: 'no-alerts',
isService: true,
agentName: 'go',
alertsCount: 0,
})}
/>
<LabelText>0 alerts (hidden)</LabelText>
</div>
</div>
),
};

export const SloBadges: StoryObj = {
render: () => (
<div style={{ display: 'flex', gap: 48, flexWrap: 'wrap' }}>
<div style={{ textAlign: 'center' }}>
<ServiceNode
{...createNodeProps({
id: 'slo-violated',
label: 'slo-violated',
isService: true,
agentName: 'java',
sloStatus: 'violated',
sloCount: 2,
})}
/>
<LabelText>Violated (2 SLOs)</LabelText>
</div>
<div style={{ textAlign: 'center' }}>
<ServiceNode
{...createNodeProps({
id: 'slo-degrading',
label: 'slo-degrading',
isService: true,
agentName: 'nodejs',
sloStatus: 'degrading',
sloCount: 3,
})}
/>
<LabelText>Degrading (3 SLOs)</LabelText>
</div>
<div style={{ textAlign: 'center' }}>
<ServiceNode
{...createNodeProps({
id: 'slo-healthy',
label: 'slo-healthy',
isService: true,
agentName: 'python',
sloStatus: 'healthy',
sloCount: 5,
})}
/>
<LabelText>Healthy (hidden on map)</LabelText>
</div>
<div style={{ textAlign: 'center' }}>
<ServiceNode
{...createNodeProps({
id: 'slo-no-data',
label: 'slo-no-data',
isService: true,
agentName: 'go',
sloStatus: 'noData',
sloCount: 1,
})}
/>
<LabelText>No data (hidden on map)</LabelText>
</div>
</div>
),
};

export const AlertAndSloCombined: StoryObj = {
render: () => (
<div style={{ display: 'flex', gap: 48, flexWrap: 'wrap' }}>
<div style={{ textAlign: 'center' }}>
<ServiceNode
{...createNodeProps({
id: 'alerts-and-violated',
label: 'alerts-and-violated',
isService: true,
agentName: 'java',
alertsCount: 3,
sloStatus: 'violated',
sloCount: 1,
})}
/>
<LabelText>Alerts + Violated SLO</LabelText>
</div>
<div style={{ textAlign: 'center' }}>
<ServiceNode
{...createNodeProps({
id: 'alerts-and-degrading',
label: 'alerts-and-degrading',
isService: true,
agentName: 'nodejs',
alertsCount: 2,
sloStatus: 'degrading',
sloCount: 4,
})}
/>
<LabelText>Alerts + Degrading SLOs</LabelText>
</div>
<div style={{ textAlign: 'center' }}>
<ServiceNode
{...createNodeProps({
id: 'alerts-anomaly-slo',
label: 'alerts-anomaly-slo',
isService: true,
agentName: 'python',
alertsCount: 7,
sloStatus: 'violated',
sloCount: 2,
serviceAnomalyStats: {
transactionType: 'request',
anomalyScore: 85,
},
})}
/>
<LabelText>Alerts + SLO + Critical anomaly</LabelText>
</div>
</div>
),
};

export const HighlightStates: StoryObj = {
render: () => (
<div style={{ display: 'flex', gap: 48, flexWrap: 'wrap' }}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: () => (
<PopoverContent
selectedNode={serviceNodeWithAlerts}
selectedEdge={null}
environment="ENVIRONMENT_ALL"
kuery=""
start="now-15m"
end="now"
onFocusClick={noop}
/>
),
};

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: () => (
<PopoverContent
selectedNode={serviceNodeWithSlo}
selectedEdge={null}
environment="ENVIRONMENT_ALL"
kuery=""
start="now-15m"
end="now"
onFocusClick={noop}
/>
),
};

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: () => (
<PopoverContent
selectedNode={serviceNodeWithAllBadges}
selectedEdge={null}
environment="ENVIRONMENT_ALL"
kuery=""
start="now-15m"
end="now"
onFocusClick={noop}
/>
),
};

const edgeSelection: ServiceMapEdge = {
id: 'e1',
source: 'svc-a',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ const mockCore = {
capabilities: {
apm: {},
ml: {},
slo: { read: true },
savedObjectsManagement: {},
},
currentAppId$: new Observable(),
Expand Down
Loading