@intentius/chant-lexicon-grafana 0.105.0 → 0.106.1

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 CHANGED
@@ -61,6 +61,6 @@ GRAF101 and GRAF102 compare dashboards with the datasources declared in the same
61
61
  ## Plain-data API
62
62
 
63
63
  - `buildGrafana(entities)` and `grafanaFiles(entities)` render every file by path, for embedding in another lexicon's output (a ConfigMap, a volume).
64
- - `GrafanaConfigMaps` and `grafanaVolumes` from `@intentius/chant-lexicon-grafana/k8s` deliver dashboards and provisioning to Kubernetes as ConfigMaps labelled for the Grafana Helm chart's sidecar (`grafana_dashboard`, `grafana_datasource`), or mounted into a plain Grafana Deployment. `GrafanaOperatorResources` from the same subpath writes `GrafanaDashboard`, `GrafanaDatasource`, `GrafanaFolder` and `GrafanaLibraryPanel` resources for a Grafana the Grafana Operator runs.
64
+ - `GrafanaConfigMaps` and `grafanaVolumes` from `@intentius/chant-lexicon-grafana/k8s` deliver dashboards and provisioning to Kubernetes as ConfigMaps labelled for the Grafana Helm chart's sidecar (`grafana_dashboard`, `grafana_datasource`), or mounted into a plain Grafana Deployment. `GrafanaOperatorResources` from the same subpath writes `GrafanaDashboard`, `GrafanaDatasource`, `GrafanaFolder` and `GrafanaLibraryPanel` resources, and `GrafanaAlertRuleGroup`, `GrafanaContactPoint`, `GrafanaNotificationPolicy`, `GrafanaMuteTiming` and `GrafanaNotificationTemplate` resources for the alerting, for a Grafana the Grafana Operator runs.
65
65
  - `renderDashboard(dashboard)` and `dashboardJson(dashboard)` render one dashboard.
66
66
  - `validateGrafanaOutput({ dashboards, datasources })` and `validateDashboardSchema(json)`, from `@intentius/chant-lexicon-grafana/validation`, run the checks without a build. They are not on the package root, so declaring dashboards does not load them; ajv loads the first time a schema is checked.
package/dist/detect.d.ts CHANGED
@@ -40,6 +40,14 @@ export declare function looksLikeDashboardProvisioning(data: unknown): data is {
40
40
  * of `name` and `rules` with `expr`) is not mistaken for one.
41
41
  */
42
42
  export declare function looksLikeAlertingProvisioning(data: unknown): data is Record<string, unknown>;
43
+ /**
44
+ * The `spec` of a Grafana Operator alerting resource (`GrafanaAlertRuleGroup`,
45
+ * `GrafanaContactPoint`, `GrafanaNotificationPolicy`, `GrafanaMuteTiming`,
46
+ * `GrafanaNotificationTemplate`) as the k8s importer offers it (#3538): every
47
+ * operator resource's spec has an `instanceSelector`, beside the field that
48
+ * holds the alerting content.
49
+ */
50
+ export declare function looksLikeOperatorAlertingSpec(data: unknown): boolean;
43
51
  /**
44
52
  * Template detection for `chant import` and friends: dashboard JSON in any
45
53
  * of the shapes above (v2 included, #2947), or a datasource, dashboard or
@@ -1 +1 @@
1
- {"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../src/detect.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,uFAAuF;AACvF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAGjF;AAED,mFAAmF;AACnF,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE3F;AAED,8GAA8G;AAC9G,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAQ1I;AAED,2GAA2G;AAC3G,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAG3D;AAED,2EAA2E;AAC3E,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI;IAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAE1H;AAED,oGAAoG;AACpG,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI;IAAE,UAAU,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC;IAAC,iBAAiB,CAAC,EAAE,OAAO,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAEvK;AAED,0EAA0E;AAC1E,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI;IAAE,UAAU,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,OAAO,EAAE,CAAA;CAAE,CAEnH;AAuBD;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAO5F;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAWrD"}
1
+ {"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../src/detect.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,uFAAuF;AACvF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAGjF;AAED,mFAAmF;AACnF,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE3F;AAED,8GAA8G;AAC9G,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAQ1I;AAED,2GAA2G;AAC3G,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAG3D;AAED,2EAA2E;AAC3E,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI;IAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAE1H;AAED,oGAAoG;AACpG,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI;IAAE,UAAU,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC;IAAC,iBAAiB,CAAC,EAAE,OAAO,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAEvK;AAED,0EAA0E;AAC1E,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI;IAAE,UAAU,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,OAAO,EAAE,CAAA;CAAE,CAEnH;AAuBD;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAO5F;AAED;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAWpE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAYrD"}
@@ -1 +1 @@
1
- {"version":3,"file":"embedded.d.ts","sourceRoot":"","sources":["../../src/import/embedded.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,uBAAuB,EAAkB,MAAM,kCAAkC,CAAC;AAOhG,eAAO,MAAM,iBAAiB,EAAE,uBAyC/B,CAAC"}
1
+ {"version":3,"file":"embedded.d.ts","sourceRoot":"","sources":["../../src/import/embedded.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,uBAAuB,EAAkB,MAAM,kCAAkC,CAAC;AAOhG,eAAO,MAAM,iBAAiB,EAAE,uBAwC/B,CAAC"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Grafana Operator alerting resources embedded in a k8s manifest, for
3
+ * `chant import` (#3538).
4
+ *
5
+ * `GrafanaOperatorResources` (#3156) writes `GrafanaAlertRuleGroup`,
6
+ * `GrafanaContactPoint`, `GrafanaNotificationPolicy`, `GrafanaMuteTiming` and
7
+ * `GrafanaNotificationTemplate` from grafana alerting declarations. The k8s
8
+ * parser offers the one field of each that holds the alerting content
9
+ * (`spec.rules`, `spec.receivers`, `spec.route`, `spec.time_intervals`,
10
+ * `spec.template`) with the whole `spec` as the document. This importer
11
+ * reads that spec back into the provisioning-file shape, which the alerting
12
+ * importer turns into declarations, and the k8s value becomes a call of the
13
+ * matching function in `@intentius/chant-lexicon-grafana/k8s` over the
14
+ * declaration, so a build gives the same field again.
15
+ *
16
+ * The reverse of what `GrafanaOperatorResources` writes:
17
+ *
18
+ * - a rule: the CRD's camel case (`missingSeriesEvalsToResolve`,
19
+ * `notificationSettings`) goes back to the file's snake case, the
20
+ * `__dashboardUid__` and `__panelId__` annotations go back to
21
+ * `dashboardUid` and `panelId`, and the values the CRD requires and the
22
+ * file leaves to Grafana's defaults (`for: 0s`, `noDataState: NoData`,
23
+ * `execErrState: Alerting`) are left out;
24
+ * - a receiver: each `valuesFrom` entry becomes `${NAME}` at its
25
+ * `targetPath` in the settings, `NAME` being the Secret key. The Secret's
26
+ * name is not part of a declaration, so a contact point that reads a
27
+ * Secret is written with a small module holding
28
+ * `operatorReceivers(contactPoint, "<secret>")`;
29
+ * - the policy tree: `object_matchers` go back to Alertmanager `matchers`
30
+ * strings where each can be written as one.
31
+ *
32
+ * A rule group's folder is a title in a declaration and a `GrafanaFolder`
33
+ * name (`folderRef`) in the resource, and the resource does not hold the
34
+ * title, so the declaration's `folder` is the reference, with a warning.
35
+ */
36
+ import type { EmbeddedContentImporter } from "@intentius/chant/import/embedded";
37
+ type Json = Record<string, unknown>;
38
+ /** One entry of `GrafanaAlertRuleGroup.spec.rules` as a rule of the provisioning file. */
39
+ export declare function provisionedRule(rule: Json): Json;
40
+ /**
41
+ * `GrafanaContactPoint.spec.receivers` as the provisioning file's receivers,
42
+ * each `valuesFrom` entry put back as `${NAME}` in the settings, and the name
43
+ * of the Secret they read (one Secret, as `GrafanaOperatorResources` writes).
44
+ */
45
+ export declare function provisionedReceivers(receivers: unknown[], where: string): {
46
+ receivers: Json[];
47
+ secretName?: string;
48
+ };
49
+ /** A route of the policy tree as the provisioning file holds it: `object_matchers` as `matchers` strings, in every nested route. */
50
+ export declare function provisionedRoute(route: Json): Json;
51
+ export declare const operatorImporter: EmbeddedContentImporter;
52
+ export {};
53
+ //# sourceMappingURL=operator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operator.d.ts","sourceRoot":"","sources":["../../src/import/operator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,KAAK,EAAmB,uBAAuB,EAAkB,MAAM,kCAAkC,CAAC;AAKjH,KAAK,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAkBpC,0FAA0F;AAC1F,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAwBhD;AAYD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,CAoBpH;AAWD,oIAAoI;AACpI,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI,CAWlD;AAwCD,eAAO,MAAM,gBAAgB,EAAE,uBA2C9B,CAAC"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "b8fcc78eb59a6ab6a0b157b359336c1925009546fcbf4b5cfdec186bc73840a1",
4
+ "manifest.json": "06345c465338218becce11ad5a3cf5e8c130d367b0eb9f8044886d2a5987489a",
5
5
  "meta.json": "330a1598c3b35ae62267e886a3416a494b0ebe9f1d2e2379253787d0a0bcb32e",
6
6
  "types/index.d.ts": "ad385264290f2486f7a54be205684de8415a25b74eee5b809ca625ae19fb08da",
7
7
  "rules/grafana-ast.ts": "51f4fac423f7a5c6d6f577074e95b4a70d4ab0a05883a86e04970a50c63d92e8",
@@ -27,9 +27,9 @@
27
27
  "rules/graf118.ts": "e4ad3c5b944185139331a1f30488c6107a87f37872a0d52b7afc03d9a2ed6125",
28
28
  "rules/grafana-helpers.ts": "0a538daafb10f32077dd6cda4c03b541f703b0a10abc51bfd241eae9365c9be6",
29
29
  "skills/chant-grafana.md": "6a7f4ba6d8a40d339fad892c612131370ef0c2595547860ee718801bfa893163",
30
- "skills/chant-grafana-provisioning.md": "b861a848761c4db9c8c07f248650af14b254c19277b35839ff23033e484089aa",
30
+ "skills/chant-grafana-provisioning.md": "1c7f473ef4e1f9c2debed62833e4f0448ed0fef9f01c83fbbc94299715ed2602",
31
31
  "skills/chant-grafana-alerting.md": "d9648dc14c848851bdccbe069dac54ad06115066f9466c90c4137d07dde6d4ad",
32
32
  "skills/chant-grafana-operations.md": "4e665f80292ab32bde6d77940cc3f3923678c58e6db0b80613489050d2cb665b"
33
33
  },
34
- "composite": "d9b4a6dbb4b8a8b4d6eca31c84bc537279ff5a783ceb746d9066e2cec9786c55"
34
+ "composite": "4a36d87b528d23995dab52878e7e42c4429aa9611a9b9bef2c133e9c4e2c7ba5"
35
35
  }
package/dist/k8s.d.ts CHANGED
@@ -23,16 +23,21 @@
23
23
  * A Grafana run by the Grafana Operator reads custom resources instead
24
24
  * (#3015): `GrafanaOperatorResources` writes a `GrafanaDashboard` per
25
25
  * dashboard, a `GrafanaDatasource` per datasource, a `GrafanaFolder` per
26
- * folder and a `GrafanaLibraryPanel` per library panel (#3186), all
27
- * `grafana.integreatly.org/v1beta1` as the k8s lexicon types them from the
28
- * operator's CRDs (pinned in its `crd-sources.ts`).
26
+ * folder and a `GrafanaLibraryPanel` per library panel (#3186), and for
27
+ * alerting (#3156) a `GrafanaAlertRuleGroup` per rule group, a
28
+ * `GrafanaContactPoint` per contact point, a `GrafanaNotificationPolicy`, a
29
+ * `GrafanaMuteTiming` per mute timing and a `GrafanaNotificationTemplate` per
30
+ * template, all `grafana.integreatly.org/v1beta1` as the k8s lexicon types
31
+ * them from the operator's CRDs (pinned in its `crd-sources.ts`).
29
32
  *
30
33
  * This module is the only part of the grafana lexicon that loads the k8s
31
34
  * lexicon; nothing else imports it.
32
35
  */
33
36
  import { type CompositeInstance } from "@intentius/chant/composite";
34
37
  import type { Declarable } from "@intentius/chant/declarable";
35
- import { ConfigMap, GrafanaDashboard, GrafanaDatasource, GrafanaFolder, GrafanaLibraryPanel } from "@intentius/chant-lexicon-k8s/generated/index";
38
+ import { ConfigMap, GrafanaAlertRuleGroup, GrafanaContactPoint, GrafanaDashboard, GrafanaDatasource, GrafanaFolder, GrafanaLibraryPanel, GrafanaMuteTiming, GrafanaNotificationPolicy, GrafanaNotificationTemplate } from "@intentius/chant-lexicon-k8s/generated/index";
39
+ import { type ProvisionedAlertRule, type ProvisionedContactPoint } from "./alerting-build.js";
40
+ import type { AlertRuleGroupEntity, ContactPointEntity, MuteTimingEntity, NotificationPolicyEntity, NotificationTemplateEntity } from "./alerting.js";
36
41
  import { type ProvisionedDatasource } from "./build.js";
37
42
  /** The label the Grafana Helm chart's sidecar looks for on dashboard ConfigMaps (`sidecar.dashboards.label`). */
38
43
  export declare const SIDECAR_DASHBOARD_LABEL = "grafana_dashboard";
@@ -148,15 +153,18 @@ export interface GrafanaOperatorResourcesProps {
148
153
  */
149
154
  dashboardSource?: "json" | "configMap";
150
155
  /**
151
- * The Secret that datasource secrets come from. A datasource field written
152
- * as `${NAME}` or `$__env{NAME}` (the way file provisioning reads the
153
- * environment) becomes a `spec.valuesFrom` entry reading key `NAME` of
154
- * this Secret, which the operator substitutes for `${NAME}`. Required when
155
- * any datasource has such a reference.
156
+ * The Secret that datasource and contact point secrets come from. A
157
+ * datasource field written as `${NAME}` or `$__env{NAME}` (the way file
158
+ * provisioning reads the environment) becomes a `spec.valuesFrom` entry
159
+ * reading key `NAME` of this Secret, which the operator substitutes for
160
+ * `${NAME}`. A contact point setting that is entirely such a reference
161
+ * becomes a `valuesFrom` entry of its receiver, which the operator writes
162
+ * into the setting at `targetPath`. Required when any datasource or
163
+ * contact point has such a reference.
156
164
  */
157
165
  secretName?: string;
158
166
  }
159
- type OperatorEntity = (InstanceType<typeof GrafanaDashboard> | InstanceType<typeof GrafanaDatasource> | InstanceType<typeof GrafanaFolder> | InstanceType<typeof GrafanaLibraryPanel>) & Declarable;
167
+ type OperatorEntity = (InstanceType<typeof GrafanaDashboard> | InstanceType<typeof GrafanaDatasource> | InstanceType<typeof GrafanaFolder> | InstanceType<typeof GrafanaLibraryPanel> | InstanceType<typeof GrafanaAlertRuleGroup> | InstanceType<typeof GrafanaContactPoint> | InstanceType<typeof GrafanaNotificationPolicy> | InstanceType<typeof GrafanaMuteTiming> | InstanceType<typeof GrafanaNotificationTemplate>) & Declarable;
160
168
  export type GrafanaOperatorResourcesMembers = Record<string, OperatorEntity>;
161
169
  export type GrafanaOperatorResourcesInstance = CompositeInstance<GrafanaOperatorResourcesMembers> & GrafanaOperatorResourcesMembers;
162
170
  /**
@@ -170,6 +178,48 @@ export declare function operatorDatasource(ds: ProvisionedDatasource, secretName
170
178
  datasource: Record<string, unknown>;
171
179
  valuesFrom: Array<Record<string, unknown>>;
172
180
  };
181
+ /**
182
+ * One contact point receiver's settings for `spec.receivers[].settings`, and
183
+ * the `valuesFrom` entries for the secrets in it. A setting that is wholly
184
+ * `${NAME}` (or `$__env{NAME}`, `$NAME`) is taken out of `settings` and read
185
+ * from key `NAME` of `secretName` into the same path by the operator, whose
186
+ * `targetPath` is a dotted path inside the settings. A reference inside
187
+ * longer text, or a `$__file` or `$__vault` one, is an error: the operator
188
+ * can only replace a whole value.
189
+ */
190
+ export declare function operatorReceiverSettings(where: string, settings: Record<string, unknown>, secretName: string | undefined): {
191
+ settings: Record<string, unknown>;
192
+ valuesFrom: Array<Record<string, unknown>>;
193
+ };
194
+ /** One contact point as `GrafanaContactPoint.spec`: its name, and its receivers with their secrets in `valuesFrom`. */
195
+ export declare function operatorContactPoint(cp: ProvisionedContactPoint, secretName: string | undefined): Record<string, unknown>;
196
+ /**
197
+ * One rule as an entry of `GrafanaAlertRuleGroup.spec.rules`. The CRD spells
198
+ * two fields in camel case (`missingSeriesEvalsToResolve`,
199
+ * `notificationSettings`), requires `for`, `noDataState` and `execErrState`
200
+ * (Grafana's defaults fill them), and has no use for `dashboardUid` and
201
+ * `panelId`, which it reads from the `__dashboardUid__` and `__panelId__`
202
+ * annotations.
203
+ */
204
+ export declare function operatorRule(rule: ProvisionedAlertRule): Record<string, unknown>;
205
+ /** One policy tree as `GrafanaNotificationPolicy.spec.route`. `orgId` is the operator's to set, so it is left out. */
206
+ export declare function operatorPolicyRoute(policy: Record<string, unknown>): Record<string, unknown>;
207
+ /**
208
+ * The fields `chant import` writes into an imported Grafana Operator
209
+ * resource (#3538): each takes the alerting declaration the field was read
210
+ * into and gives the field `GrafanaOperatorResources` writes for it, so the
211
+ * resource builds back to what was imported.
212
+ */
213
+ /** `GrafanaAlertRuleGroup.spec.rules` for a rule group. */
214
+ export declare function operatorRules(group: AlertRuleGroupEntity): Array<Record<string, unknown>>;
215
+ /** `GrafanaContactPoint.spec.receivers` for a contact point, its `${NAME}` secrets read from `secretName`. */
216
+ export declare function operatorReceivers(cp: ContactPointEntity, secretName?: string): Array<Record<string, unknown>>;
217
+ /** `GrafanaNotificationPolicy.spec.route` for a policy tree. */
218
+ export declare function operatorPolicy(policy: NotificationPolicyEntity): Record<string, unknown>;
219
+ /** `GrafanaMuteTiming.spec.time_intervals` for a mute timing. */
220
+ export declare function operatorTimeIntervals(timing: MuteTimingEntity): Array<Record<string, unknown>>;
221
+ /** `GrafanaNotificationTemplate.spec.template` for a template. */
222
+ export declare function operatorTemplate(template: NotificationTemplateEntity): string;
173
223
  /**
174
224
  * The Grafana Operator's custom resources for a set of grafana declarations
175
225
  * (`grafana.integreatly.org/v1beta1`, Grafana Operator v5.25.0):
@@ -184,7 +234,15 @@ export declare function operatorDatasource(ds: ProvisionedDatasource, secretName
184
234
  * - a `GrafanaDashboard` per dashboard, `<name>-dashboard-<uid>`, holding
185
235
  * `dashboardJson(dashboard)` in `spec.json` and naming its folder with
186
236
  * `folderRef`;
187
- * - a `GrafanaDatasource` per datasource, `<name>-datasource-<uid>`.
237
+ * - a `GrafanaDatasource` per datasource, `<name>-datasource-<uid>`;
238
+ * - for the alerting the build declares: a `GrafanaAlertRuleGroup` per rule
239
+ * group, `<name>-rule-group-<folder>-<group>`, in the `GrafanaFolder` whose
240
+ * title is the group's `folder` (one is written when no folder has it);
241
+ * a `GrafanaContactPoint` per contact point, `<name>-contact-point-<name>`,
242
+ * whose `${NAME}` secrets go to each receiver's `valuesFrom`; a
243
+ * `GrafanaNotificationPolicy`, `<name>-notification-policy`, with its
244
+ * nested routes inline; a `GrafanaMuteTiming` per mute timing and a
245
+ * `GrafanaNotificationTemplate` per template.
188
246
  *
189
247
  * Every resource carries `instanceSelector`. Export the result from a k8s
190
248
  * build root, beside or instead of `GrafanaConfigMaps`.
package/dist/k8s.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"k8s.d.ts","sourceRoot":"","sources":["../src/k8s.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"k8s.d.ts","sourceRoot":"","sources":["../src/k8s.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grafana",
3
- "version": "0.105.0",
3
+ "version": "0.106.1",
4
4
  "chantVersion": ">=0.1.0",
5
5
  "namespace": "Grafana",
6
6
  "intrinsics": [],
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAmD9D;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,EAAE,aA6I3B,CAAC"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAoD9D;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,EAAE,aA6I3B,CAAC"}
@@ -25,7 +25,7 @@ docker run -p 3000:3000 \
25
25
 
26
26
  ## Kubernetes
27
27
 
28
- Use `GrafanaConfigMaps({ entities, namespace })` from `@intentius/chant-lexicon-grafana/k8s` in a k8s build root. It writes one ConfigMap per dashboard labelled `grafana_dashboard: "1"`, with the folder in the `k8s-sidecar-target-directory` annotation. The datasource file goes in a ConfigMap labelled `grafana_datasource: "1"`, and the provider file in a third. The Grafana Helm chart's sidecar picks these up as they are. For a Grafana without the sidecar, spread `grafanaVolumes({ entities })`'s `volumes` and `volumeMounts` into the Deployment: provisioning goes under `/etc/grafana/provisioning`, and each dashboard folder gets a projected volume of its own. For a Grafana the Grafana Operator runs, use `GrafanaOperatorResources({ entities, namespace, instanceSelector })` instead: a `GrafanaFolder` per folder, a `GrafanaLibraryPanel` per library panel the dashboards place, a `GrafanaDashboard` per dashboard (JSON in `spec.json`, folder by `folderRef`) and a `GrafanaDatasource` per datasource. `instanceSelector` is required and must match the labels on your `Grafana` resource. A datasource secret written `${NAME}` or `$__env{NAME}` needs `secretName`, the Secret holding key `NAME`. `grafanaFiles(entities)` still returns every file by path for any other layout.
28
+ Use `GrafanaConfigMaps({ entities, namespace })` from `@intentius/chant-lexicon-grafana/k8s` in a k8s build root. It writes one ConfigMap per dashboard labelled `grafana_dashboard: "1"`, with the folder in the `k8s-sidecar-target-directory` annotation. The datasource file goes in a ConfigMap labelled `grafana_datasource: "1"`, and the provider file in a third. The Grafana Helm chart's sidecar picks these up as they are. For a Grafana without the sidecar, spread `grafanaVolumes({ entities })`'s `volumes` and `volumeMounts` into the Deployment: provisioning goes under `/etc/grafana/provisioning`, and each dashboard folder gets a projected volume of its own. For a Grafana the Grafana Operator runs, use `GrafanaOperatorResources({ entities, namespace, instanceSelector })` instead: a `GrafanaFolder` per folder, a `GrafanaLibraryPanel` per library panel the dashboards place, a `GrafanaDashboard` per dashboard (JSON in `spec.json`, folder by `folderRef`) a `GrafanaDatasource` per datasource, and for alerting a `GrafanaAlertRuleGroup` per rule group, a `GrafanaContactPoint` per contact point, a `GrafanaNotificationPolicy`, a `GrafanaMuteTiming` per mute timing and a `GrafanaNotificationTemplate` per template. `instanceSelector` is required and must match the labels on your `Grafana` resource. A datasource secret, or a contact point setting that is wholly a variable, written `${NAME}` or `$__env{NAME}` needs `secretName`, the Secret holding key `NAME`; contact point secrets go in each receiver's `valuesFrom`. `grafanaFiles(entities)` still returns every file by path for any other layout.
29
29
 
30
30
  ## Changing where dashboards live
31
31
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-grafana",
3
- "version": "0.105.0",
3
+ "version": "0.106.1",
4
4
  "description": "Grafana lexicon for chant — typed dashboards, panels, queries and datasources, serialized to dashboard JSON and Grafana provisioning files",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
@@ -83,7 +83,7 @@
83
83
  "zod": "^4.3.6"
84
84
  },
85
85
  "peerDependencies": {
86
- "@intentius/chant": "^0.105.0",
86
+ "@intentius/chant": "^0.106.1",
87
87
  "typescript": "^5.9.3",
88
88
  "zod": "^4.3.6"
89
89
  }
package/src/detect.ts CHANGED
@@ -88,6 +88,26 @@ export function looksLikeAlertingProvisioning(data: unknown): data is Record<str
88
88
  return data.apiVersion !== undefined || Array.isArray(data.groups);
89
89
  }
90
90
 
91
+ /**
92
+ * The `spec` of a Grafana Operator alerting resource (`GrafanaAlertRuleGroup`,
93
+ * `GrafanaContactPoint`, `GrafanaNotificationPolicy`, `GrafanaMuteTiming`,
94
+ * `GrafanaNotificationTemplate`) as the k8s importer offers it (#3538): every
95
+ * operator resource's spec has an `instanceSelector`, beside the field that
96
+ * holds the alerting content.
97
+ */
98
+ export function looksLikeOperatorAlertingSpec(data: unknown): boolean {
99
+ if (typeof data !== "object" || data === null || Array.isArray(data)) return false;
100
+ const d = data as Record<string, unknown>;
101
+ if (typeof d.instanceSelector !== "object" || d.instanceSelector === null) return false;
102
+ return (
103
+ (typeof d.name === "string" && Array.isArray(d.rules)) ||
104
+ (typeof d.name === "string" && Array.isArray(d.receivers)) ||
105
+ (typeof d.route === "object" && d.route !== null) ||
106
+ (typeof d.name === "string" && Array.isArray(d.time_intervals)) ||
107
+ (typeof d.name === "string" && typeof d.template === "string")
108
+ );
109
+ }
110
+
91
111
  /**
92
112
  * Template detection for `chant import` and friends: dashboard JSON in any
93
113
  * of the shapes above (v2 included, #2947), or a datasource, dashboard or
@@ -95,6 +115,7 @@ export function looksLikeAlertingProvisioning(data: unknown): data is Record<str
95
115
  */
96
116
  export function detectTemplate(data: unknown): boolean {
97
117
  return (
118
+ looksLikeOperatorAlertingSpec(data) ||
98
119
  looksLikeDashboard(data) ||
99
120
  looksLikeLegacyRowsDashboard(data) ||
100
121
  looksLikeDashboardResource(data) ||
@@ -2,6 +2,7 @@ import { describe, expect, test } from "vitest";
2
2
  import { embeddedDocument, type EmbeddedContent } from "@intentius/chant/import/embedded";
3
3
  import { grafanaPlugin } from "../plugin";
4
4
  import { dashboardImporter } from "./embedded";
5
+ import { operatorImporter } from "./operator";
5
6
 
6
7
  const DASHBOARD = JSON.stringify({
7
8
  title: "API",
@@ -22,7 +23,7 @@ const site = (text: string): EmbeddedContent => ({
22
23
 
23
24
  describe("dashboard JSON embedded in another lexicon's resource (#2962)", () => {
24
25
  test("the plugin registers the importer", () => {
25
- expect(grafanaPlugin.embeddedImporters?.()).toEqual([dashboardImporter]);
26
+ expect(grafanaPlugin.embeddedImporters?.()).toEqual([dashboardImporter, operatorImporter]);
26
27
  });
27
28
 
28
29
  test("matches classic and v2 dashboard JSON; not other text", () => {
@@ -54,4 +55,11 @@ describe("dashboard JSON embedded in another lexicon's resource (#2962)", () =>
54
55
  const file = out.files.find((f) => f.path === binding.from);
55
56
  expect(file?.content).toContain(`const ${binding.name} = new Dashboard(`);
56
57
  });
58
+
59
+ test("panels and queries are written inline, as `chant import dashboard.json` writes them (#3184)", () => {
60
+ const out = dashboardImporter.import(site(DASHBOARD));
61
+ const text = out.files.map((f) => f.content).join("\n");
62
+ expect(text).toContain("new StatPanel(");
63
+ expect(text).not.toMatch(/const panel\d* = new StatPanel/);
64
+ });
57
65
  });
@@ -45,12 +45,11 @@ export const dashboardImporter: EmbeddedContentImporter = {
45
45
  const resource = ir.resources.find((r) => r.type === DASHBOARD_RESOURCE_TYPE);
46
46
  if (!resource) throw new Error((ir.warnings ?? []).join(" ") || "no dashboard was read");
47
47
  const { plan } = resource.properties as unknown as PlanResourceProperties;
48
- // Core gives the content a directory of its own; the plan's would nest a second one inside it. The host's
49
- // project need not list grafana (a k8s project holding a dashboard ConfigMap), and then core lint does not know
50
- // grafana's panels and queries are property-kind, so they keep the flat shape that lints clean without it: a
51
- // const per declaration, nested values lifted, eight to a module (#2988).
52
- const flat = { ...plan, directory: "", main: undefined, declarations: plan.declarations.map(({ property: _, ...d }) => d) };
53
- const { files, exported } = generatePlanModules(flat);
48
+ // Core gives the content a directory of its own; the plan's would nest a second one inside it. Panels and
49
+ // queries stay inline: core lint takes property-kind classes from every lexicon the source imports (#3184), so
50
+ // the host's project need not list grafana.
51
+ const nested = { ...plan, directory: "", main: undefined };
52
+ const { files, exported } = generatePlanModules(nested);
54
53
  const dashboard = exported.get("dashboard");
55
54
  if (!dashboard) throw new Error("the import declared no dashboard");
56
55
  return {
@@ -0,0 +1,213 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import type { EmbeddedContent } from "@intentius/chant/import/embedded";
3
+ import { grafanaPlugin } from "../plugin";
4
+ import { operatorContactPoint, operatorPolicyRoute, operatorRule } from "../k8s";
5
+ import { operatorImporter, provisionedReceivers, provisionedRoute, provisionedRule } from "./operator";
6
+
7
+ const site = (hostType: string, select: string, spec: Record<string, unknown>): EmbeddedContent => ({
8
+ host: "k8s",
9
+ hostType,
10
+ location: `${hostType.split("::").pop()} x spec.${select}`,
11
+ directory: "x",
12
+ document: spec,
13
+ select,
14
+ labels: {},
15
+ });
16
+
17
+ const SELECTOR = { matchLabels: { dashboards: "grafana" } };
18
+
19
+ describe("Grafana Operator alerting embedded in a k8s manifest (#3538)", () => {
20
+ test("the plugin registers the importer", () => {
21
+ expect(grafanaPlugin.embeddedImporters?.()).toContain(operatorImporter);
22
+ });
23
+
24
+ test("matches the five kinds at their own field, and nothing else", () => {
25
+ expect(operatorImporter.matches(site("K8s::Grafana::GrafanaAlertRuleGroup", "rules", { rules: [] }))).toBe(true);
26
+ expect(operatorImporter.matches(site("K8s::Grafana::GrafanaContactPoint", "receivers", { receivers: [] }))).toBe(true);
27
+ expect(operatorImporter.matches(site("K8s::Grafana::GrafanaNotificationPolicy", "route", { route: {} }))).toBe(true);
28
+ expect(operatorImporter.matches(site("K8s::Grafana::GrafanaMuteTiming", "time_intervals", { time_intervals: [] }))).toBe(true);
29
+ expect(operatorImporter.matches(site("K8s::Grafana::GrafanaNotificationTemplate", "template", { template: "x" }))).toBe(true);
30
+ expect(operatorImporter.matches(site("K8s::Grafana::GrafanaAlertRuleGroup", "receivers", {}))).toBe(false);
31
+ expect(operatorImporter.matches(site("K8s::Core::ConfigMap", "rules", { rules: [] }))).toBe(false);
32
+ });
33
+
34
+ describe("a rule", () => {
35
+ test("goes back to the file's spelling, without the values the CRD requires and Grafana defaults", () => {
36
+ expect(
37
+ provisionedRule({
38
+ uid: "r",
39
+ title: "R",
40
+ condition: "A",
41
+ data: [],
42
+ for: "0s",
43
+ noDataState: "NoData",
44
+ execErrState: "Alerting",
45
+ missingSeriesEvalsToResolve: 3,
46
+ notificationSettings: { receiver: "oncall", group_by: ["a"] },
47
+ annotations: { __dashboardUid__: "dash", __panelId__: "2", summary: "s" },
48
+ }),
49
+ ).toEqual({
50
+ uid: "r",
51
+ title: "R",
52
+ condition: "A",
53
+ data: [],
54
+ missing_series_evals_to_resolve: 3,
55
+ notification_settings: { receiver: "oncall", group_by: ["a"] },
56
+ dashboardUid: "dash",
57
+ panelId: 2,
58
+ annotations: { summary: "s" },
59
+ });
60
+ });
61
+
62
+ test("keeps values that are not the defaults, and a record rule's condition is the rule's own", () => {
63
+ const back = provisionedRule({ uid: "r", title: "R", condition: "B", data: [], for: "5m", noDataState: "Alerting", execErrState: "Error" });
64
+ expect(back).toMatchObject({ for: "5m", noDataState: "Alerting", execErrState: "Error", condition: "B" });
65
+ expect(provisionedRule({ uid: "r", title: "R", condition: "A", data: [], record: { metric: "m", from: "A" } })).not.toHaveProperty("condition");
66
+ });
67
+
68
+ test("is the inverse of operatorRule", () => {
69
+ const rule = {
70
+ uid: "r",
71
+ title: "R",
72
+ condition: "A",
73
+ data: [],
74
+ for: "5m",
75
+ labels: { severity: "page" },
76
+ dashboardUid: "dash",
77
+ panelId: 4,
78
+ annotations: { summary: "s" },
79
+ missing_series_evals_to_resolve: 2,
80
+ notification_settings: { receiver: "oncall" },
81
+ };
82
+ expect(provisionedRule(operatorRule(rule))).toEqual(rule);
83
+ });
84
+ });
85
+
86
+ describe("a receiver", () => {
87
+ test("reads each valuesFrom entry back as ${NAME} at its path, and names the Secret", () => {
88
+ const forward = operatorContactPoint(
89
+ { name: "oncall", receivers: [{ uid: "u", type: "slack", settings: { title: "t", url: "${SLACK_URL}", tlsConfig: { clientKey: "$KEY" } } }] },
90
+ "grafana-secrets",
91
+ );
92
+ const back = provisionedReceivers(forward.receivers as unknown[], "contact point");
93
+ expect(back.secretName).toBe("grafana-secrets");
94
+ expect(back.receivers).toEqual([{ uid: "u", type: "slack", settings: { title: "t", url: "${SLACK_URL}", tlsConfig: { clientKey: "${KEY}" } } }]);
95
+ });
96
+
97
+ test("without valuesFrom there is no Secret", () => {
98
+ expect(provisionedReceivers([{ uid: "m", type: "email", settings: { addresses: "a@example.com" } }], "cp")).toEqual({
99
+ receivers: [{ uid: "m", type: "email", settings: { addresses: "a@example.com" } }],
100
+ });
101
+ });
102
+
103
+ test("refuses what a ${NAME} reference cannot say", () => {
104
+ const from = (valueFrom: unknown, name = "s") => [{ type: "x", settings: {}, valuesFrom: [{ targetPath: "url", valueFrom }, { targetPath: "b", valueFrom: { secretKeyRef: { name, key: "B" } } }] }];
105
+ expect(() => provisionedReceivers(from({ configMapKeyRef: { name: "c", key: "K" } }), "cp")).toThrow(/secretKeyRef/);
106
+ expect(() => provisionedReceivers(from({ secretKeyRef: { name: "s", key: "not-a-name" } }), "cp")).toThrow(/secretKeyRef/);
107
+ expect(() => provisionedReceivers(from({ secretKeyRef: { name: "one", key: "A" } }, "two"), "cp")).toThrow(/one secretName/);
108
+ });
109
+ });
110
+
111
+ describe("the policy tree", () => {
112
+ test("object_matchers go back to matchers in every nested route", () => {
113
+ const route = operatorPolicyRoute({
114
+ receiver: "oncall",
115
+ routes: [{ receiver: "tickets", matchers: ['severity="ticket"', "team=~a|b"], routes: [{ matchers: ["env!=dev"] }] }],
116
+ });
117
+ expect(provisionedRoute(route)).toEqual({
118
+ receiver: "oncall",
119
+ routes: [{ receiver: "tickets", matchers: ['severity="ticket"', 'team=~"a|b"'], routes: [{ matchers: ['env!="dev"'] }] }],
120
+ });
121
+ });
122
+
123
+ test("a matcher that cannot be written as a string keeps them all as objects", () => {
124
+ const objects = [
125
+ ["a", "=", "x"],
126
+ ["b", "=", 'say "hi"'],
127
+ ];
128
+ expect(provisionedRoute({ receiver: "r", object_matchers: objects })).toEqual({ receiver: "r", object_matchers: objects });
129
+ });
130
+ });
131
+
132
+ describe("import", () => {
133
+ test("a rule group becomes an AlertRuleGroup, read through operatorRules, with a warning about its folder", () => {
134
+ const spec = {
135
+ instanceSelector: SELECTOR,
136
+ name: "errors",
137
+ folderRef: "grafana-folder-alerts",
138
+ interval: "30s",
139
+ rules: [
140
+ {
141
+ uid: "high-errors",
142
+ title: "High errors",
143
+ condition: "A",
144
+ for: "5m",
145
+ noDataState: "NoData",
146
+ execErrState: "Alerting",
147
+ data: [{ refId: "A", relativeTimeRange: { from: 600, to: 0 }, datasourceUid: "prom", model: { refId: "A", expr: "up", datasource: { type: "prometheus", uid: "prom" } } }],
148
+ },
149
+ ],
150
+ };
151
+ const out = operatorImporter.import(site("K8s::Grafana::GrafanaAlertRuleGroup", "rules", spec));
152
+ expect(out.value.shape).toBe("single");
153
+ expect(out.value.through).toEqual({ from: "@intentius/chant-lexicon-grafana/k8s", name: "operatorRules" });
154
+ const [binding] = out.value.bindings;
155
+ expect(out.files.find((f) => f.path === binding.from)?.content).toContain(`const ${binding.name} = new AlertRuleGroup(`);
156
+ const text = out.files.map((f) => f.content).join("\n");
157
+ expect(text).toContain('folder: "grafana-folder-alerts"');
158
+ expect(text).toContain("new AlertRule(");
159
+ expect(out.warnings?.join("\n")).toContain('rule group "errors": a declaration\'s folder is a title');
160
+ });
161
+
162
+ test("a contact point that reads a Secret gets a module naming it", () => {
163
+ const spec = {
164
+ instanceSelector: SELECTOR,
165
+ name: "oncall",
166
+ receivers: [
167
+ {
168
+ uid: "oncall-slack",
169
+ type: "slack",
170
+ settings: { title: "chant" },
171
+ valuesFrom: [{ targetPath: "url", valueFrom: { secretKeyRef: { name: "grafana-secrets", key: "SLACK_URL" } } }],
172
+ },
173
+ ],
174
+ };
175
+ const out = operatorImporter.import(site("K8s::Grafana::GrafanaContactPoint", "receivers", spec));
176
+ expect(out.value.through).toBeUndefined();
177
+ const [binding] = out.value.bindings;
178
+ expect(binding).toEqual({ from: "receivers.ts", name: "receivers" });
179
+ const module = out.files.find((f) => f.path === "receivers.ts")!.content;
180
+ expect(module).toContain('import { operatorReceivers } from "@intentius/chant-lexicon-grafana/k8s";');
181
+ expect(module).toContain('"grafana-secrets"');
182
+ expect(out.files.map((f) => f.content).join("\n")).toContain("${SLACK_URL}");
183
+ });
184
+
185
+ test("a contact point with no Secret is read through operatorReceivers alone", () => {
186
+ const spec = { name: "mail", receivers: [{ uid: "m", type: "email", settings: { addresses: "a@example.com" } }] };
187
+ const out = operatorImporter.import(site("K8s::Grafana::GrafanaContactPoint", "receivers", spec));
188
+ expect(out.value.through).toEqual({ from: "@intentius/chant-lexicon-grafana/k8s", name: "operatorReceivers" });
189
+ expect(out.files.some((f) => f.path === "receivers.ts")).toBe(false);
190
+ });
191
+
192
+ test("a policy, a mute timing and a template", () => {
193
+ const policy = operatorImporter.import(site("K8s::Grafana::GrafanaNotificationPolicy", "route", { route: { receiver: "oncall", routes: [{ object_matchers: [["severity", "=", "page"]] }] } }));
194
+ expect(policy.value.through?.name).toBe("operatorPolicy");
195
+ const text = policy.files.map((f) => f.content).join("\n");
196
+ expect(text).toContain("new NotificationPolicy(");
197
+ expect(text).toContain('severity="page"');
198
+
199
+ const mute = operatorImporter.import(site("K8s::Grafana::GrafanaMuteTiming", "time_intervals", { name: "weekends", time_intervals: [{ weekdays: ["saturday", "sunday"] }] }));
200
+ expect(mute.value.through?.name).toBe("operatorTimeIntervals");
201
+ expect(mute.files.map((f) => f.content).join("\n")).toContain("new MuteTiming(");
202
+
203
+ const template = operatorImporter.import(site("K8s::Grafana::GrafanaNotificationTemplate", "template", { name: "t", template: '{{ define "t" }}x{{ end }}' }));
204
+ expect(template.value.through?.name).toBe("operatorTemplate");
205
+ expect(template.files.map((f) => f.content).join("\n")).toContain("new NotificationTemplate(");
206
+ });
207
+
208
+ test("a policy with no receiver, and a group with no folder, throw so the host keeps them as written", () => {
209
+ expect(() => operatorImporter.import(site("K8s::Grafana::GrafanaNotificationPolicy", "route", { route: { group_by: ["a"] } }))).toThrow();
210
+ expect(() => operatorImporter.import(site("K8s::Grafana::GrafanaAlertRuleGroup", "rules", { name: "g", rules: [] }))).toThrow(/names no folder/);
211
+ });
212
+ });
213
+ });