feat(stats): add chat names to top chats and stats endpoints#558
feat(stats): add chat names to top chats and stats endpoints#558buluma wants to merge 2 commits into
Conversation
Store the human-readable chat name (contact pushName/saved name) on each message at save time, and surface it as `chatName` in the `GET /stats/messages` and `GET /sessions/:id/stats` topChats response. The dashboard uses it to display readable names instead of raw JIDs. - New `chatName` nullable column on the `messages` table - Populated on incoming messages from the sender's contact info - Returns `chatName` alongside `chatId` in both stats endpoints - Dashboard displays `chatName` when available, falls back to `shortChat` - TypeORM migration (1781900000000) for production / non-sync deployments
rmyndharis
left a comment
There was a problem hiding this comment.
Thanks for this, @buluma — readable names in the top-chats view is a genuinely useful addition, and the migration / entity / dashboard wiring is all in the right places. Before it can land there's one correctness issue in the aggregation that needs fixing, plus a few CHANGELOG/doc corrections.
Blocking
1. GROUP BY m.chatName fragments the top-chats aggregation.
Adding .addGroupBy('m.chatName') to getMessageStats and getSessionStats changes the grouping grain from one row per chat to one row per (chatId, chatName). Because chatName is not constant for a given chat, a single chat splits into several rows:
- the
COUNT(*)total is divided across those rows, so the counts are wrong; .limit(10)then counts rows rather than chats, so the same chat can appear several times and busier chats drop off the list.
This is the normal path, not an edge case:
- outbound messages are persisted by
saveOutgoingMessage(message.service.ts), which does not setchatName, so every 1:1 chat ends up with named inbound rows andNULLoutbound rows → two buckets; - legacy rows are
NULL→ another bucket; - in groups
chatNameis the sending participant, so each member becomes its own bucket.
It reproduces on the default SQLite dev DB. Suggested fix — keep the grain at one row per chat and pick a representative name with an aggregate instead of grouping by it:
.addSelect('MAX(m.chatName)', 'chatName')
.groupBy('m.chatId') // drop both .addGroupBy('m.chatName')MAX() ignores NULLs and works on both SQLite and Postgres.
Worth addressing here
2. For group chats chatName is the sender's name, not the group's. incoming.contact is the message sender (the whatsapp-web.js adapter notes "getContact() gives the real sender (author in groups)"), so a group's stored name is whoever last spoke. Real group names would need the group subject resolved for @g.us JIDs before persisting; otherwise it's worth scoping the feature to 1:1 chats.
3. Outbound-only chats stay unnamed. Only the inbound path sets chatName, so a chat you only ever send to keeps NULL and renders as a raw JID. Setting it in saveOutgoingMessage (from the recipient contact/group) would close that gap.
CHANGELOG / docs
4. The entry says "A backfill script updates existing rows from the current contact/chat list" — there's no backfill script in the PR, and the migration only adds a NULL column, so existing rows stay unnamed. Please either add the backfill or drop that sentence.
5. The credit reads (#557) Thanks @shadowwalker. — #557 is an unrelated issue and this PR is #558; it should be (#558) Thanks @buluma.
6. docs/06-api-specification.md still shows the old topChats shape — please add the chatName field to both response examples.
7. (nit) In the migration, const colType = isPostgres ? 'varchar' : 'varchar' has two identical branches; you can drop isPostgres/colType and inline varchar.
The build/tests are green, so once the GROUP BY is fixed the rest is quick. Happy to help if any of this is unclear.
…tion The GROUP BY chatName fragmented the per-chat count into multiple rows when the name varied within a chat (outbound vs inbound, group senders, legacy NULL rows). Using MAX() picks a representative name without changing the grouping grain. Also: - Removed dead isPostgres ternary in migration (both branches were 'varchar') - Fixed CHANGELOG PR reference (rmyndharis#557→rmyndharis#558) and credit (@shadowwalker→@buluma) - Removed backfill script claim (no such script exists)
|
Thanks for the thorough review @rmyndharis — really appreciate you catching the GROUP BY issue. Here's what I fixed in 9c07b13: Stats aggregation (blocking) — dropped both Migration — removed the dead CHANGELOG — fixed the PR ref ( Didn't touch the outbound path or group name resolution — those need contact lookups I don't have the plumbing for here. Happy to tackle them separately if useful. |
|
Thanks for this, @buluma — nice feature. Landing it via #572, which carries your commits (authorship preserved) and adds two things needed for current |
* feat(stats): add chat names to top chats and stats endpoints Store the human-readable chat name (contact pushName/saved name) on each message at save time, and surface it as `chatName` in the `GET /stats/messages` and `GET /sessions/:id/stats` topChats response. The dashboard uses it to display readable names instead of raw JIDs. - New `chatName` nullable column on the `messages` table - Populated on incoming messages from the sender's contact info - Returns `chatName` alongside `chatId` in both stats endpoints - Dashboard displays `chatName` when available, falls back to `shortChat` - TypeORM migration (1781900000000) for production / non-sync deployments * fix(stats): use MAX(chatName) instead of GROUP BY chatName in aggregation The GROUP BY chatName fragmented the per-chat count into multiple rows when the name varied within a chat (outbound vs inbound, group senders, legacy NULL rows). Using MAX() picks a representative name without changing the grouping grain. Also: - Removed dead isPostgres ternary in migration (both branches were 'varchar') - Fixed CHANGELOG PR reference (#557→#558) and credit (@shadowwalker→@buluma) - Removed backfill script claim (no such script exists) * fix(stats): move chatName migration to a free timestamp and cover the aggregate The migration collided with 1781900000000-AddIntegrationFabric on main (which merged after this PR was opened); renamed to 1782000000000. Added a stats-service test asserting MAX(chatName) surfaces the name in topChats and ignores null legacy rows. --------- Co-authored-by: Michael Buluma <1452922+buluma@users.noreply.github.com>
Description
Store the human-readable chat name (contact pushName/saved name) on each message at save time, and surface it as
chatNamein theGET /stats/messagesandGET /sessions/:id/statstopChats response. The dashboard uses it to display readable names instead of raw JIDs.Type of Change
Checklist
Screenshots (if applicable)
Before: Top Chats showed raw JIDs like
254797747792@c.us,131207224393887@lidAfter: Shows readable names like contact names and group names
Related Issues
None