Skip to content

Add portfolio analytics page across all properties - #190

Merged
ralyodio merged 1 commit into
masterfrom
worktree-global-analytics
Aug 9, 2026
Merged

ralyodio merged 1 commit into
masterfrom
worktree-global-analytics

Conversation

@ralyodio

@ralyodio ralyodio commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

The per-project stats page answers "how is this site doing?"; there was no way to ask "how is the whole portfolio doing?". /analytics rolls every project the viewer can see into one view and, for each figure, compares the last N days against the N days before them so the answer is explicitly up, down, or sideways.

  • Multi-project tracker_* RPCs (security invoker, so RLS still scopes them). Aggregating the whole set in one query rather than merging per-project calls keeps the top-N rankings correct: a bucket ranked 11th on ten sites can be feat(aeo-score): time-series rollup + sparkline on project page #1 overall and would otherwise never surface.
  • tracker_project_totals returns current AND previous window per project, which drives both the headline trend chips and the per-property table.
  • tracker_project_daily_series is the only projects x days query, so callers pass a narrowed id list; the chart's "Other" band is derived by subtracting the charted sites from the exact full-portfolio series.
  • Top pages / exit pages / interactions group by project as well as path, because "/" exists on every site and collapsing them means nothing.
  • buildDaily() moved to lib/tracker/series.ts and is now shared with the per-project page instead of being duplicated.

Selectable 7/30/90-day window and an org filter that defaults to all orgs.

The per-project stats page answers "how is this site doing?"; there was no
way to ask "how is the whole portfolio doing?". /analytics rolls every
project the viewer can see into one view and, for each figure, compares the
last N days against the N days before them so the answer is explicitly up,
down, or sideways.

- Multi-project tracker_* RPCs (security invoker, so RLS still scopes them).
  Aggregating the whole set in one query rather than merging per-project
  calls keeps the top-N rankings correct: a bucket ranked 11th on ten sites
  can be #1 overall and would otherwise never surface.
- tracker_project_totals returns current AND previous window per project,
  which drives both the headline trend chips and the per-property table.
- tracker_project_daily_series is the only projects x days query, so callers
  pass a narrowed id list; the chart's "Other" band is derived by
  subtracting the charted sites from the exact full-portfolio series.
- Top pages / exit pages / interactions group by project as well as path,
  because "/" exists on every site and collapsing them means nothing.
- buildDaily() moved to lib/tracker/series.ts and is now shared with the
  per-project page instead of being duplicated.

Selectable 7/30/90-day window and an org filter that defaults to all orgs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ralyodio
ralyodio merged commit 107eded into master Aug 9, 2026
7 checks passed
@ralyodio
ralyodio deleted the worktree-global-analytics branch August 9, 2026 13:42
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

ThreatCrush Security Scan

50 finding(s)

HIGH/CRITICAL: 4 | MEDIUM: 43 | LOW: 3

Severity Rule Location
HIGH secret-generic-credential app/(marketing)/docs/autoblog-webhook/page.tsx:145
HIGH secret-generic-credential lib/sp/platforms/facebook.ts:32
HIGH secret-generic-credential lib/sp/platforms/linkedin.ts:25
HIGH manifest-typosquat package.json:59
MEDIUM js-unescaped-html-sink app/(app)/admin/email-broadcast/EmailBroadcastForm.tsx:125
MEDIUM sql-template-interpolation app/(app)/projects/[id]/autoblog/actions.tsx:96
MEDIUM js-unescaped-html-sink app/(app)/projects/[id]/autoblog/articles/[articleId]/page.tsx:214
MEDIUM sql-template-interpolation app/(app)/projects/[id]/autoblog/setup/form.tsx:504
MEDIUM sql-template-interpolation app/(app)/projects/[id]/uptime/monitor-actions.tsx:28
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:67
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:97
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:104
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:110
MEDIUM js-unescaped-html-sink app/(marketing)/recent/page.tsx:186
MEDIUM js-unescaped-html-sink app/(marketing)/recent/page.tsx:190
MEDIUM sql-template-interpolation app/actions/admin.ts:114
MEDIUM sql-template-interpolation app/actions/orgs.ts:328
MEDIUM sql-template-interpolation app/api/lx/keywords/regenerate/route.ts:59
MEDIUM js-unescaped-html-sink app/c/[project]/[slug]/page.tsx:77
MEDIUM js-unescaped-html-sink app/c/[project]/page.tsx:57
MEDIUM js-unescaped-html-sink app/careers.js/route.ts:228
MEDIUM js-unescaped-html-sink app/careers.js/route.ts:285
MEDIUM js-unescaped-html-sink app/layout.tsx:129
MEDIUM js-open-redirect app/login/form.tsx:39
MEDIUM js-unescaped-html-sink app/r/[token]/page.tsx:176
MEDIUM js-open-redirect app/signup/form.tsx:43
MEDIUM js-open-redirect components/billing/buy-credits-modal.tsx:98
MEDIUM js-unescaped-html-sink components/json-ld.tsx:8
MEDIUM js-unescaped-html-sink components/report/markdown-view.tsx:15
MEDIUM sql-template-interpolation lib/audit/checks/security.ts:48
MEDIUM redos-nested-quantifier lib/careers/jobs.ts:139
MEDIUM js-unescaped-html-sink lib/careers/page-templates.ts:198
MEDIUM redos-nested-quantifier lib/emailMarkdown.ts:130
MEDIUM redos-nested-quantifier lib/lx/articleGen.ts:93
MEDIUM sql-template-interpolation lib/lx/articleGen.ts:367
MEDIUM sql-template-interpolation lib/lx/articleGen.ts:379
MEDIUM sql-template-interpolation lib/lx/articleGen.ts:380
MEDIUM sql-template-interpolation lib/lx/articleGen.ts:1340
MEDIUM sql-template-interpolation lib/lx/articleGen.ts:1362
MEDIUM sql-template-interpolation lib/lx/guestPostGen.ts:109
MEDIUM tls-verification-disabled lib/onion.ts:47
MEDIUM sql-template-interpolation lib/sp/platforms/linkedin.ts:177
MEDIUM sql-template-interpolation scripts/delete-archived-projects.mjs:97
MEDIUM sql-template-interpolation scripts/delete-archived-projects.mjs:102
MEDIUM sql-template-interpolation scripts/lx-republish-todays-articles.mjs:102
MEDIUM js-dynamic-code-execution tests/careers-page-templates.test.ts:21
MEDIUM js-dynamic-code-execution tests/careers-widget-script.test.ts:69
LOW secret-generic-credential tests/contract/coinpay.test.ts:4
LOW secret-generic-credential tests/contract/posthog-integration.test.ts:13
LOW secret-generic-credential tests/lead-campaign.test.ts:16

Snippets are redacted; ThreatCrush never prints matched credential material.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant