mirror of
https://github.com/go-gitea/gitea.git
synced 2026-09-08 14:03:24 +09:00
fix(issue): display error toast on batch action failures instead of reloading page (#38593)
Batch operations in the issues list (labels, milestones, projects, assignees, etc.) could fail silently because `updateIssuesMeta` caught and suppressed fetch errors internally. The caller would then proceed to reload the page, clearing all selected issue checkboxes and giving the impression that the operation had succeeded. - Remove updateIssuesMeta and use our "fetch-action" framework to handle errors and page reloading - Refactor backend code to use ctx.JSONError --------- Signed-off-by: Sudhanshu Singh <sudhanshuwriterblc@gmail.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
co-authored by
wxiaoguang
parent
ee10ae168c
commit
6ac19f6226
@@ -8,6 +8,7 @@ import {registerGlobalSelectorFunc} from '../modules/observer.ts';
|
||||
import {Idiomorph} from 'idiomorph';
|
||||
import {parseDom} from '../utils.ts';
|
||||
import {html} from '../utils/html.ts';
|
||||
import type {RequestData} from '../types.ts';
|
||||
|
||||
const {appSubUrl, runModeIsProd} = window.config;
|
||||
|
||||
@@ -15,16 +16,16 @@ type FetchActionOpts = {
|
||||
method: string;
|
||||
url: string;
|
||||
headers?: HeadersInit;
|
||||
body?: FormData;
|
||||
data?: RequestData;
|
||||
formSubmitter?: HTMLElement | null;
|
||||
|
||||
// pseudo selectors/commands to update the current page with the response text when the response is text (html)
|
||||
// e.g.: "$this", "$innerHTML", "$closest(tr) td .the-class", "$body #the-id"
|
||||
successSync: string;
|
||||
successSync?: string;
|
||||
|
||||
// the loading indicator element selector, it uses the same syntax as "data-fetch-sync" to find the element(s)
|
||||
// empty means no loading indicator, "$this" means the element itself
|
||||
loadingIndicator: string;
|
||||
loadingIndicator?: string;
|
||||
};
|
||||
|
||||
// fetchActionDoRedirect does real redirection to bypass the browser's limitations of "location"
|
||||
@@ -120,10 +121,10 @@ function buildFetchActionUrl(el: HTMLElement, opt: FetchActionOpts) {
|
||||
return url;
|
||||
}
|
||||
|
||||
async function performActionRequest(el: HTMLElement, opt: FetchActionOpts) {
|
||||
// Use "fetch" to send a request and handle the error cases, return the success response and let caller handle
|
||||
export async function performFetchActionRequest(el: HTMLElement, opt: FetchActionOpts): Promise<Response | null> {
|
||||
const attrIsLoading = 'data-fetch-is-loading';
|
||||
if (el.getAttribute(attrIsLoading)) return;
|
||||
if (!await confirmFetchAction(opt.formSubmitter ?? el)) return;
|
||||
if (el.getAttribute(attrIsLoading)) return null;
|
||||
|
||||
el.setAttribute(attrIsLoading, 'true');
|
||||
toggleLoadingIndicator(el, opt, true);
|
||||
@@ -132,11 +133,8 @@ async function performActionRequest(el: HTMLElement, opt: FetchActionOpts) {
|
||||
const url = buildFetchActionUrl(el, opt);
|
||||
const headers = new Headers(opt.headers);
|
||||
headers.set('X-Gitea-Fetch-Action', '1');
|
||||
const resp = await request(url, {method: opt.method, body: opt.body, headers});
|
||||
if (resp.ok) {
|
||||
await handleFetchActionSuccess(el, opt, resp);
|
||||
return;
|
||||
}
|
||||
const resp = await request(url, {method: opt.method, data: opt.data, headers});
|
||||
if (resp.ok) return resp;
|
||||
await handleFetchActionError(resp);
|
||||
} catch (err) {
|
||||
if (errorName(err) !== 'AbortError') {
|
||||
@@ -147,6 +145,14 @@ async function performActionRequest(el: HTMLElement, opt: FetchActionOpts) {
|
||||
toggleLoadingIndicator(el, opt, false);
|
||||
el.removeAttribute(attrIsLoading);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Use "fetch" to send a request and fully handle its response
|
||||
export async function performFetchAction(el: HTMLElement, opt: FetchActionOpts) {
|
||||
if (!await confirmFetchAction(opt.formSubmitter ?? el)) return;
|
||||
const resp = await performFetchActionRequest(el, opt);
|
||||
if (resp) await handleFetchActionSuccess(el, opt, resp);
|
||||
}
|
||||
|
||||
type SubmitFormFetchActionOpts = {
|
||||
@@ -181,7 +187,7 @@ function prepareFormFetchActionOpts(formEl: HTMLFormElement, opts: SubmitFormFet
|
||||
return {
|
||||
method: formMethodUpper,
|
||||
url: reqUrl,
|
||||
body: reqBody,
|
||||
data: reqBody,
|
||||
formSubmitter: opts.formSubmitter,
|
||||
loadingIndicator: '$this', // for form submit, by default, the loading indicator is the whole form
|
||||
successSync: formEl.getAttribute('data-fetch-sync') ?? '', // by default, no fetch sync for form submit
|
||||
@@ -190,7 +196,7 @@ function prepareFormFetchActionOpts(formEl: HTMLFormElement, opts: SubmitFormFet
|
||||
|
||||
export async function submitFormFetchAction(formEl: HTMLFormElement, opts: SubmitFormFetchActionOpts = {}) {
|
||||
hideToastsAll();
|
||||
await performActionRequest(formEl, prepareFormFetchActionOpts(formEl, opts));
|
||||
await performFetchAction(formEl, prepareFormFetchActionOpts(formEl, opts));
|
||||
}
|
||||
|
||||
async function confirmFetchAction(el: HTMLElement) {
|
||||
@@ -221,7 +227,7 @@ async function confirmFetchAction(el: HTMLElement) {
|
||||
|
||||
async function performLinkFetchAction(el: HTMLElement) {
|
||||
hideToastsAll();
|
||||
await performActionRequest(el, {
|
||||
await performFetchAction(el, {
|
||||
method: el.getAttribute('data-fetch-method') || 'POST', // by default, the method is POST for link-action
|
||||
url: el.getAttribute('data-url')!,
|
||||
loadingIndicator: el.getAttribute('data-fetch-indicator') ?? '$this', // by default, the link-action itself is the loading indicator
|
||||
@@ -237,7 +243,7 @@ export async function performFetchActionTrigger(el: HTMLElement, triggerType: Fe
|
||||
const defaultLoadingIndicator = isUserInitiated ? '$this' : '';
|
||||
|
||||
if (isUserInitiated) hideToastsAll();
|
||||
await performActionRequest(el, {
|
||||
await performFetchAction(el, {
|
||||
method: el.getAttribute('data-fetch-method') || 'GET', // by default, the method is GET for fetch trigger action
|
||||
url: el.getAttribute('data-fetch-url')!,
|
||||
loadingIndicator: el.getAttribute('data-fetch-indicator') ?? defaultLoadingIndicator,
|
||||
|
||||
Reference in New Issue
Block a user