diff --git a/internal/gitea/client.go b/internal/gitea/client.go index 0623ec3..6df4917 100644 --- a/internal/gitea/client.go +++ b/internal/gitea/client.go @@ -326,7 +326,8 @@ type PaginatedPulls struct { // ListAllIssues fetches issues across all repos in the given orgs, // using concurrent requests with a semaphore. Results are paginated. // The label parameter filters issues by label name (empty string means no filter). -func (c *Client) ListAllIssues(ctx context.Context, token string, orgs []string, state string, page int, label string) (PaginatedIssues, error) { +// The repoFilter parameter narrows results to a single repo name (empty means all repos). +func (c *Client) ListAllIssues(ctx context.Context, token string, orgs []string, state string, page int, label string, repoFilter string) (PaginatedIssues, error) { if state == "" { state = "open" } @@ -334,7 +335,7 @@ func (c *Client) ListAllIssues(ctx context.Context, token string, orgs []string, page = 1 } - cacheKey := fmt.Sprintf("issues-%s-%s-%s", state, strings.Join(orgs, ","), label) + cacheKey := fmt.Sprintf("issues-%s-%s-%s-%s", state, strings.Join(orgs, ","), label, repoFilter) var allIssues []Issue if cached, ok := c.getFromCache(cacheKey); ok { allIssues = cached.([]Issue) @@ -349,6 +350,17 @@ func (c *Client) ListAllIssues(ctx context.Context, token string, orgs []string, allRepos = append(allRepos, repos...) } + // Filter to a single repo if specified. + if repoFilter != "" { + var filtered []Repo + for _, r := range allRepos { + if r.Name == repoFilter { + filtered = append(filtered, r) + } + } + allRepos = filtered + } + // Fan out issue fetching across repos. var mu sync.Mutex sem := make(chan struct{}, c.maxConcurrent) @@ -429,7 +441,8 @@ func (c *Client) ListAllIssues(ctx context.Context, token string, orgs []string, // ListAllPullRequests fetches PRs across all repos in the given orgs. // Results are paginated. The label parameter filters PRs by label name. -func (c *Client) ListAllPullRequests(ctx context.Context, token string, orgs []string, state string, page int, label string) (PaginatedPulls, error) { +// The repoFilter parameter narrows results to a single repo name. +func (c *Client) ListAllPullRequests(ctx context.Context, token string, orgs []string, state string, page int, label string, repoFilter string) (PaginatedPulls, error) { if state == "" { state = "open" } @@ -437,7 +450,7 @@ func (c *Client) ListAllPullRequests(ctx context.Context, token string, orgs []s page = 1 } - cacheKey := fmt.Sprintf("pulls-%s-%s-%s", state, strings.Join(orgs, ","), label) + cacheKey := fmt.Sprintf("pulls-%s-%s-%s-%s", state, strings.Join(orgs, ","), label, repoFilter) var allPRs []PullRequest if cached, ok := c.getFromCache(cacheKey); ok { allPRs = cached.([]PullRequest) @@ -451,6 +464,17 @@ func (c *Client) ListAllPullRequests(ctx context.Context, token string, orgs []s allRepos = append(allRepos, repos...) } + // Filter to a single repo if specified. + if repoFilter != "" { + var filtered []Repo + for _, r := range allRepos { + if r.Name == repoFilter { + filtered = append(filtered, r) + } + } + allRepos = filtered + } + var mu sync.Mutex sem := make(chan struct{}, c.maxConcurrent) var wg sync.WaitGroup @@ -531,7 +555,7 @@ func (c *Client) GetTriageQueue(ctx context.Context, token string, orgs []string // Collect all open issues across all pages. var issues []Issue for page := 1; ; page++ { - result, err := c.ListAllIssues(ctx, token, orgs, "open", page, "") + result, err := c.ListAllIssues(ctx, token, orgs, "open", page, "", "") if err != nil { return nil, fmt.Errorf("fetching issues for triage: %w", err) } @@ -544,7 +568,7 @@ func (c *Client) GetTriageQueue(ctx context.Context, token string, orgs []string // Collect all open PRs across all pages. var prs []PullRequest for page := 1; ; page++ { - result, err := c.ListAllPullRequests(ctx, token, orgs, "open", page, "") + result, err := c.ListAllPullRequests(ctx, token, orgs, "open", page, "", "") if err != nil { return nil, fmt.Errorf("fetching PRs for triage: %w", err) } diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index dad5df8..2530c7d 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -250,6 +250,8 @@ func (h *Handler) ListIssues(w http.ResponseWriter, r *http.Request) { SelectedOrg string SelectedState string SelectedLabel string + SelectedRepo string + Repos []string HasMore bool NextPage int Error string @@ -261,6 +263,7 @@ func (h *Handler) ListIssues(w http.ResponseWriter, r *http.Request) { selectedState = "open" } selectedLabel := r.URL.Query().Get("label") + selectedRepo := r.URL.Query().Get("repo") page, _ := strconv.Atoi(r.URL.Query().Get("page")) if page < 1 { page = 1 @@ -271,6 +274,7 @@ func (h *Handler) ListIssues(w http.ResponseWriter, r *http.Request) { SelectedOrg: selectedOrg, SelectedState: selectedState, SelectedLabel: selectedLabel, + SelectedRepo: selectedRepo, } if len(orgNames) == 0 { @@ -280,9 +284,19 @@ func (h *Handler) ListIssues(w http.ResponseWriter, r *http.Request) { queryOrgs := orgNames if selectedOrg != "" { queryOrgs = []string{selectedOrg} + + // Populate repo list for the selected org. + repos, err := h.Client.ListOrgRepos(r.Context(), token, selectedOrg) + if err != nil { + slog.Warn("failed to list repos for org filter", "error", err, "org", selectedOrg) + } else { + for _, repo := range repos { + data.Repos = append(data.Repos, repo.Name) + } + } } - result, err := h.Client.ListAllIssues(r.Context(), token, queryOrgs, selectedState, page, selectedLabel) + result, err := h.Client.ListAllIssues(r.Context(), token, queryOrgs, selectedState, page, selectedLabel, selectedRepo) if err != nil { slog.Error("failed to list issues", "error", err) data.Error = "Error loading issues." @@ -342,6 +356,8 @@ func (h *Handler) ListPulls(w http.ResponseWriter, r *http.Request) { SelectedOrg string SelectedState string SelectedLabel string + SelectedRepo string + Repos []string HasMore bool NextPage int Error string @@ -353,6 +369,7 @@ func (h *Handler) ListPulls(w http.ResponseWriter, r *http.Request) { selectedState = "open" } selectedLabel := r.URL.Query().Get("label") + selectedRepo := r.URL.Query().Get("repo") page, _ := strconv.Atoi(r.URL.Query().Get("page")) if page < 1 { page = 1 @@ -363,6 +380,7 @@ func (h *Handler) ListPulls(w http.ResponseWriter, r *http.Request) { SelectedOrg: selectedOrg, SelectedState: selectedState, SelectedLabel: selectedLabel, + SelectedRepo: selectedRepo, } if len(orgNames) == 0 { @@ -371,9 +389,19 @@ func (h *Handler) ListPulls(w http.ResponseWriter, r *http.Request) { queryOrgs := orgNames if selectedOrg != "" { queryOrgs = []string{selectedOrg} + + // Populate repo list for the selected org. + repos, err := h.Client.ListOrgRepos(r.Context(), token, selectedOrg) + if err != nil { + slog.Warn("failed to list repos for org filter", "error", err, "org", selectedOrg) + } else { + for _, repo := range repos { + data.Repos = append(data.Repos, repo.Name) + } + } } - result, err := h.Client.ListAllPullRequests(r.Context(), token, queryOrgs, selectedState, page, selectedLabel) + result, err := h.Client.ListAllPullRequests(r.Context(), token, queryOrgs, selectedState, page, selectedLabel, selectedRepo) if err != nil { slog.Error("failed to list pull requests", "error", err) data.Error = "Error loading pull requests." diff --git a/internal/templates/issues.html b/internal/templates/issues.html index 4d884f5..c657cbd 100644 --- a/internal/templates/issues.html +++ b/internal/templates/issues.html @@ -14,7 +14,7 @@ {{end}} {{if .HasMore}} -