From 6709e28da78a0ea7e63f9fe4e32f620abdc88d14 Mon Sep 17 00:00:00 2001 From: Chester Date: Wed, 1 May 2024 09:40:23 +0800 Subject: [PATCH] Add API endpoints for getting action jobs status (#26673) Sample of response, it is similar to Github actions ref https://docs.github.com/en/rest/actions/workflow-runs?apiVersion=2022-11-28#list-workflow-runs-for-a-repository ``` json { "workflow_runs": [ { "id": 3, "name": "Explore-Gitea-Actions", "head_branch": "main", "head_sha": "6d8d29a9f7a01ded8f8aeb64341cb31ee1ab5f19", "run_number": 3, "event": "push", "display_title": "More job", "status": "success", "workflow_id": "demo2.yaml", "url": "/chester/test/actions/runs/3", "created_at": "2023-08-22T13:41:33-04:00", "updated_at": "2023-08-22T13:41:37-04:00", "run_started_at": "2023-08-22T13:41:33-04:00" }, { "id": 2, "name": "Explore-Gitea-Actions", "head_branch": "main", "head_sha": "6d8d29a9f7a01ded8f8aeb64341cb31ee1ab5f19", "run_number": 2, "event": "push", "display_title": "More job", "status": "success", "workflow_id": "demo.yaml", "url": "/chester/test/actions/runs/2", "created_at": "2023-08-22T13:41:30-04:00", "updated_at": "2023-08-22T13:41:33-04:00", "run_started_at": "2023-08-22T13:41:30-04:00" }, { "id": 1, "name": "Explore-Gitea-Actions", "head_branch": "main", "head_sha": "e5369ab054cae79899ba36e45ee82811a6e0acd5", "run_number": 1, "event": "push", "display_title": "Add job", "status": "failure", "workflow_id": "demo.yaml", "url": "/chester/test/actions/runs/1", "created_at": "2023-08-22T13:15:21-04:00", "updated_at": "2023-08-22T13:18:10-04:00", "run_started_at": "2023-08-22T13:15:21-04:00" } ], "total_count": 3 } ``` --------- Co-authored-by: yp05327 <576951401@qq.com> Co-authored-by: puni9869 <80308335+puni9869@users.noreply.github.com> --- modules/structs/repo_actions.go | 34 ++++++++ routers/api/v1/api.go | 3 + routers/api/v1/repo/actions.go | 80 +++++++++++++++++ routers/api/v1/swagger/repo.go | 7 ++ services/convert/convert.go | 27 ++++++ templates/swagger/v1_json.tmpl | 149 ++++++++++++++++++++++++++++++++ 6 files changed, 300 insertions(+) create mode 100644 modules/structs/repo_actions.go create mode 100644 routers/api/v1/repo/actions.go diff --git a/modules/structs/repo_actions.go b/modules/structs/repo_actions.go new file mode 100644 index 0000000000..b13f344738 --- /dev/null +++ b/modules/structs/repo_actions.go @@ -0,0 +1,34 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package structs + +import ( + "time" +) + +// ActionTask represents a ActionTask +type ActionTask struct { + ID int64 `json:"id"` + Name string `json:"name"` + HeadBranch string `json:"head_branch"` + HeadSHA string `json:"head_sha"` + RunNumber int64 `json:"run_number"` + Event string `json:"event"` + DisplayTitle string `json:"display_title"` + Status string `json:"status"` + WorkflowID string `json:"workflow_id"` + URL string `json:"url"` + // swagger:strfmt date-time + CreatedAt time.Time `json:"created_at"` + // swagger:strfmt date-time + UpdatedAt time.Time `json:"updated_at"` + // swagger:strfmt date-time + RunStartedAt time.Time `json:"run_started_at"` +} + +// ActionTaskResponse returns a ActionTask +type ActionTaskResponse struct { + Entries []*ActionTask `json:"workflow_runs"` + TotalCount int64 `json:"total_count"` +} diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 73071aa8df..74062c44ac 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1168,6 +1168,9 @@ func Routes() *web.Route { m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateTagOption{}), repo.CreateTag) m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteTag) }, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(true)) + m.Group("/actions", func() { + m.Get("/tasks", repo.ListActionTasks) + }, reqRepoReader(unit.TypeActions), context.ReferencesGitRepo(true)) m.Group("/keys", func() { m.Combo("").Get(repo.ListDeployKeys). Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey) diff --git a/routers/api/v1/repo/actions.go b/routers/api/v1/repo/actions.go new file mode 100644 index 0000000000..635cb4e138 --- /dev/null +++ b/routers/api/v1/repo/actions.go @@ -0,0 +1,80 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo + +import ( + "net/http" + + actions_model "code.gitea.io/gitea/models/actions" + "code.gitea.io/gitea/models/db" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/routers/api/v1/utils" + "code.gitea.io/gitea/services/context" + "code.gitea.io/gitea/services/convert" +) + +// ListActionTasks list all the actions of a repository +func ListActionTasks(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/actions/tasks repository ListActionTasks + // --- + // summary: List a repository's action tasks + // 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: page + // in: query + // description: page number of results to return (1-based) + // type: integer + // - name: limit + // in: query + // description: page size of results, default maximum page size is 50 + // type: integer + // responses: + // "200": + // "$ref": "#/responses/TasksList" + // "400": + // "$ref": "#/responses/error" + // "403": + // "$ref": "#/responses/forbidden" + // "404": + // "$ref": "#/responses/notFound" + // "409": + // "$ref": "#/responses/conflict" + // "422": + // "$ref": "#/responses/validationError" + + tasks, total, err := db.FindAndCount[actions_model.ActionTask](ctx, &actions_model.FindTaskOptions{ + ListOptions: utils.GetListOptions(ctx), + RepoID: ctx.Repo.Repository.ID, + }) + if err != nil { + ctx.Error(http.StatusInternalServerError, "ListActionTasks", err) + return + } + + res := new(api.ActionTaskResponse) + res.TotalCount = total + + res.Entries = make([]*api.ActionTask, len(tasks)) + for i := range tasks { + convertedTask, err := convert.ToActionTask(ctx, tasks[i]) + if err != nil { + ctx.Error(http.StatusInternalServerError, "ToActionTask", err) + return + } + res.Entries[i] = convertedTask + } + + ctx.JSON(http.StatusOK, &res) +} diff --git a/routers/api/v1/swagger/repo.go b/routers/api/v1/swagger/repo.go index c3219f28d6..fcd34a63a9 100644 --- a/routers/api/v1/swagger/repo.go +++ b/routers/api/v1/swagger/repo.go @@ -415,6 +415,13 @@ type swaggerRepoNewIssuePinsAllowed struct { Body api.NewIssuePinsAllowed `json:"body"` } +// TasksList +// swagger:response TasksList +type swaggerRepoTasksList struct { + // in:body + Body api.ActionTaskResponse `json:"body"` +} + // swagger:response Compare type swaggerCompare struct { // in:body diff --git a/services/convert/convert.go b/services/convert/convert.go index 3b6139d2fe..c44179632e 100644 --- a/services/convert/convert.go +++ b/services/convert/convert.go @@ -11,6 +11,7 @@ import ( "strings" "time" + actions_model "code.gitea.io/gitea/models/actions" asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/auth" git_model "code.gitea.io/gitea/models/git" @@ -24,6 +25,7 @@ import ( "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/gitdiff" @@ -193,6 +195,31 @@ func ToTag(repo *repo_model.Repository, t *git.Tag) *api.Tag { } } +// ToActionTask convert a actions_model.ActionTask to an api.ActionTask +func ToActionTask(ctx context.Context, t *actions_model.ActionTask) (*api.ActionTask, error) { + if err := t.LoadAttributes(ctx); err != nil { + return nil, err + } + + url := strings.TrimSuffix(setting.AppURL, "/") + t.GetRunLink() + + return &api.ActionTask{ + ID: t.ID, + Name: t.Job.Name, + HeadBranch: t.Job.Run.PrettyRef(), + HeadSHA: t.Job.CommitSHA, + RunNumber: t.Job.Run.Index, + Event: t.Job.Run.TriggerEvent, + DisplayTitle: t.Job.Run.Title, + Status: t.Status.String(), + WorkflowID: t.Job.Run.WorkflowID, + URL: url, + CreatedAt: t.Created.AsLocalTime(), + UpdatedAt: t.Updated.AsLocalTime(), + RunStartedAt: t.Started.AsLocalTime(), + }, nil +} + // ToVerification convert a git.Commit.Signature to an api.PayloadCommitVerification func ToVerification(ctx context.Context, c *git.Commit) *api.PayloadCommitVerification { verif := asymkey_model.ParseCommitWithSignature(ctx, c) diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 362a847332..0c5e5c974d 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -3997,6 +3997,66 @@ } } }, + "/repos/{owner}/{repo}/actions/tasks": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "List a repository's action tasks", + "operationId": "ListActionTasks", + "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": "integer", + "description": "page number of results to return (1-based)", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size of results, default maximum page size is 50", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "$ref": "#/responses/TasksList" + }, + "400": { + "$ref": "#/responses/error" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + }, + "409": { + "$ref": "#/responses/conflict" + }, + "422": { + "$ref": "#/responses/validationError" + } + } + } + }, "/repos/{owner}/{repo}/actions/variables": { "get": { "produces": [ @@ -17953,6 +18013,89 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, + "ActionTask": { + "description": "ActionTask represents a ActionTask", + "type": "object", + "properties": { + "created_at": { + "type": "string", + "format": "date-time", + "x-go-name": "CreatedAt" + }, + "display_title": { + "type": "string", + "x-go-name": "DisplayTitle" + }, + "event": { + "type": "string", + "x-go-name": "Event" + }, + "head_branch": { + "type": "string", + "x-go-name": "HeadBranch" + }, + "head_sha": { + "type": "string", + "x-go-name": "HeadSHA" + }, + "id": { + "type": "integer", + "format": "int64", + "x-go-name": "ID" + }, + "name": { + "type": "string", + "x-go-name": "Name" + }, + "run_number": { + "type": "integer", + "format": "int64", + "x-go-name": "RunNumber" + }, + "run_started_at": { + "type": "string", + "format": "date-time", + "x-go-name": "RunStartedAt" + }, + "status": { + "type": "string", + "x-go-name": "Status" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "x-go-name": "UpdatedAt" + }, + "url": { + "type": "string", + "x-go-name": "URL" + }, + "workflow_id": { + "type": "string", + "x-go-name": "WorkflowID" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, + "ActionTaskResponse": { + "description": "ActionTaskResponse returns a ActionTask", + "type": "object", + "properties": { + "total_count": { + "type": "integer", + "format": "int64", + "x-go-name": "TotalCount" + }, + "workflow_runs": { + "type": "array", + "items": { + "$ref": "#/definitions/ActionTask" + }, + "x-go-name": "Entries" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, "ActionVariable": { "description": "ActionVariable return value of the query API", "type": "object", @@ -25409,6 +25552,12 @@ } } }, + "TasksList": { + "description": "TasksList", + "schema": { + "$ref": "#/definitions/ActionTaskResponse" + } + }, "Team": { "description": "Team", "schema": {