@intentius/chant-lexicon-grafana 0.97.0 → 0.99.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (451) hide show
  1. package/README.md +15 -5
  2. package/dist/actions/grafana.d.ts +24 -0
  3. package/dist/actions/grafana.d.ts.map +1 -0
  4. package/dist/actions/index.d.ts +2 -0
  5. package/dist/actions/index.d.ts.map +1 -0
  6. package/dist/alerting-build.d.ts +128 -0
  7. package/dist/alerting-build.d.ts.map +1 -0
  8. package/dist/alerting.d.ts +273 -0
  9. package/dist/alerting.d.ts.map +1 -0
  10. package/dist/api/apply.d.ts +156 -0
  11. package/dist/api/apply.d.ts.map +1 -0
  12. package/dist/api/bind.d.ts +32 -0
  13. package/dist/api/bind.d.ts.map +1 -0
  14. package/dist/api/client.d.ts +76 -0
  15. package/dist/api/client.d.ts.map +1 -0
  16. package/dist/api/converge.d.ts +33 -0
  17. package/dist/api/converge.d.ts.map +1 -0
  18. package/dist/api/dashboards.d.ts +102 -0
  19. package/dist/api/dashboards.d.ts.map +1 -0
  20. package/dist/api/datasources.d.ts +26 -0
  21. package/dist/api/datasources.d.ts.map +1 -0
  22. package/dist/api/fake-grafana-writes.d.ts +41 -0
  23. package/dist/api/fake-grafana-writes.d.ts.map +1 -0
  24. package/dist/api/fake-grafana.d.ts +61 -0
  25. package/dist/api/fake-grafana.d.ts.map +1 -0
  26. package/dist/api/folders.d.ts +110 -0
  27. package/dist/api/folders.d.ts.map +1 -0
  28. package/dist/api/library-panels.d.ts +52 -0
  29. package/dist/api/library-panels.d.ts.map +1 -0
  30. package/dist/build.d.ts +30 -2
  31. package/dist/build.d.ts.map +1 -1
  32. package/dist/catalog.d.ts +1 -1
  33. package/dist/catalog.d.ts.map +1 -1
  34. package/dist/codegen/docs.d.ts.map +1 -1
  35. package/dist/codegen/schema-types.d.ts +3 -3
  36. package/dist/codegen/schema-types.d.ts.map +1 -1
  37. package/dist/composites/agent-dashboard.d.ts.map +1 -1
  38. package/dist/composites/catalog.d.ts.map +1 -1
  39. package/dist/composites/index.d.ts +4 -2
  40. package/dist/composites/index.d.ts.map +1 -1
  41. package/dist/composites/red-dashboard.d.ts +21 -2
  42. package/dist/composites/red-dashboard.d.ts.map +1 -1
  43. package/dist/composites/shared.d.ts +7 -0
  44. package/dist/composites/shared.d.ts.map +1 -1
  45. package/dist/composites/slo-alert-rules.d.ts +92 -0
  46. package/dist/composites/slo-alert-rules.d.ts.map +1 -0
  47. package/dist/config.d.ts +111 -0
  48. package/dist/config.d.ts.map +1 -0
  49. package/dist/contact-point-secrets.d.ts +17 -0
  50. package/dist/contact-point-secrets.d.ts.map +1 -0
  51. package/dist/dashboard.d.ts +16 -2
  52. package/dist/dashboard.d.ts.map +1 -1
  53. package/dist/datasource-refs.d.ts +95 -0
  54. package/dist/datasource-refs.d.ts.map +1 -0
  55. package/dist/datasource.d.ts +29 -0
  56. package/dist/datasource.d.ts.map +1 -1
  57. package/dist/deep-observe-hooks.d.ts +64 -0
  58. package/dist/deep-observe-hooks.d.ts.map +1 -0
  59. package/dist/deep-observe.d.ts +42 -0
  60. package/dist/deep-observe.d.ts.map +1 -0
  61. package/dist/describe-resources.d.ts +49 -0
  62. package/dist/describe-resources.d.ts.map +1 -0
  63. package/dist/detect.d.ts +28 -1
  64. package/dist/detect.d.ts.map +1 -1
  65. package/dist/export-resources.d.ts +38 -0
  66. package/dist/export-resources.d.ts.map +1 -0
  67. package/dist/import/alerting.d.ts +40 -0
  68. package/dist/import/alerting.d.ts.map +1 -0
  69. package/dist/import/edits.d.ts +47 -0
  70. package/dist/import/edits.d.ts.map +1 -0
  71. package/dist/import/embedded.d.ts +14 -0
  72. package/dist/import/embedded.d.ts.map +1 -0
  73. package/dist/import/generator.d.ts +60 -0
  74. package/dist/import/generator.d.ts.map +1 -0
  75. package/dist/import/live-export.d.ts +108 -0
  76. package/dist/import/live-export.d.ts.map +1 -0
  77. package/dist/import/mappings.d.ts +83 -0
  78. package/dist/import/mappings.d.ts.map +1 -0
  79. package/dist/import/model.d.ts +98 -0
  80. package/dist/import/model.d.ts.map +1 -0
  81. package/dist/import/normalize-alerting.d.ts +26 -0
  82. package/dist/import/normalize-alerting.d.ts.map +1 -0
  83. package/dist/import/normalize.d.ts +74 -0
  84. package/dist/import/normalize.d.ts.map +1 -0
  85. package/dist/import/parser.d.ts +110 -0
  86. package/dist/import/parser.d.ts.map +1 -0
  87. package/dist/import/report.d.ts +26 -0
  88. package/dist/import/report.d.ts.map +1 -0
  89. package/dist/import/testdata/fixtures.d.ts +35 -0
  90. package/dist/import/testdata/fixtures.d.ts.map +1 -0
  91. package/dist/import/v2.d.ts +72 -0
  92. package/dist/import/v2.d.ts.map +1 -0
  93. package/dist/index.d.ts +26 -9
  94. package/dist/index.d.ts.map +1 -1
  95. package/dist/integrity.json +18 -10
  96. package/dist/k8s.d.ts +107 -0
  97. package/dist/k8s.d.ts.map +1 -0
  98. package/dist/lint/audit-catalog.d.ts +2 -1
  99. package/dist/lint/audit-catalog.d.ts.map +1 -1
  100. package/dist/lint/post-synth/graf101.d.ts +2 -2
  101. package/dist/lint/post-synth/graf102.d.ts +1 -1
  102. package/dist/lint/post-synth/graf107.d.ts +1 -1
  103. package/dist/lint/post-synth/graf108.d.ts +8 -0
  104. package/dist/lint/post-synth/graf108.d.ts.map +1 -0
  105. package/dist/lint/post-synth/graf109.d.ts +8 -0
  106. package/dist/lint/post-synth/graf109.d.ts.map +1 -0
  107. package/dist/lint/post-synth/graf111.d.ts +8 -0
  108. package/dist/lint/post-synth/graf111.d.ts.map +1 -0
  109. package/dist/lint/post-synth/graf112.d.ts +8 -0
  110. package/dist/lint/post-synth/graf112.d.ts.map +1 -0
  111. package/dist/lint/post-synth/graf113.d.ts +8 -0
  112. package/dist/lint/post-synth/graf113.d.ts.map +1 -0
  113. package/dist/lint/post-synth/graf114.d.ts +8 -0
  114. package/dist/lint/post-synth/graf114.d.ts.map +1 -0
  115. package/dist/lint/post-synth/graf115.d.ts +8 -0
  116. package/dist/lint/post-synth/graf115.d.ts.map +1 -0
  117. package/dist/lint/post-synth/grafana-helpers.d.ts +7 -5
  118. package/dist/lint/post-synth/grafana-helpers.d.ts.map +1 -1
  119. package/dist/lint/post-synth/index.d.ts.map +1 -1
  120. package/dist/lint/rules/literal-secret.d.ts +7 -3
  121. package/dist/lint/rules/literal-secret.d.ts.map +1 -1
  122. package/dist/lsp/hover.d.ts.map +1 -1
  123. package/dist/manifest.json +1 -1
  124. package/dist/members.d.ts +27 -0
  125. package/dist/members.d.ts.map +1 -0
  126. package/dist/meta.json +135 -0
  127. package/dist/okf/index.md +36 -2
  128. package/dist/okf/rules/GRAF002.md +3 -2
  129. package/dist/okf/rules/GRAF101.md +3 -2
  130. package/dist/okf/rules/GRAF102.md +1 -0
  131. package/dist/okf/rules/GRAF108.md +11 -0
  132. package/dist/okf/rules/GRAF109.md +15 -0
  133. package/dist/okf/rules/GRAF111.md +11 -0
  134. package/dist/okf/rules/GRAF112.md +16 -0
  135. package/dist/okf/rules/GRAF113.md +16 -0
  136. package/dist/okf/rules/GRAF114.md +11 -0
  137. package/dist/okf/rules/GRAF115.md +11 -0
  138. package/dist/okf/types/AlertListPanel.md +9 -0
  139. package/dist/okf/types/AlertQuery.md +9 -0
  140. package/dist/okf/types/AlertRule.md +9 -0
  141. package/dist/okf/types/AlertRuleGroup.md +9 -0
  142. package/dist/okf/types/BarChartPanel.md +9 -0
  143. package/dist/okf/types/BarGaugePanel.md +9 -0
  144. package/dist/okf/types/CanvasPanel.md +9 -0
  145. package/dist/okf/types/ClassicConditionsExpression.md +9 -0
  146. package/dist/okf/types/ContactPoint.md +14 -0
  147. package/dist/okf/types/Dashboard.md +1 -0
  148. package/dist/okf/types/Datasource.md +3 -2
  149. package/dist/okf/types/ExternalDatasource.md +15 -0
  150. package/dist/okf/types/FlameGraphPanel.md +9 -0
  151. package/dist/okf/types/GeomapPanel.md +9 -0
  152. package/dist/okf/types/HistogramPanel.md +9 -0
  153. package/dist/okf/types/MathExpression.md +9 -0
  154. package/dist/okf/types/MuteTiming.md +13 -0
  155. package/dist/okf/types/NodeGraphPanel.md +9 -0
  156. package/dist/okf/types/NotificationPolicy.md +9 -0
  157. package/dist/okf/types/NotificationTemplate.md +9 -0
  158. package/dist/okf/types/PieChartPanel.md +9 -0
  159. package/dist/okf/types/ReduceExpression.md +9 -0
  160. package/dist/okf/types/ResampleExpression.md +9 -0
  161. package/dist/okf/types/SqlExpression.md +9 -0
  162. package/dist/okf/types/StateTimelinePanel.md +9 -0
  163. package/dist/okf/types/StatusHistoryPanel.md +9 -0
  164. package/dist/okf/types/ThresholdExpression.md +9 -0
  165. package/dist/okf/types/TrendPanel.md +9 -0
  166. package/dist/okf/types/XYChartPanel.md +9 -0
  167. package/dist/op/activities/grafana-apply.d.ts +75 -0
  168. package/dist/op/activities/grafana-apply.d.ts.map +1 -0
  169. package/dist/op/activities/index.d.ts +9 -0
  170. package/dist/op/activities/index.d.ts.map +1 -0
  171. package/dist/op/builders.d.ts +24 -0
  172. package/dist/op/builders.d.ts.map +1 -0
  173. package/dist/ownership.d.ts +67 -0
  174. package/dist/ownership.d.ts.map +1 -0
  175. package/dist/panel-options.d.ts +65 -0
  176. package/dist/panel-options.d.ts.map +1 -0
  177. package/dist/panels.d.ts +46 -9
  178. package/dist/panels.d.ts.map +1 -1
  179. package/dist/pin.d.ts +29 -7
  180. package/dist/pin.d.ts.map +1 -1
  181. package/dist/plugin.d.ts.map +1 -1
  182. package/dist/promql-check.d.ts +51 -0
  183. package/dist/promql-check.d.ts.map +1 -0
  184. package/dist/query.d.ts +9 -8
  185. package/dist/query.d.ts.map +1 -1
  186. package/dist/rules/graf101.ts +3 -3
  187. package/dist/rules/graf102.ts +1 -1
  188. package/dist/rules/graf107.ts +1 -1
  189. package/dist/rules/graf108.ts +17 -0
  190. package/dist/rules/graf109.ts +17 -0
  191. package/dist/rules/graf111.ts +17 -0
  192. package/dist/rules/graf112.ts +17 -0
  193. package/dist/rules/graf113.ts +17 -0
  194. package/dist/rules/graf114.ts +17 -0
  195. package/dist/rules/graf115.ts +17 -0
  196. package/dist/rules/grafana-helpers.ts +15 -8
  197. package/dist/rules/literal-secret.ts +60 -6
  198. package/dist/schema/barchart.gen.d.ts +198 -0
  199. package/dist/schema/barchart.gen.d.ts.map +1 -0
  200. package/dist/schema/bargauge.gen.d.ts +97 -0
  201. package/dist/schema/bargauge.gen.d.ts.map +1 -0
  202. package/dist/schema/canvas.gen.d.ts +152 -0
  203. package/dist/schema/canvas.gen.d.ts.map +1 -0
  204. package/dist/schema/dashboard.gen.d.ts +196 -21
  205. package/dist/schema/dashboard.gen.d.ts.map +1 -1
  206. package/dist/schema/expr.gen.d.ts +606 -0
  207. package/dist/schema/expr.gen.d.ts.map +1 -0
  208. package/dist/schema/gauge.gen.d.ts.map +1 -1
  209. package/dist/schema/geomap.gen.d.ts +100 -0
  210. package/dist/schema/geomap.gen.d.ts.map +1 -0
  211. package/dist/schema/heatmap.gen.d.ts.map +1 -1
  212. package/dist/schema/histogram.gen.d.ts +132 -0
  213. package/dist/schema/histogram.gen.d.ts.map +1 -0
  214. package/dist/schema/index.d.ts +12 -0
  215. package/dist/schema/index.d.ts.map +1 -1
  216. package/dist/schema/logs.gen.d.ts +1 -0
  217. package/dist/schema/logs.gen.d.ts.map +1 -1
  218. package/dist/schema/loki.gen.d.ts.map +1 -1
  219. package/dist/schema/nodegraph.gen.d.ts +49 -0
  220. package/dist/schema/nodegraph.gen.d.ts.map +1 -0
  221. package/dist/schema/piechart.gen.d.ts +127 -0
  222. package/dist/schema/piechart.gen.d.ts.map +1 -0
  223. package/dist/schema/prometheus.gen.d.ts.map +1 -1
  224. package/dist/schema/stat.gen.d.ts.map +1 -1
  225. package/dist/schema/statetimeline.gen.d.ts +124 -0
  226. package/dist/schema/statetimeline.gen.d.ts.map +1 -0
  227. package/dist/schema/statushistory.gen.d.ts +114 -0
  228. package/dist/schema/statushistory.gen.d.ts.map +1 -0
  229. package/dist/schema/table.gen.d.ts +22 -3
  230. package/dist/schema/table.gen.d.ts.map +1 -1
  231. package/dist/schema/tempo.gen.d.ts.map +1 -1
  232. package/dist/schema/text.gen.d.ts.map +1 -1
  233. package/dist/schema/timeseries.gen.d.ts +5 -1
  234. package/dist/schema/timeseries.gen.d.ts.map +1 -1
  235. package/dist/schema/trend.gen.d.ts +180 -0
  236. package/dist/schema/trend.gen.d.ts.map +1 -0
  237. package/dist/schema/xychart.gen.d.ts +145 -0
  238. package/dist/schema/xychart.gen.d.ts.map +1 -0
  239. package/dist/schema-validate.d.ts +30 -4
  240. package/dist/schema-validate.d.ts.map +1 -1
  241. package/dist/serializer.d.ts +11 -3
  242. package/dist/serializer.d.ts.map +1 -1
  243. package/dist/skill-defs.d.ts.map +1 -1
  244. package/dist/skills/chant-grafana-alerting.md +49 -0
  245. package/dist/skills/chant-grafana-provisioning.md +3 -3
  246. package/dist/skills/chant-grafana.md +12 -6
  247. package/dist/spec/fetch-units-cli.d.ts +3 -0
  248. package/dist/spec/fetch-units-cli.d.ts.map +1 -0
  249. package/dist/spec/overlay.d.ts +43 -0
  250. package/dist/spec/overlay.d.ts.map +1 -0
  251. package/dist/spec/schemas.d.ts +13 -7
  252. package/dist/spec/schemas.d.ts.map +1 -1
  253. package/dist/spec/units.d.ts +35 -0
  254. package/dist/spec/units.d.ts.map +1 -0
  255. package/dist/spec/units.gen.d.ts +2 -0
  256. package/dist/spec/units.gen.d.ts.map +1 -0
  257. package/dist/transformations.d.ts +459 -0
  258. package/dist/transformations.d.ts.map +1 -0
  259. package/dist/util.d.ts +6 -0
  260. package/dist/util.d.ts.map +1 -1
  261. package/dist/validate-alerting.d.ts +65 -0
  262. package/dist/validate-alerting.d.ts.map +1 -0
  263. package/dist/validate-output.d.ts +49 -9
  264. package/dist/validate-output.d.ts.map +1 -1
  265. package/dist/validate.d.ts.map +1 -1
  266. package/dist/variables.d.ts +7 -2
  267. package/dist/variables.d.ts.map +1 -1
  268. package/package.json +12 -4
  269. package/src/actions/actions.test.ts +24 -0
  270. package/src/actions/grafana.ts +40 -0
  271. package/src/actions/index.ts +1 -0
  272. package/src/alerting-build.ts +376 -0
  273. package/src/alerting.e2e.test.ts +212 -0
  274. package/src/alerting.test.ts +272 -0
  275. package/src/alerting.ts +435 -0
  276. package/src/api/apply.test.ts +238 -0
  277. package/src/api/apply.ts +384 -0
  278. package/src/api/bind.ts +58 -0
  279. package/src/api/client.ts +148 -0
  280. package/src/api/converge.ts +63 -0
  281. package/src/api/dashboards.ts +247 -0
  282. package/src/api/datasources.ts +33 -0
  283. package/src/api/fake-grafana-writes.ts +201 -0
  284. package/src/api/fake-grafana.ts +186 -0
  285. package/src/api/folders.ts +265 -0
  286. package/src/api/library-panels.ts +101 -0
  287. package/src/build.test.ts +210 -5
  288. package/src/build.ts +180 -43
  289. package/src/catalog.ts +86 -2
  290. package/src/codegen/docs.ts +42 -9
  291. package/src/codegen/schema-types.ts +16 -6
  292. package/src/composites/agent-dashboard.ts +6 -3
  293. package/src/composites/catalog.test.ts +2 -2
  294. package/src/composites/catalog.ts +82 -0
  295. package/src/composites/composites.test.ts +73 -9
  296. package/src/composites/index.ts +4 -2
  297. package/src/composites/queries.e2e.test.ts +374 -0
  298. package/src/composites/red-dashboard.ts +83 -12
  299. package/src/composites/shared.ts +11 -0
  300. package/src/composites/slo-alert-rules.ts +149 -0
  301. package/src/config.test.ts +61 -0
  302. package/src/config.ts +138 -0
  303. package/src/contact-point-secrets.ts +63 -0
  304. package/src/dashboard.ts +16 -2
  305. package/src/datasource-refs.test.ts +56 -0
  306. package/src/datasource-refs.ts +189 -0
  307. package/src/datasource.ts +52 -0
  308. package/src/deep-observe-hooks.ts +252 -0
  309. package/src/deep-observe.test.ts +265 -0
  310. package/src/deep-observe.ts +164 -0
  311. package/src/describe-resources.test.ts +255 -0
  312. package/src/describe-resources.ts +209 -0
  313. package/src/detect.ts +79 -2
  314. package/src/export-resources.test.ts +129 -0
  315. package/src/export-resources.ts +84 -0
  316. package/src/generated/lexicon-grafana.json +135 -0
  317. package/src/import/alerting-roundtrip.test.ts +161 -0
  318. package/src/import/alerting.ts +500 -0
  319. package/src/import/edits.ts +95 -0
  320. package/src/import/embedded.test.ts +45 -0
  321. package/src/import/embedded.ts +47 -0
  322. package/src/import/generated-types.e2e.test.ts +118 -0
  323. package/src/import/generator.test.ts +210 -0
  324. package/src/import/generator.ts +462 -0
  325. package/src/import/live-export.ts +264 -0
  326. package/src/import/mappings.ts +264 -0
  327. package/src/import/model.ts +110 -0
  328. package/src/import/normalize-alerting.ts +101 -0
  329. package/src/import/normalize.test.ts +117 -0
  330. package/src/import/normalize.ts +364 -0
  331. package/src/import/parser.test.ts +440 -0
  332. package/src/import/parser.ts +1236 -0
  333. package/src/import/report.ts +62 -0
  334. package/src/import/roundtrip.test.ts +468 -0
  335. package/src/import/testdata/fixtures.ts +129 -0
  336. package/src/import/v2.test.ts +292 -0
  337. package/src/import/v2.ts +803 -0
  338. package/src/import.e2e.test.ts +31 -84
  339. package/src/import.test.ts +9 -3
  340. package/src/index.ts +190 -5
  341. package/src/k8s.test.ts +98 -0
  342. package/src/k8s.ts +197 -0
  343. package/src/lint/audit-catalog.ts +60 -3
  344. package/src/lint/core-lint.test.ts +43 -0
  345. package/src/lint/post-synth/graf101.ts +3 -3
  346. package/src/lint/post-synth/graf102.ts +1 -1
  347. package/src/lint/post-synth/graf107.ts +1 -1
  348. package/src/lint/post-synth/graf108.ts +17 -0
  349. package/src/lint/post-synth/graf109.ts +17 -0
  350. package/src/lint/post-synth/graf111.ts +17 -0
  351. package/src/lint/post-synth/graf112.ts +17 -0
  352. package/src/lint/post-synth/graf113.ts +17 -0
  353. package/src/lint/post-synth/graf114.ts +17 -0
  354. package/src/lint/post-synth/graf115.ts +17 -0
  355. package/src/lint/post-synth/grafana-helpers.ts +15 -8
  356. package/src/lint/post-synth/index.ts +14 -0
  357. package/src/lint/post-synth/post-synth.test.ts +235 -11
  358. package/src/lint/rules/literal-secret.ts +60 -6
  359. package/src/lint/rules/rules.test.ts +27 -0
  360. package/src/lsp/completions.test.ts +5 -0
  361. package/src/lsp/hover.test.ts +7 -0
  362. package/src/lsp/hover.ts +8 -1
  363. package/src/members.ts +68 -0
  364. package/src/observe.e2e.test.ts +255 -0
  365. package/src/op/activities/grafana-apply.e2e.test.ts +175 -0
  366. package/src/op/activities/grafana-apply.test.ts +229 -0
  367. package/src/op/activities/grafana-apply.ts +171 -0
  368. package/src/op/activities/index.ts +8 -0
  369. package/src/op/builders.test.ts +40 -0
  370. package/src/op/builders.ts +34 -0
  371. package/src/ownership.ts +103 -0
  372. package/src/panel-options.ts +66 -0
  373. package/src/panels.test.ts +51 -0
  374. package/src/panels.ts +148 -10
  375. package/src/pin.ts +57 -6
  376. package/src/plugin.test.ts +5 -2
  377. package/src/plugin.ts +66 -0
  378. package/src/promql-check.test.ts +108 -0
  379. package/src/promql-check.ts +189 -0
  380. package/src/query.ts +13 -8
  381. package/src/schema/barchart.gen.ts +221 -0
  382. package/src/schema/bargauge.gen.ts +111 -0
  383. package/src/schema/canvas.gen.ts +176 -0
  384. package/src/schema/dashboard.gen.ts +206 -22
  385. package/src/schema/expr.gen.ts +618 -0
  386. package/src/schema/gauge.gen.ts +2 -1
  387. package/src/schema/geomap.gen.ts +112 -0
  388. package/src/schema/heatmap.gen.ts +2 -1
  389. package/src/schema/histogram.gen.ts +151 -0
  390. package/src/schema/index.ts +12 -0
  391. package/src/schema/logs.gen.ts +3 -1
  392. package/src/schema/loki.gen.ts +2 -1
  393. package/src/schema/nodegraph.gen.ts +58 -0
  394. package/src/schema/piechart.gen.ts +146 -0
  395. package/src/schema/prometheus.gen.ts +2 -1
  396. package/src/schema/stat.gen.ts +2 -1
  397. package/src/schema/statetimeline.gen.ts +143 -0
  398. package/src/schema/statushistory.gen.ts +132 -0
  399. package/src/schema/table.gen.ts +27 -4
  400. package/src/schema/tempo.gen.ts +2 -1
  401. package/src/schema/text.gen.ts +2 -1
  402. package/src/schema/timeseries.gen.ts +7 -2
  403. package/src/schema/trend.gen.ts +208 -0
  404. package/src/schema/xychart.gen.ts +167 -0
  405. package/src/schema-overlay.test.ts +282 -0
  406. package/src/schema-validate.ts +141 -50
  407. package/src/schema.test.ts +4 -2
  408. package/src/serializer.test.ts +13 -1
  409. package/src/serializer.ts +11 -3
  410. package/src/skill-defs.ts +17 -0
  411. package/src/skills/chant-grafana-alerting.md +49 -0
  412. package/src/skills/chant-grafana-provisioning.md +3 -3
  413. package/src/skills/chant-grafana.md +12 -6
  414. package/src/spec/fetch-cli.ts +3 -3
  415. package/src/spec/fetch-units-cli.ts +37 -0
  416. package/src/spec/overlay/canvas.overlay.json +142 -0
  417. package/src/spec/overlay/dashboard.overlay.json +378 -0
  418. package/src/spec/overlay/expr.overlay.json +14 -0
  419. package/src/spec/overlay/geomap.overlay.json +35 -0
  420. package/src/spec/overlay/logs.overlay.json +14 -0
  421. package/src/spec/overlay/nodegraph.overlay.json +31 -0
  422. package/src/spec/overlay/piechart.overlay.json +17 -0
  423. package/src/spec/overlay/table.overlay.json +84 -0
  424. package/src/spec/overlay/timeseries.overlay.json +21 -0
  425. package/src/spec/overlay/trend.overlay.json +17 -0
  426. package/src/spec/overlay/xychart.overlay.json +16 -0
  427. package/src/spec/overlay.ts +105 -0
  428. package/src/spec/schemas/barchart.jsonschema.json +419 -0
  429. package/src/spec/schemas/bargauge.jsonschema.json +224 -0
  430. package/src/spec/schemas/canvas.jsonschema.json +356 -0
  431. package/src/spec/schemas/dashboardv2.jsonschema.json +3387 -0
  432. package/src/spec/schemas/expr.jsonschema.json +987 -0
  433. package/src/spec/schemas/geomap.jsonschema.json +220 -0
  434. package/src/spec/schemas/histogram.jsonschema.json +308 -0
  435. package/src/spec/schemas/nodegraph.jsonschema.json +77 -0
  436. package/src/spec/schemas/piechart.jsonschema.json +275 -0
  437. package/src/spec/schemas/statetimeline.jsonschema.json +324 -0
  438. package/src/spec/schemas/statushistory.jsonschema.json +292 -0
  439. package/src/spec/schemas/trend.jsonschema.json +449 -0
  440. package/src/spec/schemas/xychart.jsonschema.json +428 -0
  441. package/src/spec/schemas.ts +22 -10
  442. package/src/spec/units.gen.ts +281 -0
  443. package/src/spec/units.ts +77 -0
  444. package/src/transformations.test.ts +65 -0
  445. package/src/transformations.ts +574 -0
  446. package/src/util.ts +8 -0
  447. package/src/validate-alerting.test.ts +255 -0
  448. package/src/validate-alerting.ts +428 -0
  449. package/src/validate-output.ts +332 -101
  450. package/src/validate.ts +14 -0
  451. package/src/variables.ts +7 -2
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Live export (#2946) against an in-memory Grafana: the IR goes through the
3
+ * importer's generator unchanged, and a dashboard exported from Grafana
4
+ * generates the same source as the same JSON given to `chant import`.
5
+ */
6
+
7
+ import { describe, expect, it } from "vitest";
8
+ import { readFileSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { exportResources } from "./export-resources";
11
+ import { fakeGrafana, liveDatasource, type FakeGrafanaState } from "./api/fake-grafana";
12
+ import { GrafanaGenerator } from "./import/generator";
13
+ import { parseGrafana } from "./import/parser";
14
+ import { grafanaPlugin } from "./plugin";
15
+ import { datasourceProps } from "./import/live-export";
16
+
17
+ const CONFIG = { grafana: { profiles: { prod: { url: "http://grafana.test" } } } };
18
+
19
+ const board = {
20
+ title: "Checkout",
21
+ tags: ["shop"],
22
+ graphTooltip: 1,
23
+ schemaVersion: 41,
24
+ panels: [
25
+ {
26
+ type: "timeseries",
27
+ id: 1,
28
+ title: "Requests",
29
+ gridPos: { h: 8, w: 12, x: 0, y: 0 },
30
+ datasource: { type: "prometheus", uid: "prom" },
31
+ targets: [{ refId: "A", expr: "sum(rate(http_requests_total[5m]))", datasource: { type: "prometheus", uid: "prom" } }],
32
+ },
33
+ ],
34
+ };
35
+
36
+ function state(overrides: Partial<FakeGrafanaState> = {}): FakeGrafanaState {
37
+ return {
38
+ api: "v1",
39
+ dashboards: {
40
+ checkout: { spec: board, annotations: { "grafana.app/managedBy": "classic-file-provisioning", "grafana.app/managerId": "chant" } },
41
+ "hand-made": { spec: { title: "Hand made", panels: [] } },
42
+ },
43
+ datasources: {
44
+ prom: liveDatasource({ name: "Prom", type: "prometheus", uid: "prom", url: "http://prometheus:9090", isDefault: true, secure: ["basicAuthPassword"] }),
45
+ },
46
+ ...overrides,
47
+ };
48
+ }
49
+
50
+ const exportFrom = (s: FakeGrafanaState, opts: Partial<Parameters<typeof exportResources>[0]> = {}) =>
51
+ exportResources({ environment: "prod", config: CONFIG, env: {}, http: fakeGrafana(s), ...opts });
52
+
53
+ describe("exportResources", () => {
54
+ it("is wired on the plugin", () => {
55
+ expect(typeof grafanaPlugin.exportResources).toBe("function");
56
+ });
57
+
58
+ it("feeds the importer's generator unchanged, and generates what chant import writes for the same JSON", async () => {
59
+ const ir = await exportFrom(state(), { selector: { type: "Grafana::Dashboard", name: "checkout" } });
60
+ const exported = new GrafanaGenerator().generate(ir);
61
+ const imported = new GrafanaGenerator().generate(parseGrafana(JSON.stringify({ ...board, uid: "checkout" })));
62
+ expect(exported).toEqual(imported);
63
+ expect(exported.map((f) => f.path)).toEqual(expect.arrayContaining([expect.stringMatching(/^checkout\/dashboard\.ts$/)]));
64
+ });
65
+
66
+ it("exports every dashboard and datasource, secrets as key names only", async () => {
67
+ const ir = await exportFrom(state());
68
+ expect(ir.resources.map((r) => `${r.type}:${r.logicalId}`).sort()).toEqual(["Grafana::Dashboard:checkout", "Grafana::Dashboard:handMade", "Grafana::Provisioning:datasources"]);
69
+ const files = new GrafanaGenerator().generate(ir);
70
+ const ds = files.find((f) => f.path === "datasources.ts")!.content;
71
+ expect(ds).toContain('basicAuthPassword: "[REDACTED]"');
72
+ expect(ir.warnings?.join("\n")).toMatch(/\[REDACTED\]/);
73
+ });
74
+
75
+ it("a dashboard refers to a datasource exported beside it by uid, rather than declaring it again", async () => {
76
+ const files = new GrafanaGenerator().generate(await exportFrom(state()));
77
+ const dashboardDatasources = files.find((f) => f.path === "checkout/datasources.ts")!.content;
78
+ expect(dashboardDatasources).not.toContain("ExternalDatasource");
79
+ expect(dashboardDatasources).toMatch(/const prom: DatasourceRef<"prometheus"> = \{ type: "prometheus", uid: "prom" \}/);
80
+ expect(files.find((f) => f.path === "datasources.ts")!.content).toContain("new Datasource(");
81
+ });
82
+
83
+ it("owned keeps only chant's dashboards and no datasources", async () => {
84
+ const ir = await exportFrom(state(), { owned: true });
85
+ expect(ir.resources.map((r) => r.logicalId)).toEqual(["checkout"]);
86
+ expect(ir.warnings?.join("\n")).toMatch(/datasources are not exported with --owned/);
87
+ });
88
+
89
+ it("reads the legacy API on a server without dashboard.grafana.app", async () => {
90
+ const ir = await exportFrom(state({ api: "legacy" }), { selector: { name: "checkout" } });
91
+ expect(ir.resources.map((r) => r.logicalId)).toEqual(["checkout"]);
92
+ });
93
+
94
+ it("leaves out a v2-stored dashboard the server does not serve at v2, and says so", async () => {
95
+ const s = state();
96
+ s.dashboards.checkout.storedVersion = "v2beta1";
97
+ const ir = await exportFrom(s, { selector: { type: "Grafana::Dashboard" } });
98
+ expect(ir.resources.map((r) => r.logicalId)).toEqual(["handMade"]);
99
+ expect(ir.warnings?.join("\n")).toMatch(/checkout is not exported: it is stored as a v2beta1 dashboard .* lossy down-conversion/);
100
+ });
101
+
102
+ it("exports a v2-stored dashboard through its v2 read, as chant import of the same v2 JSON does (#2947)", async () => {
103
+ const fixture = JSON.parse(readFileSync(join(import.meta.dirname, "..", "test", "fixtures", "exports", "grafana-13.2.2", "tabs.v2-resource.json"), "utf-8")) as { spec: Record<string, unknown> };
104
+ const s = state();
105
+ s.dashboards.checkout = { ...s.dashboards.checkout, storedVersion: "v2", v2: fixture.spec };
106
+ const ir = await exportFrom(s, { selector: { type: "Grafana::Dashboard", name: "checkout" } });
107
+ expect(ir.resources.map((r) => r.logicalId)).toEqual(["checkoutTabs"]);
108
+ expect(ir.warnings).toContainEqual(expect.stringMatching(/^dashboard checkout is stored as v2 and exported in the classic model: layout: the dashboard's tabs "Overview" and "Details" become expanded rows/));
109
+ const exported = new GrafanaGenerator().generate(ir);
110
+ const imported = new GrafanaGenerator().generate(parseGrafana(JSON.stringify({ ...fixture, metadata: { name: "checkout" } })));
111
+ expect(exported).toEqual(imported);
112
+ });
113
+
114
+ it("fails loudly when there is no Grafana to read", async () => {
115
+ await expect(exportResources({ environment: "staging", config: CONFIG, env: {} })).rejects.toThrow(/GRAFANA_URL/);
116
+ });
117
+ });
118
+
119
+ describe("datasourceProps", () => {
120
+ const live = liveDatasource({ name: "Prom", type: "prometheus", uid: "prom", url: "http://p:9090", secure: ["password"] });
121
+
122
+ it("keeps what somebody set, and names secrets without their values", () => {
123
+ expect(datasourceProps(live)).toEqual({ name: "Prom", type: "prometheus", uid: "prom", url: "http://p:9090", secureJsonData: { password: "[REDACTED]" } });
124
+ });
125
+
126
+ it("keeps the API's defaults under verbatim, and editable from readOnly", () => {
127
+ expect(datasourceProps({ ...live, readOnly: false }, { keepDefaults: true })).toMatchObject({ access: "proxy", isDefault: false, editable: true });
128
+ });
129
+ });
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Live export for grafana (#2946): `chant import --from <env>` writes the
3
+ * environment's dashboards and datasources as chant TypeScript.
4
+ *
5
+ * All I/O is here; the mapping is pure in ./import/live-export.ts, and the
6
+ * IR it builds is the importer's own (#2945), so a dashboard exported from
7
+ * Grafana and the same dashboard's JSON given to `chant import` generate the
8
+ * same source through the same `GrafanaGenerator`.
9
+ *
10
+ * - Every dashboard in the organisation is listed over the API the thin
11
+ * read uses (`/apis/dashboard.grafana.app`, or `/api/search` plus
12
+ * `/api/dashboards/uid` on Grafana 11). A dashboard stored as v2 is read at
13
+ * v2 and exported through its classic form, with a warning for each thing
14
+ * the classic form cannot hold (#2947).
15
+ * - Every datasource is read by uid, for `secureJsonFields`; secrets come
16
+ * back as key names only (see `datasourceProps`).
17
+ * - `selector.type` is `Grafana::Dashboard` or `Grafana::Datasource`;
18
+ * `selector.name` is a uid.
19
+ * - `owned` keeps only dashboards that are chant's (./ownership.ts), and
20
+ * leaves datasources out, since their ownership cannot be read. There is no
21
+ * project here to name its providers, so a file-provisioned dashboard is
22
+ * chant's when its provider is the default one, `chant`.
23
+ * - `verbatim` keeps datasource keys at the API's "not set" values. A
24
+ * dashboard is always planned by the importer, which leaves out keys at
25
+ * Grafana's defaults either way.
26
+ */
27
+
28
+ import type { ExportedTemplate, ResourceSelector } from "@intentius/chant/lexicon";
29
+ import { bindGrafana, type BindOptions } from "./api/bind";
30
+ import { classicDashboardOf, listDashboards } from "./api/dashboards";
31
+ import { listDatasources, readDatasource } from "./api/datasources";
32
+ import { DASHBOARD_TYPE } from "./dashboard";
33
+ import { DATASOURCE_TYPE } from "./datasource";
34
+ import { exportTemplate } from "./import/live-export";
35
+ import { DEFAULT_PROVIDER_NAME, dashboardOwnership } from "./ownership";
36
+
37
+ type Json = Record<string, unknown>;
38
+
39
+ export interface GrafanaExportOptions extends Omit<BindOptions, "environment"> {
40
+ environment: string;
41
+ stack?: string;
42
+ region?: string;
43
+ selector?: ResourceSelector;
44
+ owned?: boolean;
45
+ verbatim?: boolean;
46
+ }
47
+
48
+ export async function exportResources(options: GrafanaExportOptions): Promise<ExportedTemplate> {
49
+ const client = await bindGrafana({ ...options });
50
+ const type = options.selector?.type;
51
+ const name = options.selector?.name;
52
+ const warnings: string[] = [];
53
+
54
+ const dashboards: Array<{ json: Json }> = [];
55
+ if (type === undefined || type === DASHBOARD_TYPE) {
56
+ const providers = new Set([DEFAULT_PROVIDER_NAME]);
57
+ for (const live of await listDashboards(client)) {
58
+ if (name !== undefined && live.uid !== name) continue;
59
+ if (options.owned && dashboardOwnership(live, providers).ownership !== "owned") continue;
60
+ const classic = classicDashboardOf(live);
61
+ if ("unsupported" in classic) {
62
+ warnings.push(`dashboard ${live.uid} is not exported: ${classic.unsupported}`);
63
+ continue;
64
+ }
65
+ for (const w of classic.warnings ?? []) warnings.push(`dashboard ${live.uid} is stored as v2 and exported in the classic model: ${w}`);
66
+ dashboards.push({ json: classic.json });
67
+ }
68
+ }
69
+
70
+ const datasources: Json[] = [];
71
+ if ((type === undefined || type === DATASOURCE_TYPE) && !options.owned) {
72
+ for (const summary of await listDatasources(client)) {
73
+ const uid = typeof summary.uid === "string" ? summary.uid : undefined;
74
+ if (!uid || (name !== undefined && uid !== name)) continue;
75
+ const read = await readDatasource(client, uid);
76
+ if ("present" in read) datasources.push(read.present);
77
+ }
78
+ } else if (options.owned && (type === undefined || type === DATASOURCE_TYPE)) {
79
+ warnings.push("datasources are not exported with --owned: Grafana's datasource API carries no ownership marker");
80
+ }
81
+
82
+ const ir = exportTemplate({ dashboards, datasources, keepDefaults: options.verbatim });
83
+ return { ...ir, warnings: [...warnings, ...(ir.warnings ?? [])] };
84
+ }
@@ -1,9 +1,54 @@
1
1
  {
2
+ "AlertListPanel": {
3
+ "resourceType": "Grafana::Panel::alertlist",
4
+ "kind": "resource",
5
+ "lexicon": "grafana"
6
+ },
7
+ "AlertQuery": {
8
+ "resourceType": "Grafana::AlertQuery",
9
+ "kind": "resource",
10
+ "lexicon": "grafana"
11
+ },
12
+ "AlertRule": {
13
+ "resourceType": "Grafana::AlertRule",
14
+ "kind": "resource",
15
+ "lexicon": "grafana"
16
+ },
17
+ "AlertRuleGroup": {
18
+ "resourceType": "Grafana::AlertRuleGroup",
19
+ "kind": "resource",
20
+ "lexicon": "grafana"
21
+ },
22
+ "BarChartPanel": {
23
+ "resourceType": "Grafana::Panel::barchart",
24
+ "kind": "resource",
25
+ "lexicon": "grafana"
26
+ },
27
+ "BarGaugePanel": {
28
+ "resourceType": "Grafana::Panel::bargauge",
29
+ "kind": "resource",
30
+ "lexicon": "grafana"
31
+ },
32
+ "CanvasPanel": {
33
+ "resourceType": "Grafana::Panel::canvas",
34
+ "kind": "resource",
35
+ "lexicon": "grafana"
36
+ },
37
+ "ClassicConditionsExpression": {
38
+ "resourceType": "Grafana::Expression::classic_conditions",
39
+ "kind": "resource",
40
+ "lexicon": "grafana"
41
+ },
2
42
  "ConstantVariable": {
3
43
  "resourceType": "Grafana::Variable::constant",
4
44
  "kind": "resource",
5
45
  "lexicon": "grafana"
6
46
  },
47
+ "ContactPoint": {
48
+ "resourceType": "Grafana::ContactPoint",
49
+ "kind": "resource",
50
+ "lexicon": "grafana"
51
+ },
7
52
  "CustomVariable": {
8
53
  "resourceType": "Grafana::Variable::custom",
9
54
  "kind": "resource",
@@ -29,16 +74,36 @@
29
74
  "kind": "resource",
30
75
  "lexicon": "grafana"
31
76
  },
77
+ "ExternalDatasource": {
78
+ "resourceType": "Grafana::ExternalDatasource",
79
+ "kind": "resource",
80
+ "lexicon": "grafana"
81
+ },
82
+ "FlameGraphPanel": {
83
+ "resourceType": "Grafana::Panel::flamegraph",
84
+ "kind": "resource",
85
+ "lexicon": "grafana"
86
+ },
32
87
  "GaugePanel": {
33
88
  "resourceType": "Grafana::Panel::gauge",
34
89
  "kind": "resource",
35
90
  "lexicon": "grafana"
36
91
  },
92
+ "GeomapPanel": {
93
+ "resourceType": "Grafana::Panel::geomap",
94
+ "kind": "resource",
95
+ "lexicon": "grafana"
96
+ },
37
97
  "HeatmapPanel": {
38
98
  "resourceType": "Grafana::Panel::heatmap",
39
99
  "kind": "resource",
40
100
  "lexicon": "grafana"
41
101
  },
102
+ "HistogramPanel": {
103
+ "resourceType": "Grafana::Panel::histogram",
104
+ "kind": "resource",
105
+ "lexicon": "grafana"
106
+ },
42
107
  "IntervalVariable": {
43
108
  "resourceType": "Grafana::Variable::interval",
44
109
  "kind": "resource",
@@ -54,6 +119,36 @@
54
119
  "kind": "resource",
55
120
  "lexicon": "grafana"
56
121
  },
122
+ "MathExpression": {
123
+ "resourceType": "Grafana::Expression::math",
124
+ "kind": "resource",
125
+ "lexicon": "grafana"
126
+ },
127
+ "MuteTiming": {
128
+ "resourceType": "Grafana::MuteTiming",
129
+ "kind": "resource",
130
+ "lexicon": "grafana"
131
+ },
132
+ "NodeGraphPanel": {
133
+ "resourceType": "Grafana::Panel::nodeGraph",
134
+ "kind": "resource",
135
+ "lexicon": "grafana"
136
+ },
137
+ "NotificationPolicy": {
138
+ "resourceType": "Grafana::NotificationPolicy",
139
+ "kind": "resource",
140
+ "lexicon": "grafana"
141
+ },
142
+ "NotificationTemplate": {
143
+ "resourceType": "Grafana::NotificationTemplate",
144
+ "kind": "resource",
145
+ "lexicon": "grafana"
146
+ },
147
+ "PieChartPanel": {
148
+ "resourceType": "Grafana::Panel::piechart",
149
+ "kind": "resource",
150
+ "lexicon": "grafana"
151
+ },
57
152
  "PromQuery": {
58
153
  "resourceType": "Grafana::Query::prometheus",
59
154
  "kind": "resource",
@@ -64,16 +159,41 @@
64
159
  "kind": "resource",
65
160
  "lexicon": "grafana"
66
161
  },
162
+ "ReduceExpression": {
163
+ "resourceType": "Grafana::Expression::reduce",
164
+ "kind": "resource",
165
+ "lexicon": "grafana"
166
+ },
167
+ "ResampleExpression": {
168
+ "resourceType": "Grafana::Expression::resample",
169
+ "kind": "resource",
170
+ "lexicon": "grafana"
171
+ },
67
172
  "Row": {
68
173
  "resourceType": "Grafana::Row",
69
174
  "kind": "resource",
70
175
  "lexicon": "grafana"
71
176
  },
177
+ "SqlExpression": {
178
+ "resourceType": "Grafana::Expression::sql",
179
+ "kind": "resource",
180
+ "lexicon": "grafana"
181
+ },
182
+ "StateTimelinePanel": {
183
+ "resourceType": "Grafana::Panel::state-timeline",
184
+ "kind": "resource",
185
+ "lexicon": "grafana"
186
+ },
72
187
  "StatPanel": {
73
188
  "resourceType": "Grafana::Panel::stat",
74
189
  "kind": "resource",
75
190
  "lexicon": "grafana"
76
191
  },
192
+ "StatusHistoryPanel": {
193
+ "resourceType": "Grafana::Panel::status-history",
194
+ "kind": "resource",
195
+ "lexicon": "grafana"
196
+ },
77
197
  "TablePanel": {
78
198
  "resourceType": "Grafana::Panel::table",
79
199
  "kind": "resource",
@@ -94,6 +214,11 @@
94
214
  "kind": "resource",
95
215
  "lexicon": "grafana"
96
216
  },
217
+ "ThresholdExpression": {
218
+ "resourceType": "Grafana::Expression::threshold",
219
+ "kind": "resource",
220
+ "lexicon": "grafana"
221
+ },
97
222
  "TimeSeriesPanel": {
98
223
  "resourceType": "Grafana::Panel::timeseries",
99
224
  "kind": "resource",
@@ -103,5 +228,15 @@
103
228
  "resourceType": "Grafana::Panel::traces",
104
229
  "kind": "resource",
105
230
  "lexicon": "grafana"
231
+ },
232
+ "TrendPanel": {
233
+ "resourceType": "Grafana::Panel::trend",
234
+ "kind": "resource",
235
+ "lexicon": "grafana"
236
+ },
237
+ "XYChartPanel": {
238
+ "resourceType": "Grafana::Panel::xychart",
239
+ "kind": "resource",
240
+ "lexicon": "grafana"
106
241
  }
107
242
  }
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Round trips of alerting provisioning files through `chant import`.
3
+ *
4
+ * Provisioning YAML -> TypeScript -> `chant build` -> provisioning YAML must
5
+ * give back the same file for every fixture: Grafana 12.4.11 and 13.2.2
6
+ * exports, and files other projects provision Grafana from (provenance in
7
+ * test/fixtures/alerting/README.md), and what the alerting example builds.
8
+ * "The same" means equal after `normalizeAlerting` once the importer's
9
+ * edits are applied to the source. The generated source must lint clean,
10
+ * and the rebuilt file must pass GRAF108 and GRAF111-GRAF114 without
11
+ * errors. generated-types.e2e.test.ts type-checks the generated source;
12
+ * alerting.e2e.test.ts provisions the rebuilt exports into Grafana.
13
+ */
14
+
15
+ import { describe, expect, test } from "vitest";
16
+ import { join } from "path";
17
+ import { load } from "js-yaml";
18
+ import { build } from "@intentius/chant/build";
19
+ import type { SerializerResult } from "@intentius/chant/serializer";
20
+ import { lintCommand } from "@intentius/chant/cli/commands/lint";
21
+ import { importCommand } from "@intentius/chant/cli/commands/import";
22
+ import { grafanaSerializer } from "../serializer";
23
+ import { ALERTING_FILE } from "../alerting-build";
24
+ import { buildGrafana } from "../build";
25
+ import { validateGrafanaOutput, type GrafanaIssue } from "../validate-output";
26
+ import { GrafanaParser, type AlertingResourceMetadata } from "./parser";
27
+ import { GrafanaGenerator } from "./generator";
28
+ import { applyEdits } from "./edits";
29
+ import { normalizeAlerting } from "./normalize-alerting";
30
+ import { ALERTING, exampleOutputs, fixturesDir, projectDir, read, removeDir, repoRoot, writeFiles } from "./testdata/fixtures";
31
+
32
+ type Json = Record<string, unknown>;
33
+
34
+ interface Imported {
35
+ source: string;
36
+ warnings: string[];
37
+ expected: Json;
38
+ rebuilt: Json;
39
+ issues: GrafanaIssue[];
40
+ lint: { errorCount: number; warningCount: number; output: string };
41
+ }
42
+
43
+ /** Several files -> IR -> TypeScript in one project -> `chant build`, `chant lint`, and the checks. */
44
+ async function importAndBuild(contents: string[]): Promise<Imported> {
45
+ const dir = projectDir();
46
+ try {
47
+ const srcDir = join(dir, "src");
48
+ const warnings: string[] = [];
49
+ const sources: string[] = [];
50
+ const expected: Json = { apiVersion: 1 };
51
+ contents.forEach((content, i) => {
52
+ const ir = new GrafanaParser().parse(content);
53
+ warnings.push(...(ir.warnings ?? []));
54
+ const generated = new GrafanaGenerator().generate(ir).map((f) => ({ ...f, path: contents.length > 1 ? `f${i}/${f.path}` : f.path }));
55
+ writeFiles(srcDir, generated);
56
+ sources.push(...generated.map((f) => `// ${f.path}\n${f.content}`));
57
+ const meta = ir.resources[0]!.metadata as unknown as AlertingResourceMetadata;
58
+ for (const [k, v] of Object.entries(applyEdits(meta.source, meta.edits))) {
59
+ if (Array.isArray(v)) expected[k] = [...((expected[k] as unknown[]) ?? []), ...v];
60
+ }
61
+ });
62
+ const result = await build(srcDir, [grafanaSerializer]);
63
+ expect(result.errors).toEqual([]);
64
+ const text = (result.outputs.get("grafana") as SerializerResult | undefined)?.files?.[ALERTING_FILE];
65
+ const built = buildGrafana(new Map([...result.entities].filter(([, e]) => e.lexicon === "grafana")));
66
+ const issues = validateGrafanaOutput({
67
+ dashboards: [],
68
+ datasources: built.datasources,
69
+ externalDatasources: built.externalDatasources,
70
+ alerting: built.alerting ? [{ json: built.alerting as unknown as Json }] : [],
71
+ });
72
+ const lint = await lintCommand({ path: srcDir, format: "stylish" });
73
+ return {
74
+ source: sources.join("\n"),
75
+ warnings,
76
+ expected,
77
+ rebuilt: text === undefined ? { apiVersion: 1 } : (load(text) as Json),
78
+ issues,
79
+ lint: { errorCount: lint.errorCount, warningCount: lint.warningCount, output: lint.output },
80
+ };
81
+ } finally {
82
+ removeDir(dir);
83
+ }
84
+ }
85
+
86
+ async function expectRoundTrip(...contents: string[]): Promise<Imported> {
87
+ const out = await importAndBuild(contents);
88
+ expect(normalizeAlerting(out.rebuilt)).toEqual(normalizeAlerting(out.expected));
89
+ expect(out.lint.errorCount + out.lint.warningCount, out.lint.output).toBe(0);
90
+ expect(out.issues.filter((i) => i.severity === "error")).toEqual([]);
91
+ return out;
92
+ }
93
+
94
+ describe("alerting provisioning YAML -> TypeScript -> alerting provisioning YAML", () => {
95
+ for (const file of ALERTING) {
96
+ test(file, async () => {
97
+ await expectRoundTrip(read(file));
98
+ });
99
+ }
100
+
101
+ test("each Grafana version's exports, imported into one project, reference each other and pass every check", async () => {
102
+ for (const v of ["grafana-12.4.11", "grafana-13.2.2"]) {
103
+ const files = ALERTING.filter((f) => f.startsWith(`alerting/${v}/`)).map((f) => read(f));
104
+ const out = await expectRoundTrip(...files);
105
+ // With the contact points and mute timings declared, GRAF113 checks every reference and finds them all.
106
+ expect(out.issues.filter((i) => i.code === "GRAF113")).toEqual([]);
107
+ expect(out.issues.filter((i) => i.code === "GRAF112")).toEqual([]);
108
+ }
109
+ });
110
+
111
+ test("a project's files, imported together, pass every check", async () => {
112
+ const marin = ALERTING.filter((f) => f.includes("/marin.")).map((f) => read(f));
113
+ const out = await expectRoundTrip(...marin);
114
+ expect(out.issues.filter((i) => i.code === "GRAF113")).toEqual([]);
115
+ });
116
+
117
+ test("the typed classes carry each server-side expression, and a datasource query keeps its model", async () => {
118
+ const out = await importAndBuild([read("alerting/grafana-13.2.2/alert-rules.yaml")]);
119
+ for (const cls of ["ReduceExpression", "ThresholdExpression", "ResampleExpression", "MathExpression", "ClassicConditionsExpression"]) {
120
+ expect(out.source).toContain(`new ${cls}(`);
121
+ }
122
+ expect(out.source).toContain('const prom = new ExternalDatasource({ type: "prometheus", uid: "prom" });');
123
+ expect(out.source).toContain("new AlertQuery({ datasource: prom, model:");
124
+ // The recovery threshold is carried; the editor's operator/query/reducer on a threshold are not needed.
125
+ expect(out.source).toContain('unloadEvaluator: { params: [0.03], type: "lt" }');
126
+ expect(out.warnings).toEqual([]);
127
+ });
128
+
129
+ test("what cannot be carried, or had to be guessed, is a warning", async () => {
130
+ const proto = await importAndBuild([read("alerting/community/proto-fleet.proto-fleet-rules.yaml")]);
131
+ expect(proto.warnings).toContainEqual(expect.stringContaining('the plugin type of "protofleet-timescaledb"'));
132
+ expect(proto.warnings).toContainEqual(expect.stringContaining("2 rule deletions (deleteRules)"));
133
+ expect(proto.source).toContain('datasource: "protofleet-timescaledb"');
134
+
135
+ const eth = await importAndBuild([read("alerting/community/eth-docker.disk_space.yml")]);
136
+ expect(eth.warnings).toContainEqual(expect.stringContaining('"PBFA97CFB590B2093"; the queries look like PromQL'));
137
+
138
+ const cps = await importAndBuild([read("alerting/grafana-13.2.2/contact-points.yaml")]);
139
+ expect(cps.warnings).toEqual([expect.stringContaining("url as $__env{ONCALL_SLACK_URL} is read from the environment")]);
140
+ expect(cps.rebuilt).toMatchObject({ contactPoints: expect.arrayContaining([expect.objectContaining({ name: "oncall" })]) });
141
+ expect(JSON.stringify(cps.rebuilt)).not.toContain("[REDACTED]");
142
+ });
143
+
144
+ test("the alerting example's file round-trips", async () => {
145
+ const [, text] = (await exampleOutputs()).find(([f]) => f === `alerting/${ALERTING_FILE}`)!;
146
+ await expectRoundTrip(text);
147
+ });
148
+
149
+ test("chant import detects the file as grafana's, and a Prometheus rule file as prometheus's", async () => {
150
+ const dir = projectDir();
151
+ try {
152
+ const alerting = await importCommand({ templatePath: join(fixturesDir, "alerting", "grafana-13.2.2", "policies.yaml"), output: join(dir, "src"), force: true });
153
+ expect(alerting.lexicon).toBe("grafana");
154
+ expect(alerting.generatedFiles).toEqual(["notifications.ts"]);
155
+ const rules = await importCommand({ templatePath: join(repoRoot, "lexicons/prometheus/src/import/testdata/rules-full.yml"), output: join(dir, "rules"), force: true });
156
+ expect(rules.lexicon).toBe("prometheus");
157
+ } finally {
158
+ removeDir(dir);
159
+ }
160
+ });
161
+ });