Skip to content

Commit a4a567f

Browse files
yp05327silverwindwxiaoguang
authored
Check disabled workflow when rerun jobs (#26535)
In GitHub, we can not rerun jobs if the workflow is disabled. --------- Co-authored-by: silverwind <[email protected]> Co-authored-by: wxiaoguang <[email protected]>
1 parent b3f7137 commit a4a567f

File tree

7 files changed

+76
-100
lines changed

7 files changed

+76
-100
lines changed

options/locale/locale_en-US.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3503,6 +3503,7 @@ workflow.disable = Disable Workflow
35033503
workflow.disable_success = Workflow '%s' disabled successfully.
35043504
workflow.enable = Enable Workflow
35053505
workflow.enable_success = Workflow '%s' enabled successfully.
3506+
workflow.disabled = Workflow is disabled.
35063507
35073508
need_approval_desc = Need approval to run workflows for fork pull request.
35083509

routers/web/repo/actions/view.go

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -259,31 +259,35 @@ func ViewPost(ctx *context_module.Context) {
259259
ctx.JSON(http.StatusOK, resp)
260260
}
261261

262-
func RerunOne(ctx *context_module.Context) {
262+
// Rerun will rerun jobs in the given run
263+
// jobIndex = 0 means rerun all jobs
264+
func Rerun(ctx *context_module.Context) {
263265
runIndex := ctx.ParamsInt64("run")
264266
jobIndex := ctx.ParamsInt64("job")
265267

266-
job, _ := getRunJobs(ctx, runIndex, jobIndex)
267-
if ctx.Written() {
268+
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
269+
if err != nil {
270+
ctx.Error(http.StatusInternalServerError, err.Error())
268271
return
269272
}
270273

271-
if err := rerunJob(ctx, job); err != nil {
272-
ctx.Error(http.StatusInternalServerError, err.Error())
274+
// can not rerun job when workflow is disabled
275+
cfgUnit := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypeActions)
276+
cfg := cfgUnit.ActionsConfig()
277+
if cfg.IsWorkflowDisabled(run.WorkflowID) {
278+
ctx.JSONError(ctx.Locale.Tr("actions.workflow.disabled"))
273279
return
274280
}
275281

276-
ctx.JSON(http.StatusOK, struct{}{})
277-
}
278-
279-
func RerunAll(ctx *context_module.Context) {
280-
runIndex := ctx.ParamsInt64("run")
281-
282-
_, jobs := getRunJobs(ctx, runIndex, 0)
282+
job, jobs := getRunJobs(ctx, runIndex, jobIndex)
283283
if ctx.Written() {
284284
return
285285
}
286286

287+
if jobIndex != 0 {
288+
jobs = []*actions_model.ActionRunJob{job}
289+
}
290+
287291
for _, j := range jobs {
288292
if err := rerunJob(ctx, j); err != nil {
289293
ctx.Error(http.StatusInternalServerError, err.Error())

routers/web/web.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1211,14 +1211,14 @@ func registerRoutes(m *web.Route) {
12111211
m.Combo("").
12121212
Get(actions.View).
12131213
Post(web.Bind(actions.ViewRequest{}), actions.ViewPost)
1214-
m.Post("/rerun", reqRepoActionsWriter, actions.RerunOne)
1214+
m.Post("/rerun", reqRepoActionsWriter, actions.Rerun)
12151215
m.Get("/logs", actions.Logs)
12161216
})
12171217
m.Post("/cancel", reqRepoActionsWriter, actions.Cancel)
12181218
m.Post("/approve", reqRepoActionsWriter, actions.Approve)
12191219
m.Post("/artifacts", actions.ArtifactsView)
12201220
m.Get("/artifacts/{artifact_name}", actions.ArtifactsDownloadView)
1221-
m.Post("/rerun", reqRepoActionsWriter, actions.RerunAll)
1221+
m.Post("/rerun", reqRepoActionsWriter, actions.Rerun)
12221222
})
12231223
}, reqRepoActionsReader, actions.MustEnableActions)
12241224

templates/base/head_script.tmpl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ If you are customizing Gitea, please do not change this file.
44
If you introduce mistakes in it, Gitea JavaScript code wouldn't run correctly.
55
*/}}
66
<script>
7+
{{/* before our JS code gets loaded, use arrays to store errors, then the arrays will be switched to our error handler later */}}
78
window.addEventListener('error', function(e) {window._globalHandlerErrors=window._globalHandlerErrors||[]; window._globalHandlerErrors.push(e);});
9+
window.addEventListener('unhandledrejection', function(e) {window._globalHandlerErrors=window._globalHandlerErrors||[]; window._globalHandlerErrors.push(e);});
810
window.config = {
911
appUrl: '{{AppUrl}}',
1012
appSubUrl: '{{AppSubUrl}}',

web_src/js/bootstrap.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ export function showGlobalErrorMessage(msg) {
2020
* @param {ErrorEvent} e
2121
*/
2222
function processWindowErrorEvent(e) {
23+
if (e.type === 'unhandledrejection') {
24+
showGlobalErrorMessage(`JavaScript promise rejection: ${e.reason}. Open browser console to see more details.`);
25+
return;
26+
}
2327
if (!e.error && e.lineno === 0 && e.colno === 0 && e.filename === '' && window.navigator.userAgent.includes('FxiOS/')) {
2428
// At the moment, Firefox (iOS) (10x) has an engine bug. See https://github.com/go-gitea/gitea/issues/20240
2529
// If a script inserts a newly created (and content changed) element into DOM, there will be a nonsense error event reporting: Script error: line 0, col 0.
@@ -30,6 +34,10 @@ function processWindowErrorEvent(e) {
3034
}
3135

3236
function initGlobalErrorHandler() {
37+
if (window._globalHandlerErrors?._inited) {
38+
showGlobalErrorMessage(`The global error handler has been initialized, do not initialize it again`);
39+
return;
40+
}
3341
if (!window.config) {
3442
showGlobalErrorMessage(`Gitea JavaScript code couldn't run correctly, please check your custom templates`);
3543
}
@@ -40,7 +48,7 @@ function initGlobalErrorHandler() {
4048
processWindowErrorEvent(e);
4149
}
4250
// then, change _globalHandlerErrors to an object with push method, to process further error events directly
43-
window._globalHandlerErrors = {'push': (e) => processWindowErrorEvent(e)};
51+
window._globalHandlerErrors = {_inited: true, push: (e) => processWindowErrorEvent(e)};
4452
}
4553

4654
initGlobalErrorHandler();

web_src/js/components/RepoActionView.vue

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
<button class="ui basic small compact button red" @click="cancelRun()" v-else-if="run.canCancel">
1515
{{ locale.cancel }}
1616
</button>
17-
<button class="ui basic small compact button gt-mr-0" @click="rerun()" v-else-if="run.canRerun">
17+
<button class="ui basic small compact button gt-mr-0 link-action" :data-url="`${run.link}/rerun`" v-else-if="run.canRerun">
1818
{{ locale.rerun_all }}
1919
</button>
2020
</div>
@@ -38,7 +38,7 @@
3838
<span class="job-brief-name gt-mx-3 gt-ellipsis">{{ job.name }}</span>
3939
</div>
4040
<span class="job-brief-item-right">
41-
<SvgIcon name="octicon-sync" role="button" :data-tooltip-content="locale.rerun" class="job-brief-rerun gt-mx-3" @click="rerunJob(index)" v-if="job.canRerun && onHoverRerunIndex === job.id"/>
41+
<SvgIcon name="octicon-sync" role="button" :data-tooltip-content="locale.rerun" class="job-brief-rerun gt-mx-3 link-action" :data-url="`${run.link}/jobs/${index}/rerun`" v-if="job.canRerun && onHoverRerunIndex === job.id"/>
4242
<span class="step-summary-duration">{{ job.duration }}</span>
4343
</span>
4444
</a>
@@ -264,17 +264,6 @@ const sfc = {
264264
this.loadJob(); // try to load the data immediately instead of waiting for next timer interval
265265
}
266266
},
267-
// rerun a job
268-
async rerunJob(idx) {
269-
const jobLink = `${this.run.link}/jobs/${idx}`;
270-
await this.fetchPost(`${jobLink}/rerun`);
271-
window.location.href = jobLink;
272-
},
273-
// rerun workflow
274-
async rerun() {
275-
await this.fetchPost(`${this.run.link}/rerun`);
276-
window.location.href = this.run.link;
277-
},
278267
// cancel a run
279268
cancelRun() {
280269
this.fetchPost(`${this.run.link}/cancel`);

web_src/js/features/common-global.js

Lines changed: 44 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.js';
88
import {svg} from '../svg.js';
99
import {hideElem, showElem, toggleElem} from '../utils/dom.js';
1010
import {htmlEscape} from 'escape-goat';
11-
import {createTippy, showTemporaryTooltip} from '../modules/tippy.js';
11+
import {showTemporaryTooltip} from '../modules/tippy.js';
1212
import {confirmModal} from './comp/ConfirmModal.js';
1313
import {showErrorToast} from '../modules/toast.js';
1414

@@ -64,9 +64,9 @@ export function initGlobalButtonClickOnEnter() {
6464
});
6565
}
6666

67-
// doRedirect does real redirection to bypass the browser's limitations of "location"
67+
// fetchActionDoRedirect does real redirection to bypass the browser's limitations of "location"
6868
// more details are in the backend's fetch-redirect handler
69-
function doRedirect(redirect) {
69+
function fetchActionDoRedirect(redirect) {
7070
const form = document.createElement('form');
7171
const input = document.createElement('input');
7272
form.method = 'post';
@@ -79,6 +79,33 @@ function doRedirect(redirect) {
7979
form.submit();
8080
}
8181

82+
async function fetchActionDoRequest(actionElem, url, opt) {
83+
try {
84+
const resp = await fetch(url, opt);
85+
if (resp.status === 200) {
86+
let {redirect} = await resp.json();
87+
redirect = redirect || actionElem.getAttribute('data-redirect');
88+
actionElem.classList.remove('dirty'); // remove the areYouSure check before reloading
89+
if (redirect) {
90+
fetchActionDoRedirect(redirect);
91+
} else {
92+
window.location.reload();
93+
}
94+
} else if (resp.status >= 400 && resp.status < 500) {
95+
const data = await resp.json();
96+
// the code was quite messy, sometimes the backend uses "err", sometimes it uses "error", and even "user_error"
97+
// but at the moment, as a new approach, we only use "errorMessage" here, backend can use JSONError() to respond.
98+
await showErrorToast(data.errorMessage || `server error: ${resp.status}`);
99+
} else {
100+
await showErrorToast(`server error: ${resp.status}`);
101+
}
102+
} catch (e) {
103+
console.error('error when doRequest', e);
104+
actionElem.classList.remove('is-loading', 'small-loading-icon');
105+
await showErrorToast(i18n.network_error);
106+
}
107+
}
108+
82109
async function formFetchAction(e) {
83110
if (!e.target.classList.contains('form-fetch-action')) return;
84111

@@ -115,50 +142,7 @@ async function formFetchAction(e) {
115142
reqOpt.body = formData;
116143
}
117144

118-
let errorTippy;
119-
const onError = (msg) => {
120-
formEl.classList.remove('is-loading', 'small-loading-icon');
121-
if (errorTippy) errorTippy.destroy();
122-
// TODO: use a better toast UI instead of the tippy. If the form height is large, the tippy position is not good
123-
errorTippy = createTippy(formEl, {
124-
content: msg,
125-
interactive: true,
126-
showOnCreate: true,
127-
hideOnClick: true,
128-
role: 'alert',
129-
theme: 'form-fetch-error',
130-
trigger: 'manual',
131-
arrow: false,
132-
});
133-
};
134-
135-
const doRequest = async () => {
136-
try {
137-
const resp = await fetch(reqUrl, reqOpt);
138-
if (resp.status === 200) {
139-
const {redirect} = await resp.json();
140-
formEl.classList.remove('dirty'); // remove the areYouSure check before reloading
141-
if (redirect) {
142-
doRedirect(redirect);
143-
} else {
144-
window.location.reload();
145-
}
146-
} else if (resp.status >= 400 && resp.status < 500) {
147-
const data = await resp.json();
148-
// the code was quite messy, sometimes the backend uses "err", sometimes it uses "error", and even "user_error"
149-
// but at the moment, as a new approach, we only use "errorMessage" here, backend can use JSONError() to respond.
150-
onError(data.errorMessage || `server error: ${resp.status}`);
151-
} else {
152-
onError(`server error: ${resp.status}`);
153-
}
154-
} catch (e) {
155-
console.error('error when doRequest', e);
156-
onError(i18n.network_error);
157-
}
158-
};
159-
160-
// TODO: add "confirm" support like "link-action" in the future
161-
await doRequest();
145+
await fetchActionDoRequest(formEl, reqUrl, reqOpt);
162146
}
163147

164148
export function initGlobalCommon() {
@@ -209,6 +193,7 @@ export function initGlobalCommon() {
209193
$('.tabular.menu .item').tab();
210194

211195
document.addEventListener('submit', formFetchAction);
196+
document.addEventListener('click', linkAction);
212197
}
213198

214199
export function initGlobalDropzone() {
@@ -269,41 +254,29 @@ export function initGlobalDropzone() {
269254
}
270255

271256
async function linkAction(e) {
272-
e.preventDefault();
273-
274257
// A "link-action" can post AJAX request to its "data-url"
275258
// Then the browser is redirected to: the "redirect" in response, or "data-redirect" attribute, or current URL by reloading.
276259
// If the "link-action" has "data-modal-confirm" attribute, a confirm modal dialog will be shown before taking action.
260+
const el = e.target.closest('.link-action');
261+
if (!el) return;
277262

278-
const $this = $(this);
279-
const redirect = $this.attr('data-redirect');
280-
281-
const doRequest = () => {
282-
$this.prop('disabled', true);
283-
$.post($this.attr('data-url'), {
284-
_csrf: csrfToken
285-
}).done((data) => {
286-
if (data && data.redirect) {
287-
window.location.href = data.redirect;
288-
} else if (redirect) {
289-
window.location.href = redirect;
290-
} else {
291-
window.location.reload();
292-
}
293-
}).always(() => {
294-
$this.prop('disabled', false);
295-
});
263+
e.preventDefault();
264+
const url = el.getAttribute('data-url');
265+
const doRequest = async () => {
266+
el.disabled = true;
267+
await fetchActionDoRequest(el, url, {method: 'POST', headers: {'X-Csrf-Token': csrfToken}});
268+
el.disabled = false;
296269
};
297270

298-
const modalConfirmContent = htmlEscape($this.attr('data-modal-confirm') || '');
271+
const modalConfirmContent = htmlEscape(el.getAttribute('data-modal-confirm') || '');
299272
if (!modalConfirmContent) {
300-
doRequest();
273+
await doRequest();
301274
return;
302275
}
303276

304-
const isRisky = $this.hasClass('red') || $this.hasClass('yellow') || $this.hasClass('orange') || $this.hasClass('negative');
277+
const isRisky = el.classList.contains('red') || el.classList.contains('yellow') || el.classList.contains('orange') || el.classList.contains('negative');
305278
if (await confirmModal({content: modalConfirmContent, buttonColor: isRisky ? 'orange' : 'green'})) {
306-
doRequest();
279+
await doRequest();
307280
}
308281
}
309282

@@ -354,7 +327,6 @@ export function initGlobalLinkActions() {
354327

355328
// Helpers.
356329
$('.delete-button').on('click', showDeletePopup);
357-
$('.link-action').on('click', linkAction);
358330
}
359331

360332
function initGlobalShowModal() {

0 commit comments

Comments
 (0)