@intentius/chant-lexicon-grafana 0.96.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 +50 -0
- package/dist/build.d.ts +116 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/catalog.d.ts +26 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/codegen/docs-cli.d.ts +3 -0
- package/dist/codegen/docs-cli.d.ts.map +1 -0
- package/dist/codegen/docs.d.ts +8 -0
- package/dist/codegen/docs.d.ts.map +1 -0
- package/dist/codegen/generate-cli.d.ts +3 -0
- package/dist/codegen/generate-cli.d.ts.map +1 -0
- package/dist/codegen/generate.d.ts +21 -0
- package/dist/codegen/generate.d.ts.map +1 -0
- package/dist/codegen/package.d.ts +7 -0
- package/dist/codegen/package.d.ts.map +1 -0
- package/dist/codegen/schema-types.d.ts +41 -0
- package/dist/codegen/schema-types.d.ts.map +1 -0
- package/dist/composites/agent-dashboard.d.ts +70 -0
- package/dist/composites/agent-dashboard.d.ts.map +1 -0
- package/dist/composites/catalog.d.ts +3 -0
- package/dist/composites/catalog.d.ts.map +1 -0
- package/dist/composites/index.d.ts +12 -0
- package/dist/composites/index.d.ts.map +1 -0
- package/dist/composites/red-dashboard.d.ts +58 -0
- package/dist/composites/red-dashboard.d.ts.map +1 -0
- package/dist/composites/shared.d.ts +68 -0
- package/dist/composites/shared.d.ts.map +1 -0
- package/dist/composites/slo-dashboard.d.ts +62 -0
- package/dist/composites/slo-dashboard.d.ts.map +1 -0
- package/dist/dashboard.d.ts +84 -0
- package/dist/dashboard.d.ts.map +1 -0
- package/dist/datasource.d.ts +67 -0
- package/dist/datasource.d.ts.map +1 -0
- package/dist/detect.d.ts +20 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/integrity.json +22 -0
- package/dist/lint/audit-catalog.d.ts +13 -0
- package/dist/lint/audit-catalog.d.ts.map +1 -0
- package/dist/lint/post-synth/graf101.d.ts +8 -0
- package/dist/lint/post-synth/graf101.d.ts.map +1 -0
- package/dist/lint/post-synth/graf102.d.ts +8 -0
- package/dist/lint/post-synth/graf102.d.ts.map +1 -0
- package/dist/lint/post-synth/graf103.d.ts +8 -0
- package/dist/lint/post-synth/graf103.d.ts.map +1 -0
- package/dist/lint/post-synth/graf104.d.ts +8 -0
- package/dist/lint/post-synth/graf104.d.ts.map +1 -0
- package/dist/lint/post-synth/graf105.d.ts +8 -0
- package/dist/lint/post-synth/graf105.d.ts.map +1 -0
- package/dist/lint/post-synth/graf106.d.ts +8 -0
- package/dist/lint/post-synth/graf106.d.ts.map +1 -0
- package/dist/lint/post-synth/graf107.d.ts +8 -0
- package/dist/lint/post-synth/graf107.d.ts.map +1 -0
- package/dist/lint/post-synth/grafana-helpers.d.ts +16 -0
- package/dist/lint/post-synth/grafana-helpers.d.ts.map +1 -0
- package/dist/lint/post-synth/index.d.ts +3 -0
- package/dist/lint/post-synth/index.d.ts.map +1 -0
- package/dist/lint/rules/grafana-ast.d.ts +21 -0
- package/dist/lint/rules/grafana-ast.d.ts.map +1 -0
- package/dist/lint/rules/index.d.ts +6 -0
- package/dist/lint/rules/index.d.ts.map +1 -0
- package/dist/lint/rules/literal-secret.d.ts +12 -0
- package/dist/lint/rules/literal-secret.d.ts.map +1 -0
- package/dist/lint/rules/uid-syntax.d.ts +12 -0
- package/dist/lint/rules/uid-syntax.d.ts.map +1 -0
- package/dist/lsp/completions.d.ts +4 -0
- package/dist/lsp/completions.d.ts.map +1 -0
- package/dist/lsp/hover.d.ts +4 -0
- package/dist/lsp/hover.d.ts.map +1 -0
- package/dist/manifest.json +8 -0
- package/dist/meta.json +107 -0
- package/dist/okf/index.md +39 -0
- package/dist/okf/rules/GRAF001.md +16 -0
- package/dist/okf/rules/GRAF002.md +15 -0
- package/dist/okf/rules/GRAF101.md +15 -0
- package/dist/okf/rules/GRAF102.md +15 -0
- package/dist/okf/rules/GRAF103.md +11 -0
- package/dist/okf/rules/GRAF104.md +15 -0
- package/dist/okf/rules/GRAF105.md +11 -0
- package/dist/okf/rules/GRAF106.md +11 -0
- package/dist/okf/rules/GRAF107.md +11 -0
- package/dist/okf/types/ConstantVariable.md +9 -0
- package/dist/okf/types/CustomVariable.md +9 -0
- package/dist/okf/types/Dashboard.md +14 -0
- package/dist/okf/types/DashboardProvider.md +9 -0
- package/dist/okf/types/Datasource.md +16 -0
- package/dist/okf/types/DatasourceVariable.md +9 -0
- package/dist/okf/types/GaugePanel.md +9 -0
- package/dist/okf/types/HeatmapPanel.md +9 -0
- package/dist/okf/types/IntervalVariable.md +9 -0
- package/dist/okf/types/LogsPanel.md +9 -0
- package/dist/okf/types/LokiQuery.md +9 -0
- package/dist/okf/types/PromQuery.md +9 -0
- package/dist/okf/types/QueryVariable.md +9 -0
- package/dist/okf/types/Row.md +9 -0
- package/dist/okf/types/StatPanel.md +9 -0
- package/dist/okf/types/TablePanel.md +9 -0
- package/dist/okf/types/TempoQuery.md +9 -0
- package/dist/okf/types/TextPanel.md +9 -0
- package/dist/okf/types/TextboxVariable.md +9 -0
- package/dist/okf/types/TimeSeriesPanel.md +9 -0
- package/dist/okf/types/TracesPanel.md +9 -0
- package/dist/package-cli.d.ts +3 -0
- package/dist/package-cli.d.ts.map +1 -0
- package/dist/panels.d.ts +138 -0
- package/dist/panels.d.ts.map +1 -0
- package/dist/pin.d.ts +32 -0
- package/dist/pin.d.ts.map +1 -0
- package/dist/plugin.d.ts +12 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/query.d.ts +81 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/rules/graf101.ts +17 -0
- package/dist/rules/graf102.ts +17 -0
- package/dist/rules/graf103.ts +17 -0
- package/dist/rules/graf104.ts +17 -0
- package/dist/rules/graf105.ts +17 -0
- package/dist/rules/graf106.ts +17 -0
- package/dist/rules/graf107.ts +17 -0
- package/dist/rules/grafana-ast.ts +70 -0
- package/dist/rules/grafana-helpers.ts +68 -0
- package/dist/rules/literal-secret.ts +48 -0
- package/dist/rules/uid-syntax.ts +56 -0
- package/dist/schema/dashboard.gen.d.ts +1231 -0
- package/dist/schema/dashboard.gen.d.ts.map +1 -0
- package/dist/schema/gauge.gen.d.ts +57 -0
- package/dist/schema/gauge.gen.d.ts.map +1 -0
- package/dist/schema/heatmap.gen.d.ts +295 -0
- package/dist/schema/heatmap.gen.d.ts.map +1 -0
- package/dist/schema/index.d.ts +18 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/logs.gen.d.ts +29 -0
- package/dist/schema/logs.gen.d.ts.map +1 -0
- package/dist/schema/loki.gen.d.ts +69 -0
- package/dist/schema/loki.gen.d.ts.map +1 -0
- package/dist/schema/prometheus.gen.d.ts +169 -0
- package/dist/schema/prometheus.gen.d.ts.map +1 -0
- package/dist/schema/stat.gen.d.ts +75 -0
- package/dist/schema/stat.gen.d.ts.map +1 -0
- package/dist/schema/table.gen.d.ts +295 -0
- package/dist/schema/table.gen.d.ts.map +1 -0
- package/dist/schema/tempo.gen.d.ts +136 -0
- package/dist/schema/tempo.gen.d.ts.map +1 -0
- package/dist/schema/text.gen.d.ts +16 -0
- package/dist/schema/text.gen.d.ts.map +1 -0
- package/dist/schema/timeseries.gen.d.ts +179 -0
- package/dist/schema/timeseries.gen.d.ts.map +1 -0
- package/dist/schema-validate.d.ts +20 -0
- package/dist/schema-validate.d.ts.map +1 -0
- package/dist/serializer.d.ts +22 -0
- package/dist/serializer.d.ts.map +1 -0
- package/dist/skill-defs.d.ts +3 -0
- package/dist/skill-defs.d.ts.map +1 -0
- package/dist/skills/chant-grafana-provisioning.md +42 -0
- package/dist/skills/chant-grafana.md +93 -0
- package/dist/spec/fetch-cli.d.ts +3 -0
- package/dist/spec/fetch-cli.d.ts.map +1 -0
- package/dist/spec/schemas.d.ts +25 -0
- package/dist/spec/schemas.d.ts.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/util.d.ts +17 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/validate-cli.d.ts +3 -0
- package/dist/validate-cli.d.ts.map +1 -0
- package/dist/validate-output.d.ts +42 -0
- package/dist/validate-output.d.ts.map +1 -0
- package/dist/validate.d.ts +12 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/variables.d.ts +100 -0
- package/dist/variables.d.ts.map +1 -0
- package/package.json +79 -0
- package/src/build.test.ts +210 -0
- package/src/build.ts +573 -0
- package/src/catalog.ts +116 -0
- package/src/codegen/docs-cli.ts +4 -0
- package/src/codegen/docs.ts +97 -0
- package/src/codegen/generate-cli.ts +8 -0
- package/src/codegen/generate.ts +83 -0
- package/src/codegen/package.ts +41 -0
- package/src/codegen/schema-types.ts +173 -0
- package/src/composites/agent-dashboard.ts +235 -0
- package/src/composites/catalog.test.ts +37 -0
- package/src/composites/catalog.ts +223 -0
- package/src/composites/composites.test.ts +314 -0
- package/src/composites/index.ts +12 -0
- package/src/composites/red-dashboard.ts +159 -0
- package/src/composites/shared.ts +94 -0
- package/src/composites/slo-dashboard.ts +177 -0
- package/src/dashboard.ts +123 -0
- package/src/datasource.ts +93 -0
- package/src/detect.ts +30 -0
- package/src/generated/lexicon-grafana.json +107 -0
- package/src/import.e2e.test.ts +147 -0
- package/src/import.test.ts +45 -0
- package/src/index.ts +178 -0
- package/src/lint/audit-catalog.ts +87 -0
- package/src/lint/post-synth/graf101.ts +17 -0
- package/src/lint/post-synth/graf102.ts +17 -0
- package/src/lint/post-synth/graf103.ts +17 -0
- package/src/lint/post-synth/graf104.ts +17 -0
- package/src/lint/post-synth/graf105.ts +17 -0
- package/src/lint/post-synth/graf106.ts +17 -0
- package/src/lint/post-synth/graf107.ts +17 -0
- package/src/lint/post-synth/grafana-helpers.ts +68 -0
- package/src/lint/post-synth/index.ts +19 -0
- package/src/lint/post-synth/post-synth.test.ts +231 -0
- package/src/lint/rules/grafana-ast.ts +70 -0
- package/src/lint/rules/index.ts +9 -0
- package/src/lint/rules/literal-secret.ts +48 -0
- package/src/lint/rules/rules.test.ts +70 -0
- package/src/lint/rules/uid-syntax.ts +56 -0
- package/src/lsp/completions.test.ts +31 -0
- package/src/lsp/completions.ts +15 -0
- package/src/lsp/hover.test.ts +25 -0
- package/src/lsp/hover.ts +28 -0
- package/src/package-cli.ts +17 -0
- package/src/panels.ts +275 -0
- package/src/pin.ts +68 -0
- package/src/plugin.test.ts +50 -0
- package/src/plugin.ts +116 -0
- package/src/query.ts +160 -0
- package/src/schema/dashboard.gen.ts +1289 -0
- package/src/schema/gauge.gen.ts +64 -0
- package/src/schema/heatmap.gen.ts +321 -0
- package/src/schema/index.ts +17 -0
- package/src/schema/logs.gen.ts +34 -0
- package/src/schema/loki.gen.ts +77 -0
- package/src/schema/prometheus.gen.ts +181 -0
- package/src/schema/stat.gen.ts +86 -0
- package/src/schema/table.gen.ts +334 -0
- package/src/schema/tempo.gen.ts +146 -0
- package/src/schema/text.gen.ts +22 -0
- package/src/schema/timeseries.gen.ts +208 -0
- package/src/schema-validate.ts +143 -0
- package/src/schema.test.ts +76 -0
- package/src/serializer.test.ts +170 -0
- package/src/serializer.ts +37 -0
- package/src/skill-defs.ts +31 -0
- package/src/skills/chant-grafana-provisioning.md +42 -0
- package/src/skills/chant-grafana.md +93 -0
- package/src/spec/fetch-cli.ts +27 -0
- package/src/spec/schemas/dashboard.jsonschema.json +1901 -0
- package/src/spec/schemas/gauge.jsonschema.json +114 -0
- package/src/spec/schemas/heatmap.jsonschema.json +480 -0
- package/src/spec/schemas/logs.jsonschema.json +94 -0
- package/src/spec/schemas/loki.jsonschema.json +118 -0
- package/src/spec/schemas/prometheus.jsonschema.json +276 -0
- package/src/spec/schemas/stat.jsonschema.json +163 -0
- package/src/spec/schemas/table.jsonschema.json +751 -0
- package/src/spec/schemas/tempo.jsonschema.json +225 -0
- package/src/spec/schemas/text.jsonschema.json +72 -0
- package/src/spec/schemas/timeseries.jsonschema.json +471 -0
- package/src/spec/schemas.ts +56 -0
- package/src/util.ts +43 -0
- package/src/validate-cli.ts +7 -0
- package/src/validate-output.ts +380 -0
- package/src/validate.ts +108 -0
- package/src/variables.ts +161 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ConstantVariable": {
|
|
3
|
+
"resourceType": "Grafana::Variable::constant",
|
|
4
|
+
"kind": "resource",
|
|
5
|
+
"lexicon": "grafana"
|
|
6
|
+
},
|
|
7
|
+
"CustomVariable": {
|
|
8
|
+
"resourceType": "Grafana::Variable::custom",
|
|
9
|
+
"kind": "resource",
|
|
10
|
+
"lexicon": "grafana"
|
|
11
|
+
},
|
|
12
|
+
"Dashboard": {
|
|
13
|
+
"resourceType": "Grafana::Dashboard",
|
|
14
|
+
"kind": "resource",
|
|
15
|
+
"lexicon": "grafana"
|
|
16
|
+
},
|
|
17
|
+
"DashboardProvider": {
|
|
18
|
+
"resourceType": "Grafana::DashboardProvider",
|
|
19
|
+
"kind": "resource",
|
|
20
|
+
"lexicon": "grafana"
|
|
21
|
+
},
|
|
22
|
+
"Datasource": {
|
|
23
|
+
"resourceType": "Grafana::Datasource",
|
|
24
|
+
"kind": "resource",
|
|
25
|
+
"lexicon": "grafana"
|
|
26
|
+
},
|
|
27
|
+
"DatasourceVariable": {
|
|
28
|
+
"resourceType": "Grafana::Variable::datasource",
|
|
29
|
+
"kind": "resource",
|
|
30
|
+
"lexicon": "grafana"
|
|
31
|
+
},
|
|
32
|
+
"GaugePanel": {
|
|
33
|
+
"resourceType": "Grafana::Panel::gauge",
|
|
34
|
+
"kind": "resource",
|
|
35
|
+
"lexicon": "grafana"
|
|
36
|
+
},
|
|
37
|
+
"HeatmapPanel": {
|
|
38
|
+
"resourceType": "Grafana::Panel::heatmap",
|
|
39
|
+
"kind": "resource",
|
|
40
|
+
"lexicon": "grafana"
|
|
41
|
+
},
|
|
42
|
+
"IntervalVariable": {
|
|
43
|
+
"resourceType": "Grafana::Variable::interval",
|
|
44
|
+
"kind": "resource",
|
|
45
|
+
"lexicon": "grafana"
|
|
46
|
+
},
|
|
47
|
+
"LogsPanel": {
|
|
48
|
+
"resourceType": "Grafana::Panel::logs",
|
|
49
|
+
"kind": "resource",
|
|
50
|
+
"lexicon": "grafana"
|
|
51
|
+
},
|
|
52
|
+
"LokiQuery": {
|
|
53
|
+
"resourceType": "Grafana::Query::loki",
|
|
54
|
+
"kind": "resource",
|
|
55
|
+
"lexicon": "grafana"
|
|
56
|
+
},
|
|
57
|
+
"PromQuery": {
|
|
58
|
+
"resourceType": "Grafana::Query::prometheus",
|
|
59
|
+
"kind": "resource",
|
|
60
|
+
"lexicon": "grafana"
|
|
61
|
+
},
|
|
62
|
+
"QueryVariable": {
|
|
63
|
+
"resourceType": "Grafana::Variable::query",
|
|
64
|
+
"kind": "resource",
|
|
65
|
+
"lexicon": "grafana"
|
|
66
|
+
},
|
|
67
|
+
"Row": {
|
|
68
|
+
"resourceType": "Grafana::Row",
|
|
69
|
+
"kind": "resource",
|
|
70
|
+
"lexicon": "grafana"
|
|
71
|
+
},
|
|
72
|
+
"StatPanel": {
|
|
73
|
+
"resourceType": "Grafana::Panel::stat",
|
|
74
|
+
"kind": "resource",
|
|
75
|
+
"lexicon": "grafana"
|
|
76
|
+
},
|
|
77
|
+
"TablePanel": {
|
|
78
|
+
"resourceType": "Grafana::Panel::table",
|
|
79
|
+
"kind": "resource",
|
|
80
|
+
"lexicon": "grafana"
|
|
81
|
+
},
|
|
82
|
+
"TempoQuery": {
|
|
83
|
+
"resourceType": "Grafana::Query::tempo",
|
|
84
|
+
"kind": "resource",
|
|
85
|
+
"lexicon": "grafana"
|
|
86
|
+
},
|
|
87
|
+
"TextboxVariable": {
|
|
88
|
+
"resourceType": "Grafana::Variable::textbox",
|
|
89
|
+
"kind": "resource",
|
|
90
|
+
"lexicon": "grafana"
|
|
91
|
+
},
|
|
92
|
+
"TextPanel": {
|
|
93
|
+
"resourceType": "Grafana::Panel::text",
|
|
94
|
+
"kind": "resource",
|
|
95
|
+
"lexicon": "grafana"
|
|
96
|
+
},
|
|
97
|
+
"TimeSeriesPanel": {
|
|
98
|
+
"resourceType": "Grafana::Panel::timeseries",
|
|
99
|
+
"kind": "resource",
|
|
100
|
+
"lexicon": "grafana"
|
|
101
|
+
},
|
|
102
|
+
"TracesPanel": {
|
|
103
|
+
"resourceType": "Grafana::Panel::traces",
|
|
104
|
+
"kind": "resource",
|
|
105
|
+
"lexicon": "grafana"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "A dashboard builds to JSON that Grafana imports without edits", against
|
|
3
|
+
* Grafana itself.
|
|
4
|
+
*
|
|
5
|
+
* Builds each example (getting-started, and dashboards-from-declarations,
|
|
6
|
+
* whose dashboards the RED, SLO and agent composites build), mounts its
|
|
7
|
+
* provisioning files and dashboards into a pinned `grafana/grafana`
|
|
8
|
+
* container, and asserts that
|
|
9
|
+
* Grafana provisioned every datasource and dashboard and hands each
|
|
10
|
+
* dashboard back with the panels that were built. It then imports the same
|
|
11
|
+
* JSON a second time through `POST /api/dashboards/db`, the path the UI's
|
|
12
|
+
* import uses, under another uid.
|
|
13
|
+
*
|
|
14
|
+
* Skipped, with the reason in the test name, when Docker is not running.
|
|
15
|
+
* On demand: `npx vitest run --project e2e lexicons/grafana`.
|
|
16
|
+
*/
|
|
17
|
+
import { execSync } from "node:child_process";
|
|
18
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
19
|
+
import { tmpdir } from "node:os";
|
|
20
|
+
import { dirname, join } from "node:path";
|
|
21
|
+
import { fileURLToPath } from "node:url";
|
|
22
|
+
import { afterAll, describe, expect, it } from "vitest";
|
|
23
|
+
import { build } from "@intentius/chant/build";
|
|
24
|
+
import type { SerializerResult } from "@intentius/chant/serializer";
|
|
25
|
+
import type { Serializer } from "@intentius/chant/serializer";
|
|
26
|
+
import { otelSerializer } from "@intentius/chant-lexicon-otel/serializer";
|
|
27
|
+
import { prometheusSerializer } from "@intentius/chant-lexicon-prometheus/serializer";
|
|
28
|
+
import { grafanaSerializer } from "./serializer";
|
|
29
|
+
|
|
30
|
+
/** The Grafana release the import test runs against; `CHANT_GRAFANA_IMAGE` overrides it. */
|
|
31
|
+
export const GRAFANA_IMAGE = process.env.CHANT_GRAFANA_IMAGE ?? "grafana/grafana:12.4.11";
|
|
32
|
+
|
|
33
|
+
function available(cmd: string): boolean {
|
|
34
|
+
try {
|
|
35
|
+
execSync(cmd, { stdio: "ignore" });
|
|
36
|
+
return true;
|
|
37
|
+
} catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const hasDocker = available("docker info");
|
|
43
|
+
const skipReason = hasDocker ? "" : "Docker is not running";
|
|
44
|
+
const AUTH = `Basic ${Buffer.from("admin:admin").toString("base64")}`;
|
|
45
|
+
const examples = join(dirname(dirname(fileURLToPath(import.meta.url))), "examples");
|
|
46
|
+
|
|
47
|
+
/** Each example, and the serializers its build root needs. */
|
|
48
|
+
const EXAMPLES: Array<{ name: string; serializers: Serializer[] }> = [
|
|
49
|
+
{ name: "getting-started", serializers: [grafanaSerializer] },
|
|
50
|
+
{ name: "dashboards-from-declarations", serializers: [otelSerializer, prometheusSerializer, grafanaSerializer] },
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
async function waitFor<T>(what: string, fn: () => Promise<T | undefined>, timeoutMs = 120_000): Promise<T> {
|
|
54
|
+
const deadline = Date.now() + timeoutMs;
|
|
55
|
+
let last: unknown;
|
|
56
|
+
while (Date.now() < deadline) {
|
|
57
|
+
try {
|
|
58
|
+
const v = await fn();
|
|
59
|
+
if (v !== undefined) return v;
|
|
60
|
+
} catch (err) {
|
|
61
|
+
last = err;
|
|
62
|
+
}
|
|
63
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
64
|
+
}
|
|
65
|
+
throw new Error(`timed out waiting for ${what}${last ? `: ${String(last)}` : ""}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
describe.skipIf(!hasDocker).each(EXAMPLES)(`Grafana imports the $name dashboards without edits${skipReason ? ` (skipped: ${skipReason})` : ""}`, ({ name, serializers }) => {
|
|
69
|
+
const CONTAINER = `chant-grafana-import-${name}-${process.pid}`;
|
|
70
|
+
const dir = mkdtempSync(join(tmpdir(), "chant-grafana-import-"));
|
|
71
|
+
|
|
72
|
+
afterAll(() => {
|
|
73
|
+
try {
|
|
74
|
+
execSync(`docker rm -f ${CONTAINER}`, { stdio: "ignore" });
|
|
75
|
+
} catch {
|
|
76
|
+
// already gone
|
|
77
|
+
}
|
|
78
|
+
rmSync(dir, { recursive: true, force: true });
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("provisions every datasource and dashboard, and accepts the JSON through the import API", { timeout: 300_000 }, async () => {
|
|
82
|
+
const result = await build(join(examples, name, "src"), serializers);
|
|
83
|
+
expect(result.errors).toEqual([]);
|
|
84
|
+
const out = result.outputs.get("grafana") as SerializerResult;
|
|
85
|
+
for (const [file, content] of Object.entries(out.files ?? {})) {
|
|
86
|
+
mkdirSync(dirname(join(dir, file)), { recursive: true });
|
|
87
|
+
writeFileSync(join(dir, file), content);
|
|
88
|
+
}
|
|
89
|
+
const index = JSON.parse(out.primary) as { dashboards: Array<{ uid: string; title: string; folder?: string; file: string }>; datasources: Array<{ uid: string; type: string; name: string }> };
|
|
90
|
+
|
|
91
|
+
execSync(
|
|
92
|
+
[
|
|
93
|
+
"docker run -d",
|
|
94
|
+
`--name ${CONTAINER}`,
|
|
95
|
+
"-p 127.0.0.1::3000",
|
|
96
|
+
"-e GF_SECURITY_ADMIN_PASSWORD=admin",
|
|
97
|
+
"-e GF_ANALYTICS_REPORTING_ENABLED=false",
|
|
98
|
+
"-e GF_ANALYTICS_CHECK_FOR_UPDATES=false",
|
|
99
|
+
`-v ${join(dir, "provisioning")}:/etc/grafana/provisioning:ro`,
|
|
100
|
+
`-v ${join(dir, "dashboards")}:/var/lib/grafana/dashboards:ro`,
|
|
101
|
+
GRAFANA_IMAGE,
|
|
102
|
+
].join(" "),
|
|
103
|
+
{ stdio: "ignore" },
|
|
104
|
+
);
|
|
105
|
+
const port = execSync(`docker port ${CONTAINER} 3000/tcp`).toString().trim().split("\n")[0].split(":").pop();
|
|
106
|
+
const base = `http://127.0.0.1:${port}`;
|
|
107
|
+
const api = async (path: string, init?: RequestInit) => {
|
|
108
|
+
const res = await fetch(`${base}${path}`, { ...init, headers: { authorization: AUTH, "content-type": "application/json", ...(init?.headers ?? {}) } });
|
|
109
|
+
return { status: res.status, body: (await res.json()) as Record<string, unknown> & unknown[] };
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
await waitFor("Grafana to answer", async () => ((await fetch(`${base}/api/health`)).ok ? true : undefined));
|
|
113
|
+
|
|
114
|
+
// Datasources: every declared one, with the uid and type chant gave it.
|
|
115
|
+
const datasources = await waitFor("datasources", async () => {
|
|
116
|
+
const { status, body } = await api("/api/datasources");
|
|
117
|
+
return status === 200 && (body as unknown[]).length >= index.datasources.length ? (body as unknown as Array<{ uid: string; type: string; name: string }>) : undefined;
|
|
118
|
+
});
|
|
119
|
+
expect(datasources.map((d) => [d.uid, d.type, d.name]).sort()).toEqual(index.datasources.map((d) => [d.uid, d.type, d.name]).sort());
|
|
120
|
+
|
|
121
|
+
// Dashboards: provisioned, in their folder, with every panel as built.
|
|
122
|
+
for (const d of index.dashboards) {
|
|
123
|
+
const { body } = await waitFor(`dashboard ${d.uid}`, async () => {
|
|
124
|
+
const r = await api(`/api/dashboards/uid/${d.uid}`);
|
|
125
|
+
return r.status === 200 ? r : undefined;
|
|
126
|
+
});
|
|
127
|
+
const meta = body.meta as { provisioned: boolean; folderTitle?: string };
|
|
128
|
+
const stored = body.dashboard as { title: string; panels: Array<Record<string, unknown>>; templating: { list: unknown[] } };
|
|
129
|
+
const built = JSON.parse(out.files![d.file]) as typeof stored;
|
|
130
|
+
expect(meta.provisioned).toBe(true);
|
|
131
|
+
if (d.folder) expect(meta.folderTitle).toBe(d.folder);
|
|
132
|
+
expect(stored.title).toBe(d.title);
|
|
133
|
+
const shape = (panels: Array<Record<string, unknown>>) => panels.map((p) => [p.id, p.type, p.title, p.gridPos]);
|
|
134
|
+
expect(shape(stored.panels)).toEqual(shape(built.panels));
|
|
135
|
+
expect(stored.templating.list.length).toBe(built.templating.list.length);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// The same JSON through the import API, under a new uid.
|
|
139
|
+
const first = index.dashboards[0];
|
|
140
|
+
const copy = { ...JSON.parse(out.files![first.file]), uid: `${first.uid}-api`, title: `${first.title} (API import)` };
|
|
141
|
+
const imported = await api("/api/dashboards/db", { method: "POST", body: JSON.stringify({ dashboard: copy, overwrite: false }) });
|
|
142
|
+
expect(imported.status).toBe(200);
|
|
143
|
+
expect(imported.body.status).toBe("success");
|
|
144
|
+
const back = await api(`/api/dashboards/uid/${first.uid}-api`);
|
|
145
|
+
expect((back.body.dashboard as { panels: unknown[] }).panels.length).toBe(copy.panels.length);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "A dashboard builds to JSON that Grafana imports without edits", offline.
|
|
3
|
+
*
|
|
4
|
+
* Builds every example and validates each dashboard against Grafana's
|
|
5
|
+
* dashboard schema at `GRAFANA_SCHEMA_PIN`, each panel's options and each
|
|
6
|
+
* query against their plugin's schema, and runs GRAF101-GRAF107 over the
|
|
7
|
+
* output. `import.e2e.test.ts` does the same against a real Grafana when
|
|
8
|
+
* Docker is available.
|
|
9
|
+
*/
|
|
10
|
+
import { describe, expect, test } from "vitest";
|
|
11
|
+
import { readdirSync, statSync } from "fs";
|
|
12
|
+
import { join, dirname } from "path";
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
14
|
+
import { build } from "@intentius/chant/build";
|
|
15
|
+
import type { SerializerResult } from "@intentius/chant/serializer";
|
|
16
|
+
import { load } from "js-yaml";
|
|
17
|
+
import { grafanaSerializer } from "./serializer";
|
|
18
|
+
import { validateDashboardSchema } from "./schema-validate";
|
|
19
|
+
import { validateGrafanaOutput } from "./validate-output";
|
|
20
|
+
import type { ProvisionedDatasource } from "./build";
|
|
21
|
+
|
|
22
|
+
const examplesDir = join(dirname(dirname(fileURLToPath(import.meta.url))), "examples");
|
|
23
|
+
const examples = readdirSync(examplesDir).filter((e) => {
|
|
24
|
+
try {
|
|
25
|
+
return statSync(join(examplesDir, e, "src")).isDirectory();
|
|
26
|
+
} catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe.each(examples)("example %s", (example) => {
|
|
32
|
+
test("every dashboard matches the pinned Grafana schemas, and every check passes", { timeout: 30_000 }, async () => {
|
|
33
|
+
const result = await build(join(examplesDir, example, "src"), [grafanaSerializer]);
|
|
34
|
+
expect(result.errors).toEqual([]);
|
|
35
|
+
const out = result.outputs.get("grafana") as SerializerResult;
|
|
36
|
+
const dashboards = Object.entries(out.files ?? {}).filter(([f]) => f.startsWith("dashboards/"));
|
|
37
|
+
expect(dashboards.length).toBeGreaterThan(0);
|
|
38
|
+
for (const [file, text] of dashboards) {
|
|
39
|
+
expect({ file, problems: validateDashboardSchema(JSON.parse(text)) }).toEqual({ file, problems: [] });
|
|
40
|
+
}
|
|
41
|
+
const ds = load(out.files?.["provisioning/datasources/chant.yaml"] ?? "datasources: []") as { datasources: ProvisionedDatasource[] };
|
|
42
|
+
const issues = validateGrafanaOutput({ dashboards: dashboards.map(([source, t]) => ({ source, json: JSON.parse(t) })), datasources: ds.datasources });
|
|
43
|
+
expect(issues).toEqual([]);
|
|
44
|
+
});
|
|
45
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// Grafana lexicon.
|
|
2
|
+
|
|
3
|
+
// Plugin and serializer
|
|
4
|
+
export { grafanaPlugin } from "./plugin";
|
|
5
|
+
export { grafanaSerializer } from "./serializer";
|
|
6
|
+
|
|
7
|
+
// Datasources
|
|
8
|
+
export {
|
|
9
|
+
Datasource,
|
|
10
|
+
DATASOURCE_TYPE,
|
|
11
|
+
BUILTIN_DATASOURCE_UIDS,
|
|
12
|
+
isDatasourceEntity,
|
|
13
|
+
type DatasourceProps,
|
|
14
|
+
type DatasourceEntity,
|
|
15
|
+
type DatasourceConstructor,
|
|
16
|
+
type DatasourceRef,
|
|
17
|
+
type DatasourceAccess,
|
|
18
|
+
type KnownDatasourceType,
|
|
19
|
+
} from "./datasource";
|
|
20
|
+
|
|
21
|
+
// Dashboards and provisioning
|
|
22
|
+
export {
|
|
23
|
+
Dashboard,
|
|
24
|
+
DashboardProvider,
|
|
25
|
+
DASHBOARD_TYPE,
|
|
26
|
+
DASHBOARD_PROVIDER_TYPE,
|
|
27
|
+
DEFAULT_DASHBOARDS_PATH,
|
|
28
|
+
isDashboardEntity,
|
|
29
|
+
isDashboardProviderEntity,
|
|
30
|
+
type DashboardProps,
|
|
31
|
+
type DashboardEntity,
|
|
32
|
+
type DashboardLinkInput,
|
|
33
|
+
type DashboardProviderProps,
|
|
34
|
+
type DashboardProviderEntity,
|
|
35
|
+
} from "./dashboard";
|
|
36
|
+
|
|
37
|
+
// Panels and rows, and the extension point for other panel plugins
|
|
38
|
+
export {
|
|
39
|
+
TimeSeriesPanel,
|
|
40
|
+
StatPanel,
|
|
41
|
+
GaugePanel,
|
|
42
|
+
TablePanel,
|
|
43
|
+
LogsPanel,
|
|
44
|
+
TracesPanel,
|
|
45
|
+
HeatmapPanel,
|
|
46
|
+
TextPanel,
|
|
47
|
+
Row,
|
|
48
|
+
definePanel,
|
|
49
|
+
registeredPanels,
|
|
50
|
+
panelDefinitionFor,
|
|
51
|
+
isPanelEntity,
|
|
52
|
+
isRowEntity,
|
|
53
|
+
PANEL_TYPE_PREFIX,
|
|
54
|
+
ROW_TYPE,
|
|
55
|
+
type PanelProps,
|
|
56
|
+
type PanelFieldConfig,
|
|
57
|
+
type PanelLink,
|
|
58
|
+
type PanelDefinition,
|
|
59
|
+
type PanelEntity,
|
|
60
|
+
type PanelClass,
|
|
61
|
+
type RowProps,
|
|
62
|
+
type RowEntity,
|
|
63
|
+
} from "./panels";
|
|
64
|
+
|
|
65
|
+
// Queries, and the extension point for other datasource plugins
|
|
66
|
+
export {
|
|
67
|
+
PromQuery,
|
|
68
|
+
TempoQuery,
|
|
69
|
+
LokiQuery,
|
|
70
|
+
defineQuery,
|
|
71
|
+
registeredQueries,
|
|
72
|
+
queryDefinitionFor,
|
|
73
|
+
isQueryEntity,
|
|
74
|
+
QUERY_TYPE_PREFIX,
|
|
75
|
+
type DatasourceInput,
|
|
76
|
+
type QueryModel,
|
|
77
|
+
type QueryProps,
|
|
78
|
+
type QueryDefinition,
|
|
79
|
+
type QueryEntity,
|
|
80
|
+
type QueryClass,
|
|
81
|
+
type PromQueryEntity,
|
|
82
|
+
type TempoQueryEntity,
|
|
83
|
+
type LokiQueryEntity,
|
|
84
|
+
} from "./query";
|
|
85
|
+
|
|
86
|
+
// Variables
|
|
87
|
+
export {
|
|
88
|
+
QueryVariable,
|
|
89
|
+
CustomVariable,
|
|
90
|
+
IntervalVariable,
|
|
91
|
+
DatasourceVariable,
|
|
92
|
+
ConstantVariable,
|
|
93
|
+
TextboxVariable,
|
|
94
|
+
isVariableEntity,
|
|
95
|
+
isDatasourceVariable,
|
|
96
|
+
isBuiltinVariable,
|
|
97
|
+
VARIABLE_NAME,
|
|
98
|
+
VARIABLE_TYPE_PREFIX,
|
|
99
|
+
type VariableKind,
|
|
100
|
+
type VariableHide,
|
|
101
|
+
type VariableEntity,
|
|
102
|
+
type VariableDatasource,
|
|
103
|
+
type DatasourceVariableEntity,
|
|
104
|
+
type QueryVariableProps,
|
|
105
|
+
type CustomVariableProps,
|
|
106
|
+
type IntervalVariableProps,
|
|
107
|
+
type DatasourceVariableProps,
|
|
108
|
+
type ConstantVariableProps,
|
|
109
|
+
type TextboxVariableProps,
|
|
110
|
+
} from "./variables";
|
|
111
|
+
|
|
112
|
+
// The generated schema types and their pin
|
|
113
|
+
export * as schema from "./schema";
|
|
114
|
+
export { DASHBOARD_SCHEMA_VERSION } from "./schema";
|
|
115
|
+
export { GRAFANA_SCHEMA_PIN, SCHEMA_NAMES, type GrafanaSchemaPin, type SchemaName } from "./pin";
|
|
116
|
+
|
|
117
|
+
// Plain-data API: render, check and detect without a build
|
|
118
|
+
export {
|
|
119
|
+
buildGrafana,
|
|
120
|
+
grafanaFiles,
|
|
121
|
+
renderDashboard,
|
|
122
|
+
dashboardJson,
|
|
123
|
+
dashboardUid,
|
|
124
|
+
panelsJson,
|
|
125
|
+
targetJson,
|
|
126
|
+
variableModel,
|
|
127
|
+
datasourceRef,
|
|
128
|
+
provisionedDatasource,
|
|
129
|
+
provisionedProvider,
|
|
130
|
+
datasourcesYaml,
|
|
131
|
+
dashboardProvidersYaml,
|
|
132
|
+
GRID_COLUMNS,
|
|
133
|
+
DATASOURCES_FILE,
|
|
134
|
+
DASHBOARD_PROVIDERS_FILE,
|
|
135
|
+
DASHBOARDS_DIR,
|
|
136
|
+
type BuiltGrafana,
|
|
137
|
+
type BuiltDashboard,
|
|
138
|
+
type GrafanaIndex,
|
|
139
|
+
type ProvisionedDatasource,
|
|
140
|
+
type ProvisionedProvider,
|
|
141
|
+
type DashboardJson,
|
|
142
|
+
type PanelJson,
|
|
143
|
+
type RowPanelJson,
|
|
144
|
+
type VariableModel,
|
|
145
|
+
type DataSourceRef,
|
|
146
|
+
} from "./build";
|
|
147
|
+
export {
|
|
148
|
+
validateGrafanaOutput,
|
|
149
|
+
issuesFor,
|
|
150
|
+
variableReferences,
|
|
151
|
+
type GrafanaIssue,
|
|
152
|
+
type GrafanaIssueCode,
|
|
153
|
+
type GrafanaArtifacts,
|
|
154
|
+
type DashboardDoc,
|
|
155
|
+
} from "./validate-output";
|
|
156
|
+
export { validateDashboardSchema, type SchemaProblem } from "./schema-validate";
|
|
157
|
+
export { looksLikeDashboard, looksLikeDatasourceProvisioning, looksLikeDashboardProvisioning } from "./detect";
|
|
158
|
+
export { slugUid, isValidUid, UID_PATTERN, type DeepPartial } from "./util";
|
|
159
|
+
|
|
160
|
+
// Composites: dashboards built from a spanmetrics connector, an Slo and the GenAI preset
|
|
161
|
+
export {
|
|
162
|
+
RedDashboard,
|
|
163
|
+
redQueries,
|
|
164
|
+
SloDashboard,
|
|
165
|
+
sloQueries,
|
|
166
|
+
AgentDashboard,
|
|
167
|
+
agentQueries,
|
|
168
|
+
type RedDashboardProps,
|
|
169
|
+
type RedDashboardMembers,
|
|
170
|
+
type RedDashboardInstance,
|
|
171
|
+
type SloDashboardProps,
|
|
172
|
+
type SloDashboardMembers,
|
|
173
|
+
type SloDashboardInstance,
|
|
174
|
+
type AgentDashboardProps,
|
|
175
|
+
type AgentDashboardMembers,
|
|
176
|
+
type AgentDashboardInstance,
|
|
177
|
+
type DashboardOptions,
|
|
178
|
+
} from "./composites";
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The grafana lexicon's chant audit catalog, contributed via
|
|
3
|
+
* `grafanaPlugin.auditCatalog()` (#687, #1346).
|
|
4
|
+
*
|
|
5
|
+
* GRAF101-GRAF107 read the emitted dashboard JSON and provisioning files, so
|
|
6
|
+
* they fire on an audit of files chant didn't build too, and are
|
|
7
|
+
* `yamlBased`. GRAF001 and GRAF002 read TypeScript source, so they are
|
|
8
|
+
* constructed with `yamlBased: false`; they are listed for a reader who
|
|
9
|
+
* meets them in a lint report.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { auditRule, type RuleMeta } from "@intentius/chant/audit/catalog";
|
|
13
|
+
|
|
14
|
+
function sourceRule(id: string, category: RuleMeta["category"], title: string, remediation: string): RuleMeta {
|
|
15
|
+
return { id, tier: "merge-worthy", fixKind: "guidance", category, title, remediation, yamlBased: false };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const grafanaAuditCatalog: Record<string, RuleMeta> = {
|
|
19
|
+
GRAF001: sourceRule(
|
|
20
|
+
"GRAF001",
|
|
21
|
+
"correctness",
|
|
22
|
+
"Grafana uid or variable name is not valid syntax",
|
|
23
|
+
'Use 1-40 letters, digits, "-" and "_" for uids, and letters, digits and "_" for variable names.',
|
|
24
|
+
),
|
|
25
|
+
GRAF002: sourceRule(
|
|
26
|
+
"GRAF002",
|
|
27
|
+
"security",
|
|
28
|
+
"Datasource secret declared as a literal",
|
|
29
|
+
"Replace the value with `$__env{NAME}` or `$__file{/path}` so Grafana reads it when it loads the provisioning file.",
|
|
30
|
+
),
|
|
31
|
+
GRAF101: auditRule(
|
|
32
|
+
"GRAF101",
|
|
33
|
+
"merge-worthy",
|
|
34
|
+
"guidance",
|
|
35
|
+
"Panel uses an undeclared datasource",
|
|
36
|
+
"Point the panel or query at a declared Datasource, or declare the datasource it names in the same build root.",
|
|
37
|
+
{ category: "correctness" },
|
|
38
|
+
),
|
|
39
|
+
GRAF102: auditRule(
|
|
40
|
+
"GRAF102",
|
|
41
|
+
"merge-worthy",
|
|
42
|
+
"guidance",
|
|
43
|
+
"Query sent to a datasource of another type",
|
|
44
|
+
"Use the query class that matches the datasource (PromQuery for prometheus, TempoQuery for tempo, LokiQuery for loki), or point the query at a datasource of its own type.",
|
|
45
|
+
{ category: "correctness" },
|
|
46
|
+
),
|
|
47
|
+
GRAF103: auditRule(
|
|
48
|
+
"GRAF103",
|
|
49
|
+
"merge-worthy",
|
|
50
|
+
"guidance",
|
|
51
|
+
"Query uses an undeclared dashboard variable",
|
|
52
|
+
"Declare the variable and list it in the dashboard's variables, or remove the reference.",
|
|
53
|
+
{ category: "correctness" },
|
|
54
|
+
),
|
|
55
|
+
GRAF104: auditRule(
|
|
56
|
+
"GRAF104",
|
|
57
|
+
"merge-worthy",
|
|
58
|
+
"guidance",
|
|
59
|
+
"Duplicate dashboard uid, datasource, panel id, variable or refId",
|
|
60
|
+
"Give each dashboard and datasource its own uid and name, and each panel, variable and query its own id within its dashboard or panel.",
|
|
61
|
+
{ category: "correctness" },
|
|
62
|
+
),
|
|
63
|
+
GRAF105: auditRule(
|
|
64
|
+
"GRAF105",
|
|
65
|
+
"report-only",
|
|
66
|
+
"guidance",
|
|
67
|
+
"Panel outside the grid or overlapping another",
|
|
68
|
+
"Keep x + w within 24 columns, and move or resize overlapping panels, or leave x and y out so the dashboard places them.",
|
|
69
|
+
{ category: "best-practice" },
|
|
70
|
+
),
|
|
71
|
+
GRAF106: auditRule(
|
|
72
|
+
"GRAF106",
|
|
73
|
+
"merge-worthy",
|
|
74
|
+
"guidance",
|
|
75
|
+
"Dashboard or datasource uid Grafana rejects, or an untitled dashboard",
|
|
76
|
+
'Use 1-40 letters, digits, "-" and "_" for uids, and give every dashboard a title.',
|
|
77
|
+
{ category: "correctness" },
|
|
78
|
+
),
|
|
79
|
+
GRAF107: auditRule(
|
|
80
|
+
"GRAF107",
|
|
81
|
+
"merge-worthy",
|
|
82
|
+
"guidance",
|
|
83
|
+
"Dashboard does not match the Grafana schema",
|
|
84
|
+
"Fix the key or value the message names; the panel option types generated from the same schema show what is allowed.",
|
|
85
|
+
{ category: "correctness" },
|
|
86
|
+
),
|
|
87
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GRAF101: A panel, query or variable uses a datasource no Datasource in the build declares
|
|
3
|
+
*
|
|
4
|
+
* Grafana shows "datasource not found" on the panel. Typed references cannot go wrong this way, so it mostly catches a { type, uid } ref or a hand-edited dashboard. A panel with queries and no datasource at all is a warning: Grafana sends them to its default datasource. This check joins dashboards against the Datasource declarations of the same build root, so it goes silent across build roots (chant #1939): with no Datasource declared in the build it reports nothing, and a datasource declared in another build root looks undeclared. Keep datasources and the dashboards that use them in one build root.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { grafanaDiagnostics } from "./grafana-helpers";
|
|
9
|
+
|
|
10
|
+
export const graf101: PostSynthCheck = {
|
|
11
|
+
id: "GRAF101",
|
|
12
|
+
description: "A panel, query or variable uses a datasource no Datasource in the build declares",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return grafanaDiagnostics(ctx, "GRAF101");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GRAF102: A query is sent to a datasource of another plugin type
|
|
3
|
+
*
|
|
4
|
+
* A PromQL query sent to Tempo fails at query time. The typed classes prevent this at compile time; the check catches refs, datasource variables of the wrong plugin type and hand-edited dashboards. Like GRAF101 it joins against the Datasource declarations of the same build root and goes silent across build roots (chant #1939).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { grafanaDiagnostics } from "./grafana-helpers";
|
|
9
|
+
|
|
10
|
+
export const graf102: PostSynthCheck = {
|
|
11
|
+
id: "GRAF102",
|
|
12
|
+
description: "A query is sent to a datasource of another plugin type",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return grafanaDiagnostics(ctx, "GRAF102");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GRAF103: A query, title, datasource or repeat uses a variable the dashboard does not declare
|
|
3
|
+
*
|
|
4
|
+
* Grafana leaves an undeclared $name in the query as text, so the query fails or matches nothing. Names Grafana defines itself ($__interval, $__rate_interval, $__range and the rest of $__*) need no declaration.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { grafanaDiagnostics } from "./grafana-helpers";
|
|
9
|
+
|
|
10
|
+
export const graf103: PostSynthCheck = {
|
|
11
|
+
id: "GRAF103",
|
|
12
|
+
description: "A query, title, datasource or repeat uses a variable the dashboard does not declare",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return grafanaDiagnostics(ctx, "GRAF103");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GRAF104: Two dashboards, datasources, panels, variables or queries share an id
|
|
3
|
+
*
|
|
4
|
+
* Dashboard uids and datasource uids and names must be unique across the build, panel ids and variable names within a dashboard, and refIds within a panel. Grafana keeps one of two dashboards with the same uid and silently drops the other.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { grafanaDiagnostics } from "./grafana-helpers";
|
|
9
|
+
|
|
10
|
+
export const graf104: PostSynthCheck = {
|
|
11
|
+
id: "GRAF104",
|
|
12
|
+
description: "Two dashboards, datasources, panels, variables or queries share an id",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return grafanaDiagnostics(ctx, "GRAF104");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GRAF105: A panel does not fit the 24-column grid, or overlaps another panel
|
|
3
|
+
*
|
|
4
|
+
* A panel wider than the grid is an error. An overlap is a warning: Grafana moves one of the panels on load, so the dashboard does not look the way it was declared. Panels without x and y are placed by the dashboard and cannot overlap each other.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { grafanaDiagnostics } from "./grafana-helpers";
|
|
9
|
+
|
|
10
|
+
export const graf105: PostSynthCheck = {
|
|
11
|
+
id: "GRAF105",
|
|
12
|
+
description: "A panel does not fit the 24-column grid, or overlaps another panel",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return grafanaDiagnostics(ctx, "GRAF105");
|
|
16
|
+
},
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GRAF106: A dashboard or datasource uid Grafana rejects, or a dashboard with no title
|
|
3
|
+
*
|
|
4
|
+
* Grafana accepts uids of 1-40 letters, digits, - and _, and refuses to save a dashboard without a title.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
import { grafanaDiagnostics } from "./grafana-helpers";
|
|
9
|
+
|
|
10
|
+
export const graf106: PostSynthCheck = {
|
|
11
|
+
id: "GRAF106",
|
|
12
|
+
description: "A dashboard or datasource uid Grafana rejects, or a dashboard with no title",
|
|
13
|
+
|
|
14
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
15
|
+
return grafanaDiagnostics(ctx, "GRAF106");
|
|
16
|
+
},
|
|
17
|
+
};
|