status filter

This commit is contained in:
2026-03-13 17:03:10 +01:00
parent 14df23e00f
commit e763659b60
4 changed files with 101 additions and 11 deletions

View File

@@ -1,7 +1,9 @@
package domain
import (
"flag"
"os"
"strings"
"github.com/joho/godotenv"
@@ -11,6 +13,14 @@ import (
func Run() {
ui := NewUI() // Instantiate UI
// Parse flags
includeStatusFlag := flag.String("status", "", "Filter issues by status (comma-separated names or IDs)")
excludeStatusFlag := flag.String("exclude-status", "", "Exclude issues by status (comma-separated names or IDs)")
flag.Usage = func() {
ui.PrintUsage(os.Args[0])
}
flag.Parse()
// Load .env file
err := godotenv.Load()
if err != nil && !os.IsNotExist(err) {
@@ -27,9 +37,22 @@ func Run() {
apiKey := ""
// Try to get values from config file first
var includeStatuses []string
var excludeStatuses []string
if config != nil {
baseURL = config.Host
apiKey = config.Token
includeStatuses = config.IncludeStatuses
excludeStatuses = config.ExcludeStatuses
}
// Flag overrides config file
if *includeStatusFlag != "" {
includeStatuses = strings.Split(*includeStatusFlag, ",")
}
if *excludeStatusFlag != "" {
excludeStatuses = strings.Split(*excludeStatusFlag, ",")
}
// Environment variables override config file
@@ -43,8 +66,14 @@ func Run() {
var projectID string
// projectID must be provided as a command-line argument, or list projects
if len(os.Args) > 1 {
projectID = os.Args[1]
if flag.NArg() > 0 {
projectID = flag.Arg(0)
// Check for misplaced flags
for i := 1; i < flag.NArg(); i++ {
if strings.HasPrefix(flag.Arg(i), "-") {
ui.PrintError("Error: flags must come BEFORE the project ID. Found misplaced flag: %s\n", flag.Arg(i))
}
}
} else {
projects, err := model.FetchAllProjects(baseURL, apiKey)
if err != nil {
@@ -70,12 +99,15 @@ func Run() {
ui.PrintError("Error fetching issues: %v\n", err)
}
ui.PrintTotalIssuesFetched(len(issues))
// Apply filtering
filteredIssues := model.FilterIssues(issues, includeStatuses, excludeStatuses)
roots := model.BuildTree(issues) // Corrected call
ui.PrintTotalIssuesFetched(len(filteredIssues))
roots := model.BuildTree(filteredIssues) // Corrected call
ui.PrintIssueTreeHeader(len(roots))
model.PrintTree(roots, "", false) // Corrected call
// Print summary
ui.PrintSummary(len(issues), len(roots))
ui.PrintSummary(len(filteredIssues), len(roots))
}

View File

@@ -10,6 +10,8 @@ import (
type Config struct {
Host string `json:"host"`
Token string `json:"token"`
IncludeStatuses []string `json:"include_statuses,omitempty"`
ExcludeStatuses []string `json:"exclude_statuses,omitempty"`
}
// Load configuration from ~/.redmine-tree.json

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"io"
"net/http"
"strings"
)
// --- Redmine API structs ---
@@ -28,6 +29,58 @@ type IssuesResponse struct {
Limit int `json:"limit"`
}
// FilterIssues filters issues based on included and excluded status names or IDs.
func FilterIssues(issues []Issue, includeStatuses, excludeStatuses []string) []Issue {
if len(includeStatuses) == 0 && len(excludeStatuses) == 0 {
return issues
}
var filtered []Issue
for _, issue := range issues {
statusID := fmt.Sprintf("%d", issue.Status.ID)
statusName := strings.ToLower(issue.Status.Title)
keep := true
// If includeStatuses is specified, issue MUST match at least one
if len(includeStatuses) > 0 {
match := false
for _, s := range includeStatuses {
s = strings.TrimSpace(strings.ToLower(s))
if s == "" {
continue
}
if s == statusID || s == statusName {
match = true
break
}
}
if !match {
keep = false
}
}
// If excludeStatuses is specified, issue MUST NOT match any
if keep && len(excludeStatuses) > 0 {
for _, s := range excludeStatuses {
s = strings.TrimSpace(strings.ToLower(s))
if s == "" {
continue
}
if s == statusID || s == statusName {
keep = false
break
}
}
}
if keep {
filtered = append(filtered, issue)
}
}
return filtered
}
// Fetch all issues with pagination
func FetchAllIssues(baseURL, apiKey, projectID string) ([]Issue, error) {
var all []Issue

View File

@@ -48,10 +48,13 @@ func (ui *UI) PrintProjects(projects []model.Project) {
// PrintUsage prints the application usage message to stderr and exits.
func (ui *UI) PrintUsage(appName string) {
ui.Printf("Usage: %s <project-id>\n", appName)
ui.Printf("Usage: %s [options] <project-id>\n", appName)
ui.Printf("Options:\n")
ui.Printf(" -status <statuses> Include only issues with these statuses (comma-separated names or IDs)\n")
ui.Printf(" -exclude-status <statuses> Exclude issues with these statuses (comma-separated names or IDs)\n\n")
ui.Printf("Environment:\n")
ui.Printf(" REDMINE_URL and REDMINE_TOKEN must be set in .env, as environment variables, or in ~/.redmine-tree.json.\n")
ui.Printf("Example: REDMINE_URL=https://redmine.example.com REDMINE_TOKEN=abc123 %s my-project\n", appName)
ui.Printf("Or: %s my-project (if REDMINE_URL, REDMINE_TOKEN are in .env or ~/.redmine-tree.json)\n", appName)
ui.Printf("Example: REDMINE_URL=https://redmine.example.com REDMINE_TOKEN=abc123 %s -status 'In Progress' my-project\n", appName)
os.Exit(1)
}