Skip to content

Commit

Permalink
✨ api: script to migrate isCancelled to assessment-results
Browse files Browse the repository at this point in the history
Co-authored-by: Andreia Pena <[email protected]>
Co-authored-by: Geoffroy Begouaussel <[email protected]>
  • Loading branch information
3 people authored Feb 18, 2025
1 parent 5f36561 commit 25caaf3
Show file tree
Hide file tree
Showing 3 changed files with 409 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import { knex } from '../../db/knex-database-connection.js';
import { AutoJuryCommentKeys } from '../../src/certification/shared/domain/models/JuryComment.js';
import { Script } from '../../src/shared/application/scripts/script.js';
import { ScriptRunner } from '../../src/shared/application/scripts/script-runner.js';
import { config } from '../../src/shared/config.js';
import { AssessmentResult } from '../../src/shared/domain/models/index.js';

export class CreateAssessmentResultForCancelledCertificationScript extends Script {
constructor() {
super({
description: 'Copy certification-course.isCancelled to assessment-results table',
permanent: false,
options: {
dryRun: {
type: 'boolean',
describe: 'Commit in the database, or rollback',
demandOption: true,
},
batchSize: {
type: 'number',
describe: 'Number of certifications to update at once',
demandOption: false,
default: 1000,
},
delayBetweenBatch: {
type: 'number',
describe: 'In ms, force a pause between SQL COMMIT',
demandOption: false,
default: 100,
},
},
});

this.totalNumberOfImpactedCertifications = 0;
}

async handle({ options, logger }) {
this.logger = logger;
const dryRun = options.dryRun;
const batchSize = options.batchSize;
const delayInMs = options.delayBetweenBatch;
this.logger.info(`dryRun=${dryRun} batchSize=${batchSize}`);

let hasNext = true;
let cursorId = 0;

do {
const transaction = await knex.transaction();
try {
const assessmentResultsToDuplicate = await getNextCancelledAssessmentResultsToDuplicate({
cursorId,
batchSize,
transaction,
});

for (const assessmentResult of assessmentResultsToDuplicate) {
const newCancelledAssessmentResultId = await duplicateCancelledAssessmentResult({
assessmentResult,
transaction,
});

const competenceMarks = await getCompetenceMarks({
assessmentResultId: assessmentResult.id,
transaction,
});

for (const competenceMark of competenceMarks) {
await duplicateCompetenceMark({
competenceMark,
newCancelledAssessmentResultId,
transaction,
});
}
}

dryRun ? await transaction.rollback() : await transaction.commit();

// Prepare for next batch
hasNext = assessmentResultsToDuplicate.length > 0;
cursorId = assessmentResultsToDuplicate?.at(-1)?.id;
this.totalNumberOfImpactedCertifications += assessmentResultsToDuplicate.length || 0;

this.logger.info(`Waiting ${delayInMs}ms before next batch`);
await this.delay(delayInMs);
} catch (error) {
await transaction.rollback();
throw error;
}
} while (hasNext);

this.logger.info(
`Number of impacted certifications:[${this.totalNumberOfImpactedCertifications}] (dryRun:[${dryRun}])`,
);
return 0;
}

async delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
}

const getNextCancelledAssessmentResultsToDuplicate = async ({ batchSize, transaction, cursorId = 0 }) => {
return transaction
.from('certification-courses-last-assessment-results')
.select('assessment-results.*')
.forUpdate()
.innerJoin(
'certification-courses',
'certification-courses.id',
'certification-courses-last-assessment-results.certificationCourseId',
)
.innerJoin(
'assessment-results',
'assessment-results.id',
'certification-courses-last-assessment-results.lastAssessmentResultId',
)
.where('certification-courses.isCancelled', '=', true)
.andWhere('assessment-results.status', '!=', 'cancelled')
.andWhere('certification-courses-last-assessment-results.lastAssessmentResultId', '>', cursorId)
.orderBy('assessment-results.id')
.limit(batchSize);
};

const duplicateCancelledAssessmentResult = async ({ assessmentResult, transaction }) => {
const [newAssessmentResult] = await transaction('assessment-results')
.insert({
level: assessmentResult.level,
pixScore: assessmentResult.pixScore,
commentByJury: assessmentResult.commentByJury,
commentForOrganization: assessmentResult.commentForOrganization,
commentForCandidate: assessmentResult.commentForCandidate,
assessmentId: assessmentResult.assessmentId,
reproducibilityRate: assessmentResult.reproducibilityRate,
commentByAutoJury: getCommentByAutoJury({ commentByAutoJury: assessmentResult.commentByAutoJury }),
emitter: assessmentResult.emitter,
status: AssessmentResult.status.CANCELLED,
juryId: config.infra.engineeringUserId,
createdAt: assessmentResult.createdAt,
})
.returning('id');

await transaction('certification-courses-last-assessment-results')
.update({ lastAssessmentResultId: newAssessmentResult.id })
.where('lastAssessmentResultId', assessmentResult.id);

return newAssessmentResult.id;
};

const duplicateCompetenceMark = async ({ competenceMark, newCancelledAssessmentResultId, transaction }) => {
await transaction('competence-marks')
.insert({
level: competenceMark.level,
score: competenceMark.score,
area_code: competenceMark.area_code,
competence_code: competenceMark.competence_code,
assessmentResultId: newCancelledAssessmentResultId,
competenceId: competenceMark.competenceId,
createdAt: competenceMark.createdAt,
})
.returning('id');
};

const getCompetenceMarks = async ({ assessmentResultId, transaction }) => {
return transaction('competence-marks').where({
assessmentResultId,
});
};

const getCommentByAutoJury = ({ commentByAutoJury }) => {
const isCancelledCommentByAutoJury = [
AutoJuryCommentKeys.CANCELLED_DUE_TO_NEUTRALIZATION,
AutoJuryCommentKeys.CANCELLED_DUE_TO_LACK_OF_ANSWERS_FOR_TECHNICAL_REASON,
].includes(commentByAutoJury);

return isCancelledCommentByAutoJury ? commentByAutoJury : null;
};

await ScriptRunner.execute(import.meta.url, CreateAssessmentResultForCancelledCertificationScript);
3 changes: 3 additions & 0 deletions api/src/shared/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ const configuration = (function () {
process.env.INFRA_CHUNK_SIZE_ORGANIZATION_LEARNER_DATA_PROCESSING,
1000,
),
engineeringUserId: process.env.ENGINEERING_USER_ID,
metricsFlushIntervalSecond: _getNumber(process.env.METRICS_FLUSH_INTERVAL_SECOND, 15),
startJobInWebProcess: toBoolean(process.env.START_JOB_IN_WEB_PROCESS),
},
Expand Down Expand Up @@ -696,6 +697,8 @@ const configuration = (function () {
config.identityProviderConfigKey = null;

config.apiManager.url = 'http://external-partners-access/';

config.infra.engineeringUserId = 800;
}

return config;
Expand Down
Loading

0 comments on commit 25caaf3

Please sign in to comment.