Add queue management dashboard (#15202)
Adds a comprehensive queue management interface to the admin panel for viewing and managing background jobs. **Features:** - Queue detail pages showing paginated job lists (50 per page) - Filter jobs by state: completed, failed, active, waiting, delayed, paused - Checkbox selection with bulk actions (delete jobs, retry failed jobs) - Per-job dropdown menu for individual retry/delete - Expandable rows showing error messages, stack traces, and job data - Relative timestamps with hover tooltips - Display attempt counts on failed jobs - Dynamic retention policy info from backend **Changes:** - Backend: New AdminPanelQueueService with GraphQL endpoints for job listing, retry, and delete - Frontend: Queue detail page with QueueJobsTable component - Updated retention policy: completed jobs kept 4 hours, failed jobs kept 7 days (max 1000 each) - Added JobState enum for type safety <img width="634" height="696" alt="Screenshot_2025-10-20_at_11 45 25" src="https://github.com/user-attachments/assets/c67bcd27-26cf-47f5-9575-3cd5684d006b" /> <img width="484" height="680" alt="Screenshot_2025-10-20_at_11 45 14" src="https://github.com/user-attachments/assets/68725cc6-b3ec-4098-99ca-f9a717d6f8f1" /> <img width="490" height="643" alt="Screenshot_2025-10-20_at_11 45 05" src="https://github.com/user-attachments/assets/b68a5809-33ff-4452-b48b-741aff7f1dd6" /> <img width="685" height="662" alt="Screenshot 2025-10-20 at 13 15 01" src="https://github.com/user-attachments/assets/eeb5207b-de5c-4b18-bdde-392892053dab" />
This commit is contained in:
+4
-2
@@ -5,7 +5,7 @@ import { type Redis } from 'ioredis';
|
||||
|
||||
import { AdminPanelHealthService } from 'src/engine/core-modules/admin-panel/admin-panel-health.service';
|
||||
import { HEALTH_INDICATORS } from 'src/engine/core-modules/admin-panel/constants/health-indicators.constants';
|
||||
import { type SystemHealth } from 'src/engine/core-modules/admin-panel/dtos/system-health.dto';
|
||||
import { type SystemHealthDTO } from 'src/engine/core-modules/admin-panel/dtos/system-health.dto';
|
||||
import { AdminPanelHealthServiceStatus } from 'src/engine/core-modules/admin-panel/enums/admin-panel-health-service-status.enum';
|
||||
import { QueueMetricsTimeRange } from 'src/engine/core-modules/admin-panel/enums/queue-metrics-time-range.enum';
|
||||
import { HEALTH_ERROR_MESSAGES } from 'src/engine/core-modules/health/constants/health-error-messages.constants';
|
||||
@@ -40,6 +40,7 @@ describe('AdminPanelHealthService', () => {
|
||||
appHealth = { isHealthy: jest.fn() } as any;
|
||||
redisClient = {
|
||||
getClient: jest.fn().mockReturnValue({} as Redis),
|
||||
getQueueClient: jest.fn().mockReturnValue({} as Redis),
|
||||
} as any;
|
||||
twentyConfigService = { get: jest.fn() } as any;
|
||||
|
||||
@@ -150,7 +151,7 @@ describe('AdminPanelHealthService', () => {
|
||||
|
||||
const result = await service.getSystemHealthStatus();
|
||||
|
||||
const expected: SystemHealth = {
|
||||
const expected: SystemHealthDTO = {
|
||||
services: [
|
||||
{
|
||||
...HEALTH_INDICATORS[HealthIndicatorId.database],
|
||||
@@ -372,6 +373,7 @@ describe('AdminPanelHealthService', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
redisClient.getClient.mockReturnValue({} as Redis);
|
||||
redisClient.getQueueClient.mockReturnValue({} as Redis);
|
||||
(Queue as unknown as jest.Mock).mockImplementation(() => mockQueue);
|
||||
});
|
||||
|
||||
|
||||
+8
-8
@@ -7,9 +7,9 @@ import {
|
||||
import { Queue } from 'bullmq';
|
||||
|
||||
import { HEALTH_INDICATORS } from 'src/engine/core-modules/admin-panel/constants/health-indicators.constants';
|
||||
import { type AdminPanelHealthServiceData } from 'src/engine/core-modules/admin-panel/dtos/admin-panel-health-service-data.dto';
|
||||
import { type QueueMetricsData } from 'src/engine/core-modules/admin-panel/dtos/queue-metrics-data.dto';
|
||||
import { type SystemHealth } from 'src/engine/core-modules/admin-panel/dtos/system-health.dto';
|
||||
import { type AdminPanelHealthServiceDataDTO } from 'src/engine/core-modules/admin-panel/dtos/admin-panel-health-service-data.dto';
|
||||
import { type QueueMetricsDataDTO } from 'src/engine/core-modules/admin-panel/dtos/queue-metrics-data.dto';
|
||||
import { type SystemHealthDTO } from 'src/engine/core-modules/admin-panel/dtos/system-health.dto';
|
||||
import { AdminPanelHealthServiceStatus } from 'src/engine/core-modules/admin-panel/enums/admin-panel-health-service-status.enum';
|
||||
import { QueueMetricsTimeRange } from 'src/engine/core-modules/admin-panel/enums/queue-metrics-time-range.enum';
|
||||
import { HealthIndicatorId } from 'src/engine/core-modules/health/enums/health-indicator-id.enum';
|
||||
@@ -117,7 +117,7 @@ export class AdminPanelHealthService {
|
||||
|
||||
async getIndicatorHealthStatus(
|
||||
indicatorId: HealthIndicatorId,
|
||||
): Promise<AdminPanelHealthServiceData> {
|
||||
): Promise<AdminPanelHealthServiceDataDTO> {
|
||||
const healthIndicator = this.healthIndicators[indicatorId];
|
||||
|
||||
if (!healthIndicator) {
|
||||
@@ -145,7 +145,7 @@ export class AdminPanelHealthService {
|
||||
return indicatorStatus;
|
||||
}
|
||||
|
||||
async getSystemHealthStatus(): Promise<SystemHealth> {
|
||||
async getSystemHealthStatus(): Promise<SystemHealthDTO> {
|
||||
const [
|
||||
databaseResult,
|
||||
redisResult,
|
||||
@@ -198,8 +198,8 @@ export class AdminPanelHealthService {
|
||||
async getQueueMetrics(
|
||||
queueName: MessageQueue,
|
||||
timeRange: QueueMetricsTimeRange = QueueMetricsTimeRange.OneDay,
|
||||
): Promise<QueueMetricsData> {
|
||||
const redis = this.redisClient.getClient();
|
||||
): Promise<QueueMetricsDataDTO> {
|
||||
const redis = this.redisClient.getQueueClient();
|
||||
const queue = new Queue(queueName, { connection: redis });
|
||||
|
||||
try {
|
||||
@@ -325,7 +325,7 @@ export class AdminPanelHealthService {
|
||||
timeRange: QueueMetricsTimeRange,
|
||||
queueName: MessageQueue,
|
||||
queueDetails: WorkerQueueHealth | null,
|
||||
): QueueMetricsData {
|
||||
): QueueMetricsDataDTO {
|
||||
try {
|
||||
return {
|
||||
queueName,
|
||||
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { Queue } from 'bullmq';
|
||||
import { type JobState as BullMQJobState } from 'bullmq/dist/esm/types';
|
||||
|
||||
import {
|
||||
bullMQToJobStateEnum,
|
||||
JobStateEnum,
|
||||
jobStateEnumToBullMQ,
|
||||
} from 'src/engine/core-modules/admin-panel/enums/job-state.enum';
|
||||
import { InternalServerError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||
import { QUEUE_RETENTION } from 'src/engine/core-modules/message-queue/constants/queue-retention.constants';
|
||||
import { type MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
import { RedisClientService } from 'src/engine/core-modules/redis-client/redis-client.service';
|
||||
|
||||
type JobOperationResult = {
|
||||
jobId: string;
|
||||
success: boolean;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class AdminPanelQueueService {
|
||||
constructor(private readonly redisClient: RedisClientService) {}
|
||||
|
||||
async getQueueJobs(
|
||||
queueName: MessageQueue,
|
||||
state: JobStateEnum,
|
||||
limit = 50,
|
||||
offset = 0,
|
||||
) {
|
||||
const redis = this.redisClient.getQueueClient();
|
||||
const queue = new Queue(queueName, { connection: redis });
|
||||
|
||||
try {
|
||||
const validLimit = Math.min(Math.max(1, limit), 200);
|
||||
const validOffset = Math.max(0, offset);
|
||||
|
||||
const start = validOffset;
|
||||
const end = validOffset + validLimit - 1;
|
||||
|
||||
// Convert GraphQL enum to BullMQ state
|
||||
const bullMQState = jobStateEnumToBullMQ[state];
|
||||
const jobs = await queue.getJobs([bullMQState], start, end, false);
|
||||
|
||||
const transformedJobs = await Promise.all(
|
||||
jobs.map(async (job) => {
|
||||
const jobBullMQState = (await job.getState()) as BullMQJobState;
|
||||
|
||||
return {
|
||||
id: job.id!,
|
||||
name: job.name,
|
||||
data: job.data,
|
||||
state: bullMQToJobStateEnum[jobBullMQState],
|
||||
timestamp: job.timestamp,
|
||||
failedReason: job.failedReason,
|
||||
processedOn: job.processedOn,
|
||||
finishedOn: job.finishedOn,
|
||||
attemptsMade: job.attemptsMade,
|
||||
returnValue: job.returnValue,
|
||||
logs: undefined,
|
||||
stackTrace: job.stackTrace,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const hasMore = jobs.length === validLimit;
|
||||
|
||||
const jobCounts = await queue.getJobCounts(
|
||||
'completed',
|
||||
'failed',
|
||||
'active',
|
||||
'waiting',
|
||||
'delayed',
|
||||
'prioritized',
|
||||
'waiting-children',
|
||||
);
|
||||
|
||||
const totalCountForState = (() => {
|
||||
switch (state) {
|
||||
case JobStateEnum.COMPLETED:
|
||||
return jobCounts.completed ?? 0;
|
||||
case JobStateEnum.FAILED:
|
||||
return jobCounts.failed ?? 0;
|
||||
case JobStateEnum.ACTIVE:
|
||||
return jobCounts.active ?? 0;
|
||||
case JobStateEnum.WAITING:
|
||||
return jobCounts.waiting ?? 0;
|
||||
case JobStateEnum.DELAYED:
|
||||
return jobCounts.delayed ?? 0;
|
||||
case JobStateEnum.PRIORITIZED:
|
||||
return jobCounts.prioritized ?? 0;
|
||||
case JobStateEnum.WAITING_CHILDREN:
|
||||
return jobCounts['waiting-children'] ?? 0;
|
||||
default:
|
||||
return jobs.length;
|
||||
}
|
||||
})();
|
||||
|
||||
return {
|
||||
jobs: transformedJobs,
|
||||
count: jobs.length,
|
||||
totalCount: totalCountForState,
|
||||
hasMore,
|
||||
retentionConfig: { ...QUEUE_RETENTION },
|
||||
};
|
||||
} catch (error) {
|
||||
throw new InternalServerError(
|
||||
`Failed to fetch jobs from queue ${queueName}: ${error instanceof Error ? error.message : String(error)}`,
|
||||
{
|
||||
userFriendlyMessage: msg`Failed to load queue jobs. Please try again later.`,
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
await queue.close();
|
||||
}
|
||||
}
|
||||
|
||||
async retryJobs(
|
||||
queueName: MessageQueue,
|
||||
jobIds: string[],
|
||||
): Promise<{
|
||||
retriedCount: number;
|
||||
results: JobOperationResult[];
|
||||
}> {
|
||||
const redis = this.redisClient.getQueueClient();
|
||||
const queue = new Queue(queueName, { connection: redis });
|
||||
|
||||
try {
|
||||
if (jobIds.length === 0) {
|
||||
await queue.retryJobs({ state: 'failed' });
|
||||
|
||||
return { retriedCount: -1, results: [] };
|
||||
}
|
||||
|
||||
const results: JobOperationResult[] = [];
|
||||
let retriedCount = 0;
|
||||
|
||||
for (const jobId of jobIds) {
|
||||
const job = await queue.getJob(jobId);
|
||||
|
||||
if (!job) {
|
||||
results.push({
|
||||
jobId,
|
||||
success: false,
|
||||
error: 'Job not found',
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const state = await job.getState();
|
||||
|
||||
if (state !== 'failed') {
|
||||
results.push({
|
||||
jobId,
|
||||
success: false,
|
||||
error: `Job is not in failed state (current state: ${state})`,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
await job.retry();
|
||||
retriedCount++;
|
||||
results.push({
|
||||
jobId,
|
||||
success: true,
|
||||
});
|
||||
} catch (error) {
|
||||
results.push({
|
||||
jobId,
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { retriedCount, results };
|
||||
} catch (error) {
|
||||
throw new InternalServerError(
|
||||
`Failed to retry jobs in queue ${queueName}: ${error instanceof Error ? error.message : String(error)}`,
|
||||
{
|
||||
userFriendlyMessage: msg`Failed to retry jobs. Please try again later.`,
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
await queue.close();
|
||||
}
|
||||
}
|
||||
|
||||
async deleteJobs(
|
||||
queueName: MessageQueue,
|
||||
jobIds: string[],
|
||||
): Promise<{
|
||||
deletedCount: number;
|
||||
results: JobOperationResult[];
|
||||
}> {
|
||||
const redis = this.redisClient.getQueueClient();
|
||||
const queue = new Queue(queueName, { connection: redis });
|
||||
|
||||
try {
|
||||
const results: JobOperationResult[] = [];
|
||||
let deletedCount = 0;
|
||||
|
||||
for (const jobId of jobIds) {
|
||||
const job = await queue.getJob(jobId);
|
||||
|
||||
if (!job) {
|
||||
results.push({
|
||||
jobId,
|
||||
success: false,
|
||||
error: 'Job not found',
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
await job.remove();
|
||||
deletedCount++;
|
||||
results.push({
|
||||
jobId,
|
||||
success: true,
|
||||
});
|
||||
} catch (error) {
|
||||
results.push({
|
||||
jobId,
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { deletedCount, results };
|
||||
} catch (error) {
|
||||
throw new InternalServerError(
|
||||
`Failed to delete jobs in queue ${queueName}: ${error instanceof Error ? error.message : String(error)}`,
|
||||
{
|
||||
userFriendlyMessage: msg`Failed to delete jobs. Please try again later.`,
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
await queue.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { TerminusModule } from '@nestjs/terminus';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { AdminPanelHealthService } from 'src/engine/core-modules/admin-panel/admin-panel-health.service';
|
||||
import { AdminPanelQueueService } from 'src/engine/core-modules/admin-panel/admin-panel-queue.service';
|
||||
import { AdminPanelResolver } from 'src/engine/core-modules/admin-panel/admin-panel.resolver';
|
||||
import { AdminPanelService } from 'src/engine/core-modules/admin-panel/admin-panel.service';
|
||||
import { AuditModule } from 'src/engine/core-modules/audit/audit.module';
|
||||
@@ -32,7 +33,12 @@ import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permi
|
||||
ImpersonationModule,
|
||||
PermissionsModule,
|
||||
],
|
||||
providers: [AdminPanelResolver, AdminPanelService, AdminPanelHealthService],
|
||||
providers: [
|
||||
AdminPanelResolver,
|
||||
AdminPanelService,
|
||||
AdminPanelHealthService,
|
||||
AdminPanelQueueService,
|
||||
],
|
||||
exports: [AdminPanelService],
|
||||
})
|
||||
export class AdminPanelModule {}
|
||||
|
||||
+71
-17
@@ -1,17 +1,22 @@
|
||||
import { UseFilters, UseGuards, UsePipes } from '@nestjs/common';
|
||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
import { Args, Int, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import GraphQLJSON from 'graphql-type-json';
|
||||
|
||||
import { AdminPanelHealthService } from 'src/engine/core-modules/admin-panel/admin-panel-health.service';
|
||||
import { AdminPanelQueueService } from 'src/engine/core-modules/admin-panel/admin-panel-queue.service';
|
||||
import { AdminPanelService } from 'src/engine/core-modules/admin-panel/admin-panel.service';
|
||||
import { ConfigVariable } from 'src/engine/core-modules/admin-panel/dtos/config-variable.dto';
|
||||
import { ConfigVariableDTO } from 'src/engine/core-modules/admin-panel/dtos/config-variable.dto';
|
||||
import { ConfigVariablesOutput } from 'src/engine/core-modules/admin-panel/dtos/config-variables.output';
|
||||
import { SystemHealth } from 'src/engine/core-modules/admin-panel/dtos/system-health.dto';
|
||||
import { DeleteJobsResponseDTO } from 'src/engine/core-modules/admin-panel/dtos/delete-jobs-response.dto';
|
||||
import { QueueJobsResponseDTO } from 'src/engine/core-modules/admin-panel/dtos/queue-jobs-response.dto';
|
||||
import { RetryJobsResponseDTO } from 'src/engine/core-modules/admin-panel/dtos/retry-jobs-response.dto';
|
||||
import { SystemHealthDTO } from 'src/engine/core-modules/admin-panel/dtos/system-health.dto';
|
||||
import { UpdateWorkspaceFeatureFlagInput } from 'src/engine/core-modules/admin-panel/dtos/update-workspace-feature-flag.input';
|
||||
import { UserLookup } from 'src/engine/core-modules/admin-panel/dtos/user-lookup.entity';
|
||||
import { UserLookupInput } from 'src/engine/core-modules/admin-panel/dtos/user-lookup.input';
|
||||
import { VersionInfo } from 'src/engine/core-modules/admin-panel/dtos/version-info.dto';
|
||||
import { VersionInfoDTO } from 'src/engine/core-modules/admin-panel/dtos/version-info.dto';
|
||||
import { JobStateEnum } from 'src/engine/core-modules/admin-panel/enums/job-state.enum';
|
||||
import { QueueMetricsTimeRange } from 'src/engine/core-modules/admin-panel/enums/queue-metrics-time-range.enum';
|
||||
import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter';
|
||||
import { FeatureFlagException } from 'src/engine/core-modules/feature-flag/feature-flag.exception';
|
||||
@@ -29,8 +34,8 @@ import { ServerLevelImpersonateGuard } from 'src/engine/guards/server-level-impe
|
||||
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
|
||||
import { AdminPanelHealthServiceData } from './dtos/admin-panel-health-service-data.dto';
|
||||
import { QueueMetricsData } from './dtos/queue-metrics-data.dto';
|
||||
import { AdminPanelHealthServiceDataDTO } from './dtos/admin-panel-health-service-data.dto';
|
||||
import { QueueMetricsDataDTO } from './dtos/queue-metrics-data.dto';
|
||||
|
||||
@UsePipes(ResolverValidationPipe)
|
||||
@Resolver()
|
||||
@@ -43,6 +48,7 @@ export class AdminPanelResolver {
|
||||
constructor(
|
||||
private adminService: AdminPanelService,
|
||||
private adminPanelHealthService: AdminPanelHealthService,
|
||||
private adminPanelQueueService: AdminPanelQueueService,
|
||||
private featureFlagService: FeatureFlagService,
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
) {}
|
||||
@@ -84,34 +90,34 @@ export class AdminPanelResolver {
|
||||
}
|
||||
|
||||
@UseGuards(WorkspaceAuthGuard, UserAuthGuard, AdminPanelGuard)
|
||||
@Query(() => SystemHealth)
|
||||
async getSystemHealthStatus(): Promise<SystemHealth> {
|
||||
@Query(() => SystemHealthDTO)
|
||||
async getSystemHealthStatus(): Promise<SystemHealthDTO> {
|
||||
return this.adminPanelHealthService.getSystemHealthStatus();
|
||||
}
|
||||
|
||||
@UseGuards(WorkspaceAuthGuard, UserAuthGuard, AdminPanelGuard)
|
||||
@Query(() => AdminPanelHealthServiceData)
|
||||
@Query(() => AdminPanelHealthServiceDataDTO)
|
||||
async getIndicatorHealthStatus(
|
||||
@Args('indicatorId', {
|
||||
type: () => HealthIndicatorId,
|
||||
})
|
||||
indicatorId: HealthIndicatorId,
|
||||
): Promise<AdminPanelHealthServiceData> {
|
||||
): Promise<AdminPanelHealthServiceDataDTO> {
|
||||
return this.adminPanelHealthService.getIndicatorHealthStatus(indicatorId);
|
||||
}
|
||||
|
||||
@UseGuards(WorkspaceAuthGuard, UserAuthGuard, AdminPanelGuard)
|
||||
@Query(() => QueueMetricsData)
|
||||
@Query(() => QueueMetricsDataDTO)
|
||||
async getQueueMetrics(
|
||||
@Args('queueName', { type: () => String })
|
||||
queueName: string,
|
||||
@Args('timeRange', {
|
||||
nullable: true,
|
||||
defaultValue: QueueMetricsTimeRange.OneDay,
|
||||
defaultValue: QueueMetricsTimeRange.OneHour,
|
||||
type: () => QueueMetricsTimeRange,
|
||||
})
|
||||
timeRange: QueueMetricsTimeRange = QueueMetricsTimeRange.OneHour,
|
||||
): Promise<QueueMetricsData> {
|
||||
): Promise<QueueMetricsDataDTO> {
|
||||
return await this.adminPanelHealthService.getQueueMetrics(
|
||||
queueName as MessageQueue,
|
||||
timeRange,
|
||||
@@ -119,16 +125,16 @@ export class AdminPanelResolver {
|
||||
}
|
||||
|
||||
@UseGuards(WorkspaceAuthGuard, UserAuthGuard, AdminPanelGuard)
|
||||
@Query(() => VersionInfo)
|
||||
async versionInfo(): Promise<VersionInfo> {
|
||||
@Query(() => VersionInfoDTO)
|
||||
async versionInfo(): Promise<VersionInfoDTO> {
|
||||
return this.adminService.getVersionInfo();
|
||||
}
|
||||
|
||||
@UseGuards(WorkspaceAuthGuard, UserAuthGuard, AdminPanelGuard)
|
||||
@Query(() => ConfigVariable)
|
||||
@Query(() => ConfigVariableDTO)
|
||||
async getDatabaseConfigVariable(
|
||||
@Args('key', { type: () => String }) key: keyof ConfigVariables,
|
||||
): Promise<ConfigVariable> {
|
||||
): Promise<ConfigVariableDTO> {
|
||||
this.twentyConfigService.validateConfigVariableExists(key as string);
|
||||
|
||||
return this.adminService.getConfigVariable(key);
|
||||
@@ -167,4 +173,52 @@ export class AdminPanelResolver {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@UseGuards(WorkspaceAuthGuard, UserAuthGuard, AdminPanelGuard)
|
||||
@Query(() => QueueJobsResponseDTO)
|
||||
async getQueueJobs(
|
||||
@Args('queueName', { type: () => String })
|
||||
queueName: string,
|
||||
@Args('state', { type: () => JobStateEnum })
|
||||
state: JobStateEnum,
|
||||
@Args('limit', { type: () => Int, nullable: true, defaultValue: 50 })
|
||||
limit?: number,
|
||||
@Args('offset', { type: () => Int, nullable: true, defaultValue: 0 })
|
||||
offset?: number,
|
||||
): Promise<QueueJobsResponseDTO> {
|
||||
return await this.adminPanelQueueService.getQueueJobs(
|
||||
queueName as MessageQueue,
|
||||
state,
|
||||
limit,
|
||||
offset,
|
||||
);
|
||||
}
|
||||
|
||||
@UseGuards(WorkspaceAuthGuard, UserAuthGuard, AdminPanelGuard)
|
||||
@Mutation(() => RetryJobsResponseDTO)
|
||||
async retryJobs(
|
||||
@Args('queueName', { type: () => String })
|
||||
queueName: string,
|
||||
@Args('jobIds', { type: () => [String] })
|
||||
jobIds: string[],
|
||||
): Promise<RetryJobsResponseDTO> {
|
||||
return await this.adminPanelQueueService.retryJobs(
|
||||
queueName as MessageQueue,
|
||||
jobIds,
|
||||
);
|
||||
}
|
||||
|
||||
@UseGuards(WorkspaceAuthGuard, UserAuthGuard, AdminPanelGuard)
|
||||
@Mutation(() => DeleteJobsResponseDTO)
|
||||
async deleteJobs(
|
||||
@Args('queueName', { type: () => String })
|
||||
queueName: string,
|
||||
@Args('jobIds', { type: () => [String] })
|
||||
jobIds: string[],
|
||||
): Promise<DeleteJobsResponseDTO> {
|
||||
return await this.adminPanelQueueService.deleteJobs(
|
||||
queueName as MessageQueue,
|
||||
jobIds,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,11 @@ import semver from 'semver';
|
||||
import { Repository } from 'typeorm';
|
||||
import * as z from 'zod';
|
||||
|
||||
import { type ConfigVariable } from 'src/engine/core-modules/admin-panel/dtos/config-variable.dto';
|
||||
import { type ConfigVariablesGroupData } from 'src/engine/core-modules/admin-panel/dtos/config-variables-group.dto';
|
||||
import { type ConfigVariableDTO } from 'src/engine/core-modules/admin-panel/dtos/config-variable.dto';
|
||||
import { type ConfigVariablesGroupDataDTO } from 'src/engine/core-modules/admin-panel/dtos/config-variables-group.dto';
|
||||
import { type ConfigVariablesOutput } from 'src/engine/core-modules/admin-panel/dtos/config-variables.output';
|
||||
import { type UserLookup } from 'src/engine/core-modules/admin-panel/dtos/user-lookup.entity';
|
||||
import { type VersionInfo } from 'src/engine/core-modules/admin-panel/dtos/version-info.dto';
|
||||
import { type VersionInfoDTO } from 'src/engine/core-modules/admin-panel/dtos/version-info.dto';
|
||||
import { AuditService } from 'src/engine/core-modules/audit/services/audit.service';
|
||||
import {
|
||||
AuthException,
|
||||
@@ -114,14 +114,14 @@ export class AdminPanelService {
|
||||
|
||||
getConfigVariablesGrouped(): ConfigVariablesOutput {
|
||||
const rawEnvVars = this.twentyConfigService.getAll();
|
||||
const groupedData = new Map<ConfigVariablesGroup, ConfigVariable[]>();
|
||||
const groupedData = new Map<ConfigVariablesGroup, ConfigVariableDTO[]>();
|
||||
|
||||
for (const [varName, { value, metadata, source }] of Object.entries(
|
||||
rawEnvVars,
|
||||
)) {
|
||||
const { group, description } = metadata;
|
||||
|
||||
const envVar: ConfigVariable = {
|
||||
const envVar: ConfigVariableDTO = {
|
||||
name: varName,
|
||||
description,
|
||||
value: value ?? null,
|
||||
@@ -139,7 +139,9 @@ export class AdminPanelService {
|
||||
groupedData.get(group)?.push(envVar);
|
||||
}
|
||||
|
||||
const groups: ConfigVariablesGroupData[] = Array.from(groupedData.entries())
|
||||
const groups: ConfigVariablesGroupDataDTO[] = Array.from(
|
||||
groupedData.entries(),
|
||||
)
|
||||
.sort((a, b) => {
|
||||
const positionA = CONFIG_VARIABLES_GROUP_METADATA[a[0]].position;
|
||||
const positionB = CONFIG_VARIABLES_GROUP_METADATA[b[0]].position;
|
||||
@@ -156,7 +158,7 @@ export class AdminPanelService {
|
||||
return { groups };
|
||||
}
|
||||
|
||||
getConfigVariable(key: string): ConfigVariable {
|
||||
getConfigVariable(key: string): ConfigVariableDTO {
|
||||
const variableWithMetadata =
|
||||
this.twentyConfigService.getVariableWithMetadata(
|
||||
key as keyof ConfigVariables,
|
||||
@@ -180,7 +182,7 @@ export class AdminPanelService {
|
||||
};
|
||||
}
|
||||
|
||||
async getVersionInfo(): Promise<VersionInfo> {
|
||||
async getVersionInfo(): Promise<VersionInfoDTO> {
|
||||
const currentVersion = this.twentyConfigService.get('APP_VERSION');
|
||||
|
||||
try {
|
||||
|
||||
+5
-5
@@ -1,11 +1,11 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { AdminPanelWorkerQueueHealth } from 'src/engine/core-modules/admin-panel/dtos/admin-panel-worker-queue-health.dto';
|
||||
import { AdminPanelWorkerQueueHealthDTO } from 'src/engine/core-modules/admin-panel/dtos/admin-panel-worker-queue-health.dto';
|
||||
import { AdminPanelHealthServiceStatus } from 'src/engine/core-modules/admin-panel/enums/admin-panel-health-service-status.enum';
|
||||
import { HealthIndicatorId } from 'src/engine/core-modules/health/enums/health-indicator-id.enum';
|
||||
|
||||
@ObjectType()
|
||||
export class AdminPanelHealthServiceData {
|
||||
@ObjectType('AdminPanelHealthServiceData')
|
||||
export class AdminPanelHealthServiceDataDTO {
|
||||
@Field(() => HealthIndicatorId)
|
||||
id: HealthIndicatorId;
|
||||
|
||||
@@ -23,6 +23,6 @@ export class AdminPanelHealthServiceData {
|
||||
@Field(() => String, { nullable: true })
|
||||
details?: string;
|
||||
|
||||
@Field(() => [AdminPanelWorkerQueueHealth], { nullable: true })
|
||||
queues?: AdminPanelWorkerQueueHealth[];
|
||||
@Field(() => [AdminPanelWorkerQueueHealthDTO], { nullable: true })
|
||||
queues?: AdminPanelWorkerQueueHealthDTO[];
|
||||
}
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@ import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { AdminPanelHealthServiceStatus } from 'src/engine/core-modules/admin-panel/enums/admin-panel-health-service-status.enum';
|
||||
|
||||
@ObjectType()
|
||||
export class AdminPanelWorkerQueueHealth {
|
||||
@ObjectType('AdminPanelWorkerQueueHealth')
|
||||
export class AdminPanelWorkerQueueHealthDTO {
|
||||
@Field(() => String)
|
||||
id: string;
|
||||
|
||||
|
||||
+2
-2
@@ -15,8 +15,8 @@ registerEnumType(ConfigVariableType, {
|
||||
name: 'ConfigVariableType',
|
||||
});
|
||||
|
||||
@ObjectType()
|
||||
export class ConfigVariable {
|
||||
@ObjectType('ConfigVariable')
|
||||
export class ConfigVariableDTO {
|
||||
@Field()
|
||||
name: string;
|
||||
|
||||
|
||||
+5
-5
@@ -1,16 +1,16 @@
|
||||
import { Field, ObjectType, registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
import { ConfigVariable } from 'src/engine/core-modules/admin-panel/dtos/config-variable.dto';
|
||||
import { ConfigVariableDTO } from 'src/engine/core-modules/admin-panel/dtos/config-variable.dto';
|
||||
import { ConfigVariablesGroup } from 'src/engine/core-modules/twenty-config/enums/config-variables-group.enum';
|
||||
|
||||
registerEnumType(ConfigVariablesGroup, {
|
||||
name: 'ConfigVariablesGroup',
|
||||
});
|
||||
|
||||
@ObjectType()
|
||||
export class ConfigVariablesGroupData {
|
||||
@Field(() => [ConfigVariable])
|
||||
variables: ConfigVariable[];
|
||||
@ObjectType('ConfigVariablesGroupData')
|
||||
export class ConfigVariablesGroupDataDTO {
|
||||
@Field(() => [ConfigVariableDTO])
|
||||
variables: ConfigVariableDTO[];
|
||||
|
||||
@Field(() => ConfigVariablesGroup)
|
||||
name: ConfigVariablesGroup;
|
||||
|
||||
+4
-4
@@ -1,9 +1,9 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { ConfigVariablesGroupData } from 'src/engine/core-modules/admin-panel/dtos/config-variables-group.dto';
|
||||
import { ConfigVariablesGroupDataDTO } from 'src/engine/core-modules/admin-panel/dtos/config-variables-group.dto';
|
||||
|
||||
@ObjectType()
|
||||
@ObjectType('ConfigVariablesOutput')
|
||||
export class ConfigVariablesOutput {
|
||||
@Field(() => [ConfigVariablesGroupData])
|
||||
groups: ConfigVariablesGroupData[];
|
||||
@Field(() => [ConfigVariablesGroupDataDTO])
|
||||
groups: ConfigVariablesGroupDataDTO[];
|
||||
}
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { Field, Int, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { JobOperationResultDTO } from 'src/engine/core-modules/admin-panel/dtos/job-operation-result.dto';
|
||||
|
||||
@ObjectType('DeleteJobsResponse')
|
||||
export class DeleteJobsResponseDTO {
|
||||
@Field(() => Int)
|
||||
deletedCount: number;
|
||||
|
||||
@Field(() => [JobOperationResultDTO])
|
||||
results: JobOperationResultDTO[];
|
||||
}
|
||||
+4
-4
@@ -1,13 +1,13 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { AuthToken } from 'src/engine/core-modules/auth/dto/token.entity';
|
||||
import { WorkspaceUrlsAndId } from 'src/engine/core-modules/workspace/dtos/workspace-subdomain-id.dto';
|
||||
import { WorkspaceUrlsAndIdDTO } from 'src/engine/core-modules/workspace/dtos/workspace-subdomain-id.dto';
|
||||
|
||||
@ObjectType()
|
||||
@ObjectType('ImpersonateOutput')
|
||||
export class ImpersonateOutput {
|
||||
@Field(() => AuthToken)
|
||||
loginToken: AuthToken;
|
||||
|
||||
@Field(() => WorkspaceUrlsAndId)
|
||||
workspace: WorkspaceUrlsAndId;
|
||||
@Field(() => WorkspaceUrlsAndIdDTO)
|
||||
workspace: WorkspaceUrlsAndIdDTO;
|
||||
}
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
@ObjectType('JobOperationResult')
|
||||
export class JobOperationResultDTO {
|
||||
@Field(() => String)
|
||||
jobId: string;
|
||||
|
||||
@Field(() => Boolean)
|
||||
success: boolean;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
error?: string;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import GraphQLJSON from 'graphql-type-json';
|
||||
|
||||
import { JobStateEnum } from 'src/engine/core-modules/admin-panel/enums/job-state.enum';
|
||||
|
||||
@ObjectType('QueueJob')
|
||||
export class QueueJobDTO {
|
||||
@Field(() => String)
|
||||
id: string;
|
||||
|
||||
@Field(() => String)
|
||||
name: string;
|
||||
|
||||
@Field(() => GraphQLJSON, { nullable: true })
|
||||
data?: object;
|
||||
|
||||
@Field(() => JobStateEnum)
|
||||
state: JobStateEnum;
|
||||
|
||||
@Field(() => Number, { nullable: true })
|
||||
timestamp?: number;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
failedReason?: string;
|
||||
|
||||
@Field(() => Number, { nullable: true })
|
||||
processedOn?: number;
|
||||
|
||||
@Field(() => Number, { nullable: true })
|
||||
finishedOn?: number;
|
||||
|
||||
@Field(() => Number)
|
||||
attemptsMade: number;
|
||||
|
||||
@Field(() => GraphQLJSON, { nullable: true })
|
||||
returnValue?: object;
|
||||
|
||||
@Field(() => [String], { nullable: true })
|
||||
logs?: string[];
|
||||
|
||||
@Field(() => [String], { nullable: true })
|
||||
stackTrace?: string[];
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { QueueJobDTO } from 'src/engine/core-modules/admin-panel/dtos/queue-job.dto';
|
||||
import { QueueRetentionConfigDTO } from 'src/engine/core-modules/admin-panel/dtos/queue-retention-config.dto';
|
||||
|
||||
@ObjectType('QueueJobsResponse')
|
||||
export class QueueJobsResponseDTO {
|
||||
@Field(() => [QueueJobDTO])
|
||||
jobs: QueueJobDTO[];
|
||||
|
||||
@Field(() => Number)
|
||||
count: number;
|
||||
|
||||
@Field(() => Number)
|
||||
totalCount: number;
|
||||
|
||||
@Field(() => Boolean)
|
||||
hasMore: boolean;
|
||||
|
||||
@Field(() => QueueRetentionConfigDTO)
|
||||
retentionConfig: QueueRetentionConfigDTO;
|
||||
}
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
@ObjectType()
|
||||
export class QueueMetricsDataPoint {
|
||||
@ObjectType('QueueMetricsDataPoint')
|
||||
export class QueueMetricsDataPointDTO {
|
||||
@Field(() => Number)
|
||||
x: number;
|
||||
|
||||
|
||||
+5
-5
@@ -1,11 +1,11 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { QueueMetricsSeries } from 'src/engine/core-modules/admin-panel/dtos/queue-metrics-series.dto';
|
||||
import { QueueMetricsSeriesDTO } from 'src/engine/core-modules/admin-panel/dtos/queue-metrics-series.dto';
|
||||
import { QueueMetricsTimeRange } from 'src/engine/core-modules/admin-panel/enums/queue-metrics-time-range.enum';
|
||||
import { WorkerQueueMetrics } from 'src/engine/core-modules/health/types/worker-queue-metrics.type';
|
||||
|
||||
@ObjectType()
|
||||
export class QueueMetricsData {
|
||||
@ObjectType('QueueMetricsData')
|
||||
export class QueueMetricsDataDTO {
|
||||
@Field(() => String)
|
||||
queueName: string;
|
||||
|
||||
@@ -18,6 +18,6 @@ export class QueueMetricsData {
|
||||
@Field(() => WorkerQueueMetrics, { nullable: true })
|
||||
details: WorkerQueueMetrics | null;
|
||||
|
||||
@Field(() => [QueueMetricsSeries])
|
||||
data: QueueMetricsSeries[];
|
||||
@Field(() => [QueueMetricsSeriesDTO])
|
||||
data: QueueMetricsSeriesDTO[];
|
||||
}
|
||||
|
||||
+5
-5
@@ -1,12 +1,12 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { QueueMetricsDataPoint } from 'src/engine/core-modules/admin-panel/dtos/queue-metrics-data-point.dto';
|
||||
import { QueueMetricsDataPointDTO } from 'src/engine/core-modules/admin-panel/dtos/queue-metrics-data-point.dto';
|
||||
|
||||
@ObjectType()
|
||||
export class QueueMetricsSeries {
|
||||
@ObjectType('QueueMetricsSeries')
|
||||
export class QueueMetricsSeriesDTO {
|
||||
@Field(() => String)
|
||||
id: string;
|
||||
|
||||
@Field(() => [QueueMetricsDataPoint])
|
||||
data: QueueMetricsDataPoint[];
|
||||
@Field(() => [QueueMetricsDataPointDTO])
|
||||
data: QueueMetricsDataPointDTO[];
|
||||
}
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
@ObjectType('QueueRetentionConfig')
|
||||
export class QueueRetentionConfigDTO {
|
||||
@Field(() => Number)
|
||||
completedMaxAge: number;
|
||||
|
||||
@Field(() => Number)
|
||||
completedMaxCount: number;
|
||||
|
||||
@Field(() => Number)
|
||||
failedMaxAge: number;
|
||||
|
||||
@Field(() => Number)
|
||||
failedMaxCount: number;
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { Field, Int, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { JobOperationResultDTO } from 'src/engine/core-modules/admin-panel/dtos/job-operation-result.dto';
|
||||
|
||||
@ObjectType('RetryJobsResponse')
|
||||
export class RetryJobsResponseDTO {
|
||||
@Field(() => Int)
|
||||
retriedCount: number;
|
||||
|
||||
@Field(() => [JobOperationResultDTO])
|
||||
results: JobOperationResultDTO[];
|
||||
}
|
||||
+6
-6
@@ -3,8 +3,8 @@ import { Field, ObjectType } from '@nestjs/graphql';
|
||||
import { AdminPanelHealthServiceStatus } from 'src/engine/core-modules/admin-panel/enums/admin-panel-health-service-status.enum';
|
||||
import { HealthIndicatorId } from 'src/engine/core-modules/health/enums/health-indicator-id.enum';
|
||||
|
||||
@ObjectType()
|
||||
export class SystemHealthService {
|
||||
@ObjectType('SystemHealthService')
|
||||
export class SystemHealthServiceDTO {
|
||||
@Field(() => HealthIndicatorId)
|
||||
id: HealthIndicatorId;
|
||||
|
||||
@@ -15,8 +15,8 @@ export class SystemHealthService {
|
||||
status: AdminPanelHealthServiceStatus;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
export class SystemHealth {
|
||||
@Field(() => [SystemHealthService])
|
||||
services: SystemHealthService[];
|
||||
@ObjectType('SystemHealth')
|
||||
export class SystemHealthDTO {
|
||||
@Field(() => [SystemHealthServiceDTO])
|
||||
services: SystemHealthServiceDTO[];
|
||||
}
|
||||
|
||||
+14
-14
@@ -2,10 +2,10 @@ import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
import { WorkspaceUrls } from 'src/engine/core-modules/workspace/dtos/workspace-urls.dto';
|
||||
import { WorkspaceUrlsDTO } from 'src/engine/core-modules/workspace/dtos/workspace-urls.dto';
|
||||
|
||||
@ObjectType()
|
||||
class UserInfo {
|
||||
@ObjectType('UserInfo')
|
||||
class UserInfoDTO {
|
||||
@Field(() => UUIDScalarType)
|
||||
id: string;
|
||||
|
||||
@@ -19,8 +19,8 @@ class UserInfo {
|
||||
lastName?: string;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
class WorkspaceInfo {
|
||||
@ObjectType('WorkspaceInfo')
|
||||
class WorkspaceInfoDTO {
|
||||
@Field(() => UUIDScalarType)
|
||||
id: string;
|
||||
|
||||
@@ -36,21 +36,21 @@ class WorkspaceInfo {
|
||||
@Field(() => Number)
|
||||
totalUsers: number;
|
||||
|
||||
@Field(() => WorkspaceUrls)
|
||||
workspaceUrls: WorkspaceUrls;
|
||||
@Field(() => WorkspaceUrlsDTO)
|
||||
workspaceUrls: WorkspaceUrlsDTO;
|
||||
|
||||
@Field(() => [UserInfo])
|
||||
users: UserInfo[];
|
||||
@Field(() => [UserInfoDTO])
|
||||
users: UserInfoDTO[];
|
||||
|
||||
@Field(() => [FeatureFlag])
|
||||
featureFlags: FeatureFlag[];
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
@ObjectType('UserLookup')
|
||||
export class UserLookup {
|
||||
@Field(() => UserInfo)
|
||||
user: UserInfo;
|
||||
@Field(() => UserInfoDTO)
|
||||
user: UserInfoDTO;
|
||||
|
||||
@Field(() => [WorkspaceInfo])
|
||||
workspaces: WorkspaceInfo[];
|
||||
@Field(() => [WorkspaceInfoDTO])
|
||||
workspaces: WorkspaceInfoDTO[];
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
@ObjectType()
|
||||
export class VersionInfo {
|
||||
@ObjectType('VersionInfo')
|
||||
export class VersionInfoDTO {
|
||||
@Field(() => String, { nullable: true })
|
||||
currentVersion?: string;
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
import { type JobState as BullMQJobState } from 'bullmq/dist/esm/types';
|
||||
|
||||
export enum JobStateEnum {
|
||||
COMPLETED = 'COMPLETED',
|
||||
FAILED = 'FAILED',
|
||||
ACTIVE = 'ACTIVE',
|
||||
WAITING = 'WAITING',
|
||||
DELAYED = 'DELAYED',
|
||||
PRIORITIZED = 'PRIORITIZED',
|
||||
WAITING_CHILDREN = 'WAITING_CHILDREN',
|
||||
}
|
||||
|
||||
registerEnumType(JobStateEnum, {
|
||||
name: 'JobState',
|
||||
description: 'Job state in the queue',
|
||||
});
|
||||
|
||||
// Mapping from GraphQL enum to BullMQ state values
|
||||
export const jobStateEnumToBullMQ: Record<JobStateEnum, BullMQJobState> = {
|
||||
[JobStateEnum.COMPLETED]: 'completed',
|
||||
[JobStateEnum.FAILED]: 'failed',
|
||||
[JobStateEnum.ACTIVE]: 'active',
|
||||
[JobStateEnum.WAITING]: 'waiting',
|
||||
[JobStateEnum.DELAYED]: 'delayed',
|
||||
[JobStateEnum.PRIORITIZED]: 'prioritized',
|
||||
[JobStateEnum.WAITING_CHILDREN]: 'waiting-children',
|
||||
};
|
||||
|
||||
// Mapping from BullMQ state values to GraphQL enum
|
||||
export const bullMQToJobStateEnum: Record<BullMQJobState, JobStateEnum> = {
|
||||
completed: JobStateEnum.COMPLETED,
|
||||
failed: JobStateEnum.FAILED,
|
||||
active: JobStateEnum.ACTIVE,
|
||||
waiting: JobStateEnum.WAITING,
|
||||
delayed: JobStateEnum.DELAYED,
|
||||
prioritized: JobStateEnum.PRIORITIZED,
|
||||
'waiting-children': JobStateEnum.WAITING_CHILDREN,
|
||||
};
|
||||
Reference in New Issue
Block a user