From 05ab844fa76a7ed1707a6752197c9cb5029f404b Mon Sep 17 00:00:00 2001 From: alicegoarnisson Date: Tue, 4 Feb 2025 10:54:08 +0100 Subject: [PATCH 1/4] tech(orga): inlining count validatedByCompetencesForUsers --- .../campaign-collective-result-repository.js | 24 +- ...paign-collective-result-repository_test.js | 978 ++++++++++-------- 2 files changed, 529 insertions(+), 473 deletions(-) diff --git a/api/src/prescription/campaign/infrastructure/repositories/campaign-collective-result-repository.js b/api/src/prescription/campaign/infrastructure/repositories/campaign-collective-result-repository.js index 5269e72aa99..170195952a2 100644 --- a/api/src/prescription/campaign/infrastructure/repositories/campaign-collective-result-repository.js +++ b/api/src/prescription/campaign/infrastructure/repositories/campaign-collective-result-repository.js @@ -2,11 +2,9 @@ import _ from 'lodash'; import { knex } from '../../../../../db/knex-database-connection.js'; import { CHUNK_SIZE_CAMPAIGN_RESULT_PROCESSING } from '../../../../shared/infrastructure/constants.js'; -import * as knowledgeElementRepository from '../../../../shared/infrastructure/repositories/knowledge-element-repository.js'; import { CampaignParticipationStatuses } from '../../../shared/domain/constants.js'; import { CampaignCollectiveResult } from '../../domain/read-models/CampaignCollectiveResult.js'; -import { getLatestParticipationSharedForOneLearner } from './helpers/get-latest-participation-shared-for-one-learner.js'; - +import * as knowledgeElementSnapshotRepository from './knowledge-element-snapshot-repository.js'; const { SHARED } = CampaignParticipationStatuses; const getCampaignCollectiveResult = async function (campaignId, campaignLearningContent) { @@ -20,10 +18,9 @@ const getCampaignCollectiveResult = async function (campaignId, campaignLearning let participantCount = 0; for (const userIdsAndSharedDates of userIdsAndSharedDatesChunks) { participantCount += userIdsAndSharedDates.length; - const knowledgeElementsGroupedByUser = await knowledgeElementRepository.findSnapshotForUsers( - Object.fromEntries(userIdsAndSharedDates), - ); - const knowledgeElements = Object.values(knowledgeElementsGroupedByUser).flat(); + const knowledgeElementsGroupedByCampaignParticipationId = + await knowledgeElementSnapshotRepository.findByCampaignParticipationIds(userIdsAndSharedDates); + const knowledgeElements = Object.values(knowledgeElementsGroupedByCampaignParticipationId).flat(); const validatedTargetedKnowledgeElementsCountByCompetenceId = campaignLearningContent.countValidatedTargetedKnowledgeElementsByCompetence(knowledgeElements); campaignCollectiveResult.addValidatedSkillCountToCompetences(validatedTargetedKnowledgeElementsCountByCompetenceId); @@ -37,16 +34,11 @@ export { getCampaignCollectiveResult }; async function _getChunksSharedParticipationsWithUserIdsAndDates(campaignId) { const results = await knex - .from('campaign-participations as cp') - .select([ - getLatestParticipationSharedForOneLearner(knex, 'sharedAt', campaignId), - 'userId', - 'organizationLearnerId', - ]) + .from('campaign-participations') + .max('id') .where({ campaignId, status: SHARED, deletedAt: null }) .groupBy('userId', 'organizationLearnerId'); - const userIdsAndDates = results.map((result) => [result.userId, result.sharedAt]); - - return _.chunk(userIdsAndDates, CHUNK_SIZE_CAMPAIGN_RESULT_PROCESSING); + const ids = results.map(({ max }) => max); + return _.chunk(ids, CHUNK_SIZE_CAMPAIGN_RESULT_PROCESSING); } diff --git a/api/tests/prescription/campaign/integration/infrastructure/repositories/campaign-collective-result-repository_test.js b/api/tests/prescription/campaign/integration/infrastructure/repositories/campaign-collective-result-repository_test.js index 2ba6cce9fd8..d5c937dbc73 100644 --- a/api/tests/prescription/campaign/integration/infrastructure/repositories/campaign-collective-result-repository_test.js +++ b/api/tests/prescription/campaign/integration/infrastructure/repositories/campaign-collective-result-repository_test.js @@ -392,7 +392,6 @@ describe('Integration | Repository | Campaign collective result repository', fun context('when there is a single participant who shared its contribution', function () { beforeEach(function () { - const longTimeAgo = new Date('2018-01-01'); const beforeCampaignParticipationShareDate = new Date('2019-01-01'); const userWithCampaignParticipationFred = _createUserWithSharedCampaignParticipation( 'Fred', @@ -402,83 +401,78 @@ describe('Integration | Repository | Campaign collective result repository', fun ); const fredId = userWithCampaignParticipationFred.userId; - _.each( - [ - { - userId: fredId, - competenceId: 'recCompetenceA', - skillId: 'recUrl1', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: fredId, - competenceId: 'recCompetenceA', - skillId: 'recUrl2', - status: 'invalidated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: fredId, - competenceId: 'recCompetenceA', - skillId: 'recUrl3', - status: 'invalidated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: fredId, - competenceId: 'recCompetenceB', - skillId: 'recFile2', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: fredId, - competenceId: 'recCompetenceB', - skillId: 'recFile3', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: fredId, - competenceId: 'recCompetenceB', - skillId: 'recFile5', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: fredId, - competenceId: 'recCompetenceB', - skillId: 'recText1', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: fredId, - competenceId: 'recCompetenceC', - skillId: 'recMedia1', - status: 'invalidated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: fredId, - competenceId: 'recCompetenceF', - skillId: 'recComputer1', - status: 'validated', - createdAt: longTimeAgo, - }, - { - userId: fredId, - competenceId: 'recCompetenceF', - skillId: 'recComputer1', - status: 'invalidated', - createdAt: beforeCampaignParticipationShareDate, - }, - ], - (knowledgeElement) => { - databaseBuilder.factory.buildKnowledgeElement(knowledgeElement); + const knowledgeElements = [ + { + userId: fredId, + competenceId: 'recCompetenceA', + skillId: 'recUrl1', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, }, - ); + { + userId: fredId, + competenceId: 'recCompetenceA', + skillId: 'recUrl2', + status: 'invalidated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: fredId, + competenceId: 'recCompetenceA', + skillId: 'recUrl3', + status: 'invalidated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: fredId, + competenceId: 'recCompetenceB', + skillId: 'recFile2', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: fredId, + competenceId: 'recCompetenceB', + skillId: 'recFile3', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: fredId, + competenceId: 'recCompetenceB', + skillId: 'recFile5', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: fredId, + competenceId: 'recCompetenceB', + skillId: 'recText1', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: fredId, + competenceId: 'recCompetenceC', + skillId: 'recMedia1', + status: 'invalidated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: fredId, + competenceId: 'recCompetenceF', + skillId: 'recComputer1', + status: 'invalidated', + createdAt: beforeCampaignParticipationShareDate, + }, + ]; + + databaseBuilder.factory.buildKnowledgeElementSnapshot({ + userId: fredId, + campaignParticipationId: userWithCampaignParticipationFred.campaignParticipation.id, + snappedAt: userWithCampaignParticipationFred.sharedAt, + snapshot: JSON.stringify(knowledgeElements.map(domainBuilder.buildKnowledgeElement)), + }); return databaseBuilder.commit(); }); @@ -557,342 +551,392 @@ describe('Integration | Repository | Campaign collective result repository', fun /* KNOWLEDGE ELEMENTS */ - _.each( - [ - // Alice - { - userId: aliceId, - competenceId: 'recCompetenceA', - skillId: 'recUrl1', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: aliceId, - competenceId: 'recCompetenceA', - skillId: 'recUrl2', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: aliceId, - competenceId: 'recCompetenceA', - skillId: 'recUrl3', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - - { - userId: aliceId, - competenceId: 'recCompetenceB', - skillId: 'recFile2', - status: 'invalidated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: aliceId, - competenceId: 'recCompetenceB', - skillId: 'recFile3', - status: 'invalidated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: aliceId, - competenceId: 'recCompetenceB', - skillId: 'recFile5', - status: 'invalidated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: aliceId, - competenceId: 'recCompetenceB', - skillId: 'recText1', - status: 'invalidated', - createdAt: beforeCampaignParticipationShareDate, - }, - - { - userId: aliceId, - competenceId: 'recCompetenceC', - skillId: 'recMedia1', - status: 'invalidated', - createdAt: beforeCampaignParticipationShareDate, - }, - - { - userId: aliceId, - competenceId: 'recCompetenceD', - skillId: 'recAlgo1', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: aliceId, - competenceId: 'recCompetenceD', - skillId: 'recAlgo2', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - - { - userId: aliceId, - competenceId: 'recCompetenceF', - skillId: 'recComputer1', - status: 'validated', - createdAt: longTimeAgo, - }, - { - userId: aliceId, - competenceId: 'recCompetenceF', - skillId: 'recComputer1', - status: 'invalidated', - createdAt: beforeCampaignParticipationShareDate, - }, - - // Bob - { - userId: bobId, - competenceId: 'recCompetenceA', - skillId: 'recUrl1', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: bobId, - competenceId: 'recCompetenceA', - skillId: 'recUrl2', - status: 'invalidated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: bobId, - competenceId: 'recCompetenceA', - skillId: 'recUrl3', - status: 'invalidated', - createdAt: beforeCampaignParticipationShareDate, - }, - - { - userId: bobId, - competenceId: 'recCompetenceB', - skillId: 'recFile2', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: bobId, - competenceId: 'recCompetenceB', - skillId: 'recFile3', - status: 'validated', - createdAt: beforeBeforeCampaignParticipationShareDate, - }, - { - userId: bobId, - competenceId: 'recCompetenceB', - skillId: 'recFile3', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: bobId, - competenceId: 'recCompetenceB', - skillId: 'recFile5', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: bobId, - competenceId: 'recCompetenceB', - skillId: 'recFile5', - status: 'invalidated', - createdAt: afterCampaignParticipationShareDate, - }, - { - userId: bobId, - competenceId: 'recCompetenceB', - skillId: 'recText1', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - - { - userId: bobId, - competenceId: 'recCompetenceC', - skillId: 'recMedia1', - status: 'invalidated', - createdAt: beforeCampaignParticipationShareDate, - }, - - { - userId: bobId, - competenceId: 'recCompetenceF', - skillId: 'recComputer1', - status: 'invalidated', - createdAt: longTimeAgo, - }, - { - userId: bobId, - competenceId: 'recCompetenceF', - skillId: 'recComputer1', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - - // Charlie - { - userId: charlieId, - competenceId: 'recCompetenceA', - skillId: 'recUrl1', - status: 'invalidated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: charlieId, - competenceId: 'recCompetenceA', - skillId: 'recUrl2', - status: 'invalidated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: charlieId, - competenceId: 'recCompetenceA', - skillId: 'recUrl3', - status: 'invalidated', - createdAt: beforeCampaignParticipationShareDate, - }, - - { - userId: charlieId, - competenceId: 'recCompetenceB', - skillId: 'recFile2', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: charlieId, - competenceId: 'recCompetenceB', - skillId: 'recFile3', - status: 'invalidated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: charlieId, - competenceId: 'recCompetenceB', - skillId: 'recFile5', - status: 'invalidated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: charlieId, - competenceId: 'recCompetenceB', - skillId: 'recText1', - status: 'invalidated', - createdAt: beforeCampaignParticipationShareDate, - }, - - { - userId: charlieId, - competenceId: 'recCompetenceC', - skillId: 'recMedia1', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: charlieId, - competenceId: 'recCompetenceC', - skillId: 'recMedia2', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - - { - userId: charlieId, - competenceId: 'recCompetenceF', - skillId: 'recComputer1', - status: 'validated', - createdAt: longTimeAgo, - }, - { - userId: charlieId, - competenceId: 'recCompetenceF', - skillId: 'recComputer1', - status: 'invalidated', - createdAt: afterCampaignParticipationShareDate, - }, - - // Dan - { - userId: danId, - competenceId: 'recCompetenceA', - skillId: 'recUrl1', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: danId, - competenceId: 'recCompetenceA', - skillId: 'recUrl2', - status: 'invalidated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: danId, - competenceId: 'recCompetenceA', - skillId: 'recUrl3', - status: 'invalidated', - createdAt: beforeCampaignParticipationShareDate, - }, - - { - userId: danId, - competenceId: 'recCompetenceB', - skillId: 'recFile2', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: danId, - competenceId: 'recCompetenceB', - skillId: 'recFile3', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: danId, - competenceId: 'recCompetenceB', - skillId: 'recFile5', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: danId, - competenceId: 'recCompetenceB', - skillId: 'recText1', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - - { - userId: danId, - competenceId: 'recCompetenceC', - skillId: 'recMedia1', - status: 'invalidated', - createdAt: beforeCampaignParticipationShareDate, - }, - - // Elo - { - userId: eloId, - competenceId: 'recCompetenceA', - skillId: 'recUrl1', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - ], - (knowledgeElement) => { - databaseBuilder.factory.buildKnowledgeElement(knowledgeElement); + const knowledgeElementsDataAlice = [ + { + userId: aliceId, + competenceId: 'recCompetenceA', + skillId: 'recUrl1', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: aliceId, + competenceId: 'recCompetenceA', + skillId: 'recUrl2', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: aliceId, + competenceId: 'recCompetenceA', + skillId: 'recUrl3', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + + { + userId: aliceId, + competenceId: 'recCompetenceB', + skillId: 'recFile2', + status: 'invalidated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: aliceId, + competenceId: 'recCompetenceB', + skillId: 'recFile3', + status: 'invalidated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: aliceId, + competenceId: 'recCompetenceB', + skillId: 'recFile5', + status: 'invalidated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: aliceId, + competenceId: 'recCompetenceB', + skillId: 'recText1', + status: 'invalidated', + createdAt: beforeCampaignParticipationShareDate, + }, + + { + userId: aliceId, + competenceId: 'recCompetenceC', + skillId: 'recMedia1', + status: 'invalidated', + createdAt: beforeCampaignParticipationShareDate, + }, + + { + userId: aliceId, + competenceId: 'recCompetenceD', + skillId: 'recAlgo1', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: aliceId, + competenceId: 'recCompetenceD', + skillId: 'recAlgo2', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + + { + userId: aliceId, + competenceId: 'recCompetenceF', + skillId: 'recComputer1', + status: 'validated', + createdAt: longTimeAgo, + }, + { + userId: aliceId, + competenceId: 'recCompetenceF', + skillId: 'recComputer1', + status: 'invalidated', + createdAt: beforeCampaignParticipationShareDate, + }, + ]; + + const knowledgeElementsDataBob = [ + // Bob + { + userId: bobId, + competenceId: 'recCompetenceA', + skillId: 'recUrl1', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: bobId, + competenceId: 'recCompetenceA', + skillId: 'recUrl2', + status: 'invalidated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: bobId, + competenceId: 'recCompetenceA', + skillId: 'recUrl3', + status: 'invalidated', + createdAt: beforeCampaignParticipationShareDate, + }, + + { + userId: bobId, + competenceId: 'recCompetenceB', + skillId: 'recFile2', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: bobId, + competenceId: 'recCompetenceB', + skillId: 'recFile3', + status: 'validated', + createdAt: beforeBeforeCampaignParticipationShareDate, + }, + { + userId: bobId, + competenceId: 'recCompetenceB', + skillId: 'recFile3', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: bobId, + competenceId: 'recCompetenceB', + skillId: 'recFile5', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: bobId, + competenceId: 'recCompetenceB', + skillId: 'recFile5', + status: 'invalidated', + createdAt: afterCampaignParticipationShareDate, + }, + { + userId: bobId, + competenceId: 'recCompetenceB', + skillId: 'recText1', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + + { + userId: bobId, + competenceId: 'recCompetenceC', + skillId: 'recMedia1', + status: 'invalidated', + createdAt: beforeCampaignParticipationShareDate, + }, + + { + userId: bobId, + competenceId: 'recCompetenceF', + skillId: 'recComputer1', + status: 'invalidated', + createdAt: longTimeAgo, + }, + { + userId: bobId, + competenceId: 'recCompetenceF', + skillId: 'recComputer1', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + ]; + + const knowledgeElementsDataCharlie = [ + { + userId: charlieId, + competenceId: 'recCompetenceA', + skillId: 'recUrl1', + status: 'invalidated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: charlieId, + competenceId: 'recCompetenceA', + skillId: 'recUrl2', + status: 'invalidated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: charlieId, + competenceId: 'recCompetenceA', + skillId: 'recUrl3', + status: 'invalidated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: charlieId, + competenceId: 'recCompetenceB', + skillId: 'recFile2', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: charlieId, + competenceId: 'recCompetenceB', + skillId: 'recFile3', + status: 'invalidated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: charlieId, + competenceId: 'recCompetenceB', + skillId: 'recFile5', + status: 'invalidated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: charlieId, + competenceId: 'recCompetenceB', + skillId: 'recText1', + status: 'invalidated', + createdAt: beforeCampaignParticipationShareDate, + }, + + { + userId: charlieId, + competenceId: 'recCompetenceC', + skillId: 'recMedia1', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: charlieId, + competenceId: 'recCompetenceC', + skillId: 'recMedia2', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + + { + userId: charlieId, + competenceId: 'recCompetenceF', + skillId: 'recComputer1', + status: 'validated', + createdAt: longTimeAgo, + }, + { + userId: charlieId, + competenceId: 'recCompetenceF', + skillId: 'recComputer1', + status: 'invalidated', + createdAt: afterCampaignParticipationShareDate, + }, + ]; + + const knowledgeElementsDataDan = [ + // Dan + { + userId: danId, + competenceId: 'recCompetenceA', + skillId: 'recUrl1', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: danId, + competenceId: 'recCompetenceA', + skillId: 'recUrl2', + status: 'invalidated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: danId, + competenceId: 'recCompetenceA', + skillId: 'recUrl3', + status: 'invalidated', + createdAt: beforeCampaignParticipationShareDate, + }, + + { + userId: danId, + competenceId: 'recCompetenceB', + skillId: 'recFile2', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: danId, + competenceId: 'recCompetenceB', + skillId: 'recFile3', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: danId, + competenceId: 'recCompetenceB', + skillId: 'recFile5', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: danId, + competenceId: 'recCompetenceB', + skillId: 'recText1', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + + { + userId: danId, + competenceId: 'recCompetenceC', + skillId: 'recMedia1', + status: 'invalidated', + createdAt: beforeCampaignParticipationShareDate, }, + ]; + const knowledgeElementsDataElo = [ + { + userId: eloId, + competenceId: 'recCompetenceA', + skillId: 'recUrl1', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + ]; + + const knowledgeElementsAlice = knowledgeElementsDataAlice.map(databaseBuilder.factory.buildKnowledgeElement); + const snapshotsAlice = _(knowledgeElementsAlice).orderBy('createdAt', 'desc').uniqBy('skillId').value(); + + databaseBuilder.factory.buildKnowledgeElementSnapshot({ + userId: userWithCampaignParticipationAlice.userId, + snappedAt: userWithCampaignParticipationAlice.campaignParticipation.sharedAt, + snapshot: JSON.stringify(snapshotsAlice), + campaignParticipationId: userWithCampaignParticipationAlice.campaignParticipation.id, + }); + + const knowledgeElementsCharlie = knowledgeElementsDataCharlie.map( + databaseBuilder.factory.buildKnowledgeElement, ); + const snapshotsCharlie = _(knowledgeElementsCharlie) + .filter((o) => o.createdAt <= campaignParticipationShareDate) + .orderBy('createdAt', 'desc') + .uniqBy('skillId') + .value(); + + databaseBuilder.factory.buildKnowledgeElementSnapshot({ + userId: userWithCampaignParticipationCharlie.userId, + snappedAt: userWithCampaignParticipationCharlie.campaignParticipation.sharedAt, + snapshot: JSON.stringify(snapshotsCharlie), + campaignParticipationId: userWithCampaignParticipationCharlie.campaignParticipation.id, + }); + + const knowledgeElementsBob = knowledgeElementsDataBob.map(databaseBuilder.factory.buildKnowledgeElement); + const snapshotsBob = _(knowledgeElementsBob) + .filter((o) => o.createdAt <= campaignParticipationShareDate) + .orderBy('createdAt', 'desc') + .uniqBy('skillId') + .value(); + + databaseBuilder.factory.buildKnowledgeElementSnapshot({ + userId: userWithCampaignParticipationBob.userId, + snappedAt: userWithCampaignParticipationBob.campaignParticipation.sharedAt, + snapshot: JSON.stringify(snapshotsBob), + campaignParticipationId: userWithCampaignParticipationBob.campaignParticipation.id, + }); + + knowledgeElementsDataDan.map(databaseBuilder.factory.buildKnowledgeElement); + + const knowledgeElementsElo = knowledgeElementsDataElo.map(databaseBuilder.factory.buildKnowledgeElement); + const snapshotsElo = _(knowledgeElementsElo).orderBy('createdAt', 'desc').uniqBy('skillId').value(); + + databaseBuilder.factory.buildKnowledgeElementSnapshot({ + userId: userWithCampaignParticipationElo.userId, + snappedAt: userWithCampaignParticipationElo.campaignParticipation.sharedAt, + snapshot: JSON.stringify(snapshotsElo), + campaignParticipationId: userWithCampaignParticipationElo.campaignParticipation.id, + }); return databaseBuilder.commit(); }); @@ -924,14 +968,8 @@ describe('Integration | Repository | Campaign collective result repository', fun beforeEach(async function () { const { id: userId } = databaseBuilder.factory.buildUser(); const organizationLearnerId = databaseBuilder.factory.buildOrganizationLearner({ userId }).id; - databaseBuilder.factory.buildCampaignParticipation({ - campaignId, - userId, - organizationLearnerId, - sharedAt: new Date('2020-01-01'), - isImproved: true, - }); - databaseBuilder.factory.buildCampaignParticipation({ + + const campaignParticipation = databaseBuilder.factory.buildCampaignParticipation({ campaignId, userId, organizationLearnerId, @@ -939,7 +977,7 @@ describe('Integration | Repository | Campaign collective result repository', fun isImproved: false, }); - databaseBuilder.factory.buildKnowledgeElement({ + const knowledgeElement = databaseBuilder.factory.buildKnowledgeElement({ userId, competenceId: 'recCompetenceA', skillId: 'recUrl1', @@ -947,6 +985,13 @@ describe('Integration | Repository | Campaign collective result repository', fun createdAt: new Date('2020-01-03'), }); + databaseBuilder.factory.buildKnowledgeElementSnapshot({ + userId: userId, + snappedAt: campaignParticipation.sharedAt, + snapshot: JSON.stringify([knowledgeElement]), + campaignParticipationId: campaignParticipation.id, + }); + await databaseBuilder.commit(); }); @@ -991,44 +1036,63 @@ describe('Integration | Repository | Campaign collective result repository', fun /* KNOWLEDGE ELEMENTS */ - _.each( - [ - // Alice - { - userId: aliceId, - competenceId: 'recOldCompetence', - skillId: 'recUrl1', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: aliceId, - competenceId: 'recOldCompetence', - skillId: 'recUrl2', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - - // Bob - { - userId: bobId, - competenceId: 'recCompetenceA', - skillId: 'recUrl1', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: bobId, - competenceId: 'recCompetenceA', - skillId: 'recUrl2', - status: 'invalidated', - createdAt: beforeCampaignParticipationShareDate, - }, - ], - (knowledgeElement) => { - databaseBuilder.factory.buildKnowledgeElement(knowledgeElement); + const knowledgeElementsDataAlice = [ + { + userId: aliceId, + competenceId: 'recOldCompetence', + skillId: 'recUrl1', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, }, - ); + { + userId: aliceId, + competenceId: 'recOldCompetence', + skillId: 'recUrl2', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + ]; + + const knowledgeElementsDataBob = [ + { + userId: bobId, + competenceId: 'recCompetenceA', + skillId: 'recUrl1', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: bobId, + competenceId: 'recCompetenceA', + skillId: 'recUrl2', + status: 'invalidated', + createdAt: beforeCampaignParticipationShareDate, + }, + ]; + + const knowledgeElementsAlice = knowledgeElementsDataAlice.map(databaseBuilder.factory.buildKnowledgeElement); + const snapshotsAlice = _(knowledgeElementsAlice).orderBy('createdAt', 'desc').uniqBy('skillId').value(); + + const knowledgeElementsBob = knowledgeElementsDataBob.map(databaseBuilder.factory.buildKnowledgeElement); + const snapshotsBob = _(knowledgeElementsBob) + .filter((o) => o.createdAt <= campaignParticipationShareDate) + .orderBy('createdAt', 'desc') + .uniqBy('skillId') + .value(); + + databaseBuilder.factory.buildKnowledgeElementSnapshot({ + userId: userWithCampaignParticipationAlice.userId, + snappedAt: userWithCampaignParticipationAlice.campaignParticipation.sharedAt, + snapshot: JSON.stringify(snapshotsAlice), + campaignParticipationId: userWithCampaignParticipationAlice.campaignParticipation.id, + }); + + databaseBuilder.factory.buildKnowledgeElementSnapshot({ + userId: userWithCampaignParticipationBob.userId, + snappedAt: userWithCampaignParticipationBob.campaignParticipation.sharedAt, + snapshot: JSON.stringify(snapshotsBob), + campaignParticipationId: userWithCampaignParticipationBob.campaignParticipation.id, + }); return databaseBuilder.commit(); }); From aaf2b1d418ebdf47dcc6a58c6851ad4eab371a15 Mon Sep 17 00:00:00 2001 From: alicegoarnisson Date: Tue, 4 Feb 2025 10:55:31 +0100 Subject: [PATCH 2/4] tech(orga): inlining countValidatedTargetedKnowledgeElementsByCompetence in campaignAssessmentParticipationResult --- ...essment-participation-result-repository.js | 5 +++-- ...ampaign-participation-result-repository.js | 20 ++++++++++++++----- ...gn-participation-result-repository_test.js | 2 ++ 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/api/src/prescription/campaign-participation/infrastructure/repositories/campaign-assessment-participation-result-repository.js b/api/src/prescription/campaign-participation/infrastructure/repositories/campaign-assessment-participation-result-repository.js index ea4c1581657..04b0154479e 100644 --- a/api/src/prescription/campaign-participation/infrastructure/repositories/campaign-assessment-participation-result-repository.js +++ b/api/src/prescription/campaign-participation/infrastructure/repositories/campaign-assessment-participation-result-repository.js @@ -49,8 +49,9 @@ async function _fetchCampaignAssessmentParticipationResultAttributesFromCampaign } async function _buildCampaignAssessmentParticipationResults(result, campaignLearningContent) { - const knowledgeElementsGroupedByUser = await knowledgeElementRepository.findSnapshotForUsers({ - [result.userId]: result.sharedAt, + const knowledgeElementsGroupedByUser = await knowledgeElementRepository.findAssessedByUserIdAndLimitDateQuery({ + userId: result.userId, + limitDate: result.sharedAt, }); const knowledgeElements = Object.values(knowledgeElementsGroupedByUser).flat(); const validatedTargetedKnowledgeElementsCountByCompetenceId = diff --git a/api/src/prescription/campaign-participation/infrastructure/repositories/campaign-participation-result-repository.js b/api/src/prescription/campaign-participation/infrastructure/repositories/campaign-participation-result-repository.js index f4b9447c1d1..b0cbbd24878 100644 --- a/api/src/prescription/campaign-participation/infrastructure/repositories/campaign-participation-result-repository.js +++ b/api/src/prescription/campaign-participation/infrastructure/repositories/campaign-participation-result-repository.js @@ -4,19 +4,18 @@ import * as assessmentRepository from '../../../../shared/infrastructure/reposit import * as competenceRepository from '../../../../shared/infrastructure/repositories/competence-repository.js'; import * as knowledgeElementRepository from '../../../../shared/infrastructure/repositories/knowledge-element-repository.js'; import * as campaignRepository from '../../../campaign/infrastructure/repositories/campaign-repository.js'; +import * as knowledgeElementSnapshotRepository from '../../../campaign/infrastructure/repositories/knowledge-element-snapshot-repository.js'; import * as campaignParticipationRepository from './campaign-participation-repository.js'; const campaignParticipationResultRepository = { async getByParticipationId(campaignParticipationId) { const campaignParticipation = await campaignParticipationRepository.get(campaignParticipationId); - const [skillIds, competences, assessment, snapshots] = await Promise.all([ + const [skillIds, competences, assessment, knowledgeElements] = await Promise.all([ campaignRepository.findSkillIds({ campaignId: campaignParticipation.campaignId }), competenceRepository.list(), assessmentRepository.get(campaignParticipation.lastAssessment.id), - knowledgeElementRepository.findSnapshotForUsers({ - [campaignParticipation.userId]: campaignParticipation.sharedAt, - }), + getKnowledgeElements(campaignParticipation), ]); const allAreas = await areaRepository.list(); @@ -25,10 +24,21 @@ const campaignParticipationResultRepository = { assessment, competences, skillIds, - knowledgeElements: snapshots[campaignParticipation.userId], + knowledgeElements, allAreas, }); }, }; +async function getKnowledgeElements(campaignParticipation) { + const snapshot = await knowledgeElementSnapshotRepository.findByCampaignParticipationIds([campaignParticipation.id]); + if (snapshot[campaignParticipation.id]) { + return snapshot[campaignParticipation.id]; + } + + return knowledgeElementRepository.findUniqByUserId({ + userId: campaignParticipation.userId, + }); +} + export { campaignParticipationResultRepository }; diff --git a/api/tests/prescription/campaign-participation/integration/infrastructure/repositories/campaign-participation-result-repository_test.js b/api/tests/prescription/campaign-participation/integration/infrastructure/repositories/campaign-participation-result-repository_test.js index 8698c02ef24..83c25c05ad8 100644 --- a/api/tests/prescription/campaign-participation/integration/infrastructure/repositories/campaign-participation-result-repository_test.js +++ b/api/tests/prescription/campaign-participation/integration/infrastructure/repositories/campaign-participation-result-repository_test.js @@ -134,6 +134,7 @@ describe('Integration | Repository | Campaign Participation Result', function () userId, snappedAt: new Date('2020-01-02'), knowledgeElementsAttributes, + campaignParticipationId, }); await databaseBuilder.commit(); const campaignAssessmentParticipationResult = @@ -195,6 +196,7 @@ describe('Integration | Repository | Campaign Participation Result', function () userId, snappedAt: new Date('2020-01-02'), knowledgeElementsAttributes, + campaignParticipationId, }); await databaseBuilder.commit(); const campaignAssessmentParticipationResult = From c711567ba0d43a491494ef74b67d320e38ee8331 Mon Sep 17 00:00:00 2001 From: alicegoarnisson Date: Tue, 4 Feb 2025 17:17:35 +0100 Subject: [PATCH 3/4] tech(api): from rebase --- .../campaign-analysis-repository.js | 37 +- ...ign-assessment-participation-repository.js | 8 +- .../campaign-profile-repository.js | 2 + .../participant-results-shared-repository.js | 23 +- .../knowledge-element-snapshot-repository.js | 28 +- .../services/placement-profile-service.js | 14 +- .../knowledge-element-repository.js | 36 +- .../campaign-participation-route_test.js | 9 + .../campaign-analysis-repository_test.js | 231 +++++----- ...ssessment-participation-repository_test.js | 80 ++-- .../campaign-profile-repository_test.js | 41 +- ...ticipant-results-shared-repository_test.js | 1 + .../campaign-results-route_test.js | 9 +- ...wledge-element-snapshot-repository_test.js | 98 +++++ .../placement-profile-service_test.js | 73 +++- .../knowledge-element-repository_test.js | 399 +----------------- .../cypress/support/step_definitions/index.js | 1 + 17 files changed, 429 insertions(+), 661 deletions(-) diff --git a/api/src/prescription/campaign-participation/infrastructure/repositories/campaign-analysis-repository.js b/api/src/prescription/campaign-participation/infrastructure/repositories/campaign-analysis-repository.js index bd5df846a9b..ae2032426d3 100644 --- a/api/src/prescription/campaign-participation/infrastructure/repositories/campaign-analysis-repository.js +++ b/api/src/prescription/campaign-participation/infrastructure/repositories/campaign-analysis-repository.js @@ -2,16 +2,15 @@ import _ from 'lodash'; import { knex } from '../../../../../db/knex-database-connection.js'; import { CHUNK_SIZE_CAMPAIGN_RESULT_PROCESSING } from '../../../../../src/shared/infrastructure/constants.js'; -import * as knowledgeElementRepository from '../../../../shared/infrastructure/repositories/knowledge-element-repository.js'; import { CampaignAnalysis } from '../../../campaign/domain/read-models/CampaignAnalysis.js'; +import * as knowledgeElementSnapshotRepository from '../../../campaign/infrastructure/repositories/knowledge-element-snapshot-repository.js'; import { CampaignParticipationStatuses } from '../../../shared/domain/constants.js'; - const { SHARED } = CampaignParticipationStatuses; const getCampaignAnalysis = async function (campaignId, campaignLearningContent, tutorials) { - const userIdsAndSharedDates = await _getSharedParticipationsWithUserIdsAndDates(campaignId); - const userIdsAndSharedDatesChunks = _.chunk(userIdsAndSharedDates, CHUNK_SIZE_CAMPAIGN_RESULT_PROCESSING); - const participantCount = userIdsAndSharedDates.length; + const campaignParticipationIds = await _getSharedParticipationsId(campaignId); + const campaignParticipationIdsChunks = _.chunk(campaignParticipationIds, CHUNK_SIZE_CAMPAIGN_RESULT_PROCESSING); + const participantCount = campaignParticipationIds.length; const campaignAnalysis = new CampaignAnalysis({ campaignId, @@ -20,10 +19,12 @@ const getCampaignAnalysis = async function (campaignId, campaignLearningContent, participantCount, }); - for (const userIdsAndSharedDates of userIdsAndSharedDatesChunks) { - const knowledgeElementsByTube = await knowledgeElementRepository.findValidatedGroupedByTubesWithinCampaign( - Object.fromEntries(userIdsAndSharedDates), - campaignLearningContent, + for (const campaignParticipationIdChunk of campaignParticipationIdsChunks) { + const knowledgeElementsByParticipation = + await knowledgeElementSnapshotRepository.findByCampaignParticipationIds(campaignParticipationIdChunk); + + const knowledgeElementsByTube = campaignLearningContent.getValidatedKnowledgeElementsGroupedByTube( + Object.values(knowledgeElementsByParticipation).flat(), ); campaignAnalysis.addToTubeRecommendations({ knowledgeElementsByTube }); } @@ -45,9 +46,10 @@ const getCampaignParticipationAnalysis = async function ( participantCount: 1, }); - const knowledgeElementsByTube = await knowledgeElementRepository.findValidatedGroupedByTubesWithinCampaign( - { [campaignParticipation.userId]: campaignParticipation.sharedAt }, - campaignLearningContent, + const snapshot = await knowledgeElementSnapshotRepository.findByCampaignParticipationIds([campaignParticipation.id]); + + const knowledgeElementsByTube = campaignLearningContent.getValidatedKnowledgeElementsGroupedByTube( + snapshot[campaignParticipation.id], ); campaignAnalysis.addToTubeRecommendations({ knowledgeElementsByTube }); @@ -57,15 +59,10 @@ const getCampaignParticipationAnalysis = async function ( export { getCampaignAnalysis, getCampaignParticipationAnalysis }; -async function _getSharedParticipationsWithUserIdsAndDates(campaignId) { +async function _getSharedParticipationsId(campaignId) { const results = await knex('campaign-participations') - .select('userId', 'sharedAt') + .pluck('id') .where({ campaignId, status: SHARED, isImproved: false, deletedAt: null }); - const userIdsAndDates = []; - for (const result of results) { - userIdsAndDates.push([result.userId, result.sharedAt]); - } - - return userIdsAndDates; + return results; } diff --git a/api/src/prescription/campaign-participation/infrastructure/repositories/campaign-assessment-participation-repository.js b/api/src/prescription/campaign-participation/infrastructure/repositories/campaign-assessment-participation-repository.js index aec7d1b4eab..f3732242d3b 100644 --- a/api/src/prescription/campaign-participation/infrastructure/repositories/campaign-assessment-participation-repository.js +++ b/api/src/prescription/campaign-participation/infrastructure/repositories/campaign-assessment-participation-repository.js @@ -80,13 +80,13 @@ async function _setSkillsCount(result) { if (result.assessmentState !== Assessment.states.COMPLETED) { const operativeSkillIds = await campaignRepository.findSkillIds({ campaignId: result.campaignId }); - const knowledgeElementsByUser = await knowledgeElementRepository.findSnapshotForUsers({ - [result.userId]: result.sharedAt, + const knowledgeElementsByUser = await knowledgeElementRepository.findAssessedByUserIdAndLimitDateQuery({ + userId: result.userId, + limitDate: result.sharedAt, }); - const knowledgeElements = knowledgeElementsByUser[result.userId]; targetedSkillsCount = operativeSkillIds.length; - testedSkillsCount = _getTestedSkillsCount(operativeSkillIds, knowledgeElements); + testedSkillsCount = _getTestedSkillsCount(operativeSkillIds, knowledgeElementsByUser); } return { targetedSkillsCount, testedSkillsCount }; diff --git a/api/src/prescription/campaign-participation/infrastructure/repositories/campaign-profile-repository.js b/api/src/prescription/campaign-participation/infrastructure/repositories/campaign-profile-repository.js index c8660ccde4c..cabf99e7d62 100644 --- a/api/src/prescription/campaign-participation/infrastructure/repositories/campaign-profile-repository.js +++ b/api/src/prescription/campaign-participation/infrastructure/repositories/campaign-profile-repository.js @@ -11,11 +11,13 @@ const findProfile = async function ({ campaignId, campaignParticipationId, local const allAreas = await areaRepository.list({ locale }); const { sharedAt, userId } = profile; + const placementProfile = await placementProfileService.getPlacementProfileWithSnapshotting({ userId, limitDate: sharedAt, allowExcessPixAndLevels: false, competences, + campaignParticipationId, }); return new CampaignProfile({ ...profile, placementProfile, allAreas }); diff --git a/api/src/prescription/campaign-participation/infrastructure/repositories/participant-results-shared-repository.js b/api/src/prescription/campaign-participation/infrastructure/repositories/participant-results-shared-repository.js index b44f389721b..dd55ca9805e 100644 --- a/api/src/prescription/campaign-participation/infrastructure/repositories/participant-results-shared-repository.js +++ b/api/src/prescription/campaign-participation/infrastructure/repositories/participant-results-shared-repository.js @@ -2,23 +2,9 @@ import { knex } from '../../../../../db/knex-database-connection.js'; import * as campaignRepository from '../../../../../src/prescription/campaign/infrastructure/repositories/campaign-repository.js'; import * as placementProfileService from '../../../../shared/domain/services/placement-profile-service.js'; import * as competenceRepository from '../../../../shared/infrastructure/repositories/competence-repository.js'; +import * as knowledgeElementSnapshotRepository from '../../../campaign/infrastructure/repositories/knowledge-element-snapshot-repository.js'; import { ParticipantResultsShared } from '../../domain/models/ParticipantResultsShared.js'; -async function _fetchKnowledgeElements(campaignParticipationId) { - const { snapshot: knowledgeElements } = await knex('campaign-participations') - .select('snapshot') - .join('knowledge-element-snapshots', function () { - this.on('knowledge-element-snapshots.userId', '=', 'campaign-participations.userId').andOn( - 'knowledge-element-snapshots.snappedAt', - '=', - 'campaign-participations.sharedAt', - ); - }) - .where('campaign-participations.id', campaignParticipationId) - .first(); - return knowledgeElements; -} - function _fetchUserIdAndSharedAt(campaignParticipationId) { return knex('campaign-participations') .select('userId', 'sharedAt') @@ -35,7 +21,9 @@ const participantResultsSharedRepository = { const skillIds = await campaignRepository.findSkillIdsByCampaignParticipationId({ campaignParticipationId, }); - const knowledgeElements = await _fetchKnowledgeElements(campaignParticipationId); + const knowledgeElements = await knowledgeElementSnapshotRepository.findByCampaignParticipationIds([ + campaignParticipationId, + ]); const { userId, sharedAt } = await _fetchUserIdAndSharedAt(campaignParticipationId); const competences = await competenceRepository.listPixCompetencesOnly(); @@ -44,11 +32,12 @@ const participantResultsSharedRepository = { limitDate: sharedAt, allowExcessPixAndLevels: false, competences, + campaignParticipationId, }); return new ParticipantResultsShared({ campaignParticipationId, - knowledgeElements, + knowledgeElements: knowledgeElements[campaignParticipationId], skillIds, placementProfile, }); diff --git a/api/src/prescription/campaign/infrastructure/repositories/knowledge-element-snapshot-repository.js b/api/src/prescription/campaign/infrastructure/repositories/knowledge-element-snapshot-repository.js index d3d6e49b1de..30a3d6ece87 100644 --- a/api/src/prescription/campaign/infrastructure/repositories/knowledge-element-snapshot-repository.js +++ b/api/src/prescription/campaign/infrastructure/repositories/knowledge-element-snapshot-repository.js @@ -95,4 +95,30 @@ const findMultipleUsersFromUserIdsAndSnappedAtDates = async function (userIdsAnd }); }; -export { findByUserIdsAndSnappedAtDates, findMultipleUsersFromUserIdsAndSnappedAtDates, save }; +/** + * + * @param {number[]} campaignParticipationIds + * @returns {Object.} + */ +const findByCampaignParticipationIds = async function (campaignParticipationIds) { + const results = await knex + .select('campaignParticipationId', 'snapshot') + .from('knowledge-element-snapshots') + .whereIn('campaignParticipationId', campaignParticipationIds); + + return Object.fromEntries( + results.map(({ campaignParticipationId, snapshot }) => [ + campaignParticipationId, + snapshot.map(({ createdAt, ...data }) => { + return new KnowledgeElement({ ...data, createdAt: new Date(createdAt) }); + }), + ]), + ); +}; + +export { + findByCampaignParticipationIds, + findByUserIdsAndSnappedAtDates, + findMultipleUsersFromUserIdsAndSnappedAtDates, + save, +}; diff --git a/api/src/shared/domain/services/placement-profile-service.js b/api/src/shared/domain/services/placement-profile-service.js index 77bf9945d92..81916034756 100644 --- a/api/src/shared/domain/services/placement-profile-service.js +++ b/api/src/shared/domain/services/placement-profile-service.js @@ -158,11 +158,15 @@ async function getPlacementProfilesWithSnapshotting({ userIdsAndDates, competenc }); } -async function getPlacementProfileWithSnapshotting({ userId, limitDate, competences, allowExcessPixAndLevels = true }) { - const snapshots = await knowledgeElementRepository.findSnapshotForUsers({ - [userId]: limitDate, - }); - const knowledgeElements = snapshots[userId]; +async function getPlacementProfileWithSnapshotting({ + userId, + limitDate, + competences, + allowExcessPixAndLevels = true, + campaignParticipationId, +}) { + const snapshots = await knowledgeElementSnapshotRepository.findByCampaignParticipationIds([campaignParticipationId]); + const knowledgeElements = snapshots[campaignParticipationId]; const knowledgeElementsByCompetence = _.groupBy(knowledgeElements, 'competenceId'); const userCompetences = _createUserCompetencesV2({ diff --git a/api/src/shared/infrastructure/repositories/knowledge-element-repository.js b/api/src/shared/infrastructure/repositories/knowledge-element-repository.js index 74ac9e305ca..0539a1c459b 100644 --- a/api/src/shared/infrastructure/repositories/knowledge-element-repository.js +++ b/api/src/shared/infrastructure/repositories/knowledge-element-repository.js @@ -1,7 +1,6 @@ import _ from 'lodash'; import { knex } from '../../../../db/knex-database-connection.js'; -import * as knowledgeElementSnapshotRepository from '../../../prescription/campaign/infrastructure/repositories/knowledge-element-snapshot-repository.js'; import { DomainTransaction } from '../../domain/DomainTransaction.js'; import { KnowledgeElement } from '../../domain/models/KnowledgeElement.js'; @@ -33,7 +32,7 @@ function _findByUserIdAndLimitDateQuery({ userId, limitDate, skillIds = [] }) { }); } -async function _findAssessedByUserIdAndLimitDateQuery({ userId, limitDate, skillIds }) { +async function findAssessedByUserIdAndLimitDateQuery({ userId, limitDate, skillIds }) { const knowledgeElementRows = await _findByUserIdAndLimitDateQuery({ userId, limitDate, skillIds }); const knowledgeElements = _.map( @@ -43,28 +42,10 @@ async function _findAssessedByUserIdAndLimitDateQuery({ userId, limitDate, skill return _applyFilters(knowledgeElements); } -async function findSnapshotForUsers(userIdsAndDates) { - const knowledgeElementsGroupedByUser = - await knowledgeElementSnapshotRepository.findByUserIdsAndSnappedAtDates(userIdsAndDates); - - for (const [userIdStr, knowledgeElementsFromSnapshot] of Object.entries(knowledgeElementsGroupedByUser)) { - const userId = parseInt(userIdStr); - let knowledgeElements = knowledgeElementsFromSnapshot; - if (!knowledgeElements) { - knowledgeElements = await _findAssessedByUserIdAndLimitDateQuery({ - userId, - limitDate: userIdsAndDates[userId], - }); - } - knowledgeElementsGroupedByUser[userId] = knowledgeElements; - } - return knowledgeElementsGroupedByUser; -} - const findUniqByUserIds = function (userIds) { return Promise.all( userIds.map(async (userId) => { - const knowledgeElements = await _findAssessedByUserIdAndLimitDateQuery({ + const knowledgeElements = await findAssessedByUserIdAndLimitDateQuery({ userId, }); @@ -81,7 +62,7 @@ const batchSave = async function ({ knowledgeElements }) { }; const findUniqByUserId = function ({ userId, limitDate, skillIds }) { - return _findAssessedByUserIdAndLimitDateQuery({ userId, limitDate, skillIds }); + return findAssessedByUserIdAndLimitDateQuery({ userId, limitDate, skillIds }); }; const findUniqByUserIdAndAssessmentId = async function ({ userId, assessmentId }) { @@ -96,7 +77,7 @@ const findUniqByUserIdAndAssessmentId = async function ({ userId, assessmentId } }; const findUniqByUserIdAndCompetenceId = async function ({ userId, competenceId }) { - const knowledgeElements = await _findAssessedByUserIdAndLimitDateQuery({ userId }); + const knowledgeElements = await findAssessedByUserIdAndLimitDateQuery({ userId }); return knowledgeElements.filter((knowledgeElement) => knowledgeElement.competenceId === competenceId); }; @@ -105,12 +86,6 @@ const findUniqByUserIdGroupedByCompetenceId = async function ({ userId, limitDat return _.groupBy(knowledgeElements, 'competenceId'); }; -const findValidatedGroupedByTubesWithinCampaign = async function (userIdsAndDates, campaignLearningContent) { - const knowledgeElementsGroupedByUser = await findSnapshotForUsers(userIdsAndDates); - - return campaignLearningContent.getValidatedKnowledgeElementsGroupedByTube(_.flatMap(knowledgeElementsGroupedByUser)); -}; - const findInvalidatedAndDirectByUserId = async function (userId) { const invalidatedKnowledgeElements = await knex(tableName) .where({ @@ -131,12 +106,11 @@ const findInvalidatedAndDirectByUserId = async function (userId) { export { batchSave, + findAssessedByUserIdAndLimitDateQuery, findInvalidatedAndDirectByUserId, - findSnapshotForUsers, findUniqByUserId, findUniqByUserIdAndAssessmentId, findUniqByUserIdAndCompetenceId, findUniqByUserIdGroupedByCompetenceId, findUniqByUserIds, - findValidatedGroupedByTubesWithinCampaign, }; diff --git a/api/tests/prescription/campaign-participation/acceptance/application/campaign-participation-route_test.js b/api/tests/prescription/campaign-participation/acceptance/application/campaign-participation-route_test.js index c034e803b71..26ce114e8f4 100644 --- a/api/tests/prescription/campaign-participation/acceptance/application/campaign-participation-route_test.js +++ b/api/tests/prescription/campaign-participation/acceptance/application/campaign-participation-route_test.js @@ -107,8 +107,17 @@ describe('Acceptance | API | Campaign Participations', function () { }); databaseBuilder.factory.buildCampaignSkill({ campaignId, skillId: 'recSkillId1' }); databaseBuilder.factory.buildCampaignSkill({ campaignId, skillId: 'recSkillId2' }); + const sharedAt = new Date('2020-01-01'); campaignParticipation = databaseBuilder.factory.buildCampaignParticipation({ campaignId, + sharedAt, + }); + + databaseBuilder.factory.buildKnowledgeElementSnapshot({ + userId, + snapshot: JSON.stringify([]), + campaignParticipationId: campaignParticipation.id, + snappedAt: sharedAt, }); await databaseBuilder.commit(); diff --git a/api/tests/prescription/campaign-participation/integration/infrastructure/repositories/campaign-analysis-repository_test.js b/api/tests/prescription/campaign-participation/integration/infrastructure/repositories/campaign-analysis-repository_test.js index 006d111becf..6728cef3167 100644 --- a/api/tests/prescription/campaign-participation/integration/infrastructure/repositories/campaign-analysis-repository_test.js +++ b/api/tests/prescription/campaign-participation/integration/infrastructure/repositories/campaign-analysis-repository_test.js @@ -262,57 +262,66 @@ describe('Integration | Repository | Campaign analysis repository', function () sharedAt: shareDate, }); - _.each( - [ - { - userId: paulId, - skillId: 'recUrl1', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: fredId, - skillId: 'recUrl1', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: fredId, - skillId: 'recUrl2', - status: 'invalidated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: joeId, - skillId: 'recUrl1', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - { userId: joeId, skillId: 'recUrl2', status: 'validated', createdAt: afterShareDate }, - { - userId: fredId, - skillId: 'recFile2', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: fredId, - skillId: 'recFile3', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: fredId, - skillId: 'someUntargetedSkill', - status: 'validated', - campaignId, - createdAt: beforeCampaignParticipationShareDate, - }, - ], - (knowledgeElement) => { - databaseBuilder.factory.buildKnowledgeElement(knowledgeElement); + const keData = [ + { + userId: paulId, + skillId: 'recUrl1', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, }, - ); + { + userId: joeId, + skillId: 'recUrl1', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + { userId: joeId, skillId: 'recUrl2', status: 'validated', createdAt: afterShareDate }, + { + userId: fredId, + skillId: 'recUrl1', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: fredId, + skillId: 'recUrl2', + status: 'invalidated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: fredId, + skillId: 'recFile2', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: fredId, + skillId: 'recFile3', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: fredId, + skillId: 'someUntargetedSkill', + status: 'validated', + campaignId, + createdAt: beforeCampaignParticipationShareDate, + }, + ]; + const knowledgeElements = keData.map(databaseBuilder.factory.buildKnowledgeElement); + + [userWithCampaignParticipationFred, userWithCampaignParticipationJoe].forEach((participation) => { + databaseBuilder.factory.buildKnowledgeElementSnapshot({ + userId: participation.userId, + campaignParticipationId: participation.campaignParticipation.id, + snappedAt: participation.campaignParticipation.sharedAt, + snapshot: JSON.stringify( + knowledgeElements.filter( + ({ userId, createdAt }) => userId === participation.userId && createdAt <= shareDate, + ), + ), + }); + }); return databaseBuilder.commit(); }); @@ -352,45 +361,46 @@ describe('Integration | Repository | Campaign analysis repository', function () _createUserWithNonSharedCampaignParticipation('Fred', campaignId); const fredId = userWithCampaignParticipationFred.userId; - _.each( - [ - { - userId: fredId, - skillId: 'recUrl1', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: fredId, - skillId: 'recUrl2', - status: 'invalidated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: fredId, - skillId: 'recFile2', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: fredId, - skillId: 'recFile3', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - { - userId: fredId, - skillId: 'someUntargetedSkill', - status: 'validated', - campaignId, - createdAt: beforeCampaignParticipationShareDate, - }, - ], - (knowledgeElement) => { - databaseBuilder.factory.buildKnowledgeElement(knowledgeElement); + const keData = [ + { + userId: fredId, + skillId: 'recUrl1', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, }, - ); - + { + userId: fredId, + skillId: 'recUrl2', + status: 'invalidated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: fredId, + skillId: 'recFile2', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: fredId, + skillId: 'recFile3', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + { + userId: fredId, + skillId: 'someUntargetedSkill', + status: 'validated', + campaignId, + createdAt: beforeCampaignParticipationShareDate, + }, + ]; + const knowledgeElements = keData.map(databaseBuilder.factory.buildKnowledgeElement); + databaseBuilder.factory.buildKnowledgeElementSnapshot({ + userId: userWithCampaignParticipationFred.userId, + campaignParticipationId: userWithCampaignParticipationFred.campaignParticipation.id, + snappedAt: userWithCampaignParticipationFred.campaignParticipation.sharedAt, + snapshot: JSON.stringify(knowledgeElements.filter(({ createdAt }) => createdAt <= shareDate)), + }); return databaseBuilder.commit(); }); @@ -430,7 +440,26 @@ describe('Integration | Repository | Campaign analysis repository', function () false, ); userId = userWithCampaignParticipation.userId; - campaignParticipation = { userId, sharedAt }; + const beforeCampaignParticipationShareDate = new Date('2019-01-01'); + + const keData = [ + { userId, skillId: 'recUrl1', status: 'validated', createdAt: beforeCampaignParticipationShareDate }, + { userId, skillId: 'recUrl2', status: 'invalidated', createdAt: beforeCampaignParticipationShareDate }, + { userId, skillId: 'recFile2', status: 'validated', createdAt: beforeCampaignParticipationShareDate }, + { userId, skillId: 'recFile3', status: 'validated', createdAt: beforeCampaignParticipationShareDate }, + { + userId, + skillId: 'someUntargetedSkill', + status: 'validated', + createdAt: beforeCampaignParticipationShareDate, + }, + ]; + databaseBuilder.factory.buildKnowledgeElementSnapshot({ + userId, + snapshot: JSON.stringify(keData), + campaignParticipationId: userWithCampaignParticipation.campaignParticipation.id, + }); + campaignParticipation = { userId, sharedAt, id: userWithCampaignParticipation.campaignParticipation.id }; const url1 = domainBuilder.buildSkill({ id: 'recUrl1', tubeId: 'recTubeUrl', name: '@url1', difficulty: 1 }); const url2 = domainBuilder.buildSkill({ id: 'recUrl2', tubeId: 'recTubeUrl', name: '@url2', difficulty: 2 }); @@ -522,30 +551,6 @@ describe('Integration | Repository | Campaign analysis repository', function () }); context('participation details', function () { - beforeEach(function () { - const beforeCampaignParticipationShareDate = new Date('2019-01-01'); - - _.each( - [ - { userId, skillId: 'recUrl1', status: 'validated', createdAt: beforeCampaignParticipationShareDate }, - { userId, skillId: 'recUrl2', status: 'invalidated', createdAt: beforeCampaignParticipationShareDate }, - { userId, skillId: 'recFile2', status: 'validated', createdAt: beforeCampaignParticipationShareDate }, - { userId, skillId: 'recFile3', status: 'validated', createdAt: beforeCampaignParticipationShareDate }, - { - userId, - skillId: 'someUntargetedSkill', - status: 'validated', - createdAt: beforeCampaignParticipationShareDate, - }, - ], - (knowledgeElement) => { - databaseBuilder.factory.buildKnowledgeElement(knowledgeElement); - }, - ); - - return databaseBuilder.commit(); - }); - it('should resolves an analysis based on participant score ignoring untargeted or non validated knowledge elements', async function () { // when const tutorials = []; diff --git a/api/tests/prescription/campaign-participation/integration/infrastructure/repositories/campaign-assessment-participation-repository_test.js b/api/tests/prescription/campaign-participation/integration/infrastructure/repositories/campaign-assessment-participation-repository_test.js index e81f6b66183..40e8ecf548c 100644 --- a/api/tests/prescription/campaign-participation/integration/infrastructure/repositories/campaign-assessment-participation-repository_test.js +++ b/api/tests/prescription/campaign-participation/integration/infrastructure/repositories/campaign-assessment-participation-repository_test.js @@ -6,7 +6,7 @@ import { Assessment } from '../../../../../../src/shared/domain/models/Assessmen import { KnowledgeElement } from '../../../../../../src/shared/domain/models/KnowledgeElement.js'; import { catchErr, databaseBuilder, expect, mockLearningContent } from '../../../../../test-helper.js'; -const { STARTED } = CampaignParticipationStatuses; +const { STARTED, SHARED } = CampaignParticipationStatuses; describe('Integration | Repository | Campaign Assessment Participation', function () { describe('#getByCampaignIdAndCampaignParticipationId', function () { @@ -128,20 +128,31 @@ describe('Integration | Repository | Campaign Assessment Participation', functio }); context('When campaign participation is not shared', function () { + let campaignParticipation, skill1; beforeEach(async function () { - const skill1 = { id: 'skill1', status: 'actif' }; - await mockLearningContent({ skills: [skill1] }); - campaignId = databaseBuilder.factory.buildAssessmentCampaign({}, [skill1]).id; - campaignParticipationId = databaseBuilder.factory.buildAssessmentFromParticipation({ + skill1 = { id: 'skill1', status: 'actif' }; + const skill2 = { id: 'skill2', status: 'actif' }; + await mockLearningContent({ skills: [skill1, skill2] }); + campaignId = databaseBuilder.factory.buildAssessmentCampaign({}, [skill1, skill2]).id; + + campaignParticipation = databaseBuilder.factory.buildCampaignParticipation({ status: STARTED, sharedAt: null, campaignId, - }).campaignParticipationId; - + }); + campaignParticipationId = campaignParticipation.id; await databaseBuilder.commit(); }); it('create CampaignAssessmentParticipation with empty results', async function () { + //given + databaseBuilder.factory.buildAssessment({ + userId: campaignParticipation.userId, + campaignParticipationId, + state: Assessment.states.COMPLETED, + }); + await databaseBuilder.commit(); + const campaignAssessmentParticipation = await campaignAssessmentParticipationRepository.getByCampaignIdAndCampaignParticipationId({ campaignId, @@ -151,6 +162,32 @@ describe('Integration | Repository | Campaign Assessment Participation', functio expect(campaignAssessmentParticipation.masteryRate).to.equal(null); expect(campaignAssessmentParticipation.progression).to.equal(1); }); + + context('when assessment is started', function () { + it('computes the progression', async function () { + //given + databaseBuilder.factory.buildAssessment({ + userId: campaignParticipation.userId, + campaignParticipationId, + state: Assessment.states.STARTED, + }); + databaseBuilder.factory.buildKnowledgeElement({ + status: KnowledgeElement.StatusType.VALIDATED, + userId: campaignParticipation.userId, + skillId: skill1.id, + createdAt: new Date('2020-01-01'), + }); + await databaseBuilder.commit(); + // then + const campaignAssessmentParticipation = + await campaignAssessmentParticipationRepository.getByCampaignIdAndCampaignParticipationId({ + campaignId, + campaignParticipationId, + }); + + expect(campaignAssessmentParticipation.progression).to.equal(0.5); + }); + }); }); context('When campaign participation is shared', function () { @@ -168,8 +205,8 @@ describe('Integration | Repository | Campaign Assessment Participation', functio campaignParticipationId = databaseBuilder.factory.buildCampaignParticipation({ campaignId, userId, - status: STARTED, - sharedAt: null, + status: SHARED, + sharedAt: new Date('2020-01-02'), }).id; databaseBuilder.factory.buildKnowledgeElement({ @@ -184,6 +221,12 @@ describe('Integration | Repository | Campaign Assessment Participation', functio skillId: skill2.id, createdAt: new Date('2020-01-01'), }); + databaseBuilder.factory.buildKnowledgeElement({ + status: KnowledgeElement.StatusType.INVALIDATED, + userId, + skillId: skill3.id, + createdAt: new Date('2020-01-04'), + }); await databaseBuilder.commit(); }); @@ -205,25 +248,6 @@ describe('Integration | Repository | Campaign Assessment Participation', functio expect(campaignAssessmentParticipation.progression).to.equal(1); }); }); - - context('when assessment is started', function () { - it('computes the progression', async function () { - databaseBuilder.factory.buildAssessment({ - campaignParticipationId, - userId, - state: Assessment.states.STARTED, - }); - await databaseBuilder.commit(); - - const campaignAssessmentParticipation = - await campaignAssessmentParticipationRepository.getByCampaignIdAndCampaignParticipationId({ - campaignId, - campaignParticipationId, - }); - - expect(campaignAssessmentParticipation.progression).to.equal(0.5); - }); - }); }); }); diff --git a/api/tests/prescription/campaign-participation/integration/infrastructure/repositories/campaign-profile-repository_test.js b/api/tests/prescription/campaign-participation/integration/infrastructure/repositories/campaign-profile-repository_test.js index 75be3f9b251..625fada2f91 100644 --- a/api/tests/prescription/campaign-participation/integration/infrastructure/repositories/campaign-profile-repository_test.js +++ b/api/tests/prescription/campaign-participation/integration/infrastructure/repositories/campaign-profile-repository_test.js @@ -203,13 +203,19 @@ describe('Integration | Repository | CampaignProfileRepository', function () { userId: user.id, sharedAt: new Date('2020-01-02'), }); - databaseBuilder.factory.buildKnowledgeElement({ + const ke = databaseBuilder.factory.buildKnowledgeElement({ userId: user.id, earnedPix: PIX_COUNT_BY_LEVEL, competenceId: 'rec1', createdAt: new Date('2020-01-01'), }); + databaseBuilder.factory.buildKnowledgeElementSnapshot({ + snappedAt: new Date('2020-01-02'), + snapshot: JSON.stringify([ke]), + campaignParticipationId: campaignParticipation.id, + }); + await databaseBuilder.commit(); const campaignProfile = await CampaignProfileRepository.findProfile({ @@ -256,39 +262,6 @@ describe('Integration | Repository | CampaignProfileRepository', function () { expect(campaignProfile.pixScore).to.equal(80); }); - - it('computes certifiable competences acquired before the sharing date of the campaign participation', async function () { - const campaignId = databaseBuilder.factory.buildCampaign().id; - - const user = databaseBuilder.factory.buildUser({ firstName: 'John', lastName: 'Shaft' }); - const campaignParticipation = databaseBuilder.factory.buildCampaignParticipation({ - campaignId, - userId: user.id, - sharedAt: new Date('2020-01-02'), - }); - databaseBuilder.factory.buildKnowledgeElement({ - userId: user.id, - earnedPix: PIX_COUNT_BY_LEVEL, - competenceId: 'rec1', - createdAt: new Date('2020-01-01'), - }); - databaseBuilder.factory.buildKnowledgeElement({ - userId: user.id, - earnedPix: PIX_COUNT_BY_LEVEL * 2, - competenceId: 'rec2', - createdAt: new Date('2020-01-03'), - }); - - await databaseBuilder.commit(); - - const campaignProfile = await CampaignProfileRepository.findProfile({ - campaignId, - campaignParticipationId: campaignParticipation.id, - locale, - }); - - expect(campaignProfile.certifiableCompetencesCount).to.equal(1); - }); }); context('when there is no campaign-participation with the given id', function () { diff --git a/api/tests/prescription/campaign-participation/integration/infrastructure/repositories/participant-results-shared-repository_test.js b/api/tests/prescription/campaign-participation/integration/infrastructure/repositories/participant-results-shared-repository_test.js index 52013e8049d..1b3b16709f6 100644 --- a/api/tests/prescription/campaign-participation/integration/infrastructure/repositories/participant-results-shared-repository_test.js +++ b/api/tests/prescription/campaign-participation/integration/infrastructure/repositories/participant-results-shared-repository_test.js @@ -365,6 +365,7 @@ function _buildParticipationWithSnapshot(participationAttributes, knowledgeEleme userId: participation.userId, snappedAt: participation.sharedAt, knowledgeElementsAttributes, + campaignParticipationId: participation.id, }); return participation; diff --git a/api/tests/prescription/campaign/acceptance/application/campaign-results-route_test.js b/api/tests/prescription/campaign/acceptance/application/campaign-results-route_test.js index 4b69a269e87..1803c7e8d12 100644 --- a/api/tests/prescription/campaign/acceptance/application/campaign-results-route_test.js +++ b/api/tests/prescription/campaign/acceptance/application/campaign-results-route_test.js @@ -565,7 +565,7 @@ describe('Acceptance | API | campaign-results-route', function () { campaignParticipationId: campaignParticipation.id, }); - databaseBuilder.factory.buildKnowledgeElement({ + const ke = databaseBuilder.factory.buildKnowledgeElement({ skillId: 'recSkillId1', status: 'validated', userId, @@ -574,6 +574,13 @@ describe('Acceptance | API | campaign-results-route', function () { createdAt: new Date('2017-12-01'), }); + databaseBuilder.factory.buildKnowledgeElementSnapshot({ + userId, + campaignParticipationId: campaignParticipation.id, + snapshot: JSON.stringify([ke]), + snappedAt: campaignParticipation.sharedAt, + }); + await databaseBuilder.commit(); const learningContent = [ diff --git a/api/tests/prescription/campaign/integration/infrastructure/repositories/knowledge-element-snapshot-repository_test.js b/api/tests/prescription/campaign/integration/infrastructure/repositories/knowledge-element-snapshot-repository_test.js index 2f13517a2a8..c807408fad4 100644 --- a/api/tests/prescription/campaign/integration/infrastructure/repositories/knowledge-element-snapshot-repository_test.js +++ b/api/tests/prescription/campaign/integration/infrastructure/repositories/knowledge-element-snapshot-repository_test.js @@ -172,6 +172,104 @@ describe('Integration | Repository | KnowledgeElementSnapshotRepository', functi }); }); + describe('#findByCampaignParticipationIds', function () { + let userId1, userId2, campaignParticipationId, secondCampaignParticipationId, otherCampaignParticipationId; + + beforeEach(function () { + userId1 = databaseBuilder.factory.buildUser().id; + userId2 = databaseBuilder.factory.buildUser().id; + + campaignParticipationId = databaseBuilder.factory.buildCampaignParticipation({ userId: userId1 }).id; + secondCampaignParticipationId = databaseBuilder.factory.buildCampaignParticipation({ userId: userId2 }).id; + otherCampaignParticipationId = databaseBuilder.factory.buildCampaignParticipation({ userId: userId2 }).id; + return databaseBuilder.commit(); + }); + + it('should return an empty object when there is no snapshot', async function () { + // given + // when + const knowledgeElementsByUserId = await knowledgeElementSnapshotRepository.findByCampaignParticipationIds([ + campaignParticipationId, + secondCampaignParticipationId, + ]); + // then + expect(knowledgeElementsByUserId).to.deep.equal({}); + }); + + it('should return only keys corresponding to existing snapshots', async function () { + // given + const snappedAt1 = new Date('2020-01-02'); + const knowledgeElement1 = databaseBuilder.factory.buildKnowledgeElement({ userId: userId1 }); + databaseBuilder.factory.buildKnowledgeElementSnapshot({ + userId: userId1, + snappedAt: snappedAt1, + snapshot: JSON.stringify([knowledgeElement1]), + campaignParticipationId, + }); + + await databaseBuilder.commit(); + // when + const knowledgeElementsByUserId = await knowledgeElementSnapshotRepository.findByCampaignParticipationIds([ + campaignParticipationId, + secondCampaignParticipationId, + ]); + // then + expect(knowledgeElementsByUserId).to.deep.equal({ [campaignParticipationId]: [knowledgeElement1] }); + }); + + it('should find knowledge elements snapshoted grouped by campaignParticipationIds', async function () { + // given + const snappedAt1 = new Date('2020-01-02'); + const knowledgeElement1 = databaseBuilder.factory.buildKnowledgeElement({ userId: userId1 }); + databaseBuilder.factory.buildKnowledgeElementSnapshot({ + userId: userId1, + snappedAt: snappedAt1, + snapshot: JSON.stringify([knowledgeElement1]), + campaignParticipationId, + }); + const snappedAt2 = new Date('2020-02-02'); + const knowledgeElement2 = databaseBuilder.factory.buildKnowledgeElement({ userId: userId2 }); + databaseBuilder.factory.buildKnowledgeElementSnapshot({ + userId: userId2, + snappedAt: snappedAt2, + snapshot: JSON.stringify([knowledgeElement2]), + campaignParticipationId: secondCampaignParticipationId, + }); + + const snappedAt3 = new Date('2020-02-03'); + const knowledgeElement3 = databaseBuilder.factory.buildKnowledgeElement({ userId: userId2 }); + databaseBuilder.factory.buildKnowledgeElementSnapshot({ + userId: userId2, + snappedAt: snappedAt3, + snapshot: JSON.stringify([knowledgeElement3]), + campaignParticipationId: otherCampaignParticipationId, + }); + + await databaseBuilder.commit(); + + // when + const knowledgeElementsByUserId = await knowledgeElementSnapshotRepository.findByCampaignParticipationIds([ + campaignParticipationId, + secondCampaignParticipationId, + ]); + + // then + expect(knowledgeElementsByUserId).to.deep.equals({ + [campaignParticipationId]: [knowledgeElement1], + [secondCampaignParticipationId]: [knowledgeElement2], + }); + }); + + it('should return null associated to userId when user does not have a snapshot', async function () { + // when + const knowledgeElementsByUserId = await knowledgeElementSnapshotRepository.findByUserIdsAndSnappedAtDates({ + [userId1]: new Date('2020-04-01T00:00:00Z'), + }); + + expect(knowledgeElementsByUserId[userId1]).to.be.null; + }); + }); + describe('#findMultipleUsersFromUserIdsAndSnappedAtDates', function () { let userId1, userId2; let snappedAt1, snappedAt2, snappedAt3; diff --git a/api/tests/shared/integration/domain/services/placement-profile-service_test.js b/api/tests/shared/integration/domain/services/placement-profile-service_test.js index d554fa77582..be91747e377 100644 --- a/api/tests/shared/integration/domain/services/placement-profile-service_test.js +++ b/api/tests/shared/integration/domain/services/placement-profile-service_test.js @@ -1,12 +1,16 @@ import { LOCALE } from '../../../../../src/shared/domain/constants.js'; -import { KnowledgeElement } from '../../../../../src/shared/domain/models/index.js'; +import { + CampaignParticipationStatuses, + CampaignTypes, + KnowledgeElement, +} from '../../../../../src/shared/domain/models/index.js'; import * as placementProfileService from '../../../../../src/shared/domain/services/placement-profile-service.js'; import { databaseBuilder, domainBuilder, expect } from '../../../../test-helper.js'; const { ENGLISH_SPOKEN } = LOCALE; describe('Shared | Integration | Domain | Services | Placement Profile Service', function () { - let userId, assessmentId; + let userId, assessmentId, campaignParticipation; let skillRemplir2DB; beforeEach(function () { @@ -94,7 +98,14 @@ describe('Shared | Integration | Domain | Services | Placement Profile Service', tubeId: 'Requin', }); userId = databaseBuilder.factory.buildUser().id; - assessmentId = databaseBuilder.factory.buildAssessment({ userId }).id; + campaignParticipation = databaseBuilder.factory.buildCampaignParticipation({ + userId, + status: CampaignParticipationStatuses.SHARED, + }); + assessmentId = databaseBuilder.factory.buildAssessment({ + userId, + campaignParticipationId: campaignParticipation.id, + }).id; return databaseBuilder.commit(); }); @@ -682,12 +693,20 @@ describe('Shared | Integration | Domain | Services | Placement Profile Service', }, ]; - it('should assign 0 pixScore and level of 0 to user competence when not assessed', async function () { + it('should assign 0 pixScore and level of 0 to user competence when campaign is TO_SHARE', async function () { // when + const campaign = databaseBuilder.factory.buildCampaign({ type: CampaignTypes.PROFILES_COLLECTION }); + const campaignParticipationProfileCollection = databaseBuilder.factory.buildCampaignParticipation({ + campaignId: campaign.id, + userId, + status: CampaignParticipationStatuses.TO_SHARE, + }); + const actualPlacementProfile = await placementProfileService.getPlacementProfileWithSnapshotting({ userId, limitDate: new Date(), competences, + campaignParticipationId: campaignParticipationProfileCollection.id, }); // then @@ -725,13 +744,21 @@ describe('Shared | Integration | Domain | Services | Placement Profile Service', describe('PixScore by competences', function () { it('should assign pixScore and level to user competence based on knowledge elements', async function () { // given - databaseBuilder.factory.buildKnowledgeElement({ + const knowledgeElement = databaseBuilder.factory.buildKnowledgeElement({ competenceId: 'competenceRecordIdTwo', skillId: 'recRemplir2', earnedPix: 23, userId, assessmentId, }); + + databaseBuilder.factory.buildKnowledgeElementSnapshot({ + userId, + snappedAt: campaignParticipation.sharedAt, + campaignParticipationId: campaignParticipation.id, + snapshot: JSON.stringify([knowledgeElement]), + }); + await databaseBuilder.commit(); // when @@ -739,6 +766,7 @@ describe('Shared | Integration | Domain | Services | Placement Profile Service', userId, limitDate: new Date(), competences, + campaignParticipationId: campaignParticipation.id, }); // then @@ -756,7 +784,7 @@ describe('Shared | Integration | Domain | Services | Placement Profile Service', it('should include both inferred and direct KnowlegdeElements to compute PixScore', async function () { // given - databaseBuilder.factory.buildKnowledgeElement({ + const ke1 = databaseBuilder.factory.buildKnowledgeElement({ competenceId: 'competenceRecordIdTwo', skillId: 'recRemplir2', earnedPix: 8, @@ -765,7 +793,7 @@ describe('Shared | Integration | Domain | Services | Placement Profile Service', assessmentId, }); - databaseBuilder.factory.buildKnowledgeElement({ + const ke2 = databaseBuilder.factory.buildKnowledgeElement({ competenceId: 'competenceRecordIdTwo', skillId: 'recRemplir4', earnedPix: 9, @@ -773,6 +801,14 @@ describe('Shared | Integration | Domain | Services | Placement Profile Service', userId, assessmentId, }); + + databaseBuilder.factory.buildKnowledgeElementSnapshot({ + userId, + snappedAt: campaignParticipation.sharedAt, + campaignParticipationId: campaignParticipation.id, + snapshot: JSON.stringify([ke1, ke2]), + }); + await databaseBuilder.commit(); // when @@ -780,6 +816,7 @@ describe('Shared | Integration | Domain | Services | Placement Profile Service', userId, limitDate: new Date(), competences, + campaignParticipationId: campaignParticipation.id, }); // then @@ -788,12 +825,20 @@ describe('Shared | Integration | Domain | Services | Placement Profile Service', context('when we dont want to limit pix score', function () { it('should not limit pixScore and level to the max reachable for user competence based on knowledge elements', async function () { - databaseBuilder.factory.buildKnowledgeElement({ + const ke = databaseBuilder.factory.buildKnowledgeElement({ competenceId: 'competenceRecordIdOne', earnedPix: 64, userId, assessmentId, }); + + databaseBuilder.factory.buildKnowledgeElementSnapshot({ + userId, + snappedAt: campaignParticipation.sharedAt, + campaignParticipationId: campaignParticipation.id, + snapshot: JSON.stringify([ke]), + }); + await databaseBuilder.commit(); // when @@ -802,6 +847,7 @@ describe('Shared | Integration | Domain | Services | Placement Profile Service', limitDate: new Date(), competences, allowExcessPixAndLevels: true, + campaignParticipationId: campaignParticipation.id, }); // then @@ -815,12 +861,20 @@ describe('Shared | Integration | Domain | Services | Placement Profile Service', context('when we want to limit pix score', function () { it('should limit pixScore to 40 and level to 5', async function () { - databaseBuilder.factory.buildKnowledgeElement({ + const ke = databaseBuilder.factory.buildKnowledgeElement({ competenceId: 'competenceRecordIdOne', earnedPix: 64, userId, assessmentId, }); + + databaseBuilder.factory.buildKnowledgeElementSnapshot({ + userId, + snappedAt: campaignParticipation.sharedAt, + campaignParticipationId: campaignParticipation.id, + snapshot: JSON.stringify([ke]), + }); + await databaseBuilder.commit(); // when @@ -829,6 +883,7 @@ describe('Shared | Integration | Domain | Services | Placement Profile Service', limitDate: new Date(), competences, allowExcessPixAndLevels: false, + campaignParticipationId: campaignParticipation.id, }); // then diff --git a/api/tests/shared/integration/infrastructure/repositories/knowledge-element-repository_test.js b/api/tests/shared/integration/infrastructure/repositories/knowledge-element-repository_test.js index 93d1640059a..706f9a5d6c2 100644 --- a/api/tests/shared/integration/infrastructure/repositories/knowledge-element-repository_test.js +++ b/api/tests/shared/integration/infrastructure/repositories/knowledge-element-repository_test.js @@ -3,7 +3,7 @@ import _ from 'lodash'; import { DomainTransaction } from '../../../../../src/shared/domain/DomainTransaction.js'; import { KnowledgeElement } from '../../../../../src/shared/domain/models/KnowledgeElement.js'; import * as knowledgeElementRepository from '../../../../../src/shared/infrastructure/repositories/knowledge-element-repository.js'; -import { databaseBuilder, domainBuilder, expect, knex, sinon } from '../../../../test-helper.js'; +import { databaseBuilder, domainBuilder, expect } from '../../../../test-helper.js'; describe('Integration | Repository | knowledgeElementRepository', function () { describe('#batchSave', function () { @@ -312,403 +312,6 @@ describe('Integration | Repository | knowledgeElementRepository', function () { }); }); - describe('#findValidatedGroupedByTubesWithinCampaign', function () { - it('should return knowledge elements within respective dates grouped by userId and tubeId within target profile of campaign', async function () { - // given - const skill1 = domainBuilder.buildSkill({ id: 'skill1', tubeId: 'tube1' }); - const skill2 = domainBuilder.buildSkill({ id: 'skill2', tubeId: 'tube1' }); - const skill3 = domainBuilder.buildSkill({ id: 'skill3', tubeId: 'tube2' }); - const tube1 = domainBuilder.buildTube({ - id: 'tube1', - skills: [skill1, skill2], - competenceId: 'competence', - }); - const tube2 = domainBuilder.buildTube({ id: 'tube2', skills: [skill3], competenceId: 'competence' }); - const competence = domainBuilder.buildCompetence({ - id: 'competence', - tubes: [tube1, tube2], - }); - const area = domainBuilder.buildArea({ id: 'areaId', competences: [competence] }); - const framework = domainBuilder.buildFramework({ areas: [area] }); - const targetProfile = domainBuilder.buildCampaignLearningContent.fromFrameworks([framework]); - const userId1 = databaseBuilder.factory.buildUser().id; - const userId2 = databaseBuilder.factory.buildUser().id; - const dateUserId1 = new Date('2020-01-03'); - const dateUserId2 = new Date('2019-01-03'); - const knowledgeElement1_1 = databaseBuilder.factory.buildKnowledgeElement({ - competenceId: competence.id, - userId: userId1, - createdAt: new Date('2020-01-02'), - skillId: skill1.id, - }); - const knowledgeElement1_2 = databaseBuilder.factory.buildKnowledgeElement({ - competenceId: competence.id, - userId: userId1, - createdAt: new Date('2020-01-02'), - skillId: skill2.id, - }); - databaseBuilder.factory.buildKnowledgeElement({ - competenceId: competence.id, - userId: userId1, - createdAt: new Date('2021-01-02'), - }); - const knowledgeElement2_1 = databaseBuilder.factory.buildKnowledgeElement({ - competenceId: competence.id, - userId: userId2, - createdAt: new Date('2019-01-02'), - skillId: skill1.id, - }); - const knowledgeElement2_2 = databaseBuilder.factory.buildKnowledgeElement({ - competenceId: competence.id, - userId: userId2, - createdAt: new Date('2019-01-02'), - skillId: skill3.id, - }); - databaseBuilder.factory.buildKnowledgeElement({ - competenceId: competence.id, - userId: userId2, - createdAt: new Date('2020-01-02'), - }); - await databaseBuilder.commit(); - - // when - const knowledgeElementsByTubeId = await knowledgeElementRepository.findValidatedGroupedByTubesWithinCampaign( - { [userId1]: dateUserId1, [userId2]: dateUserId2 }, - targetProfile, - ); - - // then - expect(knowledgeElementsByTubeId[tube1.id][0]).to.be.instanceOf(KnowledgeElement); - expect(knowledgeElementsByTubeId[tube1.id]).to.deep.include.members([ - knowledgeElement1_1, - knowledgeElement1_2, - knowledgeElement2_1, - ]); - expect(knowledgeElementsByTubeId[tube2.id]).to.deep.include.members([knowledgeElement2_2]); - }); - - it('should return the knowledge elements in the snapshot when user has a snapshot for this date', async function () { - // given - const learningContent = domainBuilder.buildCampaignLearningContent.withSimpleContent(); - const campaignParticipationId = databaseBuilder.factory.buildCampaignParticipation().id; - - const userId = databaseBuilder.factory.buildUser().id; - const dateUserId = new Date('2020-01-03'); - const knowledgeElement = databaseBuilder.factory.buildKnowledgeElement({ - userId, - skillId: learningContent.skills[0].id, - }); - databaseBuilder.factory.buildKnowledgeElementSnapshot({ - userId, - snappedAt: dateUserId, - snapshot: JSON.stringify([knowledgeElement]), - campaignParticipationId, - }); - await databaseBuilder.commit(); - - // when - const knowledgeElementsByTubeId = await knowledgeElementRepository.findValidatedGroupedByTubesWithinCampaign( - { [userId]: dateUserId }, - learningContent, - ); - - // then - expect(knowledgeElementsByTubeId[learningContent.tubes[0].id][0]).to.deep.equal(knowledgeElement); - }); - - context('when user does not have a snapshot for this date', function () { - context('when no date is provided along with the user', function () { - it('should return the knowledge elements with limit date as now', async function () { - // given - const learningContent = domainBuilder.buildCampaignLearningContent.withSimpleContent(); - const userId = databaseBuilder.factory.buildUser().id; - const expectedKnowledgeElement = databaseBuilder.factory.buildKnowledgeElement({ - userId, - createdAt: new Date('2018-01-01'), - skillId: learningContent.skills[0].id, - }); - await databaseBuilder.commit(); - - // when - const knowledgeElementsByTubeId = await knowledgeElementRepository.findValidatedGroupedByTubesWithinCampaign( - { [userId]: null }, - learningContent, - ); - - // then - expect(knowledgeElementsByTubeId).to.deep.equal({ - [learningContent.tubes[0].id]: [expectedKnowledgeElement], - }); - }); - - it('should not trigger snapshotting', async function () { - // given - const learningContent = domainBuilder.buildCampaignLearningContent.withSimpleContent(); - const userId = databaseBuilder.factory.buildUser().id; - databaseBuilder.factory.buildKnowledgeElement({ - userId, - createdAt: new Date('2018-01-01'), - skillId: learningContent.skills[0].id, - }); - await databaseBuilder.commit(); - - // when - await knowledgeElementRepository.findValidatedGroupedByTubesWithinCampaign( - { [userId]: null }, - learningContent, - ); - - // then - const actualUserSnapshots = await knex.select('*').from('knowledge-element-snapshots').where({ userId }); - expect(actualUserSnapshots).to.have.lengthOf(0); - }); - }); - - context('when a date is provided along with the user', function () { - it('should return the knowledge elements at date', async function () { - // given - const learningContent = domainBuilder.buildCampaignLearningContent.withSimpleContent(); - const userId = databaseBuilder.factory.buildUser().id; - const expectedKnowledgeElement = databaseBuilder.factory.buildKnowledgeElement({ - userId, - createdAt: new Date('2018-01-01'), - skillId: learningContent.skills[0].id, - }); - await databaseBuilder.commit(); - - // when - const knowledgeElementsByTubeId = await knowledgeElementRepository.findValidatedGroupedByTubesWithinCampaign( - { [userId]: new Date('2018-02-01') }, - learningContent, - ); - - // then - expect(knowledgeElementsByTubeId).to.deep.equal({ - [learningContent.tubes[0].id]: [expectedKnowledgeElement], - }); - }); - }); - }); - - it('should avoid returning non targeted knowledge elements when there are knowledge elements that are not in the target profile', async function () { - // given - const learningContent = domainBuilder.buildCampaignLearningContent.withSimpleContent(); - const userId = databaseBuilder.factory.buildUser().id; - databaseBuilder.factory.buildKnowledgeElement({ - userId, - createdAt: new Date('2018-01-01'), - skillId: 'id_de_skill_improbable_et_different_de_celui_du_builder', - }); - await databaseBuilder.commit(); - - // when - const knowledgeElementsByTubeId = await knowledgeElementRepository.findValidatedGroupedByTubesWithinCampaign( - { [userId]: null }, - learningContent, - ); - - // then - expect(knowledgeElementsByTubeId).to.deep.equal({ - [learningContent.tubes[0].id]: [], - }); - }); - - it('should exclusively return validated knowledge elements', async function () { - // given - const learningContent = domainBuilder.buildCampaignLearningContent.withSimpleContent(); - const userId = databaseBuilder.factory.buildUser().id; - databaseBuilder.factory.buildKnowledgeElement({ - userId, - createdAt: new Date('2018-01-01'), - skillId: learningContent.skills[0].id, - status: 'invalidated', - }); - await databaseBuilder.commit(); - - // when - const knowledgeElementsByTubeId = await knowledgeElementRepository.findValidatedGroupedByTubesWithinCampaign( - { [userId]: null }, - learningContent, - ); - - // then - expect(knowledgeElementsByTubeId).to.deep.equal({ - [learningContent.tubes[0].id]: [], - }); - }); - - it('should return an empty array on tube that does not have any targeted knowledge elements', async function () { - // given - const learningContent = domainBuilder.buildCampaignLearningContent.withSimpleContent(); - const userId = databaseBuilder.factory.buildUser().id; - await databaseBuilder.commit(); - - // when - const knowledgeElementsByTubeId = await knowledgeElementRepository.findValidatedGroupedByTubesWithinCampaign( - { [userId]: null }, - learningContent, - ); - - // then - expect(knowledgeElementsByTubeId).to.deep.equal({ - [learningContent.tubes[0].id]: [], - }); - }); - }); - - describe('#findSnapshotForUsers', function () { - let sandbox; - let userId1; - let userId2; - - beforeEach(function () { - userId1 = databaseBuilder.factory.buildUser().id; - userId2 = databaseBuilder.factory.buildUser().id; - sandbox = sinon.createSandbox(); - return databaseBuilder.commit(); - }); - - it('should return knowledge elements within respective dates and users', async function () { - // given - const dateUserId1 = new Date('2020-01-03'); - const dateUserId2 = new Date('2019-01-03'); - const user1knowledgeElement1 = databaseBuilder.factory.buildKnowledgeElement({ - userId: userId1, - createdAt: new Date('2020-01-01'), - skillId: 'rec1', - }); - const user1knowledgeElement2 = databaseBuilder.factory.buildKnowledgeElement({ - userId: userId1, - createdAt: new Date('2020-01-02'), - skillId: 'rec2', - }); - databaseBuilder.factory.buildKnowledgeElement({ - userId: userId1, - createdAt: new Date('2021-01-02'), - skillId: 'rec3', - }); - const user2knowledgeElement1 = databaseBuilder.factory.buildKnowledgeElement({ - userId: userId2, - createdAt: new Date('2019-01-01'), - skillId: 'rec4', - }); - const user2knowledgeElement2 = databaseBuilder.factory.buildKnowledgeElement({ - userId: userId2, - createdAt: new Date('2019-01-02'), - skillId: 'rec5', - }); - databaseBuilder.factory.buildKnowledgeElement({ - userId: userId2, - createdAt: new Date('2020-01-02'), - skillId: 'rec6', - }); - await databaseBuilder.commit(); - - // when - const knowledgeElementsByUserIdAndCompetenceId = await knowledgeElementRepository.findSnapshotForUsers({ - [userId1]: dateUserId1, - [userId2]: dateUserId2, - }); - - // then - expect(knowledgeElementsByUserIdAndCompetenceId[userId1][0]).to.be.instanceOf(KnowledgeElement); - expect(knowledgeElementsByUserIdAndCompetenceId[userId1]).to.have.lengthOf(2); - expect(knowledgeElementsByUserIdAndCompetenceId[userId2]).to.have.lengthOf(2); - expect(knowledgeElementsByUserIdAndCompetenceId[userId1]).to.deep.include.members([ - user1knowledgeElement1, - user1knowledgeElement2, - ]); - expect(knowledgeElementsByUserIdAndCompetenceId[userId2]).to.deep.include.members([ - user2knowledgeElement1, - user2knowledgeElement2, - ]); - }); - - context('when user has a snapshot for this date', function () { - afterEach(function () { - sandbox.restore(); - }); - - it('should return the knowledge elements in the snapshot', async function () { - // given - const dateSharedAtUserId1 = new Date('2020-01-03'); - const knowledgeElement = databaseBuilder.factory.buildKnowledgeElement({ userId: userId1 }); - databaseBuilder.factory.buildKnowledgeElementSnapshot({ - userId: userId1, - snappedAt: dateSharedAtUserId1, - snapshot: JSON.stringify([knowledgeElement]), - }); - await databaseBuilder.commit(); - - // when - const knowledgeElementsByUserIdAndCompetenceId = await knowledgeElementRepository.findSnapshotForUsers({ - [userId1]: dateSharedAtUserId1, - }); - - // then - expect(knowledgeElementsByUserIdAndCompetenceId[userId1][0]).to.deep.equal(knowledgeElement); - }); - }); - - context('when user does not have a snapshot for this date', function () { - context('when no date is provided along with the user', function () { - let expectedKnowledgeElement; - - beforeEach(function () { - expectedKnowledgeElement = databaseBuilder.factory.buildKnowledgeElement({ - userId: userId1, - createdAt: new Date('2018-01-01'), - }); - return databaseBuilder.commit(); - }); - - it('should return all knowledge elements', async function () { - // when - const knowledgeElementsByUserIdAndCompetenceId = await knowledgeElementRepository.findSnapshotForUsers({ - [userId1]: null, - }); - - // then - expect(knowledgeElementsByUserIdAndCompetenceId[userId1]).to.deep.include.members([expectedKnowledgeElement]); - }); - - it('should not trigger snapshotting', async function () { - // when - await knowledgeElementRepository.findSnapshotForUsers({ [userId1]: null }); - - // then - const actualUserSnapshots = await knex - .select('*') - .from('knowledge-element-snapshots') - .where({ userId: userId1 }); - expect(actualUserSnapshots).to.have.lengthOf(0); - }); - }); - - context('when a date is provided along with the user', function () { - let expectedKnowledgeElement; - - beforeEach(function () { - expectedKnowledgeElement = databaseBuilder.factory.buildKnowledgeElement({ - userId: userId1, - createdAt: new Date('2018-01-01'), - }); - return databaseBuilder.commit(); - }); - - it('should return the knowledge elements at date', async function () { - // when - const knowledgeElementsByUserIdAndCompetenceId = await knowledgeElementRepository.findSnapshotForUsers({ - [userId1]: new Date('2018-02-01'), - }); - - // then - expect(knowledgeElementsByUserIdAndCompetenceId[userId1]).to.deep.include.members([expectedKnowledgeElement]); - }); - }); - }); - }); - describe('#findInvalidatedAndDirectByUserId', function () { it('should find invalidated & direct KE with given UserId', async function () { // Given diff --git a/high-level-tests/e2e/cypress/support/step_definitions/index.js b/high-level-tests/e2e/cypress/support/step_definitions/index.js index fe27db9e222..a504612371d 100644 --- a/high-level-tests/e2e/cypress/support/step_definitions/index.js +++ b/high-level-tests/e2e/cypress/support/step_definitions/index.js @@ -20,6 +20,7 @@ Given("les données de test sont chargées", () => { cy.task("db:fixture", "assessments"); cy.task("db:fixture", "answers"); cy.task("db:fixture", "knowledge-elements"); + cy.task("db:fixture", "knowledge-element-snapshots"); cy.task("db:fixture", "pix-admin-roles"); cy.task("db:fixture", "trainings"); cy.task("db:fixture", "target-profile-trainings"); From 7510781c98a0c5695daf97c7e81776634553c613 Mon Sep 17 00:00:00 2001 From: alicegoarnisson Date: Wed, 5 Feb 2025 09:20:06 +0100 Subject: [PATCH 4/4] tech(api): new method to replace userId/sharedAt in findMultipleUsersFromUserIdsAndSnappedAtDates --- .../knowledge-element-snapshot-repository.js | 38 +++++-------- ...nParticipationKnowledgeElementSnapshots.js | 4 +- ...wledge-element-snapshot-repository_test.js | 55 ++++--------------- 3 files changed, 28 insertions(+), 69 deletions(-) diff --git a/api/src/prescription/campaign/infrastructure/repositories/knowledge-element-snapshot-repository.js b/api/src/prescription/campaign/infrastructure/repositories/knowledge-element-snapshot-repository.js index 30a3d6ece87..ef544428b76 100644 --- a/api/src/prescription/campaign/infrastructure/repositories/knowledge-element-snapshot-repository.js +++ b/api/src/prescription/campaign/infrastructure/repositories/knowledge-element-snapshot-repository.js @@ -68,31 +68,23 @@ const findByUserIdsAndSnappedAtDates = async function (userIdsAndSnappedAtDates /** * @function - * @name findMultipleUsersFromUserIdsAndSnappedAtDates + * @name findCampaignParticipationKnowledgeElementSnapshots * - * @param {Array} userIdsAndSnappedAtDates + * @param {number[]} campaignParticipationIds * @returns {Promise>} */ -const findMultipleUsersFromUserIdsAndSnappedAtDates = async function (userIdsAndSnappedAtDates) { - const params = userIdsAndSnappedAtDates.map((userIdAndDate) => { - return [userIdAndDate.userId, userIdAndDate.sharedAt]; - }); - - const results = await knex - .select('userId', 'snapshot', 'snappedAt', 'campaignParticipationId') - .from('knowledge-element-snapshots') - .whereIn(['knowledge-element-snapshots.userId', 'snappedAt'], params); - - return results.map((result) => { - const mappedKnowledgeElements = _toKnowledgeElementCollection({ snapshot: result.snapshot }); - - return new CampaignParticipationKnowledgeElementSnapshots({ - userId: result.userId, - snappedAt: result.snappedAt, - knowledgeElements: mappedKnowledgeElements, - campaignParticipationId: result.campaignParticipationId, - }); - }); +const findCampaignParticipationKnowledgeElementSnapshots = async function (campaignParticipationIds) { + const knowledgeElements = await findByCampaignParticipationIds(campaignParticipationIds); + const knowledgeElementsList = []; + campaignParticipationIds.map((campaignParticipationId) => + knowledgeElementsList.push( + new CampaignParticipationKnowledgeElementSnapshots({ + knowledgeElements: knowledgeElements[campaignParticipationId], + campaignParticipationId: campaignParticipationId, + }), + ), + ); + return knowledgeElementsList; }; /** @@ -119,6 +111,6 @@ const findByCampaignParticipationIds = async function (campaignParticipationIds) export { findByCampaignParticipationIds, findByUserIdsAndSnappedAtDates, - findMultipleUsersFromUserIdsAndSnappedAtDates, + findCampaignParticipationKnowledgeElementSnapshots, save, }; diff --git a/api/src/prescription/shared/domain/read-models/CampaignParticipationKnowledgeElementSnapshots.js b/api/src/prescription/shared/domain/read-models/CampaignParticipationKnowledgeElementSnapshots.js index 3fa415ad361..142b90135e2 100644 --- a/api/src/prescription/shared/domain/read-models/CampaignParticipationKnowledgeElementSnapshots.js +++ b/api/src/prescription/shared/domain/read-models/CampaignParticipationKnowledgeElementSnapshots.js @@ -1,7 +1,5 @@ class CampaignParticipationKnowledgeElementSnapshots { - constructor({ userId, snappedAt, knowledgeElements, campaignParticipationId } = {}) { - this.userId = userId; - this.snappedAt = snappedAt; + constructor({ knowledgeElements, campaignParticipationId } = {}) { this.knowledgeElements = knowledgeElements; this.campaignParticipationId = campaignParticipationId; } diff --git a/api/tests/prescription/campaign/integration/infrastructure/repositories/knowledge-element-snapshot-repository_test.js b/api/tests/prescription/campaign/integration/infrastructure/repositories/knowledge-element-snapshot-repository_test.js index c807408fad4..4b72b08cf23 100644 --- a/api/tests/prescription/campaign/integration/infrastructure/repositories/knowledge-element-snapshot-repository_test.js +++ b/api/tests/prescription/campaign/integration/infrastructure/repositories/knowledge-element-snapshot-repository_test.js @@ -270,11 +270,12 @@ describe('Integration | Repository | KnowledgeElementSnapshotRepository', functi }); }); - describe('#findMultipleUsersFromUserIdsAndSnappedAtDates', function () { + describe('#findCampaignParticipationKnowledgeElementSnapshots', function () { let userId1, userId2; let snappedAt1, snappedAt2, snappedAt3; let knowledgeElement1, knowledgeElement2, knowledgeElement3; let campaignParticipationId1, campaignParticipationId2, campaignParticipationId3; + let campaignParticipation1, campaignParticipation2, campaignParticipation3; let learningContent, campaignLearningContent; beforeEach(async function () { @@ -301,7 +302,7 @@ describe('Integration | Repository | KnowledgeElementSnapshotRepository', functi campaignLearningContent = domainBuilder.buildCampaignLearningContent(learningContent); snappedAt1 = new Date('2020-01-02'); - databaseBuilder.factory.buildCampaignParticipation({ + campaignParticipation1 = databaseBuilder.factory.buildCampaignParticipation({ id: campaignParticipationId1, userId: userId1, sharedAt: snappedAt1, @@ -319,7 +320,7 @@ describe('Integration | Repository | KnowledgeElementSnapshotRepository', functi }); snappedAt2 = new Date('2020-02-02'); - databaseBuilder.factory.buildCampaignParticipation({ + campaignParticipation2 = databaseBuilder.factory.buildCampaignParticipation({ id: campaignParticipationId2, userId: userId2, sharedAt: snappedAt2, @@ -337,7 +338,7 @@ describe('Integration | Repository | KnowledgeElementSnapshotRepository', functi }); snappedAt3 = new Date('2022-02-02'); - databaseBuilder.factory.buildCampaignParticipation({ + campaignParticipation3 = databaseBuilder.factory.buildCampaignParticipation({ id: campaignParticipationId3, userId: userId2, sharedAt: snappedAt3, @@ -360,10 +361,10 @@ describe('Integration | Repository | KnowledgeElementSnapshotRepository', functi it('should find knowledge elements snapshoted grouped by campaign participation id for given userIds and their respective dates', async function () { // when const knowledgeElementsByUserId = - await knowledgeElementSnapshotRepository.findMultipleUsersFromUserIdsAndSnappedAtDates([ - { userId: userId1, sharedAt: snappedAt1 }, - { userId: userId2, sharedAt: snappedAt2 }, - { userId: userId2, sharedAt: snappedAt3 }, + await knowledgeElementSnapshotRepository.findCampaignParticipationKnowledgeElementSnapshots([ + campaignParticipationId1, + campaignParticipationId2, + campaignParticipationId3, ]); // then @@ -371,50 +372,18 @@ describe('Integration | Repository | KnowledgeElementSnapshotRepository', functi expect(knowledgeElementsByUserId).to.deep.members([ { - userId: userId1, - snappedAt: snappedAt1, knowledgeElements: [knowledgeElement1], - campaignParticipationId: campaignParticipationId1, + campaignParticipationId: campaignParticipation1.id, }, { - userId: userId2, - snappedAt: snappedAt2, knowledgeElements: [knowledgeElement2], - campaignParticipationId: campaignParticipationId2, + campaignParticipationId: campaignParticipation2.id, }, { - userId: userId2, - snappedAt: snappedAt3, knowledgeElements: [knowledgeElement3], - campaignParticipationId: campaignParticipationId3, + campaignParticipationId: campaignParticipation3.id, }, ]); }); - - it('should return empty list of snapshoted knowledge elements given unmatching dates', async function () { - // when - const snappedAt = new Date('2023-02-01'); - const knowledgeElementsByUserId = - await knowledgeElementSnapshotRepository.findMultipleUsersFromUserIdsAndSnappedAtDates([ - { userId: userId1, sharedAt: snappedAt }, - ]); - - // then - expect(knowledgeElementsByUserId).lengthOf(0); - }); - - it('should return empty list of snapshoted knowledge elements given unmatching userId', async function () { - const userId = databaseBuilder.factory.buildUser().id; - - await databaseBuilder.commit(); - // when - const knowledgeElementsByUserId = - await knowledgeElementSnapshotRepository.findMultipleUsersFromUserIdsAndSnappedAtDates([ - { userId, sharedAt: snappedAt1 }, - ]); - - // then - expect(knowledgeElementsByUserId).lengthOf(0); - }); }); });