Skip to content

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

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

Closed
Closed
Show file tree
Hide file tree
Changes from 16 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
57 changes: 57 additions & 0 deletions models/actions/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,3 +435,60 @@ func UpdateRun(ctx context.Context, run *ActionRun, cols ...string) error {
}

type ActionRunIndex db.ResourceIndex

// DeleteRunByIDs delete action_run and action_run_job.
func DeleteRunByIDs(ctx context.Context, ids []int64) error {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return err
}
defer committer.Close()

_, err = db.GetEngine(ctx).
Table("action_task").
Join("INNER", "action_task_step", "action_task.id = action_task_step.task_id").
In("action_task.job_id", ids).
Delete(ActionTaskStep{})
if err != nil {
return err
}

_, err = db.GetEngine(ctx).
Table("action_task").
Join("INNER", "action_task_output", "action_task.id = action_task_output.task_id").
In("action_task.job_id", ids).
Delete(ActionTaskOutput{})
if err != nil {
return err
}

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

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

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

return committer.Commit()
}

func GetRunsByIDs(ctx context.Context, ids []int64) ([]*ActionRun, error) {
var runs []*ActionRun
err := db.GetEngine(ctx).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
}
38 changes: 38 additions & 0 deletions routers/web/repo/actions/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,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 Down Expand Up @@ -312,6 +313,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 +426,39 @@ 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
}

actionRun, err := actions_model.GetRunsByIDs(ctx, req.ActionIDs)
if err != nil {
ctx.ServerError("failed to get action_run", err)
return
}

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

err = actions_model.DeleteRunByIDs(ctx, req.ActionIDs)
if err != nil {
ctx.ServerError("failed to delete action_run", err)
return
}
ctx.Status(http.StatusNoContent)
}

type DeleteRunsRequest struct {
ActionIDs []int64 `json:"actionIds"`
}
2 changes: 2 additions & 0 deletions routers/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -1434,6 +1434,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
97 changes: 97 additions & 0 deletions web_src/js/features/repo-action-list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import {queryElems, toggleElem} from '../utils/dom.ts';
import {confirmModal} from './comp/ConfirmModal.ts';
import {showErrorToast} from '../modules/toast.ts';
import {POST} from '../modules/fetch.ts';

function initRepoActionListCheckboxes() {
const actionListSelectAll = document.querySelector<HTMLInputElement>('.action-checkbox-all');
if (!actionListSelectAll) return; // logged out state
const issueCheckboxes = document.querySelectorAll<HTMLInputElement>('.action-checkbox:not([disabled])');
const actionDelete = document.querySelector('#action-delete');
const syncIssueSelectionState = () => {
const enabledCheckboxes = Array.from(issueCheckboxes).filter((el) => !el.disabled);
const checkedCheckboxes = enabledCheckboxes.filter((el) => el.checked);
const anyChecked = Boolean(checkedCheckboxes.length);
const allChecked = anyChecked && checkedCheckboxes.length === enabledCheckboxes.length;

if (allChecked) {
actionListSelectAll.checked = true;
actionListSelectAll.indeterminate = false;
} else if (anyChecked) {
actionListSelectAll.checked = false;
actionListSelectAll.indeterminate = true;
} else {
actionListSelectAll.checked = false;
actionListSelectAll.indeterminate = false;
}
if (actionDelete) {
toggleElem('#action-delete', anyChecked);
}
};

for (const el of issueCheckboxes) {
el.addEventListener('change', syncIssueSelectionState);
}

actionListSelectAll.addEventListener('change', () => {
for (const el of issueCheckboxes) {
if (!el.disabled) {
el.checked = actionListSelectAll.checked;
}
}
syncIssueSelectionState();
});

queryElems(document, '.action-action', (el) => el.addEventListener('click',
async (e: MouseEvent) => {
e.preventDefault();

const action = el.getAttribute('data-action');
const url = el.getAttribute('data-url');
const actionIDList: number[] = [];
const radix = 10;
for (const el of document.querySelectorAll<HTMLInputElement>('.action-checkbox:checked:not([disabled])')) {
const id = el.getAttribute('data-action-id');
if (id) {
actionIDList.push(parseInt(id, radix));
}
}
if (actionIDList.length < 1) return;

// for delete
if (action === 'delete') {
const confirmText = el.getAttribute('data-action-delete-confirm');
if (!await confirmModal({content: confirmText, confirmButtonColor: 'red'})) {
return;
}
}

try {
await deleteActions(url, actionIDList);
window.location.reload();
} catch (err) {
showErrorToast(err.responseJSON?.error ?? err.message);
}
},
));
}

async function deleteActions(url: string, actionIds: number[]) {
try {
const response = await POST(url, {
data: {
actionIds,
},
});
if (!response.ok) {
throw new Error('failed to delete actions');
}
} catch (error) {
console.error(error);
}
}
export function initRepoActionList() {
if (document.querySelector('.page-content.repository.actions')) {
initRepoActionListCheckboxes();
}
}
2 changes: 2 additions & 0 deletions web_src/js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ import {
initGlobalEnterQuickSubmit,
initGlobalFormDirtyLeaveConfirm,
} from './features/common-form.ts';
import {initRepoActionList} from './features/repo-action-list.ts';

initGiteaFomantic();
initDirAuto();
Expand Down Expand Up @@ -214,5 +215,6 @@ onDomReady(() => {
initColorPickers,

initOAuth2SettingsDisableCheckbox,
initRepoActionList,
]);
});
Loading