@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.
Files changed (224) hide show
  1. package/README.md +12 -6
  2. package/dist/annotations.d.ts +61 -0
  3. package/dist/annotations.d.ts.map +1 -0
  4. package/dist/api/apply.d.ts +8 -4
  5. package/dist/api/apply.d.ts.map +1 -1
  6. package/dist/api/fake-grafana.d.ts +2 -0
  7. package/dist/api/fake-grafana.d.ts.map +1 -1
  8. package/dist/api/folders.d.ts +53 -17
  9. package/dist/api/folders.d.ts.map +1 -1
  10. package/dist/build.d.ts +26 -5
  11. package/dist/build.d.ts.map +1 -1
  12. package/dist/catalog.d.ts +1 -1
  13. package/dist/catalog.d.ts.map +1 -1
  14. package/dist/codegen/docs.d.ts.map +1 -1
  15. package/dist/codegen/generate.d.ts +7 -1
  16. package/dist/codegen/generate.d.ts.map +1 -1
  17. package/dist/composites/shared.d.ts +4 -3
  18. package/dist/composites/shared.d.ts.map +1 -1
  19. package/dist/dashboard.d.ts +42 -14
  20. package/dist/dashboard.d.ts.map +1 -1
  21. package/dist/datasource-refs.d.ts +9 -5
  22. package/dist/datasource-refs.d.ts.map +1 -1
  23. package/dist/datasource-settings.d.ts +834 -0
  24. package/dist/datasource-settings.d.ts.map +1 -0
  25. package/dist/datasource.d.ts +53 -11
  26. package/dist/datasource.d.ts.map +1 -1
  27. package/dist/deep-observe-hooks.d.ts +1 -1
  28. package/dist/deep-observe-hooks.d.ts.map +1 -1
  29. package/dist/deep-observe.d.ts +9 -4
  30. package/dist/deep-observe.d.ts.map +1 -1
  31. package/dist/describe-resources.d.ts +1 -0
  32. package/dist/describe-resources.d.ts.map +1 -1
  33. package/dist/detect.d.ts +4 -2
  34. package/dist/detect.d.ts.map +1 -1
  35. package/dist/folder.d.ts +68 -0
  36. package/dist/folder.d.ts.map +1 -0
  37. package/dist/import/generator.d.ts.map +1 -1
  38. package/dist/import/mappings.d.ts +12 -6
  39. package/dist/import/mappings.d.ts.map +1 -1
  40. package/dist/import/model.d.ts +7 -0
  41. package/dist/import/model.d.ts.map +1 -1
  42. package/dist/import/normalize.d.ts.map +1 -1
  43. package/dist/import/parser.d.ts +16 -1
  44. package/dist/import/parser.d.ts.map +1 -1
  45. package/dist/import/testdata/fixtures.d.ts +6 -0
  46. package/dist/import/testdata/fixtures.d.ts.map +1 -1
  47. package/dist/import/v2.d.ts +4 -3
  48. package/dist/import/v2.d.ts.map +1 -1
  49. package/dist/index.d.ts +11 -9
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/init-templates.d.ts +27 -0
  52. package/dist/init-templates.d.ts.map +1 -0
  53. package/dist/integrity.json +11 -9
  54. package/dist/lint/audit-catalog.d.ts +1 -1
  55. package/dist/lint/audit-catalog.d.ts.map +1 -1
  56. package/dist/lint/post-synth/graf101.d.ts +1 -1
  57. package/dist/lint/post-synth/graf104.d.ts +2 -2
  58. package/dist/lint/post-synth/graf106.d.ts +2 -2
  59. package/dist/lint/post-synth/graf110.d.ts +8 -0
  60. package/dist/lint/post-synth/graf110.d.ts.map +1 -0
  61. package/dist/lint/post-synth/grafana-helpers.d.ts.map +1 -1
  62. package/dist/lint/post-synth/index.d.ts.map +1 -1
  63. package/dist/manifest.json +1 -1
  64. package/dist/meta.json +70 -0
  65. package/dist/okf/index.md +18 -3
  66. package/dist/okf/rules/GRAF101.md +2 -2
  67. package/dist/okf/rules/GRAF104.md +2 -2
  68. package/dist/okf/rules/GRAF106.md +2 -2
  69. package/dist/okf/rules/GRAF110.md +11 -0
  70. package/dist/okf/types/AdhocVariable.md +9 -0
  71. package/dist/okf/types/AzureMonitorQuery.md +9 -0
  72. package/dist/okf/types/BigQueryQuery.md +9 -0
  73. package/dist/okf/types/CloudMonitoringQuery.md +9 -0
  74. package/dist/okf/types/CloudWatchQuery.md +9 -0
  75. package/dist/okf/types/Dashboard.md +1 -1
  76. package/dist/okf/types/Datasource.md +1 -1
  77. package/dist/okf/types/DatasourceProvisioning.md +9 -0
  78. package/dist/okf/types/ElasticsearchQuery.md +9 -0
  79. package/dist/okf/types/ExternalDatasource.md +1 -1
  80. package/dist/okf/types/Folder.md +9 -0
  81. package/dist/okf/types/GroupByVariable.md +9 -0
  82. package/dist/okf/types/MSSQLQuery.md +9 -0
  83. package/dist/okf/types/MySQLQuery.md +9 -0
  84. package/dist/okf/types/PostgresQuery.md +9 -0
  85. package/dist/okf/types/PyroscopeQuery.md +9 -0
  86. package/dist/okf/types/SwitchVariable.md +9 -0
  87. package/dist/op/activities/grafana-apply.d.ts +11 -0
  88. package/dist/op/activities/grafana-apply.d.ts.map +1 -1
  89. package/dist/op/activities/index.d.ts +1 -1
  90. package/dist/op/activities/index.d.ts.map +1 -1
  91. package/dist/ownership.d.ts +11 -0
  92. package/dist/ownership.d.ts.map +1 -1
  93. package/dist/pin.d.ts +1 -1
  94. package/dist/pin.d.ts.map +1 -1
  95. package/dist/plugin.d.ts.map +1 -1
  96. package/dist/promql-check.d.ts.map +1 -1
  97. package/dist/query-models.d.ts +95 -0
  98. package/dist/query-models.d.ts.map +1 -0
  99. package/dist/query.d.ts +107 -10
  100. package/dist/query.d.ts.map +1 -1
  101. package/dist/rules/graf101.ts +2 -2
  102. package/dist/rules/graf104.ts +3 -3
  103. package/dist/rules/graf106.ts +3 -3
  104. package/dist/rules/graf110.ts +17 -0
  105. package/dist/rules/grafana-helpers.ts +13 -3
  106. package/dist/schema/azuremonitor.gen.d.ts +354 -0
  107. package/dist/schema/azuremonitor.gen.d.ts.map +1 -0
  108. package/dist/schema/bigquery.gen.d.ts +93 -0
  109. package/dist/schema/bigquery.gen.d.ts.map +1 -0
  110. package/dist/schema/cloudwatch.gen.d.ts +394 -0
  111. package/dist/schema/cloudwatch.gen.d.ts.map +1 -0
  112. package/dist/schema/elasticsearch.gen.d.ts +444 -0
  113. package/dist/schema/elasticsearch.gen.d.ts.map +1 -0
  114. package/dist/schema/googlecloudmonitoring.gen.d.ts +312 -0
  115. package/dist/schema/googlecloudmonitoring.gen.d.ts.map +1 -0
  116. package/dist/schema/grafanapyroscope.gen.d.ts +60 -0
  117. package/dist/schema/grafanapyroscope.gen.d.ts.map +1 -0
  118. package/dist/schema/index.d.ts +6 -0
  119. package/dist/schema/index.d.ts.map +1 -1
  120. package/dist/schema/table.gen.d.ts +13 -0
  121. package/dist/schema/table.gen.d.ts.map +1 -1
  122. package/dist/schema-validate.d.ts +9 -0
  123. package/dist/schema-validate.d.ts.map +1 -1
  124. package/dist/skill-defs.d.ts.map +1 -1
  125. package/dist/skills/chant-grafana-operations.md +108 -0
  126. package/dist/skills/chant-grafana.md +5 -4
  127. package/dist/spec/schemas.d.ts +9 -7
  128. package/dist/spec/schemas.d.ts.map +1 -1
  129. package/dist/spec/schemas.gen.d.ts +3 -0
  130. package/dist/spec/schemas.gen.d.ts.map +1 -0
  131. package/dist/validate-alerting.d.ts.map +1 -1
  132. package/dist/validate-output.d.ts +15 -2
  133. package/dist/validate-output.d.ts.map +1 -1
  134. package/dist/validate.d.ts.map +1 -1
  135. package/dist/validation.d.ts +14 -0
  136. package/dist/validation.d.ts.map +1 -0
  137. package/dist/variables.d.ts +102 -5
  138. package/dist/variables.d.ts.map +1 -1
  139. package/package.json +2 -2
  140. package/src/annotations.ts +84 -0
  141. package/src/api/apply.test.ts +19 -1
  142. package/src/api/apply.ts +18 -8
  143. package/src/api/fake-grafana.ts +6 -2
  144. package/src/api/folders.ts +106 -32
  145. package/src/build.test.ts +142 -6
  146. package/src/build.ts +126 -24
  147. package/src/catalog.ts +20 -2
  148. package/src/codegen/docs.ts +13 -6
  149. package/src/codegen/generate.ts +27 -1
  150. package/src/composites/shared.ts +3 -2
  151. package/src/dashboard.ts +59 -14
  152. package/src/datasource-refs.ts +30 -6
  153. package/src/datasource-settings.test.ts +106 -0
  154. package/src/datasource-settings.ts +941 -0
  155. package/src/datasource.ts +71 -11
  156. package/src/deep-observe-hooks.ts +16 -1
  157. package/src/deep-observe.test.ts +47 -1
  158. package/src/deep-observe.ts +36 -13
  159. package/src/describe-resources.test.ts +16 -0
  160. package/src/describe-resources.ts +36 -1
  161. package/src/detect.ts +3 -3
  162. package/src/folder.ts +118 -0
  163. package/src/generated/lexicon-grafana.json +70 -0
  164. package/src/import/generated-types.e2e.test.ts +15 -3
  165. package/src/import/generator.ts +2 -1
  166. package/src/import/mappings.ts +98 -26
  167. package/src/import/model.ts +7 -0
  168. package/src/import/normalize.ts +32 -3
  169. package/src/import/parser.test.ts +97 -21
  170. package/src/import/parser.ts +169 -18
  171. package/src/import/roundtrip.test.ts +104 -9
  172. package/src/import/testdata/fixtures.ts +8 -0
  173. package/src/import/v2.ts +4 -3
  174. package/src/index.ts +122 -13
  175. package/src/init-templates.test.ts +74 -0
  176. package/src/init-templates.ts +349 -0
  177. package/src/lint/audit-catalog.ts +9 -1
  178. package/src/lint/post-synth/graf101.ts +2 -2
  179. package/src/lint/post-synth/graf104.ts +3 -3
  180. package/src/lint/post-synth/graf106.ts +3 -3
  181. package/src/lint/post-synth/graf110.ts +17 -0
  182. package/src/lint/post-synth/grafana-helpers.ts +13 -3
  183. package/src/lint/post-synth/index.ts +2 -0
  184. package/src/lint/post-synth/post-synth.test.ts +99 -2
  185. package/src/load-cost.test.ts +35 -0
  186. package/src/op/activities/grafana-apply.e2e.test.ts +14 -5
  187. package/src/op/activities/grafana-apply.test.ts +16 -2
  188. package/src/op/activities/grafana-apply.ts +29 -3
  189. package/src/op/activities/index.ts +1 -1
  190. package/src/ownership.ts +19 -0
  191. package/src/panels.test.ts +3 -1
  192. package/src/pin.ts +12 -0
  193. package/src/plugin.test.ts +9 -2
  194. package/src/plugin.ts +9 -0
  195. package/src/promql-check.ts +5 -1
  196. package/src/query-models.ts +94 -0
  197. package/src/query.test.ts +166 -0
  198. package/src/query.ts +177 -12
  199. package/src/schema/azuremonitor.gen.ts +381 -0
  200. package/src/schema/bigquery.gen.ts +110 -0
  201. package/src/schema/cloudwatch.gen.ts +423 -0
  202. package/src/schema/elasticsearch.gen.ts +508 -0
  203. package/src/schema/googlecloudmonitoring.gen.ts +330 -0
  204. package/src/schema/grafanapyroscope.gen.ts +66 -0
  205. package/src/schema/index.ts +6 -0
  206. package/src/schema/table.gen.ts +14 -0
  207. package/src/schema-validate.ts +58 -7
  208. package/src/skill-defs.ts +17 -0
  209. package/src/skills/chant-grafana-operations.md +108 -0
  210. package/src/skills/chant-grafana.md +5 -4
  211. package/src/spec/overlay/table.overlay.json +19 -0
  212. package/src/spec/schemas/azuremonitor.jsonschema.json +679 -0
  213. package/src/spec/schemas/bigquery.jsonschema.json +290 -0
  214. package/src/spec/schemas/cloudwatch.jsonschema.json +721 -0
  215. package/src/spec/schemas/elasticsearch.jsonschema.json +1695 -0
  216. package/src/spec/schemas/googlecloudmonitoring.jsonschema.json +488 -0
  217. package/src/spec/schemas/grafanapyroscope.jsonschema.json +93 -0
  218. package/src/spec/schemas.gen.ts +40 -0
  219. package/src/spec/schemas.ts +9 -7
  220. package/src/validate-alerting.ts +4 -2
  221. package/src/validate-output.ts +96 -4
  222. package/src/validate.ts +14 -0
  223. package/src/validation.ts +27 -0
  224. package/src/variables.ts +113 -6
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Hand-written query models for the datasources that have no schema.
3
+ *
4
+ * PostgreSQL, MySQL and Microsoft SQL Server share one query model, defined
5
+ * in TypeScript in Grafana's `@grafana/sql` package with no CUE kind, so
6
+ * foundation-sdk publishes no JSON Schema for it and `npm run generate` has
7
+ * nothing to read. The types here follow `packages/grafana-sql/src` at
8
+ * Grafana v13.2.2, cited per type. The three plugins bundled with
9
+ * grafana/grafana:13.2.2 (grafana-postgresql-datasource 13.0.3, mysql 13.1.2,
10
+ * mssql 13.0.5) use this model unchanged. GRAF107 does not check these
11
+ * queries' fields; the dashboard schema's `Panel` still checks the panel.
12
+ */
13
+
14
+ /** `QueryEditorExpressionType`, packages/grafana-sql/src/expressions.ts:34-42. */
15
+ export type SqlExpressionType = "property" | "operator" | "or" | "and" | "groupBy" | "function" | "functionParameter";
16
+
17
+ /** `QueryEditorProperty`, packages/grafana-sql/src/expressions.ts:5-8 (its type is the one-member `QueryEditorPropertyType`). */
18
+ export interface SqlProperty {
19
+ type: "string";
20
+ name?: string;
21
+ }
22
+
23
+ /** `QueryEditorFunctionParameterExpression`, packages/grafana-sql/src/expressions.ts:64-67. */
24
+ export interface SqlFunctionParameterExpression {
25
+ type: "functionParameter";
26
+ name?: string;
27
+ }
28
+
29
+ /** `QueryEditorFunctionExpression`, packages/grafana-sql/src/expressions.ts:57-62: a selected column, optionally wrapped in an aggregation. */
30
+ export interface SqlFunctionExpression {
31
+ type: "function";
32
+ name?: string;
33
+ alias?: string;
34
+ parameters?: SqlFunctionParameterExpression[];
35
+ }
36
+
37
+ /** `QueryEditorGroupByExpression`, packages/grafana-sql/src/expressions.ts:52-55. */
38
+ export interface SqlGroupByExpression {
39
+ type: "groupBy";
40
+ property: SqlProperty;
41
+ }
42
+
43
+ /** `QueryEditorPropertyExpression`, packages/grafana-sql/src/expressions.ts:29-32. */
44
+ export interface SqlPropertyExpression {
45
+ type: "property";
46
+ property: SqlProperty;
47
+ }
48
+
49
+ /**
50
+ * The visual builder's query: `SQLExpression`, packages/grafana-sql/src/types.ts:67-77.
51
+ * `whereJsonTree` is react-awesome-query-builder's `JsonTree`, carried as data.
52
+ */
53
+ export interface SqlBuilderQuery {
54
+ columns?: SqlFunctionExpression[];
55
+ whereJsonTree?: Record<string, unknown>;
56
+ whereString?: string;
57
+ filters?: Array<{ name: string; value: string }>;
58
+ groupBy?: SqlGroupByExpression[];
59
+ orderBy?: SqlPropertyExpression;
60
+ orderByDirection?: "ASC" | "DESC";
61
+ limit?: number;
62
+ offset?: number;
63
+ }
64
+
65
+ /** `QueryFormat`, packages/grafana-sql/src/types.ts:39-42. */
66
+ export type SqlQueryFormat = "time_series" | "table";
67
+
68
+ /**
69
+ * A PostgreSQL, MySQL or MSSQL query: `SQLQuery`, packages/grafana-sql/src/types.ts:46-56,
70
+ * over `DataQuery` from @grafana/schema (refId, hide, key, queryType, datasource).
71
+ *
72
+ * In code mode (`editorMode: "code"`, `rawQuery: true`) `rawSql` is the
73
+ * query; in builder mode `dataset`, `table` and `sql` describe it and Grafana
74
+ * writes the generated SQL to `rawSql`.
75
+ */
76
+ export interface SqlQueryModel {
77
+ refId?: string;
78
+ hide?: boolean;
79
+ key?: string;
80
+ queryType?: string;
81
+ datasource?: { type?: string; uid?: string };
82
+ /** The SQL, with Grafana's `$__timeFilter()` and other macros. */
83
+ rawSql?: string;
84
+ format?: SqlQueryFormat;
85
+ alias?: string;
86
+ dataset?: string;
87
+ table?: string;
88
+ sql?: SqlBuilderQuery;
89
+ /** `EditorMode` from @grafana/plugin-ui: the raw SQL editor or the visual builder. */
90
+ editorMode?: "code" | "builder";
91
+ rawQuery?: boolean;
92
+ /** `SQLQueryMeta`, packages/grafana-sql/src/types.ts:44: which columns a variable query takes its values and texts from. */
93
+ meta?: { valueField?: string; textField?: string };
94
+ }
@@ -0,0 +1,166 @@
1
+ /**
2
+ * The query classes beyond Prometheus, Tempo and Loki (#2951): each renders
3
+ * as a target of its plugin, GRAF107 checks the schema-typed ones, the
4
+ * PostgreSQL class answers to the plugin's old id, and GRAF108 still parses
5
+ * only what reaches a Prometheus.
6
+ */
7
+ import { describe, expect, test } from "vitest";
8
+ import { Dashboard } from "./dashboard";
9
+ import { Datasource } from "./datasource";
10
+ import { TablePanel, TimeSeriesPanel } from "./panels";
11
+ import {
12
+ AzureMonitorQuery,
13
+ BigQueryQuery,
14
+ CloudMonitoringQuery,
15
+ CloudWatchQuery,
16
+ ElasticsearchQuery,
17
+ LokiQuery,
18
+ MSSQLQuery,
19
+ MySQLQuery,
20
+ PostgresQuery,
21
+ PromQuery,
22
+ PyroscopeQuery,
23
+ queryDefinitionFor,
24
+ registeredQueries,
25
+ } from "./query";
26
+ import { renderDashboard } from "./build";
27
+ import { validateDashboardSchema } from "./schema-validate";
28
+ import { prometheusQueries } from "./promql-check";
29
+ import { knownDatasources } from "./datasource-refs";
30
+ import { SCHEMA_NAMES } from "./pin";
31
+
32
+ type Json = Record<string, unknown>;
33
+
34
+ const es = new Datasource({ name: "Logs", type: "elasticsearch" });
35
+ const cw = new Datasource({ name: "CloudWatch", type: "cloudwatch" });
36
+ const az = new Datasource({ name: "Azure", type: "grafana-azure-monitor-datasource" });
37
+ const gcm = new Datasource({ name: "GCM", type: "stackdriver" });
38
+ const bq = new Datasource({ name: "BigQuery", type: "grafana-bigquery-datasource" });
39
+ const py = new Datasource({ name: "Pyroscope", type: "grafana-pyroscope-datasource" });
40
+ const pg = new Datasource({ name: "Postgres", type: "grafana-postgresql-datasource" });
41
+ const pgOld = new Datasource({ name: "Postgres old", type: "postgres" });
42
+ const my = new Datasource({ name: "MySQL", type: "mysql" });
43
+ const ms = new Datasource({ name: "MSSQL", type: "mssql" });
44
+ const prom = new Datasource({ name: "Prometheus", type: "prometheus" });
45
+ const loki = new Datasource({ name: "Loki", type: "loki" });
46
+
47
+ function render(panels: unknown[]): Json {
48
+ return renderDashboard(new Dashboard({ title: "Q", uid: "q", panels: panels as never })) as unknown as Json;
49
+ }
50
+
51
+ const targetsOf = (json: Json, i = 0) => ((json.panels as Json[])[i].targets as Json[]);
52
+
53
+ describe("query classes", () => {
54
+ test("every query schema at the pin has a class, and every class names a schema or is hand-typed", () => {
55
+ const schemas = registeredQueries().filter((d) => d.builtin).flatMap((d) => (d.schema ? [d.schema] : []));
56
+ expect(schemas.sort()).toEqual(
57
+ ["prometheus", "tempo", "loki", "elasticsearch", "cloudwatch", "azuremonitor", "googlecloudmonitoring", "bigquery", "grafanapyroscope"].sort(),
58
+ );
59
+ expect(schemas.every((s) => (SCHEMA_NAMES as readonly string[]).includes(s))).toBe(true);
60
+ expect(registeredQueries().filter((d) => d.builtin && !d.schema).map((d) => d.className).sort()).toEqual(["MSSQLQuery", "MySQLQuery", "PostgresQuery"]);
61
+ });
62
+
63
+ test("each renders as a target of its plugin and passes GRAF107", () => {
64
+ const json = render([
65
+ new TimeSeriesPanel({
66
+ datasource: es,
67
+ targets: [
68
+ new ElasticsearchQuery({
69
+ query: "level:error",
70
+ timeField: "@timestamp",
71
+ metrics: [{ id: "1", type: "count" }],
72
+ bucketAggs: [{ id: "2", type: "date_histogram", field: "@timestamp", settings: { interval: "auto" } }],
73
+ }),
74
+ ],
75
+ }),
76
+ new TimeSeriesPanel({
77
+ datasource: cw,
78
+ targets: [
79
+ new CloudWatchQuery({ queryMode: "Metrics", namespace: "AWS/Lambda", metricName: "Errors", statistic: "Sum", dimensions: { FunctionName: "checkout" } }),
80
+ new CloudWatchQuery({ queryMode: "Logs", expression: "fields @message | limit 20", logGroups: [{ arn: "arn:aws:logs:x", name: "/x" }] }),
81
+ ],
82
+ }),
83
+ new TablePanel({
84
+ datasource: az,
85
+ targets: [new AzureMonitorQuery({ queryType: "Azure Log Analytics", azureLogAnalytics: { query: "AppRequests | take 10", resources: ["/subscriptions/x"] } })],
86
+ }),
87
+ new TimeSeriesPanel({
88
+ datasource: gcm,
89
+ targets: [new CloudMonitoringQuery({ queryType: "promQL", promQLQuery: { projectName: "p", expr: "up", step: "10s" } })],
90
+ }),
91
+ new TablePanel({ datasource: bq, targets: [new BigQueryQuery({ rawSql: "SELECT 1", project: "p" })] }),
92
+ new TimeSeriesPanel({ datasource: py, targets: [new PyroscopeQuery({ profileTypeId: "process_cpu:cpu:nanoseconds:cpu:nanoseconds", queryType: "metrics" })] }),
93
+ new TablePanel({ datasource: pg, targets: [new PostgresQuery({ rawSql: "SELECT now()", format: "table", editorMode: "code", rawQuery: true })] }),
94
+ new TablePanel({ datasource: my, targets: [new MySQLQuery({ rawSql: "SELECT 1" })] }),
95
+ new TablePanel({ datasource: ms, targets: [new MSSQLQuery({ rawSql: "SELECT TOP 1 1" })] }),
96
+ ]);
97
+ const types = (json.panels as Json[]).map((p) => (targetsOf({ panels: [p] })[0].datasource as Json).type);
98
+ expect(types).toEqual([
99
+ "elasticsearch",
100
+ "cloudwatch",
101
+ "grafana-azure-monitor-datasource",
102
+ "stackdriver",
103
+ "grafana-bigquery-datasource",
104
+ "grafana-pyroscope-datasource",
105
+ "grafana-postgresql-datasource",
106
+ "mysql",
107
+ "mssql",
108
+ ]);
109
+ expect(targetsOf(json, 1).map((t) => t.refId)).toEqual(["A", "B"]);
110
+ expect(validateDashboardSchema(json)).toEqual([]);
111
+ });
112
+
113
+ test("GRAF107 checks the schema-typed models: a wrong value is an error, an unknown key a warning", () => {
114
+ const json = render([
115
+ new TimeSeriesPanel({
116
+ datasource: es,
117
+ // @ts-expect-error a bucket aggregation type the schema does not list
118
+ targets: [new ElasticsearchQuery({ query: "*", bucketAggs: [{ id: "2", type: "histogramz" }] })],
119
+ }),
120
+ new TimeSeriesPanel({
121
+ datasource: cw,
122
+ // @ts-expect-error queryMode is Metrics, Logs or Annotations
123
+ targets: [new CloudWatchQuery({ queryMode: "Traces", namespace: "x" })],
124
+ }),
125
+ new TablePanel({
126
+ datasource: bq,
127
+ // @ts-expect-error BigQuery's query has no expr
128
+ targets: [new BigQueryQuery({ rawSql: "SELECT 1", expr: "x" })],
129
+ }),
130
+ ]);
131
+ const problems = validateDashboardSchema(json);
132
+ expect(problems.filter((p) => p.path.startsWith("/panels/0/targets/0/bucketAggs")).some((p) => p.severity === "error")).toBe(true);
133
+ expect(problems.some((p) => p.path.startsWith("/panels/1/targets/0") && p.severity === "error")).toBe(true);
134
+ expect(problems).toContainEqual({ path: "/panels/2/targets/0", message: 'unknown key "expr" (not in the pinned schema)', severity: "warning" });
135
+ });
136
+
137
+ test("a query only takes a datasource of its own plugin type", () => {
138
+ // @ts-expect-error a MySQL query cannot go to a Postgres datasource
139
+ new MySQLQuery({ rawSql: "SELECT 1", datasource: pg });
140
+ // @ts-expect-error nor an Elasticsearch query to Loki
141
+ new ElasticsearchQuery({ query: "*", datasource: loki });
142
+ expect(new PostgresQuery({ rawSql: "SELECT 1", datasource: pgOld }).props.datasource).toBe(pgOld);
143
+ });
144
+
145
+ test("the PostgreSQL class answers to the plugin's old id, postgres", () => {
146
+ expect(queryDefinitionFor("postgres")?.className).toBe("PostgresQuery");
147
+ expect(queryDefinitionFor("grafana-postgresql-datasource")?.className).toBe("PostgresQuery");
148
+ expect(registeredQueries().filter((d) => d.className === "PostgresQuery")).toHaveLength(1);
149
+ });
150
+ });
151
+
152
+ describe("GRAF108 routing with the new query types", () => {
153
+ test("only queries that reach a Prometheus are parsed, whatever field holds PromQL-looking text", () => {
154
+ const json = render([
155
+ new TimeSeriesPanel({ datasource: prom, targets: [new PromQuery({ expr: "sum(rate(x[5m]))" })] }),
156
+ new TimeSeriesPanel({ datasource: gcm, targets: [new CloudMonitoringQuery({ queryType: "promQL", promQLQuery: { projectName: "p", expr: "sum(", step: "1m" } })] }),
157
+ new TimeSeriesPanel({ datasource: loki, targets: [new LokiQuery({ expr: 'sum(rate({job="x"}[5m]' })] }),
158
+ new TablePanel({ datasource: pg, targets: [new PostgresQuery({ rawSql: "SELECT sum(" })] }),
159
+ new TimeSeriesPanel({ datasource: cw, targets: [new CloudWatchQuery({ queryMode: "Metrics", namespace: "", expression: "SUM(METRICS(" })] }),
160
+ ]);
161
+ const known = knownDatasources(
162
+ [prom, gcm, loki, pg, cw].map((d) => ({ name: d.props.name, type: d.datasourceType, uid: d.uid })),
163
+ );
164
+ expect(prometheusQueries(json, known).map((q) => q.expr)).toEqual(["sum(rate(x[5m]))"]);
165
+ });
166
+ });
package/src/query.ts CHANGED
@@ -1,7 +1,11 @@
1
1
  /**
2
- * Typed queries, one class per datasource plugin: `PromQuery` (PromQL),
3
- * `TempoQuery` (TraceQL) and `LokiQuery` (LogQL), each typed from that
4
- * plugin's query schema at `GRAFANA_SCHEMA_PIN`.
2
+ * Typed queries, one class per datasource plugin. `PromQuery` (PromQL),
3
+ * `TempoQuery` (TraceQL), `LokiQuery` (LogQL), `ElasticsearchQuery`,
4
+ * `CloudWatchQuery`, `AzureMonitorQuery`, `CloudMonitoringQuery`,
5
+ * `BigQueryQuery` and `PyroscopeQuery` are typed from that plugin's query
6
+ * schema at `GRAFANA_SCHEMA_PIN`. `PostgresQuery`, `MySQLQuery` and
7
+ * `MSSQLQuery` have no schema upstream and are typed by hand from Grafana's
8
+ * source (`./query-models.ts`).
5
9
  *
6
10
  * The expression is a string. What the lexicon checks is where the query
7
11
  * goes: `datasource` only accepts a datasource of the query's own plugin
@@ -21,6 +25,13 @@ import type { SchemaName } from "./pin";
21
25
  import type { Dataquery as PrometheusDataquery } from "./schema/prometheus.gen";
22
26
  import type { Dataquery as TempoDataquery } from "./schema/tempo.gen";
23
27
  import type { Dataquery as LokiDataquery } from "./schema/loki.gen";
28
+ import type { Dataquery as ElasticsearchDataquery } from "./schema/elasticsearch.gen";
29
+ import type { Request as CloudWatchRequest } from "./schema/cloudwatch.gen";
30
+ import type { MonitorQuery as AzureMonitorDataquery } from "./schema/azuremonitor.gen";
31
+ import type { CloudMonitoringQuery as CloudMonitoringDataquery } from "./schema/googlecloudmonitoring.gen";
32
+ import type { Dataquery as BigQueryDataquery } from "./schema/bigquery.gen";
33
+ import type { Dataquery as PyroscopeDataquery } from "./schema/grafanapyroscope.gen";
34
+ import type { SqlQueryModel } from "./query-models";
24
35
 
25
36
  /** Where a query or panel sends its request: a declared or external datasource, a datasource variable, or the ref of one declared elsewhere. */
26
37
  export type DatasourceInput<T extends string = string> =
@@ -29,8 +40,26 @@ export type DatasourceInput<T extends string = string> =
29
40
  | DatasourceVariableEntity<T>
30
41
  | DatasourceRef<T>;
31
42
 
32
- /** A query model's fields, less what chant fills in (`datasource`, `refId` stays optional). */
33
- export type QueryModel<M> = Omit<M, "datasource">;
43
+ /**
44
+ * A query model's fields, less what chant fills in (`datasource`, `refId`
45
+ * stays optional). A model that is a union (CloudWatch's metrics, logs and
46
+ * annotation queries) stays one, member by member.
47
+ */
48
+ export type QueryModel<M> = M extends unknown ? Omit<M, "datasource"> : never;
49
+
50
+ /** `M` with the fields `K` optional, member by member: for fields a schema requires that Grafana fills in when they are missing. */
51
+ export type Loosen<M, K extends PropertyKey> = M extends unknown ? Omit<M, K> & Partial<Pick<M, K & keyof M>> : never;
52
+
53
+ type KeysOfUnion<U> = U extends unknown ? keyof U : never;
54
+
55
+ /**
56
+ * A union of models as one object type: every member's fields, each
57
+ * optional, typed as the union of what the members allow there. A class
58
+ * whose props are a union could not have a single prop named by
59
+ * `PropsOf<typeof Class>["key"]`, which is how imported source types its
60
+ * lifted consts.
61
+ */
62
+ export type MergedModel<U> = { [K in KeysOfUnion<U>]?: U extends unknown ? (K extends keyof U ? U[K] : never) : never };
34
63
 
35
64
  export type QueryProps<M, T extends string> = QueryModel<M> & {
36
65
  /** Overrides the panel's datasource for this query. */
@@ -51,6 +80,12 @@ export interface QueryDefinition<T extends string = string, M = Record<string, u
51
80
  defaults?: Partial<M>;
52
81
  /** True only for the queries this package ships. */
53
82
  builtin: boolean;
83
+ /**
84
+ * Other plugin ids Grafana accepts for the same plugin (`aliasIDs` in its
85
+ * plugin.json), e.g. `postgres` for `grafana-postgresql-datasource`. The
86
+ * class is registered under each, so imports and GRAF107 find it by either.
87
+ */
88
+ aliases?: readonly string[];
54
89
  }
55
90
 
56
91
  export interface QueryEntity<T extends string = string, M = Record<string, unknown>> extends Declarable {
@@ -71,9 +106,9 @@ function registry(): Map<string, QueryDefinition> {
71
106
  return (g[REGISTRY_KEY] ??= new Map());
72
107
  }
73
108
 
74
- /** Every registered query definition, keyed by datasource type. */
109
+ /** Every registered query definition, once each (a definition with aliases is registered under several types). */
75
110
  export function registeredQueries(): QueryDefinition[] {
76
- return [...registry().values()];
111
+ return [...new Set(registry().values())];
77
112
  }
78
113
 
79
114
  export function queryDefinitionFor(datasourceType: string): QueryDefinition | undefined {
@@ -85,7 +120,7 @@ function makeQueryClass<T extends string, M>(def: QueryDefinition<T, M>): QueryC
85
120
  if (existing?.builtin && !def.builtin) {
86
121
  throw new Error(`grafana: queries for "${def.datasourceType}" are built in (${existing.className}); use that class.`);
87
122
  }
88
- registry().set(def.datasourceType, def as unknown as QueryDefinition);
123
+ for (const type of [def.datasourceType, ...(def.aliases ?? [])]) registry().set(type, def as unknown as QueryDefinition);
89
124
  const Base = createProperty(`${QUERY_TYPE_PREFIX}${def.datasourceType}`, "grafana") as unknown as (
90
125
  this: object,
91
126
  props: Record<string, unknown>,
@@ -104,11 +139,11 @@ function makeQueryClass<T extends string, M>(def: QueryDefinition<T, M>): QueryC
104
139
  *
105
140
  * @example
106
141
  * ```ts
107
- * interface ElasticQueryModel { query: string; metrics?: Array<{ type: string; id: string }>; refId?: string }
142
+ * interface OpenSearchQueryModel { query: string; queryType?: "lucene" | "PPL"; refId?: string }
108
143
  *
109
- * export const ElasticQuery = defineQuery<ElasticQueryModel>()({
110
- * datasourceType: "elasticsearch",
111
- * className: "ElasticQuery",
144
+ * export const OpenSearchQuery = defineQuery<OpenSearchQueryModel>()({
145
+ * datasourceType: "grafana-opensearch-datasource",
146
+ * className: "OpenSearchQuery",
112
147
  * expressionField: "query",
113
148
  * });
114
149
  * ```
@@ -160,6 +195,136 @@ export const LokiQuery = makeQueryClass<"loki", LokiDataquery>({
160
195
  builtin: true,
161
196
  });
162
197
 
198
+ /**
199
+ * An Elasticsearch query: `query` is Lucene (or the query string of a raw
200
+ * data, raw document or logs query), `metrics` and `bucketAggs` the
201
+ * aggregations.
202
+ */
203
+ export const ElasticsearchQuery = makeQueryClass<"elasticsearch", ElasticsearchDataquery>({
204
+ datasourceType: "elasticsearch",
205
+ className: "ElasticsearchQuery",
206
+ description: "An Elasticsearch query; query is Lucene, metrics and bucketAggs the aggregations",
207
+ schema: "elasticsearch",
208
+ expressionField: "query",
209
+ builtin: true,
210
+ });
211
+
212
+ /**
213
+ * A CloudWatch query: a metrics query (`queryMode: "Metrics"`), a Logs
214
+ * Insights query (`queryMode: "Logs"`, `expression` in `queryLanguage`) or an
215
+ * annotation query. The three schema definitions are merged into one type
216
+ * with every field but `queryMode` optional, so each field is one prop;
217
+ * GRAF107 checks the fields against the three definitions. Grafana's editor
218
+ * starts `id` and `region` at `""` and `"default"`
219
+ * (public/app/plugins/datasource/cloudwatch/defaultQueries.ts:12-27 and
220
+ * 41-52 at v13.2.2), and the backend reads a missing region as the
221
+ * datasource's default (pkg/tsdb/cloudwatch/cloudwatch.go:65).
222
+ */
223
+ export const CloudWatchQuery = makeQueryClass<"cloudwatch", MergedModel<CloudWatchRequest> & Pick<CloudWatchRequest, "queryMode">>({
224
+ datasourceType: "cloudwatch",
225
+ className: "CloudWatchQuery",
226
+ description: "A CloudWatch metrics, Logs Insights or annotation query, by queryMode",
227
+ schema: "cloudwatch",
228
+ expressionField: "expression",
229
+ builtin: true,
230
+ });
231
+
232
+ /**
233
+ * An Azure Monitor query: `queryType` picks which of `azureMonitor`,
234
+ * `azureLogAnalytics` (KQL), `azureResourceGraph` or `azureTraces` holds it.
235
+ */
236
+ export const AzureMonitorQuery = makeQueryClass<"grafana-azure-monitor-datasource", AzureMonitorDataquery>({
237
+ datasourceType: "grafana-azure-monitor-datasource",
238
+ className: "AzureMonitorQuery",
239
+ description: "An Azure Monitor metrics, Log Analytics (KQL), Resource Graph or traces query, by queryType",
240
+ schema: "azuremonitor",
241
+ builtin: true,
242
+ });
243
+
244
+ /**
245
+ * A Google Cloud Monitoring query (plugin id `stackdriver`): `queryType`
246
+ * picks which of `timeSeriesList`, `timeSeriesQuery` (MQL), `sloQuery` or
247
+ * `promQLQuery` holds it.
248
+ */
249
+ export const CloudMonitoringQuery = makeQueryClass<"stackdriver", CloudMonitoringDataquery>({
250
+ datasourceType: "stackdriver",
251
+ className: "CloudMonitoringQuery",
252
+ description: "A Google Cloud Monitoring time series, MQL, SLO or PromQL query, by queryType",
253
+ schema: "googlecloudmonitoring",
254
+ builtin: true,
255
+ });
256
+
257
+ /**
258
+ * A BigQuery query: `rawSql` is GoogleSQL. `format` and `rawSql` may be left
259
+ * out: the plugin fills in `table` and `""` (src/utils.ts:147-160 in
260
+ * grafana/google-bigquery-datasource v3.4.2).
261
+ */
262
+ export const BigQueryQuery = makeQueryClass<"grafana-bigquery-datasource", Loosen<BigQueryDataquery, "format" | "rawSql">>({
263
+ datasourceType: "grafana-bigquery-datasource",
264
+ className: "BigQueryQuery",
265
+ description: "A BigQuery query; rawSql is GoogleSQL",
266
+ schema: "bigquery",
267
+ expressionField: "rawSql",
268
+ builtin: true,
269
+ });
270
+
271
+ /**
272
+ * A Grafana Pyroscope query: `labelSelector` selects the profiles of
273
+ * `profileTypeId`. `labelSelector` and `groupBy` may be left out: the plugin
274
+ * starts them at `{}` and none (dataquery.ts:67-75 and datasource.ts:156-164
275
+ * in grafana-pyroscope-datasource 13.0.4, the version bundled with
276
+ * grafana/grafana:13.2.2).
277
+ */
278
+ export const PyroscopeQuery = makeQueryClass<"grafana-pyroscope-datasource", Loosen<PyroscopeDataquery, "labelSelector" | "groupBy">>({
279
+ datasourceType: "grafana-pyroscope-datasource",
280
+ className: "PyroscopeQuery",
281
+ description: "A Grafana Pyroscope profile or metrics query; labelSelector selects the profiles",
282
+ schema: "grafanapyroscope",
283
+ expressionField: "labelSelector",
284
+ builtin: true,
285
+ });
286
+
287
+ /**
288
+ * A PostgreSQL query: `rawSql` is SQL with Grafana's macros. Also used for
289
+ * the plugin's old id, `postgres` (an `aliasIDs` entry in the plugin.json of
290
+ * grafana-postgresql-datasource 13.0.3, bundled with grafana/grafana:13.2.2).
291
+ */
292
+ export const PostgresQuery = makeQueryClass<"grafana-postgresql-datasource" | "postgres", SqlQueryModel>({
293
+ datasourceType: "grafana-postgresql-datasource",
294
+ aliases: ["postgres"],
295
+ className: "PostgresQuery",
296
+ description: "A PostgreSQL query; rawSql is SQL with Grafana's macros",
297
+ expressionField: "rawSql",
298
+ builtin: true,
299
+ });
300
+
301
+ /** A MySQL query: `rawSql` is SQL with Grafana's macros. */
302
+ export const MySQLQuery = makeQueryClass<"mysql", SqlQueryModel>({
303
+ datasourceType: "mysql",
304
+ className: "MySQLQuery",
305
+ description: "A MySQL query; rawSql is SQL with Grafana's macros",
306
+ expressionField: "rawSql",
307
+ builtin: true,
308
+ });
309
+
310
+ /** A Microsoft SQL Server query: `rawSql` is T-SQL with Grafana's macros. */
311
+ export const MSSQLQuery = makeQueryClass<"mssql", SqlQueryModel>({
312
+ datasourceType: "mssql",
313
+ className: "MSSQLQuery",
314
+ description: "A Microsoft SQL Server query; rawSql is T-SQL with Grafana's macros",
315
+ expressionField: "rawSql",
316
+ builtin: true,
317
+ });
318
+
163
319
  export type PromQueryEntity = InstanceType<typeof PromQuery>;
164
320
  export type TempoQueryEntity = InstanceType<typeof TempoQuery>;
165
321
  export type LokiQueryEntity = InstanceType<typeof LokiQuery>;
322
+ export type ElasticsearchQueryEntity = InstanceType<typeof ElasticsearchQuery>;
323
+ export type CloudWatchQueryEntity = InstanceType<typeof CloudWatchQuery>;
324
+ export type AzureMonitorQueryEntity = InstanceType<typeof AzureMonitorQuery>;
325
+ export type CloudMonitoringQueryEntity = InstanceType<typeof CloudMonitoringQuery>;
326
+ export type BigQueryQueryEntity = InstanceType<typeof BigQueryQuery>;
327
+ export type PyroscopeQueryEntity = InstanceType<typeof PyroscopeQuery>;
328
+ export type PostgresQueryEntity = InstanceType<typeof PostgresQuery>;
329
+ export type MySQLQueryEntity = InstanceType<typeof MySQLQuery>;
330
+ export type MSSQLQueryEntity = InstanceType<typeof MSSQLQuery>;