package widgets import ( "bytes" "io" "os/exec" "github.com/gdamore/tcell" "github.com/mattn/go-runewidth" "git.sr.ht/~sircmpwn/aerc2/lib/ui" ) type MessageViewer struct { grid *ui.Grid term *Terminal } var testMsg = `Makes the following changes to the Event type: * make 'user' and 'ticket' nullable since some events require it * add 'by_user' and 'from_ticket' to enable mentions * remove 'assinged_user' which is no longer used Ticket: https://todo.sr.ht/~sircmpwn/todo.sr.ht/156 --- tests/test_comments.py | 23 ++- .../versions/75ff2f7624fd_new_event_fields.py | 142 ++++++++++++++++++ todosrht/templates/events.html | 18 ++- todosrht/templates/ticket.html | 31 +++- todosrht/tickets.py | 14 +- todosrht/types/event.py | 16 +- 6 files changed, 207 insertions(+), 37 deletions(-) create mode 100644 todosrht/alembic/versions/75ff2f7624fd_new_event_fields.py diff --git a/tests/test_comments.py b/tests/test_comments.py index 4b3161d..b85d751 100644 --- a/tests/test_comments.py +++ b/tests/test_comments.py @@ -253,20 +253,25 @@ def test_notifications_and_events(mailbox): # Check correct events are generated comment_events = {e for e in ticket.events if e.event_type == EventType.comment} - user_events = {e for e in ticket.events + u1_events = {e for e in u1.events + if e.event_type == EventType.user_mentioned} + u2_events = {e for e in u2.events if e.event_type == EventType.user_mentioned} assert len(comment_events) == 1 - assert len(user_events) == 2 + assert len(u1_events) == 1 + assert len(u2_events) == 1 - u1_mention = next(e for e in user_events if e.user == u1) - u2_mention = next(e for e in user_events if e.user == u2) + u1_mention = u1_events.pop() + u2_mention = u2_events.pop() assert u1_mention.comment == comment - assert u1_mention.ticket == ticket + assert u1_mention.from_ticket == ticket + assert u1_mention.by_user == commenter assert u2_mention.comment == comment - assert u2_mention.ticket == ticket + assert u2_mention.from_ticket == ticket + assert u2_mention.by_user == commenter assert len(t1.events) == 1 assert len(t2.events) == 1 @@ -276,10 +281,12 @@ def test_notifications_and_events(mailbox): t2_mention = t2.events[0] assert t1_mention.comment == comment - assert t1_mention.user == commenter + assert t1_mention.from_ticket == ticket + assert t1_mention.by_user == commenter assert t2_mention.comment == comment - assert t2_mention.user == commenter + assert t2_mention.from_ticket == ticket + assert t2_mention.by_user == commenter def test_ticket_mention_pattern(): def match(text): diff --git a/todosrht/alembic/versions/75ff2f7624fd_new_event_fields.py b/todosrht/alembic/versions/75ff2f7624fd_new_event_fields.py new file mode 100644 index 0000000..1c55bfe --- /dev/null +++ b/todosrht/alembic/versions/75ff2f7624fd_new_event_fields.py @@ -0,0 +1,142 @@ +"""Add new event fields and migrate data. + +Also makes Event.ticket_id and Event.user_id nullable since some these fields +can be empty for mention events. + +Revision ID: 75ff2f7624fd +Revises: c7146cb70d6b +Create Date: 2019-03-28 16:26:18.714300 + +""" + +# revision identifiers, used by Alembic. +revision = "75ff2f7624fd" +down_revision = "c7146cb70d6b" ` func NewMessageViewer() *MessageViewer { grid := ui.NewGrid().Rows([]ui.GridSpec{ {ui.SIZE_EXACT, 4}, {ui.SIZE_WEIGHT, 1}, }).Columns([]ui.GridSpec{ {ui.SIZE_WEIGHT, 1}, }) headers := ui.NewGrid().Rows([]ui.GridSpec{ {ui.SIZE_EXACT, 1}, {ui.SIZE_EXACT, 1}, {ui.SIZE_EXACT, 1}, {ui.SIZE_EXACT, 1}, }).Columns([]ui.GridSpec{ {ui.SIZE_WEIGHT, 1}, {ui.SIZE_WEIGHT, 1}, }) headers.AddChild( &HeaderView{ Name: "From", Value: "Ivan Habunek ", }).At(0, 0) headers.AddChild( &HeaderView{ Name: "To", Value: "~sircmpwn/sr.ht-dev@lists.sr.ht", }).At(0, 1) headers.AddChild( &HeaderView{ Name: "Subject", Value: "[PATCH todo.sr.ht v2 1/3 Alter Event fields " + "and migrate data]", }).At(1, 0).Span(1, 2) headers.AddChild( &HeaderView{ Name: "PGP", Value: "✓ Valid PGP signature from Ivan Habunek", }).At(2, 0).Span(1, 2) headers.AddChild(ui.NewFill(' ')).At(3, 0).Span(1, 2) body := ui.NewGrid().Rows([]ui.GridSpec{ {ui.SIZE_WEIGHT, 1}, }).Columns([]ui.GridSpec{ {ui.SIZE_WEIGHT, 1}, {ui.SIZE_EXACT, 20}, }) cmd := exec.Command("sh", "-c", "./contrib/hldiff.py | less -R") pipe, _ := cmd.StdinPipe() term, _ := NewTerminal(cmd) term.OnStart = func() { go func() { reader := bytes.NewBufferString(testMsg) io.Copy(pipe, reader) pipe.Close() }() } term.Focus(true) body.AddChild(term).At(0, 0) body.AddChild(ui.NewBordered( &MultipartView{}, ui.BORDER_LEFT)).At(0, 1) grid.AddChild(headers).At(0, 0) grid.AddChild(body).At(1, 0) return &MessageViewer{grid, term} } func (mv *MessageViewer) Draw(ctx *ui.Context) { mv.grid.Draw(ctx) } func (mv *MessageViewer) Invalidate() { mv.grid.Invalidate() } func (mv *MessageViewer) OnInvalidate(fn func(d ui.Drawable)) { mv.grid.OnInvalidate(func(_ ui.Drawable) { fn(mv) }) } func (mv *MessageViewer) Event(event tcell.Event) bool { return mv.term.Event(event) } func (mv *MessageViewer) Focus(focus bool) { mv.term.Focus(focus) } type HeaderView struct { onInvalidate func(d ui.Drawable) Name string Value string } func (hv *HeaderView) Draw(ctx *ui.Context) { size := runewidth.StringWidth(hv.Name) var ( hstyle tcell.Style vstyle tcell.Style ) // TODO: Make this more robust and less dumb if hv.Name == "PGP" { vstyle = tcell.StyleDefault.Foreground(tcell.ColorGreen) hstyle = tcell.StyleDefault.Bold(true) } else { vstyle = tcell.StyleDefault hstyle = tcell.StyleDefault.Bold(true) } ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', vstyle) ctx.Printf(0, 0, hstyle, hv.Name) ctx.Printf(size, 0, vstyle, " "+hv.Value) } func (hv *HeaderView) Invalidate() { if hv.onInvalidate != nil { hv.onInvalidate(hv) } } func (hv *HeaderView) OnInvalidate(fn func(d ui.Drawable)) { hv.onInvalidate = fn } type MultipartView struct { onInvalidate func(d ui.Drawable) } func (mpv *MultipartView) Draw(ctx *ui.Context) { ctx.Fill(0, 0, ctx.Width(), ctx.Height(), ' ', tcell.StyleDefault) ctx.Fill(0, 0, ctx.Width(), 1, ' ', tcell.StyleDefault.Reverse(true)) ctx.Printf(0, 0, tcell.StyleDefault.Reverse(true), "text/plain") ctx.Printf(0, 1, tcell.StyleDefault, "text/html") ctx.Printf(0, 2, tcell.StyleDefault, "application/pgp-si…") } func (mpv *MultipartView) Invalidate() { if mpv.onInvalidate != nil { mpv.onInvalidate(mpv) } } func (mpv *MultipartView) OnInvalidate(fn func(d ui.Drawable)) { mpv.onInvalidate = fn }