@cloudcare/guance-front-tools 1.0.12 → 1.0.13

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 (39) hide show
  1. package/guance-all-charts.json +3415 -0
  2. package/lib/cjs/generated/dashboardCharts.d.ts +54 -13
  3. package/lib/cjs/scripts/grafana-covert-to-guance-core.d.ts +1 -1
  4. package/lib/cjs/scripts/grafana-covert-to-guance-core.js +5 -289
  5. package/lib/esm/generated/dashboardCharts.d.ts +54 -13
  6. package/lib/esm/scripts/grafana-covert-to-guance-core.d.ts +1 -1
  7. package/lib/esm/scripts/grafana-covert-to-guance-core.js +5 -289
  8. package/lib/example/grafana2.json +878 -0
  9. package/lib/example/guance-dahs-3.json +348 -0
  10. package/lib/scripts/grafana-covert-to-guance-core.js +5 -286
  11. package/lib/scripts/grafana-covert-to-guance-core.ts +12 -326
  12. package/lib/scripts/grafana-covert-to-guance.js +52 -29
  13. package/lib/scripts/grafana-covert-to-guance.ts +60 -32
  14. package/package.json +4 -3
  15. package/schemas/charts/chart-schema.json +8 -5
  16. package/schemas/charts/common/chart-link-item-schema.json +48 -0
  17. package/schemas/charts/common/chart-links-schema.json +9 -0
  18. package/schemas/charts/common/common-chart-types-schema.json +3 -1
  19. package/schemas/charts/dashboard-schema.json +11 -4
  20. package/schemas/charts/query/query-item-schema.json +19 -1
  21. package/schemas/charts/settings/settings-time-schema.json +1 -5
  22. package/schemas/charts/settings/settings-unit-items-schema.json +3 -1
  23. package/schemas/charts/settings/settings-units-schema.json +2 -3
  24. package/scripts/validate-file.mjs +57 -0
  25. package/skills/grafana-to-guance-dashboard/SKILL.md +102 -0
  26. package/skills/grafana-to-guance-dashboard/agents/openai.yaml +4 -0
  27. package/skills/grafana-to-guance-dashboard/references/converter-notes.md +134 -0
  28. package/skills/grafana-to-guance-dashboard/scripts/convert-grafana-dashboard.mjs +1899 -0
  29. package/test/cli.test.mjs +316 -0
  30. package/test-output/grafana2.cli.guance.json +1029 -0
  31. package/test-output/grafana2.guance.json +1029 -0
  32. package/test-output/grafana2.keep-meta.guance.json +1384 -0
  33. package/test-output/pod.guance.json +2153 -0
  34. package/test-output/skill-test2-enhanced.guance.json +21596 -0
  35. package/test-output/skill-test2-validated.guance.json +11610 -0
  36. package/test-output/skill-test2.guance.json +11610 -0
  37. package/test-output/test.guance.json +1086 -0
  38. package/test-output/test2.guance.guance-promql.json +23212 -0
  39. package/test-output/test2.guance.json +17554 -0
package/test/cli.test.mjs CHANGED
@@ -7,12 +7,16 @@ import { spawnSync } from 'node:child_process'
7
7
  import { fileURLToPath } from 'node:url'
8
8
 
9
9
  import { covert } from '../lib/scripts/grafana-covert-to-guance-core.js'
10
+ import {
11
+ convertDashboard,
12
+ } from '../skills/grafana-to-guance-dashboard/scripts/convert-grafana-dashboard.mjs'
10
13
 
11
14
  const __filename = fileURLToPath(import.meta.url)
12
15
  const __dirname = path.dirname(__filename)
13
16
  const projectRoot = path.resolve(__dirname, '..')
14
17
  const cliPath = path.join(projectRoot, 'cli.js')
15
18
  const grafanaInputPath = path.join(projectRoot, 'lib/example/test2.json')
19
+ const grafanaLoadInputPath = path.join(projectRoot, 'lib/example/grafana2.json')
16
20
 
17
21
  function runCli(args, options = {}) {
18
22
  return spawnSync(process.execPath, [cliPath, ...args], {
@@ -26,6 +30,291 @@ function createTempDir() {
26
30
  return fs.mkdtempSync(path.join(os.tmpdir(), 'guance-front-tools-cli-'))
27
31
  }
28
32
 
33
+ test('covert delegates to the shared converter implementation', () => {
34
+ const grafanaDashboard = JSON.parse(fs.readFileSync(grafanaInputPath, 'utf-8'))
35
+
36
+ const actual = covert(grafanaDashboard)
37
+ const expected = convertDashboard(grafanaDashboard)
38
+
39
+ assert.deepEqual(actual, expected)
40
+ })
41
+
42
+ test('shared converter infers units from query text when panel unit is missing', () => {
43
+ const grafanaDashboard = JSON.parse(fs.readFileSync(grafanaLoadInputPath, 'utf-8'))
44
+
45
+ const actual = convertDashboard(grafanaDashboard)
46
+ const loadChart = actual.main.charts.find((chart) =>
47
+ chart.queries.some((query) => String(query?.query?.q || '').includes('load5s'))
48
+ )
49
+
50
+ assert.ok(loadChart)
51
+ assert.equal(loadChart.extend.settings.unitType, 'global')
52
+ assert.deepEqual(loadChart.extend.settings.globalUnit, ['custom', 'short'])
53
+ })
54
+
55
+ test('shared converter maps offset queries to guance compare settings', () => {
56
+ const grafanaDashboard = JSON.parse(fs.readFileSync(grafanaInputPath, 'utf-8'))
57
+
58
+ const actual = convertDashboard(grafanaDashboard)
59
+ const serviceQpsChart = actual.main.charts.find((chart) => chart.name === '服务QPS')
60
+
61
+ assert.ok(serviceQpsChart)
62
+ assert.equal(serviceQpsChart.extend.settings.openCompare, true)
63
+ assert.deepEqual(serviceQpsChart.extend.settings.compareType, ['dayCompare', 'weekCompare'])
64
+ assert.deepEqual(serviceQpsChart.extend.settings.compares, [
65
+ { label: '日同比', value: 'dayCompare' },
66
+ { label: '周同比', value: 'weekCompare' },
67
+ ])
68
+ assert.equal(serviceQpsChart.extend.settings.compareChartType, 'sequence')
69
+ })
70
+
71
+ test('shared converter maps legend and table sort semantics into guance settings', () => {
72
+ const grafanaDashboard = JSON.parse(fs.readFileSync(grafanaInputPath, 'utf-8'))
73
+
74
+ const actual = convertDashboard(grafanaDashboard)
75
+ const errorRateChart = actual.main.charts.find((chart) => chart.name === '系统错误率 - 节点维度')
76
+ const releaseTableChart = actual.main.charts.find((chart) => chart.name === '📚流水线发布(最近五天) -- 按服务筛选')
77
+
78
+ assert.ok(errorRateChart)
79
+ assert.equal(errorRateChart.extend.settings.sorderByOrder, 'desc')
80
+
81
+ assert.ok(releaseTableChart)
82
+ assert.equal(releaseTableChart.extend.settings.mainMeasurementSort, 'top')
83
+ })
84
+
85
+ test('shared converter maps repeat panels and infers bytes and seconds units', () => {
86
+ const actual = convertDashboard({
87
+ title: 'synthetic',
88
+ panels: [
89
+ {
90
+ id: 1,
91
+ title: 'Memory',
92
+ type: 'timeseries',
93
+ repeat: 'host',
94
+ repeatDirection: 'h',
95
+ maxPerRow: 4,
96
+ gridPos: { x: 0, y: 0, w: 12, h: 8 },
97
+ fieldConfig: { defaults: { custom: {} }, overrides: [] },
98
+ options: {},
99
+ targets: [{ expr: 'sum(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) by (instance)' }],
100
+ },
101
+ {
102
+ id: 2,
103
+ title: 'GC Pause',
104
+ type: 'timeseries',
105
+ gridPos: { x: 12, y: 0, w: 12, h: 8 },
106
+ fieldConfig: { defaults: { custom: {} }, overrides: [] },
107
+ options: {},
108
+ targets: [{ expr: 'rate(jvm_gc_pause_seconds_sum[1m]) by (instance)' }],
109
+ },
110
+ ],
111
+ templating: { list: [] },
112
+ })
113
+
114
+ assert.deepEqual(actual.main.charts[0].extend.settings.globalUnit, ['digital', 'B'])
115
+ assert.equal(actual.main.charts[0].extend.settings.repeatChartVariable, 'host')
116
+ assert.equal(actual.main.charts[0].extend.settings.repeatChartRowLimit, 4)
117
+ assert.equal(actual.main.charts[0].extend.settings.extend.layout.repeatDirection, 'h')
118
+ assert.deepEqual(actual.main.charts[1].extend.settings.globalUnit, ['time', 's'])
119
+ })
120
+
121
+ test('shared converter maps field override units into custom settings.units', () => {
122
+ const actual = convertDashboard({
123
+ title: 'synthetic-units',
124
+ panels: [
125
+ {
126
+ id: 1,
127
+ title: 'Custom Units',
128
+ type: 'timeseries',
129
+ gridPos: { x: 0, y: 0, w: 12, h: 8 },
130
+ fieldConfig: {
131
+ defaults: { custom: {} },
132
+ overrides: [
133
+ {
134
+ matcher: { id: 'byName', options: 'first(mem_used_percent)' },
135
+ properties: [{ id: 'unit', value: 'percent' }],
136
+ },
137
+ ],
138
+ },
139
+ options: {},
140
+ targets: [{ expr: 'first(mem_used_percent)' }],
141
+ },
142
+ ],
143
+ templating: { list: [] },
144
+ })
145
+
146
+ const settings = actual.main.charts[0].extend.settings
147
+ assert.equal(settings.unitType, 'custom')
148
+ assert.deepEqual(settings.units, [
149
+ {
150
+ key: 'first(mem_used_percent)',
151
+ name: 'first(mem_used_percent)',
152
+ unit: 'percent',
153
+ units: ['percent', 'percent'],
154
+ },
155
+ ])
156
+ })
157
+
158
+ test('shared converter maps fixed color overrides into settings.colors', () => {
159
+ const actual = convertDashboard({
160
+ title: 'synthetic-colors',
161
+ panels: [
162
+ {
163
+ id: 1,
164
+ title: 'Custom Colors',
165
+ type: 'timeseries',
166
+ gridPos: { x: 0, y: 0, w: 12, h: 8 },
167
+ fieldConfig: {
168
+ defaults: { custom: {} },
169
+ overrides: [
170
+ {
171
+ matcher: { id: 'byName', options: '95th percentile' },
172
+ properties: [{ id: 'color', value: { fixedColor: 'blue', mode: 'fixed' } }],
173
+ },
174
+ {
175
+ matcher: { id: 'byName', options: 'Requests' },
176
+ properties: [{ id: 'color', value: { mode: 'continuous-BlPu' } }],
177
+ },
178
+ ],
179
+ },
180
+ options: {},
181
+ targets: [{ expr: 'histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[1m]))' }],
182
+ },
183
+ ],
184
+ templating: { list: [] },
185
+ })
186
+
187
+ assert.deepEqual(actual.main.charts[0].extend.settings.colors, [
188
+ {
189
+ key: '95th percentile',
190
+ name: '95th percentile',
191
+ color: 'blue',
192
+ },
193
+ ])
194
+ })
195
+
196
+ test('shared converter maps topk and reduce limits into slimit', () => {
197
+ const grafanaDashboard = JSON.parse(fs.readFileSync(grafanaInputPath, 'utf-8'))
198
+ const actual = convertDashboard(grafanaDashboard)
199
+ const pieChart = actual.main.charts.find((chart) => chart.name === 'QPS 节点间分布')
200
+
201
+ assert.ok(pieChart)
202
+ assert.equal(pieChart.extend.settings.slimit, 100)
203
+
204
+ const synthetic = convertDashboard({
205
+ title: 'synthetic-slimit',
206
+ panels: [
207
+ {
208
+ id: 1,
209
+ title: 'Toplist',
210
+ type: 'bargauge',
211
+ gridPos: { x: 0, y: 0, w: 12, h: 8 },
212
+ fieldConfig: { defaults: { custom: {} }, overrides: [] },
213
+ options: { reduceOptions: { limit: 15 } },
214
+ targets: [{ expr: 'sum(node_cpu_seconds_total) by (instance)' }],
215
+ },
216
+ ],
217
+ templating: { list: [] },
218
+ })
219
+
220
+ assert.equal(synthetic.main.charts[0].extend.settings.slimit, 15)
221
+ })
222
+
223
+ test('shared converter maps toplist thresholds and table mappings into color mapping settings', () => {
224
+ const actual = convertDashboard({
225
+ title: 'synthetic-color-mappings',
226
+ panels: [
227
+ {
228
+ id: 1,
229
+ title: 'Toplist',
230
+ type: 'bargauge',
231
+ gridPos: { x: 0, y: 0, w: 12, h: 8 },
232
+ fieldConfig: {
233
+ defaults: {
234
+ custom: {},
235
+ thresholds: {
236
+ mode: 'absolute',
237
+ steps: [
238
+ { color: 'green', value: null },
239
+ { color: 'red', value: 80 },
240
+ ],
241
+ },
242
+ },
243
+ overrides: [],
244
+ },
245
+ options: { reduceOptions: { limit: 10 } },
246
+ targets: [{ expr: 'sum(node_cpu_seconds_total) by (instance)' }],
247
+ },
248
+ {
249
+ id: 2,
250
+ title: 'Table',
251
+ type: 'table',
252
+ gridPos: { x: 12, y: 0, w: 12, h: 8 },
253
+ fieldConfig: {
254
+ defaults: { custom: {} },
255
+ overrides: [
256
+ {
257
+ matcher: { id: 'byName', options: 'status' },
258
+ properties: [
259
+ {
260
+ id: 'mappings',
261
+ value: [
262
+ {
263
+ type: 'value',
264
+ options: {
265
+ 0: { text: 'OK' },
266
+ },
267
+ },
268
+ ],
269
+ },
270
+ {
271
+ id: 'color',
272
+ value: { fixedColor: 'red', mode: 'fixed' },
273
+ },
274
+ ],
275
+ },
276
+ ],
277
+ },
278
+ transformations: [
279
+ {
280
+ id: 'organize',
281
+ options: {
282
+ renameByName: { status: '状态' },
283
+ },
284
+ },
285
+ ],
286
+ options: {},
287
+ targets: [{ rawSql: 'select status from demo' }],
288
+ },
289
+ ],
290
+ templating: { list: [] },
291
+ })
292
+
293
+ assert.deepEqual(actual.main.charts[0].extend.settings.colorMappings, [
294
+ {
295
+ value: [80],
296
+ bgColor: 'green',
297
+ operation: '<',
298
+ },
299
+ {
300
+ value: [80],
301
+ bgColor: 'red',
302
+ operation: '>=',
303
+ },
304
+ ])
305
+
306
+ assert.deepEqual(actual.main.charts[1].extend.settings.valColorMappings, [
307
+ {
308
+ field: '状态',
309
+ value: ['0'],
310
+ bgColor: '',
311
+ fontColor: 'red',
312
+ lineColor: '',
313
+ operation: '=',
314
+ },
315
+ ])
316
+ })
317
+
29
318
  test('cli converts grafana dashboard to guance dashboard with explicit output path', () => {
30
319
  const tempDir = createTempDir()
31
320
  const outPath = path.join(tempDir, 'nested', 'guance-dashboard.json')
@@ -41,6 +330,33 @@ test('cli converts grafana dashboard to guance dashboard with explicit output pa
41
330
  assert.deepEqual(actual, expected)
42
331
  })
43
332
 
333
+ test('cli supports shared converter flags and matches API output', () => {
334
+ const tempDir = createTempDir()
335
+ const outPath = path.join(tempDir, 'flags', 'guance-dashboard.json')
336
+ const grafanaDashboard = JSON.parse(fs.readFileSync(grafanaInputPath, 'utf-8'))
337
+ const expected = convertDashboard(grafanaDashboard, {
338
+ guancePromqlCompatible: true,
339
+ keepGrafanaMeta: true,
340
+ })
341
+
342
+ const result = runCli([
343
+ '-d',
344
+ grafanaInputPath,
345
+ '-o',
346
+ outPath,
347
+ '--validate',
348
+ '--guance-promql-compatible',
349
+ '--keep-grafana-meta',
350
+ ])
351
+
352
+ assert.equal(result.status, 0, result.stderr || result.stdout)
353
+ assert.match(result.stdout, /Validated /)
354
+ assert.equal(fs.existsSync(outPath), true)
355
+
356
+ const actual = JSON.parse(fs.readFileSync(outPath, 'utf-8'))
357
+ assert.deepEqual(actual, expected)
358
+ })
359
+
44
360
  test('cli writes default output file to current working directory', () => {
45
361
  const tempDir = createTempDir()
46
362
  const outPath = path.join(tempDir, 'guance-dashboard.json')