Skip to content

Commit 3023dc1

Browse files
authored
Merge pull request #694 from rajbos/rajbos/admin-token-usage-dashboard
feat(sharing-server): admin token usage dashboard
2 parents 7c809df + 9095c9c commit 3023dc1

4 files changed

Lines changed: 435 additions & 2 deletions

File tree

.github/workflows/sharing-server-deploy.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,11 @@ jobs:
175175
-backend-config="key=${{ needs.setup.outputs.state_key }}"
176176
177177
- name: Reconcile custom domain Terraform state
178-
if: steps.prereqs.outputs.configured == 'true' && vars.SHARING_CUSTOM_DOMAIN != ''
178+
# Custom domains (and their managed certs) are only meaningful on stable, long-lived
179+
# environments (production). Per-branch testing environments each get a unique ACA FQDN
180+
# which already has Azure TLS — setting SHARING_CUSTOM_DOMAIN on the testing GitHub
181+
# environment will be ignored here to prevent 60-minute cert provisioning on every PR.
182+
if: steps.prereqs.outputs.configured == 'true' && vars.SHARING_CUSTOM_DOMAIN != '' && needs.setup.outputs.is_prod == 'true'
179183
working-directory: sharing-server/infra
180184
env:
181185
TF_VAR_resource_group_name: ${{ vars.AZURE_RESOURCE_GROUP }}

sharing-server/infra/main.tf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,10 @@ resource "azurerm_container_app" "this" {
191191

192192
# ── Custom domain + managed TLS certificate ───────────────────────────────────
193193
# Only created when var.custom_domain is set.
194+
# Intended for stable, long-lived environments (production) only.
195+
# Per-branch testing environments should leave var.custom_domain empty — they use
196+
# the ACA-generated FQDN (*.azurecontainerapps.io) which already has Azure TLS.
197+
# Provisioning a managed cert requires CNAME validation and can take up to 60 min.
194198
# DNS prerequisites (must exist before applying):
195199
# CNAME <subdomain> → local.aca_fqdn
196200
# TXT asuid.<subdomain> → azurerm_container_app_environment.this.custom_domain_verification_id

sharing-server/src/db.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,27 @@ export interface UserRow {
1414
fluency_score_json: string | null;
1515
}
1616

17+
export interface UserUsageSummary {
18+
user_id: number;
19+
github_login: string;
20+
github_name: string | null;
21+
avatar_url: string | null;
22+
is_admin: number;
23+
total_input: number;
24+
total_output: number;
25+
total_interactions: number;
26+
days_active: number;
27+
last_upload_day: string | null;
28+
}
29+
30+
export interface AdminDailyRow {
31+
day: string;
32+
github_login: string;
33+
input_tokens: number;
34+
output_tokens: number;
35+
interactions: number;
36+
}
37+
1738
export interface UploadRow {
1839
id: number;
1940
user_id: number;
@@ -208,6 +229,7 @@ function initSchema(db: DatabaseSync): void {
208229
db.exec(`
209230
CREATE INDEX IF NOT EXISTS idx_uploads_user_day ON usage_uploads(user_id, day);
210231
CREATE INDEX IF NOT EXISTS idx_uploads_dataset ON usage_uploads(dataset_id, day);
232+
CREATE INDEX IF NOT EXISTS idx_uploads_day ON usage_uploads(day);
211233
`);
212234

213235
// Add fluency_json column if it doesn't exist (migration for existing DBs)
@@ -358,3 +380,48 @@ export function closeDb(): void {
358380
_db = undefined;
359381
}
360382
}
383+
384+
/**
385+
* Per-user aggregate stats for the admin dashboard.
386+
* LEFT JOIN ensures users with no uploads in the period still appear (with zeros).
387+
* `last_upload_day` reflects their all-time last upload, not filtered to the period.
388+
*/
389+
export function getAdminUserSummaries(days: number): UserUsageSummary[] {
390+
const cutoff = new Date();
391+
cutoff.setUTCDate(cutoff.getUTCDate() - days);
392+
const cutoffStr = cutoff.toISOString().slice(0, 10);
393+
return getDb().prepare(`
394+
SELECT
395+
u.id AS user_id,
396+
u.github_login,
397+
u.github_name,
398+
u.avatar_url,
399+
u.is_admin,
400+
COALESCE(SUM(CASE WHEN uu.day >= ? THEN uu.input_tokens ELSE 0 END), 0) AS total_input,
401+
COALESCE(SUM(CASE WHEN uu.day >= ? THEN uu.output_tokens ELSE 0 END), 0) AS total_output,
402+
COALESCE(SUM(CASE WHEN uu.day >= ? THEN uu.interactions ELSE 0 END), 0) AS total_interactions,
403+
COUNT(DISTINCT CASE WHEN uu.day >= ? THEN uu.day END) AS days_active,
404+
MAX(uu.day) AS last_upload_day
405+
FROM users u
406+
LEFT JOIN usage_uploads uu ON uu.user_id = u.id
407+
GROUP BY u.id
408+
ORDER BY total_input + total_output DESC
409+
`).all(cutoffStr, cutoffStr, cutoffStr, cutoffStr) as unknown as UserUsageSummary[];
410+
}
411+
412+
/** Daily per-user token totals for the admin trend chart. */
413+
export function getAdminDailyTotals(days: number): AdminDailyRow[] {
414+
return getDb().prepare(`
415+
SELECT
416+
uu.day,
417+
u.github_login,
418+
SUM(uu.input_tokens) AS input_tokens,
419+
SUM(uu.output_tokens) AS output_tokens,
420+
SUM(uu.interactions) AS interactions
421+
FROM usage_uploads uu
422+
JOIN users u ON u.id = uu.user_id
423+
WHERE uu.day >= date('now', '-' || ? || ' days')
424+
GROUP BY uu.day, u.github_login
425+
ORDER BY uu.day, u.github_login
426+
`).all(days) as unknown as AdminDailyRow[];
427+
}

0 commit comments

Comments
 (0)