taler-dashboard/issues.go

295 lines
6.7 KiB
Go

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
<http://www.gnu.org/licenses/>
@author Özgür Kesim <oec-taler@kesim.org>
*/
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
Tags []KeyVal
History []Change
}
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 struct {
Id uint32
Summary string
Status KeyVal
Resolution KeyVal
Handler KeyVal
}
}
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 {
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 { 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
}