@principal-ai/subsystems-react 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +7 -44
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -32
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +9 -199
- package/src/components/ConfigurationSelector.tsx +0 -133
- package/src/components/GraphRenderer.tsx +0 -3195
- package/src/components/MultiCanvasRenderer.tsx +0 -295
- package/src/components/NodeTooltip.tsx +0 -279
- package/src/components/PendingChanges.test.tsx +0 -375
- package/src/components/SelectionSidebar.tsx +0 -352
- package/src/components/SequenceDiagramRenderer.tsx +0 -1156
- package/src/components/WorkflowSequenceDiagram.tsx +0 -270
- package/src/components/dashboard/DashboardRenderer.tsx +0 -317
- package/src/components/dashboard/MetricPanel.tsx +0 -254
- package/src/components/dashboard/MockDataProvider.ts +0 -330
- package/src/components/dashboard/components/BarChart.tsx +0 -299
- package/src/components/dashboard/components/LineChart.tsx +0 -279
- package/src/components/dashboard/components/MetricCard.tsx +0 -270
- package/src/components/dashboard/components/SourceLink.tsx +0 -63
- package/src/components/dashboard/components/TimeRangeSelector.tsx +0 -280
- package/src/components/dashboard/components/index.ts +0 -5
- package/src/components/dashboard/index.ts +0 -47
- package/src/components/dashboard/types.ts +0 -126
- package/src/components/state-view/PipelineView.tsx +0 -347
- package/src/components/state-view/index.ts +0 -14
- package/src/components/state-view/types.ts +0 -261
- package/src/components/state-view/useStateView.ts +0 -205
- package/src/contexts/GraphEditContext.tsx +0 -27
- package/src/contexts/TooltipPortalContext.ts +0 -8
- package/src/edges/CustomEdge.tsx +0 -402
- package/src/edges/GenericEdge.tsx +0 -36
- package/src/hooks/usePathBasedEvents.ts +0 -192
- package/src/hooks/useSequenceLayout.ts +0 -543
- package/src/hooks/useUndoRedo.ts +0 -144
- package/src/nodes/CustomNode.tsx +0 -1231
- package/src/nodes/GenericNode.tsx +0 -55
- package/src/nodes/otel/OtelBoundaryNode.tsx +0 -224
- package/src/nodes/otel/OtelEventNode.tsx +0 -225
- package/src/nodes/otel/OtelResourceNode.tsx +0 -251
- package/src/nodes/otel/OtelScopeNode.tsx +0 -221
- package/src/nodes/otel/OtelSpanConventionNode.tsx +0 -295
- package/src/nodes/otel/index.ts +0 -23
- package/src/nodes/otel/shared/NodeBadges.tsx +0 -295
- package/src/nodes/otel/shared/NodeContent.tsx +0 -204
- package/src/nodes/otel/shared/index.ts +0 -8
- package/src/nodes/otel/shared/types.ts +0 -138
- package/src/nodes/otel/shared/useNodeBehavior.ts +0 -114
- package/src/stories/Audit/BoundaryNodes.stories.tsx +0 -385
- package/src/stories/Audit/CanvasEdgeTypes.stories.tsx +0 -1448
- package/src/stories/Audit/CanvasNodeTypes.stories.tsx +0 -1049
- package/src/stories/Audit/ColorPriority.stories.tsx +0 -895
- package/src/stories/Audit/DiamondBadges.stories.tsx +0 -629
- package/src/stories/Audit/NodeDimensionsTesting.stories.tsx +0 -724
- package/src/stories/Audit/NodeFieldsAudit.stories.tsx +0 -996
- package/src/stories/Audit/NodeFontSizeTesting.stories.tsx +0 -715
- package/src/stories/Audit/NodeShapes.stories.tsx +0 -1208
- package/src/stories/Audit/WideNodesTesting.stories.tsx +0 -1045
- package/src/stories/Components/DashboardRenderer.stories.tsx +0 -263
- package/src/stories/Components/GraphRenderer.stories.tsx +0 -3236
- package/src/stories/Components/MultiCanvasRenderer.stories.tsx +0 -370
- package/src/stories/Components/SequenceDiagram.stories.tsx +0 -255
- package/src/stories/Components/StateView.stories.tsx +0 -417
- package/src/stories/Components/TraceViewer.stories.tsx +0 -442
- package/src/stories/Components/WorkflowSequenceDiagram.stories.tsx +0 -802
- package/src/stories/Features/AnimationWorkshop.stories.tsx +0 -727
- package/src/stories/Features/ChangeTypeVisual.stories.tsx +0 -67
- package/src/stories/Features/EventDrivenAnimations.stories.tsx +0 -513
- package/src/stories/Features/MultiConfig.stories.tsx +0 -539
- package/src/stories/Layout/ElkEdgeRouting.stories.tsx +0 -539
- package/src/stories/Layout/GraphOrientation.stories.tsx +0 -500
- package/src/stories/Layout/MultiDirectionalConnections.stories.tsx +0 -352
- package/src/stories/Nodes/NodeDefinitionComparison.stories.tsx +0 -560
- package/src/stories/Nodes/NodeNameFallbacks.stories.tsx +0 -491
- package/src/stories/Nodes/NumericColors.stories.tsx +0 -440
- package/src/stories/OTEL/EventNameDisplay.stories.tsx +0 -694
- package/src/stories/OTEL/EventNameFallback.stories.tsx +0 -578
- package/src/stories/OTEL/OtelComponents.stories.tsx +0 -422
- package/src/stories/OTEL/OtelNodeTypesPrototype.stories.tsx +0 -806
- package/src/stories/OTEL/RawEventCards.stories.tsx +0 -57
- package/src/stories/OTEL/SpanBadges.stories.tsx +0 -840
- package/src/stories/OTEL/ValidationEventsCanvas.stories.tsx +0 -209
- package/src/stories/RealWorld/BacklogMdCanvases.stories.tsx +0 -721
- package/src/stories/RealWorld/FileCitySequence.stories.tsx +0 -516
- package/src/stories/RealWorld/RealCanvasFiles.stories.tsx +0 -336
- package/src/stories/__traces__/test-run.canvas.json +0 -241
- package/src/stories/data/activity-feed-analytics.dashboard.json +0 -300
- package/src/stories/data/file-city-images-transformed.otel.canvas.json +0 -295
- package/src/stories/data/file-city-workflows.json +0 -269
- package/src/stories/data/graph-converter-test-execution.json +0 -443
- package/src/stories/data/graph-converter-test.workflow.json +0 -105
- package/src/stories/data/graph-converter-validated-execution.json +0 -58
- package/src/utils/animationMapping.ts +0 -166
- package/src/utils/canvasBounds.ts +0 -146
- package/src/utils/graphConverter.ts +0 -148
- package/src/utils/iconResolver.tsx +0 -53
- package/src/utils/orientationUtils.ts +0 -73
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MetricPanel
|
|
3
|
-
*
|
|
4
|
-
* Wrapper component that determines which visualization to render
|
|
5
|
-
* based on the metric definition and data.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import React from 'react';
|
|
9
|
-
import { useTheme } from '@principal-ade/industry-theme';
|
|
10
|
-
import type {
|
|
11
|
-
MetricPanelProps,
|
|
12
|
-
MetricDefinition,
|
|
13
|
-
MetricData,
|
|
14
|
-
DisplayComponent,
|
|
15
|
-
} from './types';
|
|
16
|
-
import { MetricCard, LineChart, BarChart, SourceLink } from './components';
|
|
17
|
-
|
|
18
|
-
export function MetricPanel({
|
|
19
|
-
metric,
|
|
20
|
-
data,
|
|
21
|
-
onMetricClick,
|
|
22
|
-
onSourceClick,
|
|
23
|
-
}: MetricPanelProps) {
|
|
24
|
-
const { theme } = useTheme();
|
|
25
|
-
|
|
26
|
-
// Determine which component to use
|
|
27
|
-
const componentType = getComponentType(metric, data);
|
|
28
|
-
|
|
29
|
-
// Render loading state
|
|
30
|
-
if (data.loading) {
|
|
31
|
-
return (
|
|
32
|
-
<div
|
|
33
|
-
style={{
|
|
34
|
-
backgroundColor: theme.colors.surface || theme.colors.background,
|
|
35
|
-
border: `1px solid ${theme.colors.border}`,
|
|
36
|
-
borderRadius: theme.radii?.[2] || 8,
|
|
37
|
-
padding: theme.space?.[3] || 16,
|
|
38
|
-
minHeight: 100,
|
|
39
|
-
display: 'flex',
|
|
40
|
-
alignItems: 'center',
|
|
41
|
-
justifyContent: 'center',
|
|
42
|
-
fontFamily: theme.fonts.body,
|
|
43
|
-
}}
|
|
44
|
-
>
|
|
45
|
-
<span style={{ color: theme.colors.textSecondary, fontSize: theme.fontSizes[1] }}>
|
|
46
|
-
Loading...
|
|
47
|
-
</span>
|
|
48
|
-
</div>
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Render error state
|
|
53
|
-
if (data.error) {
|
|
54
|
-
return (
|
|
55
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
56
|
-
<div
|
|
57
|
-
style={{
|
|
58
|
-
backgroundColor: theme.colors.surface || theme.colors.background,
|
|
59
|
-
border: `1px solid ${theme.colors.error}`,
|
|
60
|
-
borderRadius: theme.radii?.[2] || 8,
|
|
61
|
-
padding: theme.space?.[3] || 16,
|
|
62
|
-
fontFamily: theme.fonts.body,
|
|
63
|
-
}}
|
|
64
|
-
>
|
|
65
|
-
<div
|
|
66
|
-
style={{
|
|
67
|
-
color: theme.colors.error,
|
|
68
|
-
marginBottom: theme.space?.[2] || 8,
|
|
69
|
-
fontSize: theme.fontSizes[1],
|
|
70
|
-
fontWeight: theme.fontWeights.medium,
|
|
71
|
-
}}
|
|
72
|
-
>
|
|
73
|
-
{metric.name}
|
|
74
|
-
</div>
|
|
75
|
-
<div style={{ color: theme.colors.textSecondary, fontSize: theme.fontSizes[0] }}>
|
|
76
|
-
{data.error}
|
|
77
|
-
</div>
|
|
78
|
-
</div>
|
|
79
|
-
|
|
80
|
-
{/* Source links - show even in error state for navigation to workflows */}
|
|
81
|
-
{metric.sources && metric.sources.length > 0 && onSourceClick && (
|
|
82
|
-
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
|
83
|
-
{metric.sources.map((source, i) => (
|
|
84
|
-
<SourceLink key={i} source={source} onClick={onSourceClick} />
|
|
85
|
-
))}
|
|
86
|
-
</div>
|
|
87
|
-
)}
|
|
88
|
-
</div>
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const handleClick = () => {
|
|
93
|
-
onMetricClick?.(metric.id);
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
return (
|
|
97
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
98
|
-
{/* The visualization component */}
|
|
99
|
-
{renderComponent(componentType, metric, data, handleClick)}
|
|
100
|
-
|
|
101
|
-
{/* Source links */}
|
|
102
|
-
{metric.sources && metric.sources.length > 0 && onSourceClick && (
|
|
103
|
-
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
|
104
|
-
{metric.sources.map((source, i) => (
|
|
105
|
-
<SourceLink key={i} source={source} onClick={onSourceClick} />
|
|
106
|
-
))}
|
|
107
|
-
</div>
|
|
108
|
-
)}
|
|
109
|
-
</div>
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Determine which component to render based on metric definition and data
|
|
115
|
-
*/
|
|
116
|
-
function getComponentType(metric: MetricDefinition, data: MetricData): DisplayComponent {
|
|
117
|
-
// Explicit display hint takes precedence
|
|
118
|
-
if (metric.display?.component) {
|
|
119
|
-
return metric.display.component;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const { type, query } = metric;
|
|
123
|
-
|
|
124
|
-
// Time series data → chart
|
|
125
|
-
if (data.series && data.series.length > 0) {
|
|
126
|
-
if (query.groupBy && query.groupBy.length > 0) {
|
|
127
|
-
return 'StackedBarChart';
|
|
128
|
-
}
|
|
129
|
-
return 'LineChart';
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Histogram data
|
|
133
|
-
if (data.histogram) {
|
|
134
|
-
return 'Histogram';
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Counter with time grouping → line/bar chart (but no series data yet)
|
|
138
|
-
if (type === 'counter' && query.timeGroup) {
|
|
139
|
-
return query.groupBy?.length ? 'BarChart' : 'LineChart';
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Gauge (percentage) → gauge or metric card
|
|
143
|
-
if (type === 'gauge') {
|
|
144
|
-
return metric.thresholds ? 'GaugeChart' : 'MetricCard';
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Default to metric card
|
|
148
|
-
return 'MetricCard';
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Render the appropriate component
|
|
153
|
-
*/
|
|
154
|
-
function renderComponent(
|
|
155
|
-
componentType: DisplayComponent,
|
|
156
|
-
metric: MetricDefinition,
|
|
157
|
-
data: MetricData,
|
|
158
|
-
onClick?: () => void
|
|
159
|
-
): React.ReactNode {
|
|
160
|
-
switch (componentType) {
|
|
161
|
-
case 'MetricCard':
|
|
162
|
-
case 'GaugeChart': // Use MetricCard for now, can add GaugeChart later
|
|
163
|
-
return (
|
|
164
|
-
<MetricCard
|
|
165
|
-
title={metric.name}
|
|
166
|
-
value={data.current ?? 0}
|
|
167
|
-
unit={metric.unit}
|
|
168
|
-
description={metric.description}
|
|
169
|
-
trend={data.trend}
|
|
170
|
-
changePercent={data.changePercent}
|
|
171
|
-
showTrend={metric.display?.showTrend ?? true}
|
|
172
|
-
showSparkline={metric.display?.showSparkline}
|
|
173
|
-
sparklineData={data.series}
|
|
174
|
-
thresholds={metric.thresholds}
|
|
175
|
-
size={metric.display?.size || 'medium'}
|
|
176
|
-
onClick={onClick}
|
|
177
|
-
/>
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
case 'LineChart':
|
|
181
|
-
return (
|
|
182
|
-
<LineChart
|
|
183
|
-
title={metric.name}
|
|
184
|
-
data={data.series || []}
|
|
185
|
-
xKey="date"
|
|
186
|
-
yKey="value"
|
|
187
|
-
unit={metric.unit}
|
|
188
|
-
onClick={onClick}
|
|
189
|
-
/>
|
|
190
|
-
);
|
|
191
|
-
|
|
192
|
-
case 'BarChart':
|
|
193
|
-
case 'StackedBarChart':
|
|
194
|
-
// Extract series keys from data
|
|
195
|
-
const series = data.series && data.series.length > 0
|
|
196
|
-
? Object.keys(data.series[0]).filter(k => k !== 'date')
|
|
197
|
-
: metric.query.groupBy || [];
|
|
198
|
-
|
|
199
|
-
return (
|
|
200
|
-
<BarChart
|
|
201
|
-
title={metric.name}
|
|
202
|
-
data={data.series || []}
|
|
203
|
-
xKey="date"
|
|
204
|
-
series={series}
|
|
205
|
-
stacked={componentType === 'StackedBarChart'}
|
|
206
|
-
unit={metric.unit}
|
|
207
|
-
onClick={onClick}
|
|
208
|
-
/>
|
|
209
|
-
);
|
|
210
|
-
|
|
211
|
-
case 'Histogram':
|
|
212
|
-
// TODO: Implement Histogram component
|
|
213
|
-
return (
|
|
214
|
-
<MetricCard
|
|
215
|
-
title={metric.name}
|
|
216
|
-
value="Histogram (TODO)"
|
|
217
|
-
description={metric.description}
|
|
218
|
-
onClick={onClick}
|
|
219
|
-
/>
|
|
220
|
-
);
|
|
221
|
-
|
|
222
|
-
case 'PieChart':
|
|
223
|
-
// TODO: Implement PieChart component
|
|
224
|
-
return (
|
|
225
|
-
<MetricCard
|
|
226
|
-
title={metric.name}
|
|
227
|
-
value="PieChart (TODO)"
|
|
228
|
-
description={metric.description}
|
|
229
|
-
onClick={onClick}
|
|
230
|
-
/>
|
|
231
|
-
);
|
|
232
|
-
|
|
233
|
-
case 'DataTable':
|
|
234
|
-
// TODO: Implement DataTable component
|
|
235
|
-
return (
|
|
236
|
-
<MetricCard
|
|
237
|
-
title={metric.name}
|
|
238
|
-
value="DataTable (TODO)"
|
|
239
|
-
description={metric.description}
|
|
240
|
-
onClick={onClick}
|
|
241
|
-
/>
|
|
242
|
-
);
|
|
243
|
-
|
|
244
|
-
default:
|
|
245
|
-
return (
|
|
246
|
-
<MetricCard
|
|
247
|
-
title={metric.name}
|
|
248
|
-
value={data.current ?? 0}
|
|
249
|
-
unit={metric.unit}
|
|
250
|
-
onClick={onClick}
|
|
251
|
-
/>
|
|
252
|
-
);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
@@ -1,330 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MockDataProvider
|
|
3
|
-
*
|
|
4
|
-
* Provides mock data for dashboard metrics during prototyping.
|
|
5
|
-
* Can use inline _mockData from the dashboard definition or generate realistic data.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type {
|
|
9
|
-
DashboardDefinition,
|
|
10
|
-
MetricDefinition,
|
|
11
|
-
MetricData,
|
|
12
|
-
DataProvider,
|
|
13
|
-
TimeSeriesPoint,
|
|
14
|
-
} from './types';
|
|
15
|
-
|
|
16
|
-
export class MockDataProvider implements DataProvider {
|
|
17
|
-
private data: Record<string, MetricData> = {};
|
|
18
|
-
|
|
19
|
-
constructor(dashboard: DashboardDefinition) {
|
|
20
|
-
for (const metric of dashboard.metrics) {
|
|
21
|
-
this.data[metric.id] = this.resolveData(metric);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
get(metricId: string): MetricData {
|
|
26
|
-
return this.data[metricId] || { error: `No data for metric: ${metricId}` };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
getAll(): Record<string, MetricData> {
|
|
30
|
-
return this.data;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
private resolveData(metric: MetricDefinition): MetricData {
|
|
34
|
-
// Use inline mock data if provided
|
|
35
|
-
if (metric._mockData) {
|
|
36
|
-
return this.transformMockData(metric._mockData, metric);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Generate data based on metric type and query
|
|
40
|
-
return this.generateData(metric);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
private transformMockData(
|
|
44
|
-
mockData: NonNullable<MetricDefinition['_mockData']>,
|
|
45
|
-
_metric: MetricDefinition
|
|
46
|
-
): MetricData {
|
|
47
|
-
const result: MetricData = {};
|
|
48
|
-
|
|
49
|
-
if (mockData.current !== undefined) {
|
|
50
|
-
result.current = mockData.current;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (mockData.previous !== undefined) {
|
|
54
|
-
result.previous = mockData.previous;
|
|
55
|
-
// Calculate change percent if not provided
|
|
56
|
-
if (result.current !== undefined) {
|
|
57
|
-
result.changePercent =
|
|
58
|
-
((result.current - mockData.previous) / mockData.previous) * 100;
|
|
59
|
-
result.trend =
|
|
60
|
-
result.changePercent > 0
|
|
61
|
-
? 'up'
|
|
62
|
-
: result.changePercent < 0
|
|
63
|
-
? 'down'
|
|
64
|
-
: 'flat';
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (mockData.trend) {
|
|
69
|
-
result.trend = mockData.trend;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (mockData.series) {
|
|
73
|
-
result.series = mockData.series;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (mockData.breakdown) {
|
|
77
|
-
result.breakdown = mockData.breakdown;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (mockData.histogram) {
|
|
81
|
-
result.histogram = mockData.histogram;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return result;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
private generateData(metric: MetricDefinition): MetricData {
|
|
88
|
-
const { type, query } = metric;
|
|
89
|
-
|
|
90
|
-
// Time series data
|
|
91
|
-
if (query.timeGroup) {
|
|
92
|
-
return this.generateTimeSeries(metric);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Single value
|
|
96
|
-
if (type === 'gauge') {
|
|
97
|
-
return this.generateGaugeData(metric);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (type === 'counter') {
|
|
101
|
-
return this.generateCounterData(metric);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (type === 'histogram') {
|
|
105
|
-
return this.generateHistogramData(metric);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Fallback
|
|
109
|
-
return {
|
|
110
|
-
current: Math.floor(Math.random() * 1000),
|
|
111
|
-
trend: 'flat',
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
private generateTimeSeries(metric: MetricDefinition): MetricData {
|
|
116
|
-
const { query } = metric;
|
|
117
|
-
const points = this.getPointCount(query.timeGroup);
|
|
118
|
-
const series: TimeSeriesPoint[] = [];
|
|
119
|
-
|
|
120
|
-
const baseValue = this.getBaseValue(metric);
|
|
121
|
-
const variance = 0.2;
|
|
122
|
-
|
|
123
|
-
// Generate dates
|
|
124
|
-
const dates = this.generateDates(query.timeGroup, points);
|
|
125
|
-
|
|
126
|
-
if (query.groupBy && query.groupBy.length > 0) {
|
|
127
|
-
// Grouped data (e.g., by isMobile)
|
|
128
|
-
const groups = this.getGroupValues(query.groupBy[0]);
|
|
129
|
-
|
|
130
|
-
for (let i = 0; i < points; i++) {
|
|
131
|
-
const point: TimeSeriesPoint = { date: dates[i] };
|
|
132
|
-
for (const group of groups) {
|
|
133
|
-
point[group] = Math.floor(
|
|
134
|
-
baseValue * (0.3 + Math.random() * 0.7) * (1 + (Math.random() - 0.5) * variance)
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
series.push(point);
|
|
138
|
-
}
|
|
139
|
-
} else {
|
|
140
|
-
// Simple time series
|
|
141
|
-
for (let i = 0; i < points; i++) {
|
|
142
|
-
series.push({
|
|
143
|
-
date: dates[i],
|
|
144
|
-
value: Math.floor(baseValue * (1 + (Math.random() - 0.5) * variance)),
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return { series };
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
private generateGaugeData(metric: MetricDefinition): MetricData {
|
|
153
|
-
const { query } = metric;
|
|
154
|
-
|
|
155
|
-
// Percentage derivations
|
|
156
|
-
if (
|
|
157
|
-
query.derivation === 'percentage' ||
|
|
158
|
-
query.derivation === 'error_rate' ||
|
|
159
|
-
query.derivation === 'success_rate'
|
|
160
|
-
) {
|
|
161
|
-
const current = Math.random() * 100;
|
|
162
|
-
const previous = Math.random() * 100;
|
|
163
|
-
return {
|
|
164
|
-
current: Math.round(current * 10) / 10,
|
|
165
|
-
previous: Math.round(previous * 10) / 10,
|
|
166
|
-
trend: current > previous ? 'up' : current < previous ? 'down' : 'flat',
|
|
167
|
-
changePercent: Math.round(((current - previous) / previous) * 100 * 10) / 10,
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Rate
|
|
172
|
-
if (query.derivation === 'rate') {
|
|
173
|
-
const current = Math.floor(Math.random() * 1000);
|
|
174
|
-
const previous = Math.floor(Math.random() * 1000);
|
|
175
|
-
return {
|
|
176
|
-
current,
|
|
177
|
-
previous,
|
|
178
|
-
trend: current > previous ? 'up' : current < previous ? 'down' : 'flat',
|
|
179
|
-
changePercent: Math.round(((current - previous) / previous) * 100),
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const current = Math.floor(Math.random() * 10000);
|
|
184
|
-
return { current, trend: 'flat' };
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
private generateCounterData(_metric: MetricDefinition): MetricData {
|
|
188
|
-
const current = Math.floor(Math.random() * 100000);
|
|
189
|
-
const previous = Math.floor(Math.random() * 100000);
|
|
190
|
-
return {
|
|
191
|
-
current,
|
|
192
|
-
previous,
|
|
193
|
-
trend: current > previous ? 'up' : current < previous ? 'down' : 'flat',
|
|
194
|
-
changePercent: Math.round(((current - previous) / previous) * 100),
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
private generateHistogramData(metric: MetricDefinition): MetricData {
|
|
199
|
-
const { query } = metric;
|
|
200
|
-
|
|
201
|
-
// Percentile value
|
|
202
|
-
if (query.percentile) {
|
|
203
|
-
const values = [50, 120, 200, 350, 500, 800, 1200];
|
|
204
|
-
const percentileIndex = Math.min(
|
|
205
|
-
Math.floor((query.percentile / 100) * values.length),
|
|
206
|
-
values.length - 1
|
|
207
|
-
);
|
|
208
|
-
const current = values[percentileIndex] + Math.floor(Math.random() * 100);
|
|
209
|
-
const previous = values[percentileIndex] + Math.floor(Math.random() * 100);
|
|
210
|
-
|
|
211
|
-
return {
|
|
212
|
-
current,
|
|
213
|
-
previous,
|
|
214
|
-
trend: current > previous ? 'up' : current < previous ? 'down' : 'flat',
|
|
215
|
-
changePercent: Math.round(((current - previous) / previous) * 100),
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Full histogram
|
|
220
|
-
return {
|
|
221
|
-
histogram: {
|
|
222
|
-
buckets: ['0-50ms', '50-100ms', '100-200ms', '200-500ms', '500ms-1s', '>1s'],
|
|
223
|
-
counts: [
|
|
224
|
-
Math.floor(Math.random() * 1000),
|
|
225
|
-
Math.floor(Math.random() * 800),
|
|
226
|
-
Math.floor(Math.random() * 400),
|
|
227
|
-
Math.floor(Math.random() * 200),
|
|
228
|
-
Math.floor(Math.random() * 100),
|
|
229
|
-
Math.floor(Math.random() * 50),
|
|
230
|
-
],
|
|
231
|
-
},
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
private getPointCount(timeGroup?: string): number {
|
|
236
|
-
switch (timeGroup) {
|
|
237
|
-
case 'minute':
|
|
238
|
-
return 60;
|
|
239
|
-
case 'hour':
|
|
240
|
-
return 24;
|
|
241
|
-
case 'day':
|
|
242
|
-
return 7;
|
|
243
|
-
case 'week':
|
|
244
|
-
return 12;
|
|
245
|
-
case 'month':
|
|
246
|
-
return 12;
|
|
247
|
-
default:
|
|
248
|
-
return 7;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
private getBaseValue(metric: MetricDefinition): number {
|
|
253
|
-
// Estimate reasonable base value based on metric type
|
|
254
|
-
if (metric.query.derivation === 'percentage' || metric.query.derivation === 'error_rate') {
|
|
255
|
-
return 50;
|
|
256
|
-
}
|
|
257
|
-
if (metric.unit === 'views' || metric.unit === 'count') {
|
|
258
|
-
return 10000;
|
|
259
|
-
}
|
|
260
|
-
if (metric.unit === 'milliseconds' || metric.unit === 'ms') {
|
|
261
|
-
return 200;
|
|
262
|
-
}
|
|
263
|
-
return 1000;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
private generateDates(timeGroup: string | undefined, count: number): string[] {
|
|
267
|
-
const dates: string[] = [];
|
|
268
|
-
const now = new Date();
|
|
269
|
-
|
|
270
|
-
for (let i = count - 1; i >= 0; i--) {
|
|
271
|
-
const date = new Date(now);
|
|
272
|
-
|
|
273
|
-
switch (timeGroup) {
|
|
274
|
-
case 'minute':
|
|
275
|
-
date.setMinutes(date.getMinutes() - i);
|
|
276
|
-
dates.push(date.toISOString().slice(11, 16)); // HH:MM
|
|
277
|
-
break;
|
|
278
|
-
case 'hour':
|
|
279
|
-
date.setHours(date.getHours() - i);
|
|
280
|
-
dates.push(date.toISOString().slice(11, 13) + ':00'); // HH:00
|
|
281
|
-
break;
|
|
282
|
-
case 'day':
|
|
283
|
-
date.setDate(date.getDate() - i);
|
|
284
|
-
dates.push(date.toISOString().slice(0, 10)); // YYYY-MM-DD
|
|
285
|
-
break;
|
|
286
|
-
case 'week':
|
|
287
|
-
date.setDate(date.getDate() - i * 7);
|
|
288
|
-
dates.push(`W${this.getWeekNumber(date)}`);
|
|
289
|
-
break;
|
|
290
|
-
case 'month':
|
|
291
|
-
date.setMonth(date.getMonth() - i);
|
|
292
|
-
dates.push(date.toISOString().slice(0, 7)); // YYYY-MM
|
|
293
|
-
break;
|
|
294
|
-
default:
|
|
295
|
-
date.setDate(date.getDate() - i);
|
|
296
|
-
dates.push(date.toISOString().slice(0, 10));
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
return dates;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
private getWeekNumber(date: Date): number {
|
|
304
|
-
const firstDayOfYear = new Date(date.getFullYear(), 0, 1);
|
|
305
|
-
const pastDaysOfYear = (date.getTime() - firstDayOfYear.getTime()) / 86400000;
|
|
306
|
-
return Math.ceil((pastDaysOfYear + firstDayOfYear.getDay() + 1) / 7);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
private getGroupValues(groupBy: string): string[] {
|
|
310
|
-
// Common group values based on attribute name
|
|
311
|
-
if (groupBy === 'isMobile') {
|
|
312
|
-
return ['mobile', 'desktop'];
|
|
313
|
-
}
|
|
314
|
-
if (groupBy === 'status' || groupBy === 'status.code') {
|
|
315
|
-
return ['success', 'error'];
|
|
316
|
-
}
|
|
317
|
-
if (groupBy === 'user.tier') {
|
|
318
|
-
return ['free', 'pro', 'enterprise'];
|
|
319
|
-
}
|
|
320
|
-
// Default generic groups
|
|
321
|
-
return ['group_a', 'group_b'];
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* Create a mock data provider from a dashboard definition
|
|
327
|
-
*/
|
|
328
|
-
export function createMockDataProvider(dashboard: DashboardDefinition): DataProvider {
|
|
329
|
-
return new MockDataProvider(dashboard);
|
|
330
|
-
}
|