@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,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Folders: the one place a build's folders become uids and parents, and how
|
|
3
|
+
* the API applier (#2948) and observation read and write them.
|
|
4
|
+
*
|
|
5
|
+
* ## What a build declares
|
|
6
|
+
*
|
|
7
|
+
* A dashboard names its folder as a path (`Dashboard.folder:
|
|
8
|
+
* "Platform/Kubernetes"`) or as a `Folder` (../folder.ts), and a `Folder`
|
|
9
|
+
* may be declared on its own. {@link resolveFolders} turns all of it into
|
|
10
|
+
* one {@link FolderPlan} per folder: every level of every path, the uid a
|
|
11
|
+
* `Folder` pins or {@link folderUidFor} of its path, and its parent's uid.
|
|
12
|
+
* The build calls it and writes the result to its index (`folders`, and a
|
|
13
|
+
* `folderUid` on each dashboard); the applier reads the index and calls it
|
|
14
|
+
* again, which also covers an index written before folders were listed.
|
|
15
|
+
* Two folders at one path with different uids, or two paths with one uid,
|
|
16
|
+
* throw: no delivery could make both.
|
|
17
|
+
*
|
|
18
|
+
* ## Which API
|
|
19
|
+
*
|
|
20
|
+
* Grafana 12.4 serves `folder.grafana.app/v1beta1` and 13.x also serves
|
|
21
|
+
* `v1`, both keeping `metadata.labels` as written (checked against 12.4.11
|
|
22
|
+
* and 13.2.2), so a folder carries chant's ownership labels the same way a
|
|
23
|
+
* dashboard does. The parent is the `grafana.app/folder` annotation, as on a
|
|
24
|
+
* dashboard. A server without the group (Grafana 11) is written over
|
|
25
|
+
* `/api/folders`, which has `parentUid` but no labels: a folder written
|
|
26
|
+
* there cannot be told apart from anyone else's, so it is never pruned.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { GrafanaApiError, type GrafanaClient } from "./client";
|
|
30
|
+
import { folderLevels, folderUidFor } from "../folder";
|
|
31
|
+
|
|
32
|
+
export { folderUidFor };
|
|
33
|
+
|
|
34
|
+
type Json = Record<string, unknown>;
|
|
35
|
+
|
|
36
|
+
function isObject(v: unknown): v is Json {
|
|
37
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function stringMap(v: unknown): Record<string, string> {
|
|
41
|
+
if (!isObject(v)) return {};
|
|
42
|
+
return Object.fromEntries(Object.entries(v).filter(([, x]) => typeof x === "string")) as Record<string, string>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const FOLDER_GROUP = "folder.grafana.app";
|
|
46
|
+
|
|
47
|
+
/** Folder API versions chant writes, newest first. */
|
|
48
|
+
export const FOLDER_VERSIONS: readonly string[] = ["v1", "v1beta1"];
|
|
49
|
+
|
|
50
|
+
/** The annotation that names a folder's (or a dashboard's) parent folder on `/apis`. */
|
|
51
|
+
export const FOLDER_ANNOTATION = "grafana.app/folder";
|
|
52
|
+
|
|
53
|
+
/** How this server serves folders. */
|
|
54
|
+
export type FolderApi = { readonly kind: "apis"; readonly version: string } | { readonly kind: "legacy" };
|
|
55
|
+
|
|
56
|
+
/** One folder a build needs. */
|
|
57
|
+
export interface FolderPlan {
|
|
58
|
+
readonly uid: string;
|
|
59
|
+
readonly title: string;
|
|
60
|
+
/** The parent folder's uid; absent for a root-level folder. */
|
|
61
|
+
readonly parentUid?: string;
|
|
62
|
+
/** Its titles from the root, joined with `/`: the directory file provisioning reads it from. */
|
|
63
|
+
readonly path?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** A folder as Grafana holds it. */
|
|
67
|
+
export interface LiveFolder {
|
|
68
|
+
readonly uid: string;
|
|
69
|
+
readonly title: string;
|
|
70
|
+
readonly parentUid?: string;
|
|
71
|
+
/** `metadata.labels`; empty on the legacy API. */
|
|
72
|
+
readonly labels: Readonly<Record<string, string>>;
|
|
73
|
+
/** `metadata.annotations`; empty on the legacy API. */
|
|
74
|
+
readonly annotations?: Readonly<Record<string, string>>;
|
|
75
|
+
readonly via: "apis" | "legacy";
|
|
76
|
+
/** The request path it lives at. */
|
|
77
|
+
readonly address: string;
|
|
78
|
+
/** The legacy folder's `version`, which its update needs. */
|
|
79
|
+
readonly version?: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The folders a build resolves to, and the uid of each path. */
|
|
83
|
+
export interface ResolvedFolders {
|
|
84
|
+
/** One per folder, sorted by path, so a parent comes before its children. */
|
|
85
|
+
readonly folders: FolderPlan[];
|
|
86
|
+
/** The uid of the folder at `path` (any spelling `folderLevels` reads the same); undefined for the General folder. */
|
|
87
|
+
uidOf(path: string | undefined): string | undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Every folder a build needs, with its uid and parent: the folders declared
|
|
92
|
+
* with a uid (`Folder`s, or dashboards already resolved), and every level of
|
|
93
|
+
* every path a dashboard names. A level no declaration pins gets
|
|
94
|
+
* {@link folderUidFor} of its path. Throws when two declarations put
|
|
95
|
+
* different uids at one path, and when two paths would get one uid unless
|
|
96
|
+
* `duplicateUids` is `keep`: the build keeps both and GRAF104 reports them,
|
|
97
|
+
* since file provisioning, which gives folders uids of its own, can still
|
|
98
|
+
* deliver them.
|
|
99
|
+
*/
|
|
100
|
+
export function resolveFolders(
|
|
101
|
+
input: { declared?: Iterable<{ uid: string; path: string }>; paths?: Iterable<string | undefined> },
|
|
102
|
+
options: { duplicateUids?: "throw" | "keep" } = {},
|
|
103
|
+
): ResolvedFolders {
|
|
104
|
+
const byPath = new Map<string, string>();
|
|
105
|
+
const key = (path: string) => folderLevels(path).join("/");
|
|
106
|
+
for (const d of input.declared ?? []) {
|
|
107
|
+
const path = key(d.path);
|
|
108
|
+
if (path === "") continue;
|
|
109
|
+
const prior = byPath.get(path);
|
|
110
|
+
if (prior !== undefined && prior !== d.uid) {
|
|
111
|
+
throw new Error(`grafana: the folder "${path}" is declared with the uids "${prior}" and "${d.uid}"; a folder has one uid`);
|
|
112
|
+
}
|
|
113
|
+
byPath.set(path, d.uid);
|
|
114
|
+
}
|
|
115
|
+
const addPath = (path: string) => {
|
|
116
|
+
const levels = folderLevels(path);
|
|
117
|
+
for (let i = 1; i <= levels.length; i++) {
|
|
118
|
+
const prefix = levels.slice(0, i).join("/");
|
|
119
|
+
if (!byPath.has(prefix)) byPath.set(prefix, folderUidFor(prefix));
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
for (const path of [...byPath.keys()]) addPath(path);
|
|
123
|
+
for (const path of input.paths ?? []) if (path) addPath(path);
|
|
124
|
+
|
|
125
|
+
const byUid = new Map<string, string>();
|
|
126
|
+
const folders: FolderPlan[] = [];
|
|
127
|
+
for (const path of [...byPath.keys()].sort((a, b) => a.localeCompare(b))) {
|
|
128
|
+
const uid = byPath.get(path)!;
|
|
129
|
+
const seen = byUid.get(uid);
|
|
130
|
+
if (seen !== undefined && options.duplicateUids !== "keep") throw new Error(`grafana: the folders "${seen}" and "${path}" would both get the uid "${uid}"; give one a Folder with its own uid`);
|
|
131
|
+
byUid.set(uid, path);
|
|
132
|
+
const levels = path.split("/");
|
|
133
|
+
const parentUid = levels.length > 1 ? byPath.get(levels.slice(0, -1).join("/")) : undefined;
|
|
134
|
+
folders.push({ uid, title: levels[levels.length - 1], ...(parentUid ? { parentUid } : {}), path });
|
|
135
|
+
}
|
|
136
|
+
return { folders, uidOf: (path) => (path === undefined ? undefined : byPath.get(key(path))) };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The folders a set of dashboards needs: every level of every folder they
|
|
141
|
+
* name, sorted by path. A dashboard that already carries its `folderUid`
|
|
142
|
+
* pins its folder's uid.
|
|
143
|
+
*/
|
|
144
|
+
export function foldersForDashboards(dashboards: Iterable<{ folder?: string; folderUid?: string }>): FolderPlan[] {
|
|
145
|
+
const list = [...dashboards];
|
|
146
|
+
return resolveFolders({
|
|
147
|
+
declared: list.filter((d) => d.folder && d.folderUid).map((d) => ({ uid: d.folderUid!, path: d.folder! })),
|
|
148
|
+
paths: list.map((d) => d.folder),
|
|
149
|
+
}).folders;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** The folder API the server behind `client` serves, discovered once per client. */
|
|
153
|
+
export function folderApi(client: GrafanaClient): Promise<FolderApi> {
|
|
154
|
+
return client.once("folder-api", async () => {
|
|
155
|
+
const group = await client.get<{ versions?: Array<{ version?: string }> }>(`/apis/${FOLDER_GROUP}`);
|
|
156
|
+
const served = new Set((group?.versions ?? []).map((v) => v.version).filter((v): v is string => typeof v === "string"));
|
|
157
|
+
const version = FOLDER_VERSIONS.find((v) => served.has(v));
|
|
158
|
+
return version ? { kind: "apis", version } : { kind: "legacy" };
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function collection(api: Extract<FolderApi, { kind: "apis" }>, namespace: string): string {
|
|
163
|
+
return `/apis/${FOLDER_GROUP}/${api.version}/namespaces/${encodeURIComponent(namespace)}/folders`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** The request path for one folder on this API. */
|
|
167
|
+
export function folderPath(api: FolderApi, namespace: string, uid: string): string {
|
|
168
|
+
return api.kind === "apis" ? `${collection(api, namespace)}/${encodeURIComponent(uid)}` : `/api/folders/${encodeURIComponent(uid)}`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function fromResource(resource: Json, address: string): LiveFolder {
|
|
172
|
+
const metadata = isObject(resource.metadata) ? resource.metadata : {};
|
|
173
|
+
const spec = isObject(resource.spec) ? resource.spec : {};
|
|
174
|
+
const annotations = stringMap(metadata.annotations);
|
|
175
|
+
const parentUid = annotations[FOLDER_ANNOTATION];
|
|
176
|
+
return {
|
|
177
|
+
uid: typeof metadata.name === "string" ? metadata.name : "",
|
|
178
|
+
title: typeof spec.title === "string" ? spec.title : "",
|
|
179
|
+
...(parentUid ? { parentUid } : {}),
|
|
180
|
+
labels: stringMap(metadata.labels),
|
|
181
|
+
annotations,
|
|
182
|
+
via: "apis",
|
|
183
|
+
address,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function fromLegacy(body: Json, address: string): LiveFolder {
|
|
188
|
+
return {
|
|
189
|
+
uid: typeof body.uid === "string" ? body.uid : "",
|
|
190
|
+
title: typeof body.title === "string" ? body.title : "",
|
|
191
|
+
...(typeof body.parentUid === "string" && body.parentUid !== "" ? { parentUid: body.parentUid } : {}),
|
|
192
|
+
labels: {},
|
|
193
|
+
via: "legacy",
|
|
194
|
+
address,
|
|
195
|
+
...(typeof body.version === "number" ? { version: body.version } : {}),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Read one folder; undefined when there is none with that uid. */
|
|
200
|
+
export async function readFolder(client: GrafanaClient, uid: string): Promise<LiveFolder | undefined> {
|
|
201
|
+
const api = await folderApi(client);
|
|
202
|
+
const address = folderPath(api, client.namespace, uid);
|
|
203
|
+
const body = await client.get<Json>(address);
|
|
204
|
+
if (body === undefined) return undefined;
|
|
205
|
+
return api.kind === "apis" ? fromResource(body, address) : fromLegacy(body, address);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* The path of the folder with this uid, its titles from the root joined with
|
|
210
|
+
* `/`, the form `Dashboard.folder` takes. One read per folder per client;
|
|
211
|
+
* undefined when the folder, or one of its parents, cannot be found.
|
|
212
|
+
*/
|
|
213
|
+
export async function liveFolderPath(client: GrafanaClient, uid: string): Promise<string | undefined> {
|
|
214
|
+
const titles: string[] = [];
|
|
215
|
+
const seen = new Set<string>();
|
|
216
|
+
for (let at: string | undefined = uid; at; ) {
|
|
217
|
+
// Grafana's own limit on nesting is 8 levels; a longer chain is a cycle.
|
|
218
|
+
if (seen.has(at) || seen.size > 8) return undefined;
|
|
219
|
+
seen.add(at);
|
|
220
|
+
const id: string = at;
|
|
221
|
+
const folder = await client.once(`folder:${id}`, () => readFolder(client, id));
|
|
222
|
+
if (!folder) return undefined;
|
|
223
|
+
titles.unshift(folder.title);
|
|
224
|
+
at = folder.parentUid;
|
|
225
|
+
}
|
|
226
|
+
return titles.join("/");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** Every folder in the organisation over `/apis`, paged. The legacy API has no labels to list by, so it is not listed. */
|
|
230
|
+
export async function listFolders(client: GrafanaClient): Promise<LiveFolder[]> {
|
|
231
|
+
const api = await folderApi(client);
|
|
232
|
+
if (api.kind !== "apis") return [];
|
|
233
|
+
const base = collection(api, client.namespace);
|
|
234
|
+
const out: LiveFolder[] = [];
|
|
235
|
+
let next: string | undefined;
|
|
236
|
+
do {
|
|
237
|
+
const page = await client.get<{ items?: unknown[]; metadata?: { continue?: string } }>(
|
|
238
|
+
`${base}?limit=500${next ? `&continue=${encodeURIComponent(next)}` : ""}`,
|
|
239
|
+
);
|
|
240
|
+
for (const item of page?.items ?? []) {
|
|
241
|
+
if (!isObject(item)) continue;
|
|
242
|
+
const name = isObject(item.metadata) && typeof item.metadata.name === "string" ? item.metadata.name : "";
|
|
243
|
+
if (name) out.push(fromResource(item, `${base}/${encodeURIComponent(name)}`));
|
|
244
|
+
}
|
|
245
|
+
next = page?.metadata?.continue || undefined;
|
|
246
|
+
} while (next);
|
|
247
|
+
return out;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Send one write; throws {@link GrafanaApiError} on anything but a 2xx. */
|
|
251
|
+
export async function send(client: GrafanaClient, method: string, path: string, body?: unknown): Promise<unknown> {
|
|
252
|
+
const res = await client.http(method, path, body);
|
|
253
|
+
if (res.status < 200 || res.status >= 300) throw new GrafanaApiError(res.status, method, path, res.json);
|
|
254
|
+
return res.json;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function sameLabels(live: Readonly<Record<string, string>>, want: Readonly<Record<string, string>>): boolean {
|
|
258
|
+
return Object.entries(want).every(([k, v]) => live[k] === v);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Create or update one folder so it matches `plan`, with chant's `labels`
|
|
263
|
+
* on it where the API keeps labels. Unchanged when title, parent and labels
|
|
264
|
+
* already match; nothing is written then.
|
|
265
|
+
*/
|
|
266
|
+
export async function ensureFolder(
|
|
267
|
+
client: GrafanaClient,
|
|
268
|
+
plan: FolderPlan,
|
|
269
|
+
labels: Readonly<Record<string, string>>,
|
|
270
|
+
): Promise<{ action: "created" | "updated" | "unchanged"; address: string; via: "apis" | "legacy" }> {
|
|
271
|
+
const api = await folderApi(client);
|
|
272
|
+
const address = folderPath(api, client.namespace, plan.uid);
|
|
273
|
+
const live = await readFolder(client, plan.uid);
|
|
274
|
+
const sameParent = (live?.parentUid ?? "") === (plan.parentUid ?? "");
|
|
275
|
+
if (api.kind === "apis") {
|
|
276
|
+
if (live && live.title === plan.title && sameParent && sameLabels(live.labels, labels)) return { action: "unchanged", address, via: "apis" };
|
|
277
|
+
// A folder's labels are merged, so a label another tool put there stays.
|
|
278
|
+
const metadata = {
|
|
279
|
+
name: plan.uid,
|
|
280
|
+
labels: { ...(live?.labels ?? {}), ...labels },
|
|
281
|
+
...(plan.parentUid ? { annotations: { [FOLDER_ANNOTATION]: plan.parentUid } } : {}),
|
|
282
|
+
};
|
|
283
|
+
const resource = { apiVersion: `${FOLDER_GROUP}/${api.version}`, kind: "Folder", metadata, spec: { title: plan.title } };
|
|
284
|
+
if (live) {
|
|
285
|
+
await send(client, "PUT", address, resource);
|
|
286
|
+
return { action: "updated", address, via: "apis" };
|
|
287
|
+
}
|
|
288
|
+
await send(client, "POST", collection(api, client.namespace), resource);
|
|
289
|
+
return { action: "created", address, via: "apis" };
|
|
290
|
+
}
|
|
291
|
+
if (live) {
|
|
292
|
+
if (live.title === plan.title && sameParent) return { action: "unchanged", address, via: "legacy" };
|
|
293
|
+
if (live.title !== plan.title) await send(client, "PUT", address, { title: plan.title, ...(live.version !== undefined ? { version: live.version } : {}), overwrite: true });
|
|
294
|
+
if (!sameParent) await send(client, "POST", `${address}/move`, { parentUid: plan.parentUid ?? "" });
|
|
295
|
+
return { action: "updated", address, via: "legacy" };
|
|
296
|
+
}
|
|
297
|
+
await send(client, "POST", "/api/folders", { uid: plan.uid, title: plan.title, ...(plan.parentUid ? { parentUid: plan.parentUid } : {}) });
|
|
298
|
+
return { action: "created", address, via: "legacy" };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* How many things a folder holds (dashboards, folders, library panels, alert
|
|
303
|
+
* rules), from `GET /api/folders/<uid>/counts`. Grafana 12.4 and 13.2 name
|
|
304
|
+
* the keys differently, so every numeric value is summed.
|
|
305
|
+
*/
|
|
306
|
+
export async function folderContentCount(client: GrafanaClient, uid: string): Promise<number> {
|
|
307
|
+
const counts = await client.get<Json>(`/api/folders/${encodeURIComponent(uid)}/counts`);
|
|
308
|
+
if (!counts) return 0;
|
|
309
|
+
return Object.values(counts).reduce<number>((n, v) => n + (typeof v === "number" ? v : 0), 0);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Delete one folder over `/apis`. Only ever called for a folder that holds
|
|
314
|
+
* nothing: Grafana 12.4 and 13.2 refuse to delete a folder with anything in
|
|
315
|
+
* it over `/apis`, and the applier checks first, so a foreign dashboard in a
|
|
316
|
+
* folder chant made is never removed with it. False when it was already gone.
|
|
317
|
+
*/
|
|
318
|
+
export async function deleteFolder(client: GrafanaClient, folder: LiveFolder): Promise<boolean> {
|
|
319
|
+
if (folder.via !== "apis") throw new Error(`grafana apply: folder "${folder.uid}" was read over /api/folders, whose delete removes what the folder holds; chant does not delete it`);
|
|
320
|
+
const res = await client.http("DELETE", folder.address);
|
|
321
|
+
if (res.status === 404) return false;
|
|
322
|
+
if (res.status < 200 || res.status >= 300) throw new GrafanaApiError(res.status, "DELETE", folder.address, res.json);
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Order folders so a child comes before its parent: the order to delete
|
|
328
|
+
* them in. Parents outside the set count as depth 0.
|
|
329
|
+
*/
|
|
330
|
+
export function childrenFirst(folders: readonly LiveFolder[]): LiveFolder[] {
|
|
331
|
+
const byUid = new Map(folders.map((f) => [f.uid, f]));
|
|
332
|
+
const depth = (f: LiveFolder, seen = new Set<string>()): number => {
|
|
333
|
+
if (!f.parentUid || seen.has(f.uid)) return 0;
|
|
334
|
+
seen.add(f.uid);
|
|
335
|
+
const parent = byUid.get(f.parentUid);
|
|
336
|
+
return parent ? 1 + depth(parent, seen) : 1;
|
|
337
|
+
};
|
|
338
|
+
return [...folders].sort((a, b) => depth(b) - depth(a) || a.uid.localeCompare(b.uid));
|
|
339
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Library panels, for the API applier (#2948).
|
|
3
|
+
*
|
|
4
|
+
* A dashboard that uses a library panel holds only a reference to it
|
|
5
|
+
* (`{ libraryPanel: { uid, name } }`); the panel itself lives in Grafana's
|
|
6
|
+
* library, and an exported dashboard carries its model in `__elements`,
|
|
7
|
+
* keyed by uid. The applier writes each element to the library over
|
|
8
|
+
* `/api/library-elements` before the dashboards that reference it, and
|
|
9
|
+
* sends the dashboards without `__elements`, `__inputs` or `__requires`,
|
|
10
|
+
* which are export-only keys Grafana does not store.
|
|
11
|
+
*
|
|
12
|
+
* The library elements API is the same on Grafana 11, 12.4 and 13.2. It has
|
|
13
|
+
* no labels, so a library panel cannot carry chant's marker and is never
|
|
14
|
+
* pruned (the applier reports that as `not-prunable`). An update needs the
|
|
15
|
+
* element's current `version`, which the read supplies.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { GrafanaClient } from "./client";
|
|
19
|
+
import { send } from "./folders";
|
|
20
|
+
import { converged } from "./converge";
|
|
21
|
+
|
|
22
|
+
type Json = Record<string, unknown>;
|
|
23
|
+
|
|
24
|
+
function isObject(v: unknown): v is Json {
|
|
25
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** `kind` of a library panel in the library elements API (2 is a library variable). */
|
|
29
|
+
export const LIBRARY_PANEL_KIND = 1;
|
|
30
|
+
|
|
31
|
+
/** Keys an exported dashboard carries that Grafana does not store. */
|
|
32
|
+
export const EXPORT_ONLY_KEYS: readonly string[] = ["__elements", "__inputs", "__requires"];
|
|
33
|
+
|
|
34
|
+
/** One library panel a build needs. */
|
|
35
|
+
export interface LibraryPanelPlan {
|
|
36
|
+
readonly uid: string;
|
|
37
|
+
readonly name: string;
|
|
38
|
+
readonly model: Json;
|
|
39
|
+
/** The folder it goes in: that of the first dashboard that carries it. Absent for the General folder. */
|
|
40
|
+
readonly folderUid?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The library panels in a dashboard's `__elements`. An element that is not a
|
|
45
|
+
* panel (a library variable), or that has no uid, name or model, is not
|
|
46
|
+
* something the library panel API can take; it is returned in `skipped`.
|
|
47
|
+
*/
|
|
48
|
+
export function libraryPanelsOf(dashboard: Json, folderUid?: string): { panels: LibraryPanelPlan[]; skipped: string[] } {
|
|
49
|
+
const elements = dashboard.__elements;
|
|
50
|
+
const panels: LibraryPanelPlan[] = [];
|
|
51
|
+
const skipped: string[] = [];
|
|
52
|
+
if (!isObject(elements)) return { panels, skipped };
|
|
53
|
+
for (const [key, el] of Object.entries(elements)) {
|
|
54
|
+
if (!isObject(el)) {
|
|
55
|
+
skipped.push(key);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const uid = typeof el.uid === "string" && el.uid !== "" ? el.uid : key;
|
|
59
|
+
const kind = el.kind ?? LIBRARY_PANEL_KIND;
|
|
60
|
+
if (kind !== LIBRARY_PANEL_KIND || typeof el.name !== "string" || !isObject(el.model)) {
|
|
61
|
+
skipped.push(uid);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
panels.push({ uid, name: el.name, model: el.model, ...(folderUid ? { folderUid } : {}) });
|
|
65
|
+
}
|
|
66
|
+
return { panels, skipped };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** A dashboard without the export-only keys, as the applier sends it. */
|
|
70
|
+
export function withoutExportKeys(dashboard: Json): Json {
|
|
71
|
+
const out: Json = { ...dashboard };
|
|
72
|
+
for (const k of EXPORT_ONLY_KEYS) delete out[k];
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function libraryPanelPath(uid: string): string {
|
|
77
|
+
return `/api/library-elements/${encodeURIComponent(uid)}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Create or update one library panel. Unchanged when its name, folder and
|
|
82
|
+
* model already match; nothing is written then.
|
|
83
|
+
*/
|
|
84
|
+
export async function ensureLibraryPanel(
|
|
85
|
+
client: GrafanaClient,
|
|
86
|
+
plan: LibraryPanelPlan,
|
|
87
|
+
): Promise<{ action: "created" | "updated" | "unchanged"; address: string }> {
|
|
88
|
+
const address = libraryPanelPath(plan.uid);
|
|
89
|
+
const live = (await client.get<{ result?: Json }>(address))?.result;
|
|
90
|
+
const body = { name: plan.name, kind: LIBRARY_PANEL_KIND, model: plan.model, folderUid: plan.folderUid ?? "" };
|
|
91
|
+
if (!live) {
|
|
92
|
+
await send(client, "POST", "/api/library-elements", { uid: plan.uid, ...body });
|
|
93
|
+
return { action: "created", address };
|
|
94
|
+
}
|
|
95
|
+
const liveFolder = typeof live.folderUid === "string" ? live.folderUid : "";
|
|
96
|
+
if (live.name === plan.name && liveFolder === (plan.folderUid ?? "") && converged(plan.model, live.model)) {
|
|
97
|
+
return { action: "unchanged", address };
|
|
98
|
+
}
|
|
99
|
+
await send(client, "PATCH", address, { ...body, version: typeof live.version === "number" ? live.version : 1 });
|
|
100
|
+
return { action: "updated", address };
|
|
101
|
+
}
|