@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
package/src/build.ts
ADDED
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* From declared entities to Grafana's own files: dashboard JSON, the
|
|
3
|
+
* datasource provisioning file and the dashboard provisioning file.
|
|
4
|
+
*
|
|
5
|
+
* Plain functions, so another lexicon (a k8s ConfigMap, a docker volume)
|
|
6
|
+
* can render the same files with `grafanaFiles()` or one dashboard with
|
|
7
|
+
* `dashboardJson()`, and the serializer is a thin wrapper around
|
|
8
|
+
* `buildGrafana()`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { dump } from "js-yaml";
|
|
12
|
+
import type { Declarable } from "@intentius/chant/declarable";
|
|
13
|
+
import { isDatasourceEntity, type DatasourceEntity, type DatasourceRef } from "./datasource";
|
|
14
|
+
import {
|
|
15
|
+
isDashboardEntity,
|
|
16
|
+
isDashboardProviderEntity,
|
|
17
|
+
DEFAULT_DASHBOARDS_PATH,
|
|
18
|
+
type DashboardEntity,
|
|
19
|
+
type DashboardLinkInput,
|
|
20
|
+
type DashboardProviderProps,
|
|
21
|
+
} from "./dashboard";
|
|
22
|
+
import { isPanelEntity, isRowEntity, type PanelEntity, type RowEntity, type PanelLink } from "./panels";
|
|
23
|
+
import { isQueryEntity, type QueryEntity, type DatasourceInput } from "./query";
|
|
24
|
+
import { isDatasourceVariable, isVariableEntity, type VariableEntity, type VariableHide } from "./variables";
|
|
25
|
+
import type {
|
|
26
|
+
Dashboard as DashboardJson,
|
|
27
|
+
DashboardLink,
|
|
28
|
+
DataSourceRef,
|
|
29
|
+
GridPos,
|
|
30
|
+
Panel as PanelJson,
|
|
31
|
+
RowPanel as RowPanelJson,
|
|
32
|
+
VariableModel,
|
|
33
|
+
} from "./schema/dashboard.gen";
|
|
34
|
+
import { DASHBOARD_SCHEMA_VERSION } from "./schema/dashboard.gen";
|
|
35
|
+
import { GRAFANA_SCHEMA_PIN } from "./pin";
|
|
36
|
+
import { compact, slugUid } from "./util";
|
|
37
|
+
|
|
38
|
+
export type { DashboardJson, PanelJson, RowPanelJson, VariableModel, DataSourceRef };
|
|
39
|
+
|
|
40
|
+
/** Grafana's grid is 24 columns wide. */
|
|
41
|
+
export const GRID_COLUMNS = 24;
|
|
42
|
+
|
|
43
|
+
/** Where each file goes, relative to the build output directory. */
|
|
44
|
+
export const DATASOURCES_FILE = "provisioning/datasources/chant.yaml";
|
|
45
|
+
export const DASHBOARD_PROVIDERS_FILE = "provisioning/dashboards/chant.yaml";
|
|
46
|
+
export const DASHBOARDS_DIR = "dashboards";
|
|
47
|
+
|
|
48
|
+
/** One entry of `datasources:` in a provisioning file. */
|
|
49
|
+
export interface ProvisionedDatasource {
|
|
50
|
+
name: string;
|
|
51
|
+
type: string;
|
|
52
|
+
uid: string;
|
|
53
|
+
access?: string;
|
|
54
|
+
url?: string;
|
|
55
|
+
isDefault?: boolean;
|
|
56
|
+
basicAuth?: boolean;
|
|
57
|
+
basicAuthUser?: string;
|
|
58
|
+
user?: string;
|
|
59
|
+
database?: string;
|
|
60
|
+
withCredentials?: boolean;
|
|
61
|
+
jsonData?: Record<string, unknown>;
|
|
62
|
+
secureJsonData?: Record<string, string>;
|
|
63
|
+
editable?: boolean;
|
|
64
|
+
orgId?: number;
|
|
65
|
+
version?: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** One entry of `providers:` in a dashboard provisioning file. */
|
|
69
|
+
export interface ProvisionedProvider {
|
|
70
|
+
name: string;
|
|
71
|
+
orgId: number;
|
|
72
|
+
folder: string;
|
|
73
|
+
folderUid?: string;
|
|
74
|
+
type: "file";
|
|
75
|
+
disableDeletion: boolean;
|
|
76
|
+
allowUiUpdates: boolean;
|
|
77
|
+
updateIntervalSeconds: number;
|
|
78
|
+
options: { path: string; foldersFromFilesStructure: boolean };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface BuiltDashboard {
|
|
82
|
+
uid: string;
|
|
83
|
+
title: string;
|
|
84
|
+
folder?: string;
|
|
85
|
+
/** Path of its JSON file, relative to the output directory. */
|
|
86
|
+
file: string;
|
|
87
|
+
json: DashboardJson;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface BuiltGrafana {
|
|
91
|
+
dashboards: BuiltDashboard[];
|
|
92
|
+
datasources: ProvisionedDatasource[];
|
|
93
|
+
providers: ProvisionedProvider[];
|
|
94
|
+
/** Every output file by path, ready to write. */
|
|
95
|
+
files: Record<string, string>;
|
|
96
|
+
/** A short summary of what was built: the serializer's primary output. */
|
|
97
|
+
index: GrafanaIndex;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface GrafanaIndex {
|
|
101
|
+
grafanaSchema: string;
|
|
102
|
+
dashboards: Array<{ uid: string; title: string; folder?: string; file: string }>;
|
|
103
|
+
datasources: Array<{ name: string; type: string; uid: string }>;
|
|
104
|
+
files: string[];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ── References ──────────────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
function isRef(value: unknown): value is DatasourceRef {
|
|
110
|
+
return (
|
|
111
|
+
typeof value === "object" &&
|
|
112
|
+
value !== null &&
|
|
113
|
+
typeof (value as DatasourceRef).type === "string" &&
|
|
114
|
+
typeof (value as DatasourceRef).uid === "string" &&
|
|
115
|
+
!("lexicon" in (value as object))
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** The `{ type, uid }` Grafana stores for a datasource, a datasource variable, or a ref. */
|
|
120
|
+
export function datasourceRef(input: DatasourceInput | undefined): DataSourceRef | undefined {
|
|
121
|
+
if (input === undefined || input === null) return undefined;
|
|
122
|
+
if (isDatasourceEntity(input)) return { type: input.datasourceType, uid: input.uid };
|
|
123
|
+
if (isDatasourceVariable(input)) return { type: input.pluginType, uid: `\${${input.variableName}}` };
|
|
124
|
+
if (isRef(input)) return { type: input.type, uid: input.uid };
|
|
125
|
+
throw new Error("grafana: a datasource must be a Datasource, a DatasourceVariable or a { type, uid } ref");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function sameRef(a: DataSourceRef | undefined, b: DataSourceRef | undefined): boolean {
|
|
129
|
+
return a?.type === b?.type && a?.uid === b?.uid;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const MIXED: DataSourceRef = { type: "datasource", uid: "-- Mixed --" };
|
|
133
|
+
|
|
134
|
+
// ── Links ───────────────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
function dashboardLink(link: DashboardLinkInput | PanelLink): DashboardLink {
|
|
137
|
+
return compact({
|
|
138
|
+
title: link.title,
|
|
139
|
+
type: link.type ?? "link",
|
|
140
|
+
url: link.url,
|
|
141
|
+
icon: link.icon ?? "external link",
|
|
142
|
+
tooltip: link.tooltip ?? "",
|
|
143
|
+
tags: link.tags ?? [],
|
|
144
|
+
asDropdown: link.asDropdown ?? false,
|
|
145
|
+
placement: link.placement,
|
|
146
|
+
targetBlank: link.targetBlank ?? false,
|
|
147
|
+
includeVars: link.includeVars ?? false,
|
|
148
|
+
keepTime: link.keepTime ?? false,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ── Variables ───────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
const HIDE: Record<VariableHide, 0 | 1 | 2> = { label: 0, valueOnly: 1, hidden: 2 };
|
|
155
|
+
const REFRESH = { never: 0, onLoad: 1, onTimeRangeChange: 2 } as const;
|
|
156
|
+
|
|
157
|
+
function escapeCustom(v: string): string {
|
|
158
|
+
return v.replace(/,/g, "\\,");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function optionsOf(values: string[], current?: { text: string | string[]; value: string | string[] }) {
|
|
162
|
+
const selected = current ? (Array.isArray(current.value) ? current.value : [current.value]) : values.slice(0, 1);
|
|
163
|
+
return values.map((v) => ({ selected: selected.includes(v), text: v, value: v }));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** One variable as Grafana's `templating.list` entry. */
|
|
167
|
+
export function variableModel(variable: VariableEntity): VariableModel {
|
|
168
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
169
|
+
const p = variable.props as any;
|
|
170
|
+
const common = {
|
|
171
|
+
type: variable.variableKind,
|
|
172
|
+
name: p.name,
|
|
173
|
+
label: p.label,
|
|
174
|
+
description: p.description,
|
|
175
|
+
hide: p.hide === undefined ? undefined : HIDE[p.hide as VariableHide],
|
|
176
|
+
skipUrlSync: p.skipUrlSync,
|
|
177
|
+
};
|
|
178
|
+
const multi = { multi: p.multi, includeAll: p.includeAll, allValue: p.allValue };
|
|
179
|
+
switch (variable.variableKind) {
|
|
180
|
+
case "query":
|
|
181
|
+
return compact({
|
|
182
|
+
...common,
|
|
183
|
+
datasource: datasourceRef(p.datasource),
|
|
184
|
+
query: p.query,
|
|
185
|
+
definition: p.query,
|
|
186
|
+
regex: p.regex,
|
|
187
|
+
refresh: REFRESH[(p.refresh ?? "onLoad") as keyof typeof REFRESH],
|
|
188
|
+
sort: p.sort,
|
|
189
|
+
...multi,
|
|
190
|
+
current: p.current,
|
|
191
|
+
options: [],
|
|
192
|
+
}) as VariableModel;
|
|
193
|
+
case "custom": {
|
|
194
|
+
const values: string[] = p.values;
|
|
195
|
+
const current = p.current ?? (values[0] !== undefined ? { text: values[0], value: values[0] } : undefined);
|
|
196
|
+
return compact({
|
|
197
|
+
...common,
|
|
198
|
+
query: values.map(escapeCustom).join(","),
|
|
199
|
+
...multi,
|
|
200
|
+
current,
|
|
201
|
+
options: optionsOf(values, current),
|
|
202
|
+
}) as VariableModel;
|
|
203
|
+
}
|
|
204
|
+
case "interval": {
|
|
205
|
+
const values: string[] = p.values;
|
|
206
|
+
const current = p.current ?? (values[0] !== undefined ? { text: values[0], value: values[0] } : undefined);
|
|
207
|
+
return compact({
|
|
208
|
+
...common,
|
|
209
|
+
query: values.join(","),
|
|
210
|
+
current,
|
|
211
|
+
options: optionsOf(values, current),
|
|
212
|
+
auto: p.auto ?? false,
|
|
213
|
+
auto_count: p.autoCount ?? 30,
|
|
214
|
+
auto_min: p.autoMin ?? "10s",
|
|
215
|
+
refresh: 2,
|
|
216
|
+
}) as VariableModel;
|
|
217
|
+
}
|
|
218
|
+
case "datasource":
|
|
219
|
+
return compact({
|
|
220
|
+
...common,
|
|
221
|
+
query: p.pluginType,
|
|
222
|
+
regex: p.regex ?? "",
|
|
223
|
+
...multi,
|
|
224
|
+
current: p.current,
|
|
225
|
+
refresh: 1,
|
|
226
|
+
options: [],
|
|
227
|
+
}) as VariableModel;
|
|
228
|
+
case "constant":
|
|
229
|
+
return compact({
|
|
230
|
+
...common,
|
|
231
|
+
hide: 2,
|
|
232
|
+
query: p.value,
|
|
233
|
+
current: { text: p.value, value: p.value },
|
|
234
|
+
}) as VariableModel;
|
|
235
|
+
case "textbox":
|
|
236
|
+
return compact({
|
|
237
|
+
...common,
|
|
238
|
+
query: p.value ?? "",
|
|
239
|
+
current: { text: p.value ?? "", value: p.value ?? "" },
|
|
240
|
+
}) as VariableModel;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ── Panels and layout ───────────────────────────────────────────
|
|
245
|
+
|
|
246
|
+
function repeatName(repeat: VariableEntity | string | undefined): string | undefined {
|
|
247
|
+
if (repeat === undefined) return undefined;
|
|
248
|
+
return typeof repeat === "string" ? repeat : repeat.variableName;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const REF_IDS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
252
|
+
function refIdAt(i: number): string {
|
|
253
|
+
return i < REF_IDS.length ? REF_IDS[i] : `${REF_IDS[Math.floor(i / REF_IDS.length) - 1]}${REF_IDS[i % REF_IDS.length]}`;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** One query as a panel target. */
|
|
257
|
+
export function targetJson(query: QueryEntity, index: number, panelDatasource?: DataSourceRef): Record<string, unknown> {
|
|
258
|
+
const def = query.queryDefinition;
|
|
259
|
+
const { datasource, ...model } = query.props as Record<string, unknown> & { datasource?: DatasourceInput };
|
|
260
|
+
const ref = datasourceRef(datasource) ?? panelDatasource;
|
|
261
|
+
return compact({
|
|
262
|
+
datasource: ref,
|
|
263
|
+
refId: (model.refId as string | undefined) ?? refIdAt(index),
|
|
264
|
+
...(def.defaults ?? {}),
|
|
265
|
+
...model,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
class Layout {
|
|
270
|
+
private x = 0;
|
|
271
|
+
private y = 0;
|
|
272
|
+
private lineH = 0;
|
|
273
|
+
private bottom = 0;
|
|
274
|
+
|
|
275
|
+
place(gp: Partial<GridPos> | undefined, size: { w: number; h: number }): GridPos {
|
|
276
|
+
const w = gp?.w ?? size.w;
|
|
277
|
+
const h = gp?.h ?? size.h;
|
|
278
|
+
if (gp?.x !== undefined && gp?.y !== undefined) {
|
|
279
|
+
this.bottom = Math.max(this.bottom, gp.y + h);
|
|
280
|
+
return { h, w, x: gp.x, y: gp.y };
|
|
281
|
+
}
|
|
282
|
+
if (this.x + w > GRID_COLUMNS && this.x > 0) {
|
|
283
|
+
this.y += this.lineH;
|
|
284
|
+
this.x = 0;
|
|
285
|
+
this.lineH = 0;
|
|
286
|
+
}
|
|
287
|
+
const pos = { h, w, x: this.x, y: this.y };
|
|
288
|
+
this.x += w;
|
|
289
|
+
this.lineH = Math.max(this.lineH, h);
|
|
290
|
+
this.bottom = Math.max(this.bottom, this.y + h);
|
|
291
|
+
return pos;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** A full-width row header below everything placed so far. */
|
|
295
|
+
row(): { pos: GridPos; mark: () => void } {
|
|
296
|
+
const y = Math.max(this.bottom, this.y + this.lineH);
|
|
297
|
+
const pos = { h: 1, w: GRID_COLUMNS, x: 0, y };
|
|
298
|
+
this.x = 0;
|
|
299
|
+
this.y = y + 1;
|
|
300
|
+
this.lineH = 0;
|
|
301
|
+
this.bottom = y + 1;
|
|
302
|
+
// Called after a collapsed row's panels: the next thing starts right under the header.
|
|
303
|
+
const mark = () => {
|
|
304
|
+
this.x = 0;
|
|
305
|
+
this.y = y + 1;
|
|
306
|
+
this.lineH = 0;
|
|
307
|
+
this.bottom = y + 1;
|
|
308
|
+
};
|
|
309
|
+
return { pos, mark };
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
class Ids {
|
|
314
|
+
private next = 1;
|
|
315
|
+
constructor(private readonly taken: Set<number>) {}
|
|
316
|
+
take(explicit?: number): number {
|
|
317
|
+
if (explicit !== undefined) return explicit;
|
|
318
|
+
while (this.taken.has(this.next)) this.next++;
|
|
319
|
+
return this.next++;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function explicitIds(items: Array<PanelEntity | RowEntity>): Set<number> {
|
|
324
|
+
const out = new Set<number>();
|
|
325
|
+
for (const item of items) {
|
|
326
|
+
if (typeof item.props.id === "number") out.add(item.props.id);
|
|
327
|
+
if (isRowEntity(item)) for (const p of item.props.panels ?? []) if (typeof p.props.id === "number") out.add(p.props.id);
|
|
328
|
+
}
|
|
329
|
+
return out;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function panelJson(panel: PanelEntity, gridPos: GridPos, id: number, inherited?: DataSourceRef): PanelJson {
|
|
333
|
+
const p = panel.props;
|
|
334
|
+
const def = panel.panelDefinition;
|
|
335
|
+
const own = datasourceRef(p.datasource) ?? inherited;
|
|
336
|
+
const queries = (p.targets ?? []).filter(isQueryEntity);
|
|
337
|
+
const targets = queries.map((q, i) => targetJson(q, i, own));
|
|
338
|
+
const refs = targets.map((t) => t.datasource as DataSourceRef | undefined);
|
|
339
|
+
let datasource = own;
|
|
340
|
+
if (!datasource && refs.length > 0) {
|
|
341
|
+
datasource = refs.every((r) => sameRef(r, refs[0])) ? refs[0] : MIXED;
|
|
342
|
+
} else if (datasource && refs.some((r) => r && !sameRef(r, datasource))) {
|
|
343
|
+
datasource = MIXED;
|
|
344
|
+
}
|
|
345
|
+
const fieldConfig = {
|
|
346
|
+
defaults: { ...(p.fieldConfig?.defaults ?? {}) },
|
|
347
|
+
overrides: p.fieldConfig?.overrides ?? [],
|
|
348
|
+
};
|
|
349
|
+
return compact({
|
|
350
|
+
type: def.type,
|
|
351
|
+
id,
|
|
352
|
+
title: p.title ?? "",
|
|
353
|
+
description: p.description,
|
|
354
|
+
gridPos,
|
|
355
|
+
datasource,
|
|
356
|
+
targets: targets.length > 0 ? targets : undefined,
|
|
357
|
+
options: (p.options ?? {}) as Record<string, unknown>,
|
|
358
|
+
fieldConfig,
|
|
359
|
+
transformations: p.transformations,
|
|
360
|
+
links: p.links?.map(dashboardLink),
|
|
361
|
+
repeat: repeatName(p.repeat),
|
|
362
|
+
repeatDirection: p.repeatDirection,
|
|
363
|
+
maxPerRow: p.maxPerRow,
|
|
364
|
+
maxDataPoints: p.maxDataPoints,
|
|
365
|
+
interval: p.interval,
|
|
366
|
+
timeFrom: p.timeFrom,
|
|
367
|
+
timeShift: p.timeShift,
|
|
368
|
+
hideTimeOverride: p.hideTimeOverride,
|
|
369
|
+
transparent: p.transparent,
|
|
370
|
+
pluginVersion: p.pluginVersion,
|
|
371
|
+
}) as PanelJson;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** A dashboard's `panels` array: rows and panels, laid out, with ids. */
|
|
375
|
+
export function panelsJson(items: Array<PanelEntity | RowEntity>): Array<PanelJson | RowPanelJson> {
|
|
376
|
+
const layout = new Layout();
|
|
377
|
+
const ids = new Ids(explicitIds(items));
|
|
378
|
+
const out: Array<PanelJson | RowPanelJson> = [];
|
|
379
|
+
for (const item of items) {
|
|
380
|
+
if (isRowEntity(item)) {
|
|
381
|
+
const r = item.props;
|
|
382
|
+
const { pos, mark } = layout.row();
|
|
383
|
+
const rowId = ids.take(r.id);
|
|
384
|
+
const rowRef = datasourceRef(r.datasource);
|
|
385
|
+
const children = (r.panels ?? []).filter(isPanelEntity).map((p) => {
|
|
386
|
+
const gp = layout.place(p.props.gridPos, p.panelDefinition.defaultSize);
|
|
387
|
+
return panelJson(p, gp, ids.take(p.props.id), rowRef);
|
|
388
|
+
});
|
|
389
|
+
const collapsed = r.collapsed ?? false;
|
|
390
|
+
out.push(
|
|
391
|
+
compact({
|
|
392
|
+
type: "row" as const,
|
|
393
|
+
collapsed,
|
|
394
|
+
title: r.title,
|
|
395
|
+
datasource: rowRef,
|
|
396
|
+
gridPos: pos,
|
|
397
|
+
id: rowId,
|
|
398
|
+
panels: collapsed ? children : [],
|
|
399
|
+
repeat: repeatName(r.repeat),
|
|
400
|
+
}),
|
|
401
|
+
);
|
|
402
|
+
if (collapsed) mark();
|
|
403
|
+
else out.push(...children);
|
|
404
|
+
} else if (isPanelEntity(item)) {
|
|
405
|
+
const gp = layout.place(item.props.gridPos, item.panelDefinition.defaultSize);
|
|
406
|
+
out.push(panelJson(item, gp, ids.take(item.props.id)));
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return out;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// ── Dashboards ──────────────────────────────────────────────────
|
|
413
|
+
|
|
414
|
+
const GRAPH_TOOLTIP = { default: 0, sharedCrosshair: 1, sharedTooltip: 2 } as const;
|
|
415
|
+
|
|
416
|
+
/** The dashboard's uid: its own, else from its export name, else from its title. */
|
|
417
|
+
export function dashboardUid(dashboard: DashboardEntity, exportName?: string): string {
|
|
418
|
+
return dashboard.props.uid ?? slugUid(exportName ?? dashboard.props.title);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/** One dashboard as the JSON Grafana imports. */
|
|
422
|
+
export function renderDashboard(dashboard: DashboardEntity, exportName?: string): DashboardJson {
|
|
423
|
+
const p = dashboard.props;
|
|
424
|
+
return compact({
|
|
425
|
+
annotations: { list: [] },
|
|
426
|
+
description: p.description,
|
|
427
|
+
editable: p.editable ?? true,
|
|
428
|
+
fiscalYearStartMonth: p.fiscalYearStartMonth ?? 0,
|
|
429
|
+
graphTooltip: GRAPH_TOOLTIP[p.graphTooltip ?? "default"],
|
|
430
|
+
links: (p.links ?? []).map(dashboardLink),
|
|
431
|
+
liveNow: p.liveNow,
|
|
432
|
+
panels: panelsJson(p.panels ?? []),
|
|
433
|
+
refresh: p.refresh,
|
|
434
|
+
schemaVersion: DASHBOARD_SCHEMA_VERSION,
|
|
435
|
+
tags: p.tags ?? [],
|
|
436
|
+
templating: { list: (p.variables ?? []).filter(isVariableEntity).map(variableModel) },
|
|
437
|
+
time: p.time ?? { from: "now-6h", to: "now" },
|
|
438
|
+
timepicker: p.timepicker ?? {},
|
|
439
|
+
timezone: p.timezone ?? "browser",
|
|
440
|
+
title: p.title,
|
|
441
|
+
uid: dashboardUid(dashboard, exportName),
|
|
442
|
+
weekStart: p.weekStart ?? "",
|
|
443
|
+
}) as DashboardJson;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/** One dashboard as formatted JSON text. */
|
|
447
|
+
export function dashboardJson(dashboard: DashboardEntity, exportName?: string): string {
|
|
448
|
+
return `${JSON.stringify(renderDashboard(dashboard, exportName), null, 2)}\n`;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// ── Datasources and providers ───────────────────────────────────
|
|
452
|
+
|
|
453
|
+
/** Replace every declared `Datasource` inside plugin settings with its uid. */
|
|
454
|
+
function resolveNested(value: unknown): unknown {
|
|
455
|
+
if (isDatasourceEntity(value)) return value.uid;
|
|
456
|
+
if (Array.isArray(value)) return value.map(resolveNested);
|
|
457
|
+
if (value && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
|
|
458
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, resolveNested(v)]));
|
|
459
|
+
}
|
|
460
|
+
return value;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/** One datasource as a provisioning entry. */
|
|
464
|
+
export function provisionedDatasource(ds: DatasourceEntity): ProvisionedDatasource {
|
|
465
|
+
const p = ds.props;
|
|
466
|
+
return compact({
|
|
467
|
+
name: p.name,
|
|
468
|
+
type: p.type,
|
|
469
|
+
uid: ds.uid,
|
|
470
|
+
access: p.access ?? "proxy",
|
|
471
|
+
url: p.url,
|
|
472
|
+
isDefault: p.isDefault,
|
|
473
|
+
basicAuth: p.basicAuth,
|
|
474
|
+
basicAuthUser: p.basicAuthUser,
|
|
475
|
+
user: p.user,
|
|
476
|
+
database: p.database,
|
|
477
|
+
withCredentials: p.withCredentials,
|
|
478
|
+
jsonData: p.jsonData === undefined ? undefined : (resolveNested(p.jsonData) as Record<string, unknown>),
|
|
479
|
+
secureJsonData: p.secureJsonData,
|
|
480
|
+
editable: p.editable ?? false,
|
|
481
|
+
orgId: p.orgId,
|
|
482
|
+
version: p.version,
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/** One dashboard provider as a provisioning entry. */
|
|
487
|
+
export function provisionedProvider(p: DashboardProviderProps): ProvisionedProvider {
|
|
488
|
+
const pinnedFolder = p.folder !== undefined || p.folderUid !== undefined;
|
|
489
|
+
return compact({
|
|
490
|
+
name: p.name,
|
|
491
|
+
orgId: p.orgId ?? 1,
|
|
492
|
+
folder: p.folder ?? "",
|
|
493
|
+
folderUid: p.folderUid,
|
|
494
|
+
type: "file" as const,
|
|
495
|
+
disableDeletion: p.disableDeletion ?? false,
|
|
496
|
+
allowUiUpdates: p.allowUiUpdates ?? false,
|
|
497
|
+
updateIntervalSeconds: p.updateIntervalSeconds ?? 30,
|
|
498
|
+
options: {
|
|
499
|
+
path: p.path ?? DEFAULT_DASHBOARDS_PATH,
|
|
500
|
+
foldersFromFilesStructure: p.foldersFromFilesStructure ?? !pinnedFolder,
|
|
501
|
+
},
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function yamlFile(header: string, body: Record<string, unknown>): string {
|
|
506
|
+
return `# ${header}\n${dump(body, { lineWidth: -1, noRefs: true, quotingType: '"' })}`;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/** The datasource provisioning file for these datasources. */
|
|
510
|
+
export function datasourcesYaml(datasources: ProvisionedDatasource[]): string {
|
|
511
|
+
return yamlFile("Grafana datasource provisioning, generated by chant.", { apiVersion: 1, datasources });
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/** The dashboard provisioning file for these providers. */
|
|
515
|
+
export function dashboardProvidersYaml(providers: ProvisionedProvider[]): string {
|
|
516
|
+
return yamlFile("Grafana dashboard provisioning, generated by chant.", { apiVersion: 1, providers });
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function folderDir(folder: string): string {
|
|
520
|
+
return folder.replace(/[/\\]+/g, "-").replace(/^\.+/, "").trim() || "General";
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// ── The whole build ─────────────────────────────────────────────
|
|
524
|
+
|
|
525
|
+
/** Everything the grafana entities in a build render to. */
|
|
526
|
+
export function buildGrafana(entities: Map<string, Declarable> | Iterable<Declarable>): BuiltGrafana {
|
|
527
|
+
const named: Array<[string | undefined, Declarable]> =
|
|
528
|
+
entities instanceof Map ? [...entities.entries()] : [...entities].map((e) => [undefined, e]);
|
|
529
|
+
const files: Record<string, string> = {};
|
|
530
|
+
|
|
531
|
+
// Sorted by name, so the provisioning file doesn't depend on which source file was read first.
|
|
532
|
+
const datasources = named
|
|
533
|
+
.filter(([, e]) => isDatasourceEntity(e))
|
|
534
|
+
.map(([, e]) => provisionedDatasource(e as DatasourceEntity))
|
|
535
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
536
|
+
|
|
537
|
+
const dashboards: BuiltDashboard[] = [];
|
|
538
|
+
for (const [name, e] of named) {
|
|
539
|
+
if (!isDashboardEntity(e)) continue;
|
|
540
|
+
const json = renderDashboard(e, name);
|
|
541
|
+
const uid = json.uid as string;
|
|
542
|
+
const folder = e.props.folder;
|
|
543
|
+
const dir = folder ? `${DASHBOARDS_DIR}/${folderDir(folder)}` : DASHBOARDS_DIR;
|
|
544
|
+
let file = `${dir}/${uid}.json`;
|
|
545
|
+
for (let n = 2; file in files; n++) file = `${dir}/${uid}-${n}.json`;
|
|
546
|
+
files[file] = `${JSON.stringify(json, null, 2)}\n`;
|
|
547
|
+
dashboards.push({ uid, title: e.props.title, ...(folder ? { folder } : {}), file, json });
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const declaredProviders = named.filter(([, e]) => isDashboardProviderEntity(e)).map(([, e]) => (e as unknown as { props: DashboardProviderProps }).props);
|
|
551
|
+
const providers =
|
|
552
|
+
declaredProviders.length > 0
|
|
553
|
+
? declaredProviders.map(provisionedProvider)
|
|
554
|
+
: dashboards.length > 0
|
|
555
|
+
? [provisionedProvider({ name: "chant" })]
|
|
556
|
+
: [];
|
|
557
|
+
|
|
558
|
+
if (datasources.length > 0) files[DATASOURCES_FILE] = datasourcesYaml(datasources);
|
|
559
|
+
if (providers.length > 0) files[DASHBOARD_PROVIDERS_FILE] = dashboardProvidersYaml(providers);
|
|
560
|
+
|
|
561
|
+
const index: GrafanaIndex = {
|
|
562
|
+
grafanaSchema: `${GRAFANA_SCHEMA_PIN.source}@${GRAFANA_SCHEMA_PIN.ref}`,
|
|
563
|
+
dashboards: dashboards.map(({ uid, title, folder, file }) => ({ uid, title, ...(folder ? { folder } : {}), file })),
|
|
564
|
+
datasources: datasources.map(({ name, type, uid }) => ({ name, type, uid })),
|
|
565
|
+
files: Object.keys(files).sort(),
|
|
566
|
+
};
|
|
567
|
+
return { dashboards, datasources, providers, files, index };
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/** Every file the grafana entities render to, by path relative to the output directory. */
|
|
571
|
+
export function grafanaFiles(entities: Map<string, Declarable> | Iterable<Declarable>): Record<string, string> {
|
|
572
|
+
return buildGrafana(entities).files;
|
|
573
|
+
}
|
package/src/catalog.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The built-in entity catalog: every class this package exports, keyed by
|
|
3
|
+
* class name. It feeds the packaged registry (`dist/meta.json`), LSP
|
|
4
|
+
* completions and hover, and the docs.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { LexiconEntry } from "@intentius/chant/lsp/lexicon-providers";
|
|
8
|
+
import { DATASOURCE_TYPE } from "./datasource";
|
|
9
|
+
import { DASHBOARD_TYPE, DASHBOARD_PROVIDER_TYPE } from "./dashboard";
|
|
10
|
+
import * as panels from "./panels";
|
|
11
|
+
import * as queries from "./query";
|
|
12
|
+
import { VARIABLE_TYPE_PREFIX } from "./variables";
|
|
13
|
+
|
|
14
|
+
export type CatalogKind = "dashboard" | "datasource" | "provider" | "panel" | "row" | "query" | "variable";
|
|
15
|
+
|
|
16
|
+
export interface CatalogEntry {
|
|
17
|
+
className: string;
|
|
18
|
+
entityType: string;
|
|
19
|
+
kind: CatalogKind;
|
|
20
|
+
/** Resource entities are emitted; property entities live inside one. */
|
|
21
|
+
entityKind: "resource" | "property";
|
|
22
|
+
/** The Grafana plugin id, for panels and queries. */
|
|
23
|
+
pluginId?: string;
|
|
24
|
+
description: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isClassWith<T>(v: unknown, key: string): v is { definition: T } {
|
|
28
|
+
return typeof v === "function" && typeof (v as unknown as Record<string, unknown>)[key] === "object";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const panelEntries: CatalogEntry[] = (Object.entries(panels) as Array<[string, unknown]>)
|
|
32
|
+
.filter((e): e is [string, panels.PanelClass] => isClassWith<panels.PanelDefinition>(e[1], "definition"))
|
|
33
|
+
.map(([className, cls]) => ({
|
|
34
|
+
className,
|
|
35
|
+
entityType: `${panels.PANEL_TYPE_PREFIX}${cls.definition.type}`,
|
|
36
|
+
kind: "panel",
|
|
37
|
+
entityKind: "property",
|
|
38
|
+
pluginId: cls.definition.type,
|
|
39
|
+
description: cls.definition.description ?? "",
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
const queryEntries: CatalogEntry[] = (Object.entries(queries) as Array<[string, unknown]>)
|
|
43
|
+
.filter((e): e is [string, queries.QueryClass] => isClassWith<queries.QueryDefinition>(e[1], "definition"))
|
|
44
|
+
.map(([className, cls]) => ({
|
|
45
|
+
className,
|
|
46
|
+
entityType: `${queries.QUERY_TYPE_PREFIX}${cls.definition.datasourceType}`,
|
|
47
|
+
kind: "query",
|
|
48
|
+
entityKind: "property",
|
|
49
|
+
pluginId: cls.definition.datasourceType,
|
|
50
|
+
description: cls.definition.description ?? "",
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
const VARIABLES: Array<[string, string, string]> = [
|
|
54
|
+
["QueryVariable", "query", "A variable whose values come from a datasource query"],
|
|
55
|
+
["CustomVariable", "custom", "A variable with a fixed list of values"],
|
|
56
|
+
["IntervalVariable", "interval", "A time interval to choose, for range selectors"],
|
|
57
|
+
["DatasourceVariable", "datasource", "A choice of datasource of one plugin type; usable wherever a datasource is"],
|
|
58
|
+
["ConstantVariable", "constant", "A hidden, fixed value"],
|
|
59
|
+
["TextboxVariable", "textbox", "A free-text box"],
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
export const BUILTIN_CATALOG: CatalogEntry[] = [
|
|
63
|
+
{
|
|
64
|
+
className: "Dashboard",
|
|
65
|
+
entityType: DASHBOARD_TYPE,
|
|
66
|
+
kind: "dashboard",
|
|
67
|
+
entityKind: "resource",
|
|
68
|
+
description: "A dashboard: title, uid, time range, variables, rows, panels and links, written as the JSON Grafana imports",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
className: "Datasource",
|
|
72
|
+
entityType: DATASOURCE_TYPE,
|
|
73
|
+
kind: "datasource",
|
|
74
|
+
entityKind: "resource",
|
|
75
|
+
description: "A datasource, declared once and referenced by panels, queries and variables",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
className: "DashboardProvider",
|
|
79
|
+
entityType: DASHBOARD_PROVIDER_TYPE,
|
|
80
|
+
kind: "provider",
|
|
81
|
+
entityKind: "resource",
|
|
82
|
+
description: "An entry in the dashboard provisioning file; a default one is written when none is declared",
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
className: "Row",
|
|
86
|
+
entityType: panels.ROW_TYPE,
|
|
87
|
+
kind: "row",
|
|
88
|
+
entityKind: "property",
|
|
89
|
+
description: "A full-width row header; its panels are placed below it, or inside it when collapsed",
|
|
90
|
+
},
|
|
91
|
+
...panelEntries,
|
|
92
|
+
...queryEntries,
|
|
93
|
+
...VARIABLES.map(
|
|
94
|
+
([className, type, description]): CatalogEntry => ({
|
|
95
|
+
className,
|
|
96
|
+
entityType: `${VARIABLE_TYPE_PREFIX}${type}`,
|
|
97
|
+
kind: "variable",
|
|
98
|
+
entityKind: "property",
|
|
99
|
+
description,
|
|
100
|
+
}),
|
|
101
|
+
),
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The catalog as a chant lexicon registry, keyed by class name. Every class
|
|
106
|
+
* is listed as a resource, including the property-kind panels, queries and
|
|
107
|
+
* variables: the registry drives `new …` completions, and an author writes
|
|
108
|
+
* `new TimeSeriesPanel(…)` the same way as `new Dashboard(…)`.
|
|
109
|
+
*/
|
|
110
|
+
export function lexiconRegistry(): Record<string, LexiconEntry> {
|
|
111
|
+
const out: Record<string, LexiconEntry> = {};
|
|
112
|
+
for (const e of [...BUILTIN_CATALOG].sort((a, b) => a.className.localeCompare(b.className))) {
|
|
113
|
+
out[e.className] = { resourceType: e.entityType, kind: "resource", lexicon: "grafana" };
|
|
114
|
+
}
|
|
115
|
+
return out;
|
|
116
|
+
}
|