grafana-react 0.0.0 → 0.0.2

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.
Files changed (113) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +88 -2
  3. package/build/cli/cli.test.d.ts +8 -0
  4. package/build/cli/cli.test.js +78 -0
  5. package/build/cli/index.d.ts +13 -0
  6. package/build/cli/index.js +266 -0
  7. package/build/components/annotation/annotation.d.ts +27 -0
  8. package/build/components/annotation/annotation.js +12 -0
  9. package/build/components/base.d.ts +23 -0
  10. package/build/components/base.js +28 -0
  11. package/build/components/dashboard/dashboard.d.ts +33 -0
  12. package/build/components/dashboard/dashboard.js +11 -0
  13. package/build/components/index.d.ts +44 -0
  14. package/build/components/index.js +54 -0
  15. package/build/components/link/link.d.ts +25 -0
  16. package/build/components/link/link.js +10 -0
  17. package/build/components/override/override.d.ts +22 -0
  18. package/build/components/override/override.js +11 -0
  19. package/build/components/panels/core/alert-list/alert-list.d.ts +33 -0
  20. package/build/components/panels/core/alert-list/alert-list.js +15 -0
  21. package/build/components/panels/core/annotations-list/annotations-list.d.ts +31 -0
  22. package/build/components/panels/core/annotations-list/annotations-list.js +15 -0
  23. package/build/components/panels/core/bar-chart/bar-chart.d.ts +47 -0
  24. package/build/components/panels/core/bar-chart/bar-chart.js +16 -0
  25. package/build/components/panels/core/bar-gauge/bar-gauge.d.ts +68 -0
  26. package/build/components/panels/core/bar-gauge/bar-gauge.js +30 -0
  27. package/build/components/panels/core/candlestick/candlestick.d.ts +42 -0
  28. package/build/components/panels/core/candlestick/candlestick.js +17 -0
  29. package/build/components/panels/core/canvas/canvas.d.ts +18 -0
  30. package/build/components/panels/core/canvas/canvas.js +12 -0
  31. package/build/components/panels/core/dashboard-list/dashboard-list.d.ts +35 -0
  32. package/build/components/panels/core/dashboard-list/dashboard-list.js +15 -0
  33. package/build/components/panels/core/datagrid/datagrid.d.ts +16 -0
  34. package/build/components/panels/core/datagrid/datagrid.js +12 -0
  35. package/build/components/panels/core/flame-graph/flame-graph.d.ts +16 -0
  36. package/build/components/panels/core/flame-graph/flame-graph.js +12 -0
  37. package/build/components/panels/core/gauge/gauge.d.ts +64 -0
  38. package/build/components/panels/core/gauge/gauge.js +30 -0
  39. package/build/components/panels/core/geomap/geomap.d.ts +20 -0
  40. package/build/components/panels/core/geomap/geomap.js +12 -0
  41. package/build/components/panels/core/heatmap/heatmap.d.ts +25 -0
  42. package/build/components/panels/core/heatmap/heatmap.js +16 -0
  43. package/build/components/panels/core/histogram/histogram.d.ts +39 -0
  44. package/build/components/panels/core/histogram/histogram.js +16 -0
  45. package/build/components/panels/core/logs/logs.d.ts +33 -0
  46. package/build/components/panels/core/logs/logs.js +17 -0
  47. package/build/components/panels/core/news/news.d.ts +20 -0
  48. package/build/components/panels/core/news/news.js +14 -0
  49. package/build/components/panels/core/node-graph/node-graph.d.ts +18 -0
  50. package/build/components/panels/core/node-graph/node-graph.js +12 -0
  51. package/build/components/panels/core/pie-chart/pie-chart.d.ts +33 -0
  52. package/build/components/panels/core/pie-chart/pie-chart.js +16 -0
  53. package/build/components/panels/core/stat/stat.d.ts +54 -0
  54. package/build/components/panels/core/stat/stat.js +18 -0
  55. package/build/components/panels/core/state-timeline/state-timeline.d.ts +37 -0
  56. package/build/components/panels/core/state-timeline/state-timeline.js +16 -0
  57. package/build/components/panels/core/status-history/status-history.d.ts +35 -0
  58. package/build/components/panels/core/status-history/status-history.js +16 -0
  59. package/build/components/panels/core/table/table.d.ts +63 -0
  60. package/build/components/panels/core/table/table.js +25 -0
  61. package/build/components/panels/core/text/text.d.ts +16 -0
  62. package/build/components/panels/core/text/text.js +10 -0
  63. package/build/components/panels/core/timeseries/timeseries.d.ts +103 -0
  64. package/build/components/panels/core/timeseries/timeseries.js +31 -0
  65. package/build/components/panels/core/traces/traces.d.ts +18 -0
  66. package/build/components/panels/core/traces/traces.js +12 -0
  67. package/build/components/panels/core/trend/trend.d.ts +39 -0
  68. package/build/components/panels/core/trend/trend.js +16 -0
  69. package/build/components/panels/core/xy-chart/xy-chart.d.ts +44 -0
  70. package/build/components/panels/core/xy-chart/xy-chart.js +17 -0
  71. package/build/components/panels/index.d.ts +32 -0
  72. package/build/components/panels/index.js +38 -0
  73. package/build/components/panels/plugins/business-variable-panel/business-variable-panel.d.ts +85 -0
  74. package/build/components/panels/plugins/business-variable-panel/business-variable-panel.js +15 -0
  75. package/build/components/plugin-panel/plugin-panel.d.ts +25 -0
  76. package/build/components/plugin-panel/plugin-panel.js +17 -0
  77. package/build/components/query/query.d.ts +33 -0
  78. package/build/components/query/query.js +19 -0
  79. package/build/components/row/row.d.ts +27 -0
  80. package/build/components/row/row.js +13 -0
  81. package/build/components/variable/variable.d.ts +32 -0
  82. package/build/components/variable/variable.js +12 -0
  83. package/build/index.d.ts +33 -0
  84. package/build/index.js +52 -0
  85. package/build/lib/index.d.ts +5 -0
  86. package/build/lib/index.js +5 -0
  87. package/build/lib/renderer.d.ts +24 -0
  88. package/build/lib/renderer.js +1531 -0
  89. package/build/lib/renderer.test.d.ts +4 -0
  90. package/build/lib/renderer.test.js +480 -0
  91. package/build/lib/utils.d.ts +70 -0
  92. package/build/lib/utils.js +229 -0
  93. package/build/lib/utils.test.d.ts +4 -0
  94. package/build/lib/utils.test.js +252 -0
  95. package/build/types/common/axis.d.ts +76 -0
  96. package/build/types/common/axis.js +8 -0
  97. package/build/types/common/enums.d.ts +50 -0
  98. package/build/types/common/enums.js +9 -0
  99. package/build/types/common/field-config.d.ts +123 -0
  100. package/build/types/common/field-config.js +8 -0
  101. package/build/types/common/index.d.ts +10 -0
  102. package/build/types/common/index.js +7 -0
  103. package/build/types/common/viz-options.d.ts +94 -0
  104. package/build/types/common/viz-options.js +8 -0
  105. package/build/types/display.d.ts +29 -0
  106. package/build/types/display.js +4 -0
  107. package/build/types/grafana-json.d.ts +147 -0
  108. package/build/types/grafana-json.js +6 -0
  109. package/build/types/index.d.ts +10 -0
  110. package/build/types/index.js +7 -0
  111. package/build/types/panel-base.d.ts +72 -0
  112. package/build/types/panel-base.js +4 -0
  113. package/package.json +67 -3
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Tests for the renderer
3
+ */
4
+ export {};
@@ -0,0 +1,480 @@
1
+ /**
2
+ * Tests for the renderer
3
+ */
4
+ import { describe, it } from 'node:test';
5
+ import assert from 'node:assert';
6
+ import React from 'react';
7
+ import { render, renderToString } from './renderer.js';
8
+ import { Dashboard, Row, Stat, Timeseries, Table, Gauge, BarGauge, Variable, Annotation, Link, Query, } from '../components/index.js';
9
+ describe('render', () => {
10
+ it('renders a minimal dashboard', () => {
11
+ const element = React.createElement(Dashboard, {
12
+ uid: 'test-dashboard',
13
+ title: 'Test Dashboard',
14
+ });
15
+ const result = render(element);
16
+ assert.strictEqual(result.uid, 'test-dashboard');
17
+ assert.strictEqual(result.title, 'Test Dashboard');
18
+ assert.strictEqual(result.schemaVersion, 40);
19
+ assert.deepStrictEqual(result.panels, []);
20
+ });
21
+ it('renders dashboard with tags and settings', () => {
22
+ const element = React.createElement(Dashboard, {
23
+ uid: 'test',
24
+ title: 'Test',
25
+ tags: ['tag1', 'tag2'],
26
+ time: '6h',
27
+ timezone: 'utc',
28
+ tooltip: 'shared',
29
+ });
30
+ const result = render(element);
31
+ assert.deepStrictEqual(result.tags, ['tag1', 'tag2']);
32
+ assert.deepStrictEqual(result.time, { from: 'now-6h', to: 'now' });
33
+ assert.strictEqual(result.timezone, 'utc');
34
+ assert.strictEqual(result.graphTooltip, 2);
35
+ });
36
+ it('renders variables', () => {
37
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(Variable, { name: 'instance', label: 'Instance', multi: true }, 'label_values(up, instance)'));
38
+ const result = render(element);
39
+ assert.strictEqual(result.templating?.list.length, 1);
40
+ const variable = result.templating.list[0];
41
+ assert.strictEqual(variable.name, 'instance');
42
+ assert.strictEqual(variable.label, 'Instance');
43
+ assert.strictEqual(variable.multi, true);
44
+ assert.strictEqual(variable.includeAll, true);
45
+ assert.deepStrictEqual(variable.query, {
46
+ query: 'label_values(up, instance)',
47
+ refId: 'VariableQueryEditor-VariableQuery',
48
+ qryType: 1,
49
+ });
50
+ });
51
+ it('renders annotations', () => {
52
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(Annotation, { name: 'Alerts', color: 'light-red', title: '{{alertname}}' }, 'ALERTS{alertstate="firing"}'));
53
+ const result = render(element);
54
+ assert.strictEqual(result.annotations?.list.length, 1);
55
+ const annotation = result.annotations.list[0];
56
+ assert.strictEqual(annotation.name, 'Alerts');
57
+ assert.strictEqual(annotation.iconColor, 'light-red');
58
+ assert.strictEqual(annotation.expr, 'ALERTS{alertstate="firing"}');
59
+ });
60
+ it('renders links', () => {
61
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test' }, React.createElement(Link, {
62
+ title: 'Docs',
63
+ url: 'https://example.com',
64
+ icon: 'external',
65
+ keepTime: true,
66
+ }));
67
+ const result = render(element);
68
+ assert.strictEqual(result.links?.length, 1);
69
+ const link = result.links[0];
70
+ assert.strictEqual(link.title, 'Docs');
71
+ assert.strictEqual(link.url, 'https://example.com');
72
+ assert.strictEqual(link.icon, 'external');
73
+ assert.strictEqual(link.keepTime, true);
74
+ });
75
+ it('renders rows with panels', () => {
76
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(Row, { title: 'Summary' }, React.createElement(Stat, { title: 'CPU %', unit: 'percent' }, '100 - avg(cpu_idle)')));
77
+ const result = render(element);
78
+ assert.strictEqual(result.panels.length, 2);
79
+ const row = result.panels[0];
80
+ assert.strictEqual(row.type, 'row');
81
+ assert.strictEqual(row.title, 'Summary');
82
+ const stat = result.panels[1];
83
+ assert.strictEqual(stat.type, 'stat');
84
+ assert.strictEqual(stat.title, 'CPU %');
85
+ assert.strictEqual(stat.targets?.[0].expr, '100 - avg(cpu_idle)');
86
+ });
87
+ it('renders stat panel with thresholds', () => {
88
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(Stat, {
89
+ title: 'Memory',
90
+ unit: 'percent',
91
+ thresholds: { 70: 'yellow', 90: 'red' },
92
+ colorMode: 'background',
93
+ }, 'memory_usage_percent'));
94
+ const result = render(element);
95
+ const panel = result.panels[0];
96
+ assert.strictEqual(panel.fieldConfig?.defaults.unit, 'percent');
97
+ assert.deepStrictEqual(panel.fieldConfig?.defaults.thresholds?.steps, [
98
+ { value: null, color: 'green' },
99
+ { value: 70, color: '#EAB839' },
100
+ { value: 90, color: 'red' },
101
+ ]);
102
+ });
103
+ it('renders timeseries with multiple queries', () => {
104
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(Timeseries, { title: 'CPU', stack: true, legend: 'right' }, React.createElement(Query, { refId: 'user', legend: 'User' }, 'cpu_user'), React.createElement(Query, { refId: 'system', legend: 'System' }, 'cpu_system')));
105
+ const result = render(element);
106
+ const panel = result.panels[0];
107
+ assert.strictEqual(panel.targets?.length, 2);
108
+ assert.strictEqual(panel.targets?.[0].refId, 'user');
109
+ assert.strictEqual(panel.targets?.[0].legendFormat, 'User');
110
+ assert.strictEqual(panel.targets?.[1].refId, 'system');
111
+ assert.strictEqual(panel.targets?.[1].legendFormat, 'System');
112
+ });
113
+ it('renders timeseries with stacking and thresholds', () => {
114
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(Timeseries, {
115
+ title: 'Usage',
116
+ stack: 'normal',
117
+ fill: 50,
118
+ thresholds: { 75: 'yellow', 85: 'red' },
119
+ thresholdStyle: 'dashed+area',
120
+ }, 'usage_metric'));
121
+ const result = render(element);
122
+ const panel = result.panels[0];
123
+ const custom = panel.fieldConfig?.defaults.custom;
124
+ assert.strictEqual(custom?.['stacking']?.mode, 'normal');
125
+ assert.strictEqual(custom?.['fillOpacity'], 50);
126
+ assert.strictEqual(custom?.['thresholdsStyle']?.mode, 'dashed+area');
127
+ });
128
+ it('handles function components', () => {
129
+ const MyDashboard = () => React.createElement(Dashboard, { uid: 'my-dashboard', title: 'My Dashboard' }, React.createElement(Stat, { title: 'Test' }, 'metric'));
130
+ const result = render(React.createElement(MyDashboard));
131
+ assert.strictEqual(result.uid, 'my-dashboard');
132
+ assert.strictEqual(result.panels.length, 1);
133
+ });
134
+ it('throws for non-dashboard root', () => {
135
+ const element = React.createElement(Stat, { title: 'Test' }, 'metric');
136
+ assert.throws(() => render(element), /Root element must be a Dashboard/);
137
+ });
138
+ });
139
+ describe('renderToString', () => {
140
+ it('returns formatted JSON string', () => {
141
+ const element = React.createElement(Dashboard, {
142
+ uid: 'test',
143
+ title: 'Test',
144
+ });
145
+ const result = renderToString(element);
146
+ assert.ok(result.includes('"uid": "test"'));
147
+ assert.ok(result.includes('\n')); // Pretty printed
148
+ });
149
+ it('returns minified JSON when pretty=false', () => {
150
+ const element = React.createElement(Dashboard, {
151
+ uid: 'test',
152
+ title: 'Test',
153
+ });
154
+ const result = renderToString(element, false);
155
+ assert.ok(!result.includes('\n'));
156
+ });
157
+ });
158
+ describe('Table panel', () => {
159
+ it('renders basic table', () => {
160
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(Table, { title: 'Status' }, 'up'));
161
+ const result = render(element);
162
+ const panel = result.panels[0];
163
+ assert.strictEqual(panel.type, 'table');
164
+ assert.strictEqual(panel.title, 'Status');
165
+ assert.strictEqual(panel.targets?.[0].format, 'table');
166
+ assert.strictEqual(panel.targets?.[0].instant, true);
167
+ });
168
+ it('renders with table options', () => {
169
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(Table, {
170
+ title: 'Status',
171
+ cellHeight: 'md',
172
+ enablePagination: true,
173
+ showHeader: false,
174
+ frozenColumns: { left: 1 },
175
+ }, 'up'));
176
+ const result = render(element);
177
+ const panel = result.panels[0];
178
+ const options = panel.options;
179
+ assert.strictEqual(options.cellHeight, 'md');
180
+ assert.strictEqual(options.enablePagination, true);
181
+ assert.strictEqual(options.showHeader, false);
182
+ assert.strictEqual(options.frameIndex, 0);
183
+ });
184
+ });
185
+ describe('Gauge panel', () => {
186
+ it('renders basic gauge with thresholds', () => {
187
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(Gauge, {
188
+ title: 'CPU',
189
+ unit: 'percent',
190
+ min: 0,
191
+ max: 100,
192
+ thresholds: { 70: 'yellow', 90: 'red' },
193
+ }, 'cpu_usage'));
194
+ const result = render(element);
195
+ const panel = result.panels[0];
196
+ assert.strictEqual(panel.type, 'gauge');
197
+ assert.strictEqual(panel.fieldConfig?.defaults.unit, 'percent');
198
+ assert.strictEqual(panel.fieldConfig?.defaults.min, 0);
199
+ assert.strictEqual(panel.fieldConfig?.defaults.max, 100);
200
+ assert.deepStrictEqual(panel.fieldConfig?.defaults.thresholds?.steps, [
201
+ { value: null, color: 'green' },
202
+ { value: 70, color: '#EAB839' },
203
+ { value: 90, color: 'red' },
204
+ ]);
205
+ });
206
+ it('renders with viz options', () => {
207
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(Gauge, {
208
+ title: 'CPU',
209
+ minVizWidth: 100,
210
+ minVizHeight: 100,
211
+ sizing: 'manual',
212
+ orientation: 'horizontal',
213
+ }, 'cpu_usage'));
214
+ const result = render(element);
215
+ const panel = result.panels[0];
216
+ const options = panel.options;
217
+ assert.strictEqual(options.minVizWidth, 100);
218
+ assert.strictEqual(options.minVizHeight, 100);
219
+ assert.strictEqual(options.sizing, 'manual');
220
+ assert.strictEqual(options.orientation, 'horizontal');
221
+ });
222
+ });
223
+ describe('BarGauge panel', () => {
224
+ it('renders basic bar gauge', () => {
225
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(BarGauge, {
226
+ title: 'Memory',
227
+ displayMode: 'gradient',
228
+ min: 0,
229
+ max: 100,
230
+ }, 'memory_usage'));
231
+ const result = render(element);
232
+ const panel = result.panels[0];
233
+ const options = panel.options;
234
+ assert.strictEqual(panel.type, 'bargauge');
235
+ assert.strictEqual(options.displayMode, 'gradient');
236
+ });
237
+ it('renders with advanced options', () => {
238
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(BarGauge, {
239
+ title: 'Memory',
240
+ showUnfilled: true,
241
+ minVizWidth: 50,
242
+ minVizHeight: 20,
243
+ valueMode: 'text',
244
+ namePlacement: 'left',
245
+ }, 'memory_usage'));
246
+ const result = render(element);
247
+ const panel = result.panels[0];
248
+ const options = panel.options;
249
+ assert.strictEqual(options.showUnfilled, true);
250
+ assert.strictEqual(options.minVizWidth, 50);
251
+ assert.strictEqual(options.minVizHeight, 20);
252
+ assert.strictEqual(options.valueMode, 'text');
253
+ assert.strictEqual(options.namePlacement, 'left');
254
+ });
255
+ });
256
+ describe('Stat panel new props', () => {
257
+ it('renders stat with percent change options', () => {
258
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(Stat, {
259
+ title: 'Users',
260
+ showPercentChange: true,
261
+ percentChangeColorMode: 'inverted',
262
+ }, 'user_count'));
263
+ const result = render(element);
264
+ const panel = result.panels[0];
265
+ const options = panel.options;
266
+ assert.strictEqual(options.showPercentChange, true);
267
+ assert.strictEqual(options.percentChangeColorMode, 'inverted');
268
+ });
269
+ it('renders stat with layout options', () => {
270
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(Stat, {
271
+ title: 'Users',
272
+ justifyMode: 'center',
273
+ wideLayout: false,
274
+ orientation: 'horizontal',
275
+ }, 'user_count'));
276
+ const result = render(element);
277
+ const panel = result.panels[0];
278
+ const options = panel.options;
279
+ assert.strictEqual(options.justifyMode, 'center');
280
+ assert.strictEqual(options.wideLayout, false);
281
+ assert.strictEqual(options.orientation, 'horizontal');
282
+ });
283
+ });
284
+ describe('Timeseries panel new props', () => {
285
+ it('renders with tooltip config object', () => {
286
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(Timeseries, {
287
+ title: 'RPS',
288
+ tooltip: { mode: 'single', sort: 'desc', maxHeight: 300 },
289
+ }, 'rate(requests[5m])'));
290
+ const result = render(element);
291
+ const panel = result.panels[0];
292
+ const options = panel.options;
293
+ const tooltip = options.tooltip;
294
+ assert.strictEqual(tooltip.mode, 'single');
295
+ assert.strictEqual(tooltip.sort, 'desc');
296
+ assert.strictEqual(tooltip.maxHeight, 300);
297
+ });
298
+ it('renders with point options', () => {
299
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(Timeseries, {
300
+ title: 'RPS',
301
+ showPoints: 'always',
302
+ pointSize: 8,
303
+ }, 'rate(requests[5m])'));
304
+ const result = render(element);
305
+ const panel = result.panels[0];
306
+ const custom = panel.fieldConfig?.defaults.custom;
307
+ assert.strictEqual(custom?.showPoints, 'always');
308
+ assert.strictEqual(custom?.pointSize, 8);
309
+ });
310
+ it('renders with axis config', () => {
311
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(Timeseries, {
312
+ title: 'RPS',
313
+ axisPlacement: 'left',
314
+ axisLabel: 'Requests/sec',
315
+ axisWidth: 60,
316
+ }, 'rate(requests[5m])'));
317
+ const result = render(element);
318
+ const panel = result.panels[0];
319
+ const custom = panel.fieldConfig?.defaults.custom;
320
+ assert.strictEqual(custom?.axisPlacement, 'left');
321
+ assert.strictEqual(custom?.axisLabel, 'Requests/sec');
322
+ assert.strictEqual(custom?.axisWidth, 60);
323
+ });
324
+ it('renders with line style and span nulls', () => {
325
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(Timeseries, {
326
+ title: 'RPS',
327
+ lineStyle: 'dash',
328
+ spanNulls: true,
329
+ }, 'rate(requests[5m])'));
330
+ const result = render(element);
331
+ const panel = result.panels[0];
332
+ const custom = panel.fieldConfig?.defaults.custom;
333
+ assert.deepStrictEqual(custom?.lineStyle, { fill: 'dash' });
334
+ assert.strictEqual(custom?.spanNulls, true);
335
+ });
336
+ });
337
+ // ============================================================================
338
+ // Panel Positioning and Wrapping Tests
339
+ // ============================================================================
340
+ describe('Panel positioning', () => {
341
+ it('places panels horizontally side by side', () => {
342
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(Stat, { title: 'A', width: 6 }, 'metric_a'), React.createElement(Stat, { title: 'B', width: 6 }, 'metric_b'), React.createElement(Stat, { title: 'C', width: 6 }, 'metric_c'));
343
+ const result = render(element);
344
+ assert.strictEqual(result.panels[0].gridPos.x, 0);
345
+ assert.strictEqual(result.panels[0].gridPos.y, 0);
346
+ assert.strictEqual(result.panels[1].gridPos.x, 6);
347
+ assert.strictEqual(result.panels[1].gridPos.y, 0);
348
+ assert.strictEqual(result.panels[2].gridPos.x, 12);
349
+ assert.strictEqual(result.panels[2].gridPos.y, 0);
350
+ });
351
+ it('wraps to next row when panel does not fit', () => {
352
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(Stat, { title: 'A', width: 12 }, 'metric_a'), React.createElement(Stat, { title: 'B', width: 12 }, 'metric_b'), React.createElement(Stat, { title: 'C', width: 12 }, 'metric_c'));
353
+ const result = render(element);
354
+ // First panel at (0, 0)
355
+ assert.strictEqual(result.panels[0].gridPos.x, 0);
356
+ assert.strictEqual(result.panels[0].gridPos.y, 0);
357
+ // Second panel at (12, 0) - fits on same row
358
+ assert.strictEqual(result.panels[1].gridPos.x, 12);
359
+ assert.strictEqual(result.panels[1].gridPos.y, 0);
360
+ // Third panel wraps to (0, 8)
361
+ assert.strictEqual(result.panels[2].gridPos.x, 0);
362
+ assert.strictEqual(result.panels[2].gridPos.y, 8); // default height is 8
363
+ });
364
+ it('uses explicit x/y positions when provided', () => {
365
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(Stat, { title: 'A', width: 6, x: 10, y: 5 }, 'metric_a'));
366
+ const result = render(element);
367
+ assert.strictEqual(result.panels[0].gridPos.x, 10);
368
+ assert.strictEqual(result.panels[0].gridPos.y, 5);
369
+ });
370
+ it('handles panels with different heights', () => {
371
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(Stat, { title: 'A', width: 12, height: 4 }, 'metric_a'), React.createElement(Stat, { title: 'B', width: 12, height: 10 }, 'metric_b'), React.createElement(Stat, { title: 'C', width: 24 }, 'metric_c'));
372
+ const result = render(element);
373
+ // A at (0, 0) with height 4
374
+ assert.strictEqual(result.panels[0].gridPos.x, 0);
375
+ assert.strictEqual(result.panels[0].gridPos.y, 0);
376
+ assert.strictEqual(result.panels[0].gridPos.h, 4);
377
+ // B at (12, 0) with height 10
378
+ assert.strictEqual(result.panels[1].gridPos.x, 12);
379
+ assert.strictEqual(result.panels[1].gridPos.y, 0);
380
+ assert.strictEqual(result.panels[1].gridPos.h, 10);
381
+ // C wraps to y=10 (max height of previous row)
382
+ assert.strictEqual(result.panels[2].gridPos.x, 0);
383
+ assert.strictEqual(result.panels[2].gridPos.y, 10);
384
+ });
385
+ it('uses default width of 12 and height of 8', () => {
386
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(Stat, { title: 'A' }, 'metric'));
387
+ const result = render(element);
388
+ assert.strictEqual(result.panels[0].gridPos.w, 12);
389
+ assert.strictEqual(result.panels[0].gridPos.h, 8);
390
+ });
391
+ it('fills entire row with width 24', () => {
392
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(Stat, { title: 'A', width: 24 }, 'metric_a'), React.createElement(Stat, { title: 'B', width: 6 }, 'metric_b'));
393
+ const result = render(element);
394
+ assert.strictEqual(result.panels[0].gridPos.x, 0);
395
+ assert.strictEqual(result.panels[0].gridPos.w, 24);
396
+ // B should wrap to next row
397
+ assert.strictEqual(result.panels[1].gridPos.x, 0);
398
+ assert.strictEqual(result.panels[1].gridPos.y, 8);
399
+ });
400
+ });
401
+ describe('Row positioning', () => {
402
+ it('places row panel at full width', () => {
403
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(Row, { title: 'Section A' }));
404
+ const result = render(element);
405
+ const row = result.panels[0];
406
+ assert.strictEqual(row.type, 'row');
407
+ assert.strictEqual(row.gridPos.x, 0);
408
+ assert.strictEqual(row.gridPos.w, 24);
409
+ assert.strictEqual(row.gridPos.h, 1);
410
+ });
411
+ it('positions panels after row correctly', () => {
412
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(Row, { title: 'Section' }, React.createElement(Stat, { title: 'A', width: 12 }, 'metric')));
413
+ const result = render(element);
414
+ // Row at y=0
415
+ assert.strictEqual(result.panels[0].type, 'row');
416
+ assert.strictEqual(result.panels[0].gridPos.y, 0);
417
+ // Panel at y=1 (after row)
418
+ assert.strictEqual(result.panels[1].type, 'stat');
419
+ assert.strictEqual(result.panels[1].gridPos.y, 1);
420
+ assert.strictEqual(result.panels[1].gridPos.x, 0);
421
+ });
422
+ it('stacks multiple rows correctly', () => {
423
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(Row, { title: 'Row 1' }, React.createElement(Stat, { title: 'A', height: 6 }, 'metric_a')), React.createElement(Row, { title: 'Row 2' }, React.createElement(Stat, { title: 'B', height: 4 }, 'metric_b')));
424
+ const result = render(element);
425
+ // Row 1 at y=0
426
+ assert.strictEqual(result.panels[0].gridPos.y, 0);
427
+ // Panel A at y=1
428
+ assert.strictEqual(result.panels[1].gridPos.y, 1);
429
+ // Row 2 at y=7 (1 + 6 = 7)
430
+ assert.strictEqual(result.panels[2].gridPos.y, 7);
431
+ // Panel B at y=8
432
+ assert.strictEqual(result.panels[3].gridPos.y, 8);
433
+ });
434
+ it('applies row padding to child panels', () => {
435
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(Row, { title: 'Padded Row', padding: 2 }, React.createElement(Stat, { title: 'A', width: 10 }, 'metric_a'), React.createElement(Stat, { title: 'B', width: 10 }, 'metric_b')));
436
+ const result = render(element);
437
+ // First panel starts at x=2 (left padding)
438
+ assert.strictEqual(result.panels[1].gridPos.x, 2);
439
+ // Second panel at x=12
440
+ assert.strictEqual(result.panels[2].gridPos.x, 12);
441
+ });
442
+ it('wraps panels within row accounting for padding', () => {
443
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(Row, { title: 'Padded Row', paddingLeft: 2, paddingRight: 2 },
444
+ // Available width = 24 - 2 - 2 = 20
445
+ React.createElement(Stat, { title: 'A', width: 10 }, 'metric_a'), React.createElement(Stat, { title: 'B', width: 10 }, 'metric_b'), React.createElement(Stat, { title: 'C', width: 10 }, 'metric_c')));
446
+ const result = render(element);
447
+ // A at x=2
448
+ assert.strictEqual(result.panels[1].gridPos.x, 2);
449
+ assert.strictEqual(result.panels[1].gridPos.y, 1);
450
+ // B at x=12 (2 + 10)
451
+ assert.strictEqual(result.panels[2].gridPos.x, 12);
452
+ assert.strictEqual(result.panels[2].gridPos.y, 1);
453
+ // C wraps to next line at x=2
454
+ assert.strictEqual(result.panels[3].gridPos.x, 2);
455
+ assert.strictEqual(result.panels[3].gridPos.y, 9); // 1 + 8 = 9
456
+ });
457
+ it('resets padding after row ends', () => {
458
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(Row, { title: 'Padded Row', padding: 4 }, React.createElement(Stat, { title: 'A', width: 8 }, 'metric_a')), React.createElement(Stat, { title: 'B', width: 8 }, 'metric_b'));
459
+ const result = render(element);
460
+ // A inside padded row at x=4
461
+ assert.strictEqual(result.panels[1].gridPos.x, 4);
462
+ // B outside row should be at x=0 (no padding)
463
+ assert.strictEqual(result.panels[2].gridPos.x, 0);
464
+ });
465
+ });
466
+ describe('Panel IDs', () => {
467
+ it('assigns sequential panel IDs', () => {
468
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(Stat, { title: 'A' }, 'metric_a'), React.createElement(Stat, { title: 'B' }, 'metric_b'), React.createElement(Stat, { title: 'C' }, 'metric_c'));
469
+ const result = render(element);
470
+ assert.strictEqual(result.panels[0].id, 1);
471
+ assert.strictEqual(result.panels[1].id, 2);
472
+ assert.strictEqual(result.panels[2].id, 3);
473
+ });
474
+ it('includes row panels in ID sequence', () => {
475
+ const element = React.createElement(Dashboard, { uid: 'test', title: 'Test', datasource: 'prometheus' }, React.createElement(Row, { title: 'Row' }, React.createElement(Stat, { title: 'A' }, 'metric')));
476
+ const result = render(element);
477
+ assert.strictEqual(result.panels[0].id, 1); // row
478
+ assert.strictEqual(result.panels[1].id, 2); // stat
479
+ });
480
+ });
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Utility functions for rendering
3
+ */
4
+ import React from 'react';
5
+ import type { ThresholdSpec, ThresholdColor, LegendConfig, LegendPlacement, VariableSort, VizTooltipOptions, TooltipDisplayMode, ReduceDataOptions, LineStyleConfig, LineStyleFill, ScaleDistributionConfig } from '../types/index.js';
6
+ import type { GrafanaThreshold } from '../types/grafana-json.js';
7
+ /**
8
+ * Get children from a React element as an array
9
+ */
10
+ export declare function getChildren(element: React.ReactElement): React.ReactNode[];
11
+ /**
12
+ * Extract text content from React children
13
+ */
14
+ export declare function extractTextContent(children: React.ReactNode): string;
15
+ /**
16
+ * Convert time shorthand to Grafana time range
17
+ * '1h' -> { from: 'now-1h', to: 'now' }
18
+ */
19
+ export declare function parseTimeRange(time: string): {
20
+ from: string;
21
+ to: string;
22
+ };
23
+ /**
24
+ * Convert tooltip mode to Grafana graphTooltip value
25
+ */
26
+ export declare function parseTooltip(tooltip: 'shared' | 'single' | 'hidden' | undefined): 0 | 1 | 2;
27
+ /**
28
+ * Convert hide option to Grafana variable hide value
29
+ */
30
+ export declare function parseVariableHide(hide: boolean | 'label' | undefined): 0 | 1 | 2;
31
+ /**
32
+ * Convert sort option to Grafana variable sort value
33
+ */
34
+ export declare function parseVariableSort(sort: VariableSort | undefined): number;
35
+ /**
36
+ * Normalize color name to Grafana color value
37
+ */
38
+ export declare function normalizeColor(color: ThresholdColor | string): string;
39
+ /**
40
+ * Convert threshold specification to Grafana threshold format
41
+ * @param spec Threshold specification
42
+ * @param baseColor Base color for the first threshold step (default: 'green')
43
+ */
44
+ export declare function normalizeThresholds(spec: ThresholdSpec | undefined, baseColor?: string): GrafanaThreshold[];
45
+ /**
46
+ * Normalize legend configuration
47
+ */
48
+ export declare function normalizeLegend(legend: LegendConfig | LegendPlacement | undefined): LegendConfig;
49
+ /**
50
+ * Generate next reference ID (A, B, C, ... Z, AA, AB, ...)
51
+ */
52
+ export declare function nextRefId(counter: number): string;
53
+ /**
54
+ * Normalize tooltip configuration
55
+ * Accepts either a TooltipDisplayMode string or full VizTooltipOptions object
56
+ */
57
+ export declare function normalizeTooltip(tooltip: VizTooltipOptions | TooltipDisplayMode | undefined): VizTooltipOptions;
58
+ /**
59
+ * Normalize reduce options for single-stat style panels
60
+ */
61
+ export declare function normalizeReduceOptions(options: ReduceDataOptions | undefined): ReduceDataOptions;
62
+ /**
63
+ * Normalize line style configuration
64
+ * Accepts either a LineStyleFill string or full LineStyleConfig object
65
+ */
66
+ export declare function normalizeLineStyle(style: LineStyleFill | LineStyleConfig | undefined): LineStyleConfig | undefined;
67
+ /**
68
+ * Normalize scale distribution configuration
69
+ */
70
+ export declare function normalizeScaleDistribution(config: ScaleDistributionConfig | undefined): ScaleDistributionConfig;