package apperr import ( "sort" "time" ) // Group is one row of the Errors tab: every record sharing a fingerprint // in the window, summed (spec ยง3.6). type Group struct { FP string `json:"fp"` Source string `json:"source"` Kind string `json:"kind,omitempty"` Name string `json:"name"` Level string `json:"level,omitempty"` Count int `json:"count"` First time.Time `json:"first"` Last time.Time `json:"last"` Hosts []string `json:"hosts"` Releases []string `json:"releases,omitempty"` Sample Record `json:"sample"` } // GroupRecords folds records into groups by FP, newest Last first. The // sample is the earliest record of the group (its stack and fields are // the first occurrence's). func GroupRecords(recs []Record) []Group { byFP := make(map[string]*Group) var order []string for _, r := range recs { first, last := r.First, r.Last if first.IsZero() { first = r.TS } if last.IsZero() { last = r.TS } count := r.Count if count == 0 { count = 1 } g := byFP[r.FP] if g == nil { g = &Group{FP: r.FP, Source: r.Source, Kind: r.Kind, Name: r.Name, Level: r.Level, First: first, Last: last, Sample: r} byFP[r.FP] = g order = append(order, r.FP) } g.Count += count if first.Before(g.First) { g.First = first g.Sample = r } if last.After(g.Last) { g.Last = last } g.Hosts = addUnique(g.Hosts, r.Host) g.Releases = addUnique(g.Releases, r.Release) } out := make([]Group, 0, len(order)) for _, fp := range order { g := byFP[fp] sort.Strings(g.Hosts) sort.Strings(g.Releases) out = append(out, *g) } sort.SliceStable(out, func(i, j int) bool { return out[i].Last.After(out[j].Last) }) return out } func addUnique(list []string, v string) []string { if v == "" { return list } for _, x := range list { if x == v { return list } } return append(list, v) }