@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.
- package/README.md +24 -8
- 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/annotations.d.ts +61 -0
- package/dist/annotations.d.ts.map +1 -0
- package/dist/api/apply.d.ts +160 -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 +63 -0
- package/dist/api/fake-grafana.d.ts.map +1 -0
- package/dist/api/folders.d.ts +146 -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 +55 -6
- 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/generate.d.ts +7 -1
- package/dist/codegen/generate.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 +11 -3
- 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 +51 -9
- package/dist/dashboard.d.ts.map +1 -1
- package/dist/datasource-refs.d.ts +99 -0
- package/dist/datasource-refs.d.ts.map +1 -0
- package/dist/datasource-settings.d.ts +834 -0
- package/dist/datasource-settings.d.ts.map +1 -0
- package/dist/datasource.d.ts +82 -11
- 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 +47 -0
- package/dist/deep-observe.d.ts.map +1 -0
- package/dist/describe-resources.d.ts +50 -0
- package/dist/describe-resources.d.ts.map +1 -0
- package/dist/detect.d.ts +32 -3
- 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/folder.d.ts +68 -0
- package/dist/folder.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 +89 -0
- package/dist/import/mappings.d.ts.map +1 -0
- package/dist/import/model.d.ts +105 -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 +125 -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 +41 -0
- package/dist/import/testdata/fixtures.d.ts.map +1 -0
- package/dist/import/v2.d.ts +73 -0
- package/dist/import/v2.d.ts.map +1 -0
- package/dist/index.d.ts +31 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/init-templates.d.ts +27 -0
- package/dist/init-templates.d.ts.map +1 -0
- package/dist/integrity.json +22 -12
- 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/graf104.d.ts +2 -2
- package/dist/lint/post-synth/graf106.d.ts +2 -2
- 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/graf110.d.ts +8 -0
- package/dist/lint/post-synth/graf110.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 +205 -0
- package/dist/okf/index.md +53 -4
- 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/GRAF104.md +2 -2
- package/dist/okf/rules/GRAF106.md +2 -2
- package/dist/okf/rules/GRAF108.md +11 -0
- package/dist/okf/rules/GRAF109.md +15 -0
- package/dist/okf/rules/GRAF110.md +11 -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/AdhocVariable.md +9 -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/AzureMonitorQuery.md +9 -0
- package/dist/okf/types/BarChartPanel.md +9 -0
- package/dist/okf/types/BarGaugePanel.md +9 -0
- package/dist/okf/types/BigQueryQuery.md +9 -0
- package/dist/okf/types/CanvasPanel.md +9 -0
- package/dist/okf/types/ClassicConditionsExpression.md +9 -0
- package/dist/okf/types/CloudMonitoringQuery.md +9 -0
- package/dist/okf/types/CloudWatchQuery.md +9 -0
- package/dist/okf/types/ContactPoint.md +14 -0
- package/dist/okf/types/Dashboard.md +2 -1
- package/dist/okf/types/Datasource.md +3 -2
- package/dist/okf/types/DatasourceProvisioning.md +9 -0
- package/dist/okf/types/ElasticsearchQuery.md +9 -0
- package/dist/okf/types/ExternalDatasource.md +15 -0
- package/dist/okf/types/FlameGraphPanel.md +9 -0
- package/dist/okf/types/Folder.md +9 -0
- package/dist/okf/types/GeomapPanel.md +9 -0
- package/dist/okf/types/GroupByVariable.md +9 -0
- package/dist/okf/types/HistogramPanel.md +9 -0
- package/dist/okf/types/MSSQLQuery.md +9 -0
- package/dist/okf/types/MathExpression.md +9 -0
- package/dist/okf/types/MuteTiming.md +13 -0
- package/dist/okf/types/MySQLQuery.md +9 -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/PostgresQuery.md +9 -0
- package/dist/okf/types/PyroscopeQuery.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/SwitchVariable.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 +86 -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 +78 -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-models.d.ts +95 -0
- package/dist/query-models.d.ts.map +1 -0
- package/dist/query.d.ts +116 -18
- 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/graf104.ts +3 -3
- package/dist/rules/graf106.ts +3 -3
- package/dist/rules/graf107.ts +1 -1
- package/dist/rules/graf108.ts +17 -0
- package/dist/rules/graf109.ts +17 -0
- package/dist/rules/graf110.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 +27 -10
- package/dist/rules/literal-secret.ts +60 -6
- package/dist/schema/azuremonitor.gen.d.ts +354 -0
- package/dist/schema/azuremonitor.gen.d.ts.map +1 -0
- 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/bigquery.gen.d.ts +93 -0
- package/dist/schema/bigquery.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/cloudwatch.gen.d.ts +394 -0
- package/dist/schema/cloudwatch.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/elasticsearch.gen.d.ts +444 -0
- package/dist/schema/elasticsearch.gen.d.ts.map +1 -0
- 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/googlecloudmonitoring.gen.d.ts +312 -0
- package/dist/schema/googlecloudmonitoring.gen.d.ts.map +1 -0
- package/dist/schema/grafanapyroscope.gen.d.ts +60 -0
- package/dist/schema/grafanapyroscope.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 +18 -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 +35 -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 +39 -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-operations.md +108 -0
- package/dist/skills/chant-grafana-provisioning.md +3 -3
- package/dist/skills/chant-grafana.md +15 -8
- 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 +21 -13
- package/dist/spec/schemas.d.ts.map +1 -1
- package/dist/spec/schemas.gen.d.ts +3 -0
- package/dist/spec/schemas.gen.d.ts.map +1 -0
- 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 +62 -9
- package/dist/validate-output.d.ts.map +1 -1
- package/dist/validate.d.ts.map +1 -1
- package/dist/validation.d.ts +14 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/variables.d.ts +109 -7
- 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/annotations.ts +84 -0
- package/src/api/apply.test.ts +256 -0
- package/src/api/apply.ts +394 -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 +190 -0
- package/src/api/folders.ts +339 -0
- package/src/api/library-panels.ts +101 -0
- package/src/build.test.ts +351 -10
- package/src/build.ts +294 -55
- package/src/catalog.ts +104 -2
- package/src/codegen/docs.ts +50 -10
- package/src/codegen/generate.ts +27 -1
- 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 +14 -2
- 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 +68 -9
- package/src/datasource-refs.test.ts +56 -0
- package/src/datasource-refs.ts +213 -0
- package/src/datasource-settings.test.ts +106 -0
- package/src/datasource-settings.ts +941 -0
- package/src/datasource.ts +123 -11
- package/src/deep-observe-hooks.ts +267 -0
- package/src/deep-observe.test.ts +311 -0
- package/src/deep-observe.ts +187 -0
- package/src/describe-resources.test.ts +271 -0
- package/src/describe-resources.ts +244 -0
- package/src/detect.ts +82 -5
- package/src/export-resources.test.ts +129 -0
- package/src/export-resources.ts +84 -0
- package/src/folder.ts +118 -0
- package/src/generated/lexicon-grafana.json +205 -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 +130 -0
- package/src/import/generator.test.ts +210 -0
- package/src/import/generator.ts +463 -0
- package/src/import/live-export.ts +264 -0
- package/src/import/mappings.ts +336 -0
- package/src/import/model.ts +117 -0
- package/src/import/normalize-alerting.ts +101 -0
- package/src/import/normalize.test.ts +117 -0
- package/src/import/normalize.ts +393 -0
- package/src/import/parser.test.ts +516 -0
- package/src/import/parser.ts +1387 -0
- package/src/import/report.ts +62 -0
- package/src/import/roundtrip.test.ts +563 -0
- package/src/import/testdata/fixtures.ts +137 -0
- package/src/import/v2.test.ts +292 -0
- package/src/import/v2.ts +804 -0
- package/src/import.e2e.test.ts +31 -84
- package/src/import.test.ts +9 -3
- package/src/index.ts +308 -14
- package/src/init-templates.test.ts +74 -0
- package/src/init-templates.ts +349 -0
- package/src/k8s.test.ts +98 -0
- package/src/k8s.ts +197 -0
- package/src/lint/audit-catalog.ts +68 -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/graf104.ts +3 -3
- package/src/lint/post-synth/graf106.ts +3 -3
- 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/graf110.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 +27 -10
- package/src/lint/post-synth/index.ts +16 -0
- package/src/lint/post-synth/post-synth.test.ts +333 -12
- package/src/lint/rules/literal-secret.ts +60 -6
- package/src/lint/rules/rules.test.ts +27 -0
- package/src/load-cost.test.ts +35 -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 +184 -0
- package/src/op/activities/grafana-apply.test.ts +243 -0
- package/src/op/activities/grafana-apply.ts +197 -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 +122 -0
- package/src/panel-options.ts +66 -0
- package/src/panels.test.ts +53 -0
- package/src/panels.ts +148 -10
- package/src/pin.ts +69 -6
- package/src/plugin.test.ts +12 -2
- package/src/plugin.ts +75 -0
- package/src/promql-check.test.ts +108 -0
- package/src/promql-check.ts +193 -0
- package/src/query-models.ts +94 -0
- package/src/query.test.ts +166 -0
- package/src/query.ts +190 -20
- package/src/schema/azuremonitor.gen.ts +381 -0
- package/src/schema/barchart.gen.ts +221 -0
- package/src/schema/bargauge.gen.ts +111 -0
- package/src/schema/bigquery.gen.ts +110 -0
- package/src/schema/canvas.gen.ts +176 -0
- package/src/schema/cloudwatch.gen.ts +423 -0
- package/src/schema/dashboard.gen.ts +206 -22
- package/src/schema/elasticsearch.gen.ts +508 -0
- 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/googlecloudmonitoring.gen.ts +330 -0
- package/src/schema/grafanapyroscope.gen.ts +66 -0
- package/src/schema/heatmap.gen.ts +2 -1
- package/src/schema/histogram.gen.ts +151 -0
- package/src/schema/index.ts +18 -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 +41 -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 +199 -57
- 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 +34 -0
- package/src/skills/chant-grafana-alerting.md +49 -0
- package/src/skills/chant-grafana-operations.md +108 -0
- package/src/skills/chant-grafana-provisioning.md +3 -3
- package/src/skills/chant-grafana.md +15 -8
- 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 +103 -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/azuremonitor.jsonschema.json +679 -0
- package/src/spec/schemas/barchart.jsonschema.json +419 -0
- package/src/spec/schemas/bargauge.jsonschema.json +224 -0
- package/src/spec/schemas/bigquery.jsonschema.json +290 -0
- package/src/spec/schemas/canvas.jsonschema.json +356 -0
- package/src/spec/schemas/cloudwatch.jsonschema.json +721 -0
- package/src/spec/schemas/dashboardv2.jsonschema.json +3387 -0
- package/src/spec/schemas/elasticsearch.jsonschema.json +1695 -0
- package/src/spec/schemas/expr.jsonschema.json +987 -0
- package/src/spec/schemas/geomap.jsonschema.json +220 -0
- package/src/spec/schemas/googlecloudmonitoring.jsonschema.json +488 -0
- package/src/spec/schemas/grafanapyroscope.jsonschema.json +93 -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.gen.ts +40 -0
- package/src/spec/schemas.ts +30 -16
- 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 +430 -0
- package/src/validate-output.ts +427 -104
- package/src/validate.ts +28 -0
- package/src/validation.ts +27 -0
- package/src/variables.ts +120 -8
package/src/validate-output.ts
CHANGED
|
@@ -1,23 +1,55 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The checks behind GRAF101-
|
|
3
|
-
* output: dashboard JSON documents
|
|
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
|
|
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 {
|
|
16
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
105
|
-
function panelRefs(panel: Json): Array<{ ref:
|
|
106
|
-
const out: Array<{ ref:
|
|
107
|
-
const panelRef = panel.datasource as
|
|
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
|
|
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
|
-
|
|
118
|
-
|
|
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
|
-
|
|
126
|
-
const
|
|
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
|
|
130
|
-
|
|
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
|
-
|
|
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
|
-
|
|
145
|
-
|
|
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
|
-
|
|
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
|
-
});
|
|
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
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
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:
|
|
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. */
|