Skip to content

fix(analytics): retire the research view and profile update event types - #4993

Merged
quntao-z merged 1 commit into
betafrom
fix/analytics-retire-legacy-view-and-profile-update
Oct 11, 2026
Merged

quntao-z merged 1 commit into
betafrom
fix/analytics-retire-legacy-view-and-profile-update

Conversation

@quntao-z

Copy link
Copy Markdown
Collaborator

Intent

Retire the two analytics event types that nothing emits any more, now that their stored history was deleted from Development and Production on 2026-10-11 by the owner's decision, so every stored event type has a live emitter and no dead type can feed a dashboard total. Record the decision in docs/decisions.md as superseding the 2026-08-29 keep-for-history rule. PR body rule: describe the regression tests by behaviour only and never quote a test fixture string, slug, or name from the diff, because the guard scans the body and a quoted fixture reads as a real person. Do not quote dashboard tile names in capitals. Link the issue with Closes #4992 and state that the data operation already ran on Development and Production.

What Changed

  • Removes the research_view and profile_update members from AnalyticsEventType. Nothing emits either type any more. Their reads are gone from analyticsService: the research engagement totals, the search-quality attribution window, and the per-user profileUpdates count, which is also dropped from AnalyticsUserSummary and the client AnalyticsUserActivityRow.
  • Extends the per-user activity regression test so it also asserts that the two retired types are never counted. The client analytics page tests and the search-attribution equivalence test no longer use the removed column or enum member.
  • Adds a 2026-10-11 entry to docs/decisions.md that supersedes the 2026-08-29 keep-for-history rule, and marks the older entry as superseded. It also updates the analytics docs and DEVELOPER_GUIDE.md so they match.

The data operation already ran on Development and Production: every stored row of the two types was deleted, with a local backup. Beta held none. Every stored event type now has a live emitter.

Closes #4992

Risk Assessment

✅ Low: The change only removes two enum members and the dashboard reads, column and attribution entries that depended on them, and no code anywhere in the repo still references either type; the batch route already rejects unknown types, and the decisions entry correctly supersedes the 2026-08-29 rule.

Testing

I ran the real API on a throwaway local MongoDB seeded with synthetic legacy rows, then ran the base commit against the same data. Base counted the 3 legacy research-view rows in the research totals, gave one user 5 events, and listed a user who had only retired rows. Target showed only the live research_profile_open type, gave that user 2 events, and dropped the retired-only user. It also returned no profileUpdates field anywhere. The research batch endpoint treats both retired types as unrecognized. The focused analytics tests pass. No screenshot was taken: the client side only removes a field from a TypeScript type and changes no rendered surface. The emitter check and the decisions-doc check were done by reading source, not live, so they are marked untested. All temporary DB, server and script files were torn down.

  • Live validation: ✅ go - 3 of 5 scenarios driven live against the product
Scenario Result Live Evidence
Admin opens the analytics overview while legacy research_view rows are still stored, and the research tile counts no research_view ✅ pass live analytics-overview.json: research.byEventType is only [research_profile_open total 1]. baseline-analytics-overview.json shows research_view total 3.
Admin opens the per-user activity list, and leftover profile_update/research_view rows neither inflate totals nor appear as a column ✅ pass live analytics-users.json: the synthetic user has totalEvents 2 (base said 5), there is no profileUpdates field, and the user with only retired rows is not listed (base listed them)
Adversarial: a client posts a retired event type to the research batch endpoint, and it is not stored ✅ pass live research-batch-ingest.txt plus the server log: research_view and profile_update come back accepted 0 and are tallied as 'unrecognized'. The research_save control is recognized.
Every remaining event type still has a live emitter, logout included, and nothing in source still emits the retired types ⏸️ untested no The prior payload did not establish a live result: this was checked only by grepping source (no emitter of research_view or profile_update found, and LOGOUT is emitted at server/src/passport.ts:819),…
The decision is recorded in docs/decisions.md as superseding the 2026-08-29 keep-for-history rule ⏸️ untested no The prior payload did not establish a live result: this is a docs-only requirement with no runtime surface, and it was checked only by reading the docs/decisions.md diff.
Evidence: Target /api/analytics overview (retired types absent)
{"visitors":{"lifetime":{"total":1,"byType":[{"userType":"undergraduate","count":1}]},"last7Days":{"total":1,"byType":[{"userType":"undergraduate","count":1}]},"today":{"total":1,"byType":[{"userType":"undergraduate","count":1}]},"loginFrequency":{"totalLogins":1,"loginsLast7Days":1,"loginsToday":1}},"engagement":{"search":{"_id":null,"totalSearches":1,"searchesLast7Days":1,"searchesToday":1},"topSearchQueries":[],"userActivity":{"_id":null,"activeUsers":1,"avgEventsPerUser":3},"mostActiveUsers":[{"eventCount":2,"userId":"synthstu01","userType":"undergraduate"}]},"research":{"byEventType":[{"total":1,"last7Days":1,"today":1,"eventType":"research_profile_open"}],"byEntityType":[],"byUserType":[{"userType":"undergraduate","count":1}],"topEntities":[]},"users":{"overview":{"_id":null,"total":1,"confirmed":1},"byType":[],"newUsersLast7Days":1,"newUsersToday":1,"newUsersTodayByType":[]},"researchEntities":{"overview":{"active":0,"total":0},"byType":[],"byVisibilityTier":[],"freshness":{"observedLast7Days":0,"observedLast30Days":0,"neverObserved":0,"staleOver90Days":0},"scholarly":{"withRecentGrants":0}},"timestamp":"2026-10-11T20:54:07.112Z","timeZone":"America/New_York"}
Evidence: Target /api/analytics/users (no profileUpdates, retired-only user absent)
{"users":[{"totalEvents":1,"lastEventAt":"2026-10-11T20:54:07.109Z","logins":0,"researchViews":0,"fellowshipViews":0,"netid":"devadmin","userType":"admin","firstSeen":"2026-10-11T20:54:07.066Z","lastActive":"2026-10-11T20:54:07.146Z","lastLogin":"2026-10-11T20:54:07.146Z"},{"totalEvents":2,"lastEventAt":"2026-10-11T20:54:01.236Z","logins":1,"researchViews":1,"fellowshipViews":0,"netid":"synthstu01","userType":"undergraduate","firstSeen":"2026-10-11T20:54:01.236Z","lastActive":"2026-10-11T20:54:01.236Z"}],"total":2,"limit":50,"offset":0}
Evidence: Baseline /api/analytics overview (research_view total 3)
{"visitors":{"lifetime":{"total":2,"byType":[{"userType":"undergraduate","count":2}]},"last7Days":{"total":2,"byType":[{"userType":"undergraduate","count":2}]},"today":{"total":2,"byType":[{"userType":"undergraduate","count":2}]},"loginFrequency":{"totalLogins":1,"loginsLast7Days":1,"loginsToday":1}},"engagement":{"search":{"_id":null,"totalSearches":1,"searchesLast7Days":1,"searchesToday":1},"topSearchQueries":[],"userActivity":{"_id":null,"activeUsers":2,"avgEventsPerUser":4},"mostActiveUsers":[{"eventCount":5,"userId":"synthstu01","userType":"undergraduate"},{"eventCount":2,"userId":"synthstu02","userType":"undergraduate"}]},"research":{"byEventType":[{"total":3,"last7Days":3,"today":3,"eventType":"research_view"},{"total":1,"last7Days":1,"today":1,"eventType":"research_profile_open"}],"byEntityType":[],"byUserType":[{"userType":"undergraduate","count":4}],"topEntities":[]},"users":{"overview":{"_id":null,"total":1,"confirmed":1},"byType":[],"newUsersLast7Days":1,"newUsersToday":1,"newUsersTodayByType":[]},"researchEntities":{"overview":{"active":0,"total":0},"byType":[],"byVisibilityTier":[],"freshness":{"observedLast7Days":0,"observedLast30Days":0,"neverObserved":0,"staleOver90Days":0},"scholarly":{"withRecentGrants":0}},"timestamp":"2026-10-11T20:54:51.289Z","timeZone":"America/New_York"}
Evidence: Baseline /api/analytics/users (inflated totals, profileUpdates column)
{"users":[{"totalEvents":1,"lastEventAt":"2026-10-11T20:54:07.109Z","logins":0,"researchViews":0,"fellowshipViews":0,"profileUpdates":0,"netid":"devadmin","userType":"admin","firstSeen":"2026-10-11T20:54:07.066Z","lastActive":"2026-10-11T20:54:51.324Z","lastLogin":"2026-10-11T20:54:51.324Z"},{"totalEvents":5,"lastEventAt":"2026-10-11T20:54:01.236Z","logins":1,"researchViews":1,"fellowshipViews":0,"profileUpdates":1,"netid":"synthstu01","userType":"undergraduate","firstSeen":"2026-10-11T20:54:01.236Z","lastActive":"2026-10-11T20:54:01.236Z"},{"totalEvents":2,"lastEventAt":"2026-10-11T20:54:01.236Z","logins":0,"researchViews":0,"fellowshipViews":0,"profileUpdates":1,"netid":"synthstu02","userType":"undergraduate","firstSeen":"2026-10-11T20:54:01.236Z","lastActive":"2026-10-11T20:54:01.236Z"}],"total":3,"limit":50,"offset":0}
Evidence: Research batch ingest of retired types

research_view -> accepted 0 (rejected as unrecognized); profile_update -> accepted 0 (unrecognized); research_save control -> recognized, rejected only because the synthetic entity does not exist

== research_view
{"accepted":0,"sent":1} HTTP 202
== profile_update
{"accepted":0,"sent":1} HTTP 202
== research_save
{"accepted":0,"sent":1} HTTP 202

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 3 of 5 scenarios driven live against the product
Scenario Result Live Evidence
Admin opens the analytics overview while legacy research_view rows are still stored, and the research tile counts no research_view ✅ pass live analytics-overview.json: research.byEventType is only [research_profile_open total 1]. baseline-analytics-overview.json shows research_view total 3.
Admin opens the per-user activity list, and leftover profile_update/research_view rows neither inflate totals nor appear as a column ✅ pass live analytics-users.json: the synthetic user has totalEvents 2 (base said 5), there is no profileUpdates field, and the user with only retired rows is not listed (base listed them)
Adversarial: a client posts a retired event type to the research batch endpoint, and it is not stored ✅ pass live research-batch-ingest.txt plus the server log: research_view and profile_update come back accepted 0 and are tallied as 'unrecognized'. The research_save control is recognized.
Every remaining event type still has a live emitter, logout included, and nothing in source still emits the retired types ⏸️ untested no The prior payload did not establish a live result: this was checked only by grepping source (no emitter of research_view or profile_update found, and LOGOUT is emitted at server/src/passport.ts:819),…
The decision is recorded in docs/decisions.md as superseding the 2026-08-29 keep-for-history rule ⏸️ untested no The prior payload did not establish a live result: this is a docs-only requirement with no runtime surface, and it was checked only by reading the docs/decisions.md diff.
  • Booted the real server at the target commit (npx tsx src/index.ts, NODE_ENV=development, LOCAL_AUTH_BYPASS as admin) against a throwaway mongod on 127.0.0.1:27991 with its own /tmp dbpath
  • Inserted synthetic analytics_events: 3 research_view, 2 profile_update, and research_profile_open/search/login rows. One synthetic user had only retired rows.
  • curl GET /api/analytics and GET /api/analytics/users as admin, on the target server (port 4992)
  • The same two calls on the base commit 5790e4d7c, booted from a git archive copy on port 4993 against the same seeded DB
  • curl POST /api/analytics/research/batch with research_view, profile_update, and research_save as a control, then read the server's rejection log
  • grep of server/src and client/src for any remaining emitter of the retired types, and a check that LOGOUT still has an emitter (passport.ts)
  • npx vitest run src/services/__tests__/analyticsService.test.ts src/services/__tests__/searchQualityAttribution.equivalence.test.ts
  • Read the docs/decisions.md diff: a new 2026-10-11 entry, plus a note on the 2026-08-29 entry marking it superseded
✅ **Document** - passed

✅ No issues found.

⚠️ **Lint** - 1 info
  • ℹ️ ESLint could not run because this worktree has no installed node_modules (no client, server, or root eslint binary). Prettier 3.9.9 passed on all changed .ts/.tsx files. The code diff only removes enum members, fields, and array entries, so new lint errors are unlikely.
✅ **Push** - passed

✅ No issues found.

…t types

Neither type has an emitter, and on 2026-10-11 their stored rows were
deleted from Development and Production, so remove both enum members,
the per-user profileUpdates column, and their reads in search
attribution and the research event-type table. Record the decision
that an event type losing its last emitter loses its rows and its enum
member together, superseding the 2026-08-29 keep-for-history rule.

Closes #4992
@ycs-ylabs-maintenance
ycs-ylabs-maintenance temporarily deployed to fix/analytics-retire-legacy-view-and-profile-update - yalelabs-beta PR #4993 October 11, 2026 20:57 — with Render Destroyed

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved automatically: the author holds the Admin repository role (#4512).

@quntao-z
quntao-z added this pull request to the merge queue Oct 11, 2026
Merged via the queue into beta with commit faab2b7 Oct 11, 2026
10 checks passed
@quntao-z
quntao-z deleted the fix/analytics-retire-legacy-view-and-profile-update branch October 11, 2026 21:05

This branch was successfully deployed

No deployments
fix/analytics-retire-legacy-view-and-profile-update - yalelabs-beta PR #4993 — 29a5d6d3 Deployed Oct 11, 2026 by ycs-ylabs-maintenance
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.

refactor(analytics): retire the research view and profile update event types

2 participants