feat(web): clear failed jobs (#5423)
* add clear failed jobs button * refactor: clean up code * chore: open api --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
parent
933c24ea6f
commit
982183600d
13 changed files with 55 additions and 9 deletions
3
cli/src/api/open-api/api.ts
generated
3
cli/src/api/open-api/api.ts
generated
|
@ -1785,7 +1785,8 @@ export const JobCommand = {
|
|||
Start: 'start',
|
||||
Pause: 'pause',
|
||||
Resume: 'resume',
|
||||
Empty: 'empty'
|
||||
Empty: 'empty',
|
||||
ClearFailed: 'clear-failed'
|
||||
} as const;
|
||||
|
||||
export type JobCommand = typeof JobCommand[keyof typeof JobCommand];
|
||||
|
|
3
mobile/openapi/lib/model/job_command.dart
generated
3
mobile/openapi/lib/model/job_command.dart
generated
|
@ -27,6 +27,7 @@ class JobCommand {
|
|||
static const pause = JobCommand._(r'pause');
|
||||
static const resume = JobCommand._(r'resume');
|
||||
static const empty = JobCommand._(r'empty');
|
||||
static const clearFailed = JobCommand._(r'clear-failed');
|
||||
|
||||
/// List of all possible values in this [enum][JobCommand].
|
||||
static const values = <JobCommand>[
|
||||
|
@ -34,6 +35,7 @@ class JobCommand {
|
|||
pause,
|
||||
resume,
|
||||
empty,
|
||||
clearFailed,
|
||||
];
|
||||
|
||||
static JobCommand? fromJson(dynamic value) => JobCommandTypeTransformer().decode(value);
|
||||
|
@ -76,6 +78,7 @@ class JobCommandTypeTransformer {
|
|||
case r'pause': return JobCommand.pause;
|
||||
case r'resume': return JobCommand.resume;
|
||||
case r'empty': return JobCommand.empty;
|
||||
case r'clear-failed': return JobCommand.clearFailed;
|
||||
default:
|
||||
if (!allowNull) {
|
||||
throw ArgumentError('Unknown enum value to decode: $data');
|
||||
|
|
|
@ -7548,7 +7548,8 @@
|
|||
"start",
|
||||
"pause",
|
||||
"resume",
|
||||
"empty"
|
||||
"empty",
|
||||
"clear-failed"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
@ -18,6 +18,7 @@ export enum JobCommand {
|
|||
PAUSE = 'pause',
|
||||
RESUME = 'resume',
|
||||
EMPTY = 'empty',
|
||||
CLEAR_FAILED = 'clear-failed',
|
||||
}
|
||||
|
||||
export enum JobName {
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
ISystemConfigRepository,
|
||||
JobHandler,
|
||||
JobItem,
|
||||
QueueCleanType,
|
||||
} from '../repositories';
|
||||
import { FeatureFlag, SystemConfigCore } from '../system-config/system-config.core';
|
||||
import { JobCommand, JobName, QueueName } from './job.constants';
|
||||
|
@ -49,6 +50,11 @@ export class JobService {
|
|||
case JobCommand.EMPTY:
|
||||
await this.jobRepository.empty(queueName);
|
||||
break;
|
||||
|
||||
case JobCommand.CLEAR_FAILED:
|
||||
const failedJobs = await this.jobRepository.clear(queueName, QueueCleanType.FAILED);
|
||||
this.logger.debug(`Cleared failed jobs: ${failedJobs}`);
|
||||
break;
|
||||
}
|
||||
|
||||
return this.getJobStatus(queueName);
|
||||
|
|
|
@ -26,6 +26,10 @@ export interface QueueStatus {
|
|||
isPaused: boolean;
|
||||
}
|
||||
|
||||
export enum QueueCleanType {
|
||||
FAILED = 'failed',
|
||||
}
|
||||
|
||||
export type JobItem =
|
||||
// Transcoding
|
||||
| { name: JobName.QUEUE_VIDEO_CONVERSION; data: IBaseJob }
|
||||
|
@ -120,6 +124,7 @@ export interface IJobRepository {
|
|||
pause(name: QueueName): Promise<void>;
|
||||
resume(name: QueueName): Promise<void>;
|
||||
empty(name: QueueName): Promise<void>;
|
||||
clear(name: QueueName, type: QueueCleanType): Promise<string[]>;
|
||||
getQueueStatus(name: QueueName): Promise<QueueStatus>;
|
||||
getJobCounts(name: QueueName): Promise<JobCounts>;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,13 @@
|
|||
import { IJobRepository, JobCounts, JobItem, JobName, JOBS_TO_QUEUE, QueueName, QueueStatus } from '@app/domain';
|
||||
import {
|
||||
IJobRepository,
|
||||
JobCounts,
|
||||
JobItem,
|
||||
JobName,
|
||||
JOBS_TO_QUEUE,
|
||||
QueueCleanType,
|
||||
QueueName,
|
||||
QueueStatus,
|
||||
} from '@app/domain';
|
||||
import { getQueueToken } from '@nestjs/bullmq';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { ModuleRef } from '@nestjs/core';
|
||||
|
@ -91,6 +100,10 @@ export class JobRepository implements IJobRepository {
|
|||
return this.getQueue(name).drain();
|
||||
}
|
||||
|
||||
clear(name: QueueName, type: QueueCleanType) {
|
||||
return this.getQueue(name).clean(0, 1000, type);
|
||||
}
|
||||
|
||||
getJobCounts(name: QueueName): Promise<JobCounts> {
|
||||
return this.getQueue(name).getJobCounts(
|
||||
'active',
|
||||
|
|
|
@ -13,5 +13,6 @@ export const newJobRepositoryMock = (): jest.Mocked<IJobRepository> => {
|
|||
queue: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||
getQueueStatus: jest.fn(),
|
||||
getJobCounts: jest.fn(),
|
||||
clear: jest.fn(),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -76,6 +76,7 @@ export const testApp = {
|
|||
getQueueStatus: jest.fn(),
|
||||
getJobCounts: jest.fn(),
|
||||
pause: jest.fn(),
|
||||
clear: jest.fn(),
|
||||
} as IJobRepository)
|
||||
.compile();
|
||||
|
||||
|
|
3
web/src/api/open-api/api.ts
generated
3
web/src/api/open-api/api.ts
generated
|
@ -1785,7 +1785,8 @@ export const JobCommand = {
|
|||
Start: 'start',
|
||||
Pause: 'pause',
|
||||
Resume: 'resume',
|
||||
Empty: 'empty'
|
||||
Empty: 'empty',
|
||||
ClearFailed: 'clear-failed'
|
||||
} as const;
|
||||
|
||||
export type JobCommand = typeof JobCommand[keyof typeof JobCommand];
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
import Badge from '$lib/components/elements/badge.svelte';
|
||||
import JobTileButton from './job-tile-button.svelte';
|
||||
import JobTileStatus from './job-tile-status.svelte';
|
||||
import Button from '$lib/components/elements/buttons/button.svelte';
|
||||
import Icon from '$lib/components/elements/icon.svelte';
|
||||
import {
|
||||
mdiAlertCircle,
|
||||
|
@ -55,12 +56,23 @@
|
|||
<div class="flex gap-2">
|
||||
{#if jobCounts.failed > 0}
|
||||
<Badge color="primary">
|
||||
<span class="text-sm">
|
||||
{jobCounts.failed.toLocaleString($locale)} failed
|
||||
</span>
|
||||
<Button
|
||||
size="tiny"
|
||||
shadow={false}
|
||||
on:click={() => dispatch('command', { command: JobCommand.ClearFailed, force: false })}
|
||||
>
|
||||
<Icon path={mdiClose} size="18" />
|
||||
</Button>
|
||||
</Badge>
|
||||
{/if}
|
||||
{#if jobCounts.delayed > 0}
|
||||
{#if jobCounts.delayed > 0 || true}
|
||||
<Badge color="secondary">
|
||||
<span class="text-sm">
|
||||
{jobCounts.delayed.toLocaleString($locale)} delayed
|
||||
</span>
|
||||
</Badge>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
</script>
|
||||
|
||||
<span
|
||||
class="inline-block h-min whitespace-nowrap px-4 pb-[0.55em] pt-[0.55em] text-center align-baseline text-xs leading-none {colorClasses[
|
||||
class="inline-block h-min whitespace-nowrap px-3 py-1 text-center align-baseline text-xs leading-none {colorClasses[
|
||||
color
|
||||
]}"
|
||||
class:rounded-md={rounded === true}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
| 'transparent-gray'
|
||||
| 'dark-gray'
|
||||
| 'overlay-primary';
|
||||
export type Size = 'icon' | 'link' | 'sm' | 'base' | 'lg';
|
||||
export type Size = 'tiny' | 'icon' | 'link' | 'sm' | 'base' | 'lg';
|
||||
export type Rounded = 'lg' | '3xl' | 'full' | false;
|
||||
export type Shadow = 'md' | false;
|
||||
</script>
|
||||
|
@ -46,6 +46,7 @@
|
|||
};
|
||||
|
||||
const sizeClasses: Record<Size, string> = {
|
||||
tiny: 'p-0 ml-2 mr-0 align-top',
|
||||
icon: 'p-2.5',
|
||||
link: 'p-2 font-medium',
|
||||
sm: 'px-4 py-2 text-sm font-medium',
|
||||
|
|
Loading…
Reference in a new issue