Repository navigation
feat(notifications): log weekly rotation push sends - #98
Merged
Merged
Conversation
…t devices The browser push was behind shouldSendPushNotification, which requires a mobile device, so web-only listeners never got it. Send it first, then gate only the mobile push. Removes the devices.length check that could never hit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A title containing >, < or & broke the <url|title> link markup in the digest. Escape them per Slack's mrkdwn rules. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shorten comments added with the trending digest, General inbox and weekly rotation changes, and use a plain hyphen in the digest's track lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Logs browser and mobile send counts, disabled endpoints and the skip reason for each processed weekly rotation notification so sends per period can be counted from logs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…push-logs # Conflicts: # apps/notifications/src/processNotifications/mappers/weeklyRotation.ts
dylanjeffers
added a commit
to AudiusProject/apps
that referenced
this pull request
Sep 24, 2026
… usage analytics (#14605) Stacked on #14602 (`fix/review-cleanup-sep`). Merge that first; this PR's base will then retarget to `main`. Instruments Weekly Rotation so reach, click-through, plays, shares, notification opens and retention can be measured in Amplitude. No existing events are renamed. ## Events and properties **Common** - `PlaybackSource`, `FavoriteSource` and `RepostSource` each gain `WEEKLY_ROTATION = 'weekly rotation'`. - `Playback: Play` gains `weeklyRotationOwnerId`. It is set on passive (auto-advance and previous) plays whose queue entry came from a mix, and is parsed from the `WEEKLY_ROTATION_TRACKS:<ownerId>` queue source in the shared playback saga, so it works on both web and mobile. - `Weekly Rotation: Banner View`, `Banner Click`, `Page View` and `Play All` gain `period` (e.g. `2026-38`), `isOwnMix` and `ownerUserId`. Banner events are always `isOwnMix: true`, with the viewer as owner. - `Weekly Rotation: Page View` gains `status` (`success` | `empty` | `error`) and `isSignedIn`. It now fires once the fetch settles, so empty mixes and errors are counted. Signed-out visitors on `/explore/weekly-rotation/:handle` are counted too. - `Modal Opened` now fires with `name: 'Share'` whenever the share modal or drawer opens (from the shared share-modal saga, any content type). It carries `source` (the ShareSource), `kind` (e.g. `weeklyRotation`) and the entity id (`userId` for a mix). - `Share` gains an optional `channel` (`copyLink` | `shareSheet`). - `Chat Entry Point` gains `kind` (what was shared) when `source: 'share'`. - `Notifications: Open Push Notification` gains `type` and `id` from the push payload. This applies to every push type. - The period helper moved to `@audius/common/utils` (`getWeeklyRotationPeriod`, `formatWeeklyRotationPeriod`), along with `getWeeklyRotationQueueSource` and `getWeeklyRotationOwnerIdFromQueueSource`. The web SSR still imports it by path, and the file has no dependencies. **Web** - The mix page's lineup plays and header play/pause use `source: 'weekly rotation'`. Before this they used `'playlist page'` or `'track tile'`. - Row favorite and repost buttons use `'weekly rotation'`. `TrackTableLineup` takes `favoriteSource` and `repostSource`, and History keeps its default. - The share modal copy link records `Share` with `channel: 'copyLink'`. DM share records `Chat Entry Point` with `kind`. - Banner fix: the CTA button's click also bubbled to the card, so `Banner Click` was recorded (and navigation ran) twice. Only the card handles the click now. **Mobile** - Mix screen tile taps now record `Playback: Play` and `Playback: Pause` with `source: 'weekly rotation'`. The header play button also records them. Before this, mobile lineup taps recorded no Playback events: `TrackLineup` accepted `playbackSource` but ignored it. It now records Playback events only when that prop is passed, and only this screen passes it, so other screens are unchanged. - Tile favorite and repost on the mix screen use `'weekly rotation'`. `TrackLineup` and `TrackTile` take `favoriteSource` and `repostSource`, defaulting to `TILE`. - In the share drawer, weeklyRotation copy link records `Share` (`channel: 'copyLink'`), the share sheet records `Share` (`channel: 'shareSheet'`), and X records `Share to Twitter`. DM share records `Chat Entry Point` with `kind`. - A Weekly Rotation notification tile tap records `Notifications: Clicked Tile` with `kind: 'WeeklyRotation'`, matching web. **Not covered** - The overflow-menu save, repost and add-to-playlist actions still report `overflow` on both platforms. Adding a source there means threading it through the table, `OverflowMenuButton` and `TrackMenu` on web, and through the overflow drawer state on mobile. `Playlist Add` has no source property at all. Row and tile buttons are covered. ## How to measure (Amplitude) | Metric | Event and filter | | --- | --- | | Reach (impressions) | `Weekly Rotation: Banner View`, uniques, grouped by `surface` and `source` | | Banner CTR | Funnel `Weekly Rotation: Banner View` → `Weekly Rotation: Banner Click`, same `surface` | | Page views, own vs shared | `Weekly Rotation: Page View` grouped by `isOwnMix`; filter `status = success` for non-empty views | | Shared-link landings | `Weekly Rotation: Page View` where `isOwnMix = false`, grouped by `isSignedIn` (signed-out = new-visitor landings) | | Empty or broken mixes | `Weekly Rotation: Page View` grouped by `status` | | Plays per user from the mix | `Playback: Play` where `source = weekly rotation` OR `weeklyRotationOwnerId` is set; formula `TOTALS / UNIQUES`. Split own vs shared by comparing `weeklyRotationOwnerId` to the user, or use `Play All`'s `isOwnMix` | | Play-all rate | Funnel `Page View` → `Weekly Rotation: Play All` | | Saves and reposts from the mix | `Favorite` or `Repost` where `source = weekly rotation` | | Share opens | `Modal Opened` where `name = Share` and `kind = weeklyRotation` | | Share rate by channel | Share opens (above) as the denominator. Channels: `Share` where `kind = weeklyRotation`, grouped by `channel` (web copy link, mobile copy link or share sheet); `Share to Twitter` where `kind = weeklyRotation`; `Chat Entry Point` where `source = share` and `kind = weeklyRotation` | | Push open rate | Opens: `Notifications: Open Push Notification` where `type = WeeklyRotation` (`id` includes the `weekly_rotation:<period>:<user>` group). Sends come from the `weekly rotation push processed` log line added in AudiusProject/pedalboard#98 (sum `mobileSent` per `groupId` period) | | Notification tile CTR | `Notifications: Clicked Tile` where `kind = WeeklyRotation` | | Week-over-week retention | Retention on `Weekly Rotation: Page View` (or `Play All`) with weekly buckets, or group by `period` to line up with the Wednesday rollover | ## Testing - `tsc` passes for common, web and mobile. `eslint` passes on the touched files. - `vitest`: new tests for the common period and queue-source helpers and for the share-modal saga's `Modal Opened`. The web OG URL test and tracks-table tests pass. - Checked locally on the web dev server by loading `/explore/weekly-rotation/dylan` signed out and reading Amplitude's unsent-event queue: - `Weekly Rotation: Page View` was recorded with `{isOwnMix: false, isSignedIn: false, ownerUserId: "18269", period: "2026-39", status: "success", trackCount: 30}`. - Play recorded `Weekly Rotation: Play All` and `Playback: Play` with `source: "weekly rotation"`. Pressing Next recorded `Playback: Play` with `source: "passive", weeklyRotationOwnerId: "18269"`. - Opening the share modal recorded `Modal Opened` with `{name: "Share", kind: "weeklyRotation", source: "page", userId: 18269}`. Copy link recorded `Share` with `channel: "copyLink"`. - Mobile jest does not run in this worktree: the react-native module mapping fails, which also happens on the base branch. No touched mobile file has tests. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #97 (
chore/review-cleanup).Adds one structured
logger.infoline per processed Weekly Rotation notification (msg: 'weekly rotation push processed') so sends per period can be counted from logs:notificationType: 'weekly_rotation',groupId(weekly_rotation:<YYYY-WW>:<user>),userIdbrowserPushEnabled,browserSent(web push subscriptions delivered)mobileDevices,mobileSent,mobileDisabled(endpoints SNS reported disabled)skippedReason:no_mobile_devicesorabusivewhen no mobile push is attemptedThe client side is instrumented in AudiusProject/apps#14605:
Notifications: Open Push Notificationnow carriestype: 'WeeklyRotation'and the pushid. Push open rate is opens in Amplitude divided by the sum ofmobileSentfor that period'sgroupIdprefix.Note:
sendPushNotificationswallows non-endpoint errors, somobileSentcounts sends that did not come back as a disabled endpoint.Testing
tsc --noEmitpasses, and eslint and prettier pass on the changed file.audius-compose test run notifications), which I did not run locally. Behavior is unchanged apart from the log line.🤖 Generated with Claude Code