@intentius/chant-lexicon-grafana 0.99.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 +12 -6
- package/dist/annotations.d.ts +61 -0
- package/dist/annotations.d.ts.map +1 -0
- package/dist/api/apply.d.ts +8 -4
- package/dist/api/apply.d.ts.map +1 -1
- package/dist/api/fake-grafana.d.ts +2 -0
- package/dist/api/fake-grafana.d.ts.map +1 -1
- package/dist/api/folders.d.ts +53 -17
- package/dist/api/folders.d.ts.map +1 -1
- package/dist/build.d.ts +26 -5
- 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/composites/shared.d.ts +4 -3
- package/dist/composites/shared.d.ts.map +1 -1
- package/dist/dashboard.d.ts +42 -14
- package/dist/dashboard.d.ts.map +1 -1
- package/dist/datasource-refs.d.ts +9 -5
- package/dist/datasource-refs.d.ts.map +1 -1
- package/dist/datasource-settings.d.ts +834 -0
- package/dist/datasource-settings.d.ts.map +1 -0
- package/dist/datasource.d.ts +53 -11
- package/dist/datasource.d.ts.map +1 -1
- package/dist/deep-observe-hooks.d.ts +1 -1
- package/dist/deep-observe-hooks.d.ts.map +1 -1
- package/dist/deep-observe.d.ts +9 -4
- package/dist/deep-observe.d.ts.map +1 -1
- package/dist/describe-resources.d.ts +1 -0
- package/dist/describe-resources.d.ts.map +1 -1
- package/dist/detect.d.ts +4 -2
- package/dist/detect.d.ts.map +1 -1
- package/dist/folder.d.ts +68 -0
- package/dist/folder.d.ts.map +1 -0
- package/dist/import/generator.d.ts.map +1 -1
- package/dist/import/mappings.d.ts +12 -6
- package/dist/import/mappings.d.ts.map +1 -1
- package/dist/import/model.d.ts +7 -0
- package/dist/import/model.d.ts.map +1 -1
- package/dist/import/normalize.d.ts.map +1 -1
- package/dist/import/parser.d.ts +16 -1
- package/dist/import/parser.d.ts.map +1 -1
- package/dist/import/testdata/fixtures.d.ts +6 -0
- package/dist/import/testdata/fixtures.d.ts.map +1 -1
- package/dist/import/v2.d.ts +4 -3
- package/dist/import/v2.d.ts.map +1 -1
- package/dist/index.d.ts +11 -9
- 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 +11 -9
- package/dist/lint/audit-catalog.d.ts +1 -1
- package/dist/lint/audit-catalog.d.ts.map +1 -1
- package/dist/lint/post-synth/graf101.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/graf110.d.ts +8 -0
- package/dist/lint/post-synth/graf110.d.ts.map +1 -0
- 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/manifest.json +1 -1
- package/dist/meta.json +70 -0
- package/dist/okf/index.md +18 -3
- package/dist/okf/rules/GRAF101.md +2 -2
- package/dist/okf/rules/GRAF104.md +2 -2
- package/dist/okf/rules/GRAF106.md +2 -2
- package/dist/okf/rules/GRAF110.md +11 -0
- package/dist/okf/types/AdhocVariable.md +9 -0
- package/dist/okf/types/AzureMonitorQuery.md +9 -0
- package/dist/okf/types/BigQueryQuery.md +9 -0
- package/dist/okf/types/CloudMonitoringQuery.md +9 -0
- package/dist/okf/types/CloudWatchQuery.md +9 -0
- package/dist/okf/types/Dashboard.md +1 -1
- package/dist/okf/types/Datasource.md +1 -1
- package/dist/okf/types/DatasourceProvisioning.md +9 -0
- package/dist/okf/types/ElasticsearchQuery.md +9 -0
- package/dist/okf/types/ExternalDatasource.md +1 -1
- package/dist/okf/types/Folder.md +9 -0
- package/dist/okf/types/GroupByVariable.md +9 -0
- package/dist/okf/types/MSSQLQuery.md +9 -0
- package/dist/okf/types/MySQLQuery.md +9 -0
- package/dist/okf/types/PostgresQuery.md +9 -0
- package/dist/okf/types/PyroscopeQuery.md +9 -0
- package/dist/okf/types/SwitchVariable.md +9 -0
- package/dist/op/activities/grafana-apply.d.ts +11 -0
- package/dist/op/activities/grafana-apply.d.ts.map +1 -1
- package/dist/op/activities/index.d.ts +1 -1
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/ownership.d.ts +11 -0
- package/dist/ownership.d.ts.map +1 -1
- package/dist/pin.d.ts +1 -1
- package/dist/pin.d.ts.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/promql-check.d.ts.map +1 -1
- package/dist/query-models.d.ts +95 -0
- package/dist/query-models.d.ts.map +1 -0
- package/dist/query.d.ts +107 -10
- package/dist/query.d.ts.map +1 -1
- package/dist/rules/graf101.ts +2 -2
- package/dist/rules/graf104.ts +3 -3
- package/dist/rules/graf106.ts +3 -3
- package/dist/rules/graf110.ts +17 -0
- package/dist/rules/grafana-helpers.ts +13 -3
- package/dist/schema/azuremonitor.gen.d.ts +354 -0
- package/dist/schema/azuremonitor.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/cloudwatch.gen.d.ts +394 -0
- package/dist/schema/cloudwatch.gen.d.ts.map +1 -0
- package/dist/schema/elasticsearch.gen.d.ts +444 -0
- package/dist/schema/elasticsearch.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/index.d.ts +6 -0
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/table.gen.d.ts +13 -0
- package/dist/schema/table.gen.d.ts.map +1 -1
- package/dist/schema-validate.d.ts +9 -0
- package/dist/schema-validate.d.ts.map +1 -1
- package/dist/skill-defs.d.ts.map +1 -1
- package/dist/skills/chant-grafana-operations.md +108 -0
- package/dist/skills/chant-grafana.md +5 -4
- package/dist/spec/schemas.d.ts +9 -7
- 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/validate-alerting.d.ts.map +1 -1
- package/dist/validate-output.d.ts +15 -2
- 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 +102 -5
- package/dist/variables.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/annotations.ts +84 -0
- package/src/api/apply.test.ts +19 -1
- package/src/api/apply.ts +18 -8
- package/src/api/fake-grafana.ts +6 -2
- package/src/api/folders.ts +106 -32
- package/src/build.test.ts +142 -6
- package/src/build.ts +126 -24
- package/src/catalog.ts +20 -2
- package/src/codegen/docs.ts +13 -6
- package/src/codegen/generate.ts +27 -1
- package/src/composites/shared.ts +3 -2
- package/src/dashboard.ts +59 -14
- package/src/datasource-refs.ts +30 -6
- package/src/datasource-settings.test.ts +106 -0
- package/src/datasource-settings.ts +941 -0
- package/src/datasource.ts +71 -11
- package/src/deep-observe-hooks.ts +16 -1
- package/src/deep-observe.test.ts +47 -1
- package/src/deep-observe.ts +36 -13
- package/src/describe-resources.test.ts +16 -0
- package/src/describe-resources.ts +36 -1
- package/src/detect.ts +3 -3
- package/src/folder.ts +118 -0
- package/src/generated/lexicon-grafana.json +70 -0
- package/src/import/generated-types.e2e.test.ts +15 -3
- package/src/import/generator.ts +2 -1
- package/src/import/mappings.ts +98 -26
- package/src/import/model.ts +7 -0
- package/src/import/normalize.ts +32 -3
- package/src/import/parser.test.ts +97 -21
- package/src/import/parser.ts +169 -18
- package/src/import/roundtrip.test.ts +104 -9
- package/src/import/testdata/fixtures.ts +8 -0
- package/src/import/v2.ts +4 -3
- package/src/index.ts +122 -13
- package/src/init-templates.test.ts +74 -0
- package/src/init-templates.ts +349 -0
- package/src/lint/audit-catalog.ts +9 -1
- package/src/lint/post-synth/graf101.ts +2 -2
- package/src/lint/post-synth/graf104.ts +3 -3
- package/src/lint/post-synth/graf106.ts +3 -3
- package/src/lint/post-synth/graf110.ts +17 -0
- package/src/lint/post-synth/grafana-helpers.ts +13 -3
- package/src/lint/post-synth/index.ts +2 -0
- package/src/lint/post-synth/post-synth.test.ts +99 -2
- package/src/load-cost.test.ts +35 -0
- package/src/op/activities/grafana-apply.e2e.test.ts +14 -5
- package/src/op/activities/grafana-apply.test.ts +16 -2
- package/src/op/activities/grafana-apply.ts +29 -3
- package/src/op/activities/index.ts +1 -1
- package/src/ownership.ts +19 -0
- package/src/panels.test.ts +3 -1
- package/src/pin.ts +12 -0
- package/src/plugin.test.ts +9 -2
- package/src/plugin.ts +9 -0
- package/src/promql-check.ts +5 -1
- package/src/query-models.ts +94 -0
- package/src/query.test.ts +166 -0
- package/src/query.ts +177 -12
- package/src/schema/azuremonitor.gen.ts +381 -0
- package/src/schema/bigquery.gen.ts +110 -0
- package/src/schema/cloudwatch.gen.ts +423 -0
- package/src/schema/elasticsearch.gen.ts +508 -0
- package/src/schema/googlecloudmonitoring.gen.ts +330 -0
- package/src/schema/grafanapyroscope.gen.ts +66 -0
- package/src/schema/index.ts +6 -0
- package/src/schema/table.gen.ts +14 -0
- package/src/schema-validate.ts +58 -7
- package/src/skill-defs.ts +17 -0
- package/src/skills/chant-grafana-operations.md +108 -0
- package/src/skills/chant-grafana.md +5 -4
- package/src/spec/overlay/table.overlay.json +19 -0
- package/src/spec/schemas/azuremonitor.jsonschema.json +679 -0
- package/src/spec/schemas/bigquery.jsonschema.json +290 -0
- package/src/spec/schemas/cloudwatch.jsonschema.json +721 -0
- package/src/spec/schemas/elasticsearch.jsonschema.json +1695 -0
- package/src/spec/schemas/googlecloudmonitoring.jsonschema.json +488 -0
- package/src/spec/schemas/grafanapyroscope.jsonschema.json +93 -0
- package/src/spec/schemas.gen.ts +40 -0
- package/src/spec/schemas.ts +9 -7
- package/src/validate-alerting.ts +4 -2
- package/src/validate-output.ts +96 -4
- package/src/validate.ts +14 -0
- package/src/validation.ts +27 -0
- package/src/variables.ts +113 -6
package/src/build.ts
CHANGED
|
@@ -10,15 +10,28 @@
|
|
|
10
10
|
|
|
11
11
|
import { dump } from "js-yaml";
|
|
12
12
|
import type { Declarable } from "@intentius/chant/declarable";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
isDatasourceDeclaration,
|
|
15
|
+
isDatasourceEntity,
|
|
16
|
+
isDatasourceProvisioningEntity,
|
|
17
|
+
isExternalDatasource,
|
|
18
|
+
type DatasourceEntity,
|
|
19
|
+
type DatasourceProvisioningProps,
|
|
20
|
+
type DatasourceRef,
|
|
21
|
+
type DeletedDatasource,
|
|
22
|
+
type ExternalDatasourceEntity,
|
|
23
|
+
} from "./datasource";
|
|
14
24
|
import {
|
|
15
25
|
isDashboardEntity,
|
|
16
26
|
isDashboardProviderEntity,
|
|
17
27
|
DEFAULT_DASHBOARDS_PATH,
|
|
18
28
|
type DashboardEntity,
|
|
19
29
|
type DashboardLinkInput,
|
|
20
|
-
type
|
|
30
|
+
type DashboardProviderEntityProps,
|
|
21
31
|
} from "./dashboard";
|
|
32
|
+
import { annotationJson } from "./annotations";
|
|
33
|
+
import { folderLevels, isFolderEntity, type FolderEntity } from "./folder";
|
|
34
|
+
import { resolveFolders, type FolderPlan } from "./api/folders";
|
|
22
35
|
import { isPanelEntity, isRowEntity, type PanelEntity, type RowEntity, type PanelLink } from "./panels";
|
|
23
36
|
import { isQueryEntity, type QueryEntity, type DatasourceInput } from "./query";
|
|
24
37
|
import { isDatasourceVariable, isVariableEntity, type VariableEntity, type VariableHide } from "./variables";
|
|
@@ -67,6 +80,12 @@ export interface ProvisionedDatasource {
|
|
|
67
80
|
version?: number;
|
|
68
81
|
}
|
|
69
82
|
|
|
83
|
+
/** The settings of the datasource provisioning file besides its datasources. */
|
|
84
|
+
export interface DatasourceFileSettings {
|
|
85
|
+
prune: boolean;
|
|
86
|
+
deleteDatasources: DeletedDatasource[];
|
|
87
|
+
}
|
|
88
|
+
|
|
70
89
|
/** One entry of `providers:` in a dashboard provisioning file. */
|
|
71
90
|
export interface ProvisionedProvider {
|
|
72
91
|
name: string;
|
|
@@ -84,6 +103,8 @@ export interface BuiltDashboard {
|
|
|
84
103
|
uid: string;
|
|
85
104
|
title: string;
|
|
86
105
|
folder?: string;
|
|
106
|
+
/** The uid of its folder, when it is in one. */
|
|
107
|
+
folderUid?: string;
|
|
87
108
|
/** Path of its JSON file, relative to the output directory. */
|
|
88
109
|
file: string;
|
|
89
110
|
json: DashboardJson;
|
|
@@ -95,6 +116,10 @@ export interface BuiltGrafana {
|
|
|
95
116
|
/** `ExternalDatasource` declarations: the checks count them, the provisioning file leaves them out. */
|
|
96
117
|
externalDatasources: ExternalDatasourceRecord[];
|
|
97
118
|
providers: ProvisionedProvider[];
|
|
119
|
+
/** Every folder the dashboards and `Folder`s name, each level of a path its own, sorted by path. */
|
|
120
|
+
folders: FolderPlan[];
|
|
121
|
+
/** `prune` and `deleteDatasources` of the datasource provisioning file. */
|
|
122
|
+
datasourceFile: DatasourceFileSettings;
|
|
98
123
|
/** The alerting provisioning file, when the build declares any alerting. */
|
|
99
124
|
alerting?: AlertingFile;
|
|
100
125
|
/** Every output file by path, ready to write. */
|
|
@@ -105,7 +130,9 @@ export interface BuiltGrafana {
|
|
|
105
130
|
|
|
106
131
|
export interface GrafanaIndex {
|
|
107
132
|
grafanaSchema: string;
|
|
108
|
-
dashboards: Array<{ uid: string; title: string; folder?: string; file: string }>;
|
|
133
|
+
dashboards: Array<{ uid: string; title: string; folder?: string; folderUid?: string; file: string }>;
|
|
134
|
+
/** The folders the dashboards are in, and any `Folder` declared: what the API applier creates, parents first. */
|
|
135
|
+
folders?: FolderPlan[];
|
|
109
136
|
datasources: Array<{ name: string; type: string; uid: string }>;
|
|
110
137
|
/** Datasources declared with `ExternalDatasource`: referenced, never provisioned. */
|
|
111
138
|
externalDatasources?: ExternalDatasourceRecord[];
|
|
@@ -215,7 +242,7 @@ export function variableModel(variable: VariableEntity): VariableModel {
|
|
|
215
242
|
...common,
|
|
216
243
|
datasource: datasourceRef(p.datasource),
|
|
217
244
|
query: p.query,
|
|
218
|
-
definition: p.query,
|
|
245
|
+
definition: p.definition ?? queryText(p.query),
|
|
219
246
|
regex: p.regex,
|
|
220
247
|
refresh: REFRESH[(p.refresh ?? "onLoad") as keyof typeof REFRESH],
|
|
221
248
|
sort: p.sort,
|
|
@@ -274,9 +301,51 @@ export function variableModel(variable: VariableEntity): VariableModel {
|
|
|
274
301
|
query: p.value ?? "",
|
|
275
302
|
current: { text: p.value ?? "", value: p.value ?? "" },
|
|
276
303
|
}) as VariableModel;
|
|
304
|
+
case "adhoc":
|
|
305
|
+
return compact({
|
|
306
|
+
...common,
|
|
307
|
+
datasource: datasourceRef(p.datasource),
|
|
308
|
+
filters: p.filters ?? [],
|
|
309
|
+
baseFilters: p.baseFilters ?? [],
|
|
310
|
+
defaultKeys: p.defaultKeys,
|
|
311
|
+
allowCustomValue: p.allowCustomValue,
|
|
312
|
+
enableGroupBy: p.enableGroupBy,
|
|
313
|
+
}) as VariableModel;
|
|
314
|
+
case "groupby": {
|
|
315
|
+
const options = p.options?.map((o: string | { text: string; value: string }) => (typeof o === "string" ? { text: o, value: o } : o));
|
|
316
|
+
const defaultValue = Array.isArray(p.defaultValue) ? { text: p.defaultValue, value: p.defaultValue } : p.defaultValue;
|
|
317
|
+
return compact({
|
|
318
|
+
...common,
|
|
319
|
+
datasource: datasourceRef(p.datasource),
|
|
320
|
+
options,
|
|
321
|
+
current: p.current ?? defaultValue,
|
|
322
|
+
defaultValue,
|
|
323
|
+
allowCustomValue: p.allowCustomValue,
|
|
324
|
+
}) as VariableModel;
|
|
325
|
+
}
|
|
326
|
+
case "switch": {
|
|
327
|
+
const on: string = p.enabledValue ?? "true";
|
|
328
|
+
const off: string = p.disabledValue ?? "false";
|
|
329
|
+
const value = p.enabled ? on : off;
|
|
330
|
+
return compact({
|
|
331
|
+
...common,
|
|
332
|
+
current: { text: value, value },
|
|
333
|
+
options: [
|
|
334
|
+
{ text: on, value: on },
|
|
335
|
+
{ text: off, value: off },
|
|
336
|
+
],
|
|
337
|
+
}) as VariableModel;
|
|
338
|
+
}
|
|
277
339
|
}
|
|
278
340
|
}
|
|
279
341
|
|
|
342
|
+
/** The text of a variable query: the string, or an object query's `query`. */
|
|
343
|
+
function queryText(query: unknown): string | undefined {
|
|
344
|
+
if (typeof query === "string") return query;
|
|
345
|
+
if (query && typeof query === "object" && typeof (query as { query?: unknown }).query === "string") return (query as { query: string }).query;
|
|
346
|
+
return undefined;
|
|
347
|
+
}
|
|
348
|
+
|
|
280
349
|
// ── Panels and layout ───────────────────────────────────────────
|
|
281
350
|
|
|
282
351
|
function repeatName(repeat: VariableEntity | string | undefined): string | undefined {
|
|
@@ -531,7 +600,7 @@ export function dashboardUid(dashboard: DashboardEntity, exportName?: string): s
|
|
|
531
600
|
export function renderDashboard(dashboard: DashboardEntity, exportName?: string): DashboardJson {
|
|
532
601
|
const p = dashboard.props;
|
|
533
602
|
return compact({
|
|
534
|
-
annotations: { list: [] },
|
|
603
|
+
annotations: { list: (p.annotations ?? []).map((a) => annotationJson(a, datasourceRef)) },
|
|
535
604
|
description: p.description,
|
|
536
605
|
editable: p.editable ?? true,
|
|
537
606
|
fiscalYearStartMonth: p.fiscalYearStartMonth ?? 0,
|
|
@@ -598,7 +667,7 @@ export function externalDatasourceRecord(ds: ExternalDatasourceEntity): External
|
|
|
598
667
|
}
|
|
599
668
|
|
|
600
669
|
/** One dashboard provider as a provisioning entry. */
|
|
601
|
-
export function provisionedProvider(p:
|
|
670
|
+
export function provisionedProvider(p: DashboardProviderEntityProps): ProvisionedProvider {
|
|
602
671
|
const pinnedFolder = p.folder !== undefined || p.folderUid !== undefined;
|
|
603
672
|
return compact({
|
|
604
673
|
name: p.name,
|
|
@@ -620,9 +689,26 @@ function yamlFile(header: string, body: Record<string, unknown>): string {
|
|
|
620
689
|
return `# ${header}\n${dump(body, { lineWidth: -1, noRefs: true, quotingType: '"' })}`;
|
|
621
690
|
}
|
|
622
691
|
|
|
623
|
-
/**
|
|
624
|
-
export function
|
|
625
|
-
return
|
|
692
|
+
/** `prune` and `deleteDatasources` as a build writes them: pruning on unless a `DatasourceProvisioning` turns it off. */
|
|
693
|
+
export function datasourceFileSettings(props: DatasourceProvisioningProps | undefined): DatasourceFileSettings {
|
|
694
|
+
return {
|
|
695
|
+
prune: props?.prune ?? true,
|
|
696
|
+
deleteDatasources: (props?.deleteDatasources ?? []).map((d) => ({ name: d.name, ...(d.orgId !== undefined ? { orgId: d.orgId } : {}) })),
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* The datasource provisioning file for these datasources. It prunes by
|
|
702
|
+
* default (`prune: true`), so a datasource removed from the build is removed
|
|
703
|
+
* from Grafana; `deleteDatasources` comes first, as Grafana runs it first.
|
|
704
|
+
*/
|
|
705
|
+
export function datasourcesYaml(datasources: ProvisionedDatasource[], settings: DatasourceFileSettings = datasourceFileSettings(undefined)): string {
|
|
706
|
+
return yamlFile("Grafana datasource provisioning, generated by chant.", {
|
|
707
|
+
apiVersion: 1,
|
|
708
|
+
...(settings.prune ? { prune: true } : {}),
|
|
709
|
+
...(settings.deleteDatasources.length > 0 ? { deleteDatasources: settings.deleteDatasources } : {}),
|
|
710
|
+
datasources,
|
|
711
|
+
});
|
|
626
712
|
}
|
|
627
713
|
|
|
628
714
|
/** The dashboard provisioning file for these providers. */
|
|
@@ -632,19 +718,18 @@ export function dashboardProvidersYaml(providers: ProvisionedProvider[]): string
|
|
|
632
718
|
|
|
633
719
|
/**
|
|
634
720
|
* A dashboard's `folder` as its directory under `dashboards/`: one
|
|
635
|
-
* subdirectory per
|
|
721
|
+
* subdirectory per level (`folderLevels`), which a provider with
|
|
636
722
|
* `foldersFromFilesStructure` turns into nested folders on Grafana 13.1 and
|
|
637
|
-
* later (GRAF109 says what earlier versions do).
|
|
638
|
-
*
|
|
639
|
-
* `dashboards/`; a backslash becomes `-`. Empty when nothing is left: the
|
|
640
|
-
* General folder.
|
|
723
|
+
* later (GRAF109 says what earlier versions do). Empty for the General
|
|
724
|
+
* folder.
|
|
641
725
|
*/
|
|
642
726
|
function folderDir(folder: string): string {
|
|
643
|
-
return folder
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
727
|
+
return folderLevels(folder).join("/");
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/** A `Folder` and every folder above it. */
|
|
731
|
+
function withAncestors(folder: FolderEntity, into: Set<FolderEntity>): void {
|
|
732
|
+
for (let f: FolderEntity | undefined = folder; f; f = f.props.parent) into.add(f);
|
|
648
733
|
}
|
|
649
734
|
|
|
650
735
|
// ── The whole build ─────────────────────────────────────────────
|
|
@@ -666,6 +751,17 @@ export function buildGrafana(entities: Map<string, Declarable> | Iterable<Declar
|
|
|
666
751
|
.map(([, e]) => externalDatasourceRecord(e as ExternalDatasourceEntity))
|
|
667
752
|
.sort((a, b) => a.uid.localeCompare(b.uid));
|
|
668
753
|
|
|
754
|
+
// Folders: every declared `Folder` and every one a dashboard holds, with their parents, pin uids; every path a dashboard names is a folder per level.
|
|
755
|
+
const declaredFolders = new Set<FolderEntity>();
|
|
756
|
+
for (const [, e] of named) {
|
|
757
|
+
if (isFolderEntity(e)) withAncestors(e, declaredFolders);
|
|
758
|
+
else if (isDashboardEntity(e) && e.folderEntity) withAncestors(e.folderEntity, declaredFolders);
|
|
759
|
+
}
|
|
760
|
+
const resolved = resolveFolders({
|
|
761
|
+
declared: [...declaredFolders].map((f) => ({ uid: f.uid, path: f.path })),
|
|
762
|
+
paths: named.flatMap(([, e]) => (isDashboardEntity(e) && e.props.folder ? [e.props.folder] : [])),
|
|
763
|
+
}, { duplicateUids: "keep" });
|
|
764
|
+
|
|
669
765
|
const dashboards: BuiltDashboard[] = [];
|
|
670
766
|
for (const [name, e] of named) {
|
|
671
767
|
if (!isDashboardEntity(e)) continue;
|
|
@@ -677,10 +773,15 @@ export function buildGrafana(entities: Map<string, Declarable> | Iterable<Declar
|
|
|
677
773
|
let file = `${dir}/${uid}.json`;
|
|
678
774
|
for (let n = 2; file in files; n++) file = `${dir}/${uid}-${n}.json`;
|
|
679
775
|
files[file] = `${JSON.stringify(json, null, 2)}\n`;
|
|
680
|
-
|
|
776
|
+
const folderUid = sub ? resolved.uidOf(sub) : undefined;
|
|
777
|
+
dashboards.push({ uid, title: e.props.title, ...(folder ? { folder } : {}), ...(folderUid ? { folderUid } : {}), file, json });
|
|
681
778
|
}
|
|
682
779
|
|
|
683
|
-
const
|
|
780
|
+
const settings = named.map(([, e]) => e).filter(isDatasourceProvisioningEntity);
|
|
781
|
+
if (settings.length > 1) throw new Error("grafana: declare at most one DatasourceProvisioning; the build writes one datasource provisioning file");
|
|
782
|
+
const datasourceFile = datasourceFileSettings(settings[0]?.props);
|
|
783
|
+
|
|
784
|
+
const declaredProviders = named.filter(([, e]) => isDashboardProviderEntity(e)).map(([, e]) => (e as unknown as { props: DashboardProviderEntityProps }).props);
|
|
684
785
|
const providers =
|
|
685
786
|
declaredProviders.length > 0
|
|
686
787
|
? declaredProviders.map(provisionedProvider)
|
|
@@ -688,20 +789,21 @@ export function buildGrafana(entities: Map<string, Declarable> | Iterable<Declar
|
|
|
688
789
|
? [provisionedProvider({ name: DEFAULT_PROVIDER_NAME })]
|
|
689
790
|
: [];
|
|
690
791
|
|
|
691
|
-
if (datasources.length > 0) files[DATASOURCES_FILE] = datasourcesYaml(datasources);
|
|
792
|
+
if (datasources.length > 0 || datasourceFile.deleteDatasources.length > 0) files[DATASOURCES_FILE] = datasourcesYaml(datasources, datasourceFile);
|
|
692
793
|
if (providers.length > 0) files[DASHBOARD_PROVIDERS_FILE] = dashboardProvidersYaml(providers);
|
|
693
794
|
const alerting = buildAlerting(named.map(([, e]) => e));
|
|
694
795
|
if (alerting) files[ALERTING_FILE] = alertingYaml(alerting.file);
|
|
695
796
|
|
|
696
797
|
const index: GrafanaIndex = {
|
|
697
798
|
grafanaSchema: `${GRAFANA_SCHEMA_PIN.source}@${GRAFANA_SCHEMA_PIN.ref}`,
|
|
698
|
-
dashboards: dashboards.map(({ uid, title, folder, file }) => ({ uid, title, ...(folder ? { folder } : {}), file })),
|
|
799
|
+
dashboards: dashboards.map(({ uid, title, folder, folderUid, file }) => ({ uid, title, ...(folder ? { folder } : {}), ...(folderUid ? { folderUid } : {}), file })),
|
|
800
|
+
...(resolved.folders.length > 0 ? { folders: resolved.folders } : {}),
|
|
699
801
|
datasources: datasources.map(({ name, type, uid }) => ({ name, type, uid })),
|
|
700
802
|
...(externalDatasources.length > 0 ? { externalDatasources } : {}),
|
|
701
803
|
...(alerting ? { alerting: alerting.index } : {}),
|
|
702
804
|
files: Object.keys(files).sort(),
|
|
703
805
|
};
|
|
704
|
-
return { dashboards, datasources, externalDatasources, providers, ...(alerting ? { alerting: alerting.file } : {}), files, index };
|
|
806
|
+
return { dashboards, datasources, externalDatasources, providers, folders: resolved.folders, datasourceFile, ...(alerting ? { alerting: alerting.file } : {}), files, index };
|
|
705
807
|
}
|
|
706
808
|
|
|
707
809
|
/** Every file the grafana entities render to, by path relative to the output directory. */
|
package/src/catalog.ts
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { LexiconEntry } from "@intentius/chant/lsp/lexicon-providers";
|
|
8
|
-
import { DATASOURCE_TYPE, EXTERNAL_DATASOURCE_TYPE } from "./datasource";
|
|
8
|
+
import { DATASOURCE_PROVISIONING_TYPE, DATASOURCE_TYPE, EXTERNAL_DATASOURCE_TYPE } from "./datasource";
|
|
9
|
+
import { FOLDER_TYPE } from "./folder";
|
|
9
10
|
import { DASHBOARD_TYPE, DASHBOARD_PROVIDER_TYPE } from "./dashboard";
|
|
10
11
|
import * as panels from "./panels";
|
|
11
12
|
import * as queries from "./query";
|
|
@@ -21,7 +22,7 @@ import {
|
|
|
21
22
|
NOTIFICATION_TEMPLATE_TYPE,
|
|
22
23
|
} from "./alerting";
|
|
23
24
|
|
|
24
|
-
export type CatalogKind = "dashboard" | "datasource" | "provider" | "panel" | "row" | "query" | "variable" | "alerting" | "expression";
|
|
25
|
+
export type CatalogKind = "dashboard" | "folder" | "datasource" | "provider" | "panel" | "row" | "query" | "variable" | "alerting" | "expression";
|
|
25
26
|
|
|
26
27
|
export interface CatalogEntry {
|
|
27
28
|
className: string;
|
|
@@ -67,6 +68,9 @@ const VARIABLES: Array<[string, string, string]> = [
|
|
|
67
68
|
["DatasourceVariable", "datasource", "A choice of datasource of one plugin type; usable wherever a datasource is"],
|
|
68
69
|
["ConstantVariable", "constant", "A hidden, fixed value"],
|
|
69
70
|
["TextboxVariable", "textbox", "A free-text box"],
|
|
71
|
+
["AdhocVariable", "adhoc", "Key/value filters Grafana adds to every query sent to the variable's datasource"],
|
|
72
|
+
["GroupByVariable", "groupby", "A choice of label keys Grafana groups every query to the variable's datasource by (experimental in Grafana)"],
|
|
73
|
+
["SwitchVariable", "switch", "An on/off switch with a value for each state (Grafana 12.3 and later)"],
|
|
70
74
|
];
|
|
71
75
|
|
|
72
76
|
const EXPRESSIONS: Array<[string, string, string]> = [
|
|
@@ -100,6 +104,20 @@ export const BUILTIN_CATALOG: CatalogEntry[] = [
|
|
|
100
104
|
entityKind: "resource",
|
|
101
105
|
description: "A datasource that already exists in Grafana: referenced like a Datasource and checked by GRAF101/GRAF102, never provisioned",
|
|
102
106
|
},
|
|
107
|
+
{
|
|
108
|
+
className: "Folder",
|
|
109
|
+
entityType: FOLDER_TYPE,
|
|
110
|
+
kind: "folder",
|
|
111
|
+
entityKind: "resource",
|
|
112
|
+
description: "A folder with a stable uid, nested with parent; a dashboard's folder may be one instead of a path",
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
className: "DatasourceProvisioning",
|
|
116
|
+
entityType: DATASOURCE_PROVISIONING_TYPE,
|
|
117
|
+
kind: "provider",
|
|
118
|
+
entityKind: "resource",
|
|
119
|
+
description: "The datasource provisioning file's prune (on by default) and deleteDatasources",
|
|
120
|
+
},
|
|
103
121
|
{
|
|
104
122
|
className: "DashboardProvider",
|
|
105
123
|
entityType: DASHBOARD_PROVIDER_TYPE,
|
package/src/codegen/docs.ts
CHANGED
|
@@ -31,10 +31,17 @@ export const overview = new Dashboard({ title: "Overview", panels: [requests] })
|
|
|
31
31
|
|
|
32
32
|
Panels: time series, stat, gauge, table, logs, traces, heatmap, text, bar
|
|
33
33
|
chart, bar gauge, pie chart, state timeline, status history, histogram, node
|
|
34
|
-
graph, XY chart, trend, canvas, geomap, flame graph and alert list, plus rows.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
graph, XY chart, trend, canvas, geomap, flame graph and alert list, plus rows.
|
|
35
|
+
Queries: Prometheus (PromQL), Tempo (TraceQL), Loki (LogQL), Elasticsearch,
|
|
36
|
+
CloudWatch, Azure Monitor, Cloud Monitoring, BigQuery, Pyroscope, and SQL for
|
|
37
|
+
PostgreSQL, MySQL and MSSQL. Panel options and query fields are generated from
|
|
38
|
+
Grafana's own schemas at a pinned version, corrected against Grafana's CUE, and
|
|
39
|
+
track Grafana 12.4 and 13.x. Every build validates the dashboards against the
|
|
40
|
+
same schemas.
|
|
41
|
+
Dashboards are written in the classic (v1) JSON model. Grafana 13's v2 model
|
|
42
|
+
(tabs, auto grids, conditional rendering) cannot be declared yet; import and
|
|
43
|
+
drift read a v2 dashboard by converting it to the classic model, and say what
|
|
44
|
+
the conversion loses.
|
|
38
45
|
\`definePanel\` and \`defineQuery\` add plugins chant doesn't ship.
|
|
39
46
|
\`chant import\` turns dashboard JSON exported from Grafana into this TypeScript,
|
|
40
47
|
and it builds back to the same dashboard (see
|
|
@@ -65,8 +72,8 @@ wrong type (GRAF102), a \`$variable\` the dashboard doesn't declare (GRAF103),
|
|
|
65
72
|
duplicate uids and ids (GRAF104), panels off the grid or overlapping (GRAF105),
|
|
66
73
|
anything Grafana's dashboard schema rejects (GRAF107), PromQL sent to a
|
|
67
74
|
Prometheus that doesn't parse (GRAF108), dashboard providers that load a
|
|
68
|
-
dashboard twice or not into its declared folder (GRAF109),
|
|
69
|
-
doesn't know (GRAF115). For alerting, they catch rules whose condition or expressions name
|
|
75
|
+
dashboard twice or not into its declared folder (GRAF109), a panel repeated over a variable that only ever
|
|
76
|
+
holds one value (GRAF110), and a unit Grafana doesn't know (GRAF115). For alerting, they catch rules whose condition or expressions name
|
|
70
77
|
no query (GRAF111), queries to undeclared datasources (GRAF112), routes to
|
|
71
78
|
undeclared contact points or mute timings (GRAF113), and uids, intervals and
|
|
72
79
|
duplicates Grafana refuses (GRAF114).
|
package/src/codegen/generate.ts
CHANGED
|
@@ -4,7 +4,11 @@
|
|
|
4
4
|
* 1. `src/schema/<name>.gen.ts` from each vendored schema in `src/spec/schemas/`
|
|
5
5
|
* (see `src/pin.ts`). These are committed, and a test fails when they
|
|
6
6
|
* drift from what this step would write.
|
|
7
|
-
* 2. `src/
|
|
7
|
+
* 2. `src/spec/schemas.gen.ts`, the same schemas with the overlay applied, as
|
|
8
|
+
* JSON text. Validation (GRAF107, the `/validation` subpath) reads them
|
|
9
|
+
* from there rather than from the files, so it needs no filesystem access
|
|
10
|
+
* and works bundled. Committed and checked the same way.
|
|
11
|
+
* 3. `src/generated/lexicon-grafana.json`, the registry of entity classes
|
|
8
12
|
* the package exports, derived from the catalog so the packaged
|
|
9
13
|
* `dist/meta.json` cannot disagree with the classes.
|
|
10
14
|
*/
|
|
@@ -29,6 +33,27 @@ function dashboardExtras(schema: Record<string, unknown>): string[] {
|
|
|
29
33
|
return ["/** The dashboard `schemaVersion` the pinned schema defaults to, and the one chant emits. */", `export const DASHBOARD_SCHEMA_VERSION = ${v};`];
|
|
30
34
|
}
|
|
31
35
|
|
|
36
|
+
/** `src/spec/schemas.gen.ts`: each schema validation uses, overlay applied, as one line of JSON text. */
|
|
37
|
+
export function schemaDataModule(): string {
|
|
38
|
+
const lines = [
|
|
39
|
+
"// Code generated by chant generate. DO NOT EDIT.",
|
|
40
|
+
"// The vendored Grafana schemas (src/spec/schemas/, see src/pin.ts) with the",
|
|
41
|
+
"// correction overlay (src/spec/overlay/) applied, as JSON text, for",
|
|
42
|
+
"// src/schema-validate.ts. A module rather than a file read, so validation",
|
|
43
|
+
"// needs no filesystem access and survives bundling; text rather than object",
|
|
44
|
+
"// literals, so loading it costs little until a schema is parsed.",
|
|
45
|
+
"",
|
|
46
|
+
'import type { SchemaName } from "../pin";',
|
|
47
|
+
"",
|
|
48
|
+
"export const SCHEMA_JSON: Readonly<Record<SchemaName, string>> = {",
|
|
49
|
+
];
|
|
50
|
+
for (const name of SCHEMA_NAMES) {
|
|
51
|
+
lines.push(` ${JSON.stringify(name)}: ${JSON.stringify(JSON.stringify(loadSchema(name as SchemaName)))},`);
|
|
52
|
+
}
|
|
53
|
+
lines.push("};", "");
|
|
54
|
+
return lines.join("\n");
|
|
55
|
+
}
|
|
56
|
+
|
|
32
57
|
/** Every generated schema module, keyed by path relative to the package root. */
|
|
33
58
|
export function schemaModules(): Record<string, string> {
|
|
34
59
|
const out: Record<string, string> = {};
|
|
@@ -37,6 +62,7 @@ export function schemaModules(): Record<string, string> {
|
|
|
37
62
|
const extra = name === "dashboard" ? dashboardExtras(schema) : [];
|
|
38
63
|
out[`src/schema/${name}.gen.ts`] = schemaModule(name, schema, extra).source;
|
|
39
64
|
}
|
|
65
|
+
out["src/spec/schemas.gen.ts"] = schemaDataModule();
|
|
40
66
|
return out;
|
|
41
67
|
}
|
|
42
68
|
|
package/src/composites/shared.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import type { DatasourceInput } from "../query";
|
|
7
7
|
import type { DashboardLinkInput } from "../dashboard";
|
|
8
|
+
import type { FolderEntity } from "../folder";
|
|
8
9
|
import type { ThresholdsConfig } from "../schema/dashboard.gen";
|
|
9
10
|
|
|
10
11
|
/** Dashboard settings every composite takes; each has a default derived from what it reads. */
|
|
@@ -16,8 +17,8 @@ export interface DashboardOptions {
|
|
|
16
17
|
uid?: string;
|
|
17
18
|
description?: string;
|
|
18
19
|
tags?: string[];
|
|
19
|
-
/** The Grafana folder to provision it into. */
|
|
20
|
-
folder?: string;
|
|
20
|
+
/** The Grafana folder to provision it into: a path, or a `Folder` to pin its uid. */
|
|
21
|
+
folder?: string | FolderEntity;
|
|
21
22
|
/** Initial time range. */
|
|
22
23
|
time?: { from: string; to: string };
|
|
23
24
|
/** Auto-refresh interval, e.g. `30s`. */
|
package/src/dashboard.ts
CHANGED
|
@@ -12,6 +12,8 @@ import type { Declarable } from "@intentius/chant/declarable";
|
|
|
12
12
|
import type { PanelEntity, RowEntity } from "./panels";
|
|
13
13
|
import type { VariableEntity } from "./variables";
|
|
14
14
|
import type { DashboardLink, TimePickerConfig } from "./schema/dashboard.gen";
|
|
15
|
+
import { isFolderEntity, type FolderEntity } from "./folder";
|
|
16
|
+
import type { AnnotationInput } from "./annotations";
|
|
15
17
|
|
|
16
18
|
/** A dashboard link: `title` and `type` required, the rest defaulted as Grafana's editor would. */
|
|
17
19
|
export type DashboardLinkInput = Partial<DashboardLink> & Pick<DashboardLink, "title" | "type">;
|
|
@@ -54,26 +56,50 @@ export interface DashboardProps {
|
|
|
54
56
|
panels?: Array<PanelEntity<any, any> | RowEntity>;
|
|
55
57
|
links?: DashboardLinkInput[];
|
|
56
58
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* folder inside Platform; earlier versions use only the last level
|
|
62
|
-
* (GRAF109 warns). Leave it out for the General folder.
|
|
59
|
+
* Annotation queries: events drawn on the dashboard's time series panels
|
|
60
|
+
* (deploys, incidents), each from a datasource. Grafana adds its own
|
|
61
|
+
* "Annotations & Alerts" query to every dashboard that has none with
|
|
62
|
+
* `builtIn: 1`, so leave that one out unless you change it.
|
|
63
63
|
*/
|
|
64
|
-
|
|
64
|
+
annotations?: AnnotationInput[];
|
|
65
|
+
/**
|
|
66
|
+
* The Grafana folder: a path (`"Platform/Kubernetes"`) or a `Folder`,
|
|
67
|
+
* which pins the folder's uid. Written as a subdirectory of `dashboards/`,
|
|
68
|
+
* which the provider maps to folders (`foldersFromFilesStructure`); a path
|
|
69
|
+
* nests one directory in another, which Grafana 13.1 and later make a
|
|
70
|
+
* Kubernetes folder inside Platform, and earlier versions use only the
|
|
71
|
+
* last level (GRAF109 warns). The API applier creates each level with its
|
|
72
|
+
* uid. Leave it out for the General folder.
|
|
73
|
+
*/
|
|
74
|
+
folder?: string | FolderEntity;
|
|
65
75
|
}
|
|
66
76
|
|
|
77
|
+
/** A dashboard's props as the entity holds them: `folder` is always the path, and a `Folder` given for it is `folderEntity`. */
|
|
78
|
+
export type DashboardEntityProps = Omit<DashboardProps, "folder"> & { folder?: string };
|
|
79
|
+
|
|
67
80
|
export interface DashboardEntity extends Declarable {
|
|
68
|
-
readonly props:
|
|
81
|
+
readonly props: DashboardEntityProps;
|
|
82
|
+
/** The `Folder` the dashboard was given, when it was given one rather than a path. */
|
|
83
|
+
readonly folderEntity?: FolderEntity;
|
|
69
84
|
}
|
|
70
85
|
|
|
71
86
|
export const DASHBOARD_TYPE = "Grafana::Dashboard";
|
|
72
87
|
|
|
73
88
|
const DashboardBase = createResource(DASHBOARD_TYPE, "grafana", {}) as unknown as (this: object, props: Record<string, unknown>) => void;
|
|
74
89
|
|
|
75
|
-
/**
|
|
90
|
+
/**
|
|
91
|
+
* A Grafana dashboard. A `Folder` given as its `folder` is held as the
|
|
92
|
+
* folder's path, so everything that reads the dashboard's props (the build,
|
|
93
|
+
* the checks, observation's diff) sees one form; the `Folder` itself, for its
|
|
94
|
+
* uid, is `folderEntity`.
|
|
95
|
+
*/
|
|
76
96
|
export const Dashboard = function (this: object, props: DashboardProps) {
|
|
97
|
+
const folder = props.folder;
|
|
98
|
+
if (isFolderEntity(folder)) {
|
|
99
|
+
DashboardBase.call(this, { ...props, folder: folder.path } as unknown as Record<string, unknown>);
|
|
100
|
+
Object.defineProperty(this, "folderEntity", { value: folder, enumerable: false });
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
77
103
|
DashboardBase.call(this, props as unknown as Record<string, unknown>);
|
|
78
104
|
} as unknown as new (props: DashboardProps) => DashboardEntity;
|
|
79
105
|
Object.defineProperty(Dashboard, "name", { value: "Dashboard" });
|
|
@@ -95,11 +121,14 @@ export interface DashboardProviderProps {
|
|
|
95
121
|
name: string;
|
|
96
122
|
orgId?: number;
|
|
97
123
|
/**
|
|
98
|
-
* Put every dashboard this provider loads in this folder
|
|
99
|
-
* `
|
|
100
|
-
*
|
|
124
|
+
* Put every dashboard this provider loads in this folder: a title, or a
|
|
125
|
+
* root-level `Folder`, whose title and uid are written as `folder` and
|
|
126
|
+
* `folderUid` (Grafana creates a provider's folder at the root, so a
|
|
127
|
+
* nested `Folder` is refused). Setting it (or `folderUid`) turns
|
|
128
|
+
* `foldersFromFilesStructure` off by default, so each dashboard's own
|
|
129
|
+
* `folder` is ignored (GRAF109 warns).
|
|
101
130
|
*/
|
|
102
|
-
folder?: string;
|
|
131
|
+
folder?: string | FolderEntity;
|
|
103
132
|
folderUid?: string;
|
|
104
133
|
/** Where the `dashboards/` output is mounted in the Grafana container. Defaults to `/var/lib/grafana/dashboards`. */
|
|
105
134
|
path?: string;
|
|
@@ -110,8 +139,11 @@ export interface DashboardProviderProps {
|
|
|
110
139
|
updateIntervalSeconds?: number;
|
|
111
140
|
}
|
|
112
141
|
|
|
142
|
+
/** A provider's props as the entity holds them: a `Folder` given for `folder` is written out as `folder` and `folderUid`. */
|
|
143
|
+
export type DashboardProviderEntityProps = Omit<DashboardProviderProps, "folder"> & { folder?: string };
|
|
144
|
+
|
|
113
145
|
export interface DashboardProviderEntity extends Declarable {
|
|
114
|
-
readonly props:
|
|
146
|
+
readonly props: DashboardProviderEntityProps;
|
|
115
147
|
}
|
|
116
148
|
|
|
117
149
|
export const DASHBOARD_PROVIDER_TYPE = "Grafana::DashboardProvider";
|
|
@@ -123,6 +155,19 @@ const ProviderBase = createResource(DASHBOARD_PROVIDER_TYPE, "grafana", {}) as u
|
|
|
123
155
|
|
|
124
156
|
/** An entry in the dashboard provisioning file. Optional: a default one is written when none is declared. */
|
|
125
157
|
export const DashboardProvider = function (this: object, props: DashboardProviderProps) {
|
|
158
|
+
const folder = props.folder;
|
|
159
|
+
if (isFolderEntity(folder)) {
|
|
160
|
+
if (folder.props.parent) {
|
|
161
|
+
throw new Error(
|
|
162
|
+
`grafana: DashboardProvider "${props.name}" puts its dashboards in the Folder "${folder.path}", which is nested; Grafana creates a provider's folder at the root. Give it a root-level Folder, or leave folder out and set each dashboard's folder`,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
if (props.folderUid !== undefined && props.folderUid !== folder.uid) {
|
|
166
|
+
throw new Error(`grafana: DashboardProvider "${props.name}" has folderUid "${props.folderUid}", but its Folder's uid is "${folder.uid}"`);
|
|
167
|
+
}
|
|
168
|
+
ProviderBase.call(this, { ...props, folder: folder.props.title, folderUid: folder.uid } as unknown as Record<string, unknown>);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
126
171
|
ProviderBase.call(this, props as unknown as Record<string, unknown>);
|
|
127
172
|
} as unknown as new (props: DashboardProviderProps) => DashboardProviderEntity;
|
|
128
173
|
Object.defineProperty(DashboardProvider, "name", { value: "DashboardProvider" });
|
package/src/datasource-refs.ts
CHANGED
|
@@ -48,14 +48,15 @@ export type ResolvedDatasource =
|
|
|
48
48
|
|
|
49
49
|
/** One place a dashboard names, or inherits, a datasource. */
|
|
50
50
|
export interface DatasourceUse {
|
|
51
|
-
/** A panel's own `datasource`, one of its queries (`targets`),
|
|
52
|
-
kind: "panel" | "query" | "variable";
|
|
51
|
+
/** A panel's own `datasource`, one of its queries (`targets`), a query, ad hoc or group by variable, or an annotation query. */
|
|
52
|
+
kind: "panel" | "query" | "variable" | "annotation";
|
|
53
53
|
/** A human description, e.g. `panel "Latency" (id 3) query B`. */
|
|
54
54
|
where: string;
|
|
55
55
|
panel?: Json;
|
|
56
56
|
target?: Json;
|
|
57
57
|
variable?: Json;
|
|
58
|
-
|
|
58
|
+
annotation?: Json;
|
|
59
|
+
/** The ref written on this panel, query, variable or annotation; unset when a query inherits its panel's. */
|
|
59
60
|
ref?: DatasourceRefJson;
|
|
60
61
|
/** Where the request goes: the query's own ref, else its panel's. */
|
|
61
62
|
resolved: ResolvedDatasource;
|
|
@@ -146,10 +147,20 @@ export function resolveDatasourceRef(
|
|
|
146
147
|
return { kind: "undeclared", uid: ref.uid, ...(ref.type ? { type: ref.type } : {}) };
|
|
147
148
|
}
|
|
148
149
|
|
|
150
|
+
/** Variable types that send requests to a datasource of their own: a query variable's query, an ad hoc or group by variable's key and value lookups. */
|
|
151
|
+
const DATASOURCE_VARIABLE_TYPES: ReadonlySet<string> = new Set(["query", "adhoc", "groupby"]);
|
|
152
|
+
|
|
153
|
+
/** A dashboard's `annotations.list`. */
|
|
154
|
+
export function annotationsOf(dashboard: Json): Json[] {
|
|
155
|
+
const list = (dashboard.annotations as { list?: unknown } | undefined)?.list;
|
|
156
|
+
return Array.isArray(list) ? (list as Json[]).filter((a) => a && typeof a === "object" && !Array.isArray(a)) : [];
|
|
157
|
+
}
|
|
158
|
+
|
|
149
159
|
/**
|
|
150
160
|
* Every place a dashboard names or inherits a datasource: each panel's own
|
|
151
|
-
* ref, each query (with its own ref, or its panel's when it has none),
|
|
152
|
-
*
|
|
161
|
+
* ref, each query (with its own ref, or its panel's when it has none), each
|
|
162
|
+
* query, ad hoc and group by variable, and each annotation query with a datasource. Rows are left
|
|
163
|
+
* out; their panels carry the ref.
|
|
153
164
|
*/
|
|
154
165
|
export function datasourceUses(dashboard: Json, known: ReadonlyMap<string, KnownDatasource>): DatasourceUse[] {
|
|
155
166
|
const dsVars = datasourceVariables(dashboard);
|
|
@@ -175,7 +186,7 @@ export function datasourceUses(dashboard: Json, known: ReadonlyMap<string, Known
|
|
|
175
186
|
}
|
|
176
187
|
}
|
|
177
188
|
for (const variable of variablesOf(dashboard)) {
|
|
178
|
-
if (variable.type
|
|
189
|
+
if (!DATASOURCE_VARIABLE_TYPES.has(String(variable.type))) continue;
|
|
179
190
|
const ref = refOf(variable.datasource);
|
|
180
191
|
out.push({
|
|
181
192
|
kind: "variable",
|
|
@@ -185,5 +196,18 @@ export function datasourceUses(dashboard: Json, known: ReadonlyMap<string, Known
|
|
|
185
196
|
resolved: resolveDatasourceRef(ref, dsVars, known),
|
|
186
197
|
});
|
|
187
198
|
}
|
|
199
|
+
for (const annotation of annotationsOf(dashboard)) {
|
|
200
|
+
const ref = refOf(annotation.datasource);
|
|
201
|
+
if (!ref) continue;
|
|
202
|
+
const target = refOf(annotation.target) as Json | undefined;
|
|
203
|
+
out.push({
|
|
204
|
+
kind: "annotation",
|
|
205
|
+
where: `annotation "${String(annotation.name ?? "?")}"`,
|
|
206
|
+
annotation,
|
|
207
|
+
...(target ? { target } : {}),
|
|
208
|
+
ref,
|
|
209
|
+
resolved: resolveDatasourceRef(ref, dsVars, known),
|
|
210
|
+
});
|
|
211
|
+
}
|
|
188
212
|
return out;
|
|
189
213
|
}
|