issue 1859: answer the mail page's greeting and reading pane from mail reading
ADR 0515 §1, §5: list also answers the mails the mail signal told of today, and each line its mail's folder and UID; entries takes an id and answers that one entry whole, with its sender's address, tasks, promises and read-later, for the reading pane's corrections.
This commit is contained in:
@@ -41,6 +41,9 @@ type OperatorList struct {
|
||||
YouPromised, WaitingOnOthers, OnItsWay []ListItem
|
||||
GoodToKnow, ReadLater, ScamWarnings []ListItem // mail items
|
||||
HandledToday []ListItem
|
||||
// ToldToday is the mails the mail signal told of today, for the page's greeting (ADR 0515 §1, ADR 0461):
|
||||
// an entry told and judged today (the signal is sent once a batch, right after it judged, tell.go).
|
||||
ToldToday []ListItem
|
||||
}
|
||||
|
||||
// BuildList builds the list of the entries of accounts (every account when accounts is empty) for the day of
|
||||
@@ -67,6 +70,9 @@ func BuildList(entries []Entry, handlings []Handling, accounts []string, now tim
|
||||
hs := byItem[e.ID]
|
||||
mail := hs[ItemMail]
|
||||
mailItem := ListItem{Entry: e, Item: ItemMail, Kind: "mail", Line: e.Summary}
|
||||
if e.Told && dayOf(e.Judged).Equal(today) {
|
||||
l.ToldToday = append(l.ToldToday, mailItem)
|
||||
}
|
||||
if !mail.Handled.IsZero() {
|
||||
if dayOf(mail.Handled).Equal(today) {
|
||||
mailItem.Handled = mail.Handled
|
||||
@@ -129,7 +135,7 @@ func BuildList(entries []Entry, handlings []Handling, accounts []string, now tim
|
||||
for _, section := range [][]ListItem{l.Today, l.ThisWeek, l.Later, l.YouPromised, l.WaitingOnOthers, l.OnItsWay} {
|
||||
sortByDue(section)
|
||||
}
|
||||
for _, section := range [][]ListItem{l.GoodToKnow, l.ReadLater, l.ScamWarnings} {
|
||||
for _, section := range [][]ListItem{l.GoodToKnow, l.ReadLater, l.ScamWarnings, l.ToldToday} {
|
||||
sort.SliceStable(section, func(i, j int) bool { return section[i].Entry.Date.After(section[j].Entry.Date) })
|
||||
}
|
||||
sort.SliceStable(l.HandledToday, func(i, j int) bool { return l.HandledToday[i].Handled.After(l.HandledToday[j].Handled) })
|
||||
@@ -168,7 +174,7 @@ func sortByDue(section []ListItem) {
|
||||
func listItemOut(it ListItem) map[string]any {
|
||||
e := it.Entry
|
||||
out := map[string]any{
|
||||
"id": e.ID, "item": it.Item, "kind": it.Kind, "line": it.Line, "account": e.Account,
|
||||
"id": e.ID, "item": it.Item, "kind": it.Kind, "line": it.Line, "account": e.Account, "folder": e.Folder, "uid": e.UID,
|
||||
"sender-name": e.SenderName, "sender-address": e.SenderAddress, "subject": e.Subject,
|
||||
"date": e.Date.UTC().Format(time.RFC3339), "summary": e.Summary, "mark": string(e.Mark), "why": e.Why,
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ type listed struct {
|
||||
ReadLater []map[string]any `json:"read-later"`
|
||||
HandledToday []map[string]any `json:"handled-today"`
|
||||
ScamWarnings []map[string]any `json:"scam-warnings"`
|
||||
ToldToday []map[string]any `json:"told-today"`
|
||||
}
|
||||
|
||||
func listOf(t *testing.T, b *Bundle, args map[string]any) listed {
|
||||
|
||||
@@ -240,7 +240,7 @@ func (b *Bundle) listTool(args map[string]any) (any, error) {
|
||||
"you-promised": sectionOut(l.YouPromised, limit), "waiting-on-others": sectionOut(l.WaitingOnOthers, limit),
|
||||
"on-its-way": sectionOut(l.OnItsWay, limit), "good-to-know": sectionOut(l.GoodToKnow, limit),
|
||||
"read-later": sectionOut(l.ReadLater, limit), "scam-warnings": sectionOut(l.ScamWarnings, limit),
|
||||
"handled-today": sectionOut(l.HandledToday, limit),
|
||||
"handled-today": sectionOut(l.HandledToday, limit), "told-today": sectionOut(l.ToldToday, limit),
|
||||
}
|
||||
looked, ok, err := store.LastAction(ctx, ActPageOpened)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package main
|
||||
|
||||
// Issue 1859 (novox/hq ADR 0515 §1, §5): what the mail page needs of mail reading — the mail signals told
|
||||
// today for its greeting, each line's folder and UID for the reading pane, and one entry by its id, whole,
|
||||
// for the pane's corrections.
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 1859/AC1 (the reader's half): list answers the mails the mail signal told of today, and each line carries
|
||||
// its mail's folder and UID.
|
||||
func TestListAnswersWhatWasToldTodayAndWhereEachMailIs(t *testing.T) {
|
||||
store := newMemStore()
|
||||
now := brusselsAt(2026, 10, 22, 9, 0)
|
||||
told := addEntry(t, store, Entry{Account: "work", Folder: "INBOX", UID: 41, Mark: MarkMatters, Told: true,
|
||||
Judged: now.Add(-time.Hour), Summary: "Sara needs the signed offer", Tasks: []Task{{N: 1, Line: "Sign the offer"}}})
|
||||
addEntry(t, store, Entry{Account: "work", UID: 42, Mark: MarkMatters, Told: true, Judged: now.AddDate(0, 0, -1)})
|
||||
l := listOf(t, listBundle(store, &now), map[string]any{})
|
||||
if len(l.ToldToday) != 1 || l.ToldToday[0]["id"] != float64(told) || l.ToldToday[0]["summary"] != "Sara needs the signed offer" {
|
||||
t.Fatalf("told today = %v, want the one mail told today", l.ToldToday)
|
||||
}
|
||||
if it := l.Later[0]; it["folder"] != "INBOX" || it["uid"] != float64(41) {
|
||||
t.Fatalf("a line = %v, want its folder INBOX and uid 41", it)
|
||||
}
|
||||
}
|
||||
|
||||
// 1859/AC7 (the reader's half): entries with an id answers that one entry whole: its folder and UID, sender
|
||||
// address, tasks, promises and read-later.
|
||||
func TestEntriesAnswersOneEntryWhole(t *testing.T) {
|
||||
store := newMemStore()
|
||||
id := entryWithItems(t, store)
|
||||
addEntry(t, store, Entry{Account: "work", UID: 77})
|
||||
out, err := testBundle(store, "mesh-mail-web").entriesTool(map[string]any{"id": float64(id)})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := out.(map[string]any)["entries"].([]map[string]any)
|
||||
if len(got) != 1 {
|
||||
t.Fatalf("entries = %v, want the one asked", got)
|
||||
}
|
||||
e := got[0]
|
||||
tasks, _ := e["tasks"].([]map[string]any)
|
||||
promises, _ := e["promises"].([]map[string]any)
|
||||
if e["folder"] != "INBOX" || e["uid"] != uint32(5) || e["sender-address"] != "lena@harbor.example" ||
|
||||
len(tasks) != 2 || tasks[1]["n"] != 2 || tasks[1]["line"] != "Forward the invoice to finance" ||
|
||||
len(promises) != 1 || promises[0]["kind"] != "on its way" || e["read-later"] != false {
|
||||
t.Fatalf("entry = %v", e)
|
||||
}
|
||||
}
|
||||
@@ -149,6 +149,7 @@ func (b *Bundle) Tools() []stdio.Tool {
|
||||
"limit": integer("how many to answer; a default bounds it when left out"),
|
||||
"group": text("only mails of this mail group, by its label"),
|
||||
"mark": text("only mails of this mark: matters, scam warning or neither"),
|
||||
"id": integer("only this one mail entry, by its id"),
|
||||
}),
|
||||
Run: b.entriesTool},
|
||||
{Name: GroupsTool,
|
||||
@@ -276,18 +277,40 @@ func entryOut(e Entry, labels map[int64]string) map[string]any {
|
||||
if !e.DueDate.IsZero() {
|
||||
due = e.DueDate.Format("2006-01-02")
|
||||
}
|
||||
tasks := make([]map[string]any, len(e.Tasks))
|
||||
for i, t := range e.Tasks {
|
||||
tasks[i] = map[string]any{"n": t.N, "line": t.Line, "due-date": dateOrEmpty(t.Due)}
|
||||
}
|
||||
promises := make([]map[string]any, len(e.Promises))
|
||||
for i, p := range e.Promises {
|
||||
promises[i] = map[string]any{"n": p.N, "kind": string(p.Kind), "line": p.Line, "who": p.Who, "date": dateOrEmpty(p.Date)}
|
||||
}
|
||||
return map[string]any{
|
||||
"id": e.ID, "account": e.Account, "message-id": e.MessageID, "date": e.Date.UTC().Format(time.RFC3339),
|
||||
"sender-name": e.SenderName, "subject": e.Subject, "group": labels[e.Group], "summary": e.Summary,
|
||||
"mark": string(e.Mark), "why": e.Why, "due-date": due,
|
||||
// For the mail page's reading pane (issue 1859): where the mail is, who sent it, and its items.
|
||||
"folder": e.Folder, "uid": e.UID, "sender-address": e.SenderAddress,
|
||||
"tasks": tasks, "promises": promises, "read-later": e.ReadLater,
|
||||
}
|
||||
}
|
||||
|
||||
func dateOrEmpty(t time.Time) string {
|
||||
if t.IsZero() {
|
||||
return ""
|
||||
}
|
||||
return t.Format("2006-01-02")
|
||||
}
|
||||
|
||||
func (b *Bundle) entriesTool(args map[string]any) (any, error) {
|
||||
if _, err := b.checkCaller(args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := only(args, "limit", "group", "mark"); err != nil {
|
||||
if err := only(args, "limit", "group", "mark", "id"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
onlyID, err := number(args, "id", 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
limit, err := number(args, "limit", 0)
|
||||
@@ -326,6 +349,16 @@ func (b *Bundle) entriesTool(args map[string]any) (any, error) {
|
||||
} else {
|
||||
entries = FilterEntries(entries, 0, mark)
|
||||
}
|
||||
if onlyID > 0 {
|
||||
// One entry, whole, by its id (issue 1859: the mail page's reading pane); none when it is not held.
|
||||
var one []Entry
|
||||
for _, e := range entries {
|
||||
if e.ID == int64(onlyID) {
|
||||
one = append(one, e)
|
||||
}
|
||||
}
|
||||
entries = one
|
||||
}
|
||||
entries = SortEntries(entries)
|
||||
if limit > 0 && len(entries) > limit {
|
||||
entries = entries[:limit]
|
||||
|
||||
Reference in New Issue
Block a user