Skip to content

fix(grafana): validate against the API docs, add data source querying and contact-point CRUD - #6712

Merged
waleedlatif1 merged 9 commits into
stagingfrom
fix/adx-tags-and-grafana-validation
Aug 14, 2026
Merged

fix(grafana): validate against the API docs, add data source querying and contact-point CRUD#6712
waleedlatif1 merged 9 commits into
stagingfrom
fix/adx-tags-and-grafana-validation

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • Fix the Azure Data Explorer tags ingestion-property example, which rendered as invalid Kusto (tags="[''daily'']"); the reference writes a tags list as tags='["TagA","TagB"]'
  • Validate the Grafana integration against Grafana's HTTP API reference and, where the published docs contradict themselves, against the Go wire structs — fixing 23 findings
  • Add 5 net-new Grafana operations, taking it from 25 to 30

Grafana: response shapes the tools got wrong

  • update_annotation declared an id that was always 0 — a patch returns only a message, so the request's annotation id is echoed and labelled as such
  • delete_folder discarded the numeric id Grafana returns and presented an input-echoed uid as an API result
  • delete_dashboard fabricated id: 0 / title: '' via || on absent fields
  • the contact-point provenance description was inverted: "api" means API-managed, empty means it stayed UI-editable
  • 25 outputs the tools emit were undeclared on the block and so unreferenceable; get_data_source had 13 of its 18 unreachable
  • version was typed string though the dashboard, folder, and data-source producers all emit a number
  • 10 shared output keys were described for one producer only, and 11 json outputs were opaque although the tools document their inner fields

Grafana: requests that could not succeed

  • create_alert_rule left noDataState and execErrState unset and invisible to the model, but Grafana's validator rejects an empty value outright, so every model-driven create failed. Both are now sent with Grafana's own defaults, and skipped for recording rules, which take a different validator
  • get_data_source routed numeric input at /api/datasources/:id, which exists only behind an off-by-default feature toggle
  • the dashboard title field was shown only for create, so a dashboard could never be renamed through Update Dashboard
  • list_annotations did not trim the dashboard UID, so a padded value matched nothing

Grafana: the health check could only report health

Grafana answers an unhealthy data source with HTTP 400 carrying the same {status, message} payload as a healthy one, and the tool framework converts any non-2xx into an opaque error — so the diagnostic was unreachable. It now goes through an internal route that reads the verdict off either status, while a failure carrying no verdict stays a real error.

Grafana: outbound hardening on the proxy routes

  • the service-account token was re-sent to redirect targets; the shared fetch only drops it when asked
  • no timeout was passed, leaving two sequential hops at the 5-minute default
  • upstream error bodies were interpolated whole into the tool result, putting up to 10MB of HTML into logs and traces
  • UID path segments are URL-encoded so they cannot re-target the request
  • update_folder sent both version and overwrite: true, which Grafana treats as alternatives, making the freshly fetched version decorative and silently clobbering a concurrent rename
  • replaced the any casts with narrowed types

New Grafana operations

  • query_data_source — the largest gap: 29 tools could read configuration but none could read a metric value. Returns the raw /api/ds/query response plus frames flattened into rows, derived from the documented schema.fields[] / data.values[] columnar layout so it works for any backend data source
  • update_contact_point / delete_contact_point — contact points could be listed and created but never corrected or removed. Both verbs answer 202 with only a message, so the UID is echoed; update is a full replace with no PATCH counterpart
  • move_folder — reuses get_folder's mapping; always sends parentUid since Grafana reads an empty value as "move to root"
  • get_alert_rule_group — surfaces the group evaluation interval, the one alerting knob the per-rule operations cannot reach

Four templates and the review-firing-alerts skill previously promised metric queries and live alert state. Three are now grounded in query_data_source; the fourth and the skill derive firing rules from alert-state annotations, which are documented to carry newState/prevState, and say so 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 and its response is only readable from Go internals, so there is no contract to build an output schema on.

Type of Change

  • Bug fix
  • New feature

Testing

813 tests pass, including new coverage for the health-check route and the ADX clause builder; every new guard verified to fail when removed. Type-check clean, all 26 audits pass, docs regenerated.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

The example rendered as tags="[''daily'']" — doubled single quotes from an
escaping slip, which is not valid Kusto. The reference writes a tags list
as tags='["TagA","TagB"]': single outer quotes with the JSON array's own
double quotes inside.

The clause builder already handled that form; only the example text was
wrong. A template literal avoids the escaping entirely, since the metadata
generator reads the source verbatim and would otherwise carry the
backslashes into the description the model sees.

Adds a test asserting the reference's exact multi-property clause
round-trips, including the comma inside the quoted array.
…outbound request hardening

Validated against Grafana's HTTP API reference and, where the docs
contradict themselves, against the Go wire structs.

Response shapes the tools got wrong:
- update_annotation declared an `id` that was always 0; a patch returns only
  a message, so the request's annotation id is echoed and labelled as such
- delete_folder discarded the numeric id Grafana returns and presented an
  input-echoed uid as if it came from the API
- delete_dashboard fabricated `id: 0` / `title: ''` via `||` on absent fields
- the contact-point `provenance` description was inverted: "api" means
  API-managed, empty means it stayed UI-editable

Requests that could not succeed:
- create_alert_rule left noDataState and execErrState unset and invisible to
  the model, but Grafana's validator rejects an empty value outright, so every
  model-driven create failed. Both are now sent with Grafana's own defaults,
  and skipped for recording rules, which take a different validator
- get_data_source routed a numeric input at /api/datasources/:id, which exists
  only behind an off-by-default feature toggle. UID only now
- list_annotations did not trim the dashboard UID, so a padded value matched
  nothing

Outbound hardening on the three proxy routes:
- the service-account token was re-sent to redirect targets; the shared fetch
  only drops it when asked, so stripAuthOnRedirect is now set
- no timeout was passed, leaving two sequential hops at the 5-minute default
- upstream error bodies were interpolated whole into the tool result, putting
  up to 10MB of HTML into logs and traces; now truncated
- UID path segments are URL-encoded so they cannot re-target the request
- update_folder sent both `version` and `overwrite: true`, which Grafana treats
  as alternatives, making the freshly fetched version decorative and silently
  clobbering a concurrent rename
- replaced the `any` casts with narrowed types

Block surface:
- 25 outputs the tools emit were undeclared and so unreferenceable downstream;
  get_data_source had 13 of its 18 unreachable
- `version` was typed string though the dashboard, folder, and data-source
  producers all emit a number
- the dashboard title field was shown only for create, so a dashboard could
  never be renamed through Update Dashboard
- six list outputs were typed json rather than array
…rafana-validation

# Conflicts:
#	apps/sim/tools/generated/tool-metadata.ts
#	apps/sim/tools/generated/tool-outputs.ts
…e block outputs

The data source health check could only ever report health. Grafana answers an
unhealthy source with HTTP 400 carrying the same {status, message} payload as a
healthy one, and the tool framework converts any non-2xx into an opaque tool
error — so the diagnostic the caller actually wants was unreachable. The check
now goes through an internal route that reads the verdict off either status and
reports it as a successful check, while a failure carrying no verdict (missing
data source, bad token, plugin with no health endpoint) stays a real error. The
plugin's `details` payload is surfaced too.

Also on that route, matching the other three: an outbound timeout, redirect
auth stripping, a truncated upstream error, and a URL-encoded UID.

Block output descriptions: ten keys are emitted by several tools with different
meanings and were described for only one producer — `database` meant both a
data source name and a health status, `annotations` both an annotation list and
an alert rule's summary map. Eleven `json` outputs were opaque although the
tools already document their inner fields. All rewritten to name every producer.

Smaller alignment fixes:
- the same EmbeddedContactPoint.settings field was typed `object` in list and
  `json` in create
- list_contact_points mapped non-nullable uid/name/type through `?? null`;
  Grafana returns an empty string, which is what create already assumed
- create_alert_rule sent `orgID`, which Grafana overwrites from the
  authenticated context, and `Number()` on a non-numeric value put NaN -> null
  in the body
- the three update routes declared `output` as required though the auth
  short-circuit omits it, and did not declare the `details` they emit on a
  validation error
…ule-group read

Four operations the integration was missing, taking it to 29.

update_contact_point / delete_contact_point close a real gap: contact points
could be listed and created but never corrected or removed. Two things worth
recording, because the published docs get both wrong:

- both verbs answer 202 with only a message, not the object. The rendered docs
  claim delete returns 204; the current spec and handler both say 202. So the
  UID is echoed from the request, the way delete_folder and update_annotation
  already do
- update is a full replace with no PATCH counterpart, so name, type, and
  settings are all required and the description says so. Omitting
  disableResolveMessage resets it

X-Disable-Provenance is exposed on update only. Its polarity is the opposite of
the alert-rule case: omitting it always succeeds, while sending it against an
API-provisioned contact point is rejected — with 403, not the 409 rules use. It
is not exposed on delete at all, because that handler never reads stored
provenance and the endpoint takes no such parameter.

move_folder reuses get_folder's mapping verbatim — same DTO. It always sends
the parentUid key, since Grafana reads an empty value as "move to the root",
which a conditionally-omitted field could not express.

get_alert_rule_group surfaces the group evaluation interval, the one alerting
knob the per-rule operations cannot reach. It reuses the shared mapAlertRule for
the nested rules, and the interval is documented as an integer of seconds.
…plates 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.
Renaming update_annotation's phantom `id` to `annotationId` and adding
`details` to the health check both created outputs the block never declared, so
neither was referenceable downstream. Caught by re-running the output-coverage
check over both integrations; the block now covers all 64 keys the 30 tools emit.
@vercel

vercel Bot commented Aug 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Aug 14, 2026 10:48pm

Request Review

@cursor

cursor Bot commented Aug 14, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes touch many Grafana proxy routes and workflow-facing outputs; regressions could break existing automations, though behavior is mostly corrective and adds tests for the health-check route.

Overview
Grafana grows from 25 to 30 operations and is aligned with Grafana’s HTTP API: wrong response shapes (annotation patch, folder delete, provenance text), missing block outputs, and broken requests (alert create without noDataState/execErrState, get data source by numeric id, dashboard rename on update) are fixed.

A dedicated data source health route treats Grafana’s HTTP 400 unhealthy payloads as successful tool output instead of opaque errors. Outbound proxy routes add timeouts, stripAuthOnRedirect, truncated error bodies, URL-encoded UIDs, and folder updates that use version without silent overwrite.

New tools: query_data_source (metric values via /api/ds/query), contact point update/delete, move_folder, and get_alert_rule_group. Docs, integration metadata, templates, and the firing-alerts skill are updated to use queries and alert-state annotations instead of implying live rule state.

Azure Data Explorer docs fix the ingestionProperties tags example to valid Kusto quoting.

Reviewed by Cursor Bugbot for commit 02fb1f6. Configure here.

@greptile-apps

greptile-apps Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR expands the Grafana integration with data-source querying, contact-point CRUD, folder movement, and alert-rule-group retrieval while correcting API request, response, proxy, and documentation behavior.

  • Adds five Grafana operations and wires them through block configuration, tool registration, generated metadata, contracts, and documentation.
  • Hardens Grafana proxy routes with encoded path segments, redirect credential protection, bounded timeouts, and truncated upstream errors.
  • Corrects Grafana response schemas, alert-rule defaults, health-check handling, data-source lookup behavior, and Azure Data Explorer ingestion-property quoting.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
apps/sim/blocks/blocks/grafana.ts Adds UI fields, operation selection, parameter mapping, and declared outputs for the new and corrected Grafana tools; the prior contact-point update omission is fixed.
apps/sim/lib/api/contracts/tools/grafana.ts Expands Grafana API response contracts and documents why provider-specific nested fields remain unconstrained; the previous rationale concern is resolved.
apps/sim/tools/grafana/query_data_source.ts Adds generic Grafana data-source querying and converts columnar response frames into referenceable rows.
apps/sim/app/api/tools/grafana/check_data_source_health/route.ts Proxies health checks so Grafana’s structured unhealthy verdict remains available even when upstream returns a non-success status.
apps/sim/tools/grafana/update_contact_point.ts Adds full-replacement contact-point updates with required UID, name, type, and settings inputs.

Reviews (2): Last reviewed commit: "fix(grafana): make Update Contact Point ..." | Re-trigger Greptile

Comment thread apps/sim/blocks/blocks/grafana.ts
Comment thread apps/sim/blocks/blocks/grafana.ts
Comment thread apps/sim/lib/api/contracts/tools/grafana.ts
The new replace operation could never succeed. contactPointType and
contactPointSettings were widened to cover it, but contactPointNameNew was
left create-only — and the update maps `name` from that field, so the required
parameter was never supplied.

disableResolveMessage had the same gap, and it matters more than it looks:
the update is a full replace, so a block-driven update was silently clearing
resolve suppression on every contact point it touched. Both fields are now
shown, and required where the API requires them.

Also states a reason on each intentionally-unconstrained response field —
Zod issue objects, alert query stages, notification settings, recording-rule
config, and data-source health detail are all genuinely opaque, but that was
left implicit.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 02fb1f6. Configure here.

@waleedlatif1
waleedlatif1 merged commit 3848f97 into staging Aug 14, 2026
30 checks passed
@waleedlatif1
waleedlatif1 deleted the fix/adx-tags-and-grafana-validation branch August 14, 2026 22:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant