Skip to content
quarry Artifact [5f17ae6611]
Login

Artifact [5f17ae6611]

Artifact 5f17ae661182acc76bf08f3607f4f43a7a3affb8592699e4223022710d279da8:


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 ""
}