Adds the *_list partners that the existing *_get tools have been
missing. Same pattern as repo_list — owner allowlisted, capLimit
helper for pagination, next_page surfaced when the page is full.
internal/gitea/issues.go:
- ListIssues(owner, repo, args) hitting
GET /api/v1/repos/{owner}/{repo}/issues with type=issues server-side
so PRs don't leak in (gitea conflates them on this endpoint).
- ListIssuesArgs struct: State, Labels, Since (ISO 8601), Page, Limit.
internal/gitea/workflows.go:
- ListWorkflowRuns(owner, repo, args) hitting
GET /api/v1/repos/{owner}/{repo}/actions/runs.
- Expanded WorkflowRun struct with DisplayTitle, Event, HeadSHA,
HeadBranch, WorkflowID, RunNumber, UpdatedAt, Actor so callers
can pin runs to a commit / branch without a second lookup.
- ListWorkflowRunsArgs: Branch, HeadSHA, Status, Event, Workflow,
Page, Limit. Status/Event 'all' treated as no-filter.
internal/tools/issue_list.go:
- Default state=open, default limit=30 (matches repo_list).
- next_page returned only when len(issues) == limit.
internal/tools/workflow_run_list.go:
- Default limit=10 (most common use is 'what just happened',
not paging).
- Returns runs + total + optional next_page.
Tests: table-driven for both — happy path, empty result, filter
combinations, allowlist rejection. workflow_run_list also asserts
the 'status=all is no-op' behavior (no query param emitted).
Closes #28
Closes #29
147 lines
4.5 KiB
Go
147 lines
4.5 KiB
Go
package gitea
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// DispatchWorkflowArgs is the request body for a workflow_dispatch trigger.
|
|
type DispatchWorkflowArgs struct {
|
|
Ref string `json:"ref"`
|
|
Inputs map[string]any `json:"inputs,omitempty"`
|
|
}
|
|
|
|
// WorkflowRunTrigger holds the run ID extracted from the Location header.
|
|
type WorkflowRunTrigger struct {
|
|
RunID int64
|
|
}
|
|
|
|
// DispatchWorkflow triggers a workflow_dispatch event and returns the new run ID.
|
|
func (c *Client) DispatchWorkflow(ctx context.Context, owner, repo, workflow string, args DispatchWorkflowArgs) (*WorkflowRunTrigger, error) {
|
|
p := fmt.Sprintf("/api/v1/repos/%s/%s/actions/workflows/%s/dispatches", owner, repo, workflow)
|
|
payload, err := json.Marshal(args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resp, err := c.doRaw(ctx, "POST", p, payload)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if resp.Status != 204 {
|
|
if mapErr := MapStatus(resp.Status, resp.Body); mapErr != nil {
|
|
return nil, mapErr
|
|
}
|
|
return nil, fmt.Errorf("unexpected status %d", resp.Status)
|
|
}
|
|
location := resp.Headers.Get("Location")
|
|
if location == "" {
|
|
return nil, fmt.Errorf("missing Location header in dispatch response")
|
|
}
|
|
// Location is e.g. "/api/v1/repos/o/r/actions/runs/123" — take the last segment.
|
|
parts := strings.Split(strings.TrimRight(location, "/"), "/")
|
|
if len(parts) == 0 {
|
|
return nil, fmt.Errorf("malformed Location: %s", location)
|
|
}
|
|
runID, err := strconv.ParseInt(parts[len(parts)-1], 10, 64)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parse run id from %q: %w", location, err)
|
|
}
|
|
return &WorkflowRunTrigger{RunID: runID}, nil
|
|
}
|
|
|
|
// WorkflowRun represents a Gitea Actions run.
|
|
type WorkflowRun struct {
|
|
ID int64 `json:"id"`
|
|
DisplayTitle string `json:"display_title,omitempty"`
|
|
Status string `json:"status"` // queued | in_progress | completed
|
|
Conclusion string `json:"conclusion"` // success | failure | cancelled | skipped (only when completed)
|
|
Event string `json:"event,omitempty"`
|
|
HeadSHA string `json:"head_sha,omitempty"`
|
|
HeadBranch string `json:"head_branch,omitempty"`
|
|
WorkflowID string `json:"workflow_id,omitempty"`
|
|
RunNumber int64 `json:"run_number,omitempty"`
|
|
StartedAt string `json:"started_at"`
|
|
UpdatedAt string `json:"updated_at,omitempty"`
|
|
HTMLURL string `json:"html_url"`
|
|
Actor struct {
|
|
Login string `json:"login"`
|
|
} `json:"actor,omitempty"`
|
|
}
|
|
|
|
// GetWorkflowRun fetches the status of a specific Actions run.
|
|
func (c *Client) GetWorkflowRun(ctx context.Context, owner, repo string, runID int64) (*WorkflowRun, error) {
|
|
p := fmt.Sprintf("/api/v1/repos/%s/%s/actions/runs/%d", owner, repo, runID)
|
|
body, status, err := c.GetJSON(ctx, p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := MapStatus(status, body); err != nil {
|
|
return nil, err
|
|
}
|
|
var run WorkflowRun
|
|
if err := json.Unmarshal(body, &run); err != nil {
|
|
return nil, err
|
|
}
|
|
return &run, nil
|
|
}
|
|
|
|
// ListWorkflowRunsArgs captures the optional query params for ListWorkflowRuns.
|
|
type ListWorkflowRunsArgs struct {
|
|
Branch string
|
|
HeadSHA string
|
|
Status string // queued | in_progress | completed | all
|
|
Event string // push | pull_request | schedule | workflow_dispatch | all
|
|
Workflow string
|
|
Page int
|
|
Limit int
|
|
}
|
|
|
|
type workflowRunsResponse struct {
|
|
TotalCount int64 `json:"total_count"`
|
|
WorkflowRuns []WorkflowRun `json:"workflow_runs"`
|
|
}
|
|
|
|
// ListWorkflowRuns fetches recent Actions runs for a repo with optional filters.
|
|
// Status / Event of "all" or "" are treated as no-filter.
|
|
func (c *Client) ListWorkflowRuns(ctx context.Context, owner, repo string, args ListWorkflowRunsArgs) (*workflowRunsResponse, error) {
|
|
q := url.Values{}
|
|
if args.Branch != "" {
|
|
q.Set("branch", args.Branch)
|
|
}
|
|
if args.HeadSHA != "" {
|
|
q.Set("head_sha", args.HeadSHA)
|
|
}
|
|
if args.Status != "" && args.Status != "all" {
|
|
q.Set("status", args.Status)
|
|
}
|
|
if args.Event != "" && args.Event != "all" {
|
|
q.Set("event", args.Event)
|
|
}
|
|
if args.Workflow != "" {
|
|
q.Set("workflow", args.Workflow)
|
|
}
|
|
if args.Page > 0 {
|
|
q.Set("page", strconv.Itoa(args.Page))
|
|
}
|
|
if args.Limit > 0 {
|
|
q.Set("limit", strconv.Itoa(args.Limit))
|
|
}
|
|
p := fmt.Sprintf("/api/v1/repos/%s/%s/actions/runs?%s", owner, repo, q.Encode())
|
|
body, status, err := c.GetJSON(ctx, p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := MapStatus(status, body); err != nil {
|
|
return nil, err
|
|
}
|
|
var resp workflowRunsResponse
|
|
if err := json.Unmarshal(body, &resp); err != nil {
|
|
return nil, err
|
|
}
|
|
return &resp, nil
|
|
}
|