taler-dashboard/data.go

332 lines
6.5 KiB
Go
Raw Normal View History

2023-12-27 03:06:11 +01:00
package main
2023-12-27 03:46:20 +01:00
/*
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>
*/
2023-12-27 03:06:11 +01:00
import (
"context"
"encoding/json"
"fmt"
"html/template"
"io"
"log"
"net/http"
2023-12-27 13:53:36 +01:00
"runtime/debug"
"sort"
2023-12-27 03:06:11 +01:00
"strings"
"sync"
"time"
)
type Data struct {
mux sync.RWMutex
url string
token string
num int
projectId int
2023-12-27 18:32:24 +01:00
filterId int
2023-12-27 03:06:11 +01:00
minimumVersion string
tmpl *template.Template
ctx context.Context
Issues Issues
2023-12-27 13:53:36 +01:00
Features Issues
2023-12-27 14:51:01 +01:00
Project Project
2023-12-27 03:06:11 +01:00
Timestamp time.Time
2023-12-27 04:18:58 +01:00
Freq time.Duration
2023-12-27 03:06:11 +01:00
Lasterror error
}
func NewData(ctx context.Context, url, token string, num int) *Data {
data := &Data{
url: url,
token: token,
ctx: ctx,
num: num,
}
funcMap := map[string]any{
"OrderedBy": OrderedBy,
}
data.tmpl = template.Must(template.New("index").Funcs(funcMap).ParseFS(content, "*.tmpl"))
2023-12-27 03:06:11 +01:00
return data
}
2023-12-27 14:51:01 +01:00
func (d *Data) getProject() {
url := fmt.Sprintf("%s/projects/%d", d.url, d.projectId)
req, e := http.NewRequestWithContext(d.ctx, "GET", url, nil)
if nil != e {
d.mux.Lock()
defer d.mux.Unlock()
d.Lasterror = e
return
}
req.Header.Add("Authorization", d.token)
r, e := http.DefaultClient.Do(req)
if nil != e {
d.mux.Lock()
defer d.mux.Unlock()
d.Lasterror = e
return
} else if 200 != r.StatusCode {
d.mux.Lock()
defer d.mux.Unlock()
d.Lasterror = fmt.Errorf("Got unexpected status %s\n", r.Status)
return
}
var project Project
p := struct{ Projects []Project }{}
e = json.NewDecoder(r.Body).Decode(&p)
if len(p.Projects) < 1 {
e = fmt.Errorf("no project found with id", d.projectId)
} else {
// filter out obsolete versions
project = p.Projects[0]
var versions Versions
for _, v := range project.Versions {
v := v
if !v.Obsolete && !v.Released {
versions = append(versions, v)
}
}
project.Versions = versions
}
d.mux.Lock()
defer d.mux.Unlock()
d.Lasterror = e
if nil != e {
return
}
d.Timestamp = time.Now()
d.Project = project
2023-12-27 18:32:24 +01:00
log.Println("Got project details for id", d.projectId,
"with", len(d.Project.Versions), "version entries")
2023-12-27 14:51:01 +01:00
}
2023-12-27 13:20:51 +01:00
var fields = []string{"id",
"description",
"summary",
"category",
"target_version",
"status",
"reporter",
"handler",
"resolution",
"priority",
"severity",
"created_at",
"updated_at",
"relationships",
"tags",
}
2023-12-27 14:51:01 +01:00
func (d *Data) getIssues() {
2023-12-27 18:32:24 +01:00
url := fmt.Sprintf("%s/issues?project_id=%d&filter_id=%d&select=%s&page_size=%d",
d.url,
d.projectId,
d.filterId,
strings.Join(fields, ","),
d.num)
2023-12-27 03:06:11 +01:00
req, e := http.NewRequestWithContext(d.ctx, "GET", url, nil)
if nil != e {
d.mux.Lock()
defer d.mux.Unlock()
d.Lasterror = e
return
}
req.Header.Add("Authorization", d.token)
r, e := http.DefaultClient.Do(req)
if nil != e {
d.mux.Lock()
defer d.mux.Unlock()
d.Lasterror = e
return
} else if 200 != r.StatusCode {
d.mux.Lock()
defer d.mux.Unlock()
d.Lasterror = fmt.Errorf("Got unexpected status %s\n", r.Status)
return
}
iss := struct{ Issues Issues }{}
e = json.NewDecoder(r.Body).Decode(&iss)
d.mux.Lock()
defer d.mux.Unlock()
d.Lasterror = e
if nil != e {
return
}
d.Timestamp = time.Now()
// Filter issues with old target versions out
var issues = Issues{}
2023-12-27 13:53:36 +01:00
var features = Issues{}
2023-12-27 14:51:01 +01:00
var open = 0
2023-12-27 03:06:11 +01:00
for _, issue := range iss.Issues {
for _, ch := range issue.Relationships {
if ch.Typ.Name == "parent-of" {
issue.Children = append(issue.Children, ch.Issue)
}
}
2023-12-27 04:39:49 +01:00
if issue.Resolution.Name == "open" &&
strings.Compare(d.minimumVersion, issue.TargetVersion.Name) < 0 {
2023-12-27 14:51:01 +01:00
open++
2023-12-27 13:53:36 +01:00
if issue.Severity.Name == "feature" {
features = append(features, issue)
} else {
issues = append(issues, issue)
}
2023-12-27 03:06:11 +01:00
}
}
d.Issues = issues
2023-12-27 13:53:36 +01:00
d.Features = features
2023-12-27 18:32:24 +01:00
log.Println("Got",
len(iss.Issues), "entries, of which",
open, "are open and relevant:",
len(features), "features and",
len(issues), "issues")
2023-12-27 13:20:51 +01:00
}
func (d *Data) Loop() {
2023-12-27 14:51:01 +01:00
d.getProject()
d.getIssues()
2023-12-27 13:20:51 +01:00
go func() {
var ticker = time.NewTicker(d.Freq)
for range ticker.C {
select {
case <-d.ctx.Done():
return
default:
log.Println("Updating data")
2023-12-27 14:51:01 +01:00
d.getProject()
d.getIssues()
2023-12-27 13:20:51 +01:00
}
}
}()
2023-12-27 03:06:11 +01:00
}
func (d *Data) printJSON(w io.Writer) {
d.mux.RLock()
defer d.mux.RUnlock()
if nil == d.Issues {
fmt.Fprintln(w, "{}")
return
}
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
enc.Encode(d.Issues)
}
func (d *Data) printTemplate(w io.Writer, name string) {
d.mux.RLock()
defer d.mux.RUnlock()
e := d.tmpl.ExecuteTemplate(w, name, d)
if nil != e {
log.Println(e)
}
}
2023-12-27 13:53:36 +01:00
func (d *Data) TargetVersions() (tv []string) {
d.mux.RLock()
defer d.mux.RUnlock()
var m = map[string]bool{}
for _, s := range d.Issues.TargetVersions() {
m[s] = true
}
for _, s := range d.Features.TargetVersions() {
m[s] = true
}
for s := range m {
tv = append(tv, s)
}
sort.Strings(tv)
return
}
2023-12-27 14:51:01 +01:00
func (d *Data) VersionsByDate() VersionsByDate {
return VersionsByDate(d.Project.Versions)
}
2023-12-27 13:53:36 +01:00
func (d *Data) Categories() (cs []string) {
d.mux.RLock()
defer d.mux.RUnlock()
var m = map[string]bool{}
for _, s := range d.Issues.Categories() {
m[s] = true
}
for _, s := range d.Features.Categories() {
m[s] = true
}
for s := range m {
cs = append(cs, s)
}
sort.Strings(cs)
return
}
func (d *Data) Tags() (ts []string) {
d.mux.RLock()
defer d.mux.RUnlock()
var m = map[string]bool{}
for _, s := range d.Issues.Tags() {
m[s] = true
}
for _, s := range d.Features.Tags() {
m[s] = true
}
for s := range m {
ts = append(ts, s)
}
sort.Strings(ts)
return
}
func (d *Data) Commit() string {
var version, timestamp = "<unknown>", "<unknown>"
if info, ok := debug.ReadBuildInfo(); ok {
for _, setting := range info.Settings {
switch setting.Key {
case "vcs.revision":
n := len(setting.Value)
if n > 8 {
n = 8
}
version = setting.Value[:n]
case "vcs.time":
timestamp = setting.Value
}
}
}
return version + " from " + timestamp
}