@intentius/chant-lexicon-grafana 0.99.0 → 0.100.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -6
- package/dist/annotations.d.ts +61 -0
- package/dist/annotations.d.ts.map +1 -0
- package/dist/api/apply.d.ts +8 -4
- package/dist/api/apply.d.ts.map +1 -1
- package/dist/api/fake-grafana.d.ts +2 -0
- package/dist/api/fake-grafana.d.ts.map +1 -1
- package/dist/api/folders.d.ts +53 -17
- package/dist/api/folders.d.ts.map +1 -1
- package/dist/build.d.ts +26 -5
- package/dist/build.d.ts.map +1 -1
- package/dist/catalog.d.ts +1 -1
- package/dist/catalog.d.ts.map +1 -1
- package/dist/codegen/docs.d.ts.map +1 -1
- package/dist/codegen/generate.d.ts +7 -1
- package/dist/codegen/generate.d.ts.map +1 -1
- package/dist/composites/shared.d.ts +4 -3
- package/dist/composites/shared.d.ts.map +1 -1
- package/dist/dashboard.d.ts +42 -14
- package/dist/dashboard.d.ts.map +1 -1
- package/dist/datasource-refs.d.ts +9 -5
- package/dist/datasource-refs.d.ts.map +1 -1
- package/dist/datasource-settings.d.ts +834 -0
- package/dist/datasource-settings.d.ts.map +1 -0
- package/dist/datasource.d.ts +53 -11
- package/dist/datasource.d.ts.map +1 -1
- package/dist/deep-observe-hooks.d.ts +1 -1
- package/dist/deep-observe-hooks.d.ts.map +1 -1
- package/dist/deep-observe.d.ts +9 -4
- package/dist/deep-observe.d.ts.map +1 -1
- package/dist/describe-resources.d.ts +1 -0
- package/dist/describe-resources.d.ts.map +1 -1
- package/dist/detect.d.ts +4 -2
- package/dist/detect.d.ts.map +1 -1
- package/dist/folder.d.ts +68 -0
- package/dist/folder.d.ts.map +1 -0
- package/dist/import/generator.d.ts.map +1 -1
- package/dist/import/mappings.d.ts +12 -6
- package/dist/import/mappings.d.ts.map +1 -1
- package/dist/import/model.d.ts +7 -0
- package/dist/import/model.d.ts.map +1 -1
- package/dist/import/normalize.d.ts.map +1 -1
- package/dist/import/parser.d.ts +16 -1
- package/dist/import/parser.d.ts.map +1 -1
- package/dist/import/testdata/fixtures.d.ts +6 -0
- package/dist/import/testdata/fixtures.d.ts.map +1 -1
- package/dist/import/v2.d.ts +4 -3
- package/dist/import/v2.d.ts.map +1 -1
- package/dist/index.d.ts +11 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/init-templates.d.ts +27 -0
- package/dist/init-templates.d.ts.map +1 -0
- package/dist/integrity.json +11 -9
- package/dist/lint/audit-catalog.d.ts +1 -1
- package/dist/lint/audit-catalog.d.ts.map +1 -1
- package/dist/lint/post-synth/graf101.d.ts +1 -1
- package/dist/lint/post-synth/graf104.d.ts +2 -2
- package/dist/lint/post-synth/graf106.d.ts +2 -2
- package/dist/lint/post-synth/graf110.d.ts +8 -0
- package/dist/lint/post-synth/graf110.d.ts.map +1 -0
- package/dist/lint/post-synth/grafana-helpers.d.ts.map +1 -1
- package/dist/lint/post-synth/index.d.ts.map +1 -1
- package/dist/manifest.json +1 -1
- package/dist/meta.json +70 -0
- package/dist/okf/index.md +18 -3
- package/dist/okf/rules/GRAF101.md +2 -2
- package/dist/okf/rules/GRAF104.md +2 -2
- package/dist/okf/rules/GRAF106.md +2 -2
- package/dist/okf/rules/GRAF110.md +11 -0
- package/dist/okf/types/AdhocVariable.md +9 -0
- package/dist/okf/types/AzureMonitorQuery.md +9 -0
- package/dist/okf/types/BigQueryQuery.md +9 -0
- package/dist/okf/types/CloudMonitoringQuery.md +9 -0
- package/dist/okf/types/CloudWatchQuery.md +9 -0
- package/dist/okf/types/Dashboard.md +1 -1
- package/dist/okf/types/Datasource.md +1 -1
- package/dist/okf/types/DatasourceProvisioning.md +9 -0
- package/dist/okf/types/ElasticsearchQuery.md +9 -0
- package/dist/okf/types/ExternalDatasource.md +1 -1
- package/dist/okf/types/Folder.md +9 -0
- package/dist/okf/types/GroupByVariable.md +9 -0
- package/dist/okf/types/MSSQLQuery.md +9 -0
- package/dist/okf/types/MySQLQuery.md +9 -0
- package/dist/okf/types/PostgresQuery.md +9 -0
- package/dist/okf/types/PyroscopeQuery.md +9 -0
- package/dist/okf/types/SwitchVariable.md +9 -0
- package/dist/op/activities/grafana-apply.d.ts +11 -0
- package/dist/op/activities/grafana-apply.d.ts.map +1 -1
- package/dist/op/activities/index.d.ts +1 -1
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/ownership.d.ts +11 -0
- package/dist/ownership.d.ts.map +1 -1
- package/dist/pin.d.ts +1 -1
- package/dist/pin.d.ts.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/promql-check.d.ts.map +1 -1
- package/dist/query-models.d.ts +95 -0
- package/dist/query-models.d.ts.map +1 -0
- package/dist/query.d.ts +107 -10
- package/dist/query.d.ts.map +1 -1
- package/dist/rules/graf101.ts +2 -2
- package/dist/rules/graf104.ts +3 -3
- package/dist/rules/graf106.ts +3 -3
- package/dist/rules/graf110.ts +17 -0
- package/dist/rules/grafana-helpers.ts +13 -3
- package/dist/schema/azuremonitor.gen.d.ts +354 -0
- package/dist/schema/azuremonitor.gen.d.ts.map +1 -0
- package/dist/schema/bigquery.gen.d.ts +93 -0
- package/dist/schema/bigquery.gen.d.ts.map +1 -0
- package/dist/schema/cloudwatch.gen.d.ts +394 -0
- package/dist/schema/cloudwatch.gen.d.ts.map +1 -0
- package/dist/schema/elasticsearch.gen.d.ts +444 -0
- package/dist/schema/elasticsearch.gen.d.ts.map +1 -0
- package/dist/schema/googlecloudmonitoring.gen.d.ts +312 -0
- package/dist/schema/googlecloudmonitoring.gen.d.ts.map +1 -0
- package/dist/schema/grafanapyroscope.gen.d.ts +60 -0
- package/dist/schema/grafanapyroscope.gen.d.ts.map +1 -0
- package/dist/schema/index.d.ts +6 -0
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/table.gen.d.ts +13 -0
- package/dist/schema/table.gen.d.ts.map +1 -1
- package/dist/schema-validate.d.ts +9 -0
- package/dist/schema-validate.d.ts.map +1 -1
- package/dist/skill-defs.d.ts.map +1 -1
- package/dist/skills/chant-grafana-operations.md +108 -0
- package/dist/skills/chant-grafana.md +5 -4
- package/dist/spec/schemas.d.ts +9 -7
- package/dist/spec/schemas.d.ts.map +1 -1
- package/dist/spec/schemas.gen.d.ts +3 -0
- package/dist/spec/schemas.gen.d.ts.map +1 -0
- package/dist/validate-alerting.d.ts.map +1 -1
- package/dist/validate-output.d.ts +15 -2
- package/dist/validate-output.d.ts.map +1 -1
- package/dist/validate.d.ts.map +1 -1
- package/dist/validation.d.ts +14 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/variables.d.ts +102 -5
- package/dist/variables.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/annotations.ts +84 -0
- package/src/api/apply.test.ts +19 -1
- package/src/api/apply.ts +18 -8
- package/src/api/fake-grafana.ts +6 -2
- package/src/api/folders.ts +106 -32
- package/src/build.test.ts +142 -6
- package/src/build.ts +126 -24
- package/src/catalog.ts +20 -2
- package/src/codegen/docs.ts +13 -6
- package/src/codegen/generate.ts +27 -1
- package/src/composites/shared.ts +3 -2
- package/src/dashboard.ts +59 -14
- package/src/datasource-refs.ts +30 -6
- package/src/datasource-settings.test.ts +106 -0
- package/src/datasource-settings.ts +941 -0
- package/src/datasource.ts +71 -11
- package/src/deep-observe-hooks.ts +16 -1
- package/src/deep-observe.test.ts +47 -1
- package/src/deep-observe.ts +36 -13
- package/src/describe-resources.test.ts +16 -0
- package/src/describe-resources.ts +36 -1
- package/src/detect.ts +3 -3
- package/src/folder.ts +118 -0
- package/src/generated/lexicon-grafana.json +70 -0
- package/src/import/generated-types.e2e.test.ts +15 -3
- package/src/import/generator.ts +2 -1
- package/src/import/mappings.ts +98 -26
- package/src/import/model.ts +7 -0
- package/src/import/normalize.ts +32 -3
- package/src/import/parser.test.ts +97 -21
- package/src/import/parser.ts +169 -18
- package/src/import/roundtrip.test.ts +104 -9
- package/src/import/testdata/fixtures.ts +8 -0
- package/src/import/v2.ts +4 -3
- package/src/index.ts +122 -13
- package/src/init-templates.test.ts +74 -0
- package/src/init-templates.ts +349 -0
- package/src/lint/audit-catalog.ts +9 -1
- package/src/lint/post-synth/graf101.ts +2 -2
- package/src/lint/post-synth/graf104.ts +3 -3
- package/src/lint/post-synth/graf106.ts +3 -3
- package/src/lint/post-synth/graf110.ts +17 -0
- package/src/lint/post-synth/grafana-helpers.ts +13 -3
- package/src/lint/post-synth/index.ts +2 -0
- package/src/lint/post-synth/post-synth.test.ts +99 -2
- package/src/load-cost.test.ts +35 -0
- package/src/op/activities/grafana-apply.e2e.test.ts +14 -5
- package/src/op/activities/grafana-apply.test.ts +16 -2
- package/src/op/activities/grafana-apply.ts +29 -3
- package/src/op/activities/index.ts +1 -1
- package/src/ownership.ts +19 -0
- package/src/panels.test.ts +3 -1
- package/src/pin.ts +12 -0
- package/src/plugin.test.ts +9 -2
- package/src/plugin.ts +9 -0
- package/src/promql-check.ts +5 -1
- package/src/query-models.ts +94 -0
- package/src/query.test.ts +166 -0
- package/src/query.ts +177 -12
- package/src/schema/azuremonitor.gen.ts +381 -0
- package/src/schema/bigquery.gen.ts +110 -0
- package/src/schema/cloudwatch.gen.ts +423 -0
- package/src/schema/elasticsearch.gen.ts +508 -0
- package/src/schema/googlecloudmonitoring.gen.ts +330 -0
- package/src/schema/grafanapyroscope.gen.ts +66 -0
- package/src/schema/index.ts +6 -0
- package/src/schema/table.gen.ts +14 -0
- package/src/schema-validate.ts +58 -7
- package/src/skill-defs.ts +17 -0
- package/src/skills/chant-grafana-operations.md +108 -0
- package/src/skills/chant-grafana.md +5 -4
- package/src/spec/overlay/table.overlay.json +19 -0
- package/src/spec/schemas/azuremonitor.jsonschema.json +679 -0
- package/src/spec/schemas/bigquery.jsonschema.json +290 -0
- package/src/spec/schemas/cloudwatch.jsonschema.json +721 -0
- package/src/spec/schemas/elasticsearch.jsonschema.json +1695 -0
- package/src/spec/schemas/googlecloudmonitoring.jsonschema.json +488 -0
- package/src/spec/schemas/grafanapyroscope.jsonschema.json +93 -0
- package/src/spec/schemas.gen.ts +40 -0
- package/src/spec/schemas.ts +9 -7
- package/src/validate-alerting.ts +4 -2
- package/src/validate-output.ts +96 -4
- package/src/validate.ts +14 -0
- package/src/validation.ts +27 -0
- package/src/variables.ts +113 -6
|
@@ -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
|
|
3
|
-
* `TempoQuery` (TraceQL)
|
|
4
|
-
*
|
|
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
|
-
/**
|
|
33
|
-
|
|
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,
|
|
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(
|
|
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
|
|
142
|
+
* interface OpenSearchQueryModel { query: string; queryType?: "lucene" | "PPL"; refId?: string }
|
|
108
143
|
*
|
|
109
|
-
* export const
|
|
110
|
-
* datasourceType: "
|
|
111
|
-
* className: "
|
|
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>;
|