Skip to content
Open
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
3 changes: 3 additions & 0 deletions src/jobs/dto/dataset-list.dto.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Expose } from "class-transformer";
import { IsArray, IsNotEmpty, IsString } from "class-validator";

export class DatasetListDto {
Expand All @@ -6,12 +7,14 @@ export class DatasetListDto {
*/
@IsString()
@IsNotEmpty()
@Expose()
readonly pid: string;

/**
* The value for the files key is an array of file names. This array is either an empty array, implying that all files within the dataset are selected, or an explicit list of dataset-relative file paths, which should be selected.
*/
@IsArray()
@IsString({ each: true })
@Expose()
readonly files: string[];
}
37 changes: 37 additions & 0 deletions src/jobs/dto/output-job-v3.dto.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,89 @@
import { ApiHideProperty } from "@nestjs/swagger";
import { DatasetListDto } from "./dataset-list.dto";
import { Expose, Transform } from "class-transformer";
import _ from "lodash";
import { jobV3toV4FieldMap } from "../types/jobs-filter-content";
import { JobClass } from "../schemas/job.schema";

const mapJobV3toV4Field = (
jobClass: JobClass,
key: keyof JobClass | keyof OutputJobV3Dto | string,
): OutputJobV3Dto[keyof OutputJobV3Dto] | JobClass[keyof JobClass] | null => {
if (!jobClass) return null;
return (
jobClass[key as keyof JobClass] ?? _.get(jobClass, jobV3toV4FieldMap[key])
);
};

export class OutputJobV3Dto {
@ApiHideProperty()
@Expose()
_id: string;

/**
* Globally unique identifier of a job.
*/
@Expose()
id: string;

/**
* The email of the person initiating the job request.
*/
@Expose()
@Transform(({ obj, key }) => mapJobV3toV4Field(obj, key))
emailJobInitiator?: string;

/**
* Type of job, e.g. archive, retrieve etc.
*/
@Expose()
type: string;

/**
* Time when job is created. Format according to chapter 5.6 internet date/time format in RFC 3339. This is handled automatically by mongoose with timestamps flag.
*/
@Expose()
@Transform(({ obj, key }) => mapJobV3toV4Field(obj, key))
creationTime: Date;

/**
* Time when job should be executed. If not specified then the Job will be executed asap. Format according to chapter 5.6 internet date/time format in RFC 3339.
*/
@Expose()
@Transform(({ obj, key }) => mapJobV3toV4Field(obj, key))
executionTime?: Date;

/**
* Object of key-value pairs defining job input parameters, e.g. 'destinationPath' for retrieve jobs or 'tapeCopies' for archive jobs.
*/
@Expose()
@Transform(({ obj }) => {
return {
username: _.get(obj, jobV3toV4FieldMap["jobParams.username"]),
..._.omitBy(obj?.jobParams, (_, key) =>
Object.values(jobV3toV4FieldMap).includes(`jobParams.${key}`),
),
};
})
jobParams: Record<string, unknown>;

/**
* Defines current status of job lifecycle.
*/
@Expose()
@Transform(({ obj, key }) => mapJobV3toV4Field(obj, key))
jobStatusMessage?: string;

/**
* Array of objects with keys: pid, files. The value for the pid key defines the dataset ID, the value for the files key is an array of file names. This array is either an empty array, implying that all files within the dataset are selected, or an explicit list of dataset-relative file paths, which should be selected.
*/
@Expose()
@Transform(({ obj, key }) => mapJobV3toV4Field(obj, key))
datasetList: DatasetListDto[];

/**
* Detailed return value after job is finished.
*/
@Expose()
jobResultObject: Record<string, unknown>;
}
23 changes: 13 additions & 10 deletions src/jobs/jobs.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
HttpStatus,
HttpException,
Req,
SerializeOptions,
ClassSerializerInterceptor,
} from "@nestjs/common";
import { Request } from "express";
import { JobsService } from "./jobs.service";
Expand Down Expand Up @@ -55,6 +57,7 @@ import { ALLOWED_JOB_KEYS, ALLOWED_JOB_FILTER_KEYS } from "./types/job-lookup";
@ApiBearerAuth()
@ApiTags("jobs")
@Controller({ path: "jobs", version: "3" })
@UseInterceptors(ClassSerializerInterceptor)
export class JobsController {
constructor(
private readonly jobsService: JobsService,
Expand Down Expand Up @@ -84,12 +87,13 @@ export class JobsController {
type: OutputJobV3Dto,
description: "Created job",
})
@SerializeOptions({ type: OutputJobV3Dto, excludeExtraneousValues: true })
async create(
@Req() request: Request,
@Body() createJobDto: CreateJobDto,
): Promise<OutputJobV3Dto | null> {
const job = await this.jobsControllerUtils.createJob(request, createJobDto);
return job ? this.jobsControllerUtils.mapJobClassV4toV3(job) : null;
return job as OutputJobV3Dto | null;
}

/**
Expand Down Expand Up @@ -120,6 +124,7 @@ export class JobsController {
type: OutputJobV3Dto,
description: "Updated job",
})
@SerializeOptions({ type: OutputJobV3Dto, excludeExtraneousValues: true })
async update(
@Req() request: Request,
@Param("id") id: string,
Expand All @@ -131,9 +136,7 @@ export class JobsController {
id,
updateJobDto,
);
return updatedJob
? this.jobsControllerUtils.mapJobClassV4toV3(updatedJob)
: null;
return updatedJob as OutputJobV3Dto | null;
}

/**
Expand Down Expand Up @@ -171,6 +174,7 @@ export class JobsController {
type: [OutputJobV3Dto],
description: "Return jobs requested.",
})
@SerializeOptions({ type: OutputJobV3Dto, excludeExtraneousValues: true })
async fullQuery(
@Req() request: Request,
@Query() filters: { fields?: string; limits?: string },
Expand All @@ -179,7 +183,7 @@ export class JobsController {
request,
filters,
)) as JobClass[] | null;
return jobs?.map(this.jobsControllerUtils.mapJobClassV4toV3) ?? null;
return jobs as OutputJobV3Dto[] | null;
}

/**
Expand Down Expand Up @@ -388,6 +392,7 @@ export class JobsController {
type: OutputJobV3Dto,
description: "Found job",
})
@SerializeOptions({ type: OutputJobV3Dto, excludeExtraneousValues: true })
async findOne(
@Req() request: Request,
@Param("id") id: string,
Expand All @@ -396,7 +401,7 @@ export class JobsController {
request,
id,
)) as JobClass | null;
return job ? this.jobsControllerUtils.mapJobClassV4toV3(job) : null;
return job as OutputJobV3Dto | null;
}

/**
Expand Down Expand Up @@ -429,6 +434,7 @@ export class JobsController {
type: [OutputJobV3Dto],
description: "Found jobs",
})
@SerializeOptions({ type: OutputJobV3Dto, excludeExtraneousValues: true })
async findAll(
@Req() request: Request,

Expand All @@ -448,10 +454,7 @@ export class JobsController {
request,
queryFilter,
)) as unknown as JobClass[] | null;
return (
jobs?.map(this.jobsControllerUtils.mapJobClassV4toV3) ??
([] as OutputJobV3Dto[])
);
return jobs as OutputJobV3Dto[] | [];
}

/**
Expand Down
30 changes: 0 additions & 30 deletions src/jobs/jobs.controller.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { CaslAbilityFactory } from "src/casl/casl-ability.factory";
import { Action } from "src/casl/action.enum";
import { CreateJobAuth, UpdateJobAuth } from "src/jobs/types/jobs-auth.enum";
import { JobClass, JobDocument } from "./schemas/job.schema";
import { OutputJobV3Dto } from "./dto/output-job-v3.dto";
import { IFacets, IFilters } from "src/common/interfaces/common.interface";
import { DatasetsService } from "src/datasets/datasets.service";
import { JobsConfigSchema } from "./types/jobs-config-schema.enum";
Expand Down Expand Up @@ -607,35 +606,6 @@ export class JobsControllerUtils {
}
}

/**
* Transform a v4 job instance so that is compatible with v3
* @param job: a JobClass instance (v4)
* @returns a OutputJobV3Dto instance
*/
mapJobClassV4toV3(job: JobClass): OutputJobV3Dto {
const jobV3 = new OutputJobV3Dto();
// Map fields from v4 to v3
jobV3._id = job._id;
jobV3.id = job.id;
jobV3.emailJobInitiator = job.contactEmail;
jobV3.type = job.type;
jobV3.creationTime = job.createdAt;
jobV3.jobStatusMessage = job.statusCode;
jobV3.jobResultObject = job.jobResultObject;
// Extract datasetList from jobParams
const { datasetList, ...jobParams } = job.jobParams;
jobV3.datasetList = datasetList as DatasetListDto[];
jobV3.jobParams = jobParams;
// Extract executionTime from jobParams
if (job.jobParams.executionTime) {
const { datasetList, executionTime, ...jobParams } = job.jobParams;
jobV3.datasetList = datasetList as DatasetListDto[];
jobV3.executionTime = executionTime as Date;
jobV3.jobParams = jobParams;
}
return jobV3;
}

/**
* Create job implementation
*/
Expand Down
8 changes: 8 additions & 0 deletions src/jobs/types/jobs-filter-content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,11 @@ export const getSwaggerJobFilterContent = (

// these fields must be present in a jobInstance, such that casl permissions can be assessed
export const mandatoryFields = ["_id", "id", "type", "ownerGroup", "ownerUser"];

export const jobV3toV4FieldMap: Record<string, string> = {
emailJobInitiator: "contactEmail",
creationTime: "createdAt",
jobStatusMessage: "statusCode",
executionTime: "jobParams.executionTime",
datasetList: "jobParams.datasetList",
};