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:
+2
-2
@@ -2,8 +2,8 @@ import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@ObjectType()
|
||||
export class TimelineCalendarEventParticipant {
|
||||
@ObjectType('TimelineCalendarEventParticipant')
|
||||
export class TimelineCalendarEventParticipantDTO {
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
personId: string | null;
|
||||
|
||||
|
||||
+13
-13
@@ -1,11 +1,11 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { TimelineCalendarEventParticipant } from 'src/engine/core-modules/calendar/dtos/timeline-calendar-event-participant.dto';
|
||||
import { TimelineCalendarEventParticipantDTO } from 'src/engine/core-modules/calendar/dtos/timeline-calendar-event-participant.dto';
|
||||
import { CalendarChannelVisibility } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
|
||||
|
||||
@ObjectType()
|
||||
class LinkMetadata {
|
||||
@ObjectType('LinkMetadata')
|
||||
class LinkMetadataDTO {
|
||||
@Field()
|
||||
label: string;
|
||||
|
||||
@@ -13,20 +13,20 @@ class LinkMetadata {
|
||||
url: string;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
export class LinksMetadata {
|
||||
@ObjectType('LinksMetadata')
|
||||
export class LinksMetadataDTO {
|
||||
@Field()
|
||||
primaryLinkLabel: string;
|
||||
|
||||
@Field()
|
||||
primaryLinkUrl: string;
|
||||
|
||||
@Field(() => [LinkMetadata], { nullable: true })
|
||||
secondaryLinks: LinkMetadata[] | null;
|
||||
@Field(() => [LinkMetadataDTO], { nullable: true })
|
||||
secondaryLinks: LinkMetadataDTO[] | null;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
export class TimelineCalendarEvent {
|
||||
@ObjectType('TimelineCalendarEvent')
|
||||
export class TimelineCalendarEventDTO {
|
||||
@Field(() => UUIDScalarType)
|
||||
id: string;
|
||||
|
||||
@@ -54,11 +54,11 @@ export class TimelineCalendarEvent {
|
||||
@Field()
|
||||
conferenceSolution: string;
|
||||
|
||||
@Field(() => LinksMetadata)
|
||||
conferenceLink: LinksMetadata;
|
||||
@Field(() => LinksMetadataDTO)
|
||||
conferenceLink: LinksMetadataDTO;
|
||||
|
||||
@Field(() => [TimelineCalendarEventParticipant])
|
||||
participants: TimelineCalendarEventParticipant[];
|
||||
@Field(() => [TimelineCalendarEventParticipantDTO])
|
||||
participants: TimelineCalendarEventParticipantDTO[];
|
||||
|
||||
@Field(() => CalendarChannelVisibility)
|
||||
visibility: CalendarChannelVisibility;
|
||||
|
||||
+5
-5
@@ -1,12 +1,12 @@
|
||||
import { Field, Int, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { TimelineCalendarEvent } from 'src/engine/core-modules/calendar/dtos/timeline-calendar-event.dto';
|
||||
import { TimelineCalendarEventDTO } from 'src/engine/core-modules/calendar/dtos/timeline-calendar-event.dto';
|
||||
|
||||
@ObjectType()
|
||||
export class TimelineCalendarEventsWithTotal {
|
||||
@ObjectType('TimelineCalendarEventsWithTotal')
|
||||
export class TimelineCalendarEventsWithTotalDTO {
|
||||
@Field(() => Int)
|
||||
totalNumberOfCalendarEvents: number;
|
||||
|
||||
@Field(() => [TimelineCalendarEvent])
|
||||
timelineCalendarEvents: TimelineCalendarEvent[];
|
||||
@Field(() => [TimelineCalendarEventDTO])
|
||||
timelineCalendarEvents: TimelineCalendarEventDTO[];
|
||||
}
|
||||
|
||||
+5
-5
@@ -5,7 +5,7 @@ import { Max } from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { TIMELINE_CALENDAR_EVENTS_MAX_PAGE_SIZE } from 'src/engine/core-modules/calendar/constants/calendar.constants';
|
||||
import { TimelineCalendarEventsWithTotal } from 'src/engine/core-modules/calendar/dtos/timeline-calendar-events-with-total.dto';
|
||||
import { TimelineCalendarEventsWithTotalDTO } from 'src/engine/core-modules/calendar/dtos/timeline-calendar-events-with-total.dto';
|
||||
import { TimelineCalendarEventService } from 'src/engine/core-modules/calendar/timeline-calendar-event.service';
|
||||
import { AuthWorkspaceMemberId } from 'src/engine/decorators/auth/auth-workspace-member-id.decorator';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
@@ -50,13 +50,13 @@ class GetTimelineCalendarEventsFromOpportunityIdArgs {
|
||||
}
|
||||
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@Resolver(() => TimelineCalendarEventsWithTotal)
|
||||
@Resolver(() => TimelineCalendarEventsWithTotalDTO)
|
||||
export class TimelineCalendarEventResolver {
|
||||
constructor(
|
||||
private readonly timelineCalendarEventService: TimelineCalendarEventService,
|
||||
) {}
|
||||
|
||||
@Query(() => TimelineCalendarEventsWithTotal)
|
||||
@Query(() => TimelineCalendarEventsWithTotalDTO)
|
||||
async getTimelineCalendarEventsFromPersonId(
|
||||
@Args()
|
||||
{ personId, page, pageSize }: GetTimelineCalendarEventsFromPersonIdArgs,
|
||||
@@ -73,7 +73,7 @@ export class TimelineCalendarEventResolver {
|
||||
return timelineCalendarEvents;
|
||||
}
|
||||
|
||||
@Query(() => TimelineCalendarEventsWithTotal)
|
||||
@Query(() => TimelineCalendarEventsWithTotalDTO)
|
||||
async getTimelineCalendarEventsFromCompanyId(
|
||||
@Args()
|
||||
{ companyId, page, pageSize }: GetTimelineCalendarEventsFromCompanyIdArgs,
|
||||
@@ -90,7 +90,7 @@ export class TimelineCalendarEventResolver {
|
||||
return timelineCalendarEvents;
|
||||
}
|
||||
|
||||
@Query(() => TimelineCalendarEventsWithTotal)
|
||||
@Query(() => TimelineCalendarEventsWithTotalDTO)
|
||||
async getTimelineCalendarEventsFromOpportunityId(
|
||||
@Args()
|
||||
{
|
||||
|
||||
+4
-4
@@ -5,7 +5,7 @@ import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/
|
||||
import { Any } from 'typeorm';
|
||||
|
||||
import { TIMELINE_CALENDAR_EVENTS_DEFAULT_PAGE_SIZE } from 'src/engine/core-modules/calendar/constants/calendar.constants';
|
||||
import { type TimelineCalendarEventsWithTotal } from 'src/engine/core-modules/calendar/dtos/timeline-calendar-events-with-total.dto';
|
||||
import { type TimelineCalendarEventsWithTotalDTO } from 'src/engine/core-modules/calendar/dtos/timeline-calendar-events-with-total.dto';
|
||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
||||
import { CalendarChannelVisibility } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
|
||||
import { type CalendarEventWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event.workspace-entity';
|
||||
@@ -27,7 +27,7 @@ export class TimelineCalendarEventService {
|
||||
personIds: string[];
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}): Promise<TimelineCalendarEventsWithTotal> {
|
||||
}): Promise<TimelineCalendarEventsWithTotalDTO> {
|
||||
const offset = (page - 1) * pageSize;
|
||||
|
||||
const calendarEventRepository =
|
||||
@@ -167,7 +167,7 @@ export class TimelineCalendarEventService {
|
||||
companyId: string;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}): Promise<TimelineCalendarEventsWithTotal> {
|
||||
}): Promise<TimelineCalendarEventsWithTotalDTO> {
|
||||
const personRepository =
|
||||
await this.twentyORMManager.getRepository<PersonWorkspaceEntity>(
|
||||
'person',
|
||||
@@ -211,7 +211,7 @@ export class TimelineCalendarEventService {
|
||||
opportunityId: string;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}): Promise<TimelineCalendarEventsWithTotal> {
|
||||
}): Promise<TimelineCalendarEventsWithTotalDTO> {
|
||||
const opportunityRepository =
|
||||
await this.twentyORMManager.getRepository<OpportunityWorkspaceEntity>(
|
||||
'opportunity',
|
||||
|
||||
Reference in New Issue
Block a user