Skip to content

Commit dea05b3

Browse files
committed
feat(grafana): complete contact-point CRUD, and add folder move and rule-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.
1 parent 5b9257c commit dea05b3

13 files changed

Lines changed: 792 additions & 8 deletions

File tree

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

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -813,4 +813,126 @@ Check the health of the Grafana instance (version, database status)
813813
| `database` | string | Database health status \(e.g., ok\) |
814814
| `version` | string | Grafana version |
815815

816+
### Grafana Update Contact Point
817+
818+
Replace a contact point by its UID. Grafana has no partial update for contact points, so every field is rewritten — resend the name, type, and full settings, or the omitted ones are reset.
819+
820+
#### Input
821+
822+
| Parameter | Type | Required | Description |
823+
| --------- | ---- | -------- | ----------- |
824+
| `apiKey` | string | Yes | Grafana Service Account Token |
825+
| `baseUrl` | string | Yes | Grafana instance URL \(e.g., https://your-grafana.com\) |
826+
| `organizationId` | string | No | Organization ID for multi-org Grafana instances \(e.g., 1, 2\) |
827+
| `contactPointUid` | string | Yes | UID of the contact point to replace |
828+
| `name` | string | Yes | Contact point name. Grafana groups receivers that share a name |
829+
| `type` | string | Yes | Receiver type, e.g. slack, email, pagerduty, webhook, opsgenie, teams, discord, telegram |
830+
| `settings` | string | Yes | JSON object of receiver settings for this type, e.g. \{"url":"https://hooks.slack.com/..."\} for slack |
831+
| `disableResolveMessage` | boolean | No | Suppress the resolved notification. Omitting this resets it to false |
832+
| `disableProvenance` | boolean | No | Send X-Disable-Provenance. Use only on a contact point whose provenance is already empty \(UI-created, or created by Sim with this on\) — sending it against an API-provisioned contact point is rejected with 403 |
833+
834+
#### Output
835+
836+
| Parameter | Type | Description |
837+
| --------- | ---- | ----------- |
838+
| `uid` | string | The UID that was updated, echoed from the request — Grafana answers a contact point update with only a message and returns no object |
839+
| `message` | string | Confirmation message from Grafana, e.g. "contactpoint updated" |
840+
841+
### Grafana Delete Contact Point
842+
843+
Permanently delete a contact point by its UID. Grafana refuses the delete while the contact point is still referenced by the notification policy tree or by an alert rule.
844+
845+
#### Input
846+
847+
| Parameter | Type | Required | Description |
848+
| --------- | ---- | -------- | ----------- |
849+
| `apiKey` | string | Yes | Grafana Service Account Token |
850+
| `baseUrl` | string | Yes | Grafana instance URL \(e.g., https://your-grafana.com\) |
851+
| `organizationId` | string | No | Organization ID for multi-org Grafana instances \(e.g., 1, 2\) |
852+
| `contactPointUid` | string | Yes | UID of the contact point to delete |
853+
854+
#### Output
855+
856+
| Parameter | Type | Description |
857+
| --------- | ---- | ----------- |
858+
| `uid` | string | The UID that was deleted, echoed from the request |
859+
| `message` | string | Confirmation message from Grafana, e.g. "contactpoint deleted" |
860+
861+
### Grafana Move Folder
862+
863+
Move a folder under a different parent folder, or to the root by leaving the parent empty. Returns the folder with its new ancestry.
864+
865+
#### Input
866+
867+
| Parameter | Type | Required | Description |
868+
| --------- | ---- | -------- | ----------- |
869+
| `apiKey` | string | Yes | Grafana Service Account Token |
870+
| `baseUrl` | string | Yes | Grafana instance URL \(e.g., https://your-grafana.com\) |
871+
| `organizationId` | string | No | Organization ID for multi-org Grafana instances \(e.g., 1, 2\) |
872+
| `folderUid` | string | Yes | UID of the folder to move |
873+
| `parentUid` | string | No | UID of the new parent folder. Leave empty to move the folder to the root |
874+
875+
#### Output
876+
877+
| Parameter | Type | Description |
878+
| --------- | ---- | ----------- |
879+
| `id` | number | The numeric ID of the folder |
880+
| `uid` | string | The UID of the folder |
881+
| `title` | string | The title of the folder |
882+
| `url` | string | The URL path of the folder |
883+
| `parentUid` | string | UID of the new parent folder, absent once moved to the root |
884+
| `parents` | array | Folder ancestry from the root down to the parent \(uid, title, url\) |
885+
| `hasAcl` | boolean | Whether the folder has custom ACL permissions |
886+
| `canSave` | boolean | Whether the caller can save the folder |
887+
| `canEdit` | boolean | Whether the caller can edit the folder |
888+
| `canAdmin` | boolean | Whether the caller can administer the folder |
889+
| `createdBy` | string | Login that created the folder |
890+
| `created` | string | Creation timestamp |
891+
| `updatedBy` | string | Login that last updated the folder |
892+
| `updated` | string | Last update timestamp |
893+
| `version` | number | Folder revision number |
894+
895+
### Grafana Get Alert Rule Group
896+
897+
Read an alert rule group: its evaluation interval and every rule in it. The interval is the group-level knob that decides how often those rules are evaluated, which the individual alert rule operations do not expose.
898+
899+
#### Input
900+
901+
| Parameter | Type | Required | Description |
902+
| --------- | ---- | -------- | ----------- |
903+
| `apiKey` | string | Yes | Grafana Service Account Token |
904+
| `baseUrl` | string | Yes | Grafana instance URL \(e.g., https://your-grafana.com\) |
905+
| `organizationId` | string | No | Organization ID for multi-org Grafana instances \(e.g., 1, 2\) |
906+
| `folderUid` | string | Yes | UID of the folder holding the rule group |
907+
| `ruleGroup` | string | Yes | Name of the rule group |
908+
909+
#### Output
910+
911+
| Parameter | Type | Description |
912+
| --------- | ---- | ----------- |
913+
| `title` | string | Name of the rule group |
914+
| `folderUid` | string | UID of the folder holding the group |
915+
| `interval` | number | How often the group is evaluated, as an integer. Grafana returns seconds here rather than a duration string |
916+
| `rules` | array | Provisioned alert rules in the group |
917+
|`id` | number | Alert rule numeric ID |
918+
|`uid` | string | Alert rule UID |
919+
|`title` | string | Alert rule title |
920+
|`condition` | string | RefId of the query used as the alert condition |
921+
|`data` | json | Alert rule query/expression data array |
922+
|`updated` | string | Last update timestamp |
923+
|`noDataState` | string | State when no data is returned |
924+
|`execErrState` | string | State on execution error |
925+
|`for` | string | Duration the condition must hold before firing |
926+
|`keepFiringFor` | string | Duration to keep firing after condition stops |
927+
|`missingSeriesEvalsToResolve` | number | Number of missing series evaluations before resolving |
928+
|`annotations` | json | Alert annotations |
929+
|`labels` | json | Alert labels |
930+
|`isPaused` | boolean | Whether the rule is paused |
931+
|`folderUID` | string | Parent folder UID |
932+
|`ruleGroup` | string | Rule group name |
933+
|`orgID` | number | Organization ID |
934+
|`provenance` | string | Provisioning source — "api" for API-managed, empty when created with X-Disable-Provenance and therefore still editable in the Grafana UI |
935+
|`notification_settings` | json | Per-rule notification settings \(overrides\) |
936+
|`record` | json | Recording rule configuration \(recording rules only\) |
937+
816938

apps/sim/blocks/blocks/grafana.ts

Lines changed: 99 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,21 @@ export const GrafanaBlock: BlockConfig<GrafanaResponse> = {
6060
{ text: 'Create contact point', field: 'contactPointNameNew', core: true },
6161
{ text: ', of type', field: 'contactPointType' },
6262
],
63+
grafana_update_contact_point: [
64+
{ text: 'Replace contact point', field: 'contactPointUid', core: true },
65+
{ text: ', as type', field: 'contactPointType' },
66+
],
67+
grafana_delete_contact_point: [
68+
{ text: 'Delete contact point', field: 'contactPointUid', core: true },
69+
],
70+
grafana_move_folder: [
71+
{ text: 'Move folder', field: 'manageFolderUid', core: true },
72+
{ text: ', under', field: 'newParentUid' },
73+
],
74+
grafana_get_alert_rule_group: [
75+
{ text: 'Read alert rule group', field: 'ruleGroupName', core: true },
76+
{ text: ', in folder', field: 'manageFolderUid' },
77+
],
6378
grafana_create_annotation: [
6479
{ text: 'Create annotation', field: 'text', core: true },
6580
{ text: ', on dashboard', field: 'annotationDashboardUid' },
@@ -112,11 +127,14 @@ export const GrafanaBlock: BlockConfig<GrafanaResponse> = {
112127
{ label: 'Delete Dashboard', id: 'grafana_delete_dashboard' },
113128
{ label: 'List Alert Rules', id: 'grafana_list_alert_rules' },
114129
{ label: 'Get Alert Rule', id: 'grafana_get_alert_rule' },
130+
{ label: 'Get Alert Rule Group', id: 'grafana_get_alert_rule_group' },
115131
{ label: 'Create Alert Rule', id: 'grafana_create_alert_rule' },
116132
{ label: 'Update Alert Rule', id: 'grafana_update_alert_rule' },
117133
{ label: 'Delete Alert Rule', id: 'grafana_delete_alert_rule' },
118134
{ label: 'List Contact Points', id: 'grafana_list_contact_points' },
119135
{ label: 'Create Contact Point', id: 'grafana_create_contact_point' },
136+
{ label: 'Update Contact Point', id: 'grafana_update_contact_point' },
137+
{ label: 'Delete Contact Point', id: 'grafana_delete_contact_point' },
120138
{ label: 'Create Annotation', id: 'grafana_create_annotation' },
121139
{ label: 'List Annotations', id: 'grafana_list_annotations' },
122140
{ label: 'Update Annotation', id: 'grafana_update_annotation' },
@@ -129,6 +147,7 @@ export const GrafanaBlock: BlockConfig<GrafanaResponse> = {
129147
{ label: 'Get Folder', id: 'grafana_get_folder' },
130148
{ label: 'Update Folder', id: 'grafana_update_folder' },
131149
{ label: 'Delete Folder', id: 'grafana_delete_folder' },
150+
{ label: 'Move Folder', id: 'grafana_move_folder' },
132151
{ label: 'Get Health', id: 'grafana_get_health' },
133152
],
134153
value: () => 'grafana_list_dashboards',
@@ -628,6 +647,7 @@ Return ONLY the JSON array - no explanations, no markdown, no extra text.`,
628647
'grafana_create_alert_rule',
629648
'grafana_update_alert_rule',
630649
'grafana_create_contact_point',
650+
'grafana_update_contact_point',
631651
],
632652
},
633653
},
@@ -875,7 +895,13 @@ Return ONLY the folder title - no explanations, no quotes, no extra text.`,
875895
required: true,
876896
condition: {
877897
field: 'operation',
878-
value: ['grafana_get_folder', 'grafana_update_folder', 'grafana_delete_folder'],
898+
value: [
899+
'grafana_get_folder',
900+
'grafana_update_folder',
901+
'grafana_delete_folder',
902+
'grafana_move_folder',
903+
'grafana_get_alert_rule_group',
904+
],
879905
},
880906
},
881907
{
@@ -894,6 +920,35 @@ Return ONLY the folder title - no explanations, no quotes, no extra text.`,
894920
condition: { field: 'operation', value: 'grafana_delete_folder' },
895921
},
896922

923+
{
924+
id: 'contactPointUid',
925+
title: 'Contact Point UID',
926+
type: 'short-input',
927+
placeholder: 'Enter contact point UID',
928+
required: {
929+
field: 'operation',
930+
value: ['grafana_update_contact_point', 'grafana_delete_contact_point'],
931+
},
932+
condition: {
933+
field: 'operation',
934+
value: ['grafana_update_contact_point', 'grafana_delete_contact_point'],
935+
},
936+
},
937+
{
938+
id: 'newParentUid',
939+
title: 'New Parent Folder UID',
940+
type: 'short-input',
941+
placeholder: 'Leave empty to move to the root',
942+
condition: { field: 'operation', value: 'grafana_move_folder' },
943+
},
944+
{
945+
id: 'ruleGroupName',
946+
title: 'Rule Group',
947+
type: 'short-input',
948+
placeholder: 'Enter rule group name',
949+
required: { field: 'operation', value: 'grafana_get_alert_rule_group' },
950+
condition: { field: 'operation', value: 'grafana_get_alert_rule_group' },
951+
},
897952
{
898953
id: 'contactPointName',
899954
title: 'Contact Point Name',
@@ -924,16 +979,25 @@ Return ONLY the folder title - no explanations, no quotes, no extra text.`,
924979
{ label: 'Discord', id: 'discord' },
925980
],
926981
value: () => 'slack',
927-
required: true,
928-
condition: { field: 'operation', value: 'grafana_create_contact_point' },
982+
required: {
983+
field: 'operation',
984+
value: ['grafana_create_contact_point', 'grafana_update_contact_point'],
985+
},
986+
condition: {
987+
field: 'operation',
988+
value: ['grafana_create_contact_point', 'grafana_update_contact_point'],
989+
},
929990
},
930991
{
931992
id: 'contactPointSettings',
932993
title: 'Settings (JSON)',
933994
type: 'long-input',
934995
placeholder: 'JSON object of receiver settings (e.g., {"url":"https://hooks.slack.com/..."})',
935996
required: true,
936-
condition: { field: 'operation', value: 'grafana_create_contact_point' },
997+
condition: {
998+
field: 'operation',
999+
value: ['grafana_create_contact_point', 'grafana_update_contact_point'],
1000+
},
9371001
wandConfig: {
9381002
enabled: true,
9391003
prompt: `Generate a Grafana contact point settings JSON object based on the user's description and receiver type.
@@ -984,6 +1048,10 @@ Return ONLY the JSON object - no explanations, no markdown, no extra text.`,
9841048
'grafana_update_folder',
9851049
'grafana_delete_folder',
9861050
'grafana_get_health',
1051+
'grafana_update_contact_point',
1052+
'grafana_delete_contact_point',
1053+
'grafana_move_folder',
1054+
'grafana_get_alert_rule_group',
9871055
],
9881056
config: {
9891057
tool: (params) => params.operation,
@@ -1015,6 +1083,19 @@ Return ONLY the JSON object - no explanations, no markdown, no extra text.`,
10151083
if (params.contactPointType) result.type = params.contactPointType
10161084
if (params.contactPointSettings) result.settings = params.contactPointSettings
10171085
break
1086+
case 'grafana_update_contact_point':
1087+
if (params.contactPointNameNew) result.name = params.contactPointNameNew
1088+
if (params.contactPointType) result.type = params.contactPointType
1089+
if (params.contactPointSettings) result.settings = params.contactPointSettings
1090+
break
1091+
case 'grafana_move_folder':
1092+
result.folderUid = params.manageFolderUid
1093+
result.parentUid = params.newParentUid ?? ''
1094+
break
1095+
case 'grafana_get_alert_rule_group':
1096+
result.folderUid = params.manageFolderUid
1097+
result.ruleGroup = params.ruleGroupName
1098+
break
10181099
case 'grafana_create_annotation':
10191100
if (params.annotationTags) result.tags = params.annotationTags
10201101
if (params.annotationDashboardUid) result.dashboardUid = params.annotationDashboardUid
@@ -1066,6 +1147,15 @@ Return ONLY the JSON object - no explanations, no markdown, no extra text.`,
10661147
},
10671148
},
10681149
inputs: {
1150+
contactPointUid: {
1151+
type: 'string',
1152+
description: 'UID of the contact point to update or delete',
1153+
},
1154+
newParentUid: {
1155+
type: 'string',
1156+
description: 'UID of the new parent folder, empty for the root',
1157+
},
1158+
ruleGroupName: { type: 'string', description: 'Name of the alert rule group' },
10691159
operation: { type: 'string', description: 'Operation to perform' },
10701160
baseUrl: { type: 'string', description: 'Grafana instance URL' },
10711161
apiKey: { type: 'string', description: 'Service Account Token' },
@@ -1170,6 +1260,11 @@ Return ONLY the JSON object - no explanations, no markdown, no extra text.`,
11701260
dataSourceUid: { type: 'string', description: 'Data source UID for health checks' },
11711261
},
11721262
outputs: {
1263+
interval: {
1264+
type: 'number',
1265+
description: 'Evaluation interval of an alert rule group, in seconds',
1266+
},
1267+
folderUid: { type: 'string', description: 'UID of the folder holding the alert rule group' },
11731268
title: {
11741269
type: 'string',
11751270
description: 'Title of the affected dashboard, folder, or alert rule',

apps/sim/lib/integrations/integrations.json

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9026,6 +9026,10 @@
90269026
"name": "Get Alert Rule",
90279027
"description": "Get a specific alert rule by its UID"
90289028
},
9029+
{
9030+
"name": "Get Alert Rule Group",
9031+
"description": "Read an alert rule group: its evaluation interval and every rule in it. The interval is the group-level knob that decides how often those rules are evaluated, which the individual alert rule operations do not expose."
9032+
},
90299033
{
90309034
"name": "Create Alert Rule",
90319035
"description": "Create a new alert rule"
@@ -9046,6 +9050,14 @@
90469050
"name": "Create Contact Point",
90479051
"description": "Create a notification contact point (e.g., Slack, email, PagerDuty)"
90489052
},
9053+
{
9054+
"name": "Update Contact Point",
9055+
"description": "Replace a contact point by its UID. Grafana has no partial update for contact points, so every field is rewritten — resend the name, type, and full settings, or the omitted ones are reset."
9056+
},
9057+
{
9058+
"name": "Delete Contact Point",
9059+
"description": "Permanently delete a contact point by its UID. Grafana refuses the delete while the contact point is still referenced by the notification policy tree or by an alert rule."
9060+
},
90499061
{
90509062
"name": "Create Annotation",
90519063
"description": "Create an annotation on a dashboard or as a global annotation"
@@ -9094,12 +9106,16 @@
90949106
"name": "Delete Folder",
90959107
"description": "Delete a folder by its UID"
90969108
},
9109+
{
9110+
"name": "Move Folder",
9111+
"description": "Move a folder under a different parent folder, or to the root by leaving the parent empty. Returns the folder with its new ancestry."
9112+
},
90979113
{
90989114
"name": "Get Health",
90999115
"description": "Check the health of the Grafana instance (version, database status)"
91009116
}
91019117
],
9102-
"operationCount": 25,
9118+
"operationCount": 29,
91039119
"triggers": [],
91049120
"triggerCount": 0,
91059121
"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.

0 commit comments

Comments
 (0)