Skip to content

Commit 0c0bb3b

Browse files
committed
feat(grafana): add data source querying, and ground the skill and templates in real tools
query_data_source closes the largest gap in the integration: 29 tools could read dashboards, folders, and alert configuration, but none could read a metric value. It posts to /api/ds/query and returns both the raw response and the frames flattened into rows. The flattening is derived from the documented layout rather than any data source's field names: a frame carries schema.fields[] alongside data.values[], where values[i] is the whole column for fields[i], so zipping them by position works for Prometheus, SQL, or anything else with a backend. A failed query is a 400 by Grafana's own status table, so it stays a tool error — unlike the health check, where the failure status carries the answer. That also lets four templates and the review-firing-alerts skill stop promising things the integration could not do. Three templates assumed a metric-query tool, which now exists. The fourth, and the skill, assumed live alert instance state, which the provisioning API never returns — they now derive firing rules from alert-state annotations, which are documented to carry newState and prevState, and say so explicitly rather than implying a live snapshot. Deliberately not added: a tool over /api/prometheus/grafana/api/v1/rules for live instance state. That endpoint appears on no Grafana HTTP API doc page, its response is only readable from Go internals and test assertions, and the instance-level state casing differs from the rule level with no documented contract. Not something to build an output schema on.
1 parent dea05b3 commit 0c0bb3b

10 files changed

Lines changed: 323 additions & 9 deletions

File tree

apps/docs/content/docs/en/integrations/grafana.mdx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -935,4 +935,32 @@ Read an alert rule group: its evaluation interval and every rule in it. The inte
935935
|`notification_settings` | json | Per-rule notification settings \(overrides\) |
936936
|`record` | json | Recording rule configuration \(recording rules only\) |
937937

938+
### Grafana Query Data Source
939+
940+
Run one or more queries against a Grafana data source that has a backend implementation, and read the values back. This is how you get actual metric numbers out of Grafana rather than dashboard or alert configuration.
941+
942+
#### Input
943+
944+
| Parameter | Type | Required | Description |
945+
| --------- | ---- | -------- | ----------- |
946+
| `apiKey` | string | Yes | Grafana Service Account Token |
947+
| `baseUrl` | string | Yes | Grafana instance URL \(e.g., https://your-grafana.com\) |
948+
| `organizationId` | string | No | Organization ID for multi-org Grafana instances \(e.g., 1, 2\) |
949+
| `queries` | string | Yes | JSON array of at least one query. Each needs a datasource.uid and a refId, plus the fields that data source expects — expr for Prometheus, rawSql for SQL. Example: \[\{"refId":"A","datasource":\{"uid":"P123"\},"expr":"up","format":"time_series"\}\] |
950+
| `from` | string | No | Start of the time range, either epoch milliseconds or Grafana relative time \(e.g., now-5m\). Defaults to now-1h |
951+
| `to` | string | No | End of the time range, epoch milliseconds or relative \(e.g., now\) |
952+
953+
#### Output
954+
955+
| Parameter | Type | Description |
956+
| --------- | ---- | ----------- |
957+
| `results` | json | Raw Grafana response, keyed by each query refId, each holding the frames that query produced |
958+
| `series` | array | The same frames flattened into rows, so values can be read without walking the columnar layout |
959+
|`refId` | string | The query this frame came from |
960+
|`fields` | array | Field metadata in column order |
961+
|`name` | string | Field name, e.g. time or A-series |
962+
|`type` | string | Field type, e.g. time or number |
963+
|`rowCount` | number | Number of rows in the frame |
964+
|`rows` | array | Rows keyed by field name |
965+
938966

apps/sim/blocks/blocks/grafana.ts

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export const GrafanaBlock: BlockConfig<GrafanaResponse> = {
5252
grafana_delete_alert_rule: [
5353
{ text: 'Delete alert rule', field: 'alertRuleUid', core: true },
5454
],
55+
grafana_query_data_source: ['Query a data source', { text: ', over', field: 'queryFrom' }],
5556
grafana_list_contact_points: [
5657
'List contact points',
5758
{ text: ', named', field: 'contactPointName' },
@@ -141,6 +142,7 @@ export const GrafanaBlock: BlockConfig<GrafanaResponse> = {
141142
{ label: 'Delete Annotation', id: 'grafana_delete_annotation' },
142143
{ label: 'List Data Sources', id: 'grafana_list_data_sources' },
143144
{ label: 'Get Data Source', id: 'grafana_get_data_source' },
145+
{ label: 'Query Data Source', id: 'grafana_query_data_source' },
144146
{ label: 'Check Data Source Health', id: 'grafana_check_data_source_health' },
145147
{ label: 'List Folders', id: 'grafana_list_folders' },
146148
{ label: 'Create Folder', id: 'grafana_create_folder' },
@@ -920,6 +922,47 @@ Return ONLY the folder title - no explanations, no quotes, no extra text.`,
920922
condition: { field: 'operation', value: 'grafana_delete_folder' },
921923
},
922924

925+
{
926+
id: 'dataSourceQueries',
927+
title: 'Queries (JSON)',
928+
type: 'long-input',
929+
placeholder: '[{"refId":"A","datasource":{"uid":"P123"},"expr":"up","format":"time_series"}]',
930+
required: { field: 'operation', value: 'grafana_query_data_source' },
931+
condition: { field: 'operation', value: 'grafana_query_data_source' },
932+
wandConfig: {
933+
enabled: true,
934+
prompt: `Generate a Grafana /api/ds/query queries array based on the user's request.
935+
936+
Rules:
937+
- Always a JSON array with at least one query object
938+
- Every query needs a refId (e.g. "A") and datasource.uid
939+
- Add the fields that data source expects: expr for Prometheus/Loki, rawSql for SQL sources
940+
- format is "time_series" or "table"
941+
942+
Examples:
943+
- [{"refId":"A","datasource":{"uid":"PROM_UID"},"expr":"rate(http_requests_total[5m])","format":"time_series"}]
944+
- [{"refId":"A","datasource":{"uid":"PG_UID"},"rawSql":"SELECT now() AS time, count(*) AS c FROM orders","format":"table"}]
945+
946+
Return ONLY the JSON array - no explanations, no markdown, no extra text.`,
947+
placeholder: 'Describe the metric or query you want...',
948+
generationType: 'json-array',
949+
},
950+
},
951+
{
952+
id: 'queryFrom',
953+
title: 'From',
954+
type: 'short-input',
955+
placeholder: 'now-1h or epoch milliseconds',
956+
condition: { field: 'operation', value: 'grafana_query_data_source' },
957+
},
958+
{
959+
id: 'queryTo',
960+
title: 'To',
961+
type: 'short-input',
962+
placeholder: 'now or epoch milliseconds',
963+
mode: 'advanced',
964+
condition: { field: 'operation', value: 'grafana_query_data_source' },
965+
},
923966
{
924967
id: 'contactPointUid',
925968
title: 'Contact Point UID',
@@ -1052,6 +1095,7 @@ Return ONLY the JSON object - no explanations, no markdown, no extra text.`,
10521095
'grafana_delete_contact_point',
10531096
'grafana_move_folder',
10541097
'grafana_get_alert_rule_group',
1098+
'grafana_query_data_source',
10551099
],
10561100
config: {
10571101
tool: (params) => params.operation,
@@ -1088,6 +1132,11 @@ Return ONLY the JSON object - no explanations, no markdown, no extra text.`,
10881132
if (params.contactPointType) result.type = params.contactPointType
10891133
if (params.contactPointSettings) result.settings = params.contactPointSettings
10901134
break
1135+
case 'grafana_query_data_source':
1136+
result.queries = params.dataSourceQueries
1137+
if (params.queryFrom) result.from = params.queryFrom
1138+
if (params.queryTo) result.to = params.queryTo
1139+
break
10911140
case 'grafana_move_folder':
10921141
result.folderUid = params.manageFolderUid
10931142
result.parentUid = params.newParentUid ?? ''
@@ -1147,6 +1196,9 @@ Return ONLY the JSON object - no explanations, no markdown, no extra text.`,
11471196
},
11481197
},
11491198
inputs: {
1199+
dataSourceQueries: { type: 'string', description: 'JSON array of data source queries' },
1200+
queryFrom: { type: 'string', description: 'Query range start, relative or epoch ms' },
1201+
queryTo: { type: 'string', description: 'Query range end, relative or epoch ms' },
11501202
contactPointUid: {
11511203
type: 'string',
11521204
description: 'UID of the contact point to update or delete',
@@ -1260,6 +1312,15 @@ Return ONLY the JSON object - no explanations, no markdown, no extra text.`,
12601312
dataSourceUid: { type: 'string', description: 'Data source UID for health checks' },
12611313
},
12621314
outputs: {
1315+
results: {
1316+
type: 'json',
1317+
description: 'Raw data source query response, keyed by query refId',
1318+
},
1319+
series: {
1320+
type: 'array',
1321+
description:
1322+
'Query frames flattened into rows (refId, fields, rowCount, rows) so values can be read directly',
1323+
},
12631324
interval: {
12641325
type: 'number',
12651326
description: 'Evaluation interval of an alert rule group, in seconds',
@@ -1416,7 +1477,7 @@ export const GrafanaBlockMeta = {
14161477
icon: GrafanaIcon,
14171478
title: 'Grafana alert auto-context',
14181479
prompt:
1419-
'Build a scheduled workflow that polls Grafana for firing alert rules, pulls related logs and recent deploys, summarizes them with an agent, and posts the enriched alert to PagerDuty and Slack.',
1480+
'Build a scheduled workflow that reads Grafana alert-state annotations to find rules that just started firing, queries the underlying data source for the current metric value, summarizes the two together with an agent, and posts the enriched alert to PagerDuty and Slack.',
14201481
modules: ['scheduled', 'agent', 'workflows'],
14211482
category: 'engineering',
14221483
tags: ['devops', 'monitoring'],
@@ -1426,7 +1487,7 @@ export const GrafanaBlockMeta = {
14261487
icon: GrafanaIcon,
14271488
title: 'Grafana SLO scorecard',
14281489
prompt:
1429-
'Create a scheduled weekly workflow that queries Grafana for SLO compliance across services, calculates burn rates, and writes a scorecard to a tables-based SRE review board.',
1490+
'Create a scheduled weekly workflow that runs SLI queries against a Grafana data source for each service, calculates error budget burn rates from the returned series, and writes a scorecard to a tables-based SRE review board.',
14301491
modules: ['scheduled', 'tables', 'agent', 'workflows'],
14311492
category: 'engineering',
14321493
tags: ['devops', 'reporting'],
@@ -1444,7 +1505,7 @@ export const GrafanaBlockMeta = {
14441505
icon: GrafanaIcon,
14451506
title: 'Grafana metric export',
14461507
prompt:
1447-
'Create a workflow that exports Grafana metric queries on schedule into a Sim table, so the data can be combined with business metrics for unified reporting.',
1508+
'Create a workflow that runs a set of Grafana data source queries on schedule and writes the returned series into a Sim table, so the metrics can be combined with business data for unified reporting.',
14481509
modules: ['scheduled', 'tables', 'agent', 'workflows'],
14491510
category: 'engineering',
14501511
tags: ['analysis', 'sync'],
@@ -1473,7 +1534,7 @@ export const GrafanaBlockMeta = {
14731534
icon: GrafanaIcon,
14741535
title: 'Grafana + Linear feature-impact',
14751536
prompt:
1476-
'Build a scheduled workflow that polls Grafana for metric regressions correlated with recent Linear releases and posts a regression review to the team Slack with the suspected change.',
1537+
'Build a scheduled workflow that queries a Grafana data source for latency and error rates, compares each series against the prior period to spot regressions, correlates them with recent Linear releases, and posts a regression review to the team Slack with the suspected change.',
14771538
modules: ['scheduled', 'agent', 'workflows'],
14781539
category: 'engineering',
14791540
tags: ['engineering', 'analysis'],
@@ -1493,7 +1554,7 @@ export const GrafanaBlockMeta = {
14931554
description:
14941555
'List Grafana alert rules and surface those currently firing with their contact points.',
14951556
content:
1496-
'# Review Firing Alerts\n\nProduce a snapshot of alerting health for an on-call handoff or incident triage.\n\n## Steps\n1. List alert rules and capture each rule name, condition, and current state.\n2. Get details on rules that are firing or in a pending state.\n3. List contact points so each firing rule can be mapped to who gets notified.\n4. Group findings by severity or folder.\n\n## Output\nReturn a list of firing and pending alerts with rule name, state, and notification target, plus a count of healthy rules. Suitable for an on-call digest.',
1557+
"# Review Firing Alerts\n\nProduce a snapshot of alerting health for an on-call handoff or incident triage.\n\n## Steps\n1. Run List Annotations with `type: 'alert'` over the window you care about. Alert-state transitions are recorded as annotations and carry `newState` and `prevState`, which is how you find what actually fired — the alert rule operations return rule *definitions*, never live instance state.\n2. Run List Alert Rules to join each firing rule id back to its title, folder, condition, and labels.\n3. Optionally run Query Data Source on the rule's own query to see how far the metric is from its threshold right now.\n4. Run List Contact Points, and Get Alert Rule Group for the evaluation interval, so each firing rule maps to who gets notified and how often it is checked.\n5. Group findings by severity label or folder.\n\n## Output\nReturn the rules that transitioned into a firing state in the window, each with its title, the transition, its notification target, and its group evaluation interval. Say explicitly that this is derived from state-change annotations rather than a live instance snapshot, and give the window covered.",
14971558
},
14981559
{
14991560
name: 'audit-dashboards',

apps/sim/lib/integrations/integrations.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9082,6 +9082,10 @@
90829082
"name": "Get Data Source",
90839083
"description": "Get a data source by its ID or UID"
90849084
},
9085+
{
9086+
"name": "Query Data Source",
9087+
"description": "Run one or more queries against a Grafana data source that has a backend implementation, and read the values back. This is how you get actual metric numbers out of Grafana rather than dashboard or alert configuration."
9088+
},
90859089
{
90869090
"name": "Check Data Source Health",
90879091
"description": "Test connectivity to a data source by its UID"
@@ -9115,7 +9119,7 @@
91159119
"description": "Check the health of the Grafana instance (version, database status)"
91169120
}
91179121
],
9118-
"operationCount": 29,
9122+
"operationCount": 30,
91199123
"triggers": [],
91209124
"triggerCount": 0,
91219125
"authType": "api-key",

apps/sim/tools/generated/tool-ids.ts

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

apps/sim/tools/generated/tool-metadata.ts

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

apps/sim/tools/generated/tool-outputs.ts

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

apps/sim/tools/grafana/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { listDashboardsTool } from '@/tools/grafana/list_dashboards'
2222
import { listDataSourcesTool } from '@/tools/grafana/list_data_sources'
2323
import { listFoldersTool } from '@/tools/grafana/list_folders'
2424
import { moveFolderTool } from '@/tools/grafana/move_folder'
25+
import { queryDataSourceTool } from '@/tools/grafana/query_data_source'
2526
import { updateAlertRuleTool } from '@/tools/grafana/update_alert_rule'
2627
import { updateAnnotationTool } from '@/tools/grafana/update_annotation'
2728
import { updateContactPointTool } from '@/tools/grafana/update_contact_point'
@@ -44,6 +45,7 @@ export const grafanaCreateContactPointTool = createContactPointTool
4445
export const grafanaUpdateContactPointTool = updateContactPointTool
4546
export const grafanaDeleteContactPointTool = deleteContactPointTool
4647
export const grafanaMoveFolderTool = moveFolderTool
48+
export const grafanaQueryDataSourceTool = queryDataSourceTool
4749
export const grafanaGetAlertRuleGroupTool = getAlertRuleGroupTool
4850

4951
export const grafanaCreateAnnotationTool = createAnnotationTool

0 commit comments

Comments
 (0)