@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
@@ -1,23 +1,53 @@
1
1
  /**
2
- * The checks behind GRAF101-GRAF107, as plain functions over built Grafana
3
- * output: dashboard JSON documents and provisioned datasources. The
2
+ * The checks behind GRAF101-GRAF109, GRAF111-GRAF114 and GRAF115, as plain
3
+ * functions over built Grafana output: dashboard JSON documents, provisioned
4
+ * datasources, dashboard providers and alerting provisioning files (the
5
+ * alerting checks are in `validate-alerting.ts`). The
4
6
  * post-synth checks run them over a build; anything else holding the same
5
7
  * JSON (a test, another lexicon embedding dashboards) can call them directly.
6
8
  *
7
9
  * Checks that join a dashboard against datasources (GRAF101, GRAF102) only
8
- * see the datasources passed in. In a build that is the build root being
9
- * built (chant #1939): with no datasource declared in it they report
10
- * nothing, and a datasource declared in another build root looks undeclared.
10
+ * see the datasources passed in: the ones provisioned and the ones declared
11
+ * with `ExternalDatasource`. In a build that is the build root being built
12
+ * (chant #1939). With none declared they warn that references cannot be
13
+ * checked; a datasource that exists in Grafana but is not declared looks
14
+ * undeclared, so declare it with `ExternalDatasource`.
11
15
  */
12
16
 
13
- import type { ProvisionedDatasource } from "./build";
14
- import { GRID_COLUMNS } from "./build";
15
- import { BUILTIN_DATASOURCE_UIDS } from "./datasource";
17
+ import type { ExternalDatasourceRecord, ProvisionedDatasource } from "./build";
18
+ import { DASHBOARDS_DIR, GRID_COLUMNS } from "./build";
19
+ import {
20
+ datasourceUses,
21
+ describePanel,
22
+ knownDatasources,
23
+ panelsOf,
24
+ variableReferences,
25
+ variablesOf,
26
+ type DatasourceRefJson,
27
+ type KnownDatasource,
28
+ } from "./datasource-refs";
16
29
  import { isBuiltinVariable } from "./variables";
17
30
  import { isValidUid } from "./util";
18
31
  import { validateDashboardSchema } from "./schema-validate";
19
-
20
- export type GrafanaIssueCode = "GRAF101" | "GRAF102" | "GRAF103" | "GRAF104" | "GRAF105" | "GRAF106" | "GRAF107";
32
+ import { checkGrafanaPromql, prometheusQueries } from "./promql-check";
33
+ import { checkAlertingIdentity, checkNotificationRefs, checkRuleDatasources, checkRulePromql, checkRuleQueries, type AlertingDoc } from "./validate-alerting";
34
+ import { closestGrafanaUnit, isGrafanaUnit } from "./spec/units";
35
+
36
+ export type GrafanaIssueCode =
37
+ | "GRAF101"
38
+ | "GRAF102"
39
+ | "GRAF103"
40
+ | "GRAF104"
41
+ | "GRAF105"
42
+ | "GRAF106"
43
+ | "GRAF107"
44
+ | "GRAF108"
45
+ | "GRAF109"
46
+ | "GRAF111"
47
+ | "GRAF112"
48
+ | "GRAF113"
49
+ | "GRAF114"
50
+ | "GRAF115";
21
51
 
22
52
  export interface GrafanaIssue {
23
53
  code: GrafanaIssueCode;
@@ -35,57 +65,26 @@ export interface DashboardDoc {
35
65
 
36
66
  export interface GrafanaArtifacts {
37
67
  dashboards: DashboardDoc[];
68
+ /** Datasources the build provisions. */
38
69
  datasources: ProvisionedDatasource[];
70
+ /** Datasources the build references but does not provision (`ExternalDatasource`). */
71
+ externalDatasources?: ExternalDatasourceRecord[];
72
+ /** Entries of `providers:` in dashboard provisioning files, as written: any field may be missing. */
73
+ providers?: Array<Record<string, unknown>>;
74
+ /** Alerting provisioning files (rule groups, contact points, policies, mute timings, templates). */
75
+ alerting?: AlertingDoc[];
39
76
  }
40
77
 
41
- type Json = Record<string, unknown>;
42
- interface Ref {
43
- type?: string;
44
- uid?: string;
45
- }
78
+ export type { AlertingDoc };
46
79
 
47
- interface PanelInfo {
48
- panel: Json;
49
- /** Panels inside a collapsed row are laid out in their own group. */
50
- group: string;
51
- }
52
-
53
- function panelsOf(dashboard: Json): PanelInfo[] {
54
- const out: PanelInfo[] = [];
55
- const top = Array.isArray(dashboard.panels) ? (dashboard.panels as Json[]) : [];
56
- for (const p of top) {
57
- if (!p || typeof p !== "object") continue;
58
- out.push({ panel: p, group: "" });
59
- if (p.type === "row" && Array.isArray(p.panels)) {
60
- for (const c of p.panels as Json[]) if (c && typeof c === "object") out.push({ panel: c, group: `row ${String(p.id)}` });
61
- }
62
- }
63
- return out;
64
- }
80
+ export { variableReferences };
65
81
 
66
- function describePanel(panel: Json): string {
67
- const title = typeof panel.title === "string" && panel.title ? `"${panel.title}"` : "(untitled)";
68
- return `panel ${title} (id ${String(panel.id ?? "?")})`;
69
- }
82
+ type Json = Record<string, unknown>;
70
83
 
71
84
  function dashName(d: Json): string {
72
85
  return `Dashboard "${String(d.title ?? d.uid ?? "?")}"`;
73
86
  }
74
87
 
75
- function variablesOf(d: Json): Json[] {
76
- const list = (d.templating as { list?: unknown } | undefined)?.list;
77
- return Array.isArray(list) ? (list as Json[]).filter((v) => v && typeof v === "object") : [];
78
- }
79
-
80
- const VAR_REF = /\$\{([A-Za-z_][A-Za-z0-9_]*)(?::[^}]*)?\}|\[\[([A-Za-z_][A-Za-z0-9_]*)(?::[^\]]*)?\]\]|\$([A-Za-z_][A-Za-z0-9_]*)/g;
81
-
82
- /** Every variable name referenced in a string. */
83
- export function variableReferences(text: string): string[] {
84
- const out: string[] = [];
85
- for (const m of text.matchAll(VAR_REF)) out.push(m[1] ?? m[2] ?? m[3]);
86
- return out;
87
- }
88
-
89
88
  function isVariableUid(uid: string | undefined): boolean {
90
89
  return typeof uid === "string" && uid.includes("$");
91
90
  }
@@ -101,79 +100,95 @@ function stringsIn(value: unknown, skip: ReadonlySet<string>, out: string[] = []
101
100
 
102
101
  const TARGET_SKIP = new Set(["refId", "datasource"]);
103
102
 
104
- /** Every datasource ref a panel makes, with where it is made. */
105
- function panelRefs(panel: Json): Array<{ ref: Ref; where: string }> {
106
- const out: Array<{ ref: Ref; where: string }> = [];
107
- const panelRef = panel.datasource as Ref | undefined;
103
+ /** Every datasource ref a panel or row writes itself, with where it is written. */
104
+ function panelRefs(panel: Json): Array<{ ref: DatasourceRefJson; where: string }> {
105
+ const out: Array<{ ref: DatasourceRefJson; where: string }> = [];
106
+ const panelRef = panel.datasource as DatasourceRefJson | undefined;
108
107
  if (panelRef && typeof panelRef === "object") out.push({ ref: panelRef, where: describePanel(panel) });
109
108
  const targets = Array.isArray(panel.targets) ? (panel.targets as Json[]) : [];
110
109
  for (const t of targets) {
111
- const r = t?.datasource as Ref | undefined;
110
+ const r = t?.datasource as DatasourceRefJson | undefined;
112
111
  if (r && typeof r === "object") out.push({ ref: r, where: `${describePanel(panel)} query ${String(t.refId ?? "?")}` });
113
112
  }
114
113
  return out;
115
114
  }
116
115
 
117
- function isPseudoRef(ref: Ref): boolean {
118
- return ref.type === "datasource" || ref.type === "grafana" || (ref.uid !== undefined && BUILTIN_DATASOURCE_UIDS.has(ref.uid));
116
+ /** The datasources a set of artifacts knows by uid: provisioned and external. */
117
+ export function knownDatasourcesOf(a: GrafanaArtifacts): Map<string, KnownDatasource> {
118
+ return knownDatasources(a.datasources, a.externalDatasources ?? []);
119
+ }
120
+
121
+ function listUids(known: ReadonlyMap<string, KnownDatasource>): string {
122
+ return [...known.keys()].map((k) => `"${k}"`).join(", ");
119
123
  }
120
124
 
121
125
  // ── GRAF101, GRAF102: datasource references ─────────────────────
122
126
 
123
127
  export function checkDatasourceRefs(a: GrafanaArtifacts): GrafanaIssue[] {
124
128
  const issues: GrafanaIssue[] = [];
125
- if (a.datasources.length === 0) return issues; // declared elsewhere, or not at all: see #1939
126
- const byUid = new Map(a.datasources.map((d) => [d.uid, d]));
129
+ const known = knownDatasourcesOf(a);
130
+ const types = new Set([...known.values()].map((k) => k.type));
127
131
  for (const { json: d } of a.dashboards) {
128
132
  const uid = String(d.uid ?? "");
129
- const vars = new Map(variablesOf(d).map((v) => [String(v.name), v]));
130
- const refs: Array<{ ref: Ref; where: string }> = [];
133
+ const push = (code: GrafanaIssueCode, severity: GrafanaIssue["severity"], message: string) =>
134
+ issues.push({ code, severity, message: `${dashName(d)} ${message}`, entity: uid });
135
+
131
136
  for (const { panel } of panelsOf(d)) {
132
137
  if (panel.type === "row") continue;
133
- refs.push(...panelRefs(panel));
134
138
  const targets = Array.isArray(panel.targets) ? (panel.targets as Json[]) : [];
135
139
  if (targets.length > 0 && !panel.datasource && targets.every((t) => !t?.datasource)) {
136
- issues.push({
137
- code: "GRAF101",
138
- severity: "warning",
139
- message: `${dashName(d)} ${describePanel(panel)} has queries but no datasource; Grafana will send them to whichever datasource is its default.`,
140
- entity: uid,
141
- });
140
+ push("GRAF101", "warning", `${describePanel(panel)} has queries but no datasource; Grafana will send them to whichever datasource is its default.`);
142
141
  }
143
142
  }
144
- for (const v of vars.values()) {
145
- if (v.type === "query" && v.datasource && typeof v.datasource === "object") refs.push({ ref: v.datasource as Ref, where: `variable "${String(v.name)}"` });
143
+
144
+ const uses = datasourceUses(d, known).filter((u) => u.ref !== undefined);
145
+ const dsVariables = variablesOf(d).filter((v) => v.type === "datasource" && typeof v.query === "string" && v.query);
146
+
147
+ // Nothing declared: the references can't be checked. Say so once per dashboard rather than staying silent.
148
+ if (known.size === 0) {
149
+ const uids = [...new Set(uses.flatMap((u) => (u.resolved.kind === "undeclared" ? [u.resolved.uid] : [])))];
150
+ const plugins = [...new Set(dsVariables.map((v) => String(v.query)))];
151
+ const what = [
152
+ ...(uids.length > 0 ? [`datasource uid${uids.length > 1 ? "s" : ""} ${uids.map((u) => `"${u}"`).join(", ")}`] : []),
153
+ ...(plugins.length > 0 ? [`datasource variables of type ${plugins.join(", ")}`] : []),
154
+ ];
155
+ if (what.length > 0) {
156
+ const them = uids.length + plugins.length > 1 ? "them" : "it";
157
+ push(
158
+ "GRAF101",
159
+ "warning",
160
+ `uses ${what.join(" and ")}, but the build declares no datasource, so GRAF101 and GRAF102 cannot check ${them}. Declare a Datasource to provision one, or an ExternalDatasource for one that already exists in Grafana.`,
161
+ );
162
+ }
146
163
  }
147
- for (const { ref, where } of refs) {
148
- if (isPseudoRef(ref) || ref.uid === undefined) continue;
149
- if (isVariableUid(ref.uid)) {
150
- const [name] = variableReferences(ref.uid);
151
- const v = name ? vars.get(name) : undefined;
152
- if (v && v.type === "datasource" && ref.type && v.query && v.query !== ref.type) {
153
- issues.push({
154
- code: "GRAF102",
155
- severity: "error",
156
- message: `${dashName(d)} ${where} expects a ${ref.type} datasource, but variable "${name}" chooses among ${String(v.query)} datasources.`,
157
- entity: uid,
158
- });
164
+
165
+ for (const { ref, where, resolved } of uses) {
166
+ if (resolved.kind === "variable") {
167
+ const v = variablesOf(d).find((x) => x.name === resolved.variable);
168
+ if (v && v.type === "datasource" && ref!.type && v.query && v.query !== ref!.type) {
169
+ push("GRAF102", "error", `${where} expects a ${ref!.type} datasource, but variable "${resolved.variable}" chooses among ${String(v.query)} datasources.`);
159
170
  }
160
- continue;
171
+ } else if (resolved.kind === "undeclared" && known.size > 0) {
172
+ push(
173
+ "GRAF101",
174
+ "error",
175
+ `${where} uses datasource uid "${resolved.uid}"${ref!.type ? ` (${ref!.type})` : ""}, which no declared Datasource or ExternalDatasource has. Declared: ${listUids(known)}.`,
176
+ );
177
+ } else if (resolved.kind === "declared" && ref!.type && ref!.type !== resolved.type) {
178
+ const ds = resolved.datasource;
179
+ push("GRAF102", "error", `${where} sends a ${ref!.type} query to ${ds.external ? "external " : ""}datasource "${ds.name ?? ds.uid}", which is ${ds.type}.`);
161
180
  }
162
- const declared = byUid.get(ref.uid);
163
- if (!declared) {
164
- issues.push({
165
- code: "GRAF101",
166
- severity: "error",
167
- message: `${dashName(d)} ${where} uses datasource uid "${ref.uid}"${ref.type ? ` (${ref.type})` : ""}, which no declared Datasource has. Declared: ${[...byUid.keys()].map((k) => `"${k}"`).join(", ")}.`,
168
- entity: uid,
169
- });
170
- } else if (ref.type && ref.type !== declared.type) {
171
- issues.push({
172
- code: "GRAF102",
173
- severity: "error",
174
- message: `${dashName(d)} ${where} sends a ${ref.type} query to datasource "${declared.name}", which is ${declared.type}.`,
175
- entity: uid,
176
- });
181
+ }
182
+
183
+ // A datasource variable offers the datasources of its plugin type; with none declared it has nothing to choose.
184
+ if (known.size > 0) {
185
+ for (const v of dsVariables) {
186
+ if (types.has(String(v.query))) continue;
187
+ push(
188
+ "GRAF101",
189
+ "error",
190
+ `variable "${String(v.name)}" chooses among ${String(v.query)} datasources, but no declared Datasource or ExternalDatasource is of type ${String(v.query)}. Declared types: ${[...types].join(", ")}.`,
191
+ );
177
192
  }
178
193
  }
179
194
  }
@@ -235,7 +250,8 @@ export function checkDuplicates(a: GrafanaArtifacts): GrafanaIssue[] {
235
250
  for (const [uid, n] of dupes(a.dashboards, (d) => (typeof d.json.uid === "string" ? d.json.uid : undefined))) {
236
251
  push(`${n} dashboards share the uid "${uid}"; Grafana keeps one of them.`, uid);
237
252
  }
238
- for (const [uid, n] of dupes(a.datasources, (d) => d.uid)) push(`${n} datasources share the uid "${uid}".`, uid);
253
+ const allUids = [...a.datasources.map((d) => d.uid), ...(a.externalDatasources ?? []).map((d) => d.uid)];
254
+ for (const [uid, n] of dupes(allUids, (u) => u)) push(`${n} datasources share the uid "${uid}".`, uid);
239
255
  for (const [name, n] of dupes(a.datasources, (d) => d.name)) push(`${n} datasources share the name "${name}"; Grafana needs names to be unique.`, name);
240
256
  for (const { json: d } of a.dashboards) {
241
257
  const uid = String(d.uid ?? "");
@@ -339,18 +355,33 @@ export function checkIdentity(a: GrafanaArtifacts): GrafanaIssue[] {
339
355
  });
340
356
  }
341
357
  }
358
+ for (const ds of a.externalDatasources ?? []) {
359
+ if (!isValidUid(ds.uid)) {
360
+ issues.push({
361
+ code: "GRAF106",
362
+ severity: "error",
363
+ message: `ExternalDatasource "${ds.name ?? ds.uid}" has uid "${ds.uid}"; Grafana accepts 1-40 letters, digits, "-" and "_".`,
364
+ entity: ds.name ?? ds.uid,
365
+ });
366
+ }
367
+ }
342
368
  return issues;
343
369
  }
344
370
 
345
371
  // ── GRAF107: the pinned schemas ─────────────────────────────────
346
372
 
373
+ /**
374
+ * A value the pinned schemas (with the correction overlay) do not allow is
375
+ * an error; a key they do not know is a warning, since newer Grafana
376
+ * versions add keys the pin has not caught up with.
377
+ */
347
378
  export function checkSchema(a: GrafanaArtifacts): GrafanaIssue[] {
348
379
  const issues: GrafanaIssue[] = [];
349
380
  for (const { json: d } of a.dashboards) {
350
381
  for (const p of validateDashboardSchema(d)) {
351
382
  issues.push({
352
383
  code: "GRAF107",
353
- severity: "error",
384
+ severity: p.severity,
354
385
  message: `${dashName(d)} ${p.path}: ${p.message} (Grafana schema).`,
355
386
  entity: String(d.uid ?? ""),
356
387
  });
@@ -359,6 +390,199 @@ export function checkSchema(a: GrafanaArtifacts): GrafanaIssue[] {
359
390
  return issues;
360
391
  }
361
392
 
393
+ // ── GRAF108: PromQL syntax ──────────────────────────────────────
394
+
395
+ /**
396
+ * Each panel query, query variable and alert rule query that reaches a
397
+ * Prometheus datasource is parsed as PromQL, with its template variables replaced by placeholders
398
+ * first (see `promql-check.ts`). A query whose datasource can't be told is
399
+ * not parsed.
400
+ */
401
+ export function checkPromqlSyntax(a: GrafanaArtifacts): GrafanaIssue[] {
402
+ const issues: GrafanaIssue[] = [];
403
+ const known = knownDatasourcesOf(a);
404
+ for (const { json: d } of a.dashboards) {
405
+ for (const { where, expr } of prometheusQueries(d, known)) {
406
+ const checked = checkGrafanaPromql(expr);
407
+ if (checked.ok) continue;
408
+ issues.push({ code: "GRAF108", severity: "error", message: `${dashName(d)} ${where} is not valid PromQL: ${checked.message}.`, entity: String(d.uid ?? "") });
409
+ }
410
+ }
411
+ issues.push(...checkRulePromql(a.alerting ?? [], known));
412
+ return issues;
413
+ }
414
+
415
+ // ── GRAF115: units ──────────────────────────────────────────────
416
+
417
+ /** Every unit a panel sets, with where: field defaults, `unit` overrides, heatmap axes and cells, and a legacy graph panel's y-axes. */
418
+ export function panelUnits(panel: Json): Array<{ where: string; unit: string }> {
419
+ const out: Array<{ where: string; unit: string }> = [];
420
+ const add = (where: string, unit: unknown) => {
421
+ if (typeof unit === "string") out.push({ where, unit });
422
+ };
423
+ const fieldConfig = panel.fieldConfig as Json | undefined;
424
+ add("fieldConfig.defaults.unit", (fieldConfig?.defaults as Json | undefined)?.unit);
425
+ const overrides = Array.isArray(fieldConfig?.overrides) ? (fieldConfig.overrides as Json[]) : [];
426
+ overrides.forEach((o, i) => {
427
+ const props = Array.isArray(o?.properties) ? (o.properties as Json[]) : [];
428
+ props.forEach((p, j) => {
429
+ if (p?.id === "unit") add(`fieldConfig.overrides[${i}].properties[${j}]`, p.value);
430
+ });
431
+ });
432
+ const options = panel.options as Json | undefined;
433
+ add("options.yAxis.unit", (options?.yAxis as Json | undefined)?.unit);
434
+ add("options.cellValues.unit", (options?.cellValues as Json | undefined)?.unit);
435
+ if (Array.isArray(panel.yaxes)) (panel.yaxes as Json[]).forEach((y, i) => add(`yaxes[${i}].format`, y?.format));
436
+ return out;
437
+ }
438
+
439
+ /**
440
+ * A unit Grafana doesn't register (at v13.2.2) and that isn't a custom
441
+ * `<kind>:` unit is shown as a literal suffix, so `"byte"` renders `5 byte`.
442
+ * A warning: it renders, just not as meant.
443
+ */
444
+ export function checkUnits(a: GrafanaArtifacts): GrafanaIssue[] {
445
+ const issues: GrafanaIssue[] = [];
446
+ for (const { json: d } of a.dashboards) {
447
+ const panels: Array<{ panel: Json; label: string }> = panelsOf(d).map(({ panel }) => ({ panel, label: describePanel(panel) }));
448
+ const elements = d.__elements;
449
+ if (elements && typeof elements === "object" && !Array.isArray(elements)) {
450
+ for (const [key, element] of Object.entries(elements as Json)) {
451
+ const model = element && typeof element === "object" ? (element as Json).model : undefined;
452
+ if (model && typeof model === "object" && !Array.isArray(model)) panels.push({ panel: model as Json, label: `library panel "${key}"` });
453
+ }
454
+ }
455
+ for (const { panel, label } of panels) {
456
+ if (panel.type === "row") continue;
457
+ for (const { where, unit } of panelUnits(panel)) {
458
+ if (isGrafanaUnit(unit)) continue;
459
+ const guess = closestGrafanaUnit(unit);
460
+ const hint = guess ? `Did you mean "${guess}"? ` : "";
461
+ issues.push({
462
+ code: "GRAF115",
463
+ severity: "warning",
464
+ message: `${dashName(d)} ${label} ${where}: unit "${unit}" is not one Grafana knows, so it is shown as a literal suffix. ${hint}For a custom unit write "suffix:${unit}" (or prefix:, si:, count:, currency:, time:).`,
465
+ entity: String(d.uid ?? ""),
466
+ });
467
+ }
468
+ }
469
+ }
470
+ return issues;
471
+ }
472
+
473
+ // ── GRAF109: where dashboard provisioning puts dashboards ───────
474
+
475
+ /** Grafana's default `[folder] max_nested_folder_depth`, and the most it can be raised to. */
476
+ const DEFAULT_FOLDER_DEPTH = 4;
477
+ const MAX_FOLDER_DEPTH = 7;
478
+
479
+ /** The folder levels a dashboard file sits in under `dashboards/`, e.g. `["Platform", "Kubernetes"]`. */
480
+ function folderLevels(source: string | undefined): string[] {
481
+ if (!source?.startsWith(`${DASHBOARDS_DIR}/`)) return [];
482
+ return source.slice(DASHBOARDS_DIR.length + 1).split("/").slice(0, -1);
483
+ }
484
+
485
+ interface ProviderView {
486
+ name: string;
487
+ orgId: number;
488
+ path: string;
489
+ folder: string;
490
+ folderUid: string;
491
+ fromFiles: boolean;
492
+ }
493
+
494
+ function providerView(p: Record<string, unknown>): ProviderView {
495
+ const options = (p.options && typeof p.options === "object" ? p.options : {}) as Json;
496
+ const path = typeof options.path === "string" ? options.path : typeof options.folder === "string" ? options.folder : "";
497
+ return {
498
+ name: typeof p.name === "string" ? p.name : "?",
499
+ orgId: typeof p.orgId === "number" && p.orgId !== 0 ? p.orgId : 1,
500
+ path: path.replace(/\/+$/, "") || "/",
501
+ folder: typeof p.folder === "string" ? p.folder : "",
502
+ folderUid: typeof p.folderUid === "string" ? p.folderUid : "",
503
+ fromFiles: options.foldersFromFilesStructure === true,
504
+ };
505
+ }
506
+
507
+ /** Whether a provider reading `a` also reads `b`: Grafana walks a provider's path recursively. */
508
+ function contains(a: string, b: string): boolean {
509
+ return a === b || a === "/" || b.startsWith(`${a}/`);
510
+ }
511
+
512
+ function listDashboards(ds: Array<{ d: Json; levels: string[] }>): string {
513
+ const shown = ds.slice(0, 3).map(({ d, levels }) => `"${String(d.title ?? d.uid ?? "?")}" (${levels.join("/")})`);
514
+ return ds.length > 3 ? `${shown.join(", ")} and ${ds.length - 3} more` : shown.join(", ");
515
+ }
516
+
517
+ export function checkProvisioning(a: GrafanaArtifacts): GrafanaIssue[] {
518
+ const issues: GrafanaIssue[] = [];
519
+ const push = (severity: GrafanaIssue["severity"], message: string, entity?: string) => issues.push({ code: "GRAF109", severity, message, entity });
520
+ const providers = (a.providers ?? []).map(providerView);
521
+ const foldered = a.dashboards.map(({ source, json }) => ({ d: json, levels: folderLevels(source) })).filter((x) => x.levels.length > 0);
522
+
523
+ // Two providers reading the same files, in the same org.
524
+ for (let i = 0; i < providers.length; i++) {
525
+ for (let j = i + 1; j < providers.length; j++) {
526
+ const [p, q] = [providers[i], providers[j]];
527
+ if (p.orgId !== q.orgId) continue;
528
+ const shared = contains(p.path, q.path) ? q.path : contains(q.path, p.path) ? p.path : undefined;
529
+ if (shared === undefined) continue;
530
+ push(
531
+ "error",
532
+ `Dashboard providers "${p.name}" and "${q.name}" both load the dashboards under ${shared}. Grafana provisions each of them twice, then takes database writes away from both providers, so later changes to the files never reach Grafana. Give each provider its own path.`,
533
+ q.name,
534
+ );
535
+ }
536
+ }
537
+
538
+ // A provider mapping directories to folders that also names a folder.
539
+ for (const p of providers) {
540
+ if (!p.fromFiles || (p.folder === "" && p.folderUid === "")) continue;
541
+ if (p.folder !== "" && p.folderUid !== "") {
542
+ push("error", `Dashboard provider "${p.name}" sets folder, folderUid and foldersFromFilesStructure; Grafana refuses to start it. Drop folder and folderUid, or foldersFromFilesStructure.`, p.name);
543
+ } else {
544
+ const which = p.folder !== "" ? `folder "${p.folder}"` : `folderUid "${p.folderUid}"`;
545
+ push(
546
+ "warning",
547
+ `Dashboard provider "${p.name}" sets ${which} and foldersFromFilesStructure. Grafana files each dashboard by its directory and the top-level ones in General, so ${which} is not used${p.folderUid !== "" ? " (before Grafana 13.1 every directory resolves to that one folder uid instead)" : ""}. Drop one of the two.`,
548
+ p.name,
549
+ );
550
+ }
551
+ }
552
+
553
+ // Dashboards with a folder that no provider maps from directories.
554
+ if (foldered.length > 0 && providers.length > 0 && !providers.some((p) => p.fromFiles)) {
555
+ const where = providers.map((p) => `"${p.name}" puts every dashboard in ${p.folder !== "" ? `folder "${p.folder}"` : p.folderUid !== "" ? `folder uid "${p.folderUid}"` : "General"}`).join("; ");
556
+ push(
557
+ "warning",
558
+ `${foldered.length === 1 ? "A dashboard declares a folder" : `${foldered.length} dashboards declare a folder`}, ${listDashboards(foldered)}, but no dashboard provider sets foldersFromFilesStructure: ${where}. Set foldersFromFilesStructure: true on the provider (and drop its folder), or leave the dashboards' folder out.`,
559
+ );
560
+ }
561
+
562
+ // Nested folders, where a provider does map directories to folders.
563
+ if (providers.some((p) => p.fromFiles)) {
564
+ for (const { d, levels } of foldered) {
565
+ if (levels.length < 2) continue;
566
+ const uid = String(d.uid ?? "");
567
+ const path = levels.join("/");
568
+ if (levels.length > MAX_FOLDER_DEPTH) {
569
+ push("error", `${dashName(d)} is in folder "${path}", ${levels.length} levels deep; Grafana nests at most ${MAX_FOLDER_DEPTH}, and the provider stops with an error when it reaches it.`, uid);
570
+ } else {
571
+ const depth =
572
+ levels.length > DEFAULT_FOLDER_DEPTH
573
+ ? ` It is ${levels.length} levels deep, past Grafana's default max_nested_folder_depth of ${DEFAULT_FOLDER_DEPTH}: raise it in [folder], or the provider stops with an error when it reaches it.`
574
+ : "";
575
+ push(
576
+ "warning",
577
+ `${dashName(d)} is in nested folder "${path}". Grafana 13.1 and later create the folders inside one another; Grafana 12.4 and 13.0 use only the last level and put it in a top-level folder "${levels[levels.length - 1]}".${depth}`,
578
+ uid,
579
+ );
580
+ }
581
+ }
582
+ }
583
+ return issues;
584
+ }
585
+
362
586
  const BY_CODE: Record<GrafanaIssueCode, (a: GrafanaArtifacts) => GrafanaIssue[]> = {
363
587
  GRAF101: (a) => checkDatasourceRefs(a).filter((i) => i.code === "GRAF101"),
364
588
  GRAF102: (a) => checkDatasourceRefs(a).filter((i) => i.code === "GRAF102"),
@@ -367,6 +591,13 @@ const BY_CODE: Record<GrafanaIssueCode, (a: GrafanaArtifacts) => GrafanaIssue[]>
367
591
  GRAF105: checkGrid,
368
592
  GRAF106: checkIdentity,
369
593
  GRAF107: checkSchema,
594
+ GRAF108: checkPromqlSyntax,
595
+ GRAF109: checkProvisioning,
596
+ GRAF111: (a) => checkRuleQueries(a.alerting ?? []),
597
+ GRAF112: (a) => checkRuleDatasources(a.alerting ?? [], knownDatasourcesOf(a)),
598
+ GRAF113: (a) => checkNotificationRefs(a.alerting ?? []),
599
+ GRAF114: (a) => checkAlertingIdentity(a.alerting ?? []),
600
+ GRAF115: checkUnits,
370
601
  };
371
602
 
372
603
  /** The issues one check finds. */
package/src/validate.ts CHANGED
@@ -23,6 +23,7 @@ export type { ValidateCheck, ValidateResult } from "@intentius/chant/codegen/val
23
23
  export const REQUIRED_NAMES = [
24
24
  "Dashboard",
25
25
  "Datasource",
26
+ "ExternalDatasource",
26
27
  "DashboardProvider",
27
28
  "Row",
28
29
  "TimeSeriesPanel",
@@ -33,6 +34,19 @@ export const REQUIRED_NAMES = [
33
34
  "TracesPanel",
34
35
  "HeatmapPanel",
35
36
  "TextPanel",
37
+ "BarChartPanel",
38
+ "BarGaugePanel",
39
+ "PieChartPanel",
40
+ "StateTimelinePanel",
41
+ "StatusHistoryPanel",
42
+ "HistogramPanel",
43
+ "NodeGraphPanel",
44
+ "XYChartPanel",
45
+ "TrendPanel",
46
+ "CanvasPanel",
47
+ "GeomapPanel",
48
+ "FlameGraphPanel",
49
+ "AlertListPanel",
36
50
  "PromQuery",
37
51
  "TempoQuery",
38
52
  "LokiQuery",
package/src/variables.ts CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
  import { createProperty } from "@intentius/chant/runtime";
13
13
  import type { Declarable } from "@intentius/chant/declarable";
14
- import type { DatasourceEntity, DatasourceRef } from "./datasource";
14
+ import type { DatasourceEntity, DatasourceRef, ExternalDatasourceEntity } from "./datasource";
15
15
  import type { VariableOption } from "./schema/dashboard.gen";
16
16
 
17
17
  export type VariableKind = "query" | "custom" | "interval" | "datasource" | "constant" | "textbox";
@@ -40,7 +40,7 @@ interface MultiValueProps {
40
40
 
41
41
  /** A variable anything can hold before its datasource is known: its own datasource ref or a datasource variable. */
42
42
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
43
- export type VariableDatasource = DatasourceEntity<any> | DatasourceRef | DatasourceVariableEntity<any>;
43
+ export type VariableDatasource = DatasourceEntity<any> | ExternalDatasourceEntity<any> | DatasourceRef | DatasourceVariableEntity<any>;
44
44
 
45
45
  export interface QueryVariableProps extends CommonVariableProps, MultiValueProps {
46
46
  datasource: VariableDatasource;
@@ -54,6 +54,11 @@ export interface QueryVariableProps extends CommonVariableProps, MultiValueProps
54
54
  }
55
55
 
56
56
  export interface CustomVariableProps extends CommonVariableProps, MultiValueProps {
57
+ /**
58
+ * The values, in Grafana's syntax: `"Production : prod"` shows
59
+ * `Production` and sets `prod`. A comma is part of the value (written as
60
+ * `\,`, and one already escaped is kept).
61
+ */
57
62
  values: string[];
58
63
  }
59
64