@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,229 @@
1
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ import { afterAll, describe, expect, test } from "vitest";
5
+ import { describeApplyConformance } from "@intentius/chant-test-utils";
6
+ import { normalizeApply } from "@intentius/chant/apply";
7
+ import { grafanaApply, readBuiltDashboards, resolveMarker, toApplyResult, type GrafanaApplyDeps } from "./grafana-apply";
8
+ import { emptyGrafana, writableGrafana, type WritableGrafanaState } from "../../api/fake-grafana-writes";
9
+ import type { GrafanaHttp } from "../../api/client";
10
+
11
+ type Json = Record<string, unknown>;
12
+
13
+ const dirs: string[] = [];
14
+ afterAll(() => {
15
+ for (const d of dirs) rmSync(d, { recursive: true, force: true });
16
+ });
17
+
18
+ const OURS = { "app.kubernetes.io/managed-by": "chant", "chant.intentius.io/stack": "shop", "chant.intentius.io/env": "prod" };
19
+
20
+ function dashboard(uid: string, extra: Json = {}): Json {
21
+ return { uid, title: uid, schemaVersion: 41, panels: [{ id: 1, type: "stat", title: "a", gridPos: { x: 0, y: 0, w: 6, h: 4 } }], ...extra };
22
+ }
23
+
24
+ /** Write a build output the way `chant build -o <dir>/grafana.json` does: the index, and the dashboard files beside it. */
25
+ function buildOutput(dashboards: Array<{ json: Json; folder?: string }>, opts: { combined?: boolean } = {}): string {
26
+ const dir = mkdtempSync(join(tmpdir(), "chant-grafana-apply-"));
27
+ dirs.push(dir);
28
+ const entries = dashboards.map(({ json, folder }) => {
29
+ const file = folder ? `dashboards/${folder}/${json.uid}.json` : `dashboards/${json.uid}.json`;
30
+ mkdirSync(dirname(join(dir, file)), { recursive: true });
31
+ writeFileSync(join(dir, file), JSON.stringify(json));
32
+ return { uid: json.uid, title: json.title, ...(folder ? { folder } : {}), file };
33
+ });
34
+ const index = { grafanaSchema: "test", dashboards: entries, datasources: [], files: entries.map((e) => e.file) };
35
+ const indexPath = join(dir, "grafana.json");
36
+ writeFileSync(indexPath, JSON.stringify(opts.combined ? { grafana: index, otel: {} } : index));
37
+ return indexPath;
38
+ }
39
+
40
+ function deps(state: WritableGrafanaState, extra: Partial<GrafanaApplyDeps> = {}, calls: string[] = []): GrafanaApplyDeps {
41
+ return {
42
+ http: writableGrafana(state, calls),
43
+ config: { grafana: { profiles: { test: { url: "http://grafana.test" } } }, ownership: { stack: "shop", env: "prod" } },
44
+ env: {},
45
+ ...extra,
46
+ };
47
+ }
48
+
49
+ describe("readBuiltDashboards", () => {
50
+ test("reads the index and the files it lists, with their folders", () => {
51
+ const indexPath = buildOutput([{ json: dashboard("a"), folder: "Team A" }, { json: dashboard("b") }]);
52
+ expect(readBuiltDashboards(indexPath)).toEqual([
53
+ { json: dashboard("a"), folder: "Team A" },
54
+ { json: dashboard("b") },
55
+ ]);
56
+ });
57
+
58
+ test("reads the grafana key of a combined multi-lexicon output", () => {
59
+ expect(readBuiltDashboards(buildOutput([{ json: dashboard("a") }], { combined: true }))).toEqual([{ json: dashboard("a") }]);
60
+ });
61
+
62
+ test("refuses a file that is not a grafana index", () => {
63
+ const dir = mkdtempSync(join(tmpdir(), "chant-grafana-apply-"));
64
+ dirs.push(dir);
65
+ writeFileSync(join(dir, "x.json"), "{}");
66
+ expect(() => readBuiltDashboards(join(dir, "x.json"))).toThrow(/not a grafana build index/);
67
+ });
68
+ });
69
+
70
+ describe("resolveMarker", () => {
71
+ test("arguments win over the project's ownership config", () => {
72
+ expect(resolveMarker({}, { ownership: { stack: "shop", env: "prod" } })).toEqual({ stack: "shop", env: "prod" });
73
+ expect(resolveMarker({ stack: "x", ownershipEnv: "dev" }, { ownership: { stack: "shop", env: "prod" } })).toEqual({ stack: "x", env: "dev" });
74
+ expect(resolveMarker({}, { ownership: { stack: "shop", enabled: false } })).toBeUndefined();
75
+ expect(resolveMarker({}, undefined)).toBeUndefined();
76
+ });
77
+
78
+ test("an env that is a build parameter reference needs ownershipEnv", () => {
79
+ expect(() => resolveMarker({}, { ownership: { stack: "shop", env: { param: "env" } } })).toThrow(/pass ownershipEnv/);
80
+ expect(resolveMarker({ ownershipEnv: "prod" }, { ownership: { stack: "shop", env: { param: "env" } } })).toEqual({ stack: "shop", env: "prod" });
81
+ });
82
+ });
83
+
84
+ describe("grafanaApply", () => {
85
+ test("binds grafana.profiles.<environment> and applies", async () => {
86
+ const state = emptyGrafana("v1");
87
+ const out = await grafanaApply({ indexPath: buildOutput([{ json: dashboard("a"), folder: "Team A" }]), environment: "test" }, undefined, deps(state));
88
+ expect(out.target).toBe("grafana.profiles.test");
89
+ expect(out.applied.map((a) => `${a.action} ${a.kind}/${a.name}`)).toEqual(["created Folder/team-a", "created Dashboard/a"]);
90
+ expect(state.dashboards.a.labels).toEqual(OURS);
91
+ });
92
+
93
+ test("no Grafana to bind is no-binding for every resource, with nothing sent", async () => {
94
+ const calls: string[] = [];
95
+ const out = await grafanaApply({ indexPath: buildOutput([{ json: dashboard("a"), folder: "Team A" }]), environment: "nowhere" }, undefined, deps(emptyGrafana("v1"), {}, calls));
96
+ expect(calls).toEqual([]);
97
+ expect(normalizeApply(toApplyResult(out)).notAttempted.map((n) => `${n.kind}/${n.name}:${n.reason}`)).toEqual(["Folder/team-a:no-binding", "Dashboard/a:no-binding"]);
98
+ });
99
+
100
+ test("a profile whose token variable is unset is no-credentials", async () => {
101
+ const out = await grafanaApply(
102
+ { indexPath: buildOutput([{ json: dashboard("a") }]), environment: "test" },
103
+ undefined,
104
+ deps(emptyGrafana("v1"), { config: { grafana: { profiles: { test: { url: "http://grafana.test", token: { env: "UNSET_TOKEN" } } } } } }),
105
+ );
106
+ expect(out.notAttempted).toEqual([expect.objectContaining({ kind: "Dashboard", name: "a", reason: "no-credentials", detail: expect.stringContaining("UNSET_TOKEN") })]);
107
+ });
108
+
109
+ test("toApplyResult keeps the request path and reports an unconsidered kind as <kind>/*", async () => {
110
+ const state = emptyGrafana("legacy");
111
+ const out = await grafanaApply({ indexPath: buildOutput([{ json: dashboard("a") }]), environment: "test", prune: true }, undefined, deps(state));
112
+ const result = normalizeApply(toApplyResult(out));
113
+ expect(result.applied).toEqual([{ kind: "Dashboard", name: "a", action: "created", physicalId: "/api/dashboards/uid/a" }]);
114
+ expect(result.notAttempted.map((n) => `${n.kind}/${n.name}:${n.reason}`)).toEqual(["Dashboard/*:not-prunable", "Folder/*:not-prunable"]);
115
+ });
116
+ });
117
+
118
+ // ── The shared apply conformance suite (applying-conformance.mdx) ──────────
119
+
120
+ /** Apply one build against `state`, recording every delete path the transport was asked for. */
121
+ async function run(state: WritableGrafanaState, dashboards: Array<{ json: Json; folder?: string }>, opts: { prune?: boolean } = {}) {
122
+ const deletes: string[] = [];
123
+ const base = writableGrafana(state);
124
+ const http: GrafanaHttp = async (method, path, body) => {
125
+ if (method === "DELETE") deletes.push(path);
126
+ return base(method, path, body);
127
+ };
128
+ const outcome = await grafanaApply({ indexPath: buildOutput(dashboards), environment: "test", ...(opts.prune ? { prune: true } : {}) }, undefined, deps(state, { http }));
129
+ return { result: toApplyResult(outcome), deletes };
130
+ }
131
+
132
+ const LIB = { uid: "burn", name: "Burn", kind: 1, model: { type: "stat", title: "Burn" } };
133
+ const LIB_VAR = { uid: "region", name: "Region", kind: 2, model: {} };
134
+
135
+ describeApplyConformance({
136
+ lexicon: "grafana",
137
+ scenarios: [
138
+ {
139
+ name: "a folder, a library panel and two dashboards on Grafana 13 (v1)",
140
+ plan: [
141
+ { kind: "Folder", name: "team-a" },
142
+ { kind: "LibraryPanel", name: "burn" },
143
+ { kind: "Dashboard", name: "api" },
144
+ { kind: "Dashboard", name: "home" },
145
+ ],
146
+ run: async () => (await run(emptyGrafana("v1"), [{ json: dashboard("api", { __elements: { burn: LIB } }), folder: "Team A" }, { json: dashboard("home") }])).result,
147
+ expectApplied: ["Folder/team-a", "LibraryPanel/burn", "Dashboard/api", "Dashboard/home"],
148
+ },
149
+ {
150
+ name: "a library variable in __elements beside a dashboard on Grafana 12.4 (v1beta1)",
151
+ plan: [
152
+ { kind: "Dashboard", name: "api" },
153
+ { kind: "LibraryElement", name: "region" },
154
+ ],
155
+ run: async () => (await run(emptyGrafana("v1beta1"), [{ json: dashboard("api", { __elements: { region: LIB_VAR } }) }])).result,
156
+ expectApplied: ["Dashboard/api"],
157
+ expectNotAttempted: ["LibraryElement/region"],
158
+ },
159
+ {
160
+ name: "a refused token",
161
+ plan: [
162
+ { kind: "Folder", name: "team-a" },
163
+ { kind: "Dashboard", name: "api" },
164
+ ],
165
+ run: async () => (await run({ ...emptyGrafana("v1"), status: 403 }, [{ json: dashboard("api"), folder: "Team A" }])).result,
166
+ expectNotAttempted: ["Folder/team-a", "Dashboard/api"],
167
+ },
168
+ {
169
+ name: "prune on Grafana 11, which has no marker channel",
170
+ plan: [{ kind: "Dashboard", name: "api" }],
171
+ run: async () => (await run(emptyGrafana("legacy"), [{ json: dashboard("api") }], { prune: true })).result,
172
+ expectApplied: ["Dashboard/api"],
173
+ expectNotAttempted: ["Dashboard/*", "Folder/*"],
174
+ },
175
+ ],
176
+ pruneScenarios: [
177
+ {
178
+ name: "an owned orphan dashboard beside one saved in the UI",
179
+ ownedOrphan: "/dashboards/orphan",
180
+ foreign: "/dashboards/foreign",
181
+ run: async () => {
182
+ const state = emptyGrafana("v1");
183
+ state.dashboards.orphan = { spec: { title: "o" }, labels: OURS, annotations: {} };
184
+ state.dashboards.foreign = { spec: { title: "f" }, labels: {}, annotations: {} };
185
+ return run(state, [{ json: dashboard("keep") }], { prune: true });
186
+ },
187
+ },
188
+ {
189
+ name: "an owned orphan dashboard beside another chant stack's",
190
+ ownedOrphan: "/dashboards/orphan",
191
+ foreign: "/dashboards/other-stack",
192
+ run: async () => {
193
+ const state = emptyGrafana("v1beta1");
194
+ state.dashboards.orphan = { spec: { title: "o" }, labels: OURS, annotations: {} };
195
+ state.dashboards["other-stack"] = { spec: { title: "x" }, labels: { ...OURS, "chant.intentius.io/stack": "billing" }, annotations: {} };
196
+ return run(state, [{ json: dashboard("keep") }], { prune: true });
197
+ },
198
+ },
199
+ {
200
+ name: "an owned orphan folder beside a foreign one",
201
+ ownedOrphan: "/folders/old-team",
202
+ foreign: "/folders/their-team",
203
+ run: async () => {
204
+ const state = emptyGrafana("v1");
205
+ state.folders["old-team"] = { spec: { title: "Old team" }, labels: OURS, annotations: {} };
206
+ state.folders["their-team"] = { spec: { title: "Their team" }, labels: {}, annotations: {} };
207
+ return run(state, [{ json: dashboard("keep"), folder: "Team A" }], { prune: true });
208
+ },
209
+ },
210
+ ],
211
+ idempotenceScenarios: [
212
+ {
213
+ name: "the same build applied twice on Grafana 13 (v1)",
214
+ run: async () => {
215
+ const state = emptyGrafana("v1");
216
+ const build = [{ json: dashboard("api", { __elements: { burn: LIB } }), folder: "Team A" }, { json: dashboard("home") }];
217
+ return { first: (await run(state, build)).result, second: (await run(state, build)).result };
218
+ },
219
+ },
220
+ {
221
+ name: "the same build applied twice on Grafana 11 (legacy API)",
222
+ run: async () => {
223
+ const state = emptyGrafana("legacy");
224
+ const build = [{ json: dashboard("api"), folder: "Team A" }];
225
+ return { first: (await run(state, build)).result, second: (await run(state, build)).result };
226
+ },
227
+ },
228
+ ],
229
+ });
@@ -0,0 +1,171 @@
1
+ /**
2
+ * `grafanaApply`, the Op activity for the Grafana API applier (#2948), and
3
+ * `toApplyResult`, its projection onto core's apply envelope (#1446).
4
+ *
5
+ * It reads a build's output (the index the serializer writes as its primary
6
+ * output, and the dashboard files beside it), binds the environment's
7
+ * Grafana the way observe and export do (`grafana.profiles.<env>`, else
8
+ * `GRAFANA_URL`; ../../api/bind.ts), and hands the plan to `applyGrafana`
9
+ * (../../api/apply.ts), which holds the API decisions.
10
+ *
11
+ * The ownership marker is the project's: `ownership.stack` and
12
+ * `ownership.env` from `chant.config.ts`, or `stack`/`ownershipEnv` passed
13
+ * in. Without a stack the apply still runs, stamping only the managed-by
14
+ * label, and a prune declines (as `not-prunable`) rather than delete
15
+ * another stack's dashboards.
16
+ */
17
+
18
+ import { readFileSync } from "node:fs";
19
+ import { dirname, join } from "node:path";
20
+ import type { ChantConfig } from "@intentius/chant/config";
21
+ import type { OwnershipMarker } from "@intentius/chant/ownership";
22
+ import {
23
+ applyResult,
24
+ notAttemptedAll,
25
+ type AppliedResource,
26
+ type ApplyResult,
27
+ type NotAttemptedResource,
28
+ } from "@intentius/chant/apply";
29
+ import { bindGrafana, GrafanaBindingError, type BindOptions } from "../../api/bind";
30
+ import type { GrafanaHttp } from "../../api/client";
31
+ import { applyGrafana, parseDashboardFile, planFromDashboards, planRefs, type BuiltDashboardInput, type GrafanaApplyOutcome } from "../../api/apply";
32
+
33
+ export type { GrafanaApplyOutcome } from "../../api/apply";
34
+
35
+ export interface GrafanaApplyArgs {
36
+ /**
37
+ * The build's primary output: the index the grafana serializer writes
38
+ * (`chant build -o dist/grafana.json`). The dashboard files it lists are
39
+ * read from the same directory. A combined multi-lexicon output works too;
40
+ * its `grafana` key is used.
41
+ */
42
+ indexPath: string;
43
+ /** The chant environment, which selects `grafana.profiles.<environment>`. */
44
+ environment?: string;
45
+ /**
46
+ * Delete this project's dashboards and folders that the build no longer
47
+ * declares: only those carrying its ownership marker, stack and env.
48
+ * Destructive, so off by default.
49
+ */
50
+ prune?: boolean;
51
+ /** Ownership stack. Default: `ownership.stack` in `chant.config.ts`. */
52
+ stack?: string;
53
+ /** Ownership env. Default: `ownership.env` in `chant.config.ts` (a literal; a `{ param }` reference needs this). */
54
+ ownershipEnv?: string;
55
+ /** Project directory for `chant.config.ts`. Default: the working directory. */
56
+ cwd?: string;
57
+ }
58
+
59
+ /** What a test (or an embedding caller) may inject instead of the project's config, the process env and `fetch`. */
60
+ export interface GrafanaApplyDeps {
61
+ http?: GrafanaHttp;
62
+ config?: Pick<ChantConfig, "grafana" | "ownership">;
63
+ env?: Record<string, string | undefined>;
64
+ }
65
+
66
+ type Json = Record<string, unknown>;
67
+
68
+ function isObject(v: unknown): v is Json {
69
+ return typeof v === "object" && v !== null && !Array.isArray(v);
70
+ }
71
+
72
+ /**
73
+ * The dashboards a build wrote, read from its index and the files beside
74
+ * it. Pure but for the file reads.
75
+ */
76
+ export function readBuiltDashboards(indexPath: string, read: (path: string) => string = (p) => readFileSync(p, "utf8")): BuiltDashboardInput[] {
77
+ const raw = parseDashboardFile(read(indexPath), indexPath);
78
+ const index = isObject(raw.grafana) && Array.isArray(raw.grafana.dashboards) ? raw.grafana : raw;
79
+ if (!Array.isArray(index.dashboards)) throw new Error(`grafana apply: ${indexPath} is not a grafana build index (it has no "dashboards" list)`);
80
+ const dir = dirname(indexPath);
81
+ return index.dashboards.map((entry, i) => {
82
+ if (!isObject(entry) || typeof entry.file !== "string") throw new Error(`grafana apply: ${indexPath} dashboards[${i}] names no file`);
83
+ const file = join(dir, entry.file);
84
+ const json = parseDashboardFile(read(file), file);
85
+ return { json, ...(typeof entry.folder === "string" && entry.folder !== "" ? { folder: entry.folder } : {}) };
86
+ });
87
+ }
88
+
89
+ async function loadConfig(cwd: string): Promise<Pick<ChantConfig, "grafana" | "ownership"> | undefined> {
90
+ try {
91
+ const { loadChantConfig } = await import("@intentius/chant/config");
92
+ return (await loadChantConfig(cwd)).config;
93
+ } catch {
94
+ return undefined;
95
+ }
96
+ }
97
+
98
+ /** The marker to stamp and prune by: the arguments, else the project's `ownership` config. */
99
+ export function resolveMarker(args: Pick<GrafanaApplyArgs, "stack" | "ownershipEnv">, config: Pick<ChantConfig, "ownership"> | undefined): OwnershipMarker | undefined {
100
+ const o = config?.ownership;
101
+ const configured = o && o.enabled !== false ? o : undefined;
102
+ const stack = args.stack ?? configured?.stack;
103
+ if (!stack) return undefined;
104
+ const env = args.ownershipEnv ?? (typeof configured?.env === "string" ? configured.env : undefined);
105
+ if (env === undefined && configured?.env !== undefined) {
106
+ throw new Error("grafana apply: ownership.env is a build parameter reference, which an apply cannot resolve; pass ownershipEnv");
107
+ }
108
+ return { stack, ...(env ? { env } : {}) };
109
+ }
110
+
111
+ /**
112
+ * Apply a grafana build to the environment's Grafana over its HTTP API.
113
+ * Dashboards and folders get chant's ownership labels; with `prune`, this
114
+ * project's dashboards and folders the build no longer has are deleted.
115
+ * A target that cannot be bound returns every resource as not attempted,
116
+ * with the binding's reason.
117
+ */
118
+ export async function grafanaApply(args: GrafanaApplyArgs, signal?: AbortSignal, deps: GrafanaApplyDeps = {}): Promise<GrafanaApplyOutcome> {
119
+ const plan = planFromDashboards(readBuiltDashboards(args.indexPath));
120
+ const config = deps.config ?? (await loadConfig(args.cwd ?? process.cwd()));
121
+ const marker = resolveMarker(args, config);
122
+ const bind: BindOptions = {
123
+ ...(args.environment !== undefined ? { environment: args.environment } : {}),
124
+ config: config ?? {},
125
+ ...(deps.env ? { env: deps.env } : {}),
126
+ ...(deps.http ? { http: deps.http } : {}),
127
+ };
128
+ let client;
129
+ try {
130
+ client = await bindGrafana(bind);
131
+ } catch (err) {
132
+ if (!(err instanceof GrafanaBindingError)) throw err;
133
+ return {
134
+ target: "",
135
+ api: "",
136
+ applied: [],
137
+ pruned: [],
138
+ notAttempted: [
139
+ ...plan.unsupported.map((u) => ({ kind: u.kind, name: u.name, reason: "unsupported-kind" as const, detail: u.detail })),
140
+ ...notAttemptedAll(
141
+ planRefs(plan).filter((r) => !plan.unsupported.some((u) => u.kind === r.kind && u.name === r.name)),
142
+ err.unresolved.reason,
143
+ err.unresolved.detail,
144
+ ),
145
+ ],
146
+ notPrunable: [],
147
+ };
148
+ }
149
+ const outcome = await applyGrafana(client, plan, { ...(marker ? { marker } : {}), ...(args.prune ? { prune: true } : {}), ...(signal ? { signal } : {}) });
150
+ for (const a of outcome.applied) console.log(`${a.action}: ${a.kind}/${a.name} (${outcome.target})`);
151
+ for (const p of outcome.pruned) console.log(`pruned: ${p.kind}/${p.name} (${outcome.target})`);
152
+ for (const n of outcome.notAttempted) console.log(`not attempted: ${n.kind}/${n.name}: ${n.reason}${n.detail ? ` (${n.detail})` : ""}`);
153
+ for (const n of outcome.notPrunable) console.log(`prune: ${n.kind} not considered: ${n.detail}`);
154
+ return outcome;
155
+ }
156
+
157
+ /**
158
+ * Project a grafana apply outcome onto core's apply envelope. The outcome
159
+ * keeps what the envelope has no room for (the request path, the API
160
+ * version, the binding's source); a kind the prune could not consider has no
161
+ * single resource name, so it is reported as `<kind>/*`, `not-prunable`,
162
+ * as the gcp applier does.
163
+ */
164
+ export function toApplyResult(outcome: GrafanaApplyOutcome): ApplyResult {
165
+ const applied: AppliedResource[] = outcome.applied.map((a) => ({ kind: a.kind, name: a.name, action: a.action, physicalId: a.address }));
166
+ const notAttempted: NotAttemptedResource[] = [
167
+ ...outcome.notAttempted.map((n) => ({ kind: n.kind, name: n.name, reason: n.reason, ...(n.detail ? { detail: n.detail } : {}) })),
168
+ ...outcome.notPrunable.map((n) => ({ kind: n.kind, name: "*", reason: "not-prunable" as const, detail: n.detail })),
169
+ ];
170
+ return applyResult(applied, outcome.pruned.map((p) => ({ kind: p.kind, name: p.name, deleted: p.deleted })), notAttempted);
171
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Grafana Op activities, resolved by the core activity registry when a
3
+ * project's `chant.config.ts` lists the `grafana` lexicon. Contributes the
4
+ * API applier (`grafanaApply`, #2948) and its envelope projection
5
+ * (`toApplyResult`), which core's apply activity looks up by name.
6
+ */
7
+ export { grafanaApply, toApplyResult, readBuiltDashboards, resolveMarker } from "./grafana-apply";
8
+ export type { GrafanaApplyArgs, GrafanaApplyDeps, GrafanaApplyOutcome } from "./grafana-apply";
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The typed step builder (chant #1288 Stage 2): the ActivityStep it makes,
3
+ * and the authoring-time type errors its `opts` type gives.
4
+ */
5
+
6
+ import { describe, test, expect } from "vitest";
7
+ import { activity, stepOutput, type StepOutputRef } from "@intentius/chant/op";
8
+ import { grafanaApply } from "./builders";
9
+ import * as activities from "./activities";
10
+
11
+ describe("grafana typed step builder", () => {
12
+ test("grafanaApply: the activity named as the module exports it, on the longInfra profile", () => {
13
+ expect(grafanaApply("dist/grafana.json")).toEqual(activity("grafanaApply", { indexPath: "dist/grafana.json" }, { profile: "longInfra" }));
14
+ expect(typeof activities.grafanaApply).toBe("function");
15
+ expect(typeof activities.toApplyResult).toBe("function");
16
+ });
17
+
18
+ test("grafanaApply: opts are the activity's own arguments", () => {
19
+ const step = grafanaApply("dist/grafana.json", { environment: "prod", prune: true, profile: "fastIdempotent" });
20
+ expect(step.args).toEqual({ indexPath: "dist/grafana.json", environment: "prod", prune: true });
21
+ expect(step.profile).toBe("fastIdempotent");
22
+ });
23
+
24
+ test("grafanaApply: accepts a StepOutputRef in a typed slot, and .out is reachable with an id", () => {
25
+ const ref = stepOutput("resolve-env", "environment");
26
+ expect(grafanaApply("dist/grafana.json", { environment: ref }).args?.environment).toBe(ref);
27
+ const out: StepOutputRef = grafanaApply("dist/grafana.json", { id: "apply" }).out.applied;
28
+ expect(out.step).toBe("apply");
29
+ });
30
+ });
31
+
32
+ // ── Compile-time-only: authoring-time type errors (never executed) ──────────
33
+ function _typeChecksOnly(): void {
34
+ // @ts-expect-error — "env" is not a key of GrafanaApplyArgs (the field is `environment`).
35
+ grafanaApply("dist/grafana.json", { env: "prod" });
36
+
37
+ // @ts-expect-error — prune must be a boolean.
38
+ grafanaApply("dist/grafana.json", { prune: "true" });
39
+ }
40
+ void _typeChecksOnly;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Typed step builder for this lexicon's activity (chant #1288 Stage 2),
3
+ * beside its `*Args` interface for the reason `lexicons/k8s/src/op/builders.ts`
4
+ * gives: core cannot import a lexicon's types. `opts` is
5
+ * {@link GrafanaApplyArgs} itself (via `Omit`/`WithStepRefs`), never
6
+ * restated.
7
+ */
8
+
9
+ import {
10
+ activity,
11
+ takeProfileAndId,
12
+ type ActivityStep,
13
+ type NamedActivityStep,
14
+ type WithStepRefs,
15
+ } from "@intentius/chant/op";
16
+ import type { GrafanaApplyArgs } from "./activities/grafana-apply";
17
+
18
+ /** Extra opts the builder accepts alongside the activity's own fields. */
19
+ type StepOpts = { profile?: ActivityStep["profile"]; id?: string };
20
+
21
+ /**
22
+ * Apply a grafana build to the environment's Grafana over its HTTP API
23
+ * (#2948): folders, library panels and dashboards, stamped with chant's
24
+ * ownership labels, and with `prune`, this project's orphans deleted.
25
+ * `opts` is {@link GrafanaApplyArgs} minus the positional `indexPath`.
26
+ * Defaults to the `longInfra` profile, as the other API appliers do.
27
+ */
28
+ export const grafanaApply = (
29
+ indexPath: string,
30
+ opts?: WithStepRefs<Omit<GrafanaApplyArgs, "indexPath">> & StepOpts,
31
+ ): NamedActivityStep => {
32
+ const { args, profile, id } = takeProfileAndId(opts as Record<string, unknown> | undefined);
33
+ return activity("grafanaApply", { indexPath, ...args }, { profile: profile ?? "longInfra", ...(id ? { id } : {}) });
34
+ };
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Whose a live dashboard is (#2946).
3
+ *
4
+ * `serializer.ts` used to say Grafana had no metadata channel. It has two,
5
+ * both on the `dashboard.grafana.app` resource and neither in the dashboard
6
+ * JSON:
7
+ *
8
+ * - **`metadata.labels`**, which Grafana keeps as written for a dashboard
9
+ * created or updated through `/apis`. chant's marker there is the shared
10
+ * Kubernetes label convention, `LABEL_OWNERSHIP_KEYS`
11
+ * (`app.kubernetes.io/managed-by: chant` plus the stack and env labels).
12
+ * The API applier (#2948) stamps it with {@link grafanaOwnershipLabels};
13
+ * this reads it back, stack and env included.
14
+ * - **`metadata.annotations`**, which Grafana writes itself for a
15
+ * file-provisioned dashboard: `grafana.app/managedBy:
16
+ * classic-file-provisioning` and `grafana.app/managerId: <provider name>`.
17
+ * The provider name is the one chant writes into
18
+ * `provisioning/dashboards/chant.yaml` (`chant` unless a
19
+ * `DashboardProvider` names another), so a dashboard whose manager is one
20
+ * of the project's providers is chant's. That channel carries no stack or
21
+ * env, so it gives a verdict and never a marker.
22
+ *
23
+ * Anything else a read can see (a dashboard saved in the UI, one another
24
+ * provider or tool manages) is `foreign`.
25
+ *
26
+ * Two reads have no channel at all, and say `unknown` rather than guess:
27
+ * the legacy `/api/dashboards/uid` read Grafana 11 is observed through, and
28
+ * every datasource read (`/api/datasources` returns no labels, and the
29
+ * `readOnly` flag says a datasource is provisioned, not by whom).
30
+ */
31
+
32
+ import {
33
+ LABEL_OWNERSHIP_KEYS,
34
+ OWNERSHIP_MANAGED_BY_VALUE,
35
+ classifyOwnership,
36
+ hasOwnershipMarker,
37
+ ownershipEntries,
38
+ readOwnership,
39
+ type ChannelKeys,
40
+ type OwnershipMarker,
41
+ } from "@intentius/chant/ownership";
42
+ import { DASHBOARD_PROVIDER_TYPE } from "./dashboard";
43
+ import type { LiveDashboard } from "./api/dashboards";
44
+
45
+ /** The label keys chant's marker uses on a `dashboard.grafana.app` resource. */
46
+ export const GRAFANA_OWNERSHIP_KEYS: ChannelKeys = LABEL_OWNERSHIP_KEYS;
47
+
48
+ export const MANAGED_BY_ANNOTATION = "grafana.app/managedBy";
49
+ export const MANAGER_ID_ANNOTATION = "grafana.app/managerId";
50
+ /** `grafana.app/managedBy` for a dashboard loaded by a file provider. */
51
+ export const FILE_PROVISIONING_MANAGER = "classic-file-provisioning";
52
+
53
+ /**
54
+ * The provider name the build writes when a project declares none. It is
55
+ * chant's managed-by value on purpose: it comes back from Grafana as the
56
+ * `grafana.app/managerId` of every dashboard the provider loads.
57
+ */
58
+ export const DEFAULT_PROVIDER_NAME = OWNERSHIP_MANAGED_BY_VALUE;
59
+
60
+ export type OwnershipVerdict = "owned" | "foreign" | "unknown";
61
+
62
+ /** The labels the API applier (#2948) writes on a dashboard it creates, for this build's stack and env. */
63
+ export function grafanaOwnershipLabels(ownership: { stack: string; env?: string }): Record<string, string> {
64
+ return ownershipEntries(GRAFANA_OWNERSHIP_KEYS, ownership);
65
+ }
66
+
67
+ /**
68
+ * The provider names that load this project's dashboards: every declared
69
+ * `DashboardProvider`, or the default one the build writes when there is
70
+ * none.
71
+ */
72
+ export function chantProviderNames(entities: Iterable<{ entityType: string; props: Record<string, unknown> }>): Set<string> {
73
+ const names = new Set<string>();
74
+ for (const e of entities) {
75
+ if (e.entityType === DASHBOARD_PROVIDER_TYPE && typeof e.props.name === "string") names.add(e.props.name);
76
+ }
77
+ if (names.size === 0) names.add(DEFAULT_PROVIDER_NAME);
78
+ return names;
79
+ }
80
+
81
+ /** Why a verdict is what it is, for an `owned: true` read to put in its `filtered` detail. */
82
+ export function ownershipGap(dashboard: LiveDashboard): string {
83
+ return dashboard.via === "legacy"
84
+ ? "it was read over /api/dashboards/uid (Grafana 11), which returns no labels or annotations"
85
+ : "it carries neither chant's managed-by label nor a manager annotation naming one of this project's dashboard providers";
86
+ }
87
+
88
+ /** A dashboard's ownership verdict, and the stack/env marker when its labels carry one. */
89
+ export function dashboardOwnership(
90
+ dashboard: LiveDashboard,
91
+ providers: ReadonlySet<string>,
92
+ ): { ownership: OwnershipVerdict; marker?: OwnershipMarker } {
93
+ if (dashboard.via === "legacy") return { ownership: "unknown" };
94
+ if (hasOwnershipMarker(dashboard.labels, GRAFANA_OWNERSHIP_KEYS)) {
95
+ const marker = readOwnership(dashboard.labels, GRAFANA_OWNERSHIP_KEYS);
96
+ return { ownership: classifyOwnership(dashboard.labels, GRAFANA_OWNERSHIP_KEYS), ...(marker ? { marker } : {}) };
97
+ }
98
+ const manager = dashboard.annotations[MANAGER_ID_ANNOTATION];
99
+ if (dashboard.annotations[MANAGED_BY_ANNOTATION] === FILE_PROVISIONING_MANAGER && manager !== undefined && providers.has(manager)) {
100
+ return { ownership: "owned" };
101
+ }
102
+ return { ownership: "foreign" };
103
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Hand-written option types for the built-in panels that have no schema.
3
+ *
4
+ * The alert list and flame graph panels define their options in TypeScript
5
+ * inside Grafana's frontend, with no CUE kind, so foundation-sdk publishes
6
+ * no JSON Schema for them and `npm run generate` has nothing to read. The
7
+ * types here follow Grafana's source at v13.2.2, cited per type. GRAF107
8
+ * checks these panels' envelope (the dashboard schema's `Panel`) but not
9
+ * their `options`; the traces panel is in the same position and keeps
10
+ * untyped options.
11
+ */
12
+
13
+ import type { ThresholdsConfig, ValueMapping } from "./schema/dashboard.gen";
14
+
15
+ /**
16
+ * How the alert list orders rules: `SortOrder` in
17
+ * public/app/plugins/panel/alertlist/types.ts:4-10 (a numeric enum starting at 1).
18
+ * 1 alphabetical ascending, 2 descending, 3 by importance, 4 time ascending, 5 time descending.
19
+ */
20
+ export type AlertListSortOrder = 1 | 2 | 3 | 4 | 5;
21
+
22
+ /** Which rule states the alert list shows: `StateFilter`, public/app/plugins/panel/alertlist/types.ts:22-30. */
23
+ export interface AlertListStateFilter {
24
+ firing: boolean;
25
+ pending: boolean;
26
+ /** Kept for dashboards saved before 9.x. */
27
+ inactive: boolean;
28
+ recovering: boolean;
29
+ noData: boolean;
30
+ normal: boolean;
31
+ error: boolean;
32
+ }
33
+
34
+ /**
35
+ * The alert list panel's options: `UnifiedAlertListOptions`,
36
+ * public/app/plugins/panel/alertlist/types.ts:32-49 at v13.2.2. Defaults are in
37
+ * public/app/plugins/panel/alertlist/module.tsx.
38
+ */
39
+ export interface AlertListOptions {
40
+ maxItems: number;
41
+ sortOrder: AlertListSortOrder;
42
+ /** Only rules from the dashboard the panel is on. */
43
+ dashboardAlerts: boolean;
44
+ groupMode: "default" | "custom";
45
+ groupBy: string[];
46
+ alertName: string;
47
+ showInstances: boolean;
48
+ /** Only rules in this folder; null for any. */
49
+ folder: { uid: string; title: string } | null;
50
+ stateFilter: AlertListStateFilter;
51
+ alertInstanceLabelFilter: string;
52
+ /** The rule source's datasource name, or null for all. */
53
+ datasource: string | null;
54
+ viewMode: "list" | "stat";
55
+ showInactiveAlerts: boolean;
56
+ /** `BigValueColorMode`, packages/grafana-ui/src/components/BigValue/BigValueTypes.ts:8-13. */
57
+ statColorMode: "background" | "background_solid" | "none" | "value";
58
+ statThresholds: ThresholdsConfig;
59
+ statValueMappings: ValueMapping[];
60
+ }
61
+
62
+ /** The flame graph panel's options: `Options`, public/app/plugins/panel/flamegraph/types.ts:1-3 at v13.2.2. */
63
+ export interface FlameGraphOptions {
64
+ /** Hide the top table and show only the flame graph. */
65
+ showFlameGraphOnly: boolean;
66
+ }