mirror of
https://github.com/coder/coder.git
synced 2026-10-31 02:43:21 +08:00
feat: add CTAs to AI budget admin notifications (#28138)
## Problem
AI budget admin notifications tell an admin that a user crossed a budget
threshold, but give them nothing to act on: no link to the user's
budget, and no pointer to the docs.
## Changes
- Add a **Manage user AI budget** action to both admin templates,
linking to the effective group's members page filtered to the affected
user, where the per-user override is set.
- Link to the AI budget documentation from both notification bodies.
- Say when the block lifts in the limit-reached notification ("blocked
until `{{period_end}}`").
- Add an `organization_name` label, needed to build the members page
URL.
- Grant the AI Gateway daemon subject read access to organizations,
which the label lookup requires.
- Add an authorization test for the notification path, which also covers
the existing group read grant.
<details>
<summary>Screenshots</summary>
**Admin Notifications in Coder UI**
<img width="456" height="695" alt="admin_notifications"
src="https://github.com/user-attachments/assets/a579f91c-55cc-497c-87f4-235f15493725"
/>
**Admin Notification in Email**
<img width="660" height="399" alt="admin_notifications_email"
src="https://github.com/user-attachments/assets/39b876fb-f512-41e5-997f-a5269cd26ddc"
/>
</details>
Related to internal Slack threads:
* https://codercom.slack.com/archives/C096PFVBZKN/p1786051630688569
* https://codercom.slack.com/archives/C04M9CNGHGF/p1786453662306149
> [!NOTE]
> Initially generated by Claude Opus 5, modified and reviewed by
@ssncferreira
This commit is contained in:
@@ -725,6 +725,7 @@ var (
|
||||
rbac.ResourceAiSeat.Type: {policy.ActionCreate}, // Required for UpsertAISeatState.
|
||||
rbac.ResourceAIProvider.Type: {policy.ActionRead}, // Required to load the provider snapshot (and per-provider keys) at startup.
|
||||
rbac.ResourceGroup.Type: {policy.ActionRead}, // Required to read the effective group.
|
||||
rbac.ResourceOrganization.Type: {policy.ActionRead}, // Required to name the organization in budget notification links.
|
||||
}),
|
||||
User: []rbac.Permission{},
|
||||
ByOrgID: map[string]rbac.OrgPermissions{},
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
UPDATE notification_templates
|
||||
SET
|
||||
body_template = $$User **{{.Labels.username}}** has used more than {{.Labels.threshold}}% of their {{.Labels.period}} AI budget ({{.Labels.limit}}).
|
||||
|
||||
Effective group: **{{.Labels.effective_group_name}}**
|
||||
{{- if eq .Labels.limit_source "user_override"}}
|
||||
|
||||
This limit is a per-user override.
|
||||
{{- end}}
|
||||
|
||||
AI budget period: {{.Labels.period_start}} - {{.Labels.period_end}}$$,
|
||||
actions = '[]'::jsonb
|
||||
WHERE
|
||||
id = '2a7b0ac1-00e1-4625-9cd5-1e5933972c77';
|
||||
|
||||
UPDATE notification_templates
|
||||
SET
|
||||
body_template = $$User **{{.Labels.username}}** has reached their {{.Labels.period}} AI budget limit ({{.Labels.limit}}). Subsequent requests will be blocked.
|
||||
|
||||
Effective group: **{{.Labels.effective_group_name}}**
|
||||
{{- if eq .Labels.limit_source "user_override"}}
|
||||
|
||||
This limit is a per-user override.
|
||||
{{- end}}
|
||||
|
||||
AI budget period: {{.Labels.period_start}} - {{.Labels.period_end}}$$,
|
||||
actions = '[]'::jsonb
|
||||
WHERE
|
||||
id = '0bafe0ea-a78b-4217-ad05-1ef12e92e025';
|
||||
@@ -0,0 +1,43 @@
|
||||
UPDATE notification_templates
|
||||
SET
|
||||
body_template = $$User **{{.Labels.username}}** has used more than {{.Labels.threshold}}% of their {{.Labels.period}} AI budget ({{.Labels.limit}}).
|
||||
|
||||
Effective group: **{{.Labels.effective_group_name}}**
|
||||
{{- if eq .Labels.limit_source "user_override"}}
|
||||
|
||||
This limit is a per-user override.
|
||||
{{- end}}
|
||||
|
||||
AI budget period: {{.Labels.period_start}} - {{.Labels.period_end}}
|
||||
|
||||
[How to configure AI budgets](https://coder.com/docs/ai-coder/ai-gateway/cost-controls#budget)$$,
|
||||
actions = '[
|
||||
{
|
||||
"label": "Manage user AI budget",
|
||||
"url": "{{base_url}}/organizations/{{.Labels.organization_name}}/groups/{{.Labels.effective_group_name}}?filter={{.Labels.username}}"
|
||||
}
|
||||
]'::jsonb
|
||||
WHERE
|
||||
id = '2a7b0ac1-00e1-4625-9cd5-1e5933972c77';
|
||||
|
||||
UPDATE notification_templates
|
||||
SET
|
||||
body_template = $$User **{{.Labels.username}}** has reached their {{.Labels.period}} AI budget limit ({{.Labels.limit}}). Subsequent requests will be blocked until {{.Labels.period_end}}.
|
||||
|
||||
Effective group: **{{.Labels.effective_group_name}}**
|
||||
{{- if eq .Labels.limit_source "user_override"}}
|
||||
|
||||
This limit is a per-user override.
|
||||
{{- end}}
|
||||
|
||||
AI budget period: {{.Labels.period_start}} - {{.Labels.period_end}}
|
||||
|
||||
Raise this user's limit to unblock them sooner. [How to configure AI budgets](https://coder.com/docs/ai-coder/ai-gateway/cost-controls#budget).$$,
|
||||
actions = '[
|
||||
{
|
||||
"label": "Manage user AI budget",
|
||||
"url": "{{base_url}}/organizations/{{.Labels.organization_name}}/groups/{{.Labels.effective_group_name}}?filter={{.Labels.username}}"
|
||||
}
|
||||
]'::jsonb
|
||||
WHERE
|
||||
id = '0bafe0ea-a78b-4217-ad05-1ef12e92e025';
|
||||
Reference in New Issue
Block a user