@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
package/src/build.ts CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  import { dump } from "js-yaml";
12
12
  import type { Declarable } from "@intentius/chant/declarable";
13
- import { isDatasourceEntity, type DatasourceEntity, type DatasourceRef } from "./datasource";
13
+ import { isDatasourceDeclaration, isDatasourceEntity, isExternalDatasource, type DatasourceEntity, type DatasourceRef, type ExternalDatasourceEntity } from "./datasource";
14
14
  import {
15
15
  isDashboardEntity,
16
16
  isDashboardProviderEntity,
@@ -34,6 +34,8 @@ import type {
34
34
  import { DASHBOARD_SCHEMA_VERSION } from "./schema/dashboard.gen";
35
35
  import { GRAFANA_SCHEMA_PIN } from "./pin";
36
36
  import { compact, slugUid } from "./util";
37
+ import { ALERTING_FILE, alertingYaml, buildAlerting, type AlertingFile, type AlertingIndex } from "./alerting-build";
38
+ import { DEFAULT_PROVIDER_NAME } from "./ownership";
37
39
 
38
40
  export type { DashboardJson, PanelJson, RowPanelJson, VariableModel, DataSourceRef };
39
41
 
@@ -90,7 +92,11 @@ export interface BuiltDashboard {
90
92
  export interface BuiltGrafana {
91
93
  dashboards: BuiltDashboard[];
92
94
  datasources: ProvisionedDatasource[];
95
+ /** `ExternalDatasource` declarations: the checks count them, the provisioning file leaves them out. */
96
+ externalDatasources: ExternalDatasourceRecord[];
93
97
  providers: ProvisionedProvider[];
98
+ /** The alerting provisioning file, when the build declares any alerting. */
99
+ alerting?: AlertingFile;
94
100
  /** Every output file by path, ready to write. */
95
101
  files: Record<string, string>;
96
102
  /** A short summary of what was built: the serializer's primary output. */
@@ -101,9 +107,20 @@ export interface GrafanaIndex {
101
107
  grafanaSchema: string;
102
108
  dashboards: Array<{ uid: string; title: string; folder?: string; file: string }>;
103
109
  datasources: Array<{ name: string; type: string; uid: string }>;
110
+ /** Datasources declared with `ExternalDatasource`: referenced, never provisioned. */
111
+ externalDatasources?: ExternalDatasourceRecord[];
112
+ /** Rule groups, contact points, policies, mute timings and templates, when the build declares any. */
113
+ alerting?: AlertingIndex;
104
114
  files: string[];
105
115
  }
106
116
 
117
+ /** An `ExternalDatasource` as the checks and the index see it. */
118
+ export interface ExternalDatasourceRecord {
119
+ type: string;
120
+ uid: string;
121
+ name?: string;
122
+ }
123
+
107
124
  // ── References ──────────────────────────────────────────────────
108
125
 
109
126
  function isRef(value: unknown): value is DatasourceRef {
@@ -116,13 +133,13 @@ function isRef(value: unknown): value is DatasourceRef {
116
133
  );
117
134
  }
118
135
 
119
- /** The `{ type, uid }` Grafana stores for a datasource, a datasource variable, or a ref. */
136
+ /** The `{ type, uid }` Grafana stores for a datasource (declared or external), a datasource variable, or a ref. */
120
137
  export function datasourceRef(input: DatasourceInput | undefined): DataSourceRef | undefined {
121
138
  if (input === undefined || input === null) return undefined;
122
- if (isDatasourceEntity(input)) return { type: input.datasourceType, uid: input.uid };
139
+ if (isDatasourceDeclaration(input)) return { type: input.datasourceType, uid: input.uid };
123
140
  if (isDatasourceVariable(input)) return { type: input.pluginType, uid: `\${${input.variableName}}` };
124
141
  if (isRef(input)) return { type: input.type, uid: input.uid };
125
- throw new Error("grafana: a datasource must be a Datasource, a DatasourceVariable or a { type, uid } ref");
142
+ throw new Error("grafana: a datasource must be a Datasource, an ExternalDatasource, a DatasourceVariable or a { type, uid } ref");
126
143
  }
127
144
 
128
145
  function sameRef(a: DataSourceRef | undefined, b: DataSourceRef | undefined): boolean {
@@ -154,8 +171,24 @@ function dashboardLink(link: DashboardLinkInput | PanelLink): DashboardLink {
154
171
  const HIDE: Record<VariableHide, 0 | 1 | 2> = { label: 0, valueOnly: 1, hidden: 2 };
155
172
  const REFRESH = { never: 0, onLoad: 1, onTimeRangeChange: 2 } as const;
156
173
 
174
+ /** A custom variable value in its `query`: a comma is escaped as `\\,`, and one already escaped is left alone. */
157
175
  function escapeCustom(v: string): string {
158
- return v.replace(/,/g, "\\,");
176
+ return v.replace(/\\?,/g, "\\,");
177
+ }
178
+
179
+ /**
180
+ * The options Grafana makes of a custom variable's `query`: split on commas
181
+ * not escaped as `\\,`, and `text : value` (spaces around the colon) read as
182
+ * an option showing `text` that sets `value`. The same parse as Grafana's
183
+ * custom variable (public/app/features/variables/custom/reducer.ts, and
184
+ * CustomVariable in @grafana/scenes).
185
+ */
186
+ export function customVariableOptions(query: string): Array<{ text: string; value: string }> {
187
+ return (query.match(/(?:\\,|[^,])+/g) ?? []).map((item) => {
188
+ const text = item.replace(/\\,/g, ",");
189
+ const kv = /^\s*(.+)\s:\s(.+)$/.exec(text);
190
+ return kv ? { text: kv[1].trim(), value: kv[2].trim() } : { text: text.trim(), value: text.trim() };
191
+ });
159
192
  }
160
193
 
161
194
  function optionsOf(values: string[], current?: { text: string | string[]; value: string | string[] }) {
@@ -192,13 +225,16 @@ export function variableModel(variable: VariableEntity): VariableModel {
192
225
  }) as VariableModel;
193
226
  case "custom": {
194
227
  const values: string[] = p.values;
195
- const current = p.current ?? (values[0] !== undefined ? { text: values[0], value: values[0] } : undefined);
228
+ const query = values.map(escapeCustom).join(",");
229
+ const options = customVariableOptions(query);
230
+ const current = p.current ?? (options[0] !== undefined ? { text: options[0].text, value: options[0].value } : undefined);
231
+ const selected = current ? (Array.isArray(current.value) ? current.value : [current.value]) : [];
196
232
  return compact({
197
233
  ...common,
198
- query: values.map(escapeCustom).join(","),
234
+ query,
199
235
  ...multi,
200
236
  current,
201
- options: optionsOf(values, current),
237
+ options: options.map((o) => ({ selected: selected.includes(o.value), ...o })),
202
238
  }) as VariableModel;
203
239
  }
204
240
  case "interval": {
@@ -266,47 +302,110 @@ export function targetJson(query: QueryEntity, index: number, panelDatasource?:
266
302
  });
267
303
  }
268
304
 
305
+ /** A bitmask of grid columns [x, x + w), clipped to the grid. */
306
+ function columns(x: number, w: number): number {
307
+ const from = Math.max(0, x);
308
+ const to = Math.min(GRID_COLUMNS, x + w);
309
+ return to > from ? ((1 << (to - from)) - 1) << from : 0;
310
+ }
311
+
312
+ /**
313
+ * Places panels on Grafana's grid. It keeps an occupancy grid, so a panel
314
+ * placed automatically never lands on one placed explicitly (reserved up
315
+ * front) or on one placed before it.
316
+ */
269
317
  class Layout {
270
318
  private x = 0;
271
- private y = 0;
319
+ private y: number;
272
320
  private lineH = 0;
273
- private bottom = 0;
321
+ private bottom: number;
322
+ /** Taken cells: one column bitmask per grid line. */
323
+ private readonly taken: number[] = [];
324
+
325
+ constructor(top = 0) {
326
+ this.y = top;
327
+ this.bottom = top;
328
+ }
329
+
330
+ /** Marks the cells of a panel with both `x` and `y` as taken, before anything is placed. */
331
+ reserve(gp: Partial<GridPos> | undefined, size: { w: number; h: number }): void {
332
+ if (gp?.x === undefined || gp?.y === undefined) return;
333
+ this.take(gp.x, gp.y, gp.w ?? size.w, gp.h ?? size.h);
334
+ }
274
335
 
275
336
  place(gp: Partial<GridPos> | undefined, size: { w: number; h: number }): GridPos {
276
337
  const w = gp?.w ?? size.w;
277
338
  const h = gp?.h ?? size.h;
278
339
  if (gp?.x !== undefined && gp?.y !== undefined) {
340
+ this.take(gp.x, gp.y, w, h);
279
341
  this.bottom = Math.max(this.bottom, gp.y + h);
280
342
  return { h, w, x: gp.x, y: gp.y };
281
343
  }
282
- if (this.x + w > GRID_COLUMNS && this.x > 0) {
283
- this.y += this.lineH;
344
+ if (gp?.y !== undefined) {
345
+ // Only `y`: the first free column on that line, else on the first line below with room.
346
+ for (let y = Math.max(0, gp.y); ; y++) {
347
+ const x = this.freeColumn(0, y, w, h);
348
+ if (x === undefined) continue;
349
+ this.take(x, y, w, h);
350
+ this.bottom = Math.max(this.bottom, y + h);
351
+ return { h, w, x, y };
352
+ }
353
+ }
354
+ // Flow: the first free spot on the current line after the previous panel, else the next line.
355
+ // Only `x`: that column, on the first line where it is free.
356
+ for (;;) {
357
+ const x =
358
+ gp?.x === undefined
359
+ ? this.freeColumn(this.x, this.y, w, h)
360
+ : gp.x >= this.x && this.free(gp.x, this.y, w, h)
361
+ ? gp.x
362
+ : undefined;
363
+ if (x !== undefined) {
364
+ const pos = { h, w, x, y: this.y };
365
+ this.take(x, this.y, w, h);
366
+ this.x = x + w;
367
+ this.lineH = Math.max(this.lineH, h);
368
+ this.bottom = Math.max(this.bottom, this.y + h);
369
+ return pos;
370
+ }
371
+ this.y += this.lineH || 1;
284
372
  this.x = 0;
285
373
  this.lineH = 0;
286
374
  }
287
- const pos = { h, w, x: this.x, y: this.y };
288
- this.x += w;
289
- this.lineH = Math.max(this.lineH, h);
290
- this.bottom = Math.max(this.bottom, this.y + h);
291
- return pos;
292
375
  }
293
376
 
294
- /** A full-width row header below everything placed so far. */
295
- row(): { pos: GridPos; mark: () => void } {
296
- const y = Math.max(this.bottom, this.y + this.lineH);
297
- const pos = { h: 1, w: GRID_COLUMNS, x: 0, y };
377
+ /** Marks the line of a row header with an explicit `y` as taken, before anything is placed. */
378
+ reserveRow(y: number | undefined): void {
379
+ if (y !== undefined) this.take(0, y, GRID_COLUMNS, 1);
380
+ }
381
+
382
+ /** A full-width row header on line `explicitY`, else on the first free line below everything placed so far. */
383
+ row(explicitY?: number): GridPos {
384
+ let y = explicitY ?? Math.max(this.bottom, this.y + this.lineH);
385
+ if (explicitY === undefined) while (!this.free(0, y, GRID_COLUMNS, 1)) y++;
386
+ this.take(0, y, GRID_COLUMNS, 1);
298
387
  this.x = 0;
299
388
  this.y = y + 1;
300
389
  this.lineH = 0;
301
390
  this.bottom = y + 1;
302
- // Called after a collapsed row's panels: the next thing starts right under the header.
303
- const mark = () => {
304
- this.x = 0;
305
- this.y = y + 1;
306
- this.lineH = 0;
307
- this.bottom = y + 1;
308
- };
309
- return { pos, mark };
391
+ return { h: 1, w: GRID_COLUMNS, x: 0, y };
392
+ }
393
+
394
+ /** The first column at or after `from` where a w×h panel fits on line `y`. A panel wider than the grid fits only at 0. */
395
+ private freeColumn(from: number, y: number, w: number, h: number): number | undefined {
396
+ for (let x = from; x === 0 || x + w <= GRID_COLUMNS; x++) if (this.free(x, y, w, h)) return x;
397
+ return undefined;
398
+ }
399
+
400
+ private free(x: number, y: number, w: number, h: number): boolean {
401
+ const mask = columns(x, w);
402
+ for (let line = Math.max(0, y); line < y + h; line++) if ((this.taken[line] ?? 0) & mask) return false;
403
+ return true;
404
+ }
405
+
406
+ private take(x: number, y: number, w: number, h: number): void {
407
+ const mask = columns(x, w);
408
+ for (let line = Math.max(0, y); line < y + h; line++) this.taken[line] = (this.taken[line] ?? 0) | mask;
310
409
  }
311
410
  }
312
411
 
@@ -334,7 +433,8 @@ function panelJson(panel: PanelEntity, gridPos: GridPos, id: number, inherited?:
334
433
  const def = panel.panelDefinition;
335
434
  const own = datasourceRef(p.datasource) ?? inherited;
336
435
  const queries = (p.targets ?? []).filter(isQueryEntity);
337
- const targets = queries.map((q, i) => targetJson(q, i, own));
436
+ // A query under a Mixed panel names its own datasource; one that names none goes to the default, not to Mixed.
437
+ const targets = queries.map((q, i) => targetJson(q, i, sameRef(own, MIXED) ? undefined : own));
338
438
  const refs = targets.map((t) => t.datasource as DataSourceRef | undefined);
339
439
  let datasource = own;
340
440
  if (!datasource && refs.length > 0) {
@@ -374,19 +474,29 @@ function panelJson(panel: PanelEntity, gridPos: GridPos, id: number, inherited?:
374
474
  /** A dashboard's `panels` array: rows and panels, laid out, with ids. */
375
475
  export function panelsJson(items: Array<PanelEntity | RowEntity>): Array<PanelJson | RowPanelJson> {
376
476
  const layout = new Layout();
477
+ // Panels on the dashboard's grid with both x and y, and rows with a y, take their cells first; a collapsed row's panels are laid out on their own.
478
+ for (const item of items) {
479
+ if (isRowEntity(item)) layout.reserveRow(item.props.gridPos?.y);
480
+ const onGrid = isRowEntity(item) ? (item.props.collapsed ? [] : (item.props.panels ?? [])) : [item];
481
+ for (const p of onGrid.filter(isPanelEntity)) layout.reserve(p.props.gridPos, p.panelDefinition.defaultSize);
482
+ }
377
483
  const ids = new Ids(explicitIds(items));
378
484
  const out: Array<PanelJson | RowPanelJson> = [];
379
485
  for (const item of items) {
380
486
  if (isRowEntity(item)) {
381
487
  const r = item.props;
382
- const { pos, mark } = layout.row();
488
+ const pos = layout.row(r.gridPos?.y);
383
489
  const rowId = ids.take(r.id);
384
490
  const rowRef = datasourceRef(r.datasource);
385
- const children = (r.panels ?? []).filter(isPanelEntity).map((p) => {
386
- const gp = layout.place(p.props.gridPos, p.panelDefinition.defaultSize);
491
+ const collapsed = r.collapsed ?? false;
492
+ // A collapsed row's panels sit under its header on a grid of their own; the next item starts right under the header.
493
+ const rowLayout = collapsed ? new Layout(pos.y + 1) : layout;
494
+ const panels = (r.panels ?? []).filter(isPanelEntity);
495
+ if (collapsed) for (const p of panels) rowLayout.reserve(p.props.gridPos, p.panelDefinition.defaultSize);
496
+ const children = panels.map((p) => {
497
+ const gp = rowLayout.place(p.props.gridPos, p.panelDefinition.defaultSize);
387
498
  return panelJson(p, gp, ids.take(p.props.id), rowRef);
388
499
  });
389
- const collapsed = r.collapsed ?? false;
390
500
  out.push(
391
501
  compact({
392
502
  type: "row" as const,
@@ -399,8 +509,7 @@ export function panelsJson(items: Array<PanelEntity | RowEntity>): Array<PanelJs
399
509
  repeat: repeatName(r.repeat),
400
510
  }),
401
511
  );
402
- if (collapsed) mark();
403
- else out.push(...children);
512
+ if (!collapsed) out.push(...children);
404
513
  } else if (isPanelEntity(item)) {
405
514
  const gp = layout.place(item.props.gridPos, item.panelDefinition.defaultSize);
406
515
  out.push(panelJson(item, gp, ids.take(item.props.id)));
@@ -431,7 +540,7 @@ export function renderDashboard(dashboard: DashboardEntity, exportName?: string)
431
540
  liveNow: p.liveNow,
432
541
  panels: panelsJson(p.panels ?? []),
433
542
  refresh: p.refresh,
434
- schemaVersion: DASHBOARD_SCHEMA_VERSION,
543
+ schemaVersion: p.schemaVersion ?? DASHBOARD_SCHEMA_VERSION,
435
544
  tags: p.tags ?? [],
436
545
  templating: { list: (p.variables ?? []).filter(isVariableEntity).map(variableModel) },
437
546
  time: p.time ?? { from: "now-6h", to: "now" },
@@ -450,9 +559,9 @@ export function dashboardJson(dashboard: DashboardEntity, exportName?: string):
450
559
 
451
560
  // ── Datasources and providers ───────────────────────────────────
452
561
 
453
- /** Replace every declared `Datasource` inside plugin settings with its uid. */
562
+ /** Replace every declared or external datasource inside plugin settings with its uid. */
454
563
  function resolveNested(value: unknown): unknown {
455
- if (isDatasourceEntity(value)) return value.uid;
564
+ if (isDatasourceDeclaration(value)) return value.uid;
456
565
  if (Array.isArray(value)) return value.map(resolveNested);
457
566
  if (value && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
458
567
  return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, resolveNested(v)]));
@@ -483,6 +592,11 @@ export function provisionedDatasource(ds: DatasourceEntity): ProvisionedDatasour
483
592
  });
484
593
  }
485
594
 
595
+ /** One `ExternalDatasource` as the checks and the index see it. */
596
+ export function externalDatasourceRecord(ds: ExternalDatasourceEntity): ExternalDatasourceRecord {
597
+ return compact({ type: ds.datasourceType, uid: ds.uid, name: ds.props.name });
598
+ }
599
+
486
600
  /** One dashboard provider as a provisioning entry. */
487
601
  export function provisionedProvider(p: DashboardProviderProps): ProvisionedProvider {
488
602
  const pinnedFolder = p.folder !== undefined || p.folderUid !== undefined;
@@ -516,8 +630,21 @@ export function dashboardProvidersYaml(providers: ProvisionedProvider[]): string
516
630
  return yamlFile("Grafana dashboard provisioning, generated by chant.", { apiVersion: 1, providers });
517
631
  }
518
632
 
633
+ /**
634
+ * A dashboard's `folder` as its directory under `dashboards/`: one
635
+ * subdirectory per `/`-separated level, which a provider with
636
+ * `foldersFromFilesStructure` turns into nested folders on Grafana 13.1 and
637
+ * later (GRAF109 says what earlier versions do). Leading dots and blank
638
+ * levels are dropped, so a folder can't name a directory outside
639
+ * `dashboards/`; a backslash becomes `-`. Empty when nothing is left: the
640
+ * General folder.
641
+ */
519
642
  function folderDir(folder: string): string {
520
- return folder.replace(/[/\\]+/g, "-").replace(/^\.+/, "").trim() || "General";
643
+ return folder
644
+ .split("/")
645
+ .map((level) => level.replace(/\\+/g, "-").trim().replace(/^\.+/, "").trim())
646
+ .filter((level) => level !== "")
647
+ .join("/");
521
648
  }
522
649
 
523
650
  // ── The whole build ─────────────────────────────────────────────
@@ -534,13 +661,19 @@ export function buildGrafana(entities: Map<string, Declarable> | Iterable<Declar
534
661
  .map(([, e]) => provisionedDatasource(e as DatasourceEntity))
535
662
  .sort((a, b) => a.name.localeCompare(b.name));
536
663
 
664
+ const externalDatasources: ExternalDatasourceRecord[] = named
665
+ .filter(([, e]) => isExternalDatasource(e))
666
+ .map(([, e]) => externalDatasourceRecord(e as ExternalDatasourceEntity))
667
+ .sort((a, b) => a.uid.localeCompare(b.uid));
668
+
537
669
  const dashboards: BuiltDashboard[] = [];
538
670
  for (const [name, e] of named) {
539
671
  if (!isDashboardEntity(e)) continue;
540
672
  const json = renderDashboard(e, name);
541
673
  const uid = json.uid as string;
542
674
  const folder = e.props.folder;
543
- const dir = folder ? `${DASHBOARDS_DIR}/${folderDir(folder)}` : DASHBOARDS_DIR;
675
+ const sub = folder ? folderDir(folder) : "";
676
+ const dir = sub ? `${DASHBOARDS_DIR}/${sub}` : DASHBOARDS_DIR;
544
677
  let file = `${dir}/${uid}.json`;
545
678
  for (let n = 2; file in files; n++) file = `${dir}/${uid}-${n}.json`;
546
679
  files[file] = `${JSON.stringify(json, null, 2)}\n`;
@@ -552,19 +685,23 @@ export function buildGrafana(entities: Map<string, Declarable> | Iterable<Declar
552
685
  declaredProviders.length > 0
553
686
  ? declaredProviders.map(provisionedProvider)
554
687
  : dashboards.length > 0
555
- ? [provisionedProvider({ name: "chant" })]
688
+ ? [provisionedProvider({ name: DEFAULT_PROVIDER_NAME })]
556
689
  : [];
557
690
 
558
691
  if (datasources.length > 0) files[DATASOURCES_FILE] = datasourcesYaml(datasources);
559
692
  if (providers.length > 0) files[DASHBOARD_PROVIDERS_FILE] = dashboardProvidersYaml(providers);
693
+ const alerting = buildAlerting(named.map(([, e]) => e));
694
+ if (alerting) files[ALERTING_FILE] = alertingYaml(alerting.file);
560
695
 
561
696
  const index: GrafanaIndex = {
562
697
  grafanaSchema: `${GRAFANA_SCHEMA_PIN.source}@${GRAFANA_SCHEMA_PIN.ref}`,
563
698
  dashboards: dashboards.map(({ uid, title, folder, file }) => ({ uid, title, ...(folder ? { folder } : {}), file })),
564
699
  datasources: datasources.map(({ name, type, uid }) => ({ name, type, uid })),
700
+ ...(externalDatasources.length > 0 ? { externalDatasources } : {}),
701
+ ...(alerting ? { alerting: alerting.index } : {}),
565
702
  files: Object.keys(files).sort(),
566
703
  };
567
- return { dashboards, datasources, providers, files, index };
704
+ return { dashboards, datasources, externalDatasources, providers, ...(alerting ? { alerting: alerting.file } : {}), files, index };
568
705
  }
569
706
 
570
707
  /** Every file the grafana entities render to, by path relative to the output directory. */
package/src/catalog.ts CHANGED
@@ -5,13 +5,23 @@
5
5
  */
6
6
 
7
7
  import type { LexiconEntry } from "@intentius/chant/lsp/lexicon-providers";
8
- import { DATASOURCE_TYPE } from "./datasource";
8
+ import { DATASOURCE_TYPE, EXTERNAL_DATASOURCE_TYPE } from "./datasource";
9
9
  import { DASHBOARD_TYPE, DASHBOARD_PROVIDER_TYPE } from "./dashboard";
10
10
  import * as panels from "./panels";
11
11
  import * as queries from "./query";
12
12
  import { VARIABLE_TYPE_PREFIX } from "./variables";
13
+ import {
14
+ ALERT_QUERY_TYPE,
15
+ ALERT_RULE_GROUP_TYPE,
16
+ ALERT_RULE_TYPE,
17
+ CONTACT_POINT_TYPE,
18
+ EXPRESSION_TYPE_PREFIX,
19
+ MUTE_TIMING_TYPE,
20
+ NOTIFICATION_POLICY_TYPE,
21
+ NOTIFICATION_TEMPLATE_TYPE,
22
+ } from "./alerting";
13
23
 
14
- export type CatalogKind = "dashboard" | "datasource" | "provider" | "panel" | "row" | "query" | "variable";
24
+ export type CatalogKind = "dashboard" | "datasource" | "provider" | "panel" | "row" | "query" | "variable" | "alerting" | "expression";
15
25
 
16
26
  export interface CatalogEntry {
17
27
  className: string;
@@ -59,6 +69,15 @@ const VARIABLES: Array<[string, string, string]> = [
59
69
  ["TextboxVariable", "textbox", "A free-text box"],
60
70
  ];
61
71
 
72
+ const EXPRESSIONS: Array<[string, string, string]> = [
73
+ ["ReduceExpression", "reduce", "A server-side expression reducing each series to one number (last, mean, max, ...)"],
74
+ ["MathExpression", "math", "A server-side math expression over other results by refId, e.g. $A / $B"],
75
+ ["ThresholdExpression", "threshold", "A server-side threshold, with an optional recovery threshold"],
76
+ ["ResampleExpression", "resample", "A server-side expression resampling a time series to a fixed window"],
77
+ ["ClassicConditionsExpression", "classic_conditions", "Grafana's legacy classic conditions, as a server-side expression"],
78
+ ["SqlExpression", "sql", "A server-side SQL expression over the other results"],
79
+ ];
80
+
62
81
  export const BUILTIN_CATALOG: CatalogEntry[] = [
63
82
  {
64
83
  className: "Dashboard",
@@ -74,6 +93,13 @@ export const BUILTIN_CATALOG: CatalogEntry[] = [
74
93
  entityKind: "resource",
75
94
  description: "A datasource, declared once and referenced by panels, queries and variables",
76
95
  },
96
+ {
97
+ className: "ExternalDatasource",
98
+ entityType: EXTERNAL_DATASOURCE_TYPE,
99
+ kind: "datasource",
100
+ entityKind: "resource",
101
+ description: "A datasource that already exists in Grafana: referenced like a Datasource and checked by GRAF101/GRAF102, never provisioned",
102
+ },
77
103
  {
78
104
  className: "DashboardProvider",
79
105
  entityType: DASHBOARD_PROVIDER_TYPE,
@@ -88,6 +114,64 @@ export const BUILTIN_CATALOG: CatalogEntry[] = [
88
114
  entityKind: "property",
89
115
  description: "A full-width row header; its panels are placed below it, or inside it when collapsed",
90
116
  },
117
+ {
118
+ className: "AlertRuleGroup",
119
+ entityType: ALERT_RULE_GROUP_TYPE,
120
+ kind: "alerting",
121
+ entityKind: "resource",
122
+ description: "A group of Grafana-managed alert and recording rules in a folder, evaluated together at one interval",
123
+ },
124
+ {
125
+ className: "ContactPoint",
126
+ entityType: CONTACT_POINT_TYPE,
127
+ kind: "alerting",
128
+ entityKind: "resource",
129
+ description: "A contact point: the integrations (email, Slack, webhook, ...) alerts are sent to, under one name",
130
+ },
131
+ {
132
+ className: "NotificationPolicy",
133
+ entityType: NOTIFICATION_POLICY_TYPE,
134
+ kind: "alerting",
135
+ entityKind: "resource",
136
+ description: "An organisation's notification policy tree: the root receiver and the routes that match alerts to contact points",
137
+ },
138
+ {
139
+ className: "MuteTiming",
140
+ entityType: MUTE_TIMING_TYPE,
141
+ kind: "alerting",
142
+ entityKind: "resource",
143
+ description: "A named time interval that mutes (or, as active_time_intervals, enables) notifications",
144
+ },
145
+ {
146
+ className: "NotificationTemplate",
147
+ entityType: NOTIFICATION_TEMPLATE_TYPE,
148
+ kind: "alerting",
149
+ entityKind: "resource",
150
+ description: "A notification template group, Go templates contact point settings can use",
151
+ },
152
+ {
153
+ className: "AlertRule",
154
+ entityType: ALERT_RULE_TYPE,
155
+ kind: "alerting",
156
+ entityKind: "property",
157
+ description: "A Grafana-managed alert or recording rule: queries, expressions and the condition that fires it",
158
+ },
159
+ {
160
+ className: "AlertQuery",
161
+ entityType: ALERT_QUERY_TYPE,
162
+ kind: "alerting",
163
+ entityKind: "property",
164
+ description: "A datasource query of an alert rule, with its model as Grafana stores it and its time range",
165
+ },
166
+ ...EXPRESSIONS.map(
167
+ ([className, type, description]): CatalogEntry => ({
168
+ className,
169
+ entityType: `${EXPRESSION_TYPE_PREFIX}${type}`,
170
+ kind: "expression",
171
+ entityKind: "property",
172
+ description,
173
+ }),
174
+ ),
91
175
  ...panelEntries,
92
176
  ...queryEntries,
93
177
  ...VARIABLES.map(
@@ -13,7 +13,8 @@ function serviceFromType(resourceType: string): string {
13
13
  }
14
14
 
15
15
  const overview = `The grafana lexicon types what a [Grafana](https://grafana.com/docs/grafana/latest/) dashboard
16
- shows: datasources, dashboards, rows, panels, queries and variables. \`chant build\`
16
+ shows: datasources, dashboards, rows, panels, queries and variables, and
17
+ Grafana-managed alerting. \`chant build\`
17
18
  writes one JSON file per dashboard, the JSON Grafana imports as it is, plus the
18
19
  provisioning files Grafana reads for dashboards and datasources. Grafana server
19
20
  settings, users and plugins are out of scope.
@@ -28,11 +29,29 @@ const requests = new TimeSeriesPanel({ title: "Requests per second", datasource:
28
29
  export const overview = new Dashboard({ title: "Overview", panels: [requests] });
29
30
  \`\`\`
30
31
 
31
- Panels: time series, stat, gauge, table, logs, traces, heatmap and text, plus
32
- rows. Queries: Prometheus (PromQL), Tempo (TraceQL) and Loki (LogQL). Panel
32
+ Panels: time series, stat, gauge, table, logs, traces, heatmap, text, bar
33
+ chart, bar gauge, pie chart, state timeline, status history, histogram, node
34
+ graph, XY chart, trend, canvas, geomap, flame graph and alert list, plus rows. Queries: Prometheus (PromQL), Tempo (TraceQL) and Loki (LogQL). Panel
33
35
  options and query fields are generated from Grafana's own schemas at a pinned
34
- version, and every build validates the dashboards against the same schemas.
36
+ version, corrected against Grafana's CUE, and track Grafana 12.4 and 13.x. Every
37
+ build validates the dashboards against the same schemas.
35
38
  \`definePanel\` and \`defineQuery\` add plugins chant doesn't ship.
39
+ \`chant import\` turns dashboard JSON exported from Grafana into this TypeScript,
40
+ and it builds back to the same dashboard (see
41
+ [Importing Dashboards](./importing/)). \`chant lifecycle diff --live\` reports
42
+ a dashboard edited in Grafana as drift, property by property, and
43
+ \`chant import --from <env>\` writes a running Grafana's dashboards as
44
+ TypeScript (see [Drift and Live Export](./observing/)). \`grafanaApply\` writes
45
+ a build to a running Grafana over its HTTP API, with folders and library
46
+ panels, and prunes the project's own dashboards and folders (see
47
+ [Apply over the API](./applying/)).
48
+
49
+ Grafana-managed alerting is typed too: rule groups with their queries and
50
+ server-side expressions (reduce, math, threshold, resample, classic
51
+ conditions, SQL), contact points, the notification policy tree, mute timings
52
+ and templates, written to Grafana's alerting provisioning file.
53
+ \`SloAlertRules\` turns a prometheus \`Slo\` into burn-rate alert rules. See
54
+ [Alerting](./alerting/).
36
55
 
37
56
  Three composites build whole dashboards from declarations in other lexicons:
38
57
  \`RedDashboard\` from an otel \`spanmetrics\` connector, \`SloDashboard\` from a
@@ -40,10 +59,17 @@ prometheus \`Slo\` and \`AgentDashboard\` from the otel GenAI preset. Metric nam
40
59
  come from the declaration, so renaming one moves the panels. See
41
60
  [Dashboards from Declarations](./composites/).
42
61
 
43
- Checks catch a query aimed at a datasource nobody declared (GRAF101) or of the
62
+ Checks catch a query aimed at a datasource nobody declared, with
63
+ \`Datasource\` or \`ExternalDatasource\` (GRAF101), or of the
44
64
  wrong type (GRAF102), a \`$variable\` the dashboard doesn't declare (GRAF103),
45
65
  duplicate uids and ids (GRAF104), panels off the grid or overlapping (GRAF105),
46
- and anything Grafana's dashboard schema rejects (GRAF107).
66
+ anything Grafana's dashboard schema rejects (GRAF107), PromQL sent to a
67
+ Prometheus that doesn't parse (GRAF108), dashboard providers that load a
68
+ dashboard twice or not into its declared folder (GRAF109), and a unit Grafana
69
+ doesn't know (GRAF115). For alerting, they catch rules whose condition or expressions name
70
+ no query (GRAF111), queries to undeclared datasources (GRAF112), routes to
71
+ undeclared contact points or mute timings (GRAF113), and uids, intervals and
72
+ duplicates Grafana refuses (GRAF114).
47
73
  `;
48
74
 
49
75
  const outputFormat = `The grafana lexicon writes Grafana's own files, keyed by path under the
@@ -54,9 +80,12 @@ directory of the \`-o\` output:
54
80
  - \`provisioning/datasources/chant.yaml\`: every declared datasource.
55
81
  - \`provisioning/dashboards/chant.yaml\`: a file provider for the dashboards
56
82
  directory; a default one unless a \`DashboardProvider\` is declared.
83
+ - \`provisioning/alerting/chant.yaml\`: rule groups, contact points, the
84
+ notification policy tree, mute timings and templates, when the build
85
+ declares any.
57
86
 
58
87
  The primary output (the \`-o\` file itself) is a JSON index of the dashboards,
59
- datasources and files that were built.
88
+ datasources, alerting and files that were built.
60
89
 
61
90
  - Dashboard JSON carries the pinned schema's \`schemaVersion\`, no numeric \`id\`,
62
91
  and every field Grafana's schema requires (link defaults, an empty
@@ -67,8 +96,12 @@ datasources and files that were built.
67
96
  referenced, and as its uid inside another datasource's \`jsonData\`.
68
97
  - \`chant build\` rewrites JSON files with sorted keys; Grafana does not care
69
98
  about key order.
70
- - Grafana's files have no metadata channel chant could use without changing
71
- what Grafana imports, so no ownership marker is stamped.
99
+ - The dashboard JSON carries no ownership marker. Grafana keeps who manages a
100
+ dashboard in the resource's metadata on its \`dashboard.grafana.app\` API,
101
+ not in the JSON: for a provisioned dashboard, the name of the provider that
102
+ loaded it, which is \`chant\` unless a \`DashboardProvider\` names another.
103
+ \`chant lifecycle diff --live\` reads that back (see
104
+ [Drift and Live Export](../observing/)).
72
105
  `;
73
106
 
74
107
  export async function generateDocs(opts?: { verbose?: boolean }): Promise<void> {
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * JSON Schema (draft-07, the subset cog emits) to TypeScript declarations.
3
3
  *
4
- * Each vendored schema becomes one module under `src/schema/`: one exported
5
- * type per entry in `definitions`, with the schema's descriptions as doc
6
- * comments. `required` is kept, so the types say what the schema says; the
4
+ * Each vendored schema, with its correction overlay applied, becomes one
5
+ * module under `src/schema/`: one exported type per entry in `definitions`,
6
+ * with the schema's descriptions as doc comments. `required` is kept, so the types say what the schema says; the
7
7
  * entity props wrap panel options in `DeepPartial` where Grafana fills the
8
8
  * rest in on import.
9
9
  */
@@ -99,6 +99,15 @@ function objectType(node: Node, indent: string): string {
99
99
  return lines.length === 2 ? "Record<string, never>" : lines.join("\n");
100
100
  }
101
101
 
102
+ /**
103
+ * A branch that only constrains which keys are present (`{ required: [...] }`),
104
+ * like the overlay's "a panel has a `type` or a `libraryPanel`". It says
105
+ * nothing about the shape, so the type ignores it and validation enforces it.
106
+ */
107
+ function constraintOnly(branches: Node[] | undefined): boolean {
108
+ return Array.isArray(branches) && branches.every((b) => Object.keys(b).every((k) => k === "required"));
109
+ }
110
+
102
111
  /** The TypeScript type for one schema node. */
103
112
  export function tsType(node: Node | boolean | undefined, indent = ""): string {
104
113
  if (node === undefined || node === true) return "unknown";
@@ -106,8 +115,8 @@ export function tsType(node: Node | boolean | undefined, indent = ""): string {
106
115
  if (node.$ref) return refName(node.$ref);
107
116
  if (node.const !== undefined) return literal(node.const);
108
117
  if (Array.isArray(node.enum)) return union(node.enum.map(literal));
109
- if (node.oneOf) return union(node.oneOf.map((n) => paren(tsType(n, indent))));
110
- if (node.anyOf) return union(node.anyOf.map((n) => paren(tsType(n, indent))));
118
+ if (node.oneOf && !constraintOnly(node.oneOf)) return union(node.oneOf.map((n) => paren(tsType(n, indent))));
119
+ if (node.anyOf && !constraintOnly(node.anyOf)) return union(node.anyOf.map((n) => paren(tsType(n, indent))));
111
120
  if (node.allOf) return node.allOf.map((n) => paren(tsType(n, indent))).join(" & ");
112
121
 
113
122
  const types = Array.isArray(node.type) ? node.type : node.type ? [node.type] : [];
@@ -154,7 +163,8 @@ export function schemaModule(name: SchemaName, schema: Record<string, unknown>,
154
163
  const defs = (schema.definitions ?? {}) as Record<string, Node>;
155
164
  const out: string[] = [
156
165
  "// Code generated by chant generate from the vendored Grafana schema",
157
- `// src/spec/schemas/${name}.jsonschema.json (see src/pin.ts). DO NOT EDIT.`,
166
+ `// src/spec/schemas/${name}.jsonschema.json (see src/pin.ts) and its correction`,
167
+ "// overlay in src/spec/overlay/ (see src/spec/overlay.ts). DO NOT EDIT.",
158
168
  "/* eslint-disable */",
159
169
  "",
160
170
  ];