@cloudcare/guance-front-tools 1.0.11 → 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.
- package/README.md +8 -0
- package/guance-all-charts.json +3415 -0
- package/lib/cjs/generated/dashboardCharts.d.ts +54 -13
- package/lib/cjs/scripts/grafana-covert-to-guance-core.d.ts +4 -0
- package/lib/cjs/scripts/grafana-covert-to-guance-core.js +10 -0
- package/lib/cjs/scripts/grafana-dashbord.d.ts +2220 -0
- package/lib/cjs/scripts/grafana-dashbord.js +4 -0
- package/lib/cjs/src/grafana-covert-to-guance.d.ts +2 -0
- package/lib/cjs/src/grafana-covert-to-guance.js +5 -0
- package/lib/cjs/src/index.d.ts +1 -0
- package/lib/cjs/src/index.js +1 -0
- package/lib/esm/generated/dashboardCharts.d.ts +54 -13
- package/lib/esm/scripts/grafana-covert-to-guance-core.d.ts +4 -0
- package/lib/esm/scripts/grafana-covert-to-guance-core.js +7 -0
- package/lib/esm/scripts/grafana-dashbord.d.ts +2220 -0
- package/lib/esm/scripts/grafana-dashbord.js +1 -0
- package/lib/esm/src/grafana-covert-to-guance.d.ts +2 -0
- package/lib/esm/src/grafana-covert-to-guance.js +1 -0
- package/lib/esm/src/index.d.ts +1 -0
- package/lib/esm/src/index.js +1 -0
- package/lib/example/grafana2.json +878 -0
- package/lib/example/guance-dahs-3.json +348 -0
- package/lib/scripts/grafana-covert-to-guance-core.js +7 -0
- package/lib/scripts/grafana-covert-to-guance-core.ts +23 -0
- package/lib/scripts/grafana-covert-to-guance.js +52 -397
- package/lib/scripts/grafana-covert-to-guance.ts +58 -410
- package/lib/src/grafana-covert-to-guance.ts +7 -0
- package/lib/src/index.ts +1 -0
- package/package.json +6 -4
- package/schemas/charts/chart-schema.json +8 -5
- package/schemas/charts/common/chart-link-item-schema.json +48 -0
- package/schemas/charts/common/chart-links-schema.json +9 -0
- package/schemas/charts/common/common-chart-types-schema.json +3 -1
- package/schemas/charts/dashboard-schema.json +11 -4
- package/schemas/charts/query/query-item-schema.json +19 -1
- package/schemas/charts/settings/settings-time-schema.json +1 -5
- package/schemas/charts/settings/settings-unit-items-schema.json +3 -1
- package/schemas/charts/settings/settings-units-schema.json +2 -3
- package/scripts/validate-file.mjs +57 -0
- package/skills/grafana-to-guance-dashboard/SKILL.md +102 -0
- package/skills/grafana-to-guance-dashboard/agents/openai.yaml +4 -0
- package/skills/grafana-to-guance-dashboard/references/converter-notes.md +134 -0
- package/skills/grafana-to-guance-dashboard/scripts/convert-grafana-dashboard.mjs +1899 -0
- package/test/cli.test.mjs +373 -0
- package/test-output/grafana2.cli.guance.json +1029 -0
- package/test-output/grafana2.guance.json +1029 -0
- package/test-output/grafana2.keep-meta.guance.json +1384 -0
- package/test-output/pod.guance.json +2153 -0
- package/test-output/skill-test2-enhanced.guance.json +21596 -0
- package/test-output/skill-test2-validated.guance.json +11610 -0
- package/test-output/skill-test2.guance.json +11610 -0
- package/test-output/test.guance.json +1086 -0
- package/test-output/test2.guance.guance-promql.json +23212 -0
- package/test-output/test2.guance.json +17554 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import test from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import fs from 'node:fs'
|
|
4
|
+
import os from 'node:os'
|
|
5
|
+
import path from 'node:path'
|
|
6
|
+
import { spawnSync } from 'node:child_process'
|
|
7
|
+
import { fileURLToPath } from 'node:url'
|
|
8
|
+
|
|
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'
|
|
13
|
+
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
15
|
+
const __dirname = path.dirname(__filename)
|
|
16
|
+
const projectRoot = path.resolve(__dirname, '..')
|
|
17
|
+
const cliPath = path.join(projectRoot, 'cli.js')
|
|
18
|
+
const grafanaInputPath = path.join(projectRoot, 'lib/example/test2.json')
|
|
19
|
+
const grafanaLoadInputPath = path.join(projectRoot, 'lib/example/grafana2.json')
|
|
20
|
+
|
|
21
|
+
function runCli(args, options = {}) {
|
|
22
|
+
return spawnSync(process.execPath, [cliPath, ...args], {
|
|
23
|
+
cwd: projectRoot,
|
|
24
|
+
encoding: 'utf-8',
|
|
25
|
+
...options,
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function createTempDir() {
|
|
30
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), 'guance-front-tools-cli-'))
|
|
31
|
+
}
|
|
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
|
+
|
|
318
|
+
test('cli converts grafana dashboard to guance dashboard with explicit output path', () => {
|
|
319
|
+
const tempDir = createTempDir()
|
|
320
|
+
const outPath = path.join(tempDir, 'nested', 'guance-dashboard.json')
|
|
321
|
+
const grafanaDashboard = JSON.parse(fs.readFileSync(grafanaInputPath, 'utf-8'))
|
|
322
|
+
const expected = covert(grafanaDashboard)
|
|
323
|
+
|
|
324
|
+
const result = runCli(['-d', grafanaInputPath, '-o', outPath])
|
|
325
|
+
|
|
326
|
+
assert.equal(result.status, 0, result.stderr || result.stdout)
|
|
327
|
+
assert.equal(fs.existsSync(outPath), true)
|
|
328
|
+
|
|
329
|
+
const actual = JSON.parse(fs.readFileSync(outPath, 'utf-8'))
|
|
330
|
+
assert.deepEqual(actual, expected)
|
|
331
|
+
})
|
|
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
|
+
|
|
360
|
+
test('cli writes default output file to current working directory', () => {
|
|
361
|
+
const tempDir = createTempDir()
|
|
362
|
+
const outPath = path.join(tempDir, 'guance-dashboard.json')
|
|
363
|
+
const grafanaDashboard = JSON.parse(fs.readFileSync(grafanaInputPath, 'utf-8'))
|
|
364
|
+
const expected = covert(grafanaDashboard)
|
|
365
|
+
|
|
366
|
+
const result = runCli(['-d', grafanaInputPath], { cwd: tempDir })
|
|
367
|
+
|
|
368
|
+
assert.equal(result.status, 0, result.stderr || result.stdout)
|
|
369
|
+
assert.equal(fs.existsSync(outPath), true)
|
|
370
|
+
|
|
371
|
+
const actual = JSON.parse(fs.readFileSync(outPath, 'utf-8'))
|
|
372
|
+
assert.deepEqual(actual, expected)
|
|
373
|
+
})
|