fix(transactions): stop job-status polling when the job never starts
The re-evaluate and apply-rule pollers rescheduled on any non-terminal status, including 'pending'. The controllers seed a 'pending' entry before dispatch, so if the queue worker is down the job never runs, failed() never fires, and the client polls for the entire hour-long TTL with a spinner stuck at '0 of 0'. Give up after 30 consecutive 'pending' ticks (~30s at the 1s interval), mirroring the guard the AI-categorization poller already has. A long 'processing' run is unaffected — only a job that never leaves 'pending' trips the cap.
This commit is contained in:
parent
9a806c0565
commit
efe60282c8
|
|
@ -178,6 +178,7 @@ export function ApplyAutomationRuleFlow({
|
|||
const pollStatus = useCallback(
|
||||
async (jobId: string) => {
|
||||
const url = statusRoute(jobId).url;
|
||||
let pendingTicks = 0;
|
||||
const poll = async (): Promise<void> => {
|
||||
const res = await fetch(url, {
|
||||
headers: { Accept: 'application/json' },
|
||||
|
|
@ -202,6 +203,13 @@ export function ApplyAutomationRuleFlow({
|
|||
setApplying(false);
|
||||
return;
|
||||
}
|
||||
// The job never started (e.g. no queue worker running) — give
|
||||
// up instead of polling forever.
|
||||
if (data.status === 'pending' && ++pendingTicks > 30) {
|
||||
toast.error(__('Failed to apply rule to transactions.'));
|
||||
setApplying(false);
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
void poll();
|
||||
}, 1000);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export function useReEvaluateAllTransactions() {
|
|||
const jobId = bulkResponse.data.job_id;
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
let pendingTicks = 0;
|
||||
const poll = async () => {
|
||||
try {
|
||||
const statusResponse = await axios.get<{
|
||||
|
|
@ -47,6 +48,13 @@ export function useReEvaluateAllTransactions() {
|
|||
resolve();
|
||||
} else if (status === 'failed') {
|
||||
reject(new Error('Job failed'));
|
||||
} else if (
|
||||
status === 'pending' &&
|
||||
++pendingTicks > 30
|
||||
) {
|
||||
// The job never started (e.g. no queue worker
|
||||
// running) — give up instead of polling forever.
|
||||
reject(new Error('Job did not start'));
|
||||
} else {
|
||||
setTimeout(poll, 1000);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -940,6 +940,7 @@ export default function Transactions({
|
|||
const jobId = bulkResponse.data.job_id;
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
let pendingTicks = 0;
|
||||
const poll = async () => {
|
||||
try {
|
||||
const statusResponse = await axios.get<{
|
||||
|
|
@ -969,6 +970,13 @@ export default function Transactions({
|
|||
resolve();
|
||||
} else if (status === 'failed') {
|
||||
reject(new Error('Job failed'));
|
||||
} else if (
|
||||
status === 'pending' &&
|
||||
++pendingTicks > 30
|
||||
) {
|
||||
// The job never started (e.g. no queue worker
|
||||
// running) — give up instead of polling forever.
|
||||
reject(new Error('Job did not start'));
|
||||
} else {
|
||||
setTimeout(poll, 1000);
|
||||
}
|
||||
|
|
@ -1472,7 +1480,7 @@ export default function Transactions({
|
|||
handleDismissAiConsent
|
||||
}
|
||||
disabled={aiConsentSaving}
|
||||
className="opacity-20 hover:opacity-100 transition-all duration-300"
|
||||
className="opacity-20 transition-all duration-300 hover:opacity-100"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
aria-label={__('Dismiss')}
|
||||
|
|
|
|||
Loading…
Reference in New Issue