Skip to content

feat(web): top cities card on the Artist Dashboard - #14770

Merged
dylanjeffers merged 1 commit into
mainfrom
feat/dashboard-top-cities
Nov 12, 2026
Merged

dylanjeffers merged 1 commit into
mainfrom
feat/dashboard-top-cities

Conversation

@dylanjeffers

Copy link
Copy Markdown
Contributor

Adds a Top Cities card to the web Artist Dashboard, under the stat tiles. It lists the 10 cities whose listeners played the artist's tracks the most, for the last 7 days, last 30 days (default) or all time, with a bar and play count for each. This matches the "top city" line in the weekly artist recap notification.

Depends on AudiusProject/api#1072 (GET /v1/users/:id/listener_cities). That endpoint only answers the artist or their managers, so manager mode works.

  • Behind the new dashboard_top_cities flag: on in development, off in production
  • Only shown to accounts with tracks
  • useListenerCities tan-query hook in common
  • SDK: only users.getListenerCities and its two models, generated from the api PR's swagger. Unrelated drift from the generator was left out. Changeset included.
  • Copy on the card notes that locations come from the listener's internet connection and that VPNs and mobile networks can show a nearby city.

Mobile is not included.

Tested locally against the api branch with a seeded artist: the week/month/all switch, the artist's own plays and plays with no city left out, empty and error states.

🤖 Generated with Claude Code

Shows the cities whose listeners played the artist's tracks the most for
the last 7 days, 30 days or all time, from the new
/v1/users/:id/listener_cities endpoint. Behind dashboard_top_cities (dev
on, prod off).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Nov 12, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 3a43617

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@audius/sdk Minor
@audius/sdk-legacy Patch
@audius/protocol-dashboard Patch
@audius/sp-actions Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

dylanjeffers added a commit to AudiusProject/api that referenced this pull request Nov 12, 2026
Adds `GET /v1/users/:id/listener_cities?time=week|month|all&limit=10`
for the Artist Dashboard's new Top Cities card. It returns the cities
whose listeners played the artist's tracks the most: `city`, `region`,
`country`, `play_count`. Only the artist or one of their managers can
call it (`requireAuthForUserId`).

Counting matches the weekly artist recap: plays of the artist's current
tracks, trimmed city, empty cities skipped, the artist's own plays
skipped. Locations come from the listener's IP (MaxMind on the streaming
node), so VPN and mobile carrier traffic can land in the wrong city.

### Why a rollup table

`plays` has no index by track (only `created_at`, `slot`, `signature`
and two user-first indexes), so a per-artist query would scan every play
in the window, or the whole table for `all`. `aggregate_monthly_plays`
has country but not city.

So this adds `aggregate_daily_city_plays` (track, UTC day, country,
region, city, count), with the primary key leading on `play_item_id`.
The endpoint reads it through `track_owner_id_idx`, then the primary
key.

A new indexer job, `CityPlaysJob`, fills it every 15s from `plays`,
checkpointed on `plays.id` in `indexing_checkpoints` (same pattern as
`hourly_play_counts`). Each run handles at most 250k play ids. On first
deploy it backfills whatever `plays` still holds (prune keeps about 400
days), starting at the lowest remaining id. After that, `all` keeps
growing even as old plays are pruned.

- `week` = today plus the 6 days before (UTC), `month` = 30 days
- The migration only creates the table, so it is quick. The backfill
runs in the job.

### Not measured

City coverage and backfill time in prod: there's no prod DB access from
here. Worth checking `SELECT last_checkpoint FROM indexing_checkpoints
WHERE tablename = 'aggregate_daily_city_plays'` against `MAX(plays.id)`
after deploy.

### Tests

- `TestV1UsersListenerCities`: week/month/all windows, limit, other
artists' tracks excluded, manager access, 403 for other users, 400 for a
bad `time`
- `TestCityPlaysJob`: batching across runs, owner plays and empty or
NULL cities skipped, trimming, day bucketing
- `TestCityPlaysJob_SkipsPrunedIds`

Also ran it end to end with a local api and the web dashboard
((AudiusProject/apps#14770)).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@dylanjeffers
dylanjeffers merged commit e7fae88 into main Nov 12, 2026
33 checks passed
@dylanjeffers
dylanjeffers deleted the feat/dashboard-top-cities branch November 12, 2026 00:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant