Skip to content

Commit

Permalink
[TECH] Support du format CSV pour le script de rescoring de certifica…
Browse files Browse the repository at this point in the history
…tion (PIX-15366).

 #10663
  • Loading branch information
pix-service-auto-merge authored Dec 2, 2024
2 parents 48c39fd + bcce378 commit 3cb34a7
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 50 deletions.
95 changes: 49 additions & 46 deletions api/scripts/certification/rescore-certifications.js
Original file line number Diff line number Diff line change
@@ -1,58 +1,61 @@
import 'dotenv/config';

import * as url from 'node:url';
import Joi from 'joi';

import { disconnect } from '../../db/knex-database-connection.js';
import { CertificationRescoringByScriptJob } from '../../src/certification/session-management/domain/models/CertificationRescoringByScriptJob.js';
import { certificationRescoringByScriptJobRepository } from '../../src/certification/session-management/infrastructure/repositories/jobs/certification-rescoring-by-script-job-repository.js';
import { logger } from '../../src/shared/infrastructure/utils/logger.js';
import { csvFileParser } from '../../src/shared/application/scripts/parsers.js';
import { Script } from '../../src/shared/application/scripts/script.js';
import { ScriptRunner } from '../../src/shared/application/scripts/script-runner.js';

const columnsSchemas = [{ name: 'certificationCourseId', schema: Joi.number() }];

export class RescoreCertificationScript extends Script {
constructor() {
super({
description: 'Rescore all certification given by CSV file. This script will schedule job to rescore',
permanent: true,
options: {
file: {
type: 'string',
describe:
'CSV File with only one column with certification-courses.id (integer) to process. Need `certificationCourseId`',
demandOption: true,
coerce: csvFileParser(columnsSchemas),
},
},
});
}

const modulePath = url.fileURLToPath(import.meta.url);
const isLaunchedFromCommandLine = process.argv[1] === modulePath;
async handle({ options, logger }) {
const { file: certificationCourses } = options;
const certificationCourseIds = certificationCourses.map(({ certificationCourseId }) => certificationCourseId);

async function main(certificationCourseIds) {
logger.info(`Publishing ${certificationCourseIds.length} rescoring jobs`);
const jobs = await _scheduleRescoringJobs(certificationCourseIds);
logger.info(`Publishing ${certificationCourseIds.length} rescoring jobs`);
const jobs = await this.#scheduleRescoringJobs(certificationCourseIds);

const errors = jobs.filter((result) => result.status === 'rejected');
if (errors.length) {
errors.forEach((result) => logger.error(result.reason, 'Some jobs could not be published'));
return 1;
}
const errors = jobs.filter((result) => result.status === 'rejected');
if (errors.length) {
errors.forEach((result) => logger.error(result.reason, 'Some jobs could not be published'));
return 1;
}

logger.info(`${jobs.length} jobs successfully published`);
return 0;
}
logger.info(`${jobs.length} jobs successfully published`);
return 0;
}

const _scheduleRescoringJobs = async (certificationCourseIds) => {
const promisefiedJobs = certificationCourseIds.map(async (certificationCourseId) => {
try {
await certificationRescoringByScriptJobRepository.performAsync(
new CertificationRescoringByScriptJob({ certificationCourseId }),
);
} catch (error) {
throw new Error(`Error for certificationCourseId: [${certificationCourseId}]`, { cause: error });
}
});
return Promise.allSettled(promisefiedJobs);
};

(async () => {
if (isLaunchedFromCommandLine) {
try {
const certificationCourseIds = process.argv[2]
.split(',')
.map((str) => parseInt(str, 10))
.filter(Number.isInteger);
const exitCode = await main(certificationCourseIds);
return exitCode;
} catch (error) {
logger.error(error);
process.exitCode = 1;
} finally {
await disconnect();
}
async #scheduleRescoringJobs(certificationCourseIds) {
const promisefiedJobs = certificationCourseIds.map(async (certificationCourseId) => {
try {
await certificationRescoringByScriptJobRepository.performAsync(
new CertificationRescoringByScriptJob({ certificationCourseId }),
);
} catch (error) {
throw new Error(`Error for certificationCourseId: [${certificationCourseId}]`, { cause: error });
}
});
return Promise.allSettled(promisefiedJobs);
}
})();
}

export { main };
await ScriptRunner.execute(import.meta.url, RescoreCertificationScript);
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
import { main } from '../../../../scripts/certification/rescore-certifications.js';
import { expect, knex } from '../../../test-helper.js';
import { RescoreCertificationScript } from '../../../../scripts/certification/rescore-certifications.js';
import { createTempFile, expect, knex, sinon } from '../../../test-helper.js';

describe('Integration | Scripts | Certification | rescore-certfication', function () {
it('should parse input file', async function () {
const script = new RescoreCertificationScript();
const { options } = script.metaInfo;
const file = 'certification-courses-ids-to-rescore.csv';
const data = 'certificationCourseId\n1\n2\n3\n';
const csvFilePath = await createTempFile(file, data);

const parsedData = await options.file.coerce(csvFilePath);

expect(parsedData).to.deep.equals([
{ certificationCourseId: 1 },
{ certificationCourseId: 2 },
{ certificationCourseId: 3 },
]);
});

it('should save pg boss jobs for each certification course ids', async function () {
// given
const certificationsCourseIdList = [1, 2];
const file = [{ certificationCourseId: 1 }, { certificationCourseId: 2 }];
const logger = { info: sinon.spy(), error: sinon.spy() };
const script = new RescoreCertificationScript();

// when
await main(certificationsCourseIdList);
await script.handle({ logger, options: { file } });

// then
const [job1, job2] = await knex('pgboss.job')
Expand Down

0 comments on commit 3cb34a7

Please sign in to comment.