@intentius/chant-lexicon-grafana 0.98.0 → 0.100.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 (532) hide show
  1. package/README.md +24 -8
  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/annotations.d.ts +61 -0
  11. package/dist/annotations.d.ts.map +1 -0
  12. package/dist/api/apply.d.ts +160 -0
  13. package/dist/api/apply.d.ts.map +1 -0
  14. package/dist/api/bind.d.ts +32 -0
  15. package/dist/api/bind.d.ts.map +1 -0
  16. package/dist/api/client.d.ts +76 -0
  17. package/dist/api/client.d.ts.map +1 -0
  18. package/dist/api/converge.d.ts +33 -0
  19. package/dist/api/converge.d.ts.map +1 -0
  20. package/dist/api/dashboards.d.ts +102 -0
  21. package/dist/api/dashboards.d.ts.map +1 -0
  22. package/dist/api/datasources.d.ts +26 -0
  23. package/dist/api/datasources.d.ts.map +1 -0
  24. package/dist/api/fake-grafana-writes.d.ts +41 -0
  25. package/dist/api/fake-grafana-writes.d.ts.map +1 -0
  26. package/dist/api/fake-grafana.d.ts +63 -0
  27. package/dist/api/fake-grafana.d.ts.map +1 -0
  28. package/dist/api/folders.d.ts +146 -0
  29. package/dist/api/folders.d.ts.map +1 -0
  30. package/dist/api/library-panels.d.ts +52 -0
  31. package/dist/api/library-panels.d.ts.map +1 -0
  32. package/dist/build.d.ts +55 -6
  33. package/dist/build.d.ts.map +1 -1
  34. package/dist/catalog.d.ts +1 -1
  35. package/dist/catalog.d.ts.map +1 -1
  36. package/dist/codegen/docs.d.ts.map +1 -1
  37. package/dist/codegen/generate.d.ts +7 -1
  38. package/dist/codegen/generate.d.ts.map +1 -1
  39. package/dist/codegen/schema-types.d.ts +3 -3
  40. package/dist/codegen/schema-types.d.ts.map +1 -1
  41. package/dist/composites/agent-dashboard.d.ts.map +1 -1
  42. package/dist/composites/catalog.d.ts.map +1 -1
  43. package/dist/composites/index.d.ts +4 -2
  44. package/dist/composites/index.d.ts.map +1 -1
  45. package/dist/composites/red-dashboard.d.ts +21 -2
  46. package/dist/composites/red-dashboard.d.ts.map +1 -1
  47. package/dist/composites/shared.d.ts +11 -3
  48. package/dist/composites/shared.d.ts.map +1 -1
  49. package/dist/composites/slo-alert-rules.d.ts +92 -0
  50. package/dist/composites/slo-alert-rules.d.ts.map +1 -0
  51. package/dist/config.d.ts +111 -0
  52. package/dist/config.d.ts.map +1 -0
  53. package/dist/contact-point-secrets.d.ts +17 -0
  54. package/dist/contact-point-secrets.d.ts.map +1 -0
  55. package/dist/dashboard.d.ts +51 -9
  56. package/dist/dashboard.d.ts.map +1 -1
  57. package/dist/datasource-refs.d.ts +99 -0
  58. package/dist/datasource-refs.d.ts.map +1 -0
  59. package/dist/datasource-settings.d.ts +834 -0
  60. package/dist/datasource-settings.d.ts.map +1 -0
  61. package/dist/datasource.d.ts +82 -11
  62. package/dist/datasource.d.ts.map +1 -1
  63. package/dist/deep-observe-hooks.d.ts +64 -0
  64. package/dist/deep-observe-hooks.d.ts.map +1 -0
  65. package/dist/deep-observe.d.ts +47 -0
  66. package/dist/deep-observe.d.ts.map +1 -0
  67. package/dist/describe-resources.d.ts +50 -0
  68. package/dist/describe-resources.d.ts.map +1 -0
  69. package/dist/detect.d.ts +32 -3
  70. package/dist/detect.d.ts.map +1 -1
  71. package/dist/export-resources.d.ts +38 -0
  72. package/dist/export-resources.d.ts.map +1 -0
  73. package/dist/folder.d.ts +68 -0
  74. package/dist/folder.d.ts.map +1 -0
  75. package/dist/import/alerting.d.ts +40 -0
  76. package/dist/import/alerting.d.ts.map +1 -0
  77. package/dist/import/edits.d.ts +47 -0
  78. package/dist/import/edits.d.ts.map +1 -0
  79. package/dist/import/embedded.d.ts +14 -0
  80. package/dist/import/embedded.d.ts.map +1 -0
  81. package/dist/import/generator.d.ts +60 -0
  82. package/dist/import/generator.d.ts.map +1 -0
  83. package/dist/import/live-export.d.ts +108 -0
  84. package/dist/import/live-export.d.ts.map +1 -0
  85. package/dist/import/mappings.d.ts +89 -0
  86. package/dist/import/mappings.d.ts.map +1 -0
  87. package/dist/import/model.d.ts +105 -0
  88. package/dist/import/model.d.ts.map +1 -0
  89. package/dist/import/normalize-alerting.d.ts +26 -0
  90. package/dist/import/normalize-alerting.d.ts.map +1 -0
  91. package/dist/import/normalize.d.ts +74 -0
  92. package/dist/import/normalize.d.ts.map +1 -0
  93. package/dist/import/parser.d.ts +125 -0
  94. package/dist/import/parser.d.ts.map +1 -0
  95. package/dist/import/report.d.ts +26 -0
  96. package/dist/import/report.d.ts.map +1 -0
  97. package/dist/import/testdata/fixtures.d.ts +41 -0
  98. package/dist/import/testdata/fixtures.d.ts.map +1 -0
  99. package/dist/import/v2.d.ts +73 -0
  100. package/dist/import/v2.d.ts.map +1 -0
  101. package/dist/index.d.ts +31 -12
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/init-templates.d.ts +27 -0
  104. package/dist/init-templates.d.ts.map +1 -0
  105. package/dist/integrity.json +22 -12
  106. package/dist/k8s.d.ts +107 -0
  107. package/dist/k8s.d.ts.map +1 -0
  108. package/dist/lint/audit-catalog.d.ts +2 -1
  109. package/dist/lint/audit-catalog.d.ts.map +1 -1
  110. package/dist/lint/post-synth/graf101.d.ts +2 -2
  111. package/dist/lint/post-synth/graf102.d.ts +1 -1
  112. package/dist/lint/post-synth/graf104.d.ts +2 -2
  113. package/dist/lint/post-synth/graf106.d.ts +2 -2
  114. package/dist/lint/post-synth/graf107.d.ts +1 -1
  115. package/dist/lint/post-synth/graf108.d.ts +8 -0
  116. package/dist/lint/post-synth/graf108.d.ts.map +1 -0
  117. package/dist/lint/post-synth/graf109.d.ts +8 -0
  118. package/dist/lint/post-synth/graf109.d.ts.map +1 -0
  119. package/dist/lint/post-synth/graf110.d.ts +8 -0
  120. package/dist/lint/post-synth/graf110.d.ts.map +1 -0
  121. package/dist/lint/post-synth/graf111.d.ts +8 -0
  122. package/dist/lint/post-synth/graf111.d.ts.map +1 -0
  123. package/dist/lint/post-synth/graf112.d.ts +8 -0
  124. package/dist/lint/post-synth/graf112.d.ts.map +1 -0
  125. package/dist/lint/post-synth/graf113.d.ts +8 -0
  126. package/dist/lint/post-synth/graf113.d.ts.map +1 -0
  127. package/dist/lint/post-synth/graf114.d.ts +8 -0
  128. package/dist/lint/post-synth/graf114.d.ts.map +1 -0
  129. package/dist/lint/post-synth/graf115.d.ts +8 -0
  130. package/dist/lint/post-synth/graf115.d.ts.map +1 -0
  131. package/dist/lint/post-synth/grafana-helpers.d.ts +7 -5
  132. package/dist/lint/post-synth/grafana-helpers.d.ts.map +1 -1
  133. package/dist/lint/post-synth/index.d.ts.map +1 -1
  134. package/dist/lint/rules/literal-secret.d.ts +7 -3
  135. package/dist/lint/rules/literal-secret.d.ts.map +1 -1
  136. package/dist/lsp/hover.d.ts.map +1 -1
  137. package/dist/manifest.json +1 -1
  138. package/dist/members.d.ts +27 -0
  139. package/dist/members.d.ts.map +1 -0
  140. package/dist/meta.json +205 -0
  141. package/dist/okf/index.md +53 -4
  142. package/dist/okf/rules/GRAF002.md +3 -2
  143. package/dist/okf/rules/GRAF101.md +3 -2
  144. package/dist/okf/rules/GRAF102.md +1 -0
  145. package/dist/okf/rules/GRAF104.md +2 -2
  146. package/dist/okf/rules/GRAF106.md +2 -2
  147. package/dist/okf/rules/GRAF108.md +11 -0
  148. package/dist/okf/rules/GRAF109.md +15 -0
  149. package/dist/okf/rules/GRAF110.md +11 -0
  150. package/dist/okf/rules/GRAF111.md +11 -0
  151. package/dist/okf/rules/GRAF112.md +16 -0
  152. package/dist/okf/rules/GRAF113.md +16 -0
  153. package/dist/okf/rules/GRAF114.md +11 -0
  154. package/dist/okf/rules/GRAF115.md +11 -0
  155. package/dist/okf/types/AdhocVariable.md +9 -0
  156. package/dist/okf/types/AlertListPanel.md +9 -0
  157. package/dist/okf/types/AlertQuery.md +9 -0
  158. package/dist/okf/types/AlertRule.md +9 -0
  159. package/dist/okf/types/AlertRuleGroup.md +9 -0
  160. package/dist/okf/types/AzureMonitorQuery.md +9 -0
  161. package/dist/okf/types/BarChartPanel.md +9 -0
  162. package/dist/okf/types/BarGaugePanel.md +9 -0
  163. package/dist/okf/types/BigQueryQuery.md +9 -0
  164. package/dist/okf/types/CanvasPanel.md +9 -0
  165. package/dist/okf/types/ClassicConditionsExpression.md +9 -0
  166. package/dist/okf/types/CloudMonitoringQuery.md +9 -0
  167. package/dist/okf/types/CloudWatchQuery.md +9 -0
  168. package/dist/okf/types/ContactPoint.md +14 -0
  169. package/dist/okf/types/Dashboard.md +2 -1
  170. package/dist/okf/types/Datasource.md +3 -2
  171. package/dist/okf/types/DatasourceProvisioning.md +9 -0
  172. package/dist/okf/types/ElasticsearchQuery.md +9 -0
  173. package/dist/okf/types/ExternalDatasource.md +15 -0
  174. package/dist/okf/types/FlameGraphPanel.md +9 -0
  175. package/dist/okf/types/Folder.md +9 -0
  176. package/dist/okf/types/GeomapPanel.md +9 -0
  177. package/dist/okf/types/GroupByVariable.md +9 -0
  178. package/dist/okf/types/HistogramPanel.md +9 -0
  179. package/dist/okf/types/MSSQLQuery.md +9 -0
  180. package/dist/okf/types/MathExpression.md +9 -0
  181. package/dist/okf/types/MuteTiming.md +13 -0
  182. package/dist/okf/types/MySQLQuery.md +9 -0
  183. package/dist/okf/types/NodeGraphPanel.md +9 -0
  184. package/dist/okf/types/NotificationPolicy.md +9 -0
  185. package/dist/okf/types/NotificationTemplate.md +9 -0
  186. package/dist/okf/types/PieChartPanel.md +9 -0
  187. package/dist/okf/types/PostgresQuery.md +9 -0
  188. package/dist/okf/types/PyroscopeQuery.md +9 -0
  189. package/dist/okf/types/ReduceExpression.md +9 -0
  190. package/dist/okf/types/ResampleExpression.md +9 -0
  191. package/dist/okf/types/SqlExpression.md +9 -0
  192. package/dist/okf/types/StateTimelinePanel.md +9 -0
  193. package/dist/okf/types/StatusHistoryPanel.md +9 -0
  194. package/dist/okf/types/SwitchVariable.md +9 -0
  195. package/dist/okf/types/ThresholdExpression.md +9 -0
  196. package/dist/okf/types/TrendPanel.md +9 -0
  197. package/dist/okf/types/XYChartPanel.md +9 -0
  198. package/dist/op/activities/grafana-apply.d.ts +86 -0
  199. package/dist/op/activities/grafana-apply.d.ts.map +1 -0
  200. package/dist/op/activities/index.d.ts +9 -0
  201. package/dist/op/activities/index.d.ts.map +1 -0
  202. package/dist/op/builders.d.ts +24 -0
  203. package/dist/op/builders.d.ts.map +1 -0
  204. package/dist/ownership.d.ts +78 -0
  205. package/dist/ownership.d.ts.map +1 -0
  206. package/dist/panel-options.d.ts +65 -0
  207. package/dist/panel-options.d.ts.map +1 -0
  208. package/dist/panels.d.ts +46 -9
  209. package/dist/panels.d.ts.map +1 -1
  210. package/dist/pin.d.ts +29 -7
  211. package/dist/pin.d.ts.map +1 -1
  212. package/dist/plugin.d.ts.map +1 -1
  213. package/dist/promql-check.d.ts +51 -0
  214. package/dist/promql-check.d.ts.map +1 -0
  215. package/dist/query-models.d.ts +95 -0
  216. package/dist/query-models.d.ts.map +1 -0
  217. package/dist/query.d.ts +116 -18
  218. package/dist/query.d.ts.map +1 -1
  219. package/dist/rules/graf101.ts +3 -3
  220. package/dist/rules/graf102.ts +1 -1
  221. package/dist/rules/graf104.ts +3 -3
  222. package/dist/rules/graf106.ts +3 -3
  223. package/dist/rules/graf107.ts +1 -1
  224. package/dist/rules/graf108.ts +17 -0
  225. package/dist/rules/graf109.ts +17 -0
  226. package/dist/rules/graf110.ts +17 -0
  227. package/dist/rules/graf111.ts +17 -0
  228. package/dist/rules/graf112.ts +17 -0
  229. package/dist/rules/graf113.ts +17 -0
  230. package/dist/rules/graf114.ts +17 -0
  231. package/dist/rules/graf115.ts +17 -0
  232. package/dist/rules/grafana-helpers.ts +27 -10
  233. package/dist/rules/literal-secret.ts +60 -6
  234. package/dist/schema/azuremonitor.gen.d.ts +354 -0
  235. package/dist/schema/azuremonitor.gen.d.ts.map +1 -0
  236. package/dist/schema/barchart.gen.d.ts +198 -0
  237. package/dist/schema/barchart.gen.d.ts.map +1 -0
  238. package/dist/schema/bargauge.gen.d.ts +97 -0
  239. package/dist/schema/bargauge.gen.d.ts.map +1 -0
  240. package/dist/schema/bigquery.gen.d.ts +93 -0
  241. package/dist/schema/bigquery.gen.d.ts.map +1 -0
  242. package/dist/schema/canvas.gen.d.ts +152 -0
  243. package/dist/schema/canvas.gen.d.ts.map +1 -0
  244. package/dist/schema/cloudwatch.gen.d.ts +394 -0
  245. package/dist/schema/cloudwatch.gen.d.ts.map +1 -0
  246. package/dist/schema/dashboard.gen.d.ts +196 -21
  247. package/dist/schema/dashboard.gen.d.ts.map +1 -1
  248. package/dist/schema/elasticsearch.gen.d.ts +444 -0
  249. package/dist/schema/elasticsearch.gen.d.ts.map +1 -0
  250. package/dist/schema/expr.gen.d.ts +606 -0
  251. package/dist/schema/expr.gen.d.ts.map +1 -0
  252. package/dist/schema/gauge.gen.d.ts.map +1 -1
  253. package/dist/schema/geomap.gen.d.ts +100 -0
  254. package/dist/schema/geomap.gen.d.ts.map +1 -0
  255. package/dist/schema/googlecloudmonitoring.gen.d.ts +312 -0
  256. package/dist/schema/googlecloudmonitoring.gen.d.ts.map +1 -0
  257. package/dist/schema/grafanapyroscope.gen.d.ts +60 -0
  258. package/dist/schema/grafanapyroscope.gen.d.ts.map +1 -0
  259. package/dist/schema/heatmap.gen.d.ts.map +1 -1
  260. package/dist/schema/histogram.gen.d.ts +132 -0
  261. package/dist/schema/histogram.gen.d.ts.map +1 -0
  262. package/dist/schema/index.d.ts +18 -0
  263. package/dist/schema/index.d.ts.map +1 -1
  264. package/dist/schema/logs.gen.d.ts +1 -0
  265. package/dist/schema/logs.gen.d.ts.map +1 -1
  266. package/dist/schema/loki.gen.d.ts.map +1 -1
  267. package/dist/schema/nodegraph.gen.d.ts +49 -0
  268. package/dist/schema/nodegraph.gen.d.ts.map +1 -0
  269. package/dist/schema/piechart.gen.d.ts +127 -0
  270. package/dist/schema/piechart.gen.d.ts.map +1 -0
  271. package/dist/schema/prometheus.gen.d.ts.map +1 -1
  272. package/dist/schema/stat.gen.d.ts.map +1 -1
  273. package/dist/schema/statetimeline.gen.d.ts +124 -0
  274. package/dist/schema/statetimeline.gen.d.ts.map +1 -0
  275. package/dist/schema/statushistory.gen.d.ts +114 -0
  276. package/dist/schema/statushistory.gen.d.ts.map +1 -0
  277. package/dist/schema/table.gen.d.ts +35 -3
  278. package/dist/schema/table.gen.d.ts.map +1 -1
  279. package/dist/schema/tempo.gen.d.ts.map +1 -1
  280. package/dist/schema/text.gen.d.ts.map +1 -1
  281. package/dist/schema/timeseries.gen.d.ts +5 -1
  282. package/dist/schema/timeseries.gen.d.ts.map +1 -1
  283. package/dist/schema/trend.gen.d.ts +180 -0
  284. package/dist/schema/trend.gen.d.ts.map +1 -0
  285. package/dist/schema/xychart.gen.d.ts +145 -0
  286. package/dist/schema/xychart.gen.d.ts.map +1 -0
  287. package/dist/schema-validate.d.ts +39 -4
  288. package/dist/schema-validate.d.ts.map +1 -1
  289. package/dist/serializer.d.ts +11 -3
  290. package/dist/serializer.d.ts.map +1 -1
  291. package/dist/skill-defs.d.ts.map +1 -1
  292. package/dist/skills/chant-grafana-alerting.md +49 -0
  293. package/dist/skills/chant-grafana-operations.md +108 -0
  294. package/dist/skills/chant-grafana-provisioning.md +3 -3
  295. package/dist/skills/chant-grafana.md +15 -8
  296. package/dist/spec/fetch-units-cli.d.ts +3 -0
  297. package/dist/spec/fetch-units-cli.d.ts.map +1 -0
  298. package/dist/spec/overlay.d.ts +43 -0
  299. package/dist/spec/overlay.d.ts.map +1 -0
  300. package/dist/spec/schemas.d.ts +21 -13
  301. package/dist/spec/schemas.d.ts.map +1 -1
  302. package/dist/spec/schemas.gen.d.ts +3 -0
  303. package/dist/spec/schemas.gen.d.ts.map +1 -0
  304. package/dist/spec/units.d.ts +35 -0
  305. package/dist/spec/units.d.ts.map +1 -0
  306. package/dist/spec/units.gen.d.ts +2 -0
  307. package/dist/spec/units.gen.d.ts.map +1 -0
  308. package/dist/transformations.d.ts +459 -0
  309. package/dist/transformations.d.ts.map +1 -0
  310. package/dist/util.d.ts +6 -0
  311. package/dist/util.d.ts.map +1 -1
  312. package/dist/validate-alerting.d.ts +65 -0
  313. package/dist/validate-alerting.d.ts.map +1 -0
  314. package/dist/validate-output.d.ts +62 -9
  315. package/dist/validate-output.d.ts.map +1 -1
  316. package/dist/validate.d.ts.map +1 -1
  317. package/dist/validation.d.ts +14 -0
  318. package/dist/validation.d.ts.map +1 -0
  319. package/dist/variables.d.ts +109 -7
  320. package/dist/variables.d.ts.map +1 -1
  321. package/package.json +12 -4
  322. package/src/actions/actions.test.ts +24 -0
  323. package/src/actions/grafana.ts +40 -0
  324. package/src/actions/index.ts +1 -0
  325. package/src/alerting-build.ts +376 -0
  326. package/src/alerting.e2e.test.ts +212 -0
  327. package/src/alerting.test.ts +272 -0
  328. package/src/alerting.ts +435 -0
  329. package/src/annotations.ts +84 -0
  330. package/src/api/apply.test.ts +256 -0
  331. package/src/api/apply.ts +394 -0
  332. package/src/api/bind.ts +58 -0
  333. package/src/api/client.ts +148 -0
  334. package/src/api/converge.ts +63 -0
  335. package/src/api/dashboards.ts +247 -0
  336. package/src/api/datasources.ts +33 -0
  337. package/src/api/fake-grafana-writes.ts +201 -0
  338. package/src/api/fake-grafana.ts +190 -0
  339. package/src/api/folders.ts +339 -0
  340. package/src/api/library-panels.ts +101 -0
  341. package/src/build.test.ts +351 -10
  342. package/src/build.ts +294 -55
  343. package/src/catalog.ts +104 -2
  344. package/src/codegen/docs.ts +50 -10
  345. package/src/codegen/generate.ts +27 -1
  346. package/src/codegen/schema-types.ts +16 -6
  347. package/src/composites/agent-dashboard.ts +6 -3
  348. package/src/composites/catalog.test.ts +2 -2
  349. package/src/composites/catalog.ts +82 -0
  350. package/src/composites/composites.test.ts +73 -9
  351. package/src/composites/index.ts +4 -2
  352. package/src/composites/queries.e2e.test.ts +374 -0
  353. package/src/composites/red-dashboard.ts +83 -12
  354. package/src/composites/shared.ts +14 -2
  355. package/src/composites/slo-alert-rules.ts +149 -0
  356. package/src/config.test.ts +61 -0
  357. package/src/config.ts +138 -0
  358. package/src/contact-point-secrets.ts +63 -0
  359. package/src/dashboard.ts +68 -9
  360. package/src/datasource-refs.test.ts +56 -0
  361. package/src/datasource-refs.ts +213 -0
  362. package/src/datasource-settings.test.ts +106 -0
  363. package/src/datasource-settings.ts +941 -0
  364. package/src/datasource.ts +123 -11
  365. package/src/deep-observe-hooks.ts +267 -0
  366. package/src/deep-observe.test.ts +311 -0
  367. package/src/deep-observe.ts +187 -0
  368. package/src/describe-resources.test.ts +271 -0
  369. package/src/describe-resources.ts +244 -0
  370. package/src/detect.ts +82 -5
  371. package/src/export-resources.test.ts +129 -0
  372. package/src/export-resources.ts +84 -0
  373. package/src/folder.ts +118 -0
  374. package/src/generated/lexicon-grafana.json +205 -0
  375. package/src/import/alerting-roundtrip.test.ts +161 -0
  376. package/src/import/alerting.ts +500 -0
  377. package/src/import/edits.ts +95 -0
  378. package/src/import/embedded.test.ts +45 -0
  379. package/src/import/embedded.ts +47 -0
  380. package/src/import/generated-types.e2e.test.ts +130 -0
  381. package/src/import/generator.test.ts +210 -0
  382. package/src/import/generator.ts +463 -0
  383. package/src/import/live-export.ts +264 -0
  384. package/src/import/mappings.ts +336 -0
  385. package/src/import/model.ts +117 -0
  386. package/src/import/normalize-alerting.ts +101 -0
  387. package/src/import/normalize.test.ts +117 -0
  388. package/src/import/normalize.ts +393 -0
  389. package/src/import/parser.test.ts +516 -0
  390. package/src/import/parser.ts +1387 -0
  391. package/src/import/report.ts +62 -0
  392. package/src/import/roundtrip.test.ts +563 -0
  393. package/src/import/testdata/fixtures.ts +137 -0
  394. package/src/import/v2.test.ts +292 -0
  395. package/src/import/v2.ts +804 -0
  396. package/src/import.e2e.test.ts +31 -84
  397. package/src/import.test.ts +9 -3
  398. package/src/index.ts +308 -14
  399. package/src/init-templates.test.ts +74 -0
  400. package/src/init-templates.ts +349 -0
  401. package/src/k8s.test.ts +98 -0
  402. package/src/k8s.ts +197 -0
  403. package/src/lint/audit-catalog.ts +68 -3
  404. package/src/lint/core-lint.test.ts +43 -0
  405. package/src/lint/post-synth/graf101.ts +3 -3
  406. package/src/lint/post-synth/graf102.ts +1 -1
  407. package/src/lint/post-synth/graf104.ts +3 -3
  408. package/src/lint/post-synth/graf106.ts +3 -3
  409. package/src/lint/post-synth/graf107.ts +1 -1
  410. package/src/lint/post-synth/graf108.ts +17 -0
  411. package/src/lint/post-synth/graf109.ts +17 -0
  412. package/src/lint/post-synth/graf110.ts +17 -0
  413. package/src/lint/post-synth/graf111.ts +17 -0
  414. package/src/lint/post-synth/graf112.ts +17 -0
  415. package/src/lint/post-synth/graf113.ts +17 -0
  416. package/src/lint/post-synth/graf114.ts +17 -0
  417. package/src/lint/post-synth/graf115.ts +17 -0
  418. package/src/lint/post-synth/grafana-helpers.ts +27 -10
  419. package/src/lint/post-synth/index.ts +16 -0
  420. package/src/lint/post-synth/post-synth.test.ts +333 -12
  421. package/src/lint/rules/literal-secret.ts +60 -6
  422. package/src/lint/rules/rules.test.ts +27 -0
  423. package/src/load-cost.test.ts +35 -0
  424. package/src/lsp/completions.test.ts +5 -0
  425. package/src/lsp/hover.test.ts +7 -0
  426. package/src/lsp/hover.ts +8 -1
  427. package/src/members.ts +68 -0
  428. package/src/observe.e2e.test.ts +255 -0
  429. package/src/op/activities/grafana-apply.e2e.test.ts +184 -0
  430. package/src/op/activities/grafana-apply.test.ts +243 -0
  431. package/src/op/activities/grafana-apply.ts +197 -0
  432. package/src/op/activities/index.ts +8 -0
  433. package/src/op/builders.test.ts +40 -0
  434. package/src/op/builders.ts +34 -0
  435. package/src/ownership.ts +122 -0
  436. package/src/panel-options.ts +66 -0
  437. package/src/panels.test.ts +53 -0
  438. package/src/panels.ts +148 -10
  439. package/src/pin.ts +69 -6
  440. package/src/plugin.test.ts +12 -2
  441. package/src/plugin.ts +75 -0
  442. package/src/promql-check.test.ts +108 -0
  443. package/src/promql-check.ts +193 -0
  444. package/src/query-models.ts +94 -0
  445. package/src/query.test.ts +166 -0
  446. package/src/query.ts +190 -20
  447. package/src/schema/azuremonitor.gen.ts +381 -0
  448. package/src/schema/barchart.gen.ts +221 -0
  449. package/src/schema/bargauge.gen.ts +111 -0
  450. package/src/schema/bigquery.gen.ts +110 -0
  451. package/src/schema/canvas.gen.ts +176 -0
  452. package/src/schema/cloudwatch.gen.ts +423 -0
  453. package/src/schema/dashboard.gen.ts +206 -22
  454. package/src/schema/elasticsearch.gen.ts +508 -0
  455. package/src/schema/expr.gen.ts +618 -0
  456. package/src/schema/gauge.gen.ts +2 -1
  457. package/src/schema/geomap.gen.ts +112 -0
  458. package/src/schema/googlecloudmonitoring.gen.ts +330 -0
  459. package/src/schema/grafanapyroscope.gen.ts +66 -0
  460. package/src/schema/heatmap.gen.ts +2 -1
  461. package/src/schema/histogram.gen.ts +151 -0
  462. package/src/schema/index.ts +18 -0
  463. package/src/schema/logs.gen.ts +3 -1
  464. package/src/schema/loki.gen.ts +2 -1
  465. package/src/schema/nodegraph.gen.ts +58 -0
  466. package/src/schema/piechart.gen.ts +146 -0
  467. package/src/schema/prometheus.gen.ts +2 -1
  468. package/src/schema/stat.gen.ts +2 -1
  469. package/src/schema/statetimeline.gen.ts +143 -0
  470. package/src/schema/statushistory.gen.ts +132 -0
  471. package/src/schema/table.gen.ts +41 -4
  472. package/src/schema/tempo.gen.ts +2 -1
  473. package/src/schema/text.gen.ts +2 -1
  474. package/src/schema/timeseries.gen.ts +7 -2
  475. package/src/schema/trend.gen.ts +208 -0
  476. package/src/schema/xychart.gen.ts +167 -0
  477. package/src/schema-overlay.test.ts +282 -0
  478. package/src/schema-validate.ts +199 -57
  479. package/src/schema.test.ts +4 -2
  480. package/src/serializer.test.ts +13 -1
  481. package/src/serializer.ts +11 -3
  482. package/src/skill-defs.ts +34 -0
  483. package/src/skills/chant-grafana-alerting.md +49 -0
  484. package/src/skills/chant-grafana-operations.md +108 -0
  485. package/src/skills/chant-grafana-provisioning.md +3 -3
  486. package/src/skills/chant-grafana.md +15 -8
  487. package/src/spec/fetch-cli.ts +3 -3
  488. package/src/spec/fetch-units-cli.ts +37 -0
  489. package/src/spec/overlay/canvas.overlay.json +142 -0
  490. package/src/spec/overlay/dashboard.overlay.json +378 -0
  491. package/src/spec/overlay/expr.overlay.json +14 -0
  492. package/src/spec/overlay/geomap.overlay.json +35 -0
  493. package/src/spec/overlay/logs.overlay.json +14 -0
  494. package/src/spec/overlay/nodegraph.overlay.json +31 -0
  495. package/src/spec/overlay/piechart.overlay.json +17 -0
  496. package/src/spec/overlay/table.overlay.json +103 -0
  497. package/src/spec/overlay/timeseries.overlay.json +21 -0
  498. package/src/spec/overlay/trend.overlay.json +17 -0
  499. package/src/spec/overlay/xychart.overlay.json +16 -0
  500. package/src/spec/overlay.ts +105 -0
  501. package/src/spec/schemas/azuremonitor.jsonschema.json +679 -0
  502. package/src/spec/schemas/barchart.jsonschema.json +419 -0
  503. package/src/spec/schemas/bargauge.jsonschema.json +224 -0
  504. package/src/spec/schemas/bigquery.jsonschema.json +290 -0
  505. package/src/spec/schemas/canvas.jsonschema.json +356 -0
  506. package/src/spec/schemas/cloudwatch.jsonschema.json +721 -0
  507. package/src/spec/schemas/dashboardv2.jsonschema.json +3387 -0
  508. package/src/spec/schemas/elasticsearch.jsonschema.json +1695 -0
  509. package/src/spec/schemas/expr.jsonschema.json +987 -0
  510. package/src/spec/schemas/geomap.jsonschema.json +220 -0
  511. package/src/spec/schemas/googlecloudmonitoring.jsonschema.json +488 -0
  512. package/src/spec/schemas/grafanapyroscope.jsonschema.json +93 -0
  513. package/src/spec/schemas/histogram.jsonschema.json +308 -0
  514. package/src/spec/schemas/nodegraph.jsonschema.json +77 -0
  515. package/src/spec/schemas/piechart.jsonschema.json +275 -0
  516. package/src/spec/schemas/statetimeline.jsonschema.json +324 -0
  517. package/src/spec/schemas/statushistory.jsonschema.json +292 -0
  518. package/src/spec/schemas/trend.jsonschema.json +449 -0
  519. package/src/spec/schemas/xychart.jsonschema.json +428 -0
  520. package/src/spec/schemas.gen.ts +40 -0
  521. package/src/spec/schemas.ts +30 -16
  522. package/src/spec/units.gen.ts +281 -0
  523. package/src/spec/units.ts +77 -0
  524. package/src/transformations.test.ts +65 -0
  525. package/src/transformations.ts +574 -0
  526. package/src/util.ts +8 -0
  527. package/src/validate-alerting.test.ts +255 -0
  528. package/src/validate-alerting.ts +430 -0
  529. package/src/validate-output.ts +427 -104
  530. package/src/validate.ts +28 -0
  531. package/src/validation.ts +27 -0
  532. package/src/variables.ts +120 -8
@@ -1,23 +1,55 @@
1
1
  /**
2
- * The checks behind GRAF101-GRAF107, as plain functions over built Grafana
3
- * output: dashboard JSON documents and provisioned datasources. The
2
+ * The checks behind GRAF101-GRAF110, GRAF111-GRAF114 and GRAF115, as plain
3
+ * functions over built Grafana output: dashboard JSON documents, provisioned
4
+ * datasources, dashboard providers and alerting provisioning files (the
5
+ * alerting checks are in `validate-alerting.ts`). The
4
6
  * post-synth checks run them over a build; anything else holding the same
5
7
  * JSON (a test, another lexicon embedding dashboards) can call them directly.
6
8
  *
7
9
  * Checks that join a dashboard against datasources (GRAF101, GRAF102) only
8
- * see the datasources passed in. In a build that is the build root being
9
- * built (chant #1939): with no datasource declared in it they report
10
- * nothing, and a datasource declared in another build root looks undeclared.
10
+ * see the datasources passed in: the ones provisioned and the ones declared
11
+ * with `ExternalDatasource`. In a build that is the build root being built
12
+ * (chant #1939). With none declared they warn that references cannot be
13
+ * checked; a datasource that exists in Grafana but is not declared looks
14
+ * undeclared, so declare it with `ExternalDatasource`.
11
15
  */
12
16
 
13
- import type { ProvisionedDatasource } from "./build";
14
- import { GRID_COLUMNS } from "./build";
15
- import { BUILTIN_DATASOURCE_UIDS } from "./datasource";
16
- import { isBuiltinVariable } from "./variables";
17
+ import type { ExternalDatasourceRecord, ProvisionedDatasource } from "./build";
18
+ import { DASHBOARDS_DIR, GRID_COLUMNS } from "./build";
19
+ import {
20
+ annotationsOf,
21
+ datasourceUses,
22
+ describePanel,
23
+ knownDatasources,
24
+ panelsOf,
25
+ variableReferences,
26
+ variablesOf,
27
+ type DatasourceRefJson,
28
+ type KnownDatasource,
29
+ } from "./datasource-refs";
30
+ import { isBuiltinVariable, MULTI_VALUE_KINDS } from "./variables";
17
31
  import { isValidUid } from "./util";
18
- import { validateDashboardSchema } from "./schema-validate";
19
-
20
- export type GrafanaIssueCode = "GRAF101" | "GRAF102" | "GRAF103" | "GRAF104" | "GRAF105" | "GRAF106" | "GRAF107";
32
+ import { schemaValidationUnavailable, validateDashboardSchema } from "./schema-validate";
33
+ import { checkGrafanaPromql, prometheusQueries } from "./promql-check";
34
+ import { checkAlertingIdentity, checkNotificationRefs, checkRuleDatasources, checkRulePromql, checkRuleQueries, type AlertingDoc } from "./validate-alerting";
35
+ import { closestGrafanaUnit, isGrafanaUnit } from "./spec/units";
36
+
37
+ export type GrafanaIssueCode =
38
+ | "GRAF101"
39
+ | "GRAF102"
40
+ | "GRAF103"
41
+ | "GRAF104"
42
+ | "GRAF105"
43
+ | "GRAF106"
44
+ | "GRAF107"
45
+ | "GRAF108"
46
+ | "GRAF109"
47
+ | "GRAF110"
48
+ | "GRAF111"
49
+ | "GRAF112"
50
+ | "GRAF113"
51
+ | "GRAF114"
52
+ | "GRAF115";
21
53
 
22
54
  export interface GrafanaIssue {
23
55
  code: GrafanaIssueCode;
@@ -35,57 +67,30 @@ export interface DashboardDoc {
35
67
 
36
68
  export interface GrafanaArtifacts {
37
69
  dashboards: DashboardDoc[];
70
+ /** Datasources the build provisions. */
38
71
  datasources: ProvisionedDatasource[];
72
+ /** Datasources the build references but does not provision (`ExternalDatasource`). */
73
+ externalDatasources?: ExternalDatasourceRecord[];
74
+ /** Entries of `providers:` in dashboard provisioning files, as written: any field may be missing. */
75
+ providers?: Array<Record<string, unknown>>;
76
+ /** Alerting provisioning files (rule groups, contact points, policies, mute timings, templates). */
77
+ alerting?: AlertingDoc[];
78
+ /** The folders the build resolved (its index's `folders`): each level of each dashboard's folder path, and every `Folder`. */
79
+ folders?: Array<{ uid: string; title: string; path?: string; parentUid?: string }>;
80
+ /** `deleteDatasources` entries of datasource provisioning files. */
81
+ deleteDatasources?: Array<{ name: string; orgId?: number }>;
39
82
  }
40
83
 
41
- type Json = Record<string, unknown>;
42
- interface Ref {
43
- type?: string;
44
- uid?: string;
45
- }
46
-
47
- interface PanelInfo {
48
- panel: Json;
49
- /** Panels inside a collapsed row are laid out in their own group. */
50
- group: string;
51
- }
84
+ export type { AlertingDoc };
52
85
 
53
- function panelsOf(dashboard: Json): PanelInfo[] {
54
- const out: PanelInfo[] = [];
55
- const top = Array.isArray(dashboard.panels) ? (dashboard.panels as Json[]) : [];
56
- for (const p of top) {
57
- if (!p || typeof p !== "object") continue;
58
- out.push({ panel: p, group: "" });
59
- if (p.type === "row" && Array.isArray(p.panels)) {
60
- for (const c of p.panels as Json[]) if (c && typeof c === "object") out.push({ panel: c, group: `row ${String(p.id)}` });
61
- }
62
- }
63
- return out;
64
- }
86
+ export { variableReferences };
65
87
 
66
- function describePanel(panel: Json): string {
67
- const title = typeof panel.title === "string" && panel.title ? `"${panel.title}"` : "(untitled)";
68
- return `panel ${title} (id ${String(panel.id ?? "?")})`;
69
- }
88
+ type Json = Record<string, unknown>;
70
89
 
71
90
  function dashName(d: Json): string {
72
91
  return `Dashboard "${String(d.title ?? d.uid ?? "?")}"`;
73
92
  }
74
93
 
75
- function variablesOf(d: Json): Json[] {
76
- const list = (d.templating as { list?: unknown } | undefined)?.list;
77
- return Array.isArray(list) ? (list as Json[]).filter((v) => v && typeof v === "object") : [];
78
- }
79
-
80
- const VAR_REF = /\$\{([A-Za-z_][A-Za-z0-9_]*)(?::[^}]*)?\}|\[\[([A-Za-z_][A-Za-z0-9_]*)(?::[^\]]*)?\]\]|\$([A-Za-z_][A-Za-z0-9_]*)/g;
81
-
82
- /** Every variable name referenced in a string. */
83
- export function variableReferences(text: string): string[] {
84
- const out: string[] = [];
85
- for (const m of text.matchAll(VAR_REF)) out.push(m[1] ?? m[2] ?? m[3]);
86
- return out;
87
- }
88
-
89
94
  function isVariableUid(uid: string | undefined): boolean {
90
95
  return typeof uid === "string" && uid.includes("$");
91
96
  }
@@ -101,79 +106,108 @@ function stringsIn(value: unknown, skip: ReadonlySet<string>, out: string[] = []
101
106
 
102
107
  const TARGET_SKIP = new Set(["refId", "datasource"]);
103
108
 
104
- /** Every datasource ref a panel makes, with where it is made. */
105
- function panelRefs(panel: Json): Array<{ ref: Ref; where: string }> {
106
- const out: Array<{ ref: Ref; where: string }> = [];
107
- const panelRef = panel.datasource as Ref | undefined;
109
+ /** Every datasource ref a panel or row writes itself, with where it is written. */
110
+ function panelRefs(panel: Json): Array<{ ref: DatasourceRefJson; where: string }> {
111
+ const out: Array<{ ref: DatasourceRefJson; where: string }> = [];
112
+ const panelRef = panel.datasource as DatasourceRefJson | undefined;
108
113
  if (panelRef && typeof panelRef === "object") out.push({ ref: panelRef, where: describePanel(panel) });
109
114
  const targets = Array.isArray(panel.targets) ? (panel.targets as Json[]) : [];
110
115
  for (const t of targets) {
111
- const r = t?.datasource as Ref | undefined;
116
+ const r = t?.datasource as DatasourceRefJson | undefined;
112
117
  if (r && typeof r === "object") out.push({ ref: r, where: `${describePanel(panel)} query ${String(t.refId ?? "?")}` });
113
118
  }
114
119
  return out;
115
120
  }
116
121
 
117
- function isPseudoRef(ref: Ref): boolean {
118
- return ref.type === "datasource" || ref.type === "grafana" || (ref.uid !== undefined && BUILTIN_DATASOURCE_UIDS.has(ref.uid));
122
+ /** The datasources a set of artifacts knows by uid: provisioned and external. */
123
+ export function knownDatasourcesOf(a: GrafanaArtifacts): Map<string, KnownDatasource> {
124
+ return knownDatasources(a.datasources, a.externalDatasources ?? []);
125
+ }
126
+
127
+ function listUids(known: ReadonlyMap<string, KnownDatasource>): string {
128
+ return [...known.keys()].map((k) => `"${k}"`).join(", ");
119
129
  }
120
130
 
121
131
  // ── GRAF101, GRAF102: datasource references ─────────────────────
122
132
 
123
133
  export function checkDatasourceRefs(a: GrafanaArtifacts): GrafanaIssue[] {
124
134
  const issues: GrafanaIssue[] = [];
125
- if (a.datasources.length === 0) return issues; // declared elsewhere, or not at all: see #1939
126
- const byUid = new Map(a.datasources.map((d) => [d.uid, d]));
135
+ const known = knownDatasourcesOf(a);
136
+ const types = new Set([...known.values()].map((k) => k.type));
127
137
  for (const { json: d } of a.dashboards) {
128
138
  const uid = String(d.uid ?? "");
129
- const vars = new Map(variablesOf(d).map((v) => [String(v.name), v]));
130
- const refs: Array<{ ref: Ref; where: string }> = [];
139
+ const push = (code: GrafanaIssueCode, severity: GrafanaIssue["severity"], message: string) =>
140
+ issues.push({ code, severity, message: `${dashName(d)} ${message}`, entity: uid });
141
+
131
142
  for (const { panel } of panelsOf(d)) {
132
143
  if (panel.type === "row") continue;
133
- refs.push(...panelRefs(panel));
134
144
  const targets = Array.isArray(panel.targets) ? (panel.targets as Json[]) : [];
135
145
  if (targets.length > 0 && !panel.datasource && targets.every((t) => !t?.datasource)) {
136
- issues.push({
137
- code: "GRAF101",
138
- severity: "warning",
139
- message: `${dashName(d)} ${describePanel(panel)} has queries but no datasource; Grafana will send them to whichever datasource is its default.`,
140
- entity: uid,
141
- });
146
+ push("GRAF101", "warning", `${describePanel(panel)} has queries but no datasource; Grafana will send them to whichever datasource is its default.`);
142
147
  }
143
148
  }
144
- for (const v of vars.values()) {
145
- if (v.type === "query" && v.datasource && typeof v.datasource === "object") refs.push({ ref: v.datasource as Ref, where: `variable "${String(v.name)}"` });
149
+
150
+ const uses = datasourceUses(d, known).filter((u) => u.ref !== undefined);
151
+ const dsVariables = variablesOf(d).filter((v) => v.type === "datasource" && typeof v.query === "string" && v.query);
152
+
153
+ // Nothing declared: the references can't be checked. Say so once per dashboard rather than staying silent.
154
+ if (known.size === 0) {
155
+ const uids = [...new Set(uses.flatMap((u) => (u.resolved.kind === "undeclared" ? [u.resolved.uid] : [])))];
156
+ const plugins = [...new Set(dsVariables.map((v) => String(v.query)))];
157
+ const what = [
158
+ ...(uids.length > 0 ? [`datasource uid${uids.length > 1 ? "s" : ""} ${uids.map((u) => `"${u}"`).join(", ")}`] : []),
159
+ ...(plugins.length > 0 ? [`datasource variables of type ${plugins.join(", ")}`] : []),
160
+ ];
161
+ if (what.length > 0) {
162
+ const them = uids.length + plugins.length > 1 ? "them" : "it";
163
+ push(
164
+ "GRAF101",
165
+ "warning",
166
+ `uses ${what.join(" and ")}, but the build declares no datasource, so GRAF101 and GRAF102 cannot check ${them}. Declare a Datasource to provision one, or an ExternalDatasource for one that already exists in Grafana.`,
167
+ );
168
+ }
146
169
  }
147
- for (const { ref, where } of refs) {
148
- if (isPseudoRef(ref) || ref.uid === undefined) continue;
149
- if (isVariableUid(ref.uid)) {
150
- const [name] = variableReferences(ref.uid);
151
- const v = name ? vars.get(name) : undefined;
152
- if (v && v.type === "datasource" && ref.type && v.query && v.query !== ref.type) {
153
- issues.push({
154
- code: "GRAF102",
155
- severity: "error",
156
- message: `${dashName(d)} ${where} expects a ${ref.type} datasource, but variable "${name}" chooses among ${String(v.query)} datasources.`,
157
- entity: uid,
158
- });
170
+
171
+ for (const { ref, where, resolved } of uses) {
172
+ if (resolved.kind === "variable") {
173
+ const v = variablesOf(d).find((x) => x.name === resolved.variable);
174
+ if (v && v.type === "datasource" && ref!.type && v.query && v.query !== ref!.type) {
175
+ push("GRAF102", "error", `${where} expects a ${ref!.type} datasource, but variable "${resolved.variable}" chooses among ${String(v.query)} datasources.`);
159
176
  }
160
- continue;
177
+ } else if (resolved.kind === "undeclared" && known.size > 0) {
178
+ push(
179
+ "GRAF101",
180
+ "error",
181
+ `${where} uses datasource uid "${resolved.uid}"${ref!.type ? ` (${ref!.type})` : ""}, which no declared Datasource or ExternalDatasource has. Declared: ${listUids(known)}.`,
182
+ );
183
+ } else if (resolved.kind === "declared" && ref!.type && ref!.type !== resolved.type) {
184
+ const ds = resolved.datasource;
185
+ push("GRAF102", "error", `${where} sends a ${ref!.type} query to ${ds.external ? "external " : ""}datasource "${ds.name ?? ds.uid}", which is ${ds.type}.`);
161
186
  }
162
- const declared = byUid.get(ref.uid);
163
- if (!declared) {
164
- issues.push({
165
- code: "GRAF101",
166
- severity: "error",
167
- message: `${dashName(d)} ${where} uses datasource uid "${ref.uid}"${ref.type ? ` (${ref.type})` : ""}, which no declared Datasource has. Declared: ${[...byUid.keys()].map((k) => `"${k}"`).join(", ")}.`,
168
- entity: uid,
169
- });
170
- } else if (ref.type && ref.type !== declared.type) {
171
- issues.push({
172
- code: "GRAF102",
173
- severity: "error",
174
- message: `${dashName(d)} ${where} sends a ${ref.type} query to datasource "${declared.name}", which is ${declared.type}.`,
175
- entity: uid,
176
- });
187
+ }
188
+
189
+ // An ExternalDatasource the provisioning file deletes (and does not provision again) is gone once Grafana provisions it.
190
+ const deleted = new Set((a.deleteDatasources ?? []).filter((x) => (x.orgId ?? 1) === 1).map((x) => x.name));
191
+ const provisioned = new Set(a.datasources.map((x) => x.name));
192
+ const gone = new Set([...known.values()].filter((k) => k.external && k.name !== undefined && deleted.has(k.name) && !provisioned.has(k.name)).map((k) => k.uid));
193
+ for (const { where, resolved } of uses) {
194
+ if (resolved.kind !== "declared" || !gone.has(resolved.datasource.uid)) continue;
195
+ push(
196
+ "GRAF101",
197
+ "error",
198
+ `${where} uses external datasource "${resolved.datasource.name}", which the datasource provisioning file deletes (deleteDatasources); Grafana removes it before the dashboard can use it.`,
199
+ );
200
+ }
201
+
202
+ // A datasource variable offers the datasources of its plugin type; with none declared it has nothing to choose.
203
+ if (known.size > 0) {
204
+ for (const v of dsVariables) {
205
+ if (types.has(String(v.query))) continue;
206
+ push(
207
+ "GRAF101",
208
+ "error",
209
+ `variable "${String(v.name)}" chooses among ${String(v.query)} datasources, but no declared Datasource or ExternalDatasource is of type ${String(v.query)}. Declared types: ${[...types].join(", ")}.`,
210
+ );
177
211
  }
178
212
  }
179
213
  }
@@ -210,10 +244,22 @@ export function checkVariables(a: GrafanaArtifacts): GrafanaIssue[] {
210
244
  }
211
245
  }
212
246
  for (const v of variablesOf(d)) {
247
+ const ref = v.datasource as DatasourceRefJson | undefined;
248
+ if (ref && typeof ref === "object" && isVariableUid(ref.uid)) {
249
+ for (const n of variableReferences(ref.uid!)) if (n !== v.name) report(n, `variable "${String(v.name)}" datasource`);
250
+ }
213
251
  if (v.type !== "query") continue;
214
- const names = new Set(stringsIn(v.query, new Set()).flatMap(variableReferences));
252
+ const names = new Set(stringsIn(v.query, new Set(["refId"])).flatMap(variableReferences));
215
253
  for (const n of names) if (n !== v.name) report(n, `variable "${String(v.name)}" query`);
216
254
  }
255
+ for (const an of annotationsOf(d)) {
256
+ const where = `annotation "${String(an.name ?? "?")}"`;
257
+ const ref = an.datasource as DatasourceRefJson | undefined;
258
+ if (ref && typeof ref === "object" && isVariableUid(ref.uid)) for (const n of variableReferences(ref.uid!)) report(n, `${where} datasource`);
259
+ // Grafana interpolates the query (`target`, or Prometheus's legacy `expr`), not the name, colour or title formats.
260
+ const names = new Set(stringsIn([an.target, an.expr], TARGET_SKIP).flatMap(variableReferences));
261
+ for (const n of names) report(n, `${where} query`);
262
+ }
217
263
  }
218
264
  return issues;
219
265
  }
@@ -235,8 +281,13 @@ export function checkDuplicates(a: GrafanaArtifacts): GrafanaIssue[] {
235
281
  for (const [uid, n] of dupes(a.dashboards, (d) => (typeof d.json.uid === "string" ? d.json.uid : undefined))) {
236
282
  push(`${n} dashboards share the uid "${uid}"; Grafana keeps one of them.`, uid);
237
283
  }
238
- for (const [uid, n] of dupes(a.datasources, (d) => d.uid)) push(`${n} datasources share the uid "${uid}".`, uid);
284
+ const allUids = [...a.datasources.map((d) => d.uid), ...(a.externalDatasources ?? []).map((d) => d.uid)];
285
+ for (const [uid, n] of dupes(allUids, (u) => u)) push(`${n} datasources share the uid "${uid}".`, uid);
239
286
  for (const [name, n] of dupes(a.datasources, (d) => d.name)) push(`${n} datasources share the name "${name}"; Grafana needs names to be unique.`, name);
287
+ for (const [uid, n] of dupes(a.folders ?? [], (f) => f.uid)) {
288
+ const paths = (a.folders ?? []).filter((f) => f.uid === uid).map((f) => `"${f.path ?? f.title}"`);
289
+ push(`${n} folders share the uid "${uid}" (${paths.join(", ")}); Grafana keeps one folder per uid, so the API applier refuses the build. Give one a Folder with its own uid.`, uid);
290
+ }
240
291
  for (const { json: d } of a.dashboards) {
241
292
  const uid = String(d.uid ?? "");
242
293
  const panels = panelsOf(d).map((p) => p.panel);
@@ -339,18 +390,51 @@ export function checkIdentity(a: GrafanaArtifacts): GrafanaIssue[] {
339
390
  });
340
391
  }
341
392
  }
393
+ for (const f of a.folders ?? []) {
394
+ const where = `Folder "${f.path ?? f.title}"`;
395
+ if (!isValidUid(f.uid)) {
396
+ issues.push({ code: "GRAF106", severity: "error", message: `${where} has uid "${f.uid}"; Grafana accepts 1-40 letters, digits, "-" and "_".`, entity: f.uid });
397
+ } else if (f.uid === "general") {
398
+ issues.push({
399
+ code: "GRAF106",
400
+ severity: "error",
401
+ message: `${where} has uid "general", which Grafana keeps for its root (the General folder) and refuses for a folder. Leave folder out to put a dashboard in General, or give the folder another uid.`,
402
+ entity: f.uid,
403
+ });
404
+ }
405
+ }
406
+ for (const ds of a.externalDatasources ?? []) {
407
+ if (!isValidUid(ds.uid)) {
408
+ issues.push({
409
+ code: "GRAF106",
410
+ severity: "error",
411
+ message: `ExternalDatasource "${ds.name ?? ds.uid}" has uid "${ds.uid}"; Grafana accepts 1-40 letters, digits, "-" and "_".`,
412
+ entity: ds.name ?? ds.uid,
413
+ });
414
+ }
415
+ }
342
416
  return issues;
343
417
  }
344
418
 
345
419
  // ── GRAF107: the pinned schemas ─────────────────────────────────
346
420
 
421
+ /**
422
+ * A value the pinned schemas (with the correction overlay) do not allow is
423
+ * an error; a key they do not know is a warning, since newer Grafana
424
+ * versions add keys the pin has not caught up with.
425
+ */
347
426
  export function checkSchema(a: GrafanaArtifacts): GrafanaIssue[] {
348
427
  const issues: GrafanaIssue[] = [];
428
+ if (a.dashboards.length === 0) return issues;
429
+ const unavailable = schemaValidationUnavailable();
430
+ if (unavailable) {
431
+ return [{ code: "GRAF107", severity: "warning", message: `Dashboards were not checked against the Grafana schema: ${unavailable}.` }];
432
+ }
349
433
  for (const { json: d } of a.dashboards) {
350
434
  for (const p of validateDashboardSchema(d)) {
351
435
  issues.push({
352
436
  code: "GRAF107",
353
- severity: "error",
437
+ severity: p.severity,
354
438
  message: `${dashName(d)} ${p.path}: ${p.message} (Grafana schema).`,
355
439
  entity: String(d.uid ?? ""),
356
440
  });
@@ -359,6 +443,237 @@ export function checkSchema(a: GrafanaArtifacts): GrafanaIssue[] {
359
443
  return issues;
360
444
  }
361
445
 
446
+ // ── GRAF108: PromQL syntax ──────────────────────────────────────
447
+
448
+ /**
449
+ * Each panel query, query variable and alert rule query that reaches a
450
+ * Prometheus datasource is parsed as PromQL, with its template variables replaced by placeholders
451
+ * first (see `promql-check.ts`). A query whose datasource can't be told is
452
+ * not parsed.
453
+ */
454
+ export function checkPromqlSyntax(a: GrafanaArtifacts): GrafanaIssue[] {
455
+ const issues: GrafanaIssue[] = [];
456
+ const known = knownDatasourcesOf(a);
457
+ for (const { json: d } of a.dashboards) {
458
+ for (const { where, expr } of prometheusQueries(d, known)) {
459
+ const checked = checkGrafanaPromql(expr);
460
+ if (checked.ok) continue;
461
+ issues.push({ code: "GRAF108", severity: "error", message: `${dashName(d)} ${where} is not valid PromQL: ${checked.message}.`, entity: String(d.uid ?? "") });
462
+ }
463
+ }
464
+ issues.push(...checkRulePromql(a.alerting ?? [], known));
465
+ return issues;
466
+ }
467
+
468
+ // ── GRAF110: repeats ────────────────────────────────────────────
469
+
470
+ /**
471
+ * A panel or row repeated over a variable Grafana cannot repeat over, or
472
+ * one that can only ever hold one value. Grafana repeats over a query,
473
+ * custom, datasource or group by variable (a `MultiValueVariable` in
474
+ * @grafana/scenes), once per selected value. Over any other kind it logs an
475
+ * error and shows the panel once; over a query, custom or datasource
476
+ * variable with neither `multi` nor `includeAll`, there is only ever one
477
+ * value to repeat for. A repeat naming no variable is GRAF103's.
478
+ */
479
+ const KIND_NAMES: Readonly<Record<string, string>> = { adhoc: "an ad hoc", interval: "an interval", groupby: "a group by" };
480
+
481
+ export function checkRepeats(a: GrafanaArtifacts): GrafanaIssue[] {
482
+ const issues: GrafanaIssue[] = [];
483
+ for (const { json: d } of a.dashboards) {
484
+ const variables = new Map(variablesOf(d).map((v) => [String(v.name), v]));
485
+ for (const { panel } of panelsOf(d)) {
486
+ if (typeof panel.repeat !== "string" || panel.repeat === "") continue;
487
+ const v = variables.get(panel.repeat);
488
+ if (!v) continue;
489
+ const where = panel.type === "row" ? `row "${String(panel.title ?? "")}"` : describePanel(panel);
490
+ const type = String(v.type);
491
+ const kind = `${KIND_NAMES[type] ?? `a ${type}`} variable`;
492
+ let why: string | undefined;
493
+ if (!MULTI_VALUE_KINDS.has(type)) {
494
+ why = `${kind}, which Grafana cannot repeat over: it shows the ${panel.type === "row" ? "row" : "panel"} once. Repeat over a query, custom, datasource or group by variable`;
495
+ } else if (type !== "groupby" && v.multi !== true && v.includeAll !== true) {
496
+ why = `${kind} with neither multi nor includeAll, so it only ever holds one value and the ${panel.type === "row" ? "row" : "panel"} shows once. Set multi or includeAll on it`;
497
+ }
498
+ if (why) {
499
+ issues.push({ code: "GRAF110", severity: "warning", message: `${dashName(d)} ${where} repeats over $${panel.repeat}, ${why}.`, entity: String(d.uid ?? "") });
500
+ }
501
+ }
502
+ }
503
+ return issues;
504
+ }
505
+
506
+ // ── GRAF115: units ──────────────────────────────────────────────
507
+
508
+ /** Every unit a panel sets, with where: field defaults, `unit` overrides, heatmap axes and cells, and a legacy graph panel's y-axes. */
509
+ export function panelUnits(panel: Json): Array<{ where: string; unit: string }> {
510
+ const out: Array<{ where: string; unit: string }> = [];
511
+ const add = (where: string, unit: unknown) => {
512
+ if (typeof unit === "string") out.push({ where, unit });
513
+ };
514
+ const fieldConfig = panel.fieldConfig as Json | undefined;
515
+ add("fieldConfig.defaults.unit", (fieldConfig?.defaults as Json | undefined)?.unit);
516
+ const overrides = Array.isArray(fieldConfig?.overrides) ? (fieldConfig.overrides as Json[]) : [];
517
+ overrides.forEach((o, i) => {
518
+ const props = Array.isArray(o?.properties) ? (o.properties as Json[]) : [];
519
+ props.forEach((p, j) => {
520
+ if (p?.id === "unit") add(`fieldConfig.overrides[${i}].properties[${j}]`, p.value);
521
+ });
522
+ });
523
+ const options = panel.options as Json | undefined;
524
+ add("options.yAxis.unit", (options?.yAxis as Json | undefined)?.unit);
525
+ add("options.cellValues.unit", (options?.cellValues as Json | undefined)?.unit);
526
+ if (Array.isArray(panel.yaxes)) (panel.yaxes as Json[]).forEach((y, i) => add(`yaxes[${i}].format`, y?.format));
527
+ return out;
528
+ }
529
+
530
+ /**
531
+ * A unit Grafana doesn't register (at v13.2.2) and that isn't a custom
532
+ * `<kind>:` unit is shown as a literal suffix, so `"byte"` renders `5 byte`.
533
+ * A warning: it renders, just not as meant.
534
+ */
535
+ export function checkUnits(a: GrafanaArtifacts): GrafanaIssue[] {
536
+ const issues: GrafanaIssue[] = [];
537
+ for (const { json: d } of a.dashboards) {
538
+ const panels: Array<{ panel: Json; label: string }> = panelsOf(d).map(({ panel }) => ({ panel, label: describePanel(panel) }));
539
+ const elements = d.__elements;
540
+ if (elements && typeof elements === "object" && !Array.isArray(elements)) {
541
+ for (const [key, element] of Object.entries(elements as Json)) {
542
+ const model = element && typeof element === "object" ? (element as Json).model : undefined;
543
+ if (model && typeof model === "object" && !Array.isArray(model)) panels.push({ panel: model as Json, label: `library panel "${key}"` });
544
+ }
545
+ }
546
+ for (const { panel, label } of panels) {
547
+ if (panel.type === "row") continue;
548
+ for (const { where, unit } of panelUnits(panel)) {
549
+ if (isGrafanaUnit(unit)) continue;
550
+ const guess = closestGrafanaUnit(unit);
551
+ const hint = guess ? `Did you mean "${guess}"? ` : "";
552
+ issues.push({
553
+ code: "GRAF115",
554
+ severity: "warning",
555
+ message: `${dashName(d)} ${label} ${where}: unit "${unit}" is not one Grafana knows, so it is shown as a literal suffix. ${hint}For a custom unit write "suffix:${unit}" (or prefix:, si:, count:, currency:, time:).`,
556
+ entity: String(d.uid ?? ""),
557
+ });
558
+ }
559
+ }
560
+ }
561
+ return issues;
562
+ }
563
+
564
+ // ── GRAF109: where dashboard provisioning puts dashboards ───────
565
+
566
+ /** Grafana's default `[folder] max_nested_folder_depth`, and the most it can be raised to. */
567
+ const DEFAULT_FOLDER_DEPTH = 4;
568
+ const MAX_FOLDER_DEPTH = 7;
569
+
570
+ /** The folder levels a dashboard file sits in under `dashboards/`, e.g. `["Platform", "Kubernetes"]`. */
571
+ function folderLevels(source: string | undefined): string[] {
572
+ if (!source?.startsWith(`${DASHBOARDS_DIR}/`)) return [];
573
+ return source.slice(DASHBOARDS_DIR.length + 1).split("/").slice(0, -1);
574
+ }
575
+
576
+ interface ProviderView {
577
+ name: string;
578
+ orgId: number;
579
+ path: string;
580
+ folder: string;
581
+ folderUid: string;
582
+ fromFiles: boolean;
583
+ }
584
+
585
+ function providerView(p: Record<string, unknown>): ProviderView {
586
+ const options = (p.options && typeof p.options === "object" ? p.options : {}) as Json;
587
+ const path = typeof options.path === "string" ? options.path : typeof options.folder === "string" ? options.folder : "";
588
+ return {
589
+ name: typeof p.name === "string" ? p.name : "?",
590
+ orgId: typeof p.orgId === "number" && p.orgId !== 0 ? p.orgId : 1,
591
+ path: path.replace(/\/+$/, "") || "/",
592
+ folder: typeof p.folder === "string" ? p.folder : "",
593
+ folderUid: typeof p.folderUid === "string" ? p.folderUid : "",
594
+ fromFiles: options.foldersFromFilesStructure === true,
595
+ };
596
+ }
597
+
598
+ /** Whether a provider reading `a` also reads `b`: Grafana walks a provider's path recursively. */
599
+ function contains(a: string, b: string): boolean {
600
+ return a === b || a === "/" || b.startsWith(`${a}/`);
601
+ }
602
+
603
+ function listDashboards(ds: Array<{ d: Json; levels: string[] }>): string {
604
+ const shown = ds.slice(0, 3).map(({ d, levels }) => `"${String(d.title ?? d.uid ?? "?")}" (${levels.join("/")})`);
605
+ return ds.length > 3 ? `${shown.join(", ")} and ${ds.length - 3} more` : shown.join(", ");
606
+ }
607
+
608
+ export function checkProvisioning(a: GrafanaArtifacts): GrafanaIssue[] {
609
+ const issues: GrafanaIssue[] = [];
610
+ const push = (severity: GrafanaIssue["severity"], message: string, entity?: string) => issues.push({ code: "GRAF109", severity, message, entity });
611
+ const providers = (a.providers ?? []).map(providerView);
612
+ const foldered = a.dashboards.map(({ source, json }) => ({ d: json, levels: folderLevels(source) })).filter((x) => x.levels.length > 0);
613
+
614
+ // Two providers reading the same files, in the same org.
615
+ for (let i = 0; i < providers.length; i++) {
616
+ for (let j = i + 1; j < providers.length; j++) {
617
+ const [p, q] = [providers[i], providers[j]];
618
+ if (p.orgId !== q.orgId) continue;
619
+ const shared = contains(p.path, q.path) ? q.path : contains(q.path, p.path) ? p.path : undefined;
620
+ if (shared === undefined) continue;
621
+ push(
622
+ "error",
623
+ `Dashboard providers "${p.name}" and "${q.name}" both load the dashboards under ${shared}. Grafana provisions each of them twice, then takes database writes away from both providers, so later changes to the files never reach Grafana. Give each provider its own path.`,
624
+ q.name,
625
+ );
626
+ }
627
+ }
628
+
629
+ // A provider mapping directories to folders that also names a folder.
630
+ for (const p of providers) {
631
+ if (!p.fromFiles || (p.folder === "" && p.folderUid === "")) continue;
632
+ if (p.folder !== "" && p.folderUid !== "") {
633
+ push("error", `Dashboard provider "${p.name}" sets folder, folderUid and foldersFromFilesStructure; Grafana refuses to start it. Drop folder and folderUid, or foldersFromFilesStructure.`, p.name);
634
+ } else {
635
+ const which = p.folder !== "" ? `folder "${p.folder}"` : `folderUid "${p.folderUid}"`;
636
+ push(
637
+ "warning",
638
+ `Dashboard provider "${p.name}" sets ${which} and foldersFromFilesStructure. Grafana files each dashboard by its directory and the top-level ones in General, so ${which} is not used${p.folderUid !== "" ? " (before Grafana 13.1 every directory resolves to that one folder uid instead)" : ""}. Drop one of the two.`,
639
+ p.name,
640
+ );
641
+ }
642
+ }
643
+
644
+ // Dashboards with a folder that no provider maps from directories.
645
+ if (foldered.length > 0 && providers.length > 0 && !providers.some((p) => p.fromFiles)) {
646
+ const where = providers.map((p) => `"${p.name}" puts every dashboard in ${p.folder !== "" ? `folder "${p.folder}"` : p.folderUid !== "" ? `folder uid "${p.folderUid}"` : "General"}`).join("; ");
647
+ push(
648
+ "warning",
649
+ `${foldered.length === 1 ? "A dashboard declares a folder" : `${foldered.length} dashboards declare a folder`}, ${listDashboards(foldered)}, but no dashboard provider sets foldersFromFilesStructure: ${where}. Set foldersFromFilesStructure: true on the provider (and drop its folder), or leave the dashboards' folder out.`,
650
+ );
651
+ }
652
+
653
+ // Nested folders, where a provider does map directories to folders.
654
+ if (providers.some((p) => p.fromFiles)) {
655
+ for (const { d, levels } of foldered) {
656
+ if (levels.length < 2) continue;
657
+ const uid = String(d.uid ?? "");
658
+ const path = levels.join("/");
659
+ if (levels.length > MAX_FOLDER_DEPTH) {
660
+ push("error", `${dashName(d)} is in folder "${path}", ${levels.length} levels deep; Grafana nests at most ${MAX_FOLDER_DEPTH}, and the provider stops with an error when it reaches it.`, uid);
661
+ } else {
662
+ const depth =
663
+ levels.length > DEFAULT_FOLDER_DEPTH
664
+ ? ` It is ${levels.length} levels deep, past Grafana's default max_nested_folder_depth of ${DEFAULT_FOLDER_DEPTH}: raise it in [folder], or the provider stops with an error when it reaches it.`
665
+ : "";
666
+ push(
667
+ "warning",
668
+ `${dashName(d)} is in nested folder "${path}". Grafana 13.1 and later create the folders inside one another; Grafana 12.4 and 13.0 use only the last level and put it in a top-level folder "${levels[levels.length - 1]}".${depth}`,
669
+ uid,
670
+ );
671
+ }
672
+ }
673
+ }
674
+ return issues;
675
+ }
676
+
362
677
  const BY_CODE: Record<GrafanaIssueCode, (a: GrafanaArtifacts) => GrafanaIssue[]> = {
363
678
  GRAF101: (a) => checkDatasourceRefs(a).filter((i) => i.code === "GRAF101"),
364
679
  GRAF102: (a) => checkDatasourceRefs(a).filter((i) => i.code === "GRAF102"),
@@ -367,6 +682,14 @@ const BY_CODE: Record<GrafanaIssueCode, (a: GrafanaArtifacts) => GrafanaIssue[]>
367
682
  GRAF105: checkGrid,
368
683
  GRAF106: checkIdentity,
369
684
  GRAF107: checkSchema,
685
+ GRAF108: checkPromqlSyntax,
686
+ GRAF109: checkProvisioning,
687
+ GRAF110: checkRepeats,
688
+ GRAF111: (a) => checkRuleQueries(a.alerting ?? []),
689
+ GRAF112: (a) => checkRuleDatasources(a.alerting ?? [], knownDatasourcesOf(a)),
690
+ GRAF113: (a) => checkNotificationRefs(a.alerting ?? []),
691
+ GRAF114: (a) => checkAlertingIdentity(a.alerting ?? []),
692
+ GRAF115: checkUnits,
370
693
  };
371
694
 
372
695
  /** The issues one check finds. */