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