mirror of
https://github.com/go-gitea/gitea.git
synced 2026-06-14 03:29:55 +00:00
feat(api): Add GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs (#37196)
- Add GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs
endpoint, matching the
https://docs.github.com/en/rest/actions/workflow-runs?apiVersion=2026-03-10#list-workflow-runs-for-a-workflow
---------
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: bircni <bircni@icloud.com>
This commit is contained in:
@@ -23,7 +23,10 @@ func (repo *Repository) GetRefCommitID(name string) (string, error) {
|
|||||||
}
|
}
|
||||||
refName := plumbing.ReferenceName(name)
|
refName := plumbing.ReferenceName(name)
|
||||||
if err := refName.Validate(); err != nil {
|
if err := refName.Validate(); err != nil {
|
||||||
return "", err
|
// Match the nogogit behavior: an unresolvable/invalid ref name
|
||||||
|
// is reported as not-existing rather than a generic validation error,
|
||||||
|
// so callers can rely on IsErrNotExist regardless of build tag.
|
||||||
|
return "", ErrNotExist{ID: name}
|
||||||
}
|
}
|
||||||
ref, err := repo.gogitRepo.Reference(refName, true)
|
ref, err := repo.gogitRepo.Reference(refName, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -117,23 +117,48 @@ type ActionWorkflowRun struct {
|
|||||||
// RunAttempt is 1-based for runs created after ActionRunAttempt was introduced.
|
// RunAttempt is 1-based for runs created after ActionRunAttempt was introduced.
|
||||||
// A value of 0 is a legacy-only sentinel for runs created before attempts existed
|
// A value of 0 is a legacy-only sentinel for runs created before attempts existed
|
||||||
// and indicates no corresponding /attempts/{n} resource is available.
|
// and indicates no corresponding /attempts/{n} resource is available.
|
||||||
RunAttempt int64 `json:"run_attempt"`
|
RunAttempt int64 `json:"run_attempt"`
|
||||||
RunNumber int64 `json:"run_number"`
|
RunNumber int64 `json:"run_number"`
|
||||||
RepositoryID int64 `json:"repository_id,omitempty"`
|
RepositoryID int64 `json:"repository_id,omitempty"`
|
||||||
HeadSha string `json:"head_sha"`
|
HeadSha string `json:"head_sha"`
|
||||||
HeadBranch string `json:"head_branch,omitempty"`
|
HeadBranch string `json:"head_branch,omitempty"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Actor *User `json:"actor,omitempty"`
|
Actor *User `json:"actor,omitempty"`
|
||||||
TriggerActor *User `json:"trigger_actor,omitempty"`
|
TriggerActor *User `json:"trigger_actor,omitempty"`
|
||||||
Repository *Repository `json:"repository,omitempty"`
|
Repository *Repository `json:"repository,omitempty"`
|
||||||
HeadRepository *Repository `json:"head_repository,omitempty"`
|
HeadRepository *Repository `json:"head_repository,omitempty"`
|
||||||
Conclusion string `json:"conclusion,omitempty"`
|
Conclusion string `json:"conclusion,omitempty"`
|
||||||
|
PullRequests []*PullRequestMinimal `json:"pull_requests"`
|
||||||
// swagger:strfmt date-time
|
// swagger:strfmt date-time
|
||||||
StartedAt time.Time `json:"started_at"`
|
StartedAt time.Time `json:"started_at"`
|
||||||
// swagger:strfmt date-time
|
// swagger:strfmt date-time
|
||||||
CompletedAt time.Time `json:"completed_at"`
|
CompletedAt time.Time `json:"completed_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PullRequestMinimal is the minimal information about a pull request, as
|
||||||
|
// returned in the `pull_requests` field of a workflow run.
|
||||||
|
type PullRequestMinimal struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Number int64 `json:"number"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Head PullRequestMinimalHead `json:"head"`
|
||||||
|
Base PullRequestMinimalHead `json:"base"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PullRequestMinimalHead is a minimal description of one side of a pull request.
|
||||||
|
type PullRequestMinimalHead struct {
|
||||||
|
Ref string `json:"ref"`
|
||||||
|
SHA string `json:"sha"`
|
||||||
|
Repo PullRequestMinimalHeadRepo `json:"repo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PullRequestMinimalHeadRepo is a minimal description of the repository on one side of a pull request.
|
||||||
|
type PullRequestMinimalHeadRepo struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
// ActionWorkflowRunsResponse returns ActionWorkflowRuns
|
// ActionWorkflowRunsResponse returns ActionWorkflowRuns
|
||||||
type ActionWorkflowRunsResponse struct {
|
type ActionWorkflowRunsResponse struct {
|
||||||
Entries []*ActionWorkflowRun `json:"workflow_runs"`
|
Entries []*ActionWorkflowRun `json:"workflow_runs"`
|
||||||
|
|||||||
@@ -99,5 +99,5 @@ func ListWorkflowRuns(ctx *context.APIContext) {
|
|||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
shared.ListRuns(ctx, 0, 0)
|
shared.ListRuns(ctx, 0, 0, "")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1188,6 +1188,7 @@ func Routes() *web.Router {
|
|||||||
m.Group("/actions/workflows", func() {
|
m.Group("/actions/workflows", func() {
|
||||||
m.Get("", repo.ActionsListRepositoryWorkflows)
|
m.Get("", repo.ActionsListRepositoryWorkflows)
|
||||||
m.Get("/{workflow_id}", repo.ActionsGetWorkflow)
|
m.Get("/{workflow_id}", repo.ActionsGetWorkflow)
|
||||||
|
m.Get("/{workflow_id}/runs", repo.ActionsListWorkflowRuns)
|
||||||
m.Put("/{workflow_id}/disable", reqRepoWriter(unit.TypeActions), repo.ActionsDisableWorkflow)
|
m.Put("/{workflow_id}/disable", reqRepoWriter(unit.TypeActions), repo.ActionsDisableWorkflow)
|
||||||
m.Put("/{workflow_id}/enable", reqRepoWriter(unit.TypeActions), repo.ActionsEnableWorkflow)
|
m.Put("/{workflow_id}/enable", reqRepoWriter(unit.TypeActions), repo.ActionsEnableWorkflow)
|
||||||
m.Post("/{workflow_id}/dispatches", reqRepoWriter(unit.TypeActions), bind(api.CreateActionWorkflowDispatch{}), repo.ActionsDispatchWorkflow)
|
m.Post("/{workflow_id}/dispatches", reqRepoWriter(unit.TypeActions), bind(api.CreateActionWorkflowDispatch{}), repo.ActionsDispatchWorkflow)
|
||||||
|
|||||||
@@ -679,7 +679,7 @@ func (Action) ListWorkflowRuns(ctx *context.APIContext) {
|
|||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
shared.ListRuns(ctx, ctx.Org.Organization.ID, 0)
|
shared.ListRuns(ctx, ctx.Org.Organization.ID, 0, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ actions_service.API = new(Action)
|
var _ actions_service.API = new(Action)
|
||||||
|
|||||||
@@ -772,6 +772,11 @@ func (Action) ListWorkflowRuns(ctx *context.APIContext) {
|
|||||||
// description: triggering sha of the workflow run
|
// description: triggering sha of the workflow run
|
||||||
// type: string
|
// type: string
|
||||||
// required: false
|
// required: false
|
||||||
|
// - name: exclude_pull_requests
|
||||||
|
// in: query
|
||||||
|
// description: if true, the `pull_requests` field on each returned run is emptied
|
||||||
|
// type: boolean
|
||||||
|
// required: false
|
||||||
// - name: page
|
// - name: page
|
||||||
// in: query
|
// in: query
|
||||||
// description: page number of results to return (1-based)
|
// description: page number of results to return (1-based)
|
||||||
@@ -790,7 +795,7 @@ func (Action) ListWorkflowRuns(ctx *context.APIContext) {
|
|||||||
|
|
||||||
repoID := ctx.Repo.Repository.ID
|
repoID := ctx.Repo.Repository.ID
|
||||||
|
|
||||||
shared.ListRuns(ctx, 0, repoID)
|
shared.ListRuns(ctx, 0, repoID, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ actions_service.API = new(Action)
|
var _ actions_service.API = new(Action)
|
||||||
@@ -967,6 +972,97 @@ func ActionsGetWorkflow(ctx *context.APIContext) {
|
|||||||
ctx.JSON(http.StatusOK, workflow)
|
ctx.JSON(http.StatusOK, workflow)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ActionsListWorkflowRuns(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs repository ActionsListWorkflowRuns
|
||||||
|
// ---
|
||||||
|
// summary: List runs for a workflow
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: workflow_id
|
||||||
|
// in: path
|
||||||
|
// description: id of the workflow, must be the workflow file name (e.g. `build.yml`)
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: event
|
||||||
|
// in: query
|
||||||
|
// description: workflow event name
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: branch
|
||||||
|
// in: query
|
||||||
|
// description: workflow branch
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: status
|
||||||
|
// in: query
|
||||||
|
// description: workflow status (pending, queued, in_progress, failure, success, skipped)
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: actor
|
||||||
|
// in: query
|
||||||
|
// description: triggered by user
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: head_sha
|
||||||
|
// in: query
|
||||||
|
// description: triggering sha of the workflow run
|
||||||
|
// type: string
|
||||||
|
// required: false
|
||||||
|
// - name: exclude_pull_requests
|
||||||
|
// in: query
|
||||||
|
// description: if true, the `pull_requests` field on each returned run is emptied
|
||||||
|
// type: boolean
|
||||||
|
// required: false
|
||||||
|
// - name: page
|
||||||
|
// in: query
|
||||||
|
// description: page number of results to return (1-based)
|
||||||
|
// type: integer
|
||||||
|
// - name: limit
|
||||||
|
// in: query
|
||||||
|
// description: page size of results
|
||||||
|
// type: integer
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/WorkflowRunsList"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
workflowID := ctx.PathParam("workflow_id")
|
||||||
|
// Existing runs prove the workflow is/was valid and cover historical workflows
|
||||||
|
// whose file was later removed. Fall back to a git lookup for never-run workflows.
|
||||||
|
runExists, err := db.Exist[actions_model.ActionRun](ctx, actions_model.FindRunOptions{
|
||||||
|
RepoID: ctx.Repo.Repository.ID,
|
||||||
|
WorkflowID: workflowID,
|
||||||
|
}.ToConds())
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !runExists {
|
||||||
|
if _, err := convert.GetActionWorkflow(ctx, ctx.Repo.GitRepo, ctx.Repo.Repository, workflowID); err != nil {
|
||||||
|
ctx.APIErrorAuto(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shared.ListRuns(ctx, 0, ctx.Repo.Repository.ID, workflowID)
|
||||||
|
}
|
||||||
|
|
||||||
func ActionsDisableWorkflow(ctx *context.APIContext) {
|
func ActionsDisableWorkflow(ctx *context.APIContext) {
|
||||||
// swagger:operation PUT /repos/{owner}/{repo}/actions/workflows/{workflow_id}/disable repository ActionsDisableWorkflow
|
// swagger:operation PUT /repos/{owner}/{repo}/actions/workflows/{workflow_id}/disable repository ActionsDisableWorkflow
|
||||||
// ---
|
// ---
|
||||||
@@ -1238,7 +1334,7 @@ func GetWorkflowRun(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, nil)
|
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, nil, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
@@ -1287,7 +1383,7 @@ func GetWorkflowRunAttempt(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, attempt)
|
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, attempt, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
@@ -1342,7 +1438,7 @@ func RerunWorkflowRun(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, nil)
|
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, nil, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -135,8 +135,9 @@ func convertToInternal(s string) ([]actions_model.Status, error) {
|
|||||||
// ownerID == 0 and repoID != 0 means all runs for the given repo
|
// ownerID == 0 and repoID != 0 means all runs for the given repo
|
||||||
// ownerID != 0 and repoID == 0 means all runs for the given user/org
|
// ownerID != 0 and repoID == 0 means all runs for the given user/org
|
||||||
// ownerID != 0 and repoID != 0 undefined behavior
|
// ownerID != 0 and repoID != 0 undefined behavior
|
||||||
|
// workflowID filters runs by workflow file name (e.g. "build.yml"), empty means no filter
|
||||||
// Access rights are checked at the API route level
|
// Access rights are checked at the API route level
|
||||||
func ListRuns(ctx *context.APIContext, ownerID, repoID int64) {
|
func ListRuns(ctx *context.APIContext, ownerID, repoID int64, workflowID string) {
|
||||||
if ownerID != 0 && repoID != 0 {
|
if ownerID != 0 && repoID != 0 {
|
||||||
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
|
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
|
||||||
}
|
}
|
||||||
@@ -144,6 +145,7 @@ func ListRuns(ctx *context.APIContext, ownerID, repoID int64) {
|
|||||||
opts := actions_model.FindRunOptions{
|
opts := actions_model.FindRunOptions{
|
||||||
OwnerID: ownerID,
|
OwnerID: ownerID,
|
||||||
RepoID: repoID,
|
RepoID: repoID,
|
||||||
|
WorkflowID: workflowID,
|
||||||
ListOptions: listOptions,
|
ListOptions: listOptions,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,6 +174,7 @@ func ListRuns(ctx *context.APIContext, ownerID, repoID int64) {
|
|||||||
if headSHA := ctx.FormString("head_sha"); headSHA != "" {
|
if headSHA := ctx.FormString("head_sha"); headSHA != "" {
|
||||||
opts.CommitSHA = headSHA
|
opts.CommitSHA = headSHA
|
||||||
}
|
}
|
||||||
|
excludePullRequests := ctx.FormBool("exclude_pull_requests")
|
||||||
|
|
||||||
runs, total, err := db.FindAndCount[actions_model.ActionRun](ctx, opts)
|
runs, total, err := db.FindAndCount[actions_model.ActionRun](ctx, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -203,7 +206,7 @@ func ListRuns(ctx *context.APIContext, ownerID, repoID int64) {
|
|||||||
res.Entries = make([]*api.ActionWorkflowRun, len(runs))
|
res.Entries = make([]*api.ActionWorkflowRun, len(runs))
|
||||||
for i := range runs {
|
for i := range runs {
|
||||||
// TODO: load run attempts in batch
|
// TODO: load run attempts in batch
|
||||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, runs[i], nil)
|
convertedRun, err := convert.ToActionWorkflowRun(ctx, runs[i], nil, excludePullRequests)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -407,7 +407,7 @@ func ListWorkflowRuns(ctx *context.APIContext) {
|
|||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
shared.ListRuns(ctx, ctx.Doer.ID, 0)
|
shared.ListRuns(ctx, ctx.Doer.ID, 0, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListWorkflowJobs lists workflow jobs
|
// ListWorkflowJobs lists workflow jobs
|
||||||
|
|||||||
@@ -818,7 +818,7 @@ func (n *actionsNotifier) WorkflowRunStatusUpdate(ctx context.Context, repo *rep
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
run.Repo = repo
|
run.Repo = repo
|
||||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, nil)
|
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, nil, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("ToActionWorkflowRun: %v", err)
|
log.Error("ToActionWorkflowRun: %v", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ func TestToActionWorkflowRun_UsesTriggerEvent(t *testing.T) {
|
|||||||
run.Event = "push"
|
run.Event = "push"
|
||||||
run.TriggerEvent = "schedule"
|
run.TriggerEvent = "schedule"
|
||||||
|
|
||||||
apiRun, err := ToActionWorkflowRun(t.Context(), run, nil)
|
apiRun, err := ToActionWorkflowRun(t.Context(), run, nil, false)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, "schedule", apiRun.Event)
|
assert.Equal(t, "schedule", apiRun.Event)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import (
|
|||||||
"gitea.dev/modules/setting"
|
"gitea.dev/modules/setting"
|
||||||
api "gitea.dev/modules/structs"
|
api "gitea.dev/modules/structs"
|
||||||
"gitea.dev/modules/util"
|
"gitea.dev/modules/util"
|
||||||
|
webhook_module "gitea.dev/modules/webhook"
|
||||||
asymkey_service "gitea.dev/services/asymkey"
|
asymkey_service "gitea.dev/services/asymkey"
|
||||||
"gitea.dev/services/gitdiff"
|
"gitea.dev/services/gitdiff"
|
||||||
|
|
||||||
@@ -256,11 +257,8 @@ func ToActionTask(ctx context.Context, t *actions_model.ActionTask) (*api.Action
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToActionWorkflowRun(ctx context.Context, run *actions_model.ActionRun, attempt *actions_model.ActionRunAttempt) (_ *api.ActionWorkflowRun, err error) {
|
func ToActionWorkflowRun(ctx context.Context, run *actions_model.ActionRun, attempt *actions_model.ActionRunAttempt, excludePullRequests bool) (_ *api.ActionWorkflowRun, err error) {
|
||||||
if err := run.LoadRepo(ctx); err != nil {
|
if err := run.LoadAttributes(ctx); err != nil {
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := run.LoadTriggerUser(ctx); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,7 +291,15 @@ func ToActionWorkflowRun(ctx context.Context, run *actions_model.ActionRun, atte
|
|||||||
completedAt = attempt.Stopped.AsLocalTime()
|
completedAt = attempt.Stopped.AsLocalTime()
|
||||||
triggerUser = attempt.TriggerUser
|
triggerUser = attempt.TriggerUser
|
||||||
if attempt.Attempt > 1 {
|
if attempt.Attempt > 1 {
|
||||||
previousAttemptURL = new(fmt.Sprintf("%s/actions/runs/%d/attempts/%d", run.Repo.APIURL(ctx), run.ID, attempt.Attempt-1))
|
url := fmt.Sprintf("%s/actions/runs/%d/attempts/%d", run.Repo.APIURL(ctx), run.ID, attempt.Attempt-1)
|
||||||
|
previousAttemptURL = &url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pullRequests := []*api.PullRequestMinimal{}
|
||||||
|
if !excludePullRequests {
|
||||||
|
pullRequests, err = loadPullRequestsForRun(ctx, run)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,6 +322,89 @@ func ToActionWorkflowRun(ctx context.Context, run *actions_model.ActionRun, atte
|
|||||||
Repository: ToRepo(ctx, run.Repo, access_model.Permission{AccessMode: perm.AccessModeNone}),
|
Repository: ToRepo(ctx, run.Repo, access_model.Permission{AccessMode: perm.AccessModeNone}),
|
||||||
TriggerActor: ToUser(ctx, triggerUser, nil),
|
TriggerActor: ToUser(ctx, triggerUser, nil),
|
||||||
Actor: ToUser(ctx, actor, nil),
|
Actor: ToUser(ctx, actor, nil),
|
||||||
|
PullRequests: pullRequests,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadPullRequestsForRun returns the pull requests associated with a run, matching
|
||||||
|
// GitHub's `pull_requests` field on workflow run responses:
|
||||||
|
// - For pull_request / pull_request_review events, the PR whose ref triggered the run.
|
||||||
|
// - For push events, open PRs whose head branch matches the pushed ref in the same repo.
|
||||||
|
// - For other events, no PRs.
|
||||||
|
func loadPullRequestsForRun(ctx context.Context, run *actions_model.ActionRun) ([]*api.PullRequestMinimal, error) {
|
||||||
|
result := []*api.PullRequestMinimal{}
|
||||||
|
refName := git.RefName(run.Ref)
|
||||||
|
var prs issues_model.PullRequestList
|
||||||
|
switch {
|
||||||
|
case run.Event.IsPullRequest() || run.Event.IsPullRequestReview():
|
||||||
|
index, err := strconv.ParseInt(refName.PullName(), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
pr, err := issues_model.GetPullRequestByIndex(ctx, run.RepoID, index)
|
||||||
|
if err != nil {
|
||||||
|
if issues_model.IsErrPullRequestNotExist(err) {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
prs = issues_model.PullRequestList{pr}
|
||||||
|
case run.Event == webhook_module.HookEventPush:
|
||||||
|
branch := refName.BranchName()
|
||||||
|
if branch == "" {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
prs, err = issues_model.GetUnmergedPullRequestsByHeadInfo(ctx, run.RepoID, branch)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
for _, pr := range prs {
|
||||||
|
minimal, err := toPullRequestMinimal(ctx, run.Repo, pr, run.CommitSHA)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result = append(result, minimal)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toPullRequestMinimal(ctx context.Context, repo *repo_model.Repository, pr *issues_model.PullRequest, headSHA string) (*api.PullRequestMinimal, error) {
|
||||||
|
if err := pr.LoadBaseRepo(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := pr.LoadHeadRepo(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
headRepo := pr.HeadRepo
|
||||||
|
if headRepo == nil {
|
||||||
|
headRepo = pr.BaseRepo
|
||||||
|
}
|
||||||
|
return &api.PullRequestMinimal{
|
||||||
|
ID: pr.ID,
|
||||||
|
Number: pr.Index,
|
||||||
|
URL: fmt.Sprintf("%s/pulls/%d", repo.APIURL(ctx), pr.Index),
|
||||||
|
Head: api.PullRequestMinimalHead{
|
||||||
|
Ref: pr.HeadBranch,
|
||||||
|
SHA: headSHA,
|
||||||
|
Repo: api.PullRequestMinimalHeadRepo{
|
||||||
|
ID: headRepo.ID,
|
||||||
|
URL: headRepo.APIURL(ctx),
|
||||||
|
Name: headRepo.Name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Base: api.PullRequestMinimalHead{
|
||||||
|
Ref: pr.BaseBranch,
|
||||||
|
SHA: pr.MergeBase,
|
||||||
|
Repo: api.PullRequestMinimalHeadRepo{
|
||||||
|
ID: pr.BaseRepo.ID,
|
||||||
|
URL: pr.BaseRepo.APIURL(ctx),
|
||||||
|
Name: pr.BaseRepo.Name,
|
||||||
|
},
|
||||||
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1046,7 +1046,7 @@ func (*webhookNotifier) WorkflowRunStatusUpdate(ctx context.Context, repo *repo_
|
|||||||
}
|
}
|
||||||
|
|
||||||
run.Repo = repo
|
run.Repo = repo
|
||||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, nil)
|
convertedRun, err := convert.ToActionWorkflowRun(ctx, run, nil, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("ToActionWorkflowRun: %v", err)
|
log.Error("ToActionWorkflowRun: %v", err)
|
||||||
return
|
return
|
||||||
|
|||||||
Generated
+175
@@ -5335,6 +5335,12 @@
|
|||||||
"name": "head_sha",
|
"name": "head_sha",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "if true, the `pull_requests` field on each returned run is emptied",
|
||||||
|
"name": "exclude_pull_requests",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "page number of results to return (1-based)",
|
"description": "page number of results to return (1-based)",
|
||||||
@@ -6622,6 +6628,103 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "List runs for a workflow",
|
||||||
|
"operationId": "ActionsListWorkflowRuns",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repo",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repo",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "id of the workflow, must be the workflow file name (e.g. `build.yml`)",
|
||||||
|
"name": "workflow_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "workflow event name",
|
||||||
|
"name": "event",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "workflow branch",
|
||||||
|
"name": "branch",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "workflow status (pending, queued, in_progress, failure, success, skipped)",
|
||||||
|
"name": "status",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "triggered by user",
|
||||||
|
"name": "actor",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "triggering sha of the workflow run",
|
||||||
|
"name": "head_sha",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "if true, the `pull_requests` field on each returned run is emptied",
|
||||||
|
"name": "exclude_pull_requests",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page number of results to return (1-based)",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page size of results",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/WorkflowRunsList"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"$ref": "#/responses/forbidden"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/repos/{owner}/{repo}/activities/feeds": {
|
"/repos/{owner}/{repo}/activities/feeds": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
@@ -22395,6 +22498,13 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "PreviousAttemptURL"
|
"x-go-name": "PreviousAttemptURL"
|
||||||
},
|
},
|
||||||
|
"pull_requests": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/PullRequestMinimal"
|
||||||
|
},
|
||||||
|
"x-go-name": "PullRequests"
|
||||||
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"$ref": "#/definitions/Repository"
|
"$ref": "#/definitions/Repository"
|
||||||
},
|
},
|
||||||
@@ -28737,6 +28847,71 @@
|
|||||||
},
|
},
|
||||||
"x-go-package": "gitea.dev/modules/structs"
|
"x-go-package": "gitea.dev/modules/structs"
|
||||||
},
|
},
|
||||||
|
"PullRequestMinimal": {
|
||||||
|
"description": "PullRequestMinimal is the minimal information about a pull request, as\nreturned in the `pull_requests` field of a workflow run.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"base": {
|
||||||
|
"$ref": "#/definitions/PullRequestMinimalHead"
|
||||||
|
},
|
||||||
|
"head": {
|
||||||
|
"$ref": "#/definitions/PullRequestMinimalHead"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "ID"
|
||||||
|
},
|
||||||
|
"number": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "Number"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "URL"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "gitea.dev/modules/structs"
|
||||||
|
},
|
||||||
|
"PullRequestMinimalHead": {
|
||||||
|
"type": "object",
|
||||||
|
"title": "PullRequestMinimalHead is a minimal description of one side of a pull request.",
|
||||||
|
"properties": {
|
||||||
|
"ref": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Ref"
|
||||||
|
},
|
||||||
|
"repo": {
|
||||||
|
"$ref": "#/definitions/PullRequestMinimalHeadRepo"
|
||||||
|
},
|
||||||
|
"sha": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "SHA"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "gitea.dev/modules/structs"
|
||||||
|
},
|
||||||
|
"PullRequestMinimalHeadRepo": {
|
||||||
|
"type": "object",
|
||||||
|
"title": "PullRequestMinimalHeadRepo is a minimal description of the repository on one side of a pull request.",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "ID"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Name"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "URL"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "gitea.dev/modules/structs"
|
||||||
|
},
|
||||||
"PullReview": {
|
"PullReview": {
|
||||||
"description": "PullReview represents a pull request review",
|
"description": "PullReview represents a pull request review",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|||||||
Generated
+198
@@ -2344,6 +2344,13 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "PreviousAttemptURL"
|
"x-go-name": "PreviousAttemptURL"
|
||||||
},
|
},
|
||||||
|
"pull_requests": {
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/PullRequestMinimal"
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
"x-go-name": "PullRequests"
|
||||||
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"$ref": "#/components/schemas/Repository"
|
"$ref": "#/components/schemas/Repository"
|
||||||
},
|
},
|
||||||
@@ -8690,6 +8697,73 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"x-go-package": "gitea.dev/modules/structs"
|
"x-go-package": "gitea.dev/modules/structs"
|
||||||
},
|
},
|
||||||
|
"PullRequestMinimal": {
|
||||||
|
"description": "PullRequestMinimal is the minimal information about a pull request, as\nreturned in the `pull_requests` field of a workflow run.",
|
||||||
|
"properties": {
|
||||||
|
"base": {
|
||||||
|
"$ref": "#/components/schemas/PullRequestMinimalHead"
|
||||||
|
},
|
||||||
|
"head": {
|
||||||
|
"$ref": "#/components/schemas/PullRequestMinimalHead"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"format": "int64",
|
||||||
|
"type": "integer",
|
||||||
|
"x-go-name": "ID"
|
||||||
|
},
|
||||||
|
"number": {
|
||||||
|
"format": "int64",
|
||||||
|
"type": "integer",
|
||||||
|
"x-go-name": "Number"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"format": "uri",
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "URL"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"x-go-package": "gitea.dev/modules/structs"
|
||||||
|
},
|
||||||
|
"PullRequestMinimalHead": {
|
||||||
|
"properties": {
|
||||||
|
"ref": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Ref"
|
||||||
|
},
|
||||||
|
"repo": {
|
||||||
|
"$ref": "#/components/schemas/PullRequestMinimalHeadRepo"
|
||||||
|
},
|
||||||
|
"sha": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "SHA"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "PullRequestMinimalHead is a minimal description of one side of a pull request.",
|
||||||
|
"type": "object",
|
||||||
|
"x-go-package": "gitea.dev/modules/structs"
|
||||||
|
},
|
||||||
|
"PullRequestMinimalHeadRepo": {
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"format": "int64",
|
||||||
|
"type": "integer",
|
||||||
|
"x-go-name": "ID"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Name"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"format": "uri",
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "URL"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "PullRequestMinimalHeadRepo is a minimal description of the repository on one side of a pull request.",
|
||||||
|
"type": "object",
|
||||||
|
"x-go-package": "gitea.dev/modules/structs"
|
||||||
|
},
|
||||||
"PullReview": {
|
"PullReview": {
|
||||||
"description": "PullReview represents a pull request review",
|
"description": "PullReview represents a pull request review",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -16269,6 +16343,14 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "if true, the `pull_requests` field on each returned run is emptied",
|
||||||
|
"in": "query",
|
||||||
|
"name": "exclude_pull_requests",
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "page number of results to return (1-based)",
|
"description": "page number of results to return (1-based)",
|
||||||
"in": "query",
|
"in": "query",
|
||||||
@@ -17672,6 +17754,122 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "ActionsListWorkflowRuns",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "owner of the repo",
|
||||||
|
"in": "path",
|
||||||
|
"name": "owner",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "name of the repo",
|
||||||
|
"in": "path",
|
||||||
|
"name": "repo",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "id of the workflow, must be the workflow file name (e.g. `build.yml`)",
|
||||||
|
"in": "path",
|
||||||
|
"name": "workflow_id",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "workflow event name",
|
||||||
|
"in": "query",
|
||||||
|
"name": "event",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "workflow branch",
|
||||||
|
"in": "query",
|
||||||
|
"name": "branch",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "workflow status (pending, queued, in_progress, failure, success, skipped)",
|
||||||
|
"in": "query",
|
||||||
|
"name": "status",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "triggered by user",
|
||||||
|
"in": "query",
|
||||||
|
"name": "actor",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "triggering sha of the workflow run",
|
||||||
|
"in": "query",
|
||||||
|
"name": "head_sha",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "if true, the `pull_requests` field on each returned run is emptied",
|
||||||
|
"in": "query",
|
||||||
|
"name": "exclude_pull_requests",
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "page number of results to return (1-based)",
|
||||||
|
"in": "query",
|
||||||
|
"name": "page",
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "page size of results",
|
||||||
|
"in": "query",
|
||||||
|
"name": "limit",
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/components/responses/WorkflowRunsList"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/components/responses/error"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"$ref": "#/components/responses/forbidden"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/components/responses/notFound"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"summary": "List runs for a workflow",
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/repos/{owner}/{repo}/activities/feeds": {
|
"/repos/{owner}/{repo}/activities/feeds": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "repoListActivityFeeds",
|
"operationId": "repoListActivityFeeds",
|
||||||
|
|||||||
@@ -9,11 +9,15 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
actions_model "gitea.dev/models/actions"
|
||||||
auth_model "gitea.dev/models/auth"
|
auth_model "gitea.dev/models/auth"
|
||||||
|
"gitea.dev/models/db"
|
||||||
api "gitea.dev/modules/structs"
|
api "gitea.dev/modules/structs"
|
||||||
|
webhook_module "gitea.dev/modules/webhook"
|
||||||
"gitea.dev/tests"
|
"gitea.dev/tests"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAPIWorkflowRun(t *testing.T) {
|
func TestAPIWorkflowRun(t *testing.T) {
|
||||||
@@ -29,6 +33,103 @@ func TestAPIWorkflowRun(t *testing.T) {
|
|||||||
t.Run("RepoRuns", func(t *testing.T) {
|
t.Run("RepoRuns", func(t *testing.T) {
|
||||||
testAPIWorkflowRunBasic(t, "/api/v1/repos/org3/repo5/actions", "User2", 802, auth_model.AccessTokenScopeReadRepository)
|
testAPIWorkflowRunBasic(t, "/api/v1/repos/org3/repo5/actions", "User2", 802, auth_model.AccessTokenScopeReadRepository)
|
||||||
})
|
})
|
||||||
|
t.Run("RepoWorkflowRuns", func(t *testing.T) {
|
||||||
|
testAPIWorkflowRunsByWorkflowID(t, "org3", "repo5", "test.yaml", "User2", 802, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
})
|
||||||
|
t.Run("PullRequestsField", testAPIWorkflowRunsPullRequestsField)
|
||||||
|
}
|
||||||
|
|
||||||
|
// testAPIWorkflowRunsPullRequestsField exercises the `pull_requests` field and the
|
||||||
|
// `exclude_pull_requests` toggle by associating an inserted run with fixture PR
|
||||||
|
// user2/repo1#3 (head: branch2, base: master).
|
||||||
|
func testAPIWorkflowRunsPullRequestsField(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
ctx := t.Context()
|
||||||
|
|
||||||
|
run := &actions_model.ActionRun{
|
||||||
|
RepoID: 1,
|
||||||
|
OwnerID: 2,
|
||||||
|
TriggerUserID: 2,
|
||||||
|
WorkflowID: "pr-assoc.yaml",
|
||||||
|
Index: 99001,
|
||||||
|
Ref: "refs/pull/3/head",
|
||||||
|
CommitSHA: "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
|
||||||
|
Event: webhook_module.HookEventPullRequest,
|
||||||
|
TriggerEvent: "pull_request_target",
|
||||||
|
Status: actions_model.StatusSuccess,
|
||||||
|
}
|
||||||
|
require.NoError(t, db.Insert(ctx, run))
|
||||||
|
|
||||||
|
token := getUserToken(t, "User2", auth_model.AccessTokenScopeReadRepository)
|
||||||
|
runsURL := "/api/v1/repos/user2/repo1/actions/workflows/pr-assoc.yaml/runs"
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", runsURL).AddTokenAuth(token)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
list := DecodeJSON(t, resp, api.ActionWorkflowRunsResponse{})
|
||||||
|
|
||||||
|
var got *api.ActionWorkflowRun
|
||||||
|
for _, r := range list.Entries {
|
||||||
|
if r.ID == run.ID {
|
||||||
|
got = r
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.NotNil(t, got, "inserted PR-triggered run not returned")
|
||||||
|
require.Len(t, got.PullRequests, 1)
|
||||||
|
pr := got.PullRequests[0]
|
||||||
|
assert.Equal(t, int64(3), pr.Number)
|
||||||
|
assert.Equal(t, "branch2", pr.Head.Ref)
|
||||||
|
assert.Equal(t, "master", pr.Base.Ref)
|
||||||
|
assert.Equal(t, int64(1), pr.Base.Repo.ID)
|
||||||
|
assert.Equal(t, "repo1", pr.Base.Repo.Name)
|
||||||
|
|
||||||
|
req = NewRequest(t, "GET", runsURL+"?exclude_pull_requests=true").AddTokenAuth(token)
|
||||||
|
resp = MakeRequest(t, req, http.StatusOK)
|
||||||
|
excluded := DecodeJSON(t, resp, api.ActionWorkflowRunsResponse{})
|
||||||
|
for _, r := range excluded.Entries {
|
||||||
|
if r.ID == run.ID {
|
||||||
|
assert.Empty(t, r.PullRequests)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAPIWorkflowRunsByWorkflowID(t *testing.T, owner, repo, workflowID, userUsername string, expectedRunID int64, scope ...auth_model.AccessTokenScope) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
token := getUserToken(t, userUsername, scope...)
|
||||||
|
|
||||||
|
workflowRunsURL := fmt.Sprintf("/api/v1/repos/%s/%s/actions/workflows/%s/runs", owner, repo, workflowID)
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", workflowRunsURL).AddTokenAuth(token)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
runList := DecodeJSON(t, resp, api.ActionWorkflowRunsResponse{})
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, run := range runList.Entries {
|
||||||
|
verifyWorkflowRunCanbeFoundWithStatusFilter(t, workflowRunsURL, token, run.ID, "", run.Status, "", "", "", "")
|
||||||
|
verifyWorkflowRunCanbeFoundWithStatusFilter(t, workflowRunsURL, token, run.ID, "", "", "", run.HeadBranch, "", "")
|
||||||
|
verifyWorkflowRunCanbeFoundWithStatusFilter(t, workflowRunsURL, token, run.ID, "", "", run.Event, "", "", "")
|
||||||
|
verifyWorkflowRunCanbeFoundWithStatusFilter(t, workflowRunsURL, token, run.ID, "", "", "", "", run.TriggerActor.UserName, "")
|
||||||
|
verifyWorkflowRunCanbeFoundWithStatusFilter(t, workflowRunsURL, token, run.ID, "", "", "", "", run.TriggerActor.UserName, run.HeadSha)
|
||||||
|
if run.ID == expectedRunID {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.True(t, found, "expected to find run with ID %d in workflow %s runs", expectedRunID, workflowID)
|
||||||
|
|
||||||
|
req = NewRequest(t, "GET", workflowRunsURL+"?exclude_pull_requests=true").AddTokenAuth(token)
|
||||||
|
resp = MakeRequest(t, req, http.StatusOK)
|
||||||
|
excludedList := DecodeJSON(t, resp, api.ActionWorkflowRunsResponse{})
|
||||||
|
excludedFound := false
|
||||||
|
for _, run := range excludedList.Entries {
|
||||||
|
assert.Empty(t, run.PullRequests, "expected pull_requests to be empty when excluded")
|
||||||
|
if run.ID == expectedRunID {
|
||||||
|
excludedFound = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.True(t, excludedFound, "expected to find run with ID %d when excluding pull requests", expectedRunID)
|
||||||
|
|
||||||
|
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/actions/workflows/nonexistent.yaml/runs", owner, repo)).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAPIWorkflowRunBasic(t *testing.T, apiRootURL, userUsername string, runID int64, scope ...auth_model.AccessTokenScope) {
|
func testAPIWorkflowRunBasic(t *testing.T, apiRootURL, userUsername string, runID int64, scope ...auth_model.AccessTokenScope) {
|
||||||
|
|||||||
Reference in New Issue
Block a user