Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(runs_list): add button delete workflow run #33138

Open
wants to merge 30 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
fcd6948
feat(runs_list): add button delete workflow run
Jan 7, 2025
862237c
fix: lint
Jan 7, 2025
5358fc5
feat(action_model): add query DeleteRunByID
Jan 7, 2025
c8dbc29
feat(web): add path DELETE /username/reponame/actions/runs/{id}
Jan 7, 2025
3938aea
feat(initGlobalDeleteButton): add option http method delete when data…
Jan 7, 2025
6c58a02
fix: lint
Jan 7, 2025
8170c47
feat(DeleteRunByID): add delete action_run_job
Jan 7, 2025
60982c7
Merge branch 'main' into feat/actions-delete-workflow-run
zsbahtiar Jan 7, 2025
c4bdf78
refactor: change middleware to reqRepoActionsWriter
Jan 8, 2025
1e75212
feat(websrc): add repo action for handling delete
Jan 8, 2025
aa8a3d0
feat(action.list): add checkbox all and button delete
Jan 8, 2025
b6afdc6
refactor(run_list): change from button delete workflow to checkbox
Jan 8, 2025
8413beb
refactor(action.delete): change to support plural delete action runs
Jan 8, 2025
7f511d9
feat(DeleteRunByIDs): add delete action_task_step, action_task_output…
Jan 8, 2025
3faea22
revert
Jan 8, 2025
0b27261
revert
Jan 8, 2025
050a6b6
feat: add GetRunJobsByRunIDs
Jan 8, 2025
5dd6245
fix: job id
Jan 8, 2025
05af60b
refactor: change to GetRunsByIDsAndTriggerUserID
Jan 8, 2025
80377f8
rm: invalid test
Jan 8, 2025
1390874
feat: add GetRunTasksByJobIDs
Jan 8, 2025
965087e
feat: add removeActionTaskLogFilenames
Jan 8, 2025
71b773a
fix: lint
Jan 8, 2025
1a7a7e7
refactor: change to DeleteActionRunAndChild
Jan 8, 2025
8c742ab
test(integration): add TestRepoActionDelete
Jan 9, 2025
e17f73a
test(integration): add assert error equal
Jan 9, 2025
95924a1
fix: lint
Jan 9, 2025
f389529
fix: job
Jan 9, 2025
530360b
fix: check backend
Jan 9, 2025
5576b10
Merge branch 'main' into feat/actions-delete-workflow-run
silverwind Jan 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions models/actions/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,3 +435,56 @@ func UpdateRun(ctx context.Context, run *ActionRun, cols ...string) error {
}

type ActionRunIndex db.ResourceIndex

// DeleteActionRunAndChild delete action_task_step, action_task_output, action_task, action_run and action_run_job.
func DeleteActionRunAndChild(ctx context.Context, runIDs, jobIDs, taskIDs []int64) error {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
}
defer committer.Close()

_, err = db.GetEngine(ctx).In("task_id", taskIDs).
Delete(ActionTaskStep{})
if err != nil {
return err
}

_, err = db.GetEngine(ctx).In("task_id", taskIDs).
Delete(ActionTaskOutput{})
if err != nil {
return err
}

_, err = db.GetEngine(ctx).In("id", taskIDs).Delete(ActionTask{})
if err != nil {
return err
}

_, err = db.GetEngine(ctx).In("id", jobIDs).Delete(ActionRunJob{})
if err != nil {
return err
}

_, err = db.GetEngine(ctx).In("id", runIDs).Delete(ActionRun{})
if err != nil {
return err
}

return committer.Commit()
}

// GetRunsByIDsAndTriggerUserID -- get all action run by trigger user with selected ids
func GetRunsByIDsAndTriggerUserID(ctx context.Context, ids []int64, triggerUserID int64) ([]*ActionRun, error) {
var runs []*ActionRun
err := db.GetEngine(ctx).Where("trigger_user_id=?", triggerUserID).
In("id", ids).Find(&runs)
if err != nil {
return nil, err
}
if len(runs) < 1 {
return nil, fmt.Errorf("run with ids %d: %w", ids, util.ErrNotExist)
}

return runs, nil
}
16 changes: 16 additions & 0 deletions models/actions/run_job.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,19 @@ func AggregateJobStatus(jobs []*ActionRunJob) Status {
return StatusUnknown // it shouldn't happen
}
}

func GetRunJobsByRunIDs(ctx context.Context, runIDs []int64) ([]*ActionRunJob, error) {
var jobs []*ActionRunJob
if err := db.GetEngine(ctx).In("run_id", runIDs).Find(&jobs); err != nil {
return nil, err
}
return jobs, nil
}

func GetRunTasksByJobIDs(ctx context.Context, jobIDs []int64) ([]*ActionTask, error) {
var tasks []*ActionTask
if err := db.GetEngine(ctx).In("job_id", jobIDs).Find(&tasks); err != nil {
return nil, err
}
return tasks, nil
}
96 changes: 96 additions & 0 deletions routers/web/repo/actions/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"bytes"
stdCtx "context"
"net/http"
"os"
"path/filepath"
"slices"
"strings"

Expand All @@ -18,6 +20,7 @@ import (
"code.gitea.io/gitea/modules/actions"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
Expand All @@ -28,6 +31,7 @@ import (
"code.gitea.io/gitea/services/convert"

"github.com/nektos/act/pkg/model"
"golang.org/x/sync/errgroup"
"gopkg.in/yaml.v3"
)

Expand Down Expand Up @@ -312,6 +316,7 @@ func prepareWorkflowList(ctx *context.Context, workflows []Workflow) {
pager.AddParamFromRequest(ctx.Req)
ctx.Data["Page"] = pager
ctx.Data["HasWorkflowsOrRuns"] = len(workflows) > 0 || len(runs) > 0
ctx.Data["IsRepoAdmin"] = ctx.IsSigned && (ctx.Repo.IsAdmin() || ctx.Doer.IsAdmin)
}

// loadIsRefDeleted loads the IsRefDeleted field for each run in the list.
Expand Down Expand Up @@ -424,3 +429,94 @@ func decodeNode(node yaml.Node, out any) bool {
}
return true
}

func DeleteRuns(ctx *context.Context) {
rd := ctx.Req.Body
defer rd.Close()

req := DeleteRunsRequest{}
if err := json.NewDecoder(rd).Decode(&req); err != nil {
ctx.ServerError("failed to decode request body into delte runs request", err)
return
}
if len(req.ActionIDs) < 1 {
ctx.ServerError("missing action_run.id for delete action run", nil)
return
}

var (
eg = new(errgroup.Group)
actionRuns []*actions_model.ActionRun
jobIDs, taskIDs []int64
taskLogFileNames []string
)
eg.Go(func() error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The database operations should be done in one transaction, but not in "errgroup goroutine".

var err error
actionRuns, err = actions_model.GetRunsByIDsAndTriggerUserID(ctx, req.ActionIDs, ctx.Doer.ID)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use Doer.ID here? For example: user A is admin, an ActionRun is not created by them, then user A can't delete that ActionRun?

return err
})

eg.Go(func() error {
actionRunJobs, err := actions_model.GetRunJobsByRunIDs(ctx, req.ActionIDs)
if err != nil {
return err
}

for _, actionRunJob := range actionRunJobs {
jobIDs = append(jobIDs, actionRunJob.ID)
}
actionTasks, err := actions_model.GetRunTasksByJobIDs(ctx, jobIDs)
if err != nil {
return err
}

for _, actionTask := range actionTasks {
taskIDs = append(taskIDs, actionTask.ID)
taskLogFileNames = append(taskLogFileNames, actionTask.LogFilename)
}
return nil
})

err := eg.Wait()
if err != nil {
ctx.ServerError("failed to get action runs and action run jobs", err)
return
}

if len(actionRuns) != len(req.ActionIDs) {
ctx.ServerError("action ids not match with request", nil)
return
}

err = actions_model.DeleteActionRunAndChild(ctx, req.ActionIDs, jobIDs, taskIDs)
if err != nil {
ctx.ServerError("failed to delete action_run", err)
return
}

removeActionTaskLogFilenames(taskLogFileNames)

ctx.Status(http.StatusNoContent)
}

type DeleteRunsRequest struct {
ActionIDs []int64 `json:"actionIds"`
}

func removeActionTaskLogFilenames(taskLogFileNames []string) {
dirNameActionLog := "actions_log"
go func() {
for _, taskLogFileName := range taskLogFileNames {
var fileName string
if filepath.IsAbs(setting.AppDataPath) {
fileName = filepath.Join(setting.AppDataPath, dirNameActionLog, taskLogFileName)
} else {
fileName = filepath.Join(setting.AppWorkPath, setting.AppDataPath, dirNameActionLog, taskLogFileName)
}

if err := os.Remove(fileName); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC there is a "storage" for Action logs, they could be in S3 (ObjectStorage) but not on filesystem

log.Error("failed to remove actions_log file %s: %v", fileName, err)
}
}
}()
}
2 changes: 2 additions & 0 deletions routers/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -1435,6 +1435,8 @@ func registerRoutes(m *web.Router) {
m.Group("/workflows/{workflow_name}", func() {
m.Get("/badge.svg", actions.GetWorkflowBadge)
})

m.Post("/runs/delete", reqRepoActionsWriter, actions.DeleteRuns)
}, optSignIn, context.RepoAssignment, repo.MustBeNotEmpty, reqRepoActionsReader, actions.MustEnableActions)
// end "/{username}/{reponame}/actions"

Expand Down
15 changes: 14 additions & 1 deletion templates/repo/actions/list.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,19 @@
</div>
</div>
<div class="twelve wide column content">
<div class="ui secondary filter menu tw-justify-end tw-flex tw-items-center">
<div id="action-filter" class="ui secondary filter menu tw-flex tw-items-center tw-justify-between">
<!-- Checkbox -->
<div class="action-list-toolbar-left">
<input type="checkbox" autocomplete="off" class="action-checkbox-all tw-mr-4 tw-ml-4" title="{{ctx.Locale.Tr "repo.issues.action_check_all"}}">
</div>
<div class="tw-flex tw-items-center">
{{if $.IsRepoAdmin}}
<div id="action-delete" class="ui jump item tw-hidden">
<button class="ui red button action-action" data-action="delete" data-url="{{$.RepoLink}}/actions/runs/delete" data-action-delete-confirm="{{ctx.Locale.Tr "confirm_delete_selected"}}">
{{ctx.Locale.Tr "repo.issues.delete"}}
</button>
</div>
{{end}}
<!-- Actor -->
<div class="ui{{if not .Actors}} disabled{{end}} dropdown jump item">
<span class="text">{{ctx.Locale.Tr "actions.runs.actor"}}</span>
Expand Down Expand Up @@ -63,6 +75,7 @@
</a>
{{end}}
</div>
</div>
</div>

{{if .AllowDisableOrEnableWorkflow}}
Expand Down
3 changes: 2 additions & 1 deletion templates/repo/actions/runs_list.tmpl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div class="flex-list run-list">
<div id="action-actions" class="flex-list run-list">
{{if not .Runs}}
<div class="empty-placeholder">
{{svg "octicon-no-entry" 48}}
Expand All @@ -7,6 +7,7 @@
{{end}}
{{range .Runs}}
<div class="flex-item tw-items-center">
<input type="checkbox" autocomplete="off" class="action-checkbox tw-mr-4 tw-ml-4" data-action-id={{.ID}} aria-label="{{ctx.Locale.Tr "repo.issues.action_check"}} &quot;{{.Title}}&quot;"{{if or (eq .Status 6) (eq .Status 5)}}disabled{{end}}>
<div class="flex-item-leading">
{{template "repo/actions/status" (dict "status" .Status.String)}}
</div>
Expand Down
Loading
Loading