diff options
-rw-r--r-- | data.go | 75 | ||||
-rw-r--r-- | list.tmpl | 1 | ||||
-rw-r--r-- | project.go (renamed from projects.go) | 30 | ||||
-rw-r--r-- | table.tmpl | 20 |
4 files changed, 106 insertions, 20 deletions
@@ -47,7 +47,7 @@ type Data struct { Issues Issues Features Issues - Projects Projects + Project Project Timestamp time.Time Freq time.Duration Lasterror error @@ -73,6 +73,63 @@ func NewData(ctx context.Context, url, token string, num int) *Data { return data } +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 + log.Println("found", len(project.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 + + fmt.Println("Got project details for", d.projectId, "with", len(d.Project.Versions), "version entries") +} + const statusFilter = `status%5B%5D=10&status%5B%5D=20&status%5B%5D=30&status%5B%5D=40&status%5B%5D=50&severity%5B%5D=20` var fields = []string{"id", @@ -92,7 +149,7 @@ var fields = []string{"id", "tags", } -func (d *Data) update() { +func (d *Data) getIssues() { url := fmt.Sprintf("%s/issues?project_id=%d&page_size=%d&%s&select=%s", d.url, d.projectId, d.num, statusFilter, strings.Join(fields, ",")) @@ -132,9 +189,11 @@ func (d *Data) update() { // Filter issues with old target versions out var issues = Issues{} var features = Issues{} + var open = 0 for _, issue := range iss.Issues { if issue.Resolution.Name == "open" && strings.Compare(d.minimumVersion, issue.TargetVersion.Name) < 0 { + open++ if issue.Severity.Name == "feature" { features = append(features, issue) } else { @@ -144,11 +203,12 @@ func (d *Data) update() { } d.Issues = issues d.Features = features - fmt.Println("got", len(iss.Issues), "entries: ", len(features), "features and ", len(issues), "issues") + fmt.Println("Got", len(iss.Issues), "entries, of which", open, "are open and relevant:", len(features), "features and", len(issues), "issues") } func (d *Data) Loop() { - d.update() + d.getProject() + d.getIssues() go func() { var ticker = time.NewTicker(d.Freq) for range ticker.C { @@ -157,7 +217,8 @@ func (d *Data) Loop() { return default: fmt.Println("updating data") - d.update() + d.getProject() + d.getIssues() } } }() @@ -203,6 +264,10 @@ func (d *Data) TargetVersions() (tv []string) { return } +func (d *Data) VersionsByDate() VersionsByDate { + return VersionsByDate(d.Project.Versions) +} + func (d *Data) Categories() (cs []string) { d.mux.RLock() defer d.mux.RUnlock() @@ -31,6 +31,7 @@ pre { {{ $top := . }} {{ $features := .Features }} {{ $issues := .Issues }} + {{ $versions := .VersionsByDate }} <!-- p> {{ range $top.Tags }} <button>{{ . }}</button> @@ -22,8 +22,6 @@ import "time" @author Özgür Kesim <oec-taler@kesim.org> */ -type Projects []Project - type Project struct { Id int Name string @@ -33,19 +31,37 @@ type Project struct { Enabled bool InheritGlobal bool `json:"inherit_global"` AccessLevel KeyVal `json:"AccessLevel"` - Versions []Version - Categories []Category + Versions Versions + Categories Categories // CustomFields []any `json:"custom_fields"` } +type Versions []Version type Version struct { KeyVal - Released bool - Obsolete bool - Timestamp time.Time + Description string + Released bool + Obsolete bool + Timestamp time.Time } +type VersionsByDate []Version + +func (v VersionsByDate) Len() int { return len(v) } +func (v VersionsByDate) Less(i, j int) bool { return v[i].Timestamp.Before(v[j].Timestamp) } +func (v VersionsByDate) Swap(i, j int) { v[i], v[j] = v[j], v[i] } + +type Categories []Category type Category struct { KeyVal Project KeyVal } + +func (vs VersionsByDate) Get(version string) *Version { + for i, v := range vs { + if v.Name == version { + return &vs[i] + } + } + return nil +} @@ -12,8 +12,7 @@ table { } th, td { padding: 8px; - border-bottom: 1px solid #ddd; - border-right: 1px solid #ddd; + border: 1px solid #ddd; } td { min-width: 10em; @@ -25,8 +24,11 @@ th.side { tr:hover { background: #efe; } -.normal { +details.version { + margin: 5px; font-weight: normal; + max-width: 40em; + text-align: justify; } .feature { background: #cde; @@ -38,7 +40,7 @@ tr:hover { border-radius: 5px; padding: 8px; } -details { +details.entry, details.feature { max-width: 30em; overflow-x: auto; white-space: nowrap; @@ -58,6 +60,7 @@ details { {{ $top := . }} {{ $features := .Features }} {{ $issues := .Issues }} + {{ $versions := .VersionsByDate }} <!--p> {{ range $top.Tags }} <button>{{ . }}</button> @@ -69,10 +72,11 @@ details { <th class="side"></th> {{ range $top.TargetVersions }} <th> - <details> - <summary>{{ . }}</summary> - <div class="normal"> - TODO: more + <details class="version"> + {{ $version := $versions.Get . }} + <summary><b>{{ . }}</b> ({{ $version.Timestamp.Format "02 Jan 06"}})</summary> + <div> + {{ $version.Description }} </div> </details> </th> |