@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
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GRAF108's pieces: template-variable substitution, the PromQL inside a
|
|
3
|
+
* Prometheus query variable, and which queries of a dashboard get parsed.
|
|
4
|
+
* The check itself, over built and hand-written dashboards, is in
|
|
5
|
+
* lint/post-synth/post-synth.test.ts.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, expect, test } from "vitest";
|
|
8
|
+
import { checkGrafanaPromql, prometheusQueries, substituteTemplateVariables, variablePromql } from "./promql-check";
|
|
9
|
+
import { knownDatasources } from "./datasource-refs";
|
|
10
|
+
|
|
11
|
+
describe("substituteTemplateVariables", () => {
|
|
12
|
+
test.each([
|
|
13
|
+
['sum(rate(x{a=~"$a"}[$__rate_interval]))', 'sum(rate(x{a=~"$a"}[5m]))'],
|
|
14
|
+
["rate(x[$__interval:$__interval])", "rate(x[5m:5m])"],
|
|
15
|
+
["rate(x[${window}]) offset $shift", "rate(x[5m]) offset 5m"],
|
|
16
|
+
["rate(x[${minutes}m])", "rate(x[1m])"],
|
|
17
|
+
['$metric{job="a"} / $__range_s', 'grafana_var{job="a"} / 1'],
|
|
18
|
+
["sum by ($group) (x) * $__interval_ms", "sum by (grafana_var) (x) * 1"],
|
|
19
|
+
["x @ ${__to:date:seconds}", "x @ 1"],
|
|
20
|
+
["rate([[metric]][[[w]]])", "rate(grafana_var[5m])"],
|
|
21
|
+
["topk(${n:raw}, x)", "topk(grafana_var, x)"],
|
|
22
|
+
["x{a='$a', b=`$b`}", "x{a='$a', b=`$b`}"],
|
|
23
|
+
['x{a="\\"$a"}[$__range]', 'x{a="\\"$a"}[5m]'],
|
|
24
|
+
])("%s", (query, text) => {
|
|
25
|
+
expect(substituteTemplateVariables(query).text).toBe(text);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("maps offsets in the substituted text back to the query", () => {
|
|
29
|
+
const s = substituteTemplateVariables("rate(x[$__rate_interval]) +");
|
|
30
|
+
expect(s.text).toBe("rate(x[5m]) +");
|
|
31
|
+
expect(s.originalOffset(7)).toBe(7);
|
|
32
|
+
expect(s.originalOffset(8)).toBe(7);
|
|
33
|
+
expect(s.originalOffset(11)).toBe(25);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("checkGrafanaPromql", () => {
|
|
38
|
+
test("passes the queries Grafana's own dashboards use", () => {
|
|
39
|
+
for (const q of [
|
|
40
|
+
'histogram_quantile(0.99, sum by (le) (rate(http_request_duration_seconds_bucket{route=~"$route"}[$__rate_interval])))',
|
|
41
|
+
'sum by (route) (rate(http_requests_total{job=~"$job", env=~"$env"}[$window]))',
|
|
42
|
+
"sum(increase(x[$__range])) / ($__range_s)",
|
|
43
|
+
'up{instance=~"[[instance]]"}',
|
|
44
|
+
]) {
|
|
45
|
+
expect({ q, r: checkGrafanaPromql(q) }).toEqual({ q, r: { ok: true } });
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("reports an unbalanced expression against the original text", () => {
|
|
50
|
+
const r = checkGrafanaPromql('sum(x{cluster="$cluster"}');
|
|
51
|
+
expect(r).toEqual({ ok: false, message: expect.stringContaining('ends early (after "sum(x{cluster="$cluster"}")') });
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("reports the offset in the query as written, not as substituted", () => {
|
|
55
|
+
const r = checkGrafanaPromql("rate(x[$__rate_interval]) by");
|
|
56
|
+
expect(r.ok).toBe(false);
|
|
57
|
+
if (!r.ok) expect(r.message).toMatch(/offset 2\d: "rate\(x\[\$__rate_interval\]\) ?" >>> /);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe("variablePromql", () => {
|
|
62
|
+
test.each([
|
|
63
|
+
["label_names()", []],
|
|
64
|
+
['label_names(up{job="a"})', ['up{job="a"}']],
|
|
65
|
+
["label_values(job)", []],
|
|
66
|
+
['label_values(up{env="$env"}, job)', ['up{env="$env"}']],
|
|
67
|
+
["metrics(^http_.*)", []],
|
|
68
|
+
["query_result(topk(5, sum by (job) (up)))", ["topk(5, sum by (job) (up))"]],
|
|
69
|
+
['up{job="a"}', ['up{job="a"}']],
|
|
70
|
+
[" ", []],
|
|
71
|
+
])("%s", (query, promql) => {
|
|
72
|
+
expect(variablePromql(query)).toEqual(promql);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("prometheusQueries", () => {
|
|
77
|
+
const known = knownDatasources([
|
|
78
|
+
{ name: "Prometheus", type: "prometheus", uid: "prom" },
|
|
79
|
+
{ name: "Loki", type: "loki", uid: "loki" },
|
|
80
|
+
]);
|
|
81
|
+
const dashboard = {
|
|
82
|
+
templating: {
|
|
83
|
+
list: [
|
|
84
|
+
{ type: "datasource", name: "ds", query: "prometheus" },
|
|
85
|
+
{ type: "query", name: "job", datasource: { type: "prometheus", uid: "prom" }, query: { query: "label_values(up, job)", refId: "V" } },
|
|
86
|
+
{ type: "query", name: "stream", datasource: { type: "loki", uid: "loki" }, query: "label_values(app)" },
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
panels: [
|
|
90
|
+
{ id: 1, title: "a", type: "stat", datasource: { type: "prometheus", uid: "prom" }, targets: [{ refId: "A", expr: "up" }, { refId: "B", expr: "" }] },
|
|
91
|
+
{ id: 2, title: "b", type: "logs", datasource: { type: "loki", uid: "loki" }, targets: [{ refId: "A", expr: '{app="x"} |= "error"' }] },
|
|
92
|
+
{ id: 3, title: "c", type: "stat", datasource: { type: "prometheus", uid: "${ds}" }, targets: [{ refId: "A", expr: "sum(up)" }] },
|
|
93
|
+
{ id: 4, title: "d", type: "stat", targets: [{ refId: "A", expr: "no datasource" }] },
|
|
94
|
+
{ id: 5, title: "e", type: "stat", datasource: { type: "datasource", uid: "-- Mixed --" }, targets: [{ refId: "A", expr: "x", datasource: { type: "loki", uid: "loki" } }, { refId: "B", expr: "y", datasource: { type: "prometheus", uid: "prom" } }] },
|
|
95
|
+
],
|
|
96
|
+
__elements: { lib: { name: "lib", kind: 1, model: { type: "stat", datasource: { type: "prometheus", uid: "prom" }, targets: [{ refId: "A", expr: "rate(x[5m])" }] } } },
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
test("takes only the queries that reach a Prometheus, including library panels", () => {
|
|
100
|
+
expect(prometheusQueries(dashboard, known)).toEqual([
|
|
101
|
+
{ where: 'panel "a" (id 1) query A', expr: "up" },
|
|
102
|
+
{ where: 'panel "c" (id 3) query A', expr: "sum(up)" },
|
|
103
|
+
{ where: 'panel "e" (id 5) query B', expr: "y" },
|
|
104
|
+
{ where: 'variable "job" query', expr: "up" },
|
|
105
|
+
{ where: 'library panel "lib" query A', expr: "rate(x[5m])" },
|
|
106
|
+
]);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GRAF108: the PromQL a dashboard sends to Prometheus, syntax-checked with
|
|
3
|
+
* the prometheus lexicon's `checkPromql` (the grammar behind the Prometheus
|
|
4
|
+
* web UI's editor, `@prometheus-io/lezer-promql`).
|
|
5
|
+
*
|
|
6
|
+
* Only queries that reach a Prometheus datasource are parsed, as
|
|
7
|
+
* `datasourceUses()` resolves them: a query's own ref, else its panel's, a
|
|
8
|
+
* datasource variable's plugin type, or an undeclared ref's own `type`. A
|
|
9
|
+
* query whose datasource can't be told (no ref at all, a `-- Mixed --` panel
|
|
10
|
+
* query with none of its own) is left alone.
|
|
11
|
+
*
|
|
12
|
+
* Grafana interpolates template variables before Prometheus sees the query,
|
|
13
|
+
* so `$var`, `${var}`, `${var:format}`, `[[var]]` and the `$__` macros are
|
|
14
|
+
* replaced with a placeholder that parses where they stand: a duration
|
|
15
|
+
* inside `[...]` or after `offset`, a number for the `_ms`/`_s` macros and
|
|
16
|
+
* `$__from`/`$__to`, an identifier anywhere else. Inside a string literal
|
|
17
|
+
* they are left as they are.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { checkPromql } from "@intentius/chant-lexicon-prometheus/promql";
|
|
21
|
+
import { datasourceUses, variablesOf, type KnownDatasource } from "./datasource-refs";
|
|
22
|
+
|
|
23
|
+
type Json = Record<string, unknown>;
|
|
24
|
+
|
|
25
|
+
const DURATION = "5m";
|
|
26
|
+
const NUMBER = "1";
|
|
27
|
+
const IDENTIFIER = "grafana_var";
|
|
28
|
+
|
|
29
|
+
/** A template variable reference starting at the regex's lastIndex. */
|
|
30
|
+
const VARIABLE = /\$\{([^}]*)\}|\[\[([A-Za-z_][^\]]*)\]\]|\$([A-Za-z_][A-Za-z0-9_]*)/y;
|
|
31
|
+
|
|
32
|
+
function isNumericMacro(name: string): boolean {
|
|
33
|
+
if (name === "__from" || name === "__to") return true;
|
|
34
|
+
return (name.startsWith("__") || name === "interval_ms") && /_(ms|s)$/.test(name);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A span of substituted text and the original text it replaced. */
|
|
38
|
+
interface Span {
|
|
39
|
+
at: number;
|
|
40
|
+
length: number;
|
|
41
|
+
original: number;
|
|
42
|
+
originalLength: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface Substituted {
|
|
46
|
+
text: string;
|
|
47
|
+
/** Maps an offset in `text` back to one in the original query. */
|
|
48
|
+
originalOffset(at: number): number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Replace every template variable outside a string literal with a placeholder that parses in its place. */
|
|
52
|
+
export function substituteTemplateVariables(query: string): Substituted {
|
|
53
|
+
let out = "";
|
|
54
|
+
const spans: Span[] = [];
|
|
55
|
+
let quote: string | undefined;
|
|
56
|
+
let depth = 0;
|
|
57
|
+
let i = 0;
|
|
58
|
+
while (i < query.length) {
|
|
59
|
+
const c = query[i];
|
|
60
|
+
if (quote) {
|
|
61
|
+
out += c;
|
|
62
|
+
if (c === "\\" && quote !== "`" && i + 1 < query.length) {
|
|
63
|
+
out += query[i + 1];
|
|
64
|
+
i += 2;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (c === quote) quote = undefined;
|
|
68
|
+
i++;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (c === "$" || (c === "[" && query[i + 1] === "[")) {
|
|
72
|
+
VARIABLE.lastIndex = i;
|
|
73
|
+
const m = VARIABLE.exec(query);
|
|
74
|
+
if (m) {
|
|
75
|
+
const name = (m[1] ?? m[2] ?? m[3]).split(/[:.]/)[0].trim();
|
|
76
|
+
const before = out.trimEnd();
|
|
77
|
+
let value: string;
|
|
78
|
+
if (depth > 0 || /\boffset$/i.test(before)) {
|
|
79
|
+
// `[${n}m]`: the unit follows, so only the number is substituted.
|
|
80
|
+
value = /[a-z]/i.test(query[i + m[0].length] ?? "") ? NUMBER : DURATION;
|
|
81
|
+
} else if (isNumericMacro(name) || before.endsWith("@")) {
|
|
82
|
+
value = NUMBER;
|
|
83
|
+
} else {
|
|
84
|
+
value = IDENTIFIER;
|
|
85
|
+
}
|
|
86
|
+
spans.push({ at: out.length, length: value.length, original: i, originalLength: m[0].length });
|
|
87
|
+
out += value;
|
|
88
|
+
i += m[0].length;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (c === '"' || c === "'" || c === "`") quote = c;
|
|
93
|
+
else if (c === "[") depth++;
|
|
94
|
+
else if (c === "]" && depth > 0) depth--;
|
|
95
|
+
out += c;
|
|
96
|
+
i++;
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
text: out,
|
|
100
|
+
originalOffset(at: number): number {
|
|
101
|
+
let shift = 0;
|
|
102
|
+
for (const s of spans) {
|
|
103
|
+
if (at < s.at) break;
|
|
104
|
+
if (at < s.at + s.length) return s.original;
|
|
105
|
+
shift = s.original + s.originalLength - (s.at + s.length);
|
|
106
|
+
}
|
|
107
|
+
return at + shift;
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export type PromqlSyntax = { ok: true } | { ok: false; message: string };
|
|
113
|
+
|
|
114
|
+
/** Check one query's PromQL syntax after substituting its template variables. */
|
|
115
|
+
export function checkGrafanaPromql(query: string): PromqlSyntax {
|
|
116
|
+
const substituted = substituteTemplateVariables(query);
|
|
117
|
+
const checked = checkPromql(substituted.text);
|
|
118
|
+
if (checked.ok) return { ok: true };
|
|
119
|
+
const at = Math.min(substituted.originalOffset(checked.position), query.length);
|
|
120
|
+
if (at >= query.length) return { ok: false, message: `the expression ends early (after "${query.slice(Math.max(0, at - 30)).trim()}")` };
|
|
121
|
+
const before = query.slice(Math.max(0, at - 30), at);
|
|
122
|
+
const after = query.slice(at, at + 30);
|
|
123
|
+
return { ok: false, message: `syntax error at offset ${at}: "${before}" >>> "${after}"` };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The PromQL inside a Prometheus query variable's query. Grafana handles
|
|
128
|
+
* `label_names()`, `label_values(selector, label)`, `metrics(regex)` and
|
|
129
|
+
* `query_result(expr)` itself and sends only the selector or expression; any
|
|
130
|
+
* other text is a series selector (`/api/v1/series?match[]=`).
|
|
131
|
+
*/
|
|
132
|
+
export function variablePromql(query: string): string[] {
|
|
133
|
+
const q = query.trim();
|
|
134
|
+
if (q === "" || /^label_names\(\s*\)$/.test(q)) return [];
|
|
135
|
+
let m = /^label_names\((.+)\)$/.exec(q);
|
|
136
|
+
if (m) return [m[1]];
|
|
137
|
+
m = /^label_values\((?:(.+),\s*)?([a-zA-Z_$][a-zA-Z0-9_]*)\)$/.exec(q);
|
|
138
|
+
if (m) return m[1] ? [m[1]] : [];
|
|
139
|
+
if (/^metrics\((.+)\)$/.test(q)) return [];
|
|
140
|
+
m = /^query_result\((.+)\)$/.exec(q);
|
|
141
|
+
if (m) return [m[1]];
|
|
142
|
+
return [q];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function variableQueryText(variable: Json): string | undefined {
|
|
146
|
+
const q = variable.query;
|
|
147
|
+
if (typeof q === "string") return q;
|
|
148
|
+
if (q && typeof q === "object" && typeof (q as Json).query === "string") return (q as Json).query as string;
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** One piece of PromQL a dashboard sends to Prometheus, and where it is written. */
|
|
153
|
+
export interface PromqlUse {
|
|
154
|
+
where: string;
|
|
155
|
+
expr: string;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** The PromQL of one dashboard (or, for a library panel, a one-panel stand-in); `library` names the `__elements` key. */
|
|
159
|
+
function usesOf(dashboard: Json, known: ReadonlyMap<string, KnownDatasource>, library?: string): PromqlUse[] {
|
|
160
|
+
const out: PromqlUse[] = [];
|
|
161
|
+
for (const use of datasourceUses(dashboard, known)) {
|
|
162
|
+
if (use.resolved.type !== "prometheus") continue;
|
|
163
|
+
if (use.kind === "query") {
|
|
164
|
+
const expr = use.target?.expr;
|
|
165
|
+
if (typeof expr !== "string" || expr.trim() === "") continue;
|
|
166
|
+
out.push({ where: library ? `library panel "${library}" query ${String(use.target?.refId ?? "?")}` : use.where, expr });
|
|
167
|
+
} else if (use.kind === "annotation" && use.annotation) {
|
|
168
|
+
// A Prometheus annotation keeps its query in `target.expr`, or at the top level when saved before Grafana 10.
|
|
169
|
+
const expr = use.target?.expr ?? use.annotation.expr;
|
|
170
|
+
if (typeof expr === "string" && expr.trim() !== "") out.push({ where: `${use.where} query`, expr });
|
|
171
|
+
} else if (use.kind === "variable" && use.variable?.type === "query" && !library) {
|
|
172
|
+
const text = variableQueryText(use.variable);
|
|
173
|
+
if (text) for (const expr of variablePromql(text)) out.push({ where: `${use.where} query`, expr });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Every PromQL query a dashboard sends to Prometheus: panel queries, query variables and the library panels an export embeds in `__elements`. */
|
|
180
|
+
export function prometheusQueries(dashboard: Json, known: ReadonlyMap<string, KnownDatasource>): PromqlUse[] {
|
|
181
|
+
const out = usesOf(dashboard, known);
|
|
182
|
+
const elements = dashboard.__elements;
|
|
183
|
+
if (elements && typeof elements === "object" && !Array.isArray(elements)) {
|
|
184
|
+
// The dashboard's variables, so a library panel's `${DS_...}` ref resolves as the dashboard's would.
|
|
185
|
+
const templating = { list: variablesOf(dashboard) };
|
|
186
|
+
for (const [key, element] of Object.entries(elements as Json)) {
|
|
187
|
+
const model = element && typeof element === "object" ? (element as Json).model : undefined;
|
|
188
|
+
if (!model || typeof model !== "object" || Array.isArray(model)) continue;
|
|
189
|
+
out.push(...usesOf({ templating, panels: [model] }, known, key));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return out;
|
|
193
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand-written query models for the datasources that have no schema.
|
|
3
|
+
*
|
|
4
|
+
* PostgreSQL, MySQL and Microsoft SQL Server share one query model, defined
|
|
5
|
+
* in TypeScript in Grafana's `@grafana/sql` package with no CUE kind, so
|
|
6
|
+
* foundation-sdk publishes no JSON Schema for it and `npm run generate` has
|
|
7
|
+
* nothing to read. The types here follow `packages/grafana-sql/src` at
|
|
8
|
+
* Grafana v13.2.2, cited per type. The three plugins bundled with
|
|
9
|
+
* grafana/grafana:13.2.2 (grafana-postgresql-datasource 13.0.3, mysql 13.1.2,
|
|
10
|
+
* mssql 13.0.5) use this model unchanged. GRAF107 does not check these
|
|
11
|
+
* queries' fields; the dashboard schema's `Panel` still checks the panel.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** `QueryEditorExpressionType`, packages/grafana-sql/src/expressions.ts:34-42. */
|
|
15
|
+
export type SqlExpressionType = "property" | "operator" | "or" | "and" | "groupBy" | "function" | "functionParameter";
|
|
16
|
+
|
|
17
|
+
/** `QueryEditorProperty`, packages/grafana-sql/src/expressions.ts:5-8 (its type is the one-member `QueryEditorPropertyType`). */
|
|
18
|
+
export interface SqlProperty {
|
|
19
|
+
type: "string";
|
|
20
|
+
name?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** `QueryEditorFunctionParameterExpression`, packages/grafana-sql/src/expressions.ts:64-67. */
|
|
24
|
+
export interface SqlFunctionParameterExpression {
|
|
25
|
+
type: "functionParameter";
|
|
26
|
+
name?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** `QueryEditorFunctionExpression`, packages/grafana-sql/src/expressions.ts:57-62: a selected column, optionally wrapped in an aggregation. */
|
|
30
|
+
export interface SqlFunctionExpression {
|
|
31
|
+
type: "function";
|
|
32
|
+
name?: string;
|
|
33
|
+
alias?: string;
|
|
34
|
+
parameters?: SqlFunctionParameterExpression[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** `QueryEditorGroupByExpression`, packages/grafana-sql/src/expressions.ts:52-55. */
|
|
38
|
+
export interface SqlGroupByExpression {
|
|
39
|
+
type: "groupBy";
|
|
40
|
+
property: SqlProperty;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** `QueryEditorPropertyExpression`, packages/grafana-sql/src/expressions.ts:29-32. */
|
|
44
|
+
export interface SqlPropertyExpression {
|
|
45
|
+
type: "property";
|
|
46
|
+
property: SqlProperty;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The visual builder's query: `SQLExpression`, packages/grafana-sql/src/types.ts:67-77.
|
|
51
|
+
* `whereJsonTree` is react-awesome-query-builder's `JsonTree`, carried as data.
|
|
52
|
+
*/
|
|
53
|
+
export interface SqlBuilderQuery {
|
|
54
|
+
columns?: SqlFunctionExpression[];
|
|
55
|
+
whereJsonTree?: Record<string, unknown>;
|
|
56
|
+
whereString?: string;
|
|
57
|
+
filters?: Array<{ name: string; value: string }>;
|
|
58
|
+
groupBy?: SqlGroupByExpression[];
|
|
59
|
+
orderBy?: SqlPropertyExpression;
|
|
60
|
+
orderByDirection?: "ASC" | "DESC";
|
|
61
|
+
limit?: number;
|
|
62
|
+
offset?: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** `QueryFormat`, packages/grafana-sql/src/types.ts:39-42. */
|
|
66
|
+
export type SqlQueryFormat = "time_series" | "table";
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* A PostgreSQL, MySQL or MSSQL query: `SQLQuery`, packages/grafana-sql/src/types.ts:46-56,
|
|
70
|
+
* over `DataQuery` from @grafana/schema (refId, hide, key, queryType, datasource).
|
|
71
|
+
*
|
|
72
|
+
* In code mode (`editorMode: "code"`, `rawQuery: true`) `rawSql` is the
|
|
73
|
+
* query; in builder mode `dataset`, `table` and `sql` describe it and Grafana
|
|
74
|
+
* writes the generated SQL to `rawSql`.
|
|
75
|
+
*/
|
|
76
|
+
export interface SqlQueryModel {
|
|
77
|
+
refId?: string;
|
|
78
|
+
hide?: boolean;
|
|
79
|
+
key?: string;
|
|
80
|
+
queryType?: string;
|
|
81
|
+
datasource?: { type?: string; uid?: string };
|
|
82
|
+
/** The SQL, with Grafana's `$__timeFilter()` and other macros. */
|
|
83
|
+
rawSql?: string;
|
|
84
|
+
format?: SqlQueryFormat;
|
|
85
|
+
alias?: string;
|
|
86
|
+
dataset?: string;
|
|
87
|
+
table?: string;
|
|
88
|
+
sql?: SqlBuilderQuery;
|
|
89
|
+
/** `EditorMode` from @grafana/plugin-ui: the raw SQL editor or the visual builder. */
|
|
90
|
+
editorMode?: "code" | "builder";
|
|
91
|
+
rawQuery?: boolean;
|
|
92
|
+
/** `SQLQueryMeta`, packages/grafana-sql/src/types.ts:44: which columns a variable query takes its values and texts from. */
|
|
93
|
+
meta?: { valueField?: string; textField?: string };
|
|
94
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The query classes beyond Prometheus, Tempo and Loki (#2951): each renders
|
|
3
|
+
* as a target of its plugin, GRAF107 checks the schema-typed ones, the
|
|
4
|
+
* PostgreSQL class answers to the plugin's old id, and GRAF108 still parses
|
|
5
|
+
* only what reaches a Prometheus.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, expect, test } from "vitest";
|
|
8
|
+
import { Dashboard } from "./dashboard";
|
|
9
|
+
import { Datasource } from "./datasource";
|
|
10
|
+
import { TablePanel, TimeSeriesPanel } from "./panels";
|
|
11
|
+
import {
|
|
12
|
+
AzureMonitorQuery,
|
|
13
|
+
BigQueryQuery,
|
|
14
|
+
CloudMonitoringQuery,
|
|
15
|
+
CloudWatchQuery,
|
|
16
|
+
ElasticsearchQuery,
|
|
17
|
+
LokiQuery,
|
|
18
|
+
MSSQLQuery,
|
|
19
|
+
MySQLQuery,
|
|
20
|
+
PostgresQuery,
|
|
21
|
+
PromQuery,
|
|
22
|
+
PyroscopeQuery,
|
|
23
|
+
queryDefinitionFor,
|
|
24
|
+
registeredQueries,
|
|
25
|
+
} from "./query";
|
|
26
|
+
import { renderDashboard } from "./build";
|
|
27
|
+
import { validateDashboardSchema } from "./schema-validate";
|
|
28
|
+
import { prometheusQueries } from "./promql-check";
|
|
29
|
+
import { knownDatasources } from "./datasource-refs";
|
|
30
|
+
import { SCHEMA_NAMES } from "./pin";
|
|
31
|
+
|
|
32
|
+
type Json = Record<string, unknown>;
|
|
33
|
+
|
|
34
|
+
const es = new Datasource({ name: "Logs", type: "elasticsearch" });
|
|
35
|
+
const cw = new Datasource({ name: "CloudWatch", type: "cloudwatch" });
|
|
36
|
+
const az = new Datasource({ name: "Azure", type: "grafana-azure-monitor-datasource" });
|
|
37
|
+
const gcm = new Datasource({ name: "GCM", type: "stackdriver" });
|
|
38
|
+
const bq = new Datasource({ name: "BigQuery", type: "grafana-bigquery-datasource" });
|
|
39
|
+
const py = new Datasource({ name: "Pyroscope", type: "grafana-pyroscope-datasource" });
|
|
40
|
+
const pg = new Datasource({ name: "Postgres", type: "grafana-postgresql-datasource" });
|
|
41
|
+
const pgOld = new Datasource({ name: "Postgres old", type: "postgres" });
|
|
42
|
+
const my = new Datasource({ name: "MySQL", type: "mysql" });
|
|
43
|
+
const ms = new Datasource({ name: "MSSQL", type: "mssql" });
|
|
44
|
+
const prom = new Datasource({ name: "Prometheus", type: "prometheus" });
|
|
45
|
+
const loki = new Datasource({ name: "Loki", type: "loki" });
|
|
46
|
+
|
|
47
|
+
function render(panels: unknown[]): Json {
|
|
48
|
+
return renderDashboard(new Dashboard({ title: "Q", uid: "q", panels: panels as never })) as unknown as Json;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const targetsOf = (json: Json, i = 0) => ((json.panels as Json[])[i].targets as Json[]);
|
|
52
|
+
|
|
53
|
+
describe("query classes", () => {
|
|
54
|
+
test("every query schema at the pin has a class, and every class names a schema or is hand-typed", () => {
|
|
55
|
+
const schemas = registeredQueries().filter((d) => d.builtin).flatMap((d) => (d.schema ? [d.schema] : []));
|
|
56
|
+
expect(schemas.sort()).toEqual(
|
|
57
|
+
["prometheus", "tempo", "loki", "elasticsearch", "cloudwatch", "azuremonitor", "googlecloudmonitoring", "bigquery", "grafanapyroscope"].sort(),
|
|
58
|
+
);
|
|
59
|
+
expect(schemas.every((s) => (SCHEMA_NAMES as readonly string[]).includes(s))).toBe(true);
|
|
60
|
+
expect(registeredQueries().filter((d) => d.builtin && !d.schema).map((d) => d.className).sort()).toEqual(["MSSQLQuery", "MySQLQuery", "PostgresQuery"]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("each renders as a target of its plugin and passes GRAF107", () => {
|
|
64
|
+
const json = render([
|
|
65
|
+
new TimeSeriesPanel({
|
|
66
|
+
datasource: es,
|
|
67
|
+
targets: [
|
|
68
|
+
new ElasticsearchQuery({
|
|
69
|
+
query: "level:error",
|
|
70
|
+
timeField: "@timestamp",
|
|
71
|
+
metrics: [{ id: "1", type: "count" }],
|
|
72
|
+
bucketAggs: [{ id: "2", type: "date_histogram", field: "@timestamp", settings: { interval: "auto" } }],
|
|
73
|
+
}),
|
|
74
|
+
],
|
|
75
|
+
}),
|
|
76
|
+
new TimeSeriesPanel({
|
|
77
|
+
datasource: cw,
|
|
78
|
+
targets: [
|
|
79
|
+
new CloudWatchQuery({ queryMode: "Metrics", namespace: "AWS/Lambda", metricName: "Errors", statistic: "Sum", dimensions: { FunctionName: "checkout" } }),
|
|
80
|
+
new CloudWatchQuery({ queryMode: "Logs", expression: "fields @message | limit 20", logGroups: [{ arn: "arn:aws:logs:x", name: "/x" }] }),
|
|
81
|
+
],
|
|
82
|
+
}),
|
|
83
|
+
new TablePanel({
|
|
84
|
+
datasource: az,
|
|
85
|
+
targets: [new AzureMonitorQuery({ queryType: "Azure Log Analytics", azureLogAnalytics: { query: "AppRequests | take 10", resources: ["/subscriptions/x"] } })],
|
|
86
|
+
}),
|
|
87
|
+
new TimeSeriesPanel({
|
|
88
|
+
datasource: gcm,
|
|
89
|
+
targets: [new CloudMonitoringQuery({ queryType: "promQL", promQLQuery: { projectName: "p", expr: "up", step: "10s" } })],
|
|
90
|
+
}),
|
|
91
|
+
new TablePanel({ datasource: bq, targets: [new BigQueryQuery({ rawSql: "SELECT 1", project: "p" })] }),
|
|
92
|
+
new TimeSeriesPanel({ datasource: py, targets: [new PyroscopeQuery({ profileTypeId: "process_cpu:cpu:nanoseconds:cpu:nanoseconds", queryType: "metrics" })] }),
|
|
93
|
+
new TablePanel({ datasource: pg, targets: [new PostgresQuery({ rawSql: "SELECT now()", format: "table", editorMode: "code", rawQuery: true })] }),
|
|
94
|
+
new TablePanel({ datasource: my, targets: [new MySQLQuery({ rawSql: "SELECT 1" })] }),
|
|
95
|
+
new TablePanel({ datasource: ms, targets: [new MSSQLQuery({ rawSql: "SELECT TOP 1 1" })] }),
|
|
96
|
+
]);
|
|
97
|
+
const types = (json.panels as Json[]).map((p) => (targetsOf({ panels: [p] })[0].datasource as Json).type);
|
|
98
|
+
expect(types).toEqual([
|
|
99
|
+
"elasticsearch",
|
|
100
|
+
"cloudwatch",
|
|
101
|
+
"grafana-azure-monitor-datasource",
|
|
102
|
+
"stackdriver",
|
|
103
|
+
"grafana-bigquery-datasource",
|
|
104
|
+
"grafana-pyroscope-datasource",
|
|
105
|
+
"grafana-postgresql-datasource",
|
|
106
|
+
"mysql",
|
|
107
|
+
"mssql",
|
|
108
|
+
]);
|
|
109
|
+
expect(targetsOf(json, 1).map((t) => t.refId)).toEqual(["A", "B"]);
|
|
110
|
+
expect(validateDashboardSchema(json)).toEqual([]);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("GRAF107 checks the schema-typed models: a wrong value is an error, an unknown key a warning", () => {
|
|
114
|
+
const json = render([
|
|
115
|
+
new TimeSeriesPanel({
|
|
116
|
+
datasource: es,
|
|
117
|
+
// @ts-expect-error a bucket aggregation type the schema does not list
|
|
118
|
+
targets: [new ElasticsearchQuery({ query: "*", bucketAggs: [{ id: "2", type: "histogramz" }] })],
|
|
119
|
+
}),
|
|
120
|
+
new TimeSeriesPanel({
|
|
121
|
+
datasource: cw,
|
|
122
|
+
// @ts-expect-error queryMode is Metrics, Logs or Annotations
|
|
123
|
+
targets: [new CloudWatchQuery({ queryMode: "Traces", namespace: "x" })],
|
|
124
|
+
}),
|
|
125
|
+
new TablePanel({
|
|
126
|
+
datasource: bq,
|
|
127
|
+
// @ts-expect-error BigQuery's query has no expr
|
|
128
|
+
targets: [new BigQueryQuery({ rawSql: "SELECT 1", expr: "x" })],
|
|
129
|
+
}),
|
|
130
|
+
]);
|
|
131
|
+
const problems = validateDashboardSchema(json);
|
|
132
|
+
expect(problems.filter((p) => p.path.startsWith("/panels/0/targets/0/bucketAggs")).some((p) => p.severity === "error")).toBe(true);
|
|
133
|
+
expect(problems.some((p) => p.path.startsWith("/panels/1/targets/0") && p.severity === "error")).toBe(true);
|
|
134
|
+
expect(problems).toContainEqual({ path: "/panels/2/targets/0", message: 'unknown key "expr" (not in the pinned schema)', severity: "warning" });
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("a query only takes a datasource of its own plugin type", () => {
|
|
138
|
+
// @ts-expect-error a MySQL query cannot go to a Postgres datasource
|
|
139
|
+
new MySQLQuery({ rawSql: "SELECT 1", datasource: pg });
|
|
140
|
+
// @ts-expect-error nor an Elasticsearch query to Loki
|
|
141
|
+
new ElasticsearchQuery({ query: "*", datasource: loki });
|
|
142
|
+
expect(new PostgresQuery({ rawSql: "SELECT 1", datasource: pgOld }).props.datasource).toBe(pgOld);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("the PostgreSQL class answers to the plugin's old id, postgres", () => {
|
|
146
|
+
expect(queryDefinitionFor("postgres")?.className).toBe("PostgresQuery");
|
|
147
|
+
expect(queryDefinitionFor("grafana-postgresql-datasource")?.className).toBe("PostgresQuery");
|
|
148
|
+
expect(registeredQueries().filter((d) => d.className === "PostgresQuery")).toHaveLength(1);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe("GRAF108 routing with the new query types", () => {
|
|
153
|
+
test("only queries that reach a Prometheus are parsed, whatever field holds PromQL-looking text", () => {
|
|
154
|
+
const json = render([
|
|
155
|
+
new TimeSeriesPanel({ datasource: prom, targets: [new PromQuery({ expr: "sum(rate(x[5m]))" })] }),
|
|
156
|
+
new TimeSeriesPanel({ datasource: gcm, targets: [new CloudMonitoringQuery({ queryType: "promQL", promQLQuery: { projectName: "p", expr: "sum(", step: "1m" } })] }),
|
|
157
|
+
new TimeSeriesPanel({ datasource: loki, targets: [new LokiQuery({ expr: 'sum(rate({job="x"}[5m]' })] }),
|
|
158
|
+
new TablePanel({ datasource: pg, targets: [new PostgresQuery({ rawSql: "SELECT sum(" })] }),
|
|
159
|
+
new TimeSeriesPanel({ datasource: cw, targets: [new CloudWatchQuery({ queryMode: "Metrics", namespace: "", expression: "SUM(METRICS(" })] }),
|
|
160
|
+
]);
|
|
161
|
+
const known = knownDatasources(
|
|
162
|
+
[prom, gcm, loki, pg, cw].map((d) => ({ name: d.props.name, type: d.datasourceType, uid: d.uid })),
|
|
163
|
+
);
|
|
164
|
+
expect(prometheusQueries(json, known).map((q) => q.expr)).toEqual(["sum(rate(x[5m]))"]);
|
|
165
|
+
});
|
|
166
|
+
});
|