Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ workflows:
only:
- develop
- feat/ai-workflows
- pm-1791

- 'build-prod':
context: org-global
Expand Down
16 changes: 16 additions & 0 deletions src/api/ai-workflow/ai-workflow.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { AiWorkflowService } from './ai-workflow.service';
import {
CreateAiWorkflowDto,
UpdateAiWorkflowDto,
CreateAiWorkflowRunItemsDto,
} from '../../dto/aiWorkflow.dto';
import { Scopes } from 'src/shared/decorators/scopes.decorator';
import { UserRole } from 'src/shared/enums/userRole.enum';
Expand All @@ -44,6 +45,21 @@ export class AiWorkflowController {
return this.aiWorkflowService.createWithValidation(createAiWorkflowDto);
}

@Post('/:workflowId/runs/:runId/items')
@Scopes(Scope.CreateWorkflowRun)
@ApiOperation({ summary: 'Create AIWorkflowRunItems in batch' })
@ApiResponse({ status: 201, description: 'AIWorkflowRunItems created successfully.' })
@ApiResponse({ status: 400, description: 'Bad Request.' })
@ApiResponse({ status: 401, description: 'Unauthorized.' })
@ApiResponse({ status: 404, description: 'Workflow or Run not found.' })
async createRunItems(
@Param('workflowId') workflowId: string,
@Param('runId') runId: string,
@Body(new ValidationPipe({ whitelist: true, transform: true })) createItemsDto: CreateAiWorkflowRunItemsDto,
) {
return this.aiWorkflowService.createRunItemsBatch(workflowId, runId, createItemsDto.items);
}

@Get(':id')
@Roles(UserRole.Admin, UserRole.User, UserRole.Copilot, UserRole.Reviewer)
@Scopes(Scope.ReadWorkflow)
Expand Down
50 changes: 50 additions & 0 deletions src/api/ai-workflow/ai-workflow.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,54 @@ export class AiWorkflowService {
data: updateDto,
});
}

async createRunItemsBatch(workflowId: string, runId: string, items: any[]) {
const workflow = await this.prisma.aiWorkflow.findUnique({
where: { id: workflowId },
});
if (!workflow) {
this.logger.error(`Workflow with id ${workflowId} not found.`);
throw new NotFoundException(`Workflow with id ${workflowId} not found.`);
}

const run = await this.prisma.aiWorkflowRun.findUnique({
where: { id: runId },
include: { workflow: true },
});
if (!run || run.workflowId !== workflowId) {
this.logger.error(`Run with id ${runId} not found or does not belong to workflow ${workflowId}.`);
throw new NotFoundException(`Run with id ${runId} not found or does not belong to workflow ${workflowId}.`);
}

for (const item of items) {
if (!item.scorecardQuestionId || !item.content) {
this.logger.error(`Invalid item: scorecardQuestionId and content are required.`);
throw new BadRequestException(`Each item must have scorecardQuestionId and content.`);
}
const questionExists = await this.prisma.scorecardQuestion.findUnique({
where: { id: item.scorecardQuestionId },
});
if (!questionExists) {
this.logger.error(`ScorecardQuestion with id ${item.scorecardQuestionId} not found.`);
throw new BadRequestException(`ScorecardQuestion with id ${item.scorecardQuestionId} not found.`);
}
}

const createdItems = await this.prisma.aiWorkflowRunItem.createMany({
data: items.map(item => ({
workflowRunId: runId,
scorecardQuestionId: item.scorecardQuestionId,
content: item.content,
upVotes: item.upVotes ?? 0,
downVotes: item.downVotes ?? 0,
questionScore: item.questionScore ?? null,
createdAt: new Date(),
// TODO: Remove this once prisma middleware implementation is done
createdBy: '',
})),
skipDuplicates: true,
});

return { createdCount: createdItems.count };
}
}
39 changes: 38 additions & 1 deletion src/dto/aiWorkflow.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ApiProperty, PartialType } from '@nestjs/swagger';
import { IsString, IsNotEmpty } from 'class-validator';
import { IsString, IsNotEmpty, IsArray, ValidateNested, IsOptional, IsInt, Min } from 'class-validator';
import { Type } from 'class-transformer';

export class CreateAiWorkflowDto {
@ApiProperty()
Expand Down Expand Up @@ -39,3 +40,39 @@ export class CreateAiWorkflowDto {
}

export class UpdateAiWorkflowDto extends PartialType(CreateAiWorkflowDto) {}

export class CreateAiWorkflowRunItemDto {
@ApiProperty()
@IsString()
@IsNotEmpty()
scorecardQuestionId: string;

@ApiProperty()
@IsString()
@IsNotEmpty()
content: string;

@ApiProperty({ required: false })
@IsOptional()
@IsInt()
@Min(0)
upVotes?: number;

@ApiProperty({ required: false })
@IsOptional()
@IsInt()
@Min(0)
downVotes?: number;

@ApiProperty({ required: false })
@IsOptional()
questionScore?: number;
}

export class CreateAiWorkflowRunItemsDto {
@ApiProperty({ type: [CreateAiWorkflowRunItemDto] })
@IsArray()
@ValidateNested({ each: true })
@Type(() => CreateAiWorkflowRunItemDto)
items: CreateAiWorkflowRunItemDto[];
}
1 change: 1 addition & 0 deletions src/shared/enums/scopes.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export enum Scope {
CreateWorkflow = 'create:workflow',
ReadWorkflow = 'read:workflow',
UpdateWorkflow = 'update:workflow',
CreateWorkflowRun = 'create:workflow-run',
}

/**
Expand Down