Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions src/lib/bullmq.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
export enum QueueName {
Builds = 'Builds',
System_Recurring = 'System (Recurring)',
System_Startup = 'System (Startup)',
Products = 'Products',
Projects = 'Projects',
Publishing = 'Publishing',
Polling = 'Polling',
UserTasks = 'User Tasks',
Emails = 'Emails',
SvelteSSE = 'Svelte SSE'
}

export enum JobType {
// Build Jobs
Build_Product = 'Build Product',
Build_PostProcess = 'Postprocess Build',
// Polling Jobs
Poll_Build = 'Check Product Build',
Poll_Project = 'Check Project Creation',
Poll_Publish = 'Check Product Publish',
// Product Jobs
Product_Create = 'Create Product - BuildEngine',
Product_Delete = 'Delete Product - BuildEngine',
Product_GetVersionCode = 'Get VersionCode for Uploaded Product',
Product_CreateLocal = 'Create Local Product',
// Project Jobs
Project_Create = 'Create Project',
Project_ImportProducts = 'Import Products for Project',
// Publishing Jobs
Publish_Product = 'Publish Product',
Publish_PostProcess = 'Postprocess Publish',
// System Jobs
System_CheckEngineStatuses = 'Check BuildEngine Statuses',
System_RefreshLangTags = 'Refresh langtags.json',
System_Migrate = 'Migrate Features from S1 to S2',
// UserTasks Job
UserTasks_Modify = 'Modify UserTasks',
// Email Jobs
Email_InviteUser = 'Invite User',
Email_SendNotificationToUser = 'Send Notification to User',
Email_SendNotificationToReviewers = 'Send Notification to Product Reviewers',
Email_SendNotificationToOrgAdminsAndOwner = 'Send Notification to Org Admins and Owners',
Email_SendBatchUserTaskNotifications = 'Send Batch User Task Notifications',
Email_NotifySuperAdminsOfNewOrganizationRequest = 'Notify Super Admins of New Organization Request',
Email_NotifySuperAdminsOfOfflineSystems = 'Notify Super Admins of Offline Systems',
Email_NotifySuperAdminsLowPriority = 'Notify Super Admins (Low Priority)',
Email_ProjectImportReport = 'Project Import Report',
// Svelte Project SSE
SvelteSSE_UpdateProject = 'Update Project',
SvelteSSE_UpdateUserTasks = 'Update UserTasks'
}

export enum JobSchedulerId {
SystemStatusEmail = 'SystemStatusEmail',
RefreshLangTags = 'RefreshLangTags',
CheckSystemStatuses = 'CheckSystemStatuses',
PruneUsers = 'PruneUsers'
}
1 change: 1 addition & 0 deletions src/lib/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@
"products_addTitle": "Select Product to Add",
"products_removeTitle": "Select Product to Remove",
"products_details": "Details",
"products_jobRecords": "Job Records",
"publications_channel": "Channel",
"publications_status": "Status",
"publications_date": "Publish Date",
Expand Down
1 change: 1 addition & 0 deletions src/lib/locales/es-419.json
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@
"products_addTitle": "Select Product to Add",
"products_removeTitle": "Select Product to Remove",
"products_details": "Detalles",
"products_jobRecords": "Registros de trabajo",
"publications_channel": "Channel",
"publications_status": "Estado",
"publications_date": "Publish Date",
Expand Down
1 change: 1 addition & 0 deletions src/lib/locales/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@
"products_addTitle": "Select Product to Add",
"products_removeTitle": "Select Product to Remove",
"products_details": "Details",
"products_jobRecords": "Job Records",
"publications_channel": "Channel",
"publications_status": "Status",
"publications_date": "Publish Date",
Expand Down
15 changes: 15 additions & 0 deletions src/lib/prisma/migrations/16_track_jobs/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-- CreateTable
CREATE TABLE "QueueRecords" (
"ProductTransitionId" INTEGER NOT NULL,
"Queue" TEXT NOT NULL,
"JobType" TEXT NOT NULL,
"JobId" TEXT NOT NULL,

CONSTRAINT "IX_QueueRecords_Queue_JobId" PRIMARY KEY ("Queue", "JobId")
);

-- CreateIndex
CREATE INDEX "IX_QueueRecords_ProductTransitionId" ON "QueueRecords"("ProductTransitionId");

-- AddForeignKey
ALTER TABLE "QueueRecords" ADD CONSTRAINT "FK_QueueRecords_ProductTransitions_ProductTransitionId" FOREIGN KEY ("ProductTransitionId") REFERENCES "ProductTransitions"("Id") ON DELETE CASCADE ON UPDATE NO ACTION;
26 changes: 19 additions & 7 deletions src/lib/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -257,25 +257,37 @@ model ProductPublications {
}

model ProductTransitions {
Id Int @id(map: "PK_ProductTransitions") @default(autoincrement())
ProductId String @db.Uuid
Id Int @id(map: "PK_ProductTransitions") @default(autoincrement())
ProductId String @db.Uuid
// Since users are never deleted, only locked, this should be fine
UserId Int?
WorkflowUserId String? @db.Uuid // ISSUE: #1102 remove when DWKit is replaced
WorkflowUserId String? @db.Uuid // ISSUE: #1102 remove when DWKit is replaced
AllowedUserNames String?
InitialState String?
DestinationState String?
Command String?
DateTransition DateTime? @db.Timestamp(6)
DateTransition DateTime? @db.Timestamp(6)
Comment String?
TransitionType Int @default(1)
TransitionType Int @default(1)
WorkflowType Int?
Product Products @relation(fields: [ProductId], references: [Id], onDelete: Cascade, onUpdate: NoAction, map: "FK_ProductTransitions_Products_ProductId")
User Users? @relation(fields: [UserId], references: [Id])
Product Products @relation(fields: [ProductId], references: [Id], onDelete: Cascade, onUpdate: NoAction, map: "FK_ProductTransitions_Products_ProductId")
User Users? @relation(fields: [UserId], references: [Id])
QueueRecords QueueRecords[]

@@index([ProductId], map: "IX_ProductTransitions_ProductId")
}

model QueueRecords {
ProductTransitionId Int
Queue String
JobType String
JobId String
ProductTransition ProductTransitions @relation(fields: [ProductTransitionId], references: [Id], onDelete: Cascade, onUpdate: NoAction, map: "FK_QueueRecords_ProductTransitions_ProductTransitionId")

@@index([ProductTransitionId], map: "IX_QueueRecords_ProductTransitionId")
@@id([Queue, JobId], map: "IX_QueueRecords_Queue_JobId")
}

model Products {
Id String @id(map: "PK_Products") @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
ProjectId Int
Expand Down
57 changes: 44 additions & 13 deletions src/lib/products/components/ProductDetails.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,26 @@
// optional chaining for if element isn't found or isn't actually a Dialog Element
(document.getElementById('modal' + productId) as HTMLDialogElement)?.showModal?.();
}
export type Transition = Prisma.ProductTransitionsGetPayload<{
select: {
TransitionType: true;
InitialState: true;
WorkflowType: true;
AllowedUserNames: true;
Command: true;
Comment: true;
DateTransition: true;
User: { select: { Name: true } };
QueueRecords: {
select: {
Queue: true;
JobId: true;
JobType: true;
};
};
};
}>;
</script>

<script lang="ts">
Expand All @@ -21,18 +41,7 @@
Store: { select: { Description: true } };
};
}>;
transitions: Prisma.ProductTransitionsGetPayload<{
select: {
TransitionType: true;
InitialState: true;
WorkflowType: true;
AllowedUserNames: true;
Command: true;
Comment: true;
DateTransition: true;
User: { select: { Name: true } };
};
}>[];
transitions: Transition[];
}
let { product, transitions }: Props = $props();
Expand Down Expand Up @@ -107,7 +116,7 @@
{#each transitions as transition}
<tr
class:font-bold={isLandmark(transition.TransitionType)}
class:no-border={transition.Comment}
class:no-border={transition.Comment || transition.QueueRecords?.length}
>
<td>
{@render transitionType(transition)}
Expand All @@ -123,6 +132,28 @@
{getTimeDateString(transition.DateTransition)}
</td>
</tr>
{#if transition.QueueRecords?.length}
<tr class:no-border={transition.Comment}>
<td colspan="4">
<details class="cursor-pointer">
<summary>{m.products_jobRecords()} ({transition.QueueRecords.length})</summary>
<ul>
{#each transition.QueueRecords as rec}
<li>
<a
class="link"
href="/admin/jobs/queue/{rec.Queue}/{encodeURIComponent(rec.JobId)}"
target="_blank"
>
{rec.Queue}: {rec.JobType}
</a>
</li>
{/each}
</ul>
</details>
</td>
</tr>
{/if}
{#if transition.Comment}
<tr>
<td colspan="4">
Expand Down
12 changes: 11 additions & 1 deletion src/lib/projects/sse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getProductActions } from '$lib/products';
import { canModifyProject } from '$lib/projects';
import { userGroupsForOrg } from '$lib/projects/server';
import { DatabaseReads } from '$lib/server/database';
import { isSuperAdmin } from '$lib/utils/roles';

const tracer = trace.getTracer('ProjectSSE');
export type ProjectDataSSE = Awaited<ReturnType<typeof getProjectDetails>>;
Expand Down Expand Up @@ -157,7 +158,16 @@ export async function getProjectDetails(id: number, userSession: Session['user']
select: {
Name: true
}
}
},
QueueRecords: isSuperAdmin(userSession.roles)
? {
select: {
Queue: true,
JobId: true,
JobType: true
}
}
: false
}
});
span.addEvent('Product transitions fetched');
Expand Down
20 changes: 20 additions & 0 deletions src/lib/server/bullmq/BullWorker.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { SpanStatusCode, trace } from '@opentelemetry/api';
import type { Exception, Job } from 'bullmq';
import { Worker } from 'bullmq';
import { DatabaseWrites } from '../database';
import * as Executor from '../job-executors';
import { getQueues, getWorkerConfig } from './queues';
import * as BullMQ from './types';
Expand All @@ -27,6 +28,25 @@ export abstract class BullWorker<T extends BullMQ.Job> {
'job.data': JSON.stringify(job.data)
});
try {
if (job.id && job.data.transitions?.length) {
const records = await DatabaseWrites.queueRecords.createManyAndReturn({
data: job.data.transitions.map((t) => ({
ProductTransitionId: t,
Queue: job.queueName,
JobType: job.data.type,
JobId: job.id! // this is in fact defined (checked in above if)
})),
select: {
Queue: true,
JobId: true
},
skipDuplicates: true
});
span.setAttribute(
'job.records',
records.map((r) => `${encodeURIComponent(r.Queue)}/${encodeURIComponent(r.JobId)}`)
);
}
return await this.run(job);
} catch (error) {
span.recordException(error as Exception);
Expand Down
Loading
Loading