@intentius/chant-lexicon-grafana 0.124.31 → 0.124.33

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.
Files changed (166) hide show
  1. package/dist/actions/grafana.js +36 -0
  2. package/dist/actions/index.js +1 -0
  3. package/dist/alerting-build.js +262 -0
  4. package/dist/alerting.js +125 -0
  5. package/dist/annotations.js +47 -0
  6. package/dist/api/apply.js +320 -0
  7. package/dist/api/bind.js +44 -0
  8. package/dist/api/client.js +113 -0
  9. package/dist/api/converge.js +61 -0
  10. package/dist/api/dashboards.js +210 -0
  11. package/dist/api/datasources.js +24 -0
  12. package/dist/api/fake-grafana-writes.js +185 -0
  13. package/dist/api/fake-grafana.js +161 -0
  14. package/dist/api/folders.js +290 -0
  15. package/dist/api/library-panels.js +82 -0
  16. package/dist/build.js +767 -0
  17. package/dist/catalog.js +198 -0
  18. package/dist/codegen/docs-cli.js +3 -0
  19. package/dist/codegen/docs.js +130 -0
  20. package/dist/codegen/generate-cli.js +7 -0
  21. package/dist/codegen/generate.js +97 -0
  22. package/dist/codegen/package.js +28 -0
  23. package/dist/codegen/schema-types.js +159 -0
  24. package/dist/composites/agent-dashboard.js +460 -0
  25. package/dist/composites/catalog.js +306 -0
  26. package/dist/composites/index.js +8 -0
  27. package/dist/composites/red-dashboard.js +135 -0
  28. package/dist/composites/shared.js +74 -0
  29. package/dist/composites/slo-alert-rules.js +96 -0
  30. package/dist/composites/slo-dashboard.js +153 -0
  31. package/dist/config.js +110 -0
  32. package/dist/contact-point-check.js +61 -0
  33. package/dist/contact-point-secrets.js +32 -0
  34. package/dist/contact-point-settings.gen.js +421 -0
  35. package/dist/dashboard.js +60 -0
  36. package/dist/datasource-refs.js +170 -0
  37. package/dist/datasource-settings.js +57 -0
  38. package/dist/datasource.js +68 -0
  39. package/dist/deep-observe-hooks.js +266 -0
  40. package/dist/deep-observe.js +171 -0
  41. package/dist/describe-resources.js +249 -0
  42. package/dist/detect.js +119 -0
  43. package/dist/export-resources.js +73 -0
  44. package/dist/folder.js +86 -0
  45. package/dist/import/alerting.js +514 -0
  46. package/dist/import/edits.js +90 -0
  47. package/dist/import/embedded.js +60 -0
  48. package/dist/import/generator.js +656 -0
  49. package/dist/import/live-export.js +246 -0
  50. package/dist/import/mappings.js +317 -0
  51. package/dist/import/model.js +23 -0
  52. package/dist/import/normalize-alerting.js +106 -0
  53. package/dist/import/normalize.js +426 -0
  54. package/dist/import/operator.js +252 -0
  55. package/dist/import/parser.js +1480 -0
  56. package/dist/import/report.js +58 -0
  57. package/dist/import/testdata/fixtures.js +122 -0
  58. package/dist/import/v2.js +814 -0
  59. package/dist/index.js +59 -0
  60. package/dist/init-templates.js +305 -0
  61. package/dist/integrity.json +2 -2
  62. package/dist/k8s.js +548 -0
  63. package/dist/library-panel.js +83 -0
  64. package/dist/lint/audit-catalog.js +40 -0
  65. package/dist/lint/audit-lineage.js +9 -0
  66. package/dist/lint/post-synth/graf101.js +13 -0
  67. package/dist/lint/post-synth/graf102.js +13 -0
  68. package/dist/lint/post-synth/graf103.js +13 -0
  69. package/dist/lint/post-synth/graf104.js +13 -0
  70. package/dist/lint/post-synth/graf105.js +13 -0
  71. package/dist/lint/post-synth/graf106.js +13 -0
  72. package/dist/lint/post-synth/graf107.js +13 -0
  73. package/dist/lint/post-synth/graf108.js +13 -0
  74. package/dist/lint/post-synth/graf109.js +13 -0
  75. package/dist/lint/post-synth/graf110.js +13 -0
  76. package/dist/lint/post-synth/graf111.js +13 -0
  77. package/dist/lint/post-synth/graf112.js +13 -0
  78. package/dist/lint/post-synth/graf113.js +13 -0
  79. package/dist/lint/post-synth/graf114.js +13 -0
  80. package/dist/lint/post-synth/graf115.js +13 -0
  81. package/dist/lint/post-synth/graf116.js +13 -0
  82. package/dist/lint/post-synth/graf117.js +13 -0
  83. package/dist/lint/post-synth/graf118.js +37 -0
  84. package/dist/lint/post-synth/grafana-helpers.js +89 -0
  85. package/dist/lint/post-synth/index.js +38 -0
  86. package/dist/lint/rules/grafana-ast.js +70 -0
  87. package/dist/lint/rules/index.js +6 -0
  88. package/dist/lint/rules/literal-secret.js +109 -0
  89. package/dist/lint/rules/uid-syntax.js +53 -0
  90. package/dist/lsp/completions.js +11 -0
  91. package/dist/lsp/hover.js +29 -0
  92. package/dist/manifest.json +1 -1
  93. package/dist/op/activities/grafana-apply.js +147 -0
  94. package/dist/op/activities/index.js +7 -0
  95. package/dist/op/builders.js +19 -0
  96. package/dist/ownership.js +102 -0
  97. package/dist/package-cli.js +14 -0
  98. package/dist/panel-options.js +12 -0
  99. package/dist/panels.js +296 -0
  100. package/dist/pin.js +113 -0
  101. package/dist/plugin.js +172 -0
  102. package/dist/promql-check.js +176 -0
  103. package/dist/query-models.js +13 -0
  104. package/dist/query-syntax.js +129 -0
  105. package/dist/query.js +227 -0
  106. package/dist/schema/annotationslist.gen.js +5 -0
  107. package/dist/schema/azuremonitor.gen.js +5 -0
  108. package/dist/schema/barchart.gen.js +5 -0
  109. package/dist/schema/bargauge.gen.js +5 -0
  110. package/dist/schema/bigquery.gen.js +5 -0
  111. package/dist/schema/candlestick.gen.js +5 -0
  112. package/dist/schema/canvas.gen.js +5 -0
  113. package/dist/schema/cloudwatch.gen.js +5 -0
  114. package/dist/schema/dashboard.gen.js +6 -0
  115. package/dist/schema/dashboardlist.gen.js +5 -0
  116. package/dist/schema/datagrid.gen.js +5 -0
  117. package/dist/schema/elasticsearch.gen.js +5 -0
  118. package/dist/schema/expr.gen.js +5 -0
  119. package/dist/schema/gauge.gen.js +5 -0
  120. package/dist/schema/geomap.gen.js +5 -0
  121. package/dist/schema/googlecloudmonitoring.gen.js +5 -0
  122. package/dist/schema/grafanapyroscope.gen.js +5 -0
  123. package/dist/schema/heatmap.gen.js +5 -0
  124. package/dist/schema/histogram.gen.js +5 -0
  125. package/dist/schema/index.js +40 -0
  126. package/dist/schema/logs.gen.js +5 -0
  127. package/dist/schema/loki.gen.js +5 -0
  128. package/dist/schema/news.gen.js +5 -0
  129. package/dist/schema/nodegraph.gen.js +5 -0
  130. package/dist/schema/piechart.gen.js +5 -0
  131. package/dist/schema/prometheus.gen.js +5 -0
  132. package/dist/schema/stat.gen.js +5 -0
  133. package/dist/schema/statetimeline.gen.js +5 -0
  134. package/dist/schema/statushistory.gen.js +5 -0
  135. package/dist/schema/table.gen.js +5 -0
  136. package/dist/schema/tempo.gen.js +5 -0
  137. package/dist/schema/text.gen.js +5 -0
  138. package/dist/schema/timeseries.gen.js +5 -0
  139. package/dist/schema/trend.gen.js +5 -0
  140. package/dist/schema/xychart.gen.js +5 -0
  141. package/dist/schema-validate.js +265 -0
  142. package/dist/serializer.js +40 -0
  143. package/dist/skill-defs.js +60 -0
  144. package/dist/spec/fetch-cli.js +26 -0
  145. package/dist/spec/fetch-notifiers-cli.js +53 -0
  146. package/dist/spec/fetch-units-cli.js +40 -0
  147. package/dist/spec/notifiers.js +126 -0
  148. package/dist/spec/overlay.js +92 -0
  149. package/dist/spec/schemas.gen.js +42 -0
  150. package/dist/spec/schemas.js +62 -0
  151. package/dist/spec/units.gen.js +280 -0
  152. package/dist/spec/units.js +72 -0
  153. package/dist/transformations.js +127 -0
  154. package/dist/util.js +34 -0
  155. package/dist/validate-alerting.js +444 -0
  156. package/dist/validate-cli.js +7 -0
  157. package/dist/validate-output.js +635 -0
  158. package/dist/validate.js +122 -0
  159. package/dist/validation.js +13 -0
  160. package/dist/variables.js +70 -0
  161. package/package.json +10 -10
  162. package/src/codegen/package.ts +2 -2
  163. package/src/spec/fetch-notifiers-cli.ts +2 -2
  164. package/src/spec/fetch-units-cli.ts +2 -2
  165. package/src/spec/overlay.ts +2 -2
  166. package/src/spec/schemas.ts +2 -2
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The Grafana RBAC actions chant's live paths call, for a custom role on the
3
+ * service account whose token a `grafana.profiles.<env>` entry names. The
4
+ * basic Editor role covers all of them; these are for a narrower role.
5
+ *
6
+ * - `Observe`: `chant lifecycle diff --live` and `chant import --from`
7
+ * (#2946), which read dashboards, folders and datasources.
8
+ * - `Apply`: the API applier (#2948), which also reads what it writes to
9
+ * compare, and writes folders, library panels and dashboards.
10
+ * - `Prune`: the applier with `prune`, which deletes dashboards and folders
11
+ * (never library panels or datasources).
12
+ *
13
+ * Grant them on `folders:*` and `dashboards:*` scopes, or on the folders
14
+ * chant writes to.
15
+ */
16
+ export const GrafanaActions = {
17
+ Observe: ["dashboards:read", "folders:read", "datasources:read"],
18
+ Apply: [
19
+ "dashboards:read",
20
+ "dashboards:create",
21
+ "dashboards:write",
22
+ "folders:read",
23
+ "folders:create",
24
+ "folders:write",
25
+ "library.panels:read",
26
+ "library.panels:create",
27
+ "library.panels:write",
28
+ ],
29
+ Prune: ["dashboards:read", "dashboards:delete", "folders:read", "folders:delete"],
30
+ };
31
+ // A Map-backed lookup, so evaluable code avoids computed element access (EVL003).
32
+ const GRAFANA_ACTIONS = new Map(Object.entries(GrafanaActions));
33
+ /** The actions for one or more access levels, deduplicated, in first-seen order. */
34
+ export function grafanaActionsFor(...levels) {
35
+ return [...new Set(levels.flatMap((l) => GRAFANA_ACTIONS.get(l) ?? []))];
36
+ }
@@ -0,0 +1 @@
1
+ export { GrafanaActions, grafanaActionsFor } from "./grafana.js";
@@ -0,0 +1,262 @@
1
+ /**
2
+ * From alerting entities to Grafana's alerting provisioning file,
3
+ * `provisioning/alerting/chant.yaml`.
4
+ *
5
+ * The file is what Grafana's file provisioner reads
6
+ * (`pkg/services/provisioning/alerting`, the same at v12.4.11 and v13.2.2)
7
+ * and what `GET /api/v1/provisioning/*\/export` writes: `apiVersion: 1`
8
+ * and the lists `groups`, `contactPoints`, `policies`, `muteTimes` and
9
+ * `templates`. Mount `provisioning/` at `/etc/grafana/provisioning`, as for
10
+ * datasources and dashboards.
11
+ *
12
+ * Plain functions, so a test or another lexicon can render the same file
13
+ * with `alertingYaml()` without a build.
14
+ */
15
+ import { dump } from "js-yaml";
16
+ import { durationMs, isValidDuration } from "@intentius/chant-lexicon-prometheus/duration";
17
+ import { EXPRESSION_DATASOURCE_UID, isAlertQueryEntity, isAlertRuleGroupEntity, isContactPointEntity, isExpressionEntity, isMuteTimingEntity, isNotificationPolicyEntity, isNotificationTemplateEntity, } from "./alerting.js";
18
+ import { isDashboardEntity } from "./dashboard.js";
19
+ import { isQueryEntity } from "./query.js";
20
+ import { isDatasourceVariable } from "./variables.js";
21
+ import { compact, slugUid } from "./util.js";
22
+ // build.ts imports this module too; only functions cross the cycle, and only at call time.
23
+ import { datasourceRef, targetJson } from "./build.js";
24
+ /** Where the alerting provisioning file goes, relative to the build output directory. */
25
+ export const ALERTING_FILE = "provisioning/alerting/chant.yaml";
26
+ /** The time range a query reads when neither it nor its rule sets one: the last 10 minutes, Grafana's editor default. */
27
+ export const DEFAULT_RELATIVE_TIME_RANGE = { from: 600, to: 0 };
28
+ /** A rule group's evaluation interval when it sets none. */
29
+ export const DEFAULT_GROUP_INTERVAL = "1m";
30
+ /** The keys an alerting provisioning file can hold, from `AlertingFileV1`. */
31
+ export const ALERTING_FILE_KEYS = [
32
+ "apiVersion",
33
+ "groups",
34
+ "deleteRules",
35
+ "contactPoints",
36
+ "deleteContactPoints",
37
+ "policies",
38
+ "resetPolicies",
39
+ "muteTimes",
40
+ "deleteMuteTimes",
41
+ "templates",
42
+ "deleteTemplates",
43
+ ];
44
+ // ── Durations ───────────────────────────────────────────────────
45
+ /** A duration as whole seconds: a number is taken as seconds, a string as a Prometheus duration. */
46
+ export function durationSeconds(value, what) {
47
+ if (typeof value === "number") {
48
+ if (!Number.isInteger(value) || value < 0)
49
+ throw new Error(`grafana: ${what} must be a whole number of seconds, got ${value}`);
50
+ return value;
51
+ }
52
+ if (!isValidDuration(value))
53
+ throw new Error(`grafana: ${what} "${value}" is not a duration like 10m or 1h`);
54
+ return Math.floor(durationMs(value) / 1000);
55
+ }
56
+ function timeRange(range, what) {
57
+ return { from: durationSeconds(range.from, `${what} from`), to: durationSeconds(range.to ?? 0, `${what} to`) };
58
+ }
59
+ // ── References ──────────────────────────────────────────────────
60
+ function receiverName(receiver) {
61
+ return typeof receiver === "string" ? receiver : receiver.props.name;
62
+ }
63
+ function timingNames(list) {
64
+ return list?.map((t) => (typeof t === "string" ? t : t.props.name));
65
+ }
66
+ // ── Rules ───────────────────────────────────────────────────────
67
+ const REF_IDS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
68
+ function refIdAt(i) {
69
+ return i < REF_IDS.length ? REF_IDS[i] : `${REF_IDS[Math.floor(i / REF_IDS.length) - 1]}${REF_IDS[i % REF_IDS.length]}`;
70
+ }
71
+ /** The uid a query's datasource resolves to, and its type when known. */
72
+ function datasourceOf(input, where) {
73
+ if (input === undefined || input === null)
74
+ throw new Error(`grafana: ${where} has no datasource; an alert query must name one`);
75
+ if (typeof input === "string")
76
+ return { uid: input };
77
+ if (isDatasourceVariable(input)) {
78
+ throw new Error(`grafana: ${where} uses a DatasourceVariable; alert rules are evaluated without dashboard variables, so name the datasource itself`);
79
+ }
80
+ const ref = datasourceRef(input);
81
+ return { uid: ref.uid, ...(ref.type ? { type: ref.type } : {}) };
82
+ }
83
+ function typedQueryModel(query, refId, where) {
84
+ const ds = datasourceOf(query.props.datasource, where);
85
+ const model = targetJson(query, 0, ds.type ? { type: ds.type, uid: ds.uid } : undefined);
86
+ return { uid: ds.uid, model: { ...model, refId } };
87
+ }
88
+ /** One entry of `data`, as the file holds it. */
89
+ export function alertQueryJson(item, index, ruleRange, rule) {
90
+ const where = `alert rule "${rule}" data[${index}]`;
91
+ const fallbackRange = ruleRange ? timeRange(ruleRange, `${where} relativeTimeRange`) : { ...DEFAULT_RELATIVE_TIME_RANGE };
92
+ if (isExpressionEntity(item)) {
93
+ const { refId: ownRefId, relativeTimeRange, ...rest } = item.props;
94
+ const refId = ownRefId ?? refIdAt(index);
95
+ return compact({
96
+ refId,
97
+ relativeTimeRange: relativeTimeRange ? timeRange(relativeTimeRange, `${where} relativeTimeRange`) : undefined,
98
+ datasourceUid: EXPRESSION_DATASOURCE_UID,
99
+ model: { datasource: { type: EXPRESSION_DATASOURCE_UID, uid: EXPRESSION_DATASOURCE_UID }, refId, type: item.expressionKind, ...rest },
100
+ });
101
+ }
102
+ if (isAlertQueryEntity(item)) {
103
+ const p = item.props;
104
+ const refId = p.refId ?? refIdAt(index);
105
+ const range = p.relativeTimeRange ? timeRange(p.relativeTimeRange, `${where} relativeTimeRange`) : fallbackRange;
106
+ let uid;
107
+ let model;
108
+ if (p.query) {
109
+ const typed = typedQueryModel(p.query, refId, where);
110
+ const own = p.datasource !== undefined ? datasourceOf(p.datasource, where) : undefined;
111
+ uid = own?.uid ?? typed.uid;
112
+ model = { ...typed.model, ...(p.model ?? {}) };
113
+ }
114
+ else {
115
+ uid = datasourceOf(p.datasource, where).uid;
116
+ model = p.model ?? {};
117
+ }
118
+ return compact({ refId, queryType: p.queryType, relativeTimeRange: range, datasourceUid: uid, model });
119
+ }
120
+ if (isQueryEntity(item)) {
121
+ const refId = item.props.refId ?? refIdAt(index);
122
+ const typed = typedQueryModel(item, refId, where);
123
+ return { refId, relativeTimeRange: fallbackRange, datasourceUid: typed.uid, model: typed.model };
124
+ }
125
+ throw new Error(`grafana: ${where} must be a query (PromQuery, AlertQuery, ...) or an expression (ReduceExpression, ...)`);
126
+ }
127
+ /** One rule as the file holds it. */
128
+ export function alertRuleJson(rule) {
129
+ const p = rule.props;
130
+ const data = (p.data ?? []).map((d, i) => alertQueryJson(d, i, p.relativeTimeRange, p.title));
131
+ const condition = p.condition ?? (p.record ? undefined : data[data.length - 1]?.refId);
132
+ const dashboardUid = p.dashboardUid === undefined ? undefined : isDashboardEntity(p.dashboardUid) ? (p.dashboardUid.props.uid ?? slugUid(p.dashboardUid.props.title)) : p.dashboardUid;
133
+ const ns = p.notification_settings;
134
+ return compact({
135
+ uid: p.uid ?? slugUid(p.title),
136
+ title: p.title,
137
+ condition,
138
+ data,
139
+ dashboardUid,
140
+ panelId: p.panelId,
141
+ noDataState: p.noDataState,
142
+ execErrState: p.execErrState,
143
+ for: p.for,
144
+ keepFiringFor: p.keepFiringFor,
145
+ missing_series_evals_to_resolve: p.missing_series_evals_to_resolve,
146
+ annotations: p.annotations,
147
+ labels: p.labels,
148
+ isPaused: p.isPaused,
149
+ notification_settings: ns
150
+ ? {
151
+ receiver: receiverName(ns.receiver),
152
+ group_by: ns.group_by,
153
+ group_wait: ns.group_wait,
154
+ group_interval: ns.group_interval,
155
+ repeat_interval: ns.repeat_interval,
156
+ mute_time_intervals: timingNames(ns.mute_time_intervals),
157
+ active_time_intervals: timingNames(ns.active_time_intervals),
158
+ }
159
+ : undefined,
160
+ record: p.record ? { ...p.record } : undefined,
161
+ });
162
+ }
163
+ /** One rule group as the file holds it. */
164
+ export function ruleGroupJson(group) {
165
+ const p = group.props;
166
+ return compact({
167
+ orgId: p.orgId,
168
+ name: p.name,
169
+ folder: p.folder,
170
+ interval: p.interval ?? DEFAULT_GROUP_INTERVAL,
171
+ rules: (p.rules ?? []).map(alertRuleJson),
172
+ });
173
+ }
174
+ // ── Notifications ───────────────────────────────────────────────
175
+ /** One contact point as the file holds it. A receiver without a uid gets one from the name and the integration type. */
176
+ export function contactPointJson(cp) {
177
+ const p = cp.props;
178
+ const taken = new Set();
179
+ const receivers = (p.receivers ?? []).map((r) => {
180
+ let uid = r.uid ?? slugUid(`${p.name} ${r.type}`);
181
+ if (r.uid === undefined)
182
+ for (let n = 2; taken.has(uid); n++)
183
+ uid = slugUid(`${p.name} ${r.type} ${n}`);
184
+ taken.add(uid);
185
+ return compact({ uid, type: r.type, settings: (r.settings ?? {}), disableResolveMessage: r.disableResolveMessage });
186
+ });
187
+ return compact({ orgId: p.orgId, name: p.name, receivers });
188
+ }
189
+ function routeJson(route) {
190
+ return compact({
191
+ receiver: route.receiver === undefined ? undefined : receiverName(route.receiver),
192
+ group_by: route.group_by,
193
+ object_matchers: route.object_matchers?.map((m) => [...m]),
194
+ matchers: route.matchers,
195
+ match: route.match,
196
+ match_re: route.match_re,
197
+ continue: route.continue,
198
+ group_wait: route.group_wait,
199
+ group_interval: route.group_interval,
200
+ repeat_interval: route.repeat_interval,
201
+ mute_time_intervals: timingNames(route.mute_time_intervals),
202
+ active_time_intervals: timingNames(route.active_time_intervals),
203
+ routes: route.routes?.map(routeJson),
204
+ });
205
+ }
206
+ /** One organisation's policy tree as the file holds it. */
207
+ export function notificationPolicyJson(policy) {
208
+ const p = policy.props;
209
+ return compact({
210
+ orgId: p.orgId,
211
+ receiver: receiverName(p.receiver),
212
+ group_by: p.group_by,
213
+ group_wait: p.group_wait,
214
+ group_interval: p.group_interval,
215
+ repeat_interval: p.repeat_interval,
216
+ mute_time_intervals: timingNames(p.mute_time_intervals),
217
+ active_time_intervals: timingNames(p.active_time_intervals),
218
+ routes: p.routes?.map(routeJson),
219
+ });
220
+ }
221
+ export function muteTimingJson(t) {
222
+ return compact({ orgId: t.props.orgId, name: t.props.name, time_intervals: (t.props.time_intervals ?? []) });
223
+ }
224
+ export function notificationTemplateJson(t) {
225
+ return compact({ orgId: t.props.orgId, name: t.props.name, template: t.props.template });
226
+ }
227
+ // ── The file ────────────────────────────────────────────────────
228
+ /** The alerting provisioning file as YAML text. */
229
+ export function alertingYaml(file) {
230
+ return `# Grafana alerting provisioning, generated by chant.\n${dump(file, { lineWidth: -1, noRefs: true, quotingType: '"' })}`;
231
+ }
232
+ /** The alerting provisioning file for the alerting entities among these, or undefined when there are none. */
233
+ export function buildAlerting(entities) {
234
+ const all = [...entities];
235
+ const byName = (a, b) => a.name.localeCompare(b.name);
236
+ const groups = all
237
+ .filter(isAlertRuleGroupEntity)
238
+ .map(ruleGroupJson)
239
+ .sort((a, b) => a.folder.localeCompare(b.folder) || a.name.localeCompare(b.name));
240
+ const contactPoints = all.filter(isContactPointEntity).map(contactPointJson).sort(byName);
241
+ const policies = all.filter(isNotificationPolicyEntity).map(notificationPolicyJson);
242
+ const muteTimes = all.filter(isMuteTimingEntity).map(muteTimingJson).sort(byName);
243
+ const templates = all.filter(isNotificationTemplateEntity).map(notificationTemplateJson).sort(byName);
244
+ if (groups.length + contactPoints.length + policies.length + muteTimes.length + templates.length === 0)
245
+ return undefined;
246
+ const file = compact({
247
+ apiVersion: 1,
248
+ groups: groups.length > 0 ? groups : undefined,
249
+ contactPoints: contactPoints.length > 0 ? contactPoints : undefined,
250
+ policies: policies.length > 0 ? policies : undefined,
251
+ muteTimes: muteTimes.length > 0 ? muteTimes : undefined,
252
+ templates: templates.length > 0 ? templates : undefined,
253
+ });
254
+ const index = {
255
+ ruleGroups: groups.map((g) => ({ name: g.name, folder: g.folder, rules: g.rules.length })),
256
+ contactPoints: contactPoints.map((c) => c.name),
257
+ policies: policies.length,
258
+ muteTimings: muteTimes.map((m) => m.name),
259
+ templates: templates.map((t) => t.name),
260
+ };
261
+ return { file, index };
262
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Grafana-managed alerting: rule groups, their rules, queries and
3
+ * server-side expressions, contact points, the notification policy tree,
4
+ * mute timings and notification templates.
5
+ *
6
+ * Everything here is written to Grafana's alerting file provisioning
7
+ * format, `provisioning/alerting/chant.yaml` (see `alerting-build.ts`). The
8
+ * shapes follow the file format as Grafana reads it
9
+ * (`pkg/services/provisioning/alerting/*_types.go`, identical at v12.4.11
10
+ * and v13.2.2), so a key here is the key in the file. Server-side
11
+ * expressions are typed from the vendored `expr` schema (see `src/pin.ts`).
12
+ *
13
+ * Resources: `AlertRuleGroup`, `ContactPoint`, `NotificationPolicy`,
14
+ * `MuteTiming`, `NotificationTemplate`. Property-kind (they live inside a
15
+ * rule group): `AlertRule`, `AlertQuery` and the expression classes.
16
+ */
17
+ import { createProperty, createResource } from "@intentius/chant/runtime";
18
+ /** The datasource uid Grafana gives server-side expressions. */
19
+ export const EXPRESSION_DATASOURCE_UID = "__expr__";
20
+ export const ALERT_QUERY_TYPE = "Grafana::AlertQuery";
21
+ const AlertQueryBase = createProperty(ALERT_QUERY_TYPE, "grafana");
22
+ /**
23
+ * One datasource query of an alert rule. A typed query (`PromQuery`) can go
24
+ * straight into a rule's `data`; `AlertQuery` is for a query model of any
25
+ * plugin, or to give a typed query its own time range or refId.
26
+ */
27
+ export const AlertQuery = function (props) {
28
+ AlertQueryBase.call(this, props);
29
+ };
30
+ Object.defineProperty(AlertQuery, "name", { value: "AlertQuery" });
31
+ export function isAlertQueryEntity(value) {
32
+ return typeof value === "object" && value !== null && value.entityType === ALERT_QUERY_TYPE && value.lexicon === "grafana";
33
+ }
34
+ export const EXPRESSION_TYPE_PREFIX = "Grafana::Expression::";
35
+ function expressionClass(kind, className) {
36
+ const Base = createProperty(`${EXPRESSION_TYPE_PREFIX}${kind}`, "grafana");
37
+ const Cls = function (props) {
38
+ Base.call(this, props);
39
+ Object.defineProperty(this, "expressionKind", { value: kind, enumerable: false });
40
+ };
41
+ Object.defineProperty(Cls, "name", { value: className });
42
+ return Cls;
43
+ }
44
+ /** Reduce a series to one number per series: `expression` is a refId (`A` or `$A`). */
45
+ export const ReduceExpression = expressionClass("reduce", "ReduceExpression");
46
+ /** Arithmetic and logic over results by refId: `$A / $B`, `$A > 0.05 && $B > 0.05`. */
47
+ export const MathExpression = expressionClass("math", "MathExpression");
48
+ /** Compare a result with a threshold; an `unloadEvaluator` adds a recovery threshold. */
49
+ export const ThresholdExpression = expressionClass("threshold", "ThresholdExpression");
50
+ /** Resample a time series to a fixed `window`. */
51
+ export const ResampleExpression = expressionClass("resample", "ResampleExpression");
52
+ /** Grafana's legacy classic conditions: reducers, evaluators and and/or across queries. */
53
+ export const ClassicConditionsExpression = expressionClass("classic_conditions", "ClassicConditionsExpression");
54
+ /** A SQL query over the other results (Grafana's SQL expressions). */
55
+ export const SqlExpression = expressionClass("sql", "SqlExpression");
56
+ export function isExpressionEntity(value) {
57
+ return (typeof value === "object" &&
58
+ value !== null &&
59
+ value.lexicon === "grafana" &&
60
+ typeof value.entityType === "string" &&
61
+ value.entityType.startsWith(EXPRESSION_TYPE_PREFIX));
62
+ }
63
+ export const ALERT_RULE_TYPE = "Grafana::AlertRule";
64
+ const AlertRuleBase = createProperty(ALERT_RULE_TYPE, "grafana");
65
+ /** A Grafana-managed alert or recording rule, inside an `AlertRuleGroup`. */
66
+ export const AlertRule = function (props) {
67
+ AlertRuleBase.call(this, props);
68
+ };
69
+ Object.defineProperty(AlertRule, "name", { value: "AlertRule" });
70
+ export function isAlertRuleEntity(value) {
71
+ return typeof value === "object" && value !== null && value.entityType === ALERT_RULE_TYPE && value.lexicon === "grafana";
72
+ }
73
+ export const ALERT_RULE_GROUP_TYPE = "Grafana::AlertRuleGroup";
74
+ const GroupBase = createResource(ALERT_RULE_GROUP_TYPE, "grafana", {});
75
+ /** A group of Grafana-managed rules, evaluated together at one interval. */
76
+ export const AlertRuleGroup = function (props) {
77
+ GroupBase.call(this, props);
78
+ };
79
+ Object.defineProperty(AlertRuleGroup, "name", { value: "AlertRuleGroup" });
80
+ export function isAlertRuleGroupEntity(value) {
81
+ return typeof value === "object" && value !== null && value.entityType === ALERT_RULE_GROUP_TYPE && value.lexicon === "grafana";
82
+ }
83
+ export const CONTACT_POINT_TYPE = "Grafana::ContactPoint";
84
+ const ContactPointBase = createResource(CONTACT_POINT_TYPE, "grafana", {});
85
+ /** A contact point: one or more integrations alerts are sent to, under one name. */
86
+ export const ContactPoint = function (props) {
87
+ ContactPointBase.call(this, props);
88
+ };
89
+ Object.defineProperty(ContactPoint, "name", { value: "ContactPoint" });
90
+ export function isContactPointEntity(value) {
91
+ return typeof value === "object" && value !== null && value.entityType === CONTACT_POINT_TYPE && value.lexicon === "grafana";
92
+ }
93
+ export const MUTE_TIMING_TYPE = "Grafana::MuteTiming";
94
+ const MuteTimingBase = createResource(MUTE_TIMING_TYPE, "grafana", {});
95
+ /** A named time interval, for a policy's or rule's `mute_time_intervals` or `active_time_intervals`. */
96
+ export const MuteTiming = function (props) {
97
+ MuteTimingBase.call(this, props);
98
+ };
99
+ Object.defineProperty(MuteTiming, "name", { value: "MuteTiming" });
100
+ export function isMuteTimingEntity(value) {
101
+ return typeof value === "object" && value !== null && value.entityType === MUTE_TIMING_TYPE && value.lexicon === "grafana";
102
+ }
103
+ export const NOTIFICATION_TEMPLATE_TYPE = "Grafana::NotificationTemplate";
104
+ const TemplateBase = createResource(NOTIFICATION_TEMPLATE_TYPE, "grafana", {});
105
+ /** A notification template group, usable from contact point settings. */
106
+ export const NotificationTemplate = function (props) {
107
+ TemplateBase.call(this, props);
108
+ };
109
+ Object.defineProperty(NotificationTemplate, "name", { value: "NotificationTemplate" });
110
+ export function isNotificationTemplateEntity(value) {
111
+ return (typeof value === "object" && value !== null && value.entityType === NOTIFICATION_TEMPLATE_TYPE && value.lexicon === "grafana");
112
+ }
113
+ export const NOTIFICATION_POLICY_TYPE = "Grafana::NotificationPolicy";
114
+ const PolicyBase = createResource(NOTIFICATION_POLICY_TYPE, "grafana", {});
115
+ /**
116
+ * An organisation's notification policy tree, from its root. Provisioning it
117
+ * replaces the whole tree, so there is one per organisation.
118
+ */
119
+ export const NotificationPolicy = function (props) {
120
+ PolicyBase.call(this, props);
121
+ };
122
+ Object.defineProperty(NotificationPolicy, "name", { value: "NotificationPolicy" });
123
+ export function isNotificationPolicyEntity(value) {
124
+ return (typeof value === "object" && value !== null && value.entityType === NOTIFICATION_POLICY_TYPE && value.lexicon === "grafana");
125
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Dashboard annotations: the queries in a dashboard's `annotations.list`
3
+ * whose events Grafana draws on time series panels (deploy markers,
4
+ * incidents, alert state changes).
5
+ *
6
+ * An annotation is plain data on `Dashboard.annotations`, typed from
7
+ * Grafana's `AnnotationQuery`, except that its `datasource` takes the same
8
+ * values a panel's does (a `Datasource`, an `ExternalDatasource`, a
9
+ * `DatasourceVariable` or a `{ type, uid }` ref) and its `target` may be a
10
+ * query entity (`new PromQuery({ expr })`). What a datasource plugin keeps
11
+ * beside the common keys (Prometheus's legacy `expr`, `step`,
12
+ * `titleFormat`) is written as given.
13
+ *
14
+ * Grafana adds its built-in "Annotations & Alerts" query (`builtIn: 1`, the
15
+ * `-- Grafana --` datasource) to any dashboard without one, so a dashboard
16
+ * leaves it out unless it changes it (hides the toggle, turns it off).
17
+ */
18
+ import { isQueryEntity } from "./query.js";
19
+ import { compact } from "./util.js";
20
+ /** The refId Grafana gives an annotation query's target. */
21
+ export const ANNOTATION_REF_ID = "Anno";
22
+ /**
23
+ * One annotation as the entry Grafana stores in `annotations.list`.
24
+ * `datasourceRef` resolves the datasource the same way a panel's is (it is
25
+ * build.ts's, passed in so this module needs nothing from the build).
26
+ */
27
+ export function annotationJson(input, datasourceRef) {
28
+ const { name, datasource, enable, iconColor, target, ...rest } = input;
29
+ let ds = datasourceRef(datasource);
30
+ let targetJson;
31
+ if (isQueryEntity(target)) {
32
+ const { datasource: queryDs, ...model } = target.props;
33
+ ds ?? (ds = datasourceRef(queryDs));
34
+ targetJson = compact({ refId: ANNOTATION_REF_ID, ...(target.queryDefinition.defaults ?? {}), ...model });
35
+ }
36
+ else {
37
+ targetJson = target;
38
+ }
39
+ return compact({
40
+ ...(ds ? { datasource: ds } : {}),
41
+ enable: enable ?? true,
42
+ iconColor: iconColor ?? "red",
43
+ name,
44
+ ...rest,
45
+ ...(targetJson !== undefined ? { target: targetJson } : {}),
46
+ });
47
+ }