Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
7 changes: 7 additions & 0 deletions src/shared/config/common.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,12 @@ export const CommonConfig = {
contactManagersEmailTemplate:
process.env.SENDGRID_CONTACT_MANAGERS_TEMPLATE ??
'd-00000000000000000000000000000000',
aiWorkflowRunCompletedEmailTemplate:
process.env.SENDGRID_AI_WORKFLOW_RUN_COMPLETED_TEMPLATE ??
'd-7d14d986ba0a4317b449164b73939910',
},
ui: {
reviewUIUrl:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider renaming reviewUIUrl to reviewUiUrl to maintain consistent camelCase naming convention.

process.env.REVIEW_UI_URL ?? 'https://review-v6.topcoder-dev.com',
},
};
88 changes: 86 additions & 2 deletions src/shared/modules/global/workflow-queue.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { GiteaService } from './gitea.service';
import { PrismaService } from './prisma.service';
import { QueueSchedulerService } from './queue-scheduler.service';
import { Job } from 'pg-boss';
import { aiWorkflowRun } from '@prisma/client';
import { aiWorkflow, aiWorkflowRun } from '@prisma/client';
import { EventBusSendEmailPayload, EventBusService } from './eventBus.service';
import { CommonConfig } from 'src/shared/config/common.config';

@Injectable()
export class WorkflowQueueHandler implements OnModuleInit {
Expand All @@ -13,6 +15,7 @@ export class WorkflowQueueHandler implements OnModuleInit {
private readonly prisma: PrismaService,
private readonly scheduler: QueueSchedulerService,
private readonly giteaService: GiteaService,
private readonly eventBusService: EventBusService,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The EventBusService is added as a dependency but there is no indication in this diff how it is used. Ensure that it is utilized appropriately in the class to justify its inclusion.

) {}

async onModuleInit() {
Expand Down Expand Up @@ -150,7 +153,7 @@ export class WorkflowQueueHandler implements OnModuleInit {
return;
}

let [aiWorkflowRun]: (aiWorkflowRun | null)[] = aiWorkflowRuns;
let [aiWorkflowRun]: ((typeof aiWorkflowRuns)[0] | null)[] = aiWorkflowRuns;

if (
!aiWorkflowRun &&
Expand Down Expand Up @@ -299,6 +302,7 @@ export class WorkflowQueueHandler implements OnModuleInit {
}
} catch (e) {
this.logger.log(aiWorkflowRun.id, e.message);
return;
}

this.logger.log({
Expand All @@ -309,9 +313,89 @@ export class WorkflowQueueHandler implements OnModuleInit {
status: conclusion,
timestamp: new Date().toISOString(),
});

try {
await this.sendWorkflowRunCompletedNotification(aiWorkflowRun);
} catch (e) {
this.logger.log(
`Failed to send workflowRun completed notification for aiWorkflowRun ${aiWorkflowRun.id}. Got error ${e.message ?? e}!`,
);
}
break;
default:
break;
}
}

async sendWorkflowRunCompletedNotification(
aiWorkflowRun: aiWorkflowRun & { workflow: aiWorkflow },
) {
const submission = await this.prisma.submission.findUnique({
where: { id: aiWorkflowRun.submissionId },
});

if (!submission) {
this.logger.log(
`Failed to send workflowRun completed notification for aiWorkflowRun ${aiWorkflowRun.id}. Submission ${aiWorkflowRun.submissionId} is missing!`,
);
return;
}

const [challenge] = await this.prisma.$queryRaw<
{ id: string; name: string }[]
>`
SELECT
id,
name
FROM challenges."Challenge" c
WHERE c.id=${submission.challengeId}
`;

if (!challenge) {
this.logger.log(
`Failed to send workflowRun completed notification for aiWorkflowRun ${aiWorkflowRun.id}. Challenge ${submission.challengeId} couldn't be fetched!`,
);
return;
}

const [user] = await this.prisma.$queryRaw<
{
handle: string;
email: string;
firstName?: string;
lastName?: string;
}[]
>`
SELECT
handle,
email,
"firstName",
"lastName"
FROM members.member u
WHERE u."userId"::text=${submission.memberId}
`;

if (!user) {
this.logger.log(
`Failed to send workflowRun completed notification for aiWorkflowRun ${aiWorkflowRun.id}. User ${submission.memberId} couldn't be fetched!`,
);
return;
}

await this.eventBusService.sendEmail({
...new EventBusSendEmailPayload(),
sendgrid_template_id:
CommonConfig.sendgridConfig.aiWorkflowRunCompletedEmailTemplate,
recipients: [user.email],
data: {
userName:
[user.firstName, user.lastName].filter(Boolean).join(' ') ||

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using the nullish coalescing operator (??) instead of the logical OR operator (||) for better handling of null or undefined values. The nullish coalescing operator will only fall back to user.handle if the result of the join operation is null or undefined, whereas the logical OR operator will also fall back if the result is an empty string.

user.handle,
aiWorkflowName: aiWorkflowRun.workflow.name,
reviewLink: `${CommonConfig.ui.reviewUIUrl}/review/active-challenges/${challenge.id}/scorecard-details/${submission.id}#aiWorkflowRunId=${aiWorkflowRun.id}`,
submissionId: submission.id,
challengeName: challenge.name,
},
});
}
}