feat(futo-backups-bot): grafana per-user link in staff notes (#560)

This commit is contained in:
Antoine Lecompte
2026-08-26 12:26:07 +00:00
committed by GitHub
parent eb89c13118
commit 5eaf0f349d
6 changed files with 19 additions and 9 deletions
+3 -3
View File
@@ -66,8 +66,8 @@ retention. yucca-api's scope stays pure account-linking.
and posts the description with a mention of the user and
`DISCORD_STAFF_ROLE_ID` (mentioning the role adds staff to the thread). A
sibling private thread `staff-<same suffix>` with **no members** carries the
user's Grafana dashboard link (`GRAFANA_USER_DASHBOARD_URL` template; the
dashboard itself is o11y-owned) and an account summary from
user's Grafana dashboard link (`GRAFANA_URL` base + the `yucca-per-user`
dashboard, mirroring yuctl's view-dashboard; the dashboard itself is o11y-owned) and an account summary from
**`GET /internal/discord/users/:userId/summary`** (email, connections,
repository count, last seen) — staff see it via Manage Threads on the
support channel; the user cannot. One open ticket per user (membership scan
@@ -98,7 +98,7 @@ uses the dev guild through `.env`.
| `INTERNAL_SECRET` | Secret ← TF-generated (`random_password`, shared with yucca-api) |
| `TRANSCRIPT_S3_ACCESS_KEY_ID` / `..._SECRET_ACCESS_KEY` | Secret ← ceph-stack-minted `*_CEPH_S3_SVC_YUCCA_TRANSCRIPTS_*` |
| `TRANSCRIPT_S3_ENDPOINT`, `TRANSCRIPT_S3_BUCKET` | cluster-settings |
| `GRAFANA_USER_DASHBOARD_URL` | cluster-settings; URL template, `{userId}` substituted |
| `GRAFANA_URL` | defaults to grafana.futostatus.com; cluster-settings override |
| `YUCCA_API_URL`, `WEB_URL` | HelmRelease env |
| `TICKET_RETENTION_DAYS` | archive retention before transcript + delete (14) |
@@ -48,8 +48,8 @@ spec:
value: http://yucca-api:3020
- name: WEB_URL
value: https://${APP_DOMAIN}
- name: GRAFANA_USER_DASHBOARD_URL
value: ${GRAFANA_USER_DASHBOARD_URL:=}
- name: GRAFANA_URL
value: ${GRAFANA_URL:=}
- name: TRANSCRIPT_S3_ENDPOINT
value: ${TRANSCRIPT_S3_ENDPOINT:=}
- name: TRANSCRIPT_S3_BUCKET
@@ -16,6 +16,10 @@ data:
OIDC_ISSUER: https://external-dev-gkhk8b.us1.zitadel.cloud
# Staging o11y Grafana (futo-backups-bot staff notes); prod rides the code
# default grafana.futostatus.com.
GRAFANA_URL: https://grafana.staging.futostatus.com
# Beta gate: these email domains may sign up without an allowlist entry;
# everyone else needs a `yuctl users allowlist` entry or invite code.
ALLOWED_EMAIL_DOMAINS: futo.org
+4 -1
View File
@@ -13,7 +13,10 @@ const schema = z.object({
YUCCA_API_URL: z.url().default('http://localhost:3020'),
INTERNAL_SECRET: z.string().default(''),
WEB_URL: z.url().default('http://localhost:5173'),
GRAFANA_USER_DASHBOARD_URL: z.string().default(''),
GRAFANA_URL: z
.string()
.default('')
.transform((value) => value || 'https://grafana.futostatus.com'),
TICKET_RETENTION_DAYS: z.coerce.number().default(14),
@@ -145,6 +145,10 @@ describe(SupportService.name, () => {
'staff-someone-6789',
expect.stringContaining('someone@example.test'),
);
expect(mocks.discord.createStaffThread).toHaveBeenCalledWith(
'staff-someone-6789',
expect.stringContaining('/d/yucca-per-user?var-user=user-1'),
);
expect((interaction as { deferReply: jest.Mock }).deferReply).toHaveBeenCalled();
expect((interaction as { editReply: jest.Mock }).editReply).toHaveBeenCalledWith(
expect.objectContaining({ content: expect.stringContaining('thread-1') }),
@@ -302,9 +302,8 @@ export class SupportService implements OnApplicationBootstrap {
lines.push('No linked FUTO Backups account.');
return lines.join('\n');
}
if (env.GRAFANA_USER_DASHBOARD_URL) {
lines.push(`Grafana: ${env.GRAFANA_USER_DASHBOARD_URL.replaceAll('{userId}', link.userId)}`);
}
const grafanaBase = env.GRAFANA_URL.replace(/\/+$/, '');
lines.push(`Grafana: ${grafanaBase}/d/yucca-per-user?var-user=${encodeURIComponent(link.userId)}`);
if (summary) {
lines.push(
`Account: ${summary.name} <${summary.email}>`,