Skip to content

Commit

Permalink
♻️ refactor: rewrite script to respect [pix/docs/adr/0056-standardisa…
Browse files Browse the repository at this point in the history
  • Loading branch information
yaf and HEYGUL authored Dec 2, 2024
1 parent 46000a0 commit bcce378
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 65 deletions.
107 changes: 48 additions & 59 deletions api/scripts/certification/rescore-certifications.js
Original file line number Diff line number Diff line change
@@ -1,72 +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 { parseCsv } from '../helpers/csvHelpers.js';

const modulePath = url.fileURLToPath(import.meta.url);
const isLaunchedFromCommandLine = process.argv[1] === modulePath;

/**
* Usage: node scripts/certification/rescore-certifications.js path/file.csv
* File has only one column of certification-courses.id (integer), no header
**/
async function main(filePath) {
logger.info('Reading and parsing csv data file... ');
const certificationCourseIds = await extractCsvData(filePath);

logger.info(`Publishing ${certificationCourseIds.length} rescoring jobs`);
const jobs = await _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;
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),
},
},
});
}

logger.info(`${jobs.length} jobs successfully published`);
return 0;
}
async handle({ options, logger }) {
const { file: certificationCourses } = options;
const certificationCourseIds = certificationCourses.map(({ certificationCourseId }) => certificationCourseId);

async function extractCsvData(filePath) {
const dataRows = await parseCsv(filePath, { header: false, skipEmptyLines: true });
return dataRows.reduce((certificationCourseIds, dataRow) => {
const certificationCenterId = parseInt(dataRow[0]);
certificationCourseIds.push(certificationCenterId);
return certificationCourseIds;
}, []);
}
logger.info(`Publishing ${certificationCourseIds.length} rescoring jobs`);
const jobs = await this.#scheduleRescoringJobs(certificationCourseIds);

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 });
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;
}
});
return Promise.allSettled(promisefiedJobs);
};

(async () => {
if (isLaunchedFromCommandLine) {
try {
const filePath = process.argv[2];
const exitCode = await main(filePath);
return exitCode;
} catch (error) {
logger.error(error);
process.exitCode = 1;
} finally {
await disconnect();
}
logger.info(`${jobs.length} jobs successfully published`);
return 0;
}
})();

export { main };
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);
}
}

await ScriptRunner.execute(import.meta.url, RescoreCertificationScript);
Original file line number Diff line number Diff line change
@@ -1,15 +1,31 @@
import { main } from '../../../../scripts/certification/rescore-certifications.js';
import { createTempFile, 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 save pg boss jobs for each certification course ids', async function () {
// given
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 = '1\n2\n';
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 file = [{ certificationCourseId: 1 }, { certificationCourseId: 2 }];
const logger = { info: sinon.spy(), error: sinon.spy() };
const script = new RescoreCertificationScript();

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

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

0 comments on commit bcce378

Please sign in to comment.