Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 59 additions & 1 deletion src/providers/grafana/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,24 @@ export type GrafanaActionName =
| "get_data_source"
| "create_data_source"
| "update_data_source"
| "delete_data_source";
| "delete_data_source"
| "list_alert_rules"
| "get_alert_rule"
| "list_alert_instances"
| "list_contact_points";

const namespaceSchema = s.string("The Grafana API namespace. Use default for the main organization.", {
minLength: 1,
});

const rawObjectSchema = s.looseObject("The raw Grafana API object.");

const alertRuleSchema = s.looseObject("A Grafana-managed alert rule.");

const alertInstanceSchema = s.looseObject("A firing or pending Grafana alert instance.");

const contactPointSchema = s.looseObject("A Grafana notification contact point.");

const folderSchema = s.object("A normalized Grafana folder.", {
uid: s.nullable(s.string("The Grafana folder UID.")),
title: s.nullable(s.string("The folder title.")),
Expand Down Expand Up @@ -325,4 +335,52 @@ export const grafanaActions: ActionDefinition[] = [
raw: s.nullable(rawObjectSchema),
}),
}),
defineProviderAction(service, {
name: "list_alert_rules",
description: "List all Grafana-managed alert rules via the provisioning API.",
requiredScopes: [],
inputSchema: s.object("No input is required to list Grafana alert rules.", {}),
outputSchema: s.object("Grafana-managed alert rules.", {
alertRules: s.array("Alert rules returned by Grafana.", alertRuleSchema),
}),
Comment on lines +343 to +345
}),
defineProviderAction(service, {
name: "get_alert_rule",
description: "Retrieve one Grafana-managed alert rule by UID via the provisioning API.",
requiredScopes: [],
inputSchema: s.object(
"Input for retrieving a Grafana alert rule.",
{ uid: s.string("The Grafana alert rule UID.", { minLength: 1 }) },
{ required: ["uid"] },
),
outputSchema: s.object("A Grafana-managed alert rule.", {
alertRule: alertRuleSchema,
}),
}),
defineProviderAction(service, {
name: "list_alert_instances",
description: "List currently firing or pending Grafana alert instances from the built-in Alertmanager.",
requiredScopes: [],
inputSchema: s.object(
"Input for listing Grafana alert instances.",
{
active: s.boolean("Include active (firing) alert instances."),
silenced: s.boolean("Include silenced alert instances."),
inhibited: s.boolean("Include inhibited alert instances."),
},
{ optional: ["active", "silenced", "inhibited"] },
),
outputSchema: s.object("Grafana alert instances.", {
alertInstances: s.array("Alert instances returned by Grafana.", alertInstanceSchema),
}),
}),
defineProviderAction(service, {
name: "list_contact_points",
description: "List Grafana notification contact points via the provisioning API.",
requiredScopes: [],
inputSchema: s.object("No input is required to list Grafana contact points.", {}),
outputSchema: s.object("Grafana notification contact points.", {
contactPoints: s.array("Contact points returned by Grafana.", contactPointSchema),
}),
}),
];
55 changes: 55 additions & 0 deletions src/providers/grafana/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,18 @@ export const grafanaActionHandlers: Record<GrafanaActionName, GrafanaActionHandl
delete_data_source(input, context) {
return executeDeleteDataSource(input, context);
},
list_alert_rules(input, context) {
return executeListAlertRules(input, context);
},
get_alert_rule(input, context) {
return executeGetAlertRule(input, context);
},
list_alert_instances(input, context) {
return executeListAlertInstances(input, context);
},
list_contact_points(input, context) {
return executeListContactPoints(input, context);
},
};

export async function validateGrafanaCredential(
Expand Down Expand Up @@ -336,6 +348,49 @@ async function executeDeleteDataSource(input: Record<string, unknown>, context:
};
}

// Alerting actions use Grafana's legacy REST API (/api/...), which is not versioned
// per release, so they are unaffected by App Platform version negotiation.
async function executeListAlertRules(_input: Record<string, unknown>, context: GrafanaContext): Promise<unknown> {
const payload = await grafanaRequestJson(
"/api/v1/provisioning/alert-rules",
{ method: "GET" },
{ ...context, phase: "execute" },
);
return { alertRules: objectArrayOrEmpty(payload) };
Comment on lines +354 to +359
}

async function executeGetAlertRule(input: Record<string, unknown>, context: GrafanaContext): Promise<unknown> {
const payload = await grafanaRequestJson(
`/api/v1/provisioning/alert-rules/${encodePathSegment(requireString(input.uid, "uid"))}`,
{ method: "GET" },
{ ...context, phase: "execute" },
);
return { alertRule: optionalRecord(payload) ?? {} };
}

async function executeListAlertInstances(input: Record<string, unknown>, context: GrafanaContext): Promise<unknown> {
const query = compactObject({
active: optionalBoolean(input.active),
silenced: optionalBoolean(input.silenced),
inhibited: optionalBoolean(input.inhibited),
});
const payload = await grafanaRequestJson(
"/api/alertmanager/grafana/api/v2/alerts",
{ method: "GET", query },
{ ...context, phase: "execute" },
);
return { alertInstances: objectArrayOrEmpty(payload) };
}

async function executeListContactPoints(_input: Record<string, unknown>, context: GrafanaContext): Promise<unknown> {
const payload = await grafanaRequestJson(
"/api/v1/provisioning/contact-points",
{ method: "GET" },
{ ...context, phase: "execute" },
);
return { contactPoints: objectArrayOrEmpty(payload) };
}

async function grafanaRequestJson(
path: string,
request: {
Expand Down
Loading