package main /* This file is part of taler-dashboard Copyright (C) 2023 Özgür Kesim taler-dashboard is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. taler-dashboard is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You can receive a copy of the GNU Affero General Public License from @author Özgür Kesim */ import ( "log" "sort" "strings" "time" ) type Issues []*Issue type Issue struct { Id uint32 Summary string Description string Project KeyVal Category KeyVal TargetVersion KeyVal `json:"target_version"` Reporter KeyVal Handler KeyVal Status KeyVal Resolution KeyVal ViewState KeyVal `json:"view_state"` Priority KeyVal Severity KeyVal Reproducibility KeyVal Sticky bool CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` Notes []Note Relationships []Relationship Children []Child `json:"-"` Tags []KeyVal History []Change } type Child struct { Id uint32 Summary string Status KeyVal Resolution KeyVal Handler KeyVal } type KeyVal struct { Id uint32 Name string Label string `json:",omitempty"` Color string `json:",omitempty"` } type Note struct { Id uint32 Text string Reporter KeyVal ViewState KeyVal `json:"view_state"` Attachments []Attachment Typ string `json:"type"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"created_at"` } type Attachment struct { Name string Content []byte } type Relationship struct { Id uint32 Typ KeyVal `json:"type"` Issue Child } type Change struct { CreatedAt time.Time `json:"created_at"` Message string User KeyVal Typ KeyVal `json:"type"` Note struct{ id int32 } } type ByCategory []*Issue func (b ByCategory) Len() int { return len(b) } func (b ByCategory) Less(i, j int) bool { return strings.Compare(b[i].Category.Name, b[j].Category.Name) < 0 } func (b ByCategory) Swap(i, j int) { b[i], b[j] = b[j], b[i] } type ByAssignment []*Issue func (b ByAssignment) Len() int { return len(b) } func (b ByAssignment) Less(i, j int) bool { return strings.Compare(b[i].Handler.Name, b[j].Handler.Name) < 0 } func (b ByAssignment) Swap(i, j int) { b[i], b[j] = b[j], b[i] } type ById []*Issue func (b ById) Len() int { return len(b) } func (b ById) Less(i, j int) bool { return b[i].Id < b[j].Id } func (b ById) Swap(i, j int) { b[i], b[j] = b[j], b[i] } type ByTarget []*Issue func (b ByTarget) Len() int { return len(b) } func (b ByTarget) Less(i, j int) bool { if strings.HasPrefix(b[i].TargetVersion.Name, "git") { return true } if strings.HasPrefix(b[j].TargetVersion.Name, "git") { return false } return strings.Compare(b[i].TargetVersion.Name, b[j].TargetVersion.Name) < 0 } func (b ByTarget) Swap(i, j int) { b[i], b[j] = b[j], b[i] } type ByHandlerAndId []*Issue func (b ByHandlerAndId) Len() int { return len(b) } func (b ByHandlerAndId) Less(i, j int) bool { if (b[i].Handler.Name == "") == (b[j].Handler.Name == "") { return b[i].Id < b[j].Id } else if b[i].Handler.Name == "" { return true } return false } func (b ByHandlerAndId) Swap(i, j int) { b[i], b[j] = b[j], b[i] } func (i Issues) Tags() (tags []string) { var m = map[string]bool{} for _, issue := range i { for _, tag := range issue.Tags { m[tag.Name] = true } } for tag := range m { tags = append(tags, tag) } sort.Strings(tags) return } func (is Issues) Assigned(assigned bool) (n int) { for _, i := range is { if assigned == (i.Handler.Name != "") { n += 1 } } return n } func (i *Issue) IsHandled() bool { return i.Handler.Name != "" } func (i Issues) TargetVersions() (targets []string) { var m = map[string]bool{} for _, issue := range i { m[issue.TargetVersion.Name] = true } for t := range m { targets = append(targets, t) } sort.Strings(targets) return } func (i Issues) Categories() []string { var m = map[string]bool{} for _, issue := range i { m[issue.Category.Name] = true } var r = []string{} for c := range m { r = append(r, c) } sort.Strings(r) return r } func (i Issues) ByCategory(cat string) (issues Issues) { for _, issue := range i { if issue.Category.Name == cat { issues = append(issues, issue) } } sort.Sort(sort.Reverse(ByHandlerAndId(issues))) return issues } func (i Issues) ByCategoryAndTarget(cat, tar string) (issues Issues) { for _, is := range i { if is.TargetVersion.Name == tar && is.Category.Name == cat { issues = append(issues, is) } } sort.Sort(sort.Reverse(ByHandlerAndId(issues))) return } // Follow the example for multiSort in https://pkg.go.dev/sort type lessFunc func(i1, i2 *Issue) bool type multiSorter struct { issues Issues less []lessFunc } func (ms *multiSorter) Len() int { return len(ms.issues) } func (ms *multiSorter) Sort(issues Issues) Issues { ms.issues = issues sort.Sort(ms) return ms.issues } func (ms *multiSorter) Swap(i, j int) { ms.issues[i], ms.issues[j] = ms.issues[j], ms.issues[i] } func (ms *multiSorter) Less(i, j int) bool { p, q := ms.issues[i], ms.issues[j] var k int for k = 0; k < len(ms.less)-1; k++ { less := ms.less[k] switch { case less(p, q): return true case less(q, p): return false } } return ms.less[k](p, q) } var severityOrder = map[string]int{ "block": 0, "crash": 1, "major": 2, "minor": 3, "text": 4, "trivial": 5, "tweak": 6} var lessFuncs = map[string]lessFunc{ "Category": func(i1, i2 *Issue) bool { return strings.Compare(i1.Category.Name, i2.Category.Name) < 0 }, "Assignment": func(i1, i2 *Issue) bool { return strings.Compare(i1.Handler.Name, i2.Handler.Name) < 0 }, "Handler": func(i1, i2 *Issue) bool { return strings.Compare(i1.Handler.Name, i2.Handler.Name) < 0 }, "Target": func(i1, i2 *Issue) bool { if strings.HasPrefix(i1.TargetVersion.Name, "git") { return true } if strings.HasPrefix(i2.TargetVersion.Name, "git") { return false } return strings.Compare(i1.TargetVersion.Name, i2.TargetVersion.Name) < 0 }, "Id": func(i1, i2 *Issue) bool { return i1.Id < i2.Id }, "Severity": func(i1, i2 *Issue) bool { s1, ok := severityOrder[i1.Severity.Name] if !ok { return true } s2, ok := severityOrder[i2.Severity.Name] if !ok { return false } return s1 < s2 }, } func OrderedBy(fields ...string) *multiSorter { m := &multiSorter{} for _, field := range fields { fn, ok := lessFuncs[field] if !ok { log.Printf("unknown field to order by: %s\n", field) return nil } m.less = append(m.less, fn) } return m }