@intentius/chant-lexicon-grafana 0.97.0 → 0.99.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.
Files changed (451) hide show
  1. package/README.md +15 -5
  2. package/dist/actions/grafana.d.ts +24 -0
  3. package/dist/actions/grafana.d.ts.map +1 -0
  4. package/dist/actions/index.d.ts +2 -0
  5. package/dist/actions/index.d.ts.map +1 -0
  6. package/dist/alerting-build.d.ts +128 -0
  7. package/dist/alerting-build.d.ts.map +1 -0
  8. package/dist/alerting.d.ts +273 -0
  9. package/dist/alerting.d.ts.map +1 -0
  10. package/dist/api/apply.d.ts +156 -0
  11. package/dist/api/apply.d.ts.map +1 -0
  12. package/dist/api/bind.d.ts +32 -0
  13. package/dist/api/bind.d.ts.map +1 -0
  14. package/dist/api/client.d.ts +76 -0
  15. package/dist/api/client.d.ts.map +1 -0
  16. package/dist/api/converge.d.ts +33 -0
  17. package/dist/api/converge.d.ts.map +1 -0
  18. package/dist/api/dashboards.d.ts +102 -0
  19. package/dist/api/dashboards.d.ts.map +1 -0
  20. package/dist/api/datasources.d.ts +26 -0
  21. package/dist/api/datasources.d.ts.map +1 -0
  22. package/dist/api/fake-grafana-writes.d.ts +41 -0
  23. package/dist/api/fake-grafana-writes.d.ts.map +1 -0
  24. package/dist/api/fake-grafana.d.ts +61 -0
  25. package/dist/api/fake-grafana.d.ts.map +1 -0
  26. package/dist/api/folders.d.ts +110 -0
  27. package/dist/api/folders.d.ts.map +1 -0
  28. package/dist/api/library-panels.d.ts +52 -0
  29. package/dist/api/library-panels.d.ts.map +1 -0
  30. package/dist/build.d.ts +30 -2
  31. package/dist/build.d.ts.map +1 -1
  32. package/dist/catalog.d.ts +1 -1
  33. package/dist/catalog.d.ts.map +1 -1
  34. package/dist/codegen/docs.d.ts.map +1 -1
  35. package/dist/codegen/schema-types.d.ts +3 -3
  36. package/dist/codegen/schema-types.d.ts.map +1 -1
  37. package/dist/composites/agent-dashboard.d.ts.map +1 -1
  38. package/dist/composites/catalog.d.ts.map +1 -1
  39. package/dist/composites/index.d.ts +4 -2
  40. package/dist/composites/index.d.ts.map +1 -1
  41. package/dist/composites/red-dashboard.d.ts +21 -2
  42. package/dist/composites/red-dashboard.d.ts.map +1 -1
  43. package/dist/composites/shared.d.ts +7 -0
  44. package/dist/composites/shared.d.ts.map +1 -1
  45. package/dist/composites/slo-alert-rules.d.ts +92 -0
  46. package/dist/composites/slo-alert-rules.d.ts.map +1 -0
  47. package/dist/config.d.ts +111 -0
  48. package/dist/config.d.ts.map +1 -0
  49. package/dist/contact-point-secrets.d.ts +17 -0
  50. package/dist/contact-point-secrets.d.ts.map +1 -0
  51. package/dist/dashboard.d.ts +16 -2
  52. package/dist/dashboard.d.ts.map +1 -1
  53. package/dist/datasource-refs.d.ts +95 -0
  54. package/dist/datasource-refs.d.ts.map +1 -0
  55. package/dist/datasource.d.ts +29 -0
  56. package/dist/datasource.d.ts.map +1 -1
  57. package/dist/deep-observe-hooks.d.ts +64 -0
  58. package/dist/deep-observe-hooks.d.ts.map +1 -0
  59. package/dist/deep-observe.d.ts +42 -0
  60. package/dist/deep-observe.d.ts.map +1 -0
  61. package/dist/describe-resources.d.ts +49 -0
  62. package/dist/describe-resources.d.ts.map +1 -0
  63. package/dist/detect.d.ts +28 -1
  64. package/dist/detect.d.ts.map +1 -1
  65. package/dist/export-resources.d.ts +38 -0
  66. package/dist/export-resources.d.ts.map +1 -0
  67. package/dist/import/alerting.d.ts +40 -0
  68. package/dist/import/alerting.d.ts.map +1 -0
  69. package/dist/import/edits.d.ts +47 -0
  70. package/dist/import/edits.d.ts.map +1 -0
  71. package/dist/import/embedded.d.ts +14 -0
  72. package/dist/import/embedded.d.ts.map +1 -0
  73. package/dist/import/generator.d.ts +60 -0
  74. package/dist/import/generator.d.ts.map +1 -0
  75. package/dist/import/live-export.d.ts +108 -0
  76. package/dist/import/live-export.d.ts.map +1 -0
  77. package/dist/import/mappings.d.ts +83 -0
  78. package/dist/import/mappings.d.ts.map +1 -0
  79. package/dist/import/model.d.ts +98 -0
  80. package/dist/import/model.d.ts.map +1 -0
  81. package/dist/import/normalize-alerting.d.ts +26 -0
  82. package/dist/import/normalize-alerting.d.ts.map +1 -0
  83. package/dist/import/normalize.d.ts +74 -0
  84. package/dist/import/normalize.d.ts.map +1 -0
  85. package/dist/import/parser.d.ts +110 -0
  86. package/dist/import/parser.d.ts.map +1 -0
  87. package/dist/import/report.d.ts +26 -0
  88. package/dist/import/report.d.ts.map +1 -0
  89. package/dist/import/testdata/fixtures.d.ts +35 -0
  90. package/dist/import/testdata/fixtures.d.ts.map +1 -0
  91. package/dist/import/v2.d.ts +72 -0
  92. package/dist/import/v2.d.ts.map +1 -0
  93. package/dist/index.d.ts +26 -9
  94. package/dist/index.d.ts.map +1 -1
  95. package/dist/integrity.json +18 -10
  96. package/dist/k8s.d.ts +107 -0
  97. package/dist/k8s.d.ts.map +1 -0
  98. package/dist/lint/audit-catalog.d.ts +2 -1
  99. package/dist/lint/audit-catalog.d.ts.map +1 -1
  100. package/dist/lint/post-synth/graf101.d.ts +2 -2
  101. package/dist/lint/post-synth/graf102.d.ts +1 -1
  102. package/dist/lint/post-synth/graf107.d.ts +1 -1
  103. package/dist/lint/post-synth/graf108.d.ts +8 -0
  104. package/dist/lint/post-synth/graf108.d.ts.map +1 -0
  105. package/dist/lint/post-synth/graf109.d.ts +8 -0
  106. package/dist/lint/post-synth/graf109.d.ts.map +1 -0
  107. package/dist/lint/post-synth/graf111.d.ts +8 -0
  108. package/dist/lint/post-synth/graf111.d.ts.map +1 -0
  109. package/dist/lint/post-synth/graf112.d.ts +8 -0
  110. package/dist/lint/post-synth/graf112.d.ts.map +1 -0
  111. package/dist/lint/post-synth/graf113.d.ts +8 -0
  112. package/dist/lint/post-synth/graf113.d.ts.map +1 -0
  113. package/dist/lint/post-synth/graf114.d.ts +8 -0
  114. package/dist/lint/post-synth/graf114.d.ts.map +1 -0
  115. package/dist/lint/post-synth/graf115.d.ts +8 -0
  116. package/dist/lint/post-synth/graf115.d.ts.map +1 -0
  117. package/dist/lint/post-synth/grafana-helpers.d.ts +7 -5
  118. package/dist/lint/post-synth/grafana-helpers.d.ts.map +1 -1
  119. package/dist/lint/post-synth/index.d.ts.map +1 -1
  120. package/dist/lint/rules/literal-secret.d.ts +7 -3
  121. package/dist/lint/rules/literal-secret.d.ts.map +1 -1
  122. package/dist/lsp/hover.d.ts.map +1 -1
  123. package/dist/manifest.json +1 -1
  124. package/dist/members.d.ts +27 -0
  125. package/dist/members.d.ts.map +1 -0
  126. package/dist/meta.json +135 -0
  127. package/dist/okf/index.md +36 -2
  128. package/dist/okf/rules/GRAF002.md +3 -2
  129. package/dist/okf/rules/GRAF101.md +3 -2
  130. package/dist/okf/rules/GRAF102.md +1 -0
  131. package/dist/okf/rules/GRAF108.md +11 -0
  132. package/dist/okf/rules/GRAF109.md +15 -0
  133. package/dist/okf/rules/GRAF111.md +11 -0
  134. package/dist/okf/rules/GRAF112.md +16 -0
  135. package/dist/okf/rules/GRAF113.md +16 -0
  136. package/dist/okf/rules/GRAF114.md +11 -0
  137. package/dist/okf/rules/GRAF115.md +11 -0
  138. package/dist/okf/types/AlertListPanel.md +9 -0
  139. package/dist/okf/types/AlertQuery.md +9 -0
  140. package/dist/okf/types/AlertRule.md +9 -0
  141. package/dist/okf/types/AlertRuleGroup.md +9 -0
  142. package/dist/okf/types/BarChartPanel.md +9 -0
  143. package/dist/okf/types/BarGaugePanel.md +9 -0
  144. package/dist/okf/types/CanvasPanel.md +9 -0
  145. package/dist/okf/types/ClassicConditionsExpression.md +9 -0
  146. package/dist/okf/types/ContactPoint.md +14 -0
  147. package/dist/okf/types/Dashboard.md +1 -0
  148. package/dist/okf/types/Datasource.md +3 -2
  149. package/dist/okf/types/ExternalDatasource.md +15 -0
  150. package/dist/okf/types/FlameGraphPanel.md +9 -0
  151. package/dist/okf/types/GeomapPanel.md +9 -0
  152. package/dist/okf/types/HistogramPanel.md +9 -0
  153. package/dist/okf/types/MathExpression.md +9 -0
  154. package/dist/okf/types/MuteTiming.md +13 -0
  155. package/dist/okf/types/NodeGraphPanel.md +9 -0
  156. package/dist/okf/types/NotificationPolicy.md +9 -0
  157. package/dist/okf/types/NotificationTemplate.md +9 -0
  158. package/dist/okf/types/PieChartPanel.md +9 -0
  159. package/dist/okf/types/ReduceExpression.md +9 -0
  160. package/dist/okf/types/ResampleExpression.md +9 -0
  161. package/dist/okf/types/SqlExpression.md +9 -0
  162. package/dist/okf/types/StateTimelinePanel.md +9 -0
  163. package/dist/okf/types/StatusHistoryPanel.md +9 -0
  164. package/dist/okf/types/ThresholdExpression.md +9 -0
  165. package/dist/okf/types/TrendPanel.md +9 -0
  166. package/dist/okf/types/XYChartPanel.md +9 -0
  167. package/dist/op/activities/grafana-apply.d.ts +75 -0
  168. package/dist/op/activities/grafana-apply.d.ts.map +1 -0
  169. package/dist/op/activities/index.d.ts +9 -0
  170. package/dist/op/activities/index.d.ts.map +1 -0
  171. package/dist/op/builders.d.ts +24 -0
  172. package/dist/op/builders.d.ts.map +1 -0
  173. package/dist/ownership.d.ts +67 -0
  174. package/dist/ownership.d.ts.map +1 -0
  175. package/dist/panel-options.d.ts +65 -0
  176. package/dist/panel-options.d.ts.map +1 -0
  177. package/dist/panels.d.ts +46 -9
  178. package/dist/panels.d.ts.map +1 -1
  179. package/dist/pin.d.ts +29 -7
  180. package/dist/pin.d.ts.map +1 -1
  181. package/dist/plugin.d.ts.map +1 -1
  182. package/dist/promql-check.d.ts +51 -0
  183. package/dist/promql-check.d.ts.map +1 -0
  184. package/dist/query.d.ts +9 -8
  185. package/dist/query.d.ts.map +1 -1
  186. package/dist/rules/graf101.ts +3 -3
  187. package/dist/rules/graf102.ts +1 -1
  188. package/dist/rules/graf107.ts +1 -1
  189. package/dist/rules/graf108.ts +17 -0
  190. package/dist/rules/graf109.ts +17 -0
  191. package/dist/rules/graf111.ts +17 -0
  192. package/dist/rules/graf112.ts +17 -0
  193. package/dist/rules/graf113.ts +17 -0
  194. package/dist/rules/graf114.ts +17 -0
  195. package/dist/rules/graf115.ts +17 -0
  196. package/dist/rules/grafana-helpers.ts +15 -8
  197. package/dist/rules/literal-secret.ts +60 -6
  198. package/dist/schema/barchart.gen.d.ts +198 -0
  199. package/dist/schema/barchart.gen.d.ts.map +1 -0
  200. package/dist/schema/bargauge.gen.d.ts +97 -0
  201. package/dist/schema/bargauge.gen.d.ts.map +1 -0
  202. package/dist/schema/canvas.gen.d.ts +152 -0
  203. package/dist/schema/canvas.gen.d.ts.map +1 -0
  204. package/dist/schema/dashboard.gen.d.ts +196 -21
  205. package/dist/schema/dashboard.gen.d.ts.map +1 -1
  206. package/dist/schema/expr.gen.d.ts +606 -0
  207. package/dist/schema/expr.gen.d.ts.map +1 -0
  208. package/dist/schema/gauge.gen.d.ts.map +1 -1
  209. package/dist/schema/geomap.gen.d.ts +100 -0
  210. package/dist/schema/geomap.gen.d.ts.map +1 -0
  211. package/dist/schema/heatmap.gen.d.ts.map +1 -1
  212. package/dist/schema/histogram.gen.d.ts +132 -0
  213. package/dist/schema/histogram.gen.d.ts.map +1 -0
  214. package/dist/schema/index.d.ts +12 -0
  215. package/dist/schema/index.d.ts.map +1 -1
  216. package/dist/schema/logs.gen.d.ts +1 -0
  217. package/dist/schema/logs.gen.d.ts.map +1 -1
  218. package/dist/schema/loki.gen.d.ts.map +1 -1
  219. package/dist/schema/nodegraph.gen.d.ts +49 -0
  220. package/dist/schema/nodegraph.gen.d.ts.map +1 -0
  221. package/dist/schema/piechart.gen.d.ts +127 -0
  222. package/dist/schema/piechart.gen.d.ts.map +1 -0
  223. package/dist/schema/prometheus.gen.d.ts.map +1 -1
  224. package/dist/schema/stat.gen.d.ts.map +1 -1
  225. package/dist/schema/statetimeline.gen.d.ts +124 -0
  226. package/dist/schema/statetimeline.gen.d.ts.map +1 -0
  227. package/dist/schema/statushistory.gen.d.ts +114 -0
  228. package/dist/schema/statushistory.gen.d.ts.map +1 -0
  229. package/dist/schema/table.gen.d.ts +22 -3
  230. package/dist/schema/table.gen.d.ts.map +1 -1
  231. package/dist/schema/tempo.gen.d.ts.map +1 -1
  232. package/dist/schema/text.gen.d.ts.map +1 -1
  233. package/dist/schema/timeseries.gen.d.ts +5 -1
  234. package/dist/schema/timeseries.gen.d.ts.map +1 -1
  235. package/dist/schema/trend.gen.d.ts +180 -0
  236. package/dist/schema/trend.gen.d.ts.map +1 -0
  237. package/dist/schema/xychart.gen.d.ts +145 -0
  238. package/dist/schema/xychart.gen.d.ts.map +1 -0
  239. package/dist/schema-validate.d.ts +30 -4
  240. package/dist/schema-validate.d.ts.map +1 -1
  241. package/dist/serializer.d.ts +11 -3
  242. package/dist/serializer.d.ts.map +1 -1
  243. package/dist/skill-defs.d.ts.map +1 -1
  244. package/dist/skills/chant-grafana-alerting.md +49 -0
  245. package/dist/skills/chant-grafana-provisioning.md +3 -3
  246. package/dist/skills/chant-grafana.md +12 -6
  247. package/dist/spec/fetch-units-cli.d.ts +3 -0
  248. package/dist/spec/fetch-units-cli.d.ts.map +1 -0
  249. package/dist/spec/overlay.d.ts +43 -0
  250. package/dist/spec/overlay.d.ts.map +1 -0
  251. package/dist/spec/schemas.d.ts +13 -7
  252. package/dist/spec/schemas.d.ts.map +1 -1
  253. package/dist/spec/units.d.ts +35 -0
  254. package/dist/spec/units.d.ts.map +1 -0
  255. package/dist/spec/units.gen.d.ts +2 -0
  256. package/dist/spec/units.gen.d.ts.map +1 -0
  257. package/dist/transformations.d.ts +459 -0
  258. package/dist/transformations.d.ts.map +1 -0
  259. package/dist/util.d.ts +6 -0
  260. package/dist/util.d.ts.map +1 -1
  261. package/dist/validate-alerting.d.ts +65 -0
  262. package/dist/validate-alerting.d.ts.map +1 -0
  263. package/dist/validate-output.d.ts +49 -9
  264. package/dist/validate-output.d.ts.map +1 -1
  265. package/dist/validate.d.ts.map +1 -1
  266. package/dist/variables.d.ts +7 -2
  267. package/dist/variables.d.ts.map +1 -1
  268. package/package.json +12 -4
  269. package/src/actions/actions.test.ts +24 -0
  270. package/src/actions/grafana.ts +40 -0
  271. package/src/actions/index.ts +1 -0
  272. package/src/alerting-build.ts +376 -0
  273. package/src/alerting.e2e.test.ts +212 -0
  274. package/src/alerting.test.ts +272 -0
  275. package/src/alerting.ts +435 -0
  276. package/src/api/apply.test.ts +238 -0
  277. package/src/api/apply.ts +384 -0
  278. package/src/api/bind.ts +58 -0
  279. package/src/api/client.ts +148 -0
  280. package/src/api/converge.ts +63 -0
  281. package/src/api/dashboards.ts +247 -0
  282. package/src/api/datasources.ts +33 -0
  283. package/src/api/fake-grafana-writes.ts +201 -0
  284. package/src/api/fake-grafana.ts +186 -0
  285. package/src/api/folders.ts +265 -0
  286. package/src/api/library-panels.ts +101 -0
  287. package/src/build.test.ts +210 -5
  288. package/src/build.ts +180 -43
  289. package/src/catalog.ts +86 -2
  290. package/src/codegen/docs.ts +42 -9
  291. package/src/codegen/schema-types.ts +16 -6
  292. package/src/composites/agent-dashboard.ts +6 -3
  293. package/src/composites/catalog.test.ts +2 -2
  294. package/src/composites/catalog.ts +82 -0
  295. package/src/composites/composites.test.ts +73 -9
  296. package/src/composites/index.ts +4 -2
  297. package/src/composites/queries.e2e.test.ts +374 -0
  298. package/src/composites/red-dashboard.ts +83 -12
  299. package/src/composites/shared.ts +11 -0
  300. package/src/composites/slo-alert-rules.ts +149 -0
  301. package/src/config.test.ts +61 -0
  302. package/src/config.ts +138 -0
  303. package/src/contact-point-secrets.ts +63 -0
  304. package/src/dashboard.ts +16 -2
  305. package/src/datasource-refs.test.ts +56 -0
  306. package/src/datasource-refs.ts +189 -0
  307. package/src/datasource.ts +52 -0
  308. package/src/deep-observe-hooks.ts +252 -0
  309. package/src/deep-observe.test.ts +265 -0
  310. package/src/deep-observe.ts +164 -0
  311. package/src/describe-resources.test.ts +255 -0
  312. package/src/describe-resources.ts +209 -0
  313. package/src/detect.ts +79 -2
  314. package/src/export-resources.test.ts +129 -0
  315. package/src/export-resources.ts +84 -0
  316. package/src/generated/lexicon-grafana.json +135 -0
  317. package/src/import/alerting-roundtrip.test.ts +161 -0
  318. package/src/import/alerting.ts +500 -0
  319. package/src/import/edits.ts +95 -0
  320. package/src/import/embedded.test.ts +45 -0
  321. package/src/import/embedded.ts +47 -0
  322. package/src/import/generated-types.e2e.test.ts +118 -0
  323. package/src/import/generator.test.ts +210 -0
  324. package/src/import/generator.ts +462 -0
  325. package/src/import/live-export.ts +264 -0
  326. package/src/import/mappings.ts +264 -0
  327. package/src/import/model.ts +110 -0
  328. package/src/import/normalize-alerting.ts +101 -0
  329. package/src/import/normalize.test.ts +117 -0
  330. package/src/import/normalize.ts +364 -0
  331. package/src/import/parser.test.ts +440 -0
  332. package/src/import/parser.ts +1236 -0
  333. package/src/import/report.ts +62 -0
  334. package/src/import/roundtrip.test.ts +468 -0
  335. package/src/import/testdata/fixtures.ts +129 -0
  336. package/src/import/v2.test.ts +292 -0
  337. package/src/import/v2.ts +803 -0
  338. package/src/import.e2e.test.ts +31 -84
  339. package/src/import.test.ts +9 -3
  340. package/src/index.ts +190 -5
  341. package/src/k8s.test.ts +98 -0
  342. package/src/k8s.ts +197 -0
  343. package/src/lint/audit-catalog.ts +60 -3
  344. package/src/lint/core-lint.test.ts +43 -0
  345. package/src/lint/post-synth/graf101.ts +3 -3
  346. package/src/lint/post-synth/graf102.ts +1 -1
  347. package/src/lint/post-synth/graf107.ts +1 -1
  348. package/src/lint/post-synth/graf108.ts +17 -0
  349. package/src/lint/post-synth/graf109.ts +17 -0
  350. package/src/lint/post-synth/graf111.ts +17 -0
  351. package/src/lint/post-synth/graf112.ts +17 -0
  352. package/src/lint/post-synth/graf113.ts +17 -0
  353. package/src/lint/post-synth/graf114.ts +17 -0
  354. package/src/lint/post-synth/graf115.ts +17 -0
  355. package/src/lint/post-synth/grafana-helpers.ts +15 -8
  356. package/src/lint/post-synth/index.ts +14 -0
  357. package/src/lint/post-synth/post-synth.test.ts +235 -11
  358. package/src/lint/rules/literal-secret.ts +60 -6
  359. package/src/lint/rules/rules.test.ts +27 -0
  360. package/src/lsp/completions.test.ts +5 -0
  361. package/src/lsp/hover.test.ts +7 -0
  362. package/src/lsp/hover.ts +8 -1
  363. package/src/members.ts +68 -0
  364. package/src/observe.e2e.test.ts +255 -0
  365. package/src/op/activities/grafana-apply.e2e.test.ts +175 -0
  366. package/src/op/activities/grafana-apply.test.ts +229 -0
  367. package/src/op/activities/grafana-apply.ts +171 -0
  368. package/src/op/activities/index.ts +8 -0
  369. package/src/op/builders.test.ts +40 -0
  370. package/src/op/builders.ts +34 -0
  371. package/src/ownership.ts +103 -0
  372. package/src/panel-options.ts +66 -0
  373. package/src/panels.test.ts +51 -0
  374. package/src/panels.ts +148 -10
  375. package/src/pin.ts +57 -6
  376. package/src/plugin.test.ts +5 -2
  377. package/src/plugin.ts +66 -0
  378. package/src/promql-check.test.ts +108 -0
  379. package/src/promql-check.ts +189 -0
  380. package/src/query.ts +13 -8
  381. package/src/schema/barchart.gen.ts +221 -0
  382. package/src/schema/bargauge.gen.ts +111 -0
  383. package/src/schema/canvas.gen.ts +176 -0
  384. package/src/schema/dashboard.gen.ts +206 -22
  385. package/src/schema/expr.gen.ts +618 -0
  386. package/src/schema/gauge.gen.ts +2 -1
  387. package/src/schema/geomap.gen.ts +112 -0
  388. package/src/schema/heatmap.gen.ts +2 -1
  389. package/src/schema/histogram.gen.ts +151 -0
  390. package/src/schema/index.ts +12 -0
  391. package/src/schema/logs.gen.ts +3 -1
  392. package/src/schema/loki.gen.ts +2 -1
  393. package/src/schema/nodegraph.gen.ts +58 -0
  394. package/src/schema/piechart.gen.ts +146 -0
  395. package/src/schema/prometheus.gen.ts +2 -1
  396. package/src/schema/stat.gen.ts +2 -1
  397. package/src/schema/statetimeline.gen.ts +143 -0
  398. package/src/schema/statushistory.gen.ts +132 -0
  399. package/src/schema/table.gen.ts +27 -4
  400. package/src/schema/tempo.gen.ts +2 -1
  401. package/src/schema/text.gen.ts +2 -1
  402. package/src/schema/timeseries.gen.ts +7 -2
  403. package/src/schema/trend.gen.ts +208 -0
  404. package/src/schema/xychart.gen.ts +167 -0
  405. package/src/schema-overlay.test.ts +282 -0
  406. package/src/schema-validate.ts +141 -50
  407. package/src/schema.test.ts +4 -2
  408. package/src/serializer.test.ts +13 -1
  409. package/src/serializer.ts +11 -3
  410. package/src/skill-defs.ts +17 -0
  411. package/src/skills/chant-grafana-alerting.md +49 -0
  412. package/src/skills/chant-grafana-provisioning.md +3 -3
  413. package/src/skills/chant-grafana.md +12 -6
  414. package/src/spec/fetch-cli.ts +3 -3
  415. package/src/spec/fetch-units-cli.ts +37 -0
  416. package/src/spec/overlay/canvas.overlay.json +142 -0
  417. package/src/spec/overlay/dashboard.overlay.json +378 -0
  418. package/src/spec/overlay/expr.overlay.json +14 -0
  419. package/src/spec/overlay/geomap.overlay.json +35 -0
  420. package/src/spec/overlay/logs.overlay.json +14 -0
  421. package/src/spec/overlay/nodegraph.overlay.json +31 -0
  422. package/src/spec/overlay/piechart.overlay.json +17 -0
  423. package/src/spec/overlay/table.overlay.json +84 -0
  424. package/src/spec/overlay/timeseries.overlay.json +21 -0
  425. package/src/spec/overlay/trend.overlay.json +17 -0
  426. package/src/spec/overlay/xychart.overlay.json +16 -0
  427. package/src/spec/overlay.ts +105 -0
  428. package/src/spec/schemas/barchart.jsonschema.json +419 -0
  429. package/src/spec/schemas/bargauge.jsonschema.json +224 -0
  430. package/src/spec/schemas/canvas.jsonschema.json +356 -0
  431. package/src/spec/schemas/dashboardv2.jsonschema.json +3387 -0
  432. package/src/spec/schemas/expr.jsonschema.json +987 -0
  433. package/src/spec/schemas/geomap.jsonschema.json +220 -0
  434. package/src/spec/schemas/histogram.jsonschema.json +308 -0
  435. package/src/spec/schemas/nodegraph.jsonschema.json +77 -0
  436. package/src/spec/schemas/piechart.jsonschema.json +275 -0
  437. package/src/spec/schemas/statetimeline.jsonschema.json +324 -0
  438. package/src/spec/schemas/statushistory.jsonschema.json +292 -0
  439. package/src/spec/schemas/trend.jsonschema.json +449 -0
  440. package/src/spec/schemas/xychart.jsonschema.json +428 -0
  441. package/src/spec/schemas.ts +22 -10
  442. package/src/spec/units.gen.ts +281 -0
  443. package/src/spec/units.ts +77 -0
  444. package/src/transformations.test.ts +65 -0
  445. package/src/transformations.ts +574 -0
  446. package/src/util.ts +8 -0
  447. package/src/validate-alerting.test.ts +255 -0
  448. package/src/validate-alerting.ts +428 -0
  449. package/src/validate-output.ts +332 -101
  450. package/src/validate.ts +14 -0
  451. package/src/variables.ts +7 -2
@@ -0,0 +1,101 @@
1
+ /**
2
+ * An alerting provisioning file in a canonical form, so two files Grafana
3
+ * provisions the same way compare equal. The round-trip tests compare the
4
+ * source (with the importer's edits applied) and the rebuilt file through
5
+ * this.
6
+ *
7
+ * What it treats as equal, each from Grafana's file reader
8
+ * (`pkg/services/provisioning/alerting/*_types.go`, v12.4.11 and v13.2.2):
9
+ *
10
+ * - `orgId: 1` and no `orgId` (a value below 1 reads as 1);
11
+ * - a rule's `noDataState: NoData`, `execErrState: Alerting`, `for: 0s`,
12
+ * `keepFiringFor: 0s`, `isPaused: false`, `panelId: 0`, empty `labels`
13
+ * and `annotations`, and the same left out;
14
+ * - a query's `queryType: ""`, and a `relativeTimeRange` of 0 to 0, and
15
+ * the same left out;
16
+ * - an expression model's `datasource` of `__expr__` and a `refId` equal
17
+ * to the query's, and the same left out (Grafana sets both);
18
+ * - `disableResolveMessage: false` and none;
19
+ * - the order of rule groups, contact points, mute timings and templates,
20
+ * which chant writes sorted.
21
+ */
22
+
23
+ import { EXPRESSION_DATASOURCE_UID } from "../alerting";
24
+ import { isObject } from "./normalize";
25
+
26
+ type Json = Record<string, unknown>;
27
+
28
+ function dropOrg(o: Json): Json {
29
+ const { orgId, ...rest } = o;
30
+ return orgId === undefined || orgId === 1 || (typeof orgId === "number" && orgId < 1) ? rest : o;
31
+ }
32
+
33
+ function isEmpty(v: unknown): boolean {
34
+ return v === undefined || v === null || (isObject(v) && Object.keys(v).length === 0);
35
+ }
36
+
37
+ function query(q: Json): Json {
38
+ const out: Json = { ...q };
39
+ if (out.queryType === "" || out.queryType === undefined) delete out.queryType;
40
+ const r = out.relativeTimeRange;
41
+ if (isObject(r) && (r.from ?? 0) === 0 && (r.to ?? 0) === 0) delete out.relativeTimeRange;
42
+ else if (isObject(r)) out.relativeTimeRange = { from: r.from ?? 0, to: r.to ?? 0 };
43
+ if (out.datasourceUid === EXPRESSION_DATASOURCE_UID && isObject(out.model)) {
44
+ const m: Json = { ...out.model };
45
+ if (isObject(m.datasource) && m.datasource.uid === EXPRESSION_DATASOURCE_UID) delete m.datasource;
46
+ if (m.refId === out.refId) delete m.refId;
47
+ out.model = m;
48
+ }
49
+ return out;
50
+ }
51
+
52
+ const RULE_DEFAULTS: Json = { noDataState: "NoData", execErrState: "Alerting", for: "0s", keepFiringFor: "0s", isPaused: false, panelId: 0, dashboardUid: "" };
53
+
54
+ function rule(r: Json): Json {
55
+ const out: Json = { ...r };
56
+ for (const [k, v] of Object.entries(RULE_DEFAULTS)) if (out[k] === v || out[k] === null) delete out[k];
57
+ if (out.for === "0") delete out.for;
58
+ if (out.keepFiringFor === "0") delete out.keepFiringFor;
59
+ for (const k of ["labels", "annotations"]) if (isEmpty(out[k])) delete out[k];
60
+ if (Array.isArray(out.data)) out.data = out.data.map((q) => (isObject(q) ? query(q) : q));
61
+ return out;
62
+ }
63
+
64
+ function byName(a: unknown, b: unknown): number {
65
+ const ka = isObject(a) ? `${String(a.folder ?? "")}\u0000${String(a.name ?? "")}` : "";
66
+ const kb = isObject(b) ? `${String(b.folder ?? "")}\u0000${String(b.name ?? "")}` : "";
67
+ return ka.localeCompare(kb);
68
+ }
69
+
70
+ /** The file in canonical form. */
71
+ export function normalizeAlerting(file: Json): Json {
72
+ const out: Json = {};
73
+ if (file.apiVersion !== undefined) out.apiVersion = file.apiVersion;
74
+ if (Array.isArray(file.groups) && file.groups.length > 0) {
75
+ out.groups = file.groups
76
+ .map((g) => (isObject(g) ? { ...dropOrg(g), rules: Array.isArray(g.rules) ? g.rules.map((r) => (isObject(r) ? rule(r) : r)) : g.rules } : g))
77
+ .sort(byName);
78
+ }
79
+ if (Array.isArray(file.contactPoints) && file.contactPoints.length > 0) {
80
+ out.contactPoints = file.contactPoints
81
+ .map((c) =>
82
+ isObject(c)
83
+ ? {
84
+ ...dropOrg(c),
85
+ receivers: Array.isArray(c.receivers)
86
+ ? c.receivers.map((r) => {
87
+ if (!isObject(r)) return r;
88
+ const { disableResolveMessage, ...rest } = r;
89
+ return disableResolveMessage === true ? r : rest;
90
+ })
91
+ : c.receivers,
92
+ }
93
+ : c,
94
+ )
95
+ .sort(byName);
96
+ }
97
+ if (Array.isArray(file.policies) && file.policies.length > 0) out.policies = file.policies.map((p) => (isObject(p) ? dropOrg(p) : p));
98
+ if (Array.isArray(file.muteTimes) && file.muteTimes.length > 0) out.muteTimes = file.muteTimes.map((m) => (isObject(m) ? dropOrg(m) : m)).sort(byName);
99
+ if (Array.isArray(file.templates) && file.templates.length > 0) out.templates = file.templates.map((t) => (isObject(t) ? dropOrg(t) : t)).sort(byName);
100
+ return out;
101
+ }
@@ -0,0 +1,117 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { canonicalVariableUid, isBuiltinAnnotation, normalizeDashboard, refIdAt, splitValues } from "./normalize";
3
+ import { applyEdits, pointer, pointerSegments } from "./edits";
4
+
5
+ const prom = { type: "prometheus", uid: "prom" };
6
+
7
+ describe("normalizeDashboard", () => {
8
+ test("keys at Grafana's default and the stored copy's bookkeeping are the same as no key", () => {
9
+ const stored = {
10
+ id: 12,
11
+ version: 4,
12
+ title: "T",
13
+ uid: "t",
14
+ editable: true,
15
+ graphTooltip: 0,
16
+ links: [],
17
+ tags: [],
18
+ time: { from: "now-6h", to: "now" },
19
+ timepicker: {},
20
+ timezone: "",
21
+ weekStart: "",
22
+ refresh: false,
23
+ annotations: { list: [] },
24
+ templating: { list: [] },
25
+ panels: [],
26
+ };
27
+ expect(normalizeDashboard(stored)).toEqual({ title: "T", uid: "t", panels: [] });
28
+ });
29
+
30
+ test("the built-in annotation, in any of the forms Grafana has written it, is no annotation", () => {
31
+ const base = { builtIn: 1, enable: true, hide: true, iconColor: "rgba(0, 211, 255, 1)", name: "Annotations & Alerts", type: "dashboard" };
32
+ for (const datasource of ["-- Grafana --", { type: "grafana", uid: "-- Grafana --" }, { type: "datasource", uid: "grafana" }, { uid: "-- Grafana --" }]) {
33
+ expect(isBuiltinAnnotation({ ...base, datasource })).toBe(true);
34
+ }
35
+ expect(isBuiltinAnnotation({ ...base, datasource: "-- Grafana --", target: { limit: 100, matchAny: false, tags: [], type: "dashboard" } })).toBe(true);
36
+ expect(isBuiltinAnnotation({ ...base, datasource: "-- Grafana --", enable: false })).toBe(false);
37
+ expect(isBuiltinAnnotation({ ...base, datasource: prom })).toBe(false);
38
+ });
39
+
40
+ test("a query without a datasource or refId gets its panel's and its position's", () => {
41
+ const d = normalizeDashboard({ panels: [{ type: "stat", datasource: { type: "prometheus", uid: "$ds" }, targets: [{ expr: "up" }, { expr: "down", refId: "" }] }] });
42
+ const ds = { type: "prometheus", uid: "${ds}" };
43
+ expect(d.panels).toEqual([
44
+ { type: "stat", datasource: ds, fieldConfig: { defaults: {}, overrides: [] }, targets: [{ expr: "up", datasource: ds, refId: "A" }, { expr: "down", datasource: ds, refId: "B" }] },
45
+ ]);
46
+ });
47
+
48
+ test("a panel's datasource is the one its queries share, whatever the panel says", () => {
49
+ const mixed = { type: "datasource", uid: "-- Mixed --" };
50
+ const [p] = normalizeDashboard({ panels: [{ type: "stat", datasource: mixed, targets: [{ refId: "A", datasource: prom }] }] }).panels as Array<{ datasource: unknown }>;
51
+ expect(p.datasource).toEqual(prom);
52
+ });
53
+
54
+ test("derived variable keys: options, a query variable's definition, a constant's current", () => {
55
+ const d = normalizeDashboard({
56
+ templating: {
57
+ list: [
58
+ { type: "query", name: "a", query: "q", definition: "q", refresh: 1, options: [{ text: "x", value: "x" }], current: {} },
59
+ { type: "query", name: "b", query: "q", refresh: 0, options: [{ text: "x", value: "x" }] },
60
+ { type: "custom", name: "c", query: "x, y", options: [] },
61
+ { type: "constant", name: "k", query: "v", hide: 2, current: { text: "v", value: "v" } },
62
+ { type: "textbox", name: "t", query: "v", current: { text: "v", value: "v", selected: false } },
63
+ ],
64
+ },
65
+ });
66
+ expect(d.templating).toEqual({
67
+ list: [
68
+ { type: "query", name: "a", query: "q" },
69
+ { type: "query", name: "b", query: "q", refresh: 0, options: [{ text: "x", value: "x" }] },
70
+ { type: "custom", name: "c", query: "x,y", current: { text: "x", value: "x" } },
71
+ { type: "constant", name: "k", query: "v" },
72
+ { type: "textbox", name: "t", query: "v" },
73
+ ],
74
+ });
75
+ });
76
+
77
+ test("nothing inside options is touched: a null there means something", () => {
78
+ const steps = [{ color: "green", value: null }];
79
+ const d = normalizeDashboard({ panels: [{ type: "stat", fieldConfig: { defaults: { thresholds: { steps } } } }] });
80
+ expect((d.panels as Array<{ fieldConfig: { defaults: unknown } }>)[0].fieldConfig.defaults).toEqual({ thresholds: { steps } });
81
+ });
82
+ });
83
+
84
+ describe("helpers", () => {
85
+ test("refIdAt", () => {
86
+ expect([0, 1, 25, 26, 27].map(refIdAt)).toEqual(["A", "B", "Z", "AA", "AB"]);
87
+ });
88
+
89
+ test("canonicalVariableUid", () => {
90
+ expect(["$ds", "${ds}", "[[ds]]", "${ds:raw}", "prom"].map(canonicalVariableUid)).toEqual(["${ds}", "${ds}", "${ds}", "${ds:raw}", "prom"]);
91
+ });
92
+
93
+ test("splitValues: commas, escaped commas and spaces", () => {
94
+ expect(splitValues("a, b\\,c ,, d")).toEqual(["a", "b,c", "d"]);
95
+ });
96
+ });
97
+
98
+ describe("applyEdits", () => {
99
+ test("substitutions, then replacements and removals by pointers into the original, then prepended variables", () => {
100
+ const source = { title: "${VAR}", panels: [{ id: 1 }, { id: 2, "a/b": 1 }, { id: 3 }], templating: { list: [{ name: "x" }] } };
101
+ const out = applyEdits(source, [
102
+ { op: "substitute", from: "${VAR}", to: "v" },
103
+ { op: "remove", path: "/panels/0" },
104
+ { op: "replace", path: pointer("panels", 1, "a/b"), value: 2 },
105
+ { op: "remove", path: "/panels/2" },
106
+ { op: "prependVariable", value: { name: "DS" } },
107
+ ]);
108
+ expect(out).toEqual({ title: "v", panels: [{ id: 2, "a/b": 2 }], templating: { list: [{ name: "DS" }, { name: "x" }] } });
109
+ // The source is left as it was.
110
+ expect(source.panels).toHaveLength(3);
111
+ });
112
+
113
+ test("pointers escape / and ~", () => {
114
+ expect(pointer("a/b", "c~d", 0)).toBe("/a~1b/c~0d/0");
115
+ expect(pointerSegments("/a~1b/c~0d/0")).toEqual(["a/b", "c~d", "0"]);
116
+ });
117
+ });
@@ -0,0 +1,364 @@
1
+ /**
2
+ * Classic dashboard JSON in a canonical form, so two dashboards Grafana
3
+ * treats the same compare equal.
4
+ *
5
+ * Grafana fills in a default for most keys a dashboard leaves out, derives
6
+ * some keys from others (a custom variable's `options` from its `query`),
7
+ * and resolves a query without a datasource to its panel's. A dashboard it
8
+ * exported and the same dashboard rebuilt by chant differ in exactly those
9
+ * ways, so both are put through `normalizeDashboard` before they are
10
+ * compared: keys at their default are removed, derived keys are removed,
11
+ * and references are written one way.
12
+ *
13
+ * The import round trip compares with it, the importer asks `isDefault`
14
+ * whether a key it cannot carry is worth a warning (a key at its default is
15
+ * not), and drift detection (#2946) can compare a stored dashboard with a
16
+ * built one the same way.
17
+ *
18
+ * Only the dashboard, panel, row, target, variable and link levels are
19
+ * touched. Nothing inside `options`, `fieldConfig`, `transformations` or a
20
+ * query model is: a `null` in there (a threshold step's base value) means
21
+ * something.
22
+ */
23
+
24
+ import { customVariableOptions } from "../build";
25
+
26
+ type Json = Record<string, unknown>;
27
+
28
+ /** Grafana's value for a dashboard key the JSON leaves out. */
29
+ export const DASHBOARD_DEFAULTS: Readonly<Json> = {
30
+ editable: true,
31
+ fiscalYearStartMonth: 0,
32
+ graphTooltip: 0,
33
+ links: [],
34
+ tags: [],
35
+ time: { from: "now-6h", to: "now" },
36
+ timepicker: {},
37
+ timezone: "",
38
+ weekStart: "",
39
+ refresh: "",
40
+ liveNow: false,
41
+ preload: false,
42
+ description: "",
43
+ };
44
+
45
+ /** Grafana's value for a panel key the JSON leaves out. */
46
+ export const PANEL_DEFAULTS: Readonly<Json> = {
47
+ title: "",
48
+ description: "",
49
+ options: {},
50
+ targets: [],
51
+ transformations: [],
52
+ links: [],
53
+ transparent: false,
54
+ hideTimeOverride: false,
55
+ repeatDirection: "h",
56
+ };
57
+
58
+ /** Grafana's value for a row key the JSON leaves out. */
59
+ export const ROW_DEFAULTS: Readonly<Json> = {
60
+ collapsed: false,
61
+ panels: [],
62
+ title: "",
63
+ };
64
+
65
+ /** Grafana's value for a variable key the JSON leaves out, whatever the variable's type. */
66
+ export const VARIABLE_DEFAULTS: Readonly<Json> = {
67
+ hide: 0,
68
+ skipUrlSync: false,
69
+ label: "",
70
+ description: "",
71
+ multi: false,
72
+ includeAll: false,
73
+ allValue: "",
74
+ regex: "",
75
+ sort: 0,
76
+ current: {},
77
+ // Added by later Grafana versions, at the value that keeps the old behaviour.
78
+ allowCustomValue: true,
79
+ regexApplyTo: "value",
80
+ valuesFormat: "csv",
81
+ // Tag support was removed in Grafana 8; the keys linger in older dashboards.
82
+ useTags: false,
83
+ tagsQuery: "",
84
+ tagValuesQuery: "",
85
+ tags: [],
86
+ queryValue: "",
87
+ };
88
+
89
+ /** Per variable type, Grafana's value for a key the JSON leaves out, over `VARIABLE_DEFAULTS`. */
90
+ export const VARIABLE_TYPE_DEFAULTS: Readonly<Record<string, Json>> = {
91
+ query: { refresh: 1 },
92
+ datasource: { refresh: 1 },
93
+ interval: { refresh: 2, auto: false, auto_count: 30, auto_min: "10s" },
94
+ };
95
+
96
+ /** The defaults chant writes into a dashboard or panel link, which Grafana reads as unset. */
97
+ export const LINK_DEFAULTS: Readonly<Json> = {
98
+ type: "link",
99
+ icon: "external link",
100
+ tooltip: "",
101
+ tags: [],
102
+ asDropdown: false,
103
+ targetBlank: false,
104
+ includeVars: false,
105
+ keepTime: false,
106
+ };
107
+
108
+ /** The annotation Grafana adds to every dashboard that has none: annotations and alerts from its own database. */
109
+ export const BUILTIN_ANNOTATION: Readonly<Json> = {
110
+ builtIn: 1,
111
+ datasource: { type: "grafana", uid: "-- Grafana --" },
112
+ enable: true,
113
+ hide: true,
114
+ iconColor: "rgba(0, 211, 255, 1)",
115
+ name: "Annotations & Alerts",
116
+ type: "dashboard",
117
+ };
118
+
119
+ /** Keys of a stored dashboard that belong to that copy (its database id, save counter), not to the dashboard. */
120
+ export const BOOKKEEPING_KEYS: readonly string[] = ["id", "version", "iteration"];
121
+
122
+ /** The panel datasource Grafana writes when a panel's queries go to different datasources. */
123
+ export const MIXED_UID = "-- Mixed --";
124
+
125
+ export function isObject(v: unknown): v is Json {
126
+ return typeof v === "object" && v !== null && !Array.isArray(v);
127
+ }
128
+
129
+ export function deepEqual(a: unknown, b: unknown): boolean {
130
+ if (a === b) return true;
131
+ if (Array.isArray(a) || Array.isArray(b)) {
132
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
133
+ return a.every((x, i) => deepEqual(x, b[i]));
134
+ }
135
+ if (isObject(a) && isObject(b)) {
136
+ const ka = Object.keys(a).filter((k) => a[k] !== undefined);
137
+ const kb = Object.keys(b).filter((k) => b[k] !== undefined);
138
+ if (ka.length !== kb.length) return false;
139
+ return ka.every((k) => deepEqual(a[k], b[k]));
140
+ }
141
+ return false;
142
+ }
143
+
144
+ /** True when `value` is what Grafana assumes for `key` when it is left out (a `null` counts as left out). */
145
+ export function isDefault(defaults: Readonly<Json>, key: string, value: unknown): boolean {
146
+ if (!(key in defaults)) return false;
147
+ return value === null || value === undefined || deepEqual(value, defaults[key]);
148
+ }
149
+
150
+ /** Defaults for one variable type. */
151
+ export function variableDefaults(type: unknown): Json {
152
+ return { ...VARIABLE_DEFAULTS, ...(VARIABLE_TYPE_DEFAULTS[String(type)] ?? {}) };
153
+ }
154
+
155
+ function dropDefaults(obj: Json, defaults: Readonly<Json>): void {
156
+ for (const key of Object.keys(obj)) if (isDefault(defaults, key, obj[key])) delete obj[key];
157
+ }
158
+
159
+ const REF_IDS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
160
+
161
+ /** The refId Grafana (and chant) gives the query at `index` when it has none: A, B, … Z, AA, AB, … */
162
+ export function refIdAt(i: number): string {
163
+ return i < REF_IDS.length ? REF_IDS[i] : `${REF_IDS[Math.floor(i / REF_IDS.length) - 1]}${REF_IDS[i % REF_IDS.length]}`;
164
+ }
165
+
166
+ /** `$name` and `[[name]]` written as `${name}`, the form a datasource variable's reference takes. */
167
+ export function canonicalVariableUid(uid: string): string {
168
+ const m = /^\$([A-Za-z_][A-Za-z0-9_]*)$/.exec(uid) ?? /^\[\[([A-Za-z_][A-Za-z0-9_]*)\]\]$/.exec(uid);
169
+ return m ? `\${${m[1]}}` : uid;
170
+ }
171
+
172
+ function canonicalRef(ref: unknown): unknown {
173
+ if (!isObject(ref)) return ref;
174
+ const out = { ...ref };
175
+ if (typeof out.uid === "string") out.uid = canonicalVariableUid(out.uid);
176
+ return out;
177
+ }
178
+
179
+ function sameRef(a: unknown, b: unknown): boolean {
180
+ return isObject(a) && isObject(b) && a.type === b.type && a.uid === b.uid;
181
+ }
182
+
183
+ function isMixed(ref: unknown): boolean {
184
+ return isObject(ref) && ref.uid === MIXED_UID;
185
+ }
186
+
187
+ /** A custom or interval variable's `query` as the list of values Grafana splits it into. */
188
+ export function splitValues(query: string): string[] {
189
+ const out: string[] = [];
190
+ for (const m of query.matchAll(/(?:\\,|[^,])+/g)) {
191
+ const v = m[0].trim().replace(/\\,/g, ",");
192
+ if (v !== "") out.push(v);
193
+ }
194
+ return out;
195
+ }
196
+
197
+ function normalizeLink(link: unknown): unknown {
198
+ if (!isObject(link)) return link;
199
+ const out = { ...link };
200
+ dropDefaults(out, LINK_DEFAULTS);
201
+ return out;
202
+ }
203
+
204
+ /**
205
+ * Per datasource type, a query key's value when the target leaves it out.
206
+ * A Tempo query without `filters` has no search filters, which `TempoQuery`
207
+ * writes as `filters: []`.
208
+ */
209
+ export const TARGET_DEFAULTS: Readonly<Record<string, Readonly<Json>>> = {
210
+ tempo: { filters: [] },
211
+ };
212
+
213
+ function normalizeTarget(target: unknown, index: number, panelDatasource: unknown): unknown {
214
+ if (!isObject(target)) return target;
215
+ const out = { ...target };
216
+ if (out.datasource === null || out.datasource === undefined) {
217
+ if (panelDatasource !== undefined && !isMixed(panelDatasource)) out.datasource = panelDatasource;
218
+ else delete out.datasource;
219
+ } else {
220
+ out.datasource = canonicalRef(out.datasource);
221
+ }
222
+ const type = isObject(out.datasource) ? out.datasource.type : undefined;
223
+ const defaults = typeof type === "string" ? TARGET_DEFAULTS[type] : undefined;
224
+ if (defaults) dropDefaults(out, defaults);
225
+ if (out.refId === undefined || out.refId === null || out.refId === "") out.refId = refIdAt(index);
226
+ return out;
227
+ }
228
+
229
+ function normalizePanel(panel: unknown): unknown {
230
+ if (!isObject(panel)) return panel;
231
+ if (panel.type === "row") return normalizeRow(panel);
232
+ const out: Json = { ...panel };
233
+ for (const key of Object.keys(out)) if (out[key] === null) delete out[key];
234
+ const own = out.datasource === undefined ? undefined : canonicalRef(out.datasource);
235
+ const targets = Array.isArray(out.targets) ? out.targets.map((t, i) => normalizeTarget(t, i, own)) : [];
236
+ if (targets.length > 0) out.targets = targets;
237
+ else delete out.targets;
238
+ // The panel's datasource is the one its queries share, or Mixed when they differ.
239
+ const refs = targets.map((t) => (isObject(t) ? t.datasource : undefined));
240
+ if (refs.length > 0 && refs.every((r) => r !== undefined && sameRef(r, refs[0]))) out.datasource = refs[0];
241
+ else if (own !== undefined) out.datasource = own;
242
+ else delete out.datasource;
243
+ const fc = isObject(out.fieldConfig) ? out.fieldConfig : {};
244
+ out.fieldConfig = { defaults: isObject(fc.defaults) ? fc.defaults : {}, overrides: Array.isArray(fc.overrides) ? fc.overrides : [] };
245
+ if (Array.isArray(out.links)) out.links = out.links.map(normalizeLink);
246
+ dropDefaults(out, PANEL_DEFAULTS);
247
+ return out;
248
+ }
249
+
250
+ function normalizeRow(row: Json): Json {
251
+ const out: Json = { ...row };
252
+ for (const key of Object.keys(out)) if (out[key] === null) delete out[key];
253
+ if (out.datasource !== undefined) out.datasource = canonicalRef(out.datasource);
254
+ if (Array.isArray(out.panels)) out.panels = out.panels.map(normalizePanel);
255
+ dropDefaults(out, ROW_DEFAULTS);
256
+ return out;
257
+ }
258
+
259
+ function normalizeVariable(variable: unknown): unknown {
260
+ if (!isObject(variable)) return variable;
261
+ const out: Json = { ...variable };
262
+ const type = out.type;
263
+ if (out.datasource === null) delete out.datasource;
264
+ else if (out.datasource !== undefined) out.datasource = canonicalRef(out.datasource);
265
+ const query = typeof out.query === "string" ? out.query : undefined;
266
+ switch (type) {
267
+ case "query":
268
+ // `definition` is the query as the editor shows it; Grafana reads `query`.
269
+ if (out.definition === undefined || out.definition === "" || (query !== undefined && out.definition === query)) delete out.definition;
270
+ // Grafana runs the query again when the dashboard loads (or the time range changes).
271
+ if (out.refresh !== 0) delete out.options;
272
+ break;
273
+ case "custom":
274
+ case "interval": {
275
+ const values = splitValues(query ?? "");
276
+ out.query = values.join(",");
277
+ delete out.options;
278
+ if (type === "custom" && (!isObject(out.current) || Object.keys(out.current).length === 0) && values.length > 0) {
279
+ // Grafana selects the first option, with a `text : value` item split into its text and value.
280
+ const first = customVariableOptions(query ?? "")[0];
281
+ if (first) out.current = { text: first.text, value: first.value };
282
+ }
283
+ out.refresh = type === "interval" ? 2 : out.refresh;
284
+ break;
285
+ }
286
+ case "datasource":
287
+ delete out.options;
288
+ break;
289
+ case "constant":
290
+ // Grafana shows a constant's value from `query` and never shows the variable.
291
+ delete out.options;
292
+ delete out.current;
293
+ delete out.hide;
294
+ break;
295
+ case "textbox": {
296
+ delete out.options;
297
+ const cur = isObject(out.current) ? out.current : undefined;
298
+ if (!cur || Object.keys(cur).length === 0 || (cur.value === (query ?? "") && (cur.text === undefined || cur.text === (query ?? "")))) {
299
+ delete out.current;
300
+ }
301
+ break;
302
+ }
303
+ }
304
+ if (isObject(out.current)) {
305
+ const { selected: _selected, ...current } = out.current;
306
+ out.current = current;
307
+ }
308
+ dropDefaults(out, variableDefaults(type));
309
+ return out;
310
+ }
311
+
312
+ /** The forms Grafana has written its own pseudo-datasource in, over the versions. */
313
+ function isGrafanaDatasource(ds: unknown): boolean {
314
+ if (ds === "-- Grafana --" || ds === "grafana") return true;
315
+ if (!isObject(ds)) return false;
316
+ const keys = Object.keys(ds).filter((k) => ds[k] !== undefined);
317
+ if (keys.some((k) => k !== "type" && k !== "uid")) return false;
318
+ return (ds.uid === "-- Grafana --" || ds.uid === "grafana") && (ds.type === undefined || ds.type === "grafana" || ds.type === "datasource");
319
+ }
320
+
321
+ /** The target Grafana writes into the built-in annotation, which says what it says without one. */
322
+ const BUILTIN_ANNOTATION_TARGET = { limit: 100, matchAny: false, tags: [], type: "dashboard" };
323
+
324
+ /**
325
+ * True for the annotation Grafana adds to every dashboard (annotations and
326
+ * alerts from its own database), in any of the forms Grafana versions have
327
+ * written it: a dashboard with it and one without are the same dashboard.
328
+ */
329
+ export function isBuiltinAnnotation(a: unknown): boolean {
330
+ if (!isObject(a) || a.builtIn !== 1 || !isGrafanaDatasource(a.datasource)) return false;
331
+ const { datasource: _ds, target, ...rest } = a;
332
+ if (target !== undefined && !deepEqual(target, BUILTIN_ANNOTATION_TARGET)) return false;
333
+ const { datasource: _builtinDs, ...expected } = BUILTIN_ANNOTATION;
334
+ return deepEqual(rest, expected);
335
+ }
336
+
337
+ function normalizeAnnotations(annotations: unknown): unknown {
338
+ const list = isObject(annotations) && Array.isArray(annotations.list) ? annotations.list : [];
339
+ const rest = list.filter((a) => !isBuiltinAnnotation(a));
340
+ return rest.length === 0 ? undefined : { list: rest };
341
+ }
342
+
343
+ /**
344
+ * The dashboard in canonical form: bookkeeping keys and keys at their
345
+ * default removed, derived keys removed, every query with its datasource and
346
+ * refId, every panel with the datasource its queries share. Returns a copy;
347
+ * key order is not canonicalised (compare with `deepEqual` or `toEqual`).
348
+ */
349
+ export function normalizeDashboard(dashboard: Json): Json {
350
+ const out: Json = { ...dashboard };
351
+ for (const key of BOOKKEEPING_KEYS) delete out[key];
352
+ for (const key of Object.keys(out)) if (out[key] === null) delete out[key];
353
+ const annotations = normalizeAnnotations(out.annotations);
354
+ if (annotations === undefined) delete out.annotations;
355
+ else out.annotations = annotations;
356
+ if (out.refresh === false) delete out.refresh;
357
+ if (Array.isArray(out.links)) out.links = out.links.map(normalizeLink);
358
+ if (Array.isArray(out.panels)) out.panels = out.panels.map(normalizePanel);
359
+ const list = isObject(out.templating) && Array.isArray(out.templating.list) ? out.templating.list : [];
360
+ if (list.length > 0) out.templating = { list: list.map(normalizeVariable) };
361
+ else delete out.templating;
362
+ dropDefaults(out, DASHBOARD_DEFAULTS);
363
+ return out;
364
+ }