Skip to content

Commit

Permalink
feat(api): add a script to populate campaignParticipationId in keSnap…
Browse files Browse the repository at this point in the history
…shot
  • Loading branch information
lionelB committed Jan 14, 2025
1 parent 06fd58f commit d27b555
Show file tree
Hide file tree
Showing 2 changed files with 189 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { knex } from '../../db/knex-database-connection.js';
import { Script } from '../../src/shared/application/scripts/script.js';
import { ScriptRunner } from '../../src/shared/application/scripts/script-runner.js';

const DEFAULT_CHUNK_SIZE = 10000;
const DEFAULT_PAUSE_DURATION = 2000;

const pause = async (duration) => {
return Promise((resolve) => {
setTimeout(resolve, duration);
});
};

// Définition du script
export class PopulateCampaignParticipationIdScript extends Script {
constructor() {
super({
description:
'This script will populate the column knowledge-element-snapshots.campaignParticipationId with campaign-participations.id',
permanent: true,
options: {
chunkSize: {
type: 'number',
default: DEFAULT_CHUNK_SIZE,
description: 'number of records to update in one update',
},
pauseDuration: {
type: 'number',
default: DEFAULT_PAUSE_DURATION,
description: 'Time in ms between each chunk processing',
},
},
});
}

async handle({ options, logger, dependencies = { pause } }) {
const result = await knex('knowledge-element-snapshots').count().whereNull('campaignParticipationId').first();
logger.info(`Try to populate ${result.count} missing campaignParticipationId`);

let [firstId] = await knex('knowledge-element-snapshots')
.whereNull('campaignParticipationId')
.orderBy('id', 'asc')
.limit(1)
.pluck('id');

let updatedRows;

do {
// We remove one because sql between is inclusive [ -- ] and we want that [ -- [
const lastId = firstId + options.chunkSize - 1;
updatedRows = await knex('knowledge-element-snapshots')
.whereNull('campaignParticipationId')
.updateFrom('campaign-participations')
.update({
campaignParticipationId: knex.ref('campaign-participations.id'),
})
.where('knowledge-element-snapshots.snappedAt', knex.ref('campaign-participations.sharedAt'))
.where('knowledge-element-snapshots.userId', knex.ref('campaign-participations.userId'))
.whereBetween('knowledge-element-snapshots.id', [firstId, lastId]);
logger.info(`update ${updatedRows} rows from "knowledge-element-snapshots"`);
firstId += options.chunkSize;
if (updatedRows > 0 && options.pauseDuration > 0) {
await dependencies.pause(options.pauseDuration);
}
} while (updatedRows > 0);

const toUpdateResult = await knex('knowledge-element-snapshots')
.count()
.whereNull('campaignParticipationId')
.first();
logger.info(`${toUpdateResult.count} rows with empty campaignParticipationId to update`);
}
}

// Exécution du script
await ScriptRunner.execute(import.meta.url, PopulateCampaignParticipationIdScript);
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { PopulateCampaignParticipationIdScript } from '../../../../scripts/prod/populate-campaign-participation-id-in-knowledge-element-snapshot.js';
import { databaseBuilder, expect, knex, sinon } from '../../../test-helper.js';

describe('Script | Prod | Delete Organization Learners From Organization', function () {
describe('Options', function () {
it('has the correct options', function () {
const script = new PopulateCampaignParticipationIdScript();
const { options } = script.metaInfo;
expect(options.chunkSize).to.deep.include({
type: 'number',
default: 10000,
description: 'number of records to update in one update',
});
expect(options.pauseDuration).to.deep.include({
type: 'number',
default: 2000,
description: 'Time in ms between each chunk processing',
});
});
});

describe('Handle', function () {
let script;
let logger;
let dependencies;

beforeEach(async function () {
script = new PopulateCampaignParticipationIdScript();
logger = { info: sinon.spy(), error: sinon.spy(), debug: sinon.spy() };
dependencies = { pause: sinon.stub() };

const campaign = databaseBuilder.factory.buildCampaign();
const otherCampaign = databaseBuilder.factory.buildCampaign();

const user = databaseBuilder.factory.buildUser({ id: 123, firstName: 'Sam', lastName: 'Sagace' });
const learner = databaseBuilder.factory.buildOrganizationLearner({
id: 456,
firstName: 'Sam',
lastName: 'Sagace',
userId: user.id,
});
const participation = databaseBuilder.factory.buildCampaignParticipation({
organizationLearnerId: learner.id,
userId: user.id,
campaign: campaign.id,
participantExternalId: null,
createdAt: new Date('2024-12-15'),
sharedAt: new Date('2024-12-16'),
assessmentCreatedAt: new Date('2024-12-15'),
});
databaseBuilder.factory.knowledgeElementSnapshotFactory.buildSnapshot({
userId: user.id,
snappedAt: participation.sharedAt,
knowledgeElementsAttributes: [{ skillId: 'skill_1', status: 'validated', earnedPix: 40 }],
});
const otherParticipation = databaseBuilder.factory.buildCampaignParticipation({
organizationLearnerId: learner.id,
userId: user.id,
campaign: otherCampaign.id,
participantExternalId: null,
createdAt: new Date('2024-05-09'),
sharedAt: new Date('2024-05-12'),
assessmentCreatedAt: new Date('2024-05-09'),
});
databaseBuilder.factory.knowledgeElementSnapshotFactory.buildSnapshot({
userId: user.id,
snappedAt: otherParticipation.sharedAt,
knowledgeElementsAttributes: [{ skillId: 'skill_1', status: 'validated', earnedPix: 40 }],
});
await databaseBuilder.commit();
});

it('log how many entries will be updated', async function () {
await script.handle({ options: { chunkSize: 1, pauseDuration: 0 }, logger, dependencies });
expect(logger.info.calledWithExactly('Try to populate 2 missing campaignParticipationId')).to.be.true;
});

it('populate empty participations one by one', async function () {
await script.handle({ options: { chunkSize: 1, pauseDuration: 0 }, logger, dependencies });
const emptyKeSnaptshots = await knex('knowledge-element-snapshots')
.whereNull('campaignParticipationId')
.count()
.first();
expect(emptyKeSnaptshots.count).to.equals(0);
expect(logger.info).to.have.been.calledWith('update 1 rows from "knowledge-element-snapshots"');
expect(logger.info).to.have.been.calledWith('0 rows with empty campaignParticipationId to update');
});

it('populate empty participations using a chunk of 1000', async function () {
await script.handle({ options: { chunkSize: 1000, pauseDuration: 0 }, logger, dependencies });
const emptyKeSnaptshots = await knex('knowledge-element-snapshots')
.whereNull('campaignParticipationId')
.count()
.first();
expect(emptyKeSnaptshots.count).to.equals(0);
expect(logger.info).to.have.been.calledWith('update 2 rows from "knowledge-element-snapshots"');
expect(logger.info).to.have.been.calledWith('0 rows with empty campaignParticipationId to update');
});

it('should pause between chunk of 1', async function () {
dependencies.pause.resolves();
await script.handle({ options: { chunkSize: 1, pauseDuration: 10 }, logger, dependencies });
const emptyKeSnaptshots = await knex('knowledge-element-snapshots')
.whereNull('campaignParticipationId')
.count()
.first();

expect(emptyKeSnaptshots.count).to.equals(0);
expect(dependencies.pause).to.have.been.calledTwice;
expect(dependencies.pause).to.have.been.calledWith(10);
});
});
});

0 comments on commit d27b555

Please sign in to comment.