@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
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { datasourceUses, knownDatasources, resolveDatasourceRef } from "./datasource-refs";
|
|
3
|
+
|
|
4
|
+
const known = knownDatasources([{ name: "Prometheus", type: "prometheus", uid: "prom" }], [{ type: "loki", uid: "logs", name: "Logs" }]);
|
|
5
|
+
|
|
6
|
+
function dashboard(panels: unknown[], variables: unknown[] = []) {
|
|
7
|
+
return { uid: "d", title: "D", panels, templating: { list: variables } };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
describe("resolveDatasourceRef", () => {
|
|
11
|
+
const vars = new Map([["ds", { type: "datasource", name: "ds", query: "prometheus" }]]);
|
|
12
|
+
|
|
13
|
+
test("resolves provisioned and external datasources to their plugin type", () => {
|
|
14
|
+
expect(resolveDatasourceRef({ uid: "prom" }, vars, known)).toMatchObject({ kind: "declared", type: "prometheus", datasource: { external: false } });
|
|
15
|
+
expect(resolveDatasourceRef({ type: "loki", uid: "logs" }, vars, known)).toMatchObject({ kind: "declared", type: "loki", datasource: { external: true, name: "Logs" } });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("resolves a datasource variable to its plugin type", () => {
|
|
19
|
+
expect(resolveDatasourceRef({ uid: "${ds}" }, vars, known)).toEqual({ kind: "variable", variable: "ds", declared: true, type: "prometheus" });
|
|
20
|
+
expect(resolveDatasourceRef({ type: "tempo", uid: "$other" }, vars, known)).toEqual({ kind: "variable", variable: "other", declared: false, type: "tempo" });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("separates Grafana's pseudo-datasources, unknown uids and no ref", () => {
|
|
24
|
+
expect(resolveDatasourceRef({ type: "datasource", uid: "-- Mixed --" }, vars, known).kind).toBe("builtin");
|
|
25
|
+
expect(resolveDatasourceRef({ type: "prometheus", uid: "gone" }, vars, known)).toEqual({ kind: "undeclared", uid: "gone", type: "prometheus" });
|
|
26
|
+
expect(resolveDatasourceRef(undefined, vars, known)).toEqual({ kind: "default" });
|
|
27
|
+
expect(resolveDatasourceRef({ type: "prometheus" }, vars, known)).toEqual({ kind: "default", type: "prometheus" });
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe("datasourceUses", () => {
|
|
32
|
+
test("a query without its own ref goes where its panel's goes", () => {
|
|
33
|
+
const d = dashboard([{ type: "stat", id: 1, title: "p", datasource: { uid: "prom" }, targets: [{ refId: "A", expr: "up" }, { refId: "B", datasource: { uid: "logs" } }] }]);
|
|
34
|
+
const queries = datasourceUses(d, known).filter((u) => u.kind === "query");
|
|
35
|
+
expect(queries.map((u) => [u.target?.refId, u.ref === undefined, u.resolved.type])).toEqual([
|
|
36
|
+
["A", true, "prometheus"],
|
|
37
|
+
["B", false, "loki"],
|
|
38
|
+
]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("a query under a -- Mixed -- panel with no ref goes to the default datasource", () => {
|
|
42
|
+
const d = dashboard([{ type: "table", id: 1, datasource: { type: "datasource", uid: "-- Mixed --" }, targets: [{ refId: "A" }] }]);
|
|
43
|
+
expect(datasourceUses(d, known).find((u) => u.kind === "query")?.resolved).toEqual({ kind: "default" });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("covers panels in collapsed rows and query variables, and skips rows", () => {
|
|
47
|
+
const d = dashboard(
|
|
48
|
+
[{ type: "row", id: 1, collapsed: true, datasource: { uid: "prom" }, panels: [{ type: "stat", id: 2, datasource: { uid: "logs" }, targets: [] }] }],
|
|
49
|
+
[{ type: "query", name: "job", datasource: { uid: "prom" }, query: "label_values(job)" }],
|
|
50
|
+
);
|
|
51
|
+
expect(datasourceUses(d, known).map((u) => [u.kind, u.where, u.resolved.type])).toEqual([
|
|
52
|
+
["panel", 'panel (untitled) (id 2)', "loki"],
|
|
53
|
+
["variable", 'variable "job"', "prometheus"],
|
|
54
|
+
]);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which datasource each panel, query and query variable in built dashboard
|
|
3
|
+
* JSON sends its request to.
|
|
4
|
+
*
|
|
5
|
+
* GRAF101 and GRAF102 use this to check references against the datasources
|
|
6
|
+
* a build knows about: the ones it provisions (`Datasource`) and the ones it
|
|
7
|
+
* only references (`ExternalDatasource`). Any other check that needs to know
|
|
8
|
+
* which plugin runs a query (a PromQL syntax check only wants the queries
|
|
9
|
+
* that reach a Prometheus) can call `datasourceUses()` and read
|
|
10
|
+
* `resolved.type`.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { ExternalDatasourceRecord, ProvisionedDatasource } from "./build";
|
|
14
|
+
import { BUILTIN_DATASOURCE_UIDS } from "./datasource";
|
|
15
|
+
|
|
16
|
+
type Json = Record<string, unknown>;
|
|
17
|
+
|
|
18
|
+
/** A `{ type, uid }` datasource reference as dashboard JSON holds it. */
|
|
19
|
+
export interface DatasourceRefJson {
|
|
20
|
+
type?: string;
|
|
21
|
+
uid?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** A datasource the build knows by uid: provisioned by it, or declared external. */
|
|
25
|
+
export interface KnownDatasource {
|
|
26
|
+
uid: string;
|
|
27
|
+
type: string;
|
|
28
|
+
name?: string;
|
|
29
|
+
/** Declared with `ExternalDatasource`: referenced, not provisioned. */
|
|
30
|
+
external: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Where a reference leads.
|
|
35
|
+
*
|
|
36
|
+
* - `declared`: a known datasource; `type` is its plugin type.
|
|
37
|
+
* - `variable`: a `${name}` uid; `type` is the datasource variable's plugin type, else the ref's own.
|
|
38
|
+
* - `builtin`: one of Grafana's pseudo-datasources (`-- Mixed --`, `-- Dashboard --`, `grafana`).
|
|
39
|
+
* - `undeclared`: a uid the build does not know; `type` is whatever the ref says.
|
|
40
|
+
* - `default`: no uid, so Grafana picks its default datasource; `type` is the ref's, if it has one.
|
|
41
|
+
*/
|
|
42
|
+
export type ResolvedDatasource =
|
|
43
|
+
| { kind: "declared"; type: string; datasource: KnownDatasource }
|
|
44
|
+
| { kind: "variable"; type?: string; variable: string; declared: boolean }
|
|
45
|
+
| { kind: "builtin"; type?: string }
|
|
46
|
+
| { kind: "undeclared"; type?: string; uid: string }
|
|
47
|
+
| { kind: "default"; type?: string };
|
|
48
|
+
|
|
49
|
+
/** One place a dashboard names, or inherits, a datasource. */
|
|
50
|
+
export interface DatasourceUse {
|
|
51
|
+
/** A panel's own `datasource`, one of its queries (`targets`), or a query variable. */
|
|
52
|
+
kind: "panel" | "query" | "variable";
|
|
53
|
+
/** A human description, e.g. `panel "Latency" (id 3) query B`. */
|
|
54
|
+
where: string;
|
|
55
|
+
panel?: Json;
|
|
56
|
+
target?: Json;
|
|
57
|
+
variable?: Json;
|
|
58
|
+
/** The ref written on this panel, query or variable; unset when a query inherits its panel's. */
|
|
59
|
+
ref?: DatasourceRefJson;
|
|
60
|
+
/** Where the request goes: the query's own ref, else its panel's. */
|
|
61
|
+
resolved: ResolvedDatasource;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Every datasource a build knows by uid. A provisioned one wins over an external one of the same uid (GRAF104 reports the clash). */
|
|
65
|
+
export function knownDatasources(
|
|
66
|
+
datasources: readonly ProvisionedDatasource[],
|
|
67
|
+
external: readonly ExternalDatasourceRecord[] = [],
|
|
68
|
+
): Map<string, KnownDatasource> {
|
|
69
|
+
const out = new Map<string, KnownDatasource>();
|
|
70
|
+
for (const d of external) out.set(d.uid, { uid: d.uid, type: d.type, ...(d.name ? { name: d.name } : {}), external: true });
|
|
71
|
+
for (const d of datasources) out.set(d.uid, { uid: d.uid, type: d.type, name: d.name, external: false });
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface PanelInfo {
|
|
76
|
+
panel: Json;
|
|
77
|
+
/** Panels inside a collapsed row are laid out in their own group. */
|
|
78
|
+
group: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Every panel of a dashboard, including those inside collapsed rows, and the rows themselves. */
|
|
82
|
+
export function panelsOf(dashboard: Json): PanelInfo[] {
|
|
83
|
+
const out: PanelInfo[] = [];
|
|
84
|
+
const top = Array.isArray(dashboard.panels) ? (dashboard.panels as Json[]) : [];
|
|
85
|
+
for (const p of top) {
|
|
86
|
+
if (!p || typeof p !== "object") continue;
|
|
87
|
+
out.push({ panel: p, group: "" });
|
|
88
|
+
if (p.type === "row" && Array.isArray(p.panels)) {
|
|
89
|
+
for (const c of p.panels as Json[]) if (c && typeof c === "object") out.push({ panel: c, group: `row ${String(p.id)}` });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function describePanel(panel: Json): string {
|
|
96
|
+
const title = typeof panel.title === "string" && panel.title ? `"${panel.title}"` : "(untitled)";
|
|
97
|
+
return `panel ${title} (id ${String(panel.id ?? "?")})`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** A dashboard's `templating.list`. */
|
|
101
|
+
export function variablesOf(dashboard: Json): Json[] {
|
|
102
|
+
const list = (dashboard.templating as { list?: unknown } | undefined)?.list;
|
|
103
|
+
return Array.isArray(list) ? (list as Json[]).filter((v) => v && typeof v === "object") : [];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
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;
|
|
107
|
+
|
|
108
|
+
/** Every variable name referenced in a string. */
|
|
109
|
+
export function variableReferences(text: string): string[] {
|
|
110
|
+
const out: string[] = [];
|
|
111
|
+
for (const m of text.matchAll(VAR_REF)) out.push(m[1] ?? m[2] ?? m[3]);
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function refOf(value: unknown): DatasourceRefJson | undefined {
|
|
116
|
+
return value && typeof value === "object" && !Array.isArray(value) ? (value as DatasourceRefJson) : undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Grafana's own pseudo-datasources, which no build declares. */
|
|
120
|
+
export function isPseudoRef(ref: DatasourceRefJson): boolean {
|
|
121
|
+
return ref.type === "datasource" || ref.type === "grafana" || (ref.uid !== undefined && BUILTIN_DATASOURCE_UIDS.has(ref.uid));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** The datasource variables of a dashboard, by name. */
|
|
125
|
+
function datasourceVariables(dashboard: Json): Map<string, Json> {
|
|
126
|
+
return new Map(variablesOf(dashboard).filter((v) => v.type === "datasource").map((v) => [String(v.name), v]));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Where one reference leads, given the dashboard's datasource variables and the known datasources. */
|
|
130
|
+
export function resolveDatasourceRef(
|
|
131
|
+
ref: DatasourceRefJson | undefined,
|
|
132
|
+
dsVariables: ReadonlyMap<string, Json>,
|
|
133
|
+
known: ReadonlyMap<string, KnownDatasource>,
|
|
134
|
+
): ResolvedDatasource {
|
|
135
|
+
if (!ref || (ref.uid === undefined && ref.type === undefined)) return { kind: "default" };
|
|
136
|
+
if (isPseudoRef(ref)) return { kind: "builtin", ...(ref.type ? { type: ref.type } : {}) };
|
|
137
|
+
if (ref.uid === undefined) return { kind: "default", ...(ref.type ? { type: ref.type } : {}) };
|
|
138
|
+
if (ref.uid.includes("$")) {
|
|
139
|
+
const [name] = variableReferences(ref.uid);
|
|
140
|
+
const v = name ? dsVariables.get(name) : undefined;
|
|
141
|
+
const type = v && typeof v.query === "string" && v.query ? v.query : ref.type;
|
|
142
|
+
return { kind: "variable", variable: name ?? ref.uid, declared: v !== undefined, ...(type ? { type } : {}) };
|
|
143
|
+
}
|
|
144
|
+
const datasource = known.get(ref.uid);
|
|
145
|
+
if (datasource) return { kind: "declared", type: datasource.type, datasource };
|
|
146
|
+
return { kind: "undeclared", uid: ref.uid, ...(ref.type ? { type: ref.type } : {}) };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Every place a dashboard names or inherits a datasource: each panel's own
|
|
151
|
+
* ref, each query (with its own ref, or its panel's when it has none), and
|
|
152
|
+
* each query variable. Rows are left out; their panels carry the ref.
|
|
153
|
+
*/
|
|
154
|
+
export function datasourceUses(dashboard: Json, known: ReadonlyMap<string, KnownDatasource>): DatasourceUse[] {
|
|
155
|
+
const dsVars = datasourceVariables(dashboard);
|
|
156
|
+
const out: DatasourceUse[] = [];
|
|
157
|
+
for (const { panel } of panelsOf(dashboard)) {
|
|
158
|
+
if (panel.type === "row") continue;
|
|
159
|
+
const panelRef = refOf(panel.datasource);
|
|
160
|
+
if (panelRef) out.push({ kind: "panel", where: describePanel(panel), panel, ref: panelRef, resolved: resolveDatasourceRef(panelRef, dsVars, known) });
|
|
161
|
+
// A query under a `-- Mixed --` panel with no ref of its own goes to the default datasource.
|
|
162
|
+
const inherited = panelRef && panelRef.uid !== "-- Mixed --" ? panelRef : undefined;
|
|
163
|
+
const targets = Array.isArray(panel.targets) ? (panel.targets as Json[]) : [];
|
|
164
|
+
for (const target of targets) {
|
|
165
|
+
if (!target || typeof target !== "object") continue;
|
|
166
|
+
const own = refOf(target.datasource);
|
|
167
|
+
out.push({
|
|
168
|
+
kind: "query",
|
|
169
|
+
where: `${describePanel(panel)} query ${String(target.refId ?? "?")}`,
|
|
170
|
+
panel,
|
|
171
|
+
target,
|
|
172
|
+
...(own ? { ref: own } : {}),
|
|
173
|
+
resolved: resolveDatasourceRef(own ?? inherited, dsVars, known),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
for (const variable of variablesOf(dashboard)) {
|
|
178
|
+
if (variable.type !== "query") continue;
|
|
179
|
+
const ref = refOf(variable.datasource);
|
|
180
|
+
out.push({
|
|
181
|
+
kind: "variable",
|
|
182
|
+
where: `variable "${String(variable.name)}"`,
|
|
183
|
+
variable,
|
|
184
|
+
...(ref ? { ref } : {}),
|
|
185
|
+
resolved: resolveDatasourceRef(ref, dsVars, known),
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
return out;
|
|
189
|
+
}
|
package/src/datasource.ts
CHANGED
|
@@ -83,6 +83,58 @@ export function isDatasourceEntity(value: unknown): value is DatasourceEntity {
|
|
|
83
83
|
);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
export const EXTERNAL_DATASOURCE_TYPE = "Grafana::ExternalDatasource";
|
|
87
|
+
|
|
88
|
+
export interface ExternalDatasourceProps<T extends string = string> {
|
|
89
|
+
/** The datasource plugin id, e.g. `prometheus`. */
|
|
90
|
+
type: T;
|
|
91
|
+
/** The uid the datasource already has in Grafana. */
|
|
92
|
+
uid: string;
|
|
93
|
+
/** Its display name in Grafana, for messages and for a `DatasourceVariable` `regex`. */
|
|
94
|
+
name?: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface ExternalDatasourceEntity<T extends string = string> extends Declarable {
|
|
98
|
+
readonly props: ExternalDatasourceProps<T>;
|
|
99
|
+
/** The plugin id. */
|
|
100
|
+
readonly datasourceType: T;
|
|
101
|
+
/** The uid panels will reference. */
|
|
102
|
+
readonly uid: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const ExternalBase = createResource(EXTERNAL_DATASOURCE_TYPE, "grafana", {}) as unknown as (this: object, props: Record<string, unknown>) => void;
|
|
106
|
+
|
|
107
|
+
export interface ExternalDatasourceConstructor {
|
|
108
|
+
new <T extends string>(props: ExternalDatasourceProps<T>): ExternalDatasourceEntity<T>;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* A datasource that already exists in Grafana: provisioned by hand, by
|
|
113
|
+
* another build root, or by another tool. It is never written to the
|
|
114
|
+
* provisioning file. Panels, queries and variables use it like a
|
|
115
|
+
* `Datasource`, and GRAF101/GRAF102 check references against it.
|
|
116
|
+
*/
|
|
117
|
+
export const ExternalDatasource = function (this: object, props: ExternalDatasourceProps) {
|
|
118
|
+
ExternalBase.call(this, props as unknown as Record<string, unknown>);
|
|
119
|
+
Object.defineProperty(this, "datasourceType", { value: props.type, enumerable: false });
|
|
120
|
+
Object.defineProperty(this, "uid", { value: props.uid, enumerable: false });
|
|
121
|
+
} as unknown as ExternalDatasourceConstructor;
|
|
122
|
+
Object.defineProperty(ExternalDatasource, "name", { value: "ExternalDatasource" });
|
|
123
|
+
|
|
124
|
+
export function isExternalDatasource(value: unknown): value is ExternalDatasourceEntity {
|
|
125
|
+
return (
|
|
126
|
+
typeof value === "object" &&
|
|
127
|
+
value !== null &&
|
|
128
|
+
(value as Declarable).entityType === EXTERNAL_DATASOURCE_TYPE &&
|
|
129
|
+
(value as Declarable).lexicon === "grafana"
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** A declared or an external datasource: anything a panel can hold that has a fixed uid. */
|
|
134
|
+
export function isDatasourceDeclaration(value: unknown): value is DatasourceEntity | ExternalDatasourceEntity {
|
|
135
|
+
return isDatasourceEntity(value) || isExternalDatasource(value);
|
|
136
|
+
}
|
|
137
|
+
|
|
86
138
|
/** A datasource declared somewhere this build can't see, named by its Grafana ref. */
|
|
87
139
|
export interface DatasourceRef<T extends string = string> {
|
|
88
140
|
type: T;
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* grafana deep-observation noise rules (#2946).
|
|
3
|
+
*
|
|
4
|
+
* Plain data on the plugin, because core applies the same rules to the
|
|
5
|
+
* declared tree (a `Dashboard`'s props, panels and queries inlined), which
|
|
6
|
+
* no reader touches. The live tree is the stored dashboard put back into the
|
|
7
|
+
* same vocabulary by the importer (./import/live-export.ts), so what is left
|
|
8
|
+
* to name here is what the translation cannot decide: values the build
|
|
9
|
+
* wrote that the author did not, values Grafana adds or drops on save, and
|
|
10
|
+
* values an author wrote out that are also what happens when they are left
|
|
11
|
+
* out.
|
|
12
|
+
*
|
|
13
|
+
* Measured against Grafana 12.4.11 and 13.2.2 (the e2e in
|
|
14
|
+
* ./observe.e2e.test.ts): read over `/api/dashboards/uid`, a stored dashboard
|
|
15
|
+
* differs from the built one only by the `id` and `version` Grafana adds
|
|
16
|
+
* (../test/e2e/stored-model.ts). Read over `/apis/dashboard.grafana.app`, its
|
|
17
|
+
* spec has no `id` or `version`, but it carries the built-in "Annotations &
|
|
18
|
+
* Alerts" annotation, and empty `options` and `fieldConfig` and every `null`
|
|
19
|
+
* are gone. The rest of this table is the build's own choices: panel ids,
|
|
20
|
+
* grid positions, query refIds, a panel's datasource taken from its queries.
|
|
21
|
+
*
|
|
22
|
+
* Every rule is one of four kinds, and they are in this order below:
|
|
23
|
+
*
|
|
24
|
+
* 1. **Never compared**, on either side: Grafana's bookkeeping.
|
|
25
|
+
* 2. **Empty is absent**, on both sides: `null`, `{}` and `[]` mean the same
|
|
26
|
+
* as leaving a key out, everywhere in a dashboard or datasource. The cost
|
|
27
|
+
* is that values added where the declaration wrote `[]` are reported as
|
|
28
|
+
* unclaimed rather than as drift.
|
|
29
|
+
* 3. **Build-derived**, on the live side where the declaration has nothing:
|
|
30
|
+
* the build filled it in, so it cannot disagree with what was declared.
|
|
31
|
+
* 4. **Default written out**, on the declared side where the live side has
|
|
32
|
+
* nothing: the author wrote the value Grafana assumes, which the
|
|
33
|
+
* translation leaves out.
|
|
34
|
+
*
|
|
35
|
+
* Rules 3 and 4 are gated on `counterpart === "absent"`, so a value the
|
|
36
|
+
* author did declare is always compared, and a change away from it is drift.
|
|
37
|
+
* (That relies on core walking a panel's props when it lists the declared
|
|
38
|
+
* paths, which #2946 fixed in `deepPathSet`.)
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import type { DeepArrayElement, DeepNode, DeepNormalizationHooks } from "@intentius/chant/deep-observation";
|
|
42
|
+
import { DASHBOARD_TYPE } from "./dashboard";
|
|
43
|
+
import { DATASOURCE_TYPE, EXTERNAL_DATASOURCE_TYPE } from "./datasource";
|
|
44
|
+
import { registeredQueries } from "./query";
|
|
45
|
+
import { DASHBOARD_SCHEMA_VERSION } from "./schema/dashboard.gen";
|
|
46
|
+
import { BOOKKEEPING_KEYS, LINK_DEFAULTS, deepEqual, isBuiltinAnnotation } from "./import/normalize";
|
|
47
|
+
import { DATASOURCE_API_DEFAULTS } from "./import/live-export";
|
|
48
|
+
|
|
49
|
+
type Json = Record<string, unknown>;
|
|
50
|
+
|
|
51
|
+
/** Where a node sits in a dashboard tree, from its index-erased pattern. */
|
|
52
|
+
type Level = "dashboard" | "panel" | "target" | "variable" | "link";
|
|
53
|
+
|
|
54
|
+
const SEGMENT = "([^.\\[\\]]+)";
|
|
55
|
+
const PANEL = "panels\\[\\](?:\\.panels\\[\\])?";
|
|
56
|
+
const LEVELS: ReadonlyArray<[Level, RegExp]> = [
|
|
57
|
+
["dashboard", new RegExp(`^${SEGMENT}$`)],
|
|
58
|
+
["panel", new RegExp(`^${PANEL}\\.${SEGMENT}$`)],
|
|
59
|
+
["target", new RegExp(`^${PANEL}\\.targets\\[\\]\\.${SEGMENT}$`)],
|
|
60
|
+
["variable", new RegExp(`^variables\\[\\]\\.${SEGMENT}$`)],
|
|
61
|
+
["link", new RegExp(`^(?:${PANEL}\\.)?links\\[\\]\\.${SEGMENT}$`)],
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
function levelOf(pattern: string): { level: Level; key: string } | undefined {
|
|
65
|
+
for (const [level, re] of LEVELS) {
|
|
66
|
+
const m = re.exec(pattern);
|
|
67
|
+
if (m) return { level, key: m[1] };
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Defaults in the props vocabulary: a key an author may write at this value,
|
|
74
|
+
* and the translation leaves out because Grafana assumes it. The dashboard
|
|
75
|
+
* and panel entries mirror `DASHBOARD_DEFAULTS` and `PANEL_DEFAULTS` in
|
|
76
|
+
* ./import/normalize.ts, with `graphTooltip` spelled as `Dashboard` takes it.
|
|
77
|
+
*/
|
|
78
|
+
export const PROP_DEFAULTS: Readonly<Record<Level, Readonly<Json>>> = {
|
|
79
|
+
dashboard: {
|
|
80
|
+
editable: true,
|
|
81
|
+
fiscalYearStartMonth: 0,
|
|
82
|
+
graphTooltip: "default",
|
|
83
|
+
links: [],
|
|
84
|
+
tags: [],
|
|
85
|
+
time: { from: "now-6h", to: "now" },
|
|
86
|
+
timepicker: {},
|
|
87
|
+
timezone: "",
|
|
88
|
+
weekStart: "",
|
|
89
|
+
refresh: "",
|
|
90
|
+
liveNow: false,
|
|
91
|
+
description: "",
|
|
92
|
+
schemaVersion: DASHBOARD_SCHEMA_VERSION,
|
|
93
|
+
},
|
|
94
|
+
panel: {
|
|
95
|
+
title: "",
|
|
96
|
+
description: "",
|
|
97
|
+
options: {},
|
|
98
|
+
targets: [],
|
|
99
|
+
transformations: [],
|
|
100
|
+
links: [],
|
|
101
|
+
transparent: false,
|
|
102
|
+
hideTimeOverride: false,
|
|
103
|
+
repeatDirection: "h",
|
|
104
|
+
// A row's.
|
|
105
|
+
collapsed: false,
|
|
106
|
+
panels: [],
|
|
107
|
+
},
|
|
108
|
+
target: {},
|
|
109
|
+
variable: {
|
|
110
|
+
label: "",
|
|
111
|
+
description: "",
|
|
112
|
+
hide: "label",
|
|
113
|
+
skipUrlSync: false,
|
|
114
|
+
multi: false,
|
|
115
|
+
includeAll: false,
|
|
116
|
+
allValue: "",
|
|
117
|
+
regex: "",
|
|
118
|
+
sort: 0,
|
|
119
|
+
refresh: "onLoad",
|
|
120
|
+
auto: false,
|
|
121
|
+
autoCount: 30,
|
|
122
|
+
autoMin: "10s",
|
|
123
|
+
},
|
|
124
|
+
link: LINK_DEFAULTS,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Keys the build fills in when a declaration leaves them out, by level:
|
|
129
|
+
* the dashboard's uid (from its export name) and schema version, a panel's
|
|
130
|
+
* id, grid position and datasource (from its queries), a query's refId.
|
|
131
|
+
* Grafana keeps what the build wrote, so undeclared, they read back as the
|
|
132
|
+
* build's choice rather than as anybody's edit.
|
|
133
|
+
*
|
|
134
|
+
* `timezone` is here as a value, not a key: chant writes `browser` for a
|
|
135
|
+
* dashboard that names none, where Grafana's own default is `""`.
|
|
136
|
+
*/
|
|
137
|
+
export const BUILD_DERIVED: Readonly<Record<Level, ReadonlySet<string>>> = {
|
|
138
|
+
dashboard: new Set(["uid", "schemaVersion"]),
|
|
139
|
+
panel: new Set(["id", "gridPos", "datasource", "pluginVersion"]),
|
|
140
|
+
target: new Set(["refId", "datasource"]),
|
|
141
|
+
// A query variable's selection is whatever the viewer last saved; the build writes one only when it is declared.
|
|
142
|
+
variable: new Set(["current", "datasource"]),
|
|
143
|
+
link: new Set(),
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/** Defaults the build merges into a query from its class (`queryType: "traceql"`, `filters: []`): any key some class defaults, at that value. */
|
|
147
|
+
function queryDefault(key: string, value: unknown): boolean {
|
|
148
|
+
for (const def of registeredQueries()) {
|
|
149
|
+
const defaults = (def as { defaults?: Json }).defaults;
|
|
150
|
+
if (defaults && key in defaults && deepEqual(defaults[key], value)) return true;
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** `[]` or a plain `{}`. Not a declarable, whose own keys are not enumerable. */
|
|
156
|
+
function isEmptyContainer(value: unknown): boolean {
|
|
157
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
158
|
+
if (typeof value !== "object" || value === null) return false;
|
|
159
|
+
const proto = Object.getPrototypeOf(value) as unknown;
|
|
160
|
+
return (proto === Object.prototype || proto === null) && Object.keys(value).length === 0;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function isEmptyFieldConfig(value: unknown): boolean {
|
|
164
|
+
return isEmptyContainer(value) || deepEqual(value, { defaults: {}, overrides: [] });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Any node at or under a `datasource` key: a reference, whichever form (ref, variable, entity) it takes. */
|
|
168
|
+
function underDatasource(pattern: string): boolean {
|
|
169
|
+
return /(^|\.)datasource(\.|$)/.test(pattern);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function isDatasourceType(entityType: string): boolean {
|
|
173
|
+
return entityType === DATASOURCE_TYPE || entityType === EXTERNAL_DATASOURCE_TYPE;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function pruneDatasource(node: DeepNode): boolean {
|
|
177
|
+
// 1. The organisation is where it lives, not a property of it; the version is Grafana's save counter.
|
|
178
|
+
if (node.pattern === "orgId" || node.pattern === "version") return true;
|
|
179
|
+
// 2.
|
|
180
|
+
if (node.value === null || isEmptyContainer(node.value)) return true;
|
|
181
|
+
if (node.counterpart !== "absent") return false;
|
|
182
|
+
if (node.side === "live") {
|
|
183
|
+
// 3. The uid comes from the name when not declared; `editable` is `!readOnly`, true for one created in the UI or API.
|
|
184
|
+
return node.pattern === "uid" || node.pattern === "editable";
|
|
185
|
+
}
|
|
186
|
+
// 4.
|
|
187
|
+
if (node.pattern === "editable") return node.value === false;
|
|
188
|
+
return node.pattern in DATASOURCE_API_DEFAULTS && deepEqual(DATASOURCE_API_DEFAULTS[node.pattern], node.value);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function pruneDashboard(node: DeepNode): boolean {
|
|
192
|
+
const at = levelOf(node.pattern);
|
|
193
|
+
|
|
194
|
+
// 1. Bookkeeping and Grafana's own additions, whatever either side says.
|
|
195
|
+
if (at?.level === "dashboard" && BOOKKEEPING_KEYS.includes(at.key)) return true;
|
|
196
|
+
if (/^annotations(\.list)?\[\]$/.test(node.pattern) && isBuiltinAnnotation(node.value)) return true;
|
|
197
|
+
if (at?.level === "panel" && (at.key === "options" || at.key === "fieldConfig") && isEmptyFieldConfig(node.value)) return true;
|
|
198
|
+
|
|
199
|
+
// 2. Empty is absent, on both sides whatever the other has. Grafana drops
|
|
200
|
+
// every `null` when it stores a dashboard through /apis, and an empty list
|
|
201
|
+
// or object means what leaving the key out means. Not gated on the
|
|
202
|
+
// counterpart, which is matched by pattern: a threshold's first step has
|
|
203
|
+
// `value: null` where the others have a number, and one panel's
|
|
204
|
+
// `overrides: []` sits beside another's list of overrides.
|
|
205
|
+
if (node.value === null || isEmptyContainer(node.value)) return true;
|
|
206
|
+
if (node.counterpart !== "absent") return false;
|
|
207
|
+
|
|
208
|
+
if (node.side === "live") {
|
|
209
|
+
// 3. Build-derived.
|
|
210
|
+
if (underDatasource(node.pattern)) return true;
|
|
211
|
+
if (/^panels\[\](?:\.panels\[\])?\.gridPos\./.test(node.pattern)) return true;
|
|
212
|
+
if (!at) return false;
|
|
213
|
+
if (BUILD_DERIVED[at.level].has(at.key)) return true;
|
|
214
|
+
if (at.level === "dashboard" && at.key === "timezone" && node.value === "browser") return true;
|
|
215
|
+
if (at.level === "target" && queryDefault(at.key, node.value)) return true;
|
|
216
|
+
if (at.level === "link" && at.key in LINK_DEFAULTS && deepEqual(LINK_DEFAULTS[at.key], node.value)) return true;
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// 4. A default written out.
|
|
221
|
+
if (!at) return false;
|
|
222
|
+
const defaults = PROP_DEFAULTS[at.level];
|
|
223
|
+
return at.key in defaults && deepEqual(defaults[at.key], node.value);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export const grafanaDeepNormalizationHooks: DeepNormalizationHooks = {
|
|
227
|
+
prune(node: DeepNode): boolean {
|
|
228
|
+
if (node.entityType === DASHBOARD_TYPE) return pruneDashboard(node);
|
|
229
|
+
if (isDatasourceType(node.entityType)) return pruneDatasource(node);
|
|
230
|
+
return false;
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* A datasource's secrets are compared by key, never by value: Grafana
|
|
235
|
+
* returns only which ones are set, the reader writes those as
|
|
236
|
+
* `[REDACTED]`, and the declared values (`$__env{...}` references, or
|
|
237
|
+
* worse) are masked the same way.
|
|
238
|
+
*/
|
|
239
|
+
mask(node: DeepNode): boolean {
|
|
240
|
+
return isDatasourceType(node.entityType) && /^secureJsonData\./.test(node.pattern);
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Dashboard tags are a set. Nothing else is reordered: panel order is
|
|
245
|
+
* layout order, and variable and link order is the order Grafana shows
|
|
246
|
+
* them in.
|
|
247
|
+
*/
|
|
248
|
+
orderKey(element: DeepArrayElement): string | undefined {
|
|
249
|
+
if (element.entityType === DASHBOARD_TYPE && element.pattern === "tags" && typeof element.element === "string") return element.element;
|
|
250
|
+
return undefined;
|
|
251
|
+
},
|
|
252
|
+
};
|