buildDigestData builds the notification payload; shape mirrors the golden fixtures in coderd/notifications/testdata. Truncation keeps the oldest archived roots (created_at ASC from the query) to preserve index-driven ordering; revisit if the digest becomes the primary surface for reviewing archived
(rows []database.AutoArchiveInactiveChatsRow, autoArchiveDays, retentionDays int32, tickStart time.Time)
| 616 | // preserve index-driven ordering; revisit if the digest becomes the |
| 617 | // primary surface for reviewing archived chats. |
| 618 | func buildDigestData(rows []database.AutoArchiveInactiveChatsRow, autoArchiveDays, retentionDays int32, tickStart time.Time) map[string]any { |
| 619 | // Cap titles; overflow surfaces as "...and N more" via the template. |
| 620 | overflow := 0 |
| 621 | if len(rows) > chatAutoArchiveDigestMaxChats { |
| 622 | overflow = len(rows) - chatAutoArchiveDigestMaxChats |
| 623 | rows = rows[:chatAutoArchiveDigestMaxChats] |
| 624 | } |
| 625 | |
| 626 | chats := make([]map[string]any, 0, len(rows)) |
| 627 | for _, r := range rows { |
| 628 | chats = append(chats, map[string]any{ |
| 629 | "title": r.Title, |
| 630 | "last_activity_humanized": humanize.RelTime(r.LastActivityAt, tickStart, "ago", "from now"), |
| 631 | }) |
| 632 | } |
| 633 | |
| 634 | // Stringify the int32 config values: the template's |
| 635 | // {{if eq .Data.retention_days "0"}} branch requires both |
| 636 | // operands to share a type, and Go templates do not coerce |
| 637 | // numeric ↔ string. Storing a raw int here would silently |
| 638 | // take the deletion-warning branch on every notification. |
| 639 | data := map[string]any{ |
| 640 | "auto_archive_days": strconv.Itoa(int(autoArchiveDays)), |
| 641 | "retention_days": strconv.Itoa(int(retentionDays)), |
| 642 | "archived_chats": chats, |
| 643 | } |
| 644 | if overflow > 0 { |
| 645 | data["additional_archived_count"] = strconv.Itoa(overflow) |
| 646 | } |
| 647 | return data |
| 648 | } |
no outgoing calls
no test coverage detected