package index
import (
"strings"
)
// ticketChangeFields are the fields a change summary names, in the order
// a reader looks for them. Mimetypes and the ticket's own id are
// bookkeeping; comments are summarised separately.
var ticketChangeFields = []string{"status", "resolution", "priority", "severity", "type", "subsystem", "assignee", "title"}
// changeCommentMax bounds the comment line in a summary.
const changeCommentMax = 120
// attachTicketChanges fills Change on the ticket rows of a timeline page:
// "opened" for a ticket's first change, otherwise the fields that change
// set and the first line of its comment. Only returned rows are read.
func (ix *Index) attachTicketChanges(out []TimelineEntry) error {
var rows []int
for i := range out {
if out[i].Kind == "t" {
rows = append(rows, i)
}
}
if len(rows) == 0 {
return nil
}
fields, err := ix.db.Prepare(`SELECT name, value FROM ticket_field WHERE hash = ?`)
if err != nil {
return err
}
defer fields.Close()
first, err := ix.db.Prepare(`SELECT hash FROM ticket_change WHERE ticket = ? ORDER BY date, hash LIMIT 1`)
if err != nil {
return err
}
defer first.Close()
for _, i := range rows {
e := &out[i]
var opener string
if err := first.QueryRow(e.Ref).Scan(&opener); err == nil && opener == e.Hash {
e.Change = "opened"
continue
}
rs, err := fields.Query(e.Hash)
if err != nil {
return err
}
set := map[string]string{}
for rs.Next() {
var n, v string
if err := rs.Scan(&n, &v); err != nil {
rs.Close()
return err
}
set[n] = v
}
rs.Close()
e.Change = summarizeTicketChange(set)
}
return nil
}
// summarizeTicketChange writes one change as a short phrase:
// "status Closed, resolution Fixed; comment: the fix is in". Empty when
// the change set nothing a reader would name.
func summarizeTicketChange(set map[string]string) string {
var parts []string
for _, f := range ticketChangeFields {
if v, ok := set[f]; ok {
v = strings.TrimSpace(v)
if v == "" {
parts = append(parts, f+" cleared")
} else {
parts = append(parts, f+" "+firstLine(v, 60))
}
}
}
out := strings.Join(parts, ", ")
comment := set["icomment"]
if comment == "" {
comment = set["comment"]
}
if comment == "" {
comment = set["+comment"]
}
if c := firstLine(comment, changeCommentMax); c != "" {
if out != "" {
out += "; "
}
out += "comment: " + c
}
return out
}
// firstLine is s's first non-empty line, cut to max runes.
func firstLine(s string, max int) string {
for _, l := range strings.Split(s, "\n") {
if l = strings.TrimSpace(l); l != "" {
if r := []rune(l); len(r) > max {
return string(r[:max-1]) + "…"
}
return l
}
}
return ""
}