Skip to content
Merged

0.9.1 #185

Show file tree
Hide file tree
Changes from all 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
6 changes: 5 additions & 1 deletion apps/backend/src/app/admin/admin.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { UnitTagsController } from './unit-tags/unit-tags.controller';
import { UnitNotesController } from './unit-notes/unit-notes.controller';
import { ResourcePackageController } from './resource-packages/resource-package.controller';
import { JournalController } from './workspace/journal.controller';
import { VariableAnalysisController } from './variable-analysis/variable-analysis.controller';
import { JobsController } from './jobs/jobs.controller';

@Module({
imports: [
Expand All @@ -35,7 +37,9 @@ import { JournalController } from './workspace/journal.controller';
UnitTagsController,
UnitNotesController,
ResourcePackageController,
JournalController
JournalController,
VariableAnalysisController,
JobsController
],
providers: []
})
Expand Down
31 changes: 31 additions & 0 deletions apps/backend/src/app/admin/jobs/dto/job.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Job } from '../../../database/entities/job.entity';

export class JobDto {
id: number;
workspace_id: number;
type: string;
status: string;
progress?: number;
error?: string;
result?: string;
created_at: Date;
updated_at: Date;

/**
* Static method to create a DTO from an entity
*/
static fromEntity(entity: Job): JobDto {
const dto = new JobDto();
dto.id = entity.id;
dto.workspace_id = entity.workspace_id;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
dto.type = (entity as any).type; // Type is added by TypeORM for inheritance
dto.status = entity.status;
dto.progress = entity.progress;
dto.error = entity.error;
dto.result = entity.result;
dto.created_at = entity.created_at;
dto.updated_at = entity.updated_at;
return dto;
}
}
152 changes: 152 additions & 0 deletions apps/backend/src/app/admin/jobs/jobs.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import {
BadRequestException,
Controller,
Get,
NotFoundException,
Param,
Post,
UseGuards
} from '@nestjs/common';
import {
ApiBadRequestResponse,
ApiBearerAuth,
ApiNotFoundResponse,
ApiOkResponse,
ApiOperation,
ApiParam,
ApiTags
} from '@nestjs/swagger';
import { JwtAuthGuard } from '../../auth/jwt-auth.guard';
import { WorkspaceGuard } from '../workspace/workspace.guard';
import { WorkspaceId } from '../workspace/workspace.decorator';
import { JobService } from '../../database/services/job.service';
import { JobDto } from './dto/job.dto';

@ApiTags('Jobs')
@Controller('admin/workspace/:workspace_id/jobs')
export class JobsController {
constructor(private readonly jobService: JobService) {}

@Get()
@UseGuards(JwtAuthGuard, WorkspaceGuard)
@ApiBearerAuth()
@ApiOperation({
summary: 'Get all jobs for a workspace',
description: 'Retrieves all jobs for a workspace'
})
@ApiParam({
name: 'workspace_id',
type: Number,
required: true,
description: 'The ID of the workspace'
})
@ApiOkResponse({
description: 'The jobs have been successfully retrieved.',
type: [JobDto]
})
@ApiBadRequestResponse({
description: 'Invalid input data.'
})
@ApiNotFoundResponse({
description: 'Workspace not found.'
})
async getJobs(@WorkspaceId() workspaceId: number): Promise<JobDto[]> {
try {
const jobs = await this.jobService.getJobs(workspaceId);
return jobs.map(job => JobDto.fromEntity(job));
} catch (error) {
if (error instanceof NotFoundException) {
throw error;
}
throw new BadRequestException(`Failed to retrieve jobs: ${error.message}`);
}
}

@Get(':job_id')
@UseGuards(JwtAuthGuard, WorkspaceGuard)
@ApiBearerAuth()
@ApiOperation({
summary: 'Get a job by ID',
description: 'Retrieves a job by ID'
})
@ApiParam({
name: 'workspace_id',
type: Number,
required: true,
description: 'The ID of the workspace'
})
@ApiParam({
name: 'job_id',
type: Number,
required: true,
description: 'The ID of the job'
})
@ApiOkResponse({
description: 'The job has been successfully retrieved.',
type: JobDto
})
@ApiNotFoundResponse({
description: 'Job not found.'
})
async getJob(
@WorkspaceId() workspaceId: number,
@Param('job_id') jobId: number
): Promise<JobDto> {
try {
const job = await this.jobService.getJob(jobId, workspaceId);
return JobDto.fromEntity(job);
} catch (error) {
if (error instanceof NotFoundException) {
throw error;
}
throw new BadRequestException(`Failed to retrieve job: ${error.message}`);
}
}

@Post(':job_id/cancel')
@UseGuards(JwtAuthGuard, WorkspaceGuard)
@ApiBearerAuth()
@ApiOperation({
summary: 'Cancel a job',
description: 'Cancels a job by ID'
})
@ApiParam({
name: 'workspace_id',
type: Number,
required: true,
description: 'The ID of the workspace'
})
@ApiParam({
name: 'job_id',
type: Number,
required: true,
description: 'The ID of the job'
})
@ApiOkResponse({
description: 'The job has been successfully cancelled.',
schema: {
type: 'object',
properties: {
success: { type: 'boolean' },
message: { type: 'string' }
}
}
})
@ApiNotFoundResponse({
description: 'Job not found.'
})
async cancelJob(
@WorkspaceId() workspaceId: number,
@Param('job_id') jobId: number
): Promise<{ success: boolean; message: string }> {
try {
await this.jobService.getJob(jobId, workspaceId);
return await this.jobService.cancelJob(jobId);
} catch (error) {
if (error instanceof NotFoundException) {
throw error;
}
throw new BadRequestException(`Failed to cancel job: ${error.message}`);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { VariableAnalysisJob } from '../../../database/entities/variable-analysis-job.entity';

export class VariableAnalysisJobDto {
id: number;
workspace_id: number;

/**
* Optional unit ID to filter by
*/
unit_id?: number;

/**
* Optional variable ID to filter by
*/
variable_id?: string;

/**
* Status of the job: 'pending', 'processing', 'completed', 'failed'
*/
status: string;
error?: string;
created_at: Date;
updated_at: Date;

/**
* Type of the job, used for inheritance discrimination
*/
type?: string;

/**
* Static method to create a DTO from an entity
*/
static fromEntity(entity: VariableAnalysisJob): VariableAnalysisJobDto {
const dto = new VariableAnalysisJobDto();
dto.id = entity.id;
dto.workspace_id = entity.workspace_id;
dto.unit_id = entity.unit_id;
dto.variable_id = entity.variable_id;
dto.status = entity.status;
dto.error = entity.error;
dto.created_at = entity.created_at;
dto.updated_at = entity.updated_at;
dto.type = entity.type;
return dto;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { VariableFrequencyDto } from './variable-frequency.dto';

export interface VariableCombo {
unitName: string;
variableId: string;
}

export class VariableAnalysisResultDto {
variableCombos: VariableCombo[];
frequencies: { [key: string]: VariableFrequencyDto[] };
total: number;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* DTO for variable frequency data
*/
export class VariableFrequencyDto {
/**
* The ID of the variable
*/
variableId: string;

/**
* The value of the variable
*/
value: string;

/**
* The count of occurrences of this value
*/
count: number;

/**
* The percentage of occurrences of this value
*/
percentage: number;
}

/**
* DTO for variable analysis result
*/
export class VariableAnalysisResultDto {
/**
* List of variable IDs
*/
variables: string[];

/**
* Map of variable ID to frequency data
*/
frequencies: { [key: string]: VariableFrequencyDto[] };
total: number;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export class VariableFrequencyDto {
unitName?: string;
variableId: string;
value: string;
count: number;
percentage: number;
}
Loading