mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 21:47:20 +00:00
What’s in this PR 1) Smarter default sorting for issues (used by the TUI) New ghapi.SortIssuesForDisplay helper that orders issues by: Priority label (P0 → P1 → P2 → none) Presence of customer-* / prospect-* labels Type labels (story → bug → ~sub-task → others) Issue number (descending) This is applied before filtering so views start in a meaningful order. [GitHub](https://github.com/fleetdm/fleet/pull/32694/files) Implementation lives in tools/github-manage/pkg/ghapi/sort.go. Comprehensive tests cover all combinations, tie-breakers, and stability. GitHub +1 2) Estimates: show the sum for the current selection The header now displays Σest sel=<sum> for the currently selected issues, both in filtered and unfiltered views, making quick capacity checks easier. [GitHub](https://github.com/fleetdm/fleet/pull/32694/files) 3) Better progress UI for workflows Task list is now windowed (last ~10 items) with auto-scroll to the currently running or most recently finished task, plus “earlier/more tasks” ellipses and a progress counter at the bottom. This keeps the view focused during long runs. [GitHub](https://github.com/fleetdm/fleet/pull/32694/files) 4) Project estimates fetch now includes total count Switched from GetEstimatedTicketsForProject to GetEstimatedTicketsForProjectWithTotal, so we can show totalAvailable alongside rawFetched/limit. [GitHub](https://github.com/fleetdm/fleet/pull/32694/files) --------- Co-authored-by: Jordan Montgomery <elijah.jordan.montgomery@gmail.com>
101 lines
2.4 KiB
Go
101 lines
2.4 KiB
Go
package ghapi
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// labelNamesLower returns a set of lowercase label names for fast lookup.
|
|
func labelNamesLower(issue Issue) map[string]struct{} {
|
|
names := make(map[string]struct{}, len(issue.Labels))
|
|
for _, l := range issue.Labels {
|
|
names[strings.ToLower(strings.TrimSpace(l.Name))] = struct{}{}
|
|
}
|
|
return names
|
|
}
|
|
|
|
// hasAnyLabelPrefix checks if any label starts with given prefixes (case-insensitive).
|
|
func hasAnyLabelPrefix(issue Issue, prefixes ...string) bool {
|
|
for _, l := range issue.Labels {
|
|
ln := strings.ToLower(strings.TrimSpace(l.Name))
|
|
for _, p := range prefixes {
|
|
if strings.HasPrefix(ln, strings.ToLower(p)) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// priorityRank returns 0 for P0, 1 for P1, 2 for P2, 3 for none.
|
|
func priorityRank(issue Issue) int {
|
|
rank := 3
|
|
for _, l := range issue.Labels {
|
|
ln := strings.ToUpper(strings.TrimSpace(l.Name))
|
|
switch ln {
|
|
case "P0":
|
|
if rank > 0 {
|
|
rank = 0
|
|
}
|
|
case "P1":
|
|
if rank > 1 {
|
|
rank = 1
|
|
}
|
|
case "P2":
|
|
if rank > 2 {
|
|
rank = 2
|
|
}
|
|
}
|
|
}
|
|
return rank
|
|
}
|
|
|
|
// custProspectRank returns 0 if any label starts with customer- or prospect-, else 1.
|
|
func custProspectRank(issue Issue) int {
|
|
if hasAnyLabelPrefix(issue, "customer-", "prospect-") {
|
|
return 0
|
|
}
|
|
return 1
|
|
}
|
|
|
|
// typeRank returns 0 for story, 1 for bug, 2 for ~sub-task, 3 otherwise.
|
|
func typeRank(issue Issue) int {
|
|
names := labelNamesLower(issue)
|
|
if _, ok := names["story"]; ok {
|
|
return 0
|
|
}
|
|
if _, ok := names["bug"]; ok {
|
|
return 1
|
|
}
|
|
if _, ok := names["~sub-task"]; ok {
|
|
return 2
|
|
}
|
|
return 3
|
|
}
|
|
|
|
// SortIssuesForDisplay sorts issues in-place using the following precedence:
|
|
// 1) Priority labels: P0, P1, P2, then none
|
|
// 2) Presence of labels starting with customer- or prospect-
|
|
// 3) Type labels: story, bug, ~sub-task, then others
|
|
// 4) Issue number descending
|
|
func SortIssuesForDisplay(items []Issue) {
|
|
sort.SliceStable(items, func(i, j int) bool {
|
|
// 1) Priority P0/P1/P2/none
|
|
pi, pj := priorityRank(items[i]), priorityRank(items[j])
|
|
if pi != pj {
|
|
return pi < pj
|
|
}
|
|
// 2) Customer/Prospect present first
|
|
ci, cj := custProspectRank(items[i]), custProspectRank(items[j])
|
|
if ci != cj {
|
|
return ci < cj
|
|
}
|
|
// 3) Type story, bug, ~sub-task, others
|
|
ti, tj := typeRank(items[i]), typeRank(items[j])
|
|
if ti != tj {
|
|
return ti < tj
|
|
}
|
|
// 4) Number descending
|
|
return items[i].Number > items[j].Number
|
|
})
|
|
}
|