Skip to content

Commit

Permalink
tech(api): simplify get quest results usecase (wip, todo englishizer …
Browse files Browse the repository at this point in the history
…le truc)

Co-authored-by: Yvonnick Frin <[email protected]>
Co-authored-by: Xavier Carron <[email protected]>
  • Loading branch information
laura-bergoens committed Feb 6, 2025
1 parent ff963f9 commit d4b5566
Show file tree
Hide file tree
Showing 8 changed files with 274 additions and 168 deletions.
26 changes: 12 additions & 14 deletions api/src/quest/domain/models/Eligibility.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,23 @@ export class Eligibility {
this.campaignParticipations = campaignParticipations;
}

/**
* @param {number} campaignParticipationId
*/
hasCampaignParticipation(campaignParticipationId) {
return Boolean(
this.campaignParticipations.find((campaignParticipation) => campaignParticipation.id === campaignParticipationId),
);
}

hasCampaignParticipationForTargetProfileId(targetProfileId) {
return Boolean(
this.campaignParticipations.find(
(campaignParticipation) => campaignParticipation.targetProfileId === targetProfileId,
),
);
}

getTargetProfileForCampaignParticipation(campaignParticipationId) {
const campaignParticipation = this.campaignParticipations.find(
(campaignParticipation) => campaignParticipation.id === campaignParticipationId,
);

return campaignParticipation?.targetProfileId ?? null;
/**
* @param {number} campaignParticipationId
*/
scoperALaParticipationUniquement({ campaignParticipationId }) {
return new Eligibility({
organizationLearner: this.organizationLearner,
organization: this.organization,
campaignParticipations: this.campaignParticipations.filter((cp) => cp.id === campaignParticipationId),
})
}
}
30 changes: 30 additions & 0 deletions api/src/quest/domain/models/Quest.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { KnowledgeElement } from '../../../shared/domain/models/index.js';
import { TYPES as ELIGIBILITY_TYPES } from "./Eligibility.js";

export const COMPARISON = {
ALL: 'all',
Expand All @@ -15,6 +16,35 @@ class Quest {
this.successRequirements = successRequirements;
}

/**
* @param {Eligibility} eligibility
* @param {number} campaignParticipationId
*
* @returns {estConcernee: boolean, aContribuéPositivement: boolean}
*/
estCeQueLaParticipationEstConcernée({ eligibility, campaignParticipationId }) {
const eligibilityScopee = eligibility.scoperALaParticipationUniquement({ campaignParticipationId });
const estDeTypeCampaignParticipations = (requirement) => requirement.type === ELIGIBILITY_TYPES.CAMPAIGN_PARTICIPATIONS;
const n_estPasDeTypeCampaignParticipations = (requirement) => requirement.type !== ELIGIBILITY_TYPES.CAMPAIGN_PARTICIPATIONS;
const isolerLesRequirementsDeTypeCampaignParticipations = (requirements) => {
return [
requirements.filter(estDeTypeCampaignParticipations),
requirements.filter(n_estPasDeTypeCampaignParticipations),
]
}
const [requirementsDeTypeCampaignParticipations, lesAutresRequirements] = isolerLesRequirementsDeTypeCampaignParticipations(this.eligibilityRequirements);
let requirementSpecifiqueALaParticipationEstOk = true;
if(requirementsDeTypeCampaignParticipations.length > 0) {
requirementSpecifiqueALaParticipationEstOk = requirementsDeTypeCampaignParticipations.some((eligibilityRequirement) =>
this.#checkRequirement(eligibilityRequirement, eligibilityScopee),
);
}

return requirementSpecifiqueALaParticipationEstOk && lesAutresRequirements.every((eligibilityRequirement) =>
this.#checkRequirement(eligibilityRequirement, eligibilityScopee),
);
}

/**
* @param {Eligibility} eligibility
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,105 +1,32 @@
const getEligibilityForThisCampaignParticipation = async (eligibilityRepository, userId, campaignParticipationId) => {
const eligibilities = await eligibilityRepository.find({ userId });
return eligibilities.find((e) => e.hasCampaignParticipation(campaignParticipationId));
};

const getTargetProfileRequirementsPerQuest = (quests) =>
quests
.map((quest) => {
const campaignParticipationsRequirement = quest.eligibilityRequirements.find(
(requirement) => requirement.type === 'campaignParticipations',
);
if (campaignParticipationsRequirement && campaignParticipationsRequirement.data.targetProfileIds)
return campaignParticipationsRequirement.data.targetProfileIds;
})
.filter(Boolean);

/**
* This function retrieves the target profiles we should use for the current participation.
* It first retrieves the target profile for the current campaign participation.
* Then it retrieves the target profile requirements for each quest.
* It filters the target profile requirements to only keep the ones that contain the target profile for the current participation.
* It checks if the user has participated in campaigns linked to all the target profiles present in the quest requirements.
* If the user has participated in campaigns linked to all the target profiles present in the quest requirements, it returns the target profile requirements containing the target profile for the current participation.
* If not, it returns the target profile for the current participation.
*
* @param campaignParticipationRepository
* @param {number} campaignParticipationId
* @param {[Quest]} quests
* @param {Eligibility} eligibility
* @returns {Promise<[number]>}
*/
const getTargetProfilesForThisCampaignParticipation = async ({
campaignParticipationRepository,
campaignParticipationId,
quests,
eligibility,
}) => {
const { targetProfileId: targetProfileForThisParticipation } =
await campaignParticipationRepository.getCampaignByParticipationId({
campaignParticipationId,
});

const targetProfileRequirementsPerQuest = getTargetProfileRequirementsPerQuest(quests);

const targetProfileRequirementsContainingTargetProfileForCurrentParticipation =
targetProfileRequirementsPerQuest.filter((targetProfileIds) =>
targetProfileIds.includes(targetProfileForThisParticipation),
);

const targetProfileRequirementContainingTargetProfileForCurrentParticipationWithParticipationForEveryTargetProfile =
targetProfileRequirementsContainingTargetProfileForCurrentParticipation.find((targetProfileRequirement) =>
targetProfileRequirement.every((targetProfileId) =>
eligibility.hasCampaignParticipationForTargetProfileId(targetProfileId),
),
);

return (
targetProfileRequirementContainingTargetProfileForCurrentParticipationWithParticipationForEveryTargetProfile ?? [
targetProfileForThisParticipation,
]
);
};

export const getQuestResultsForCampaignParticipation = async ({
userId,
campaignParticipationId,
questRepository,
eligibilityRepository,
rewardRepository,
campaignParticipationRepository,
}) => {
const quests = await questRepository.findAll();

if (quests.length === 0) {
return [];
}

const eligibility = await getEligibilityForThisCampaignParticipation(
eligibilityRepository,
userId,
campaignParticipationId,
const eligibilities = await eligibilityRepository.find({ userId });
const eligibility = eligibilities.find(
(eligibility) => eligibility.hasCampaignParticipation(campaignParticipationId)
);
if(!eligibility) {
return [];
}

if (!eligibility) return [];

const targetProfileIdsForThisCampaignParticipation = await getTargetProfilesForThisCampaignParticipation({
campaignParticipationRepository,
campaignParticipationId,
quests,
eligibility,
});

eligibility.campaignParticipations = targetProfileIdsForThisCampaignParticipation.map((targetProfileId) => ({
targetProfileId,
}));
const questsRelatedToCampaignParticipation = quests.filter((q) =>
q.estCeQueLaParticipationEstConcernée({ eligibility, campaignParticipationId })
);

const questResults = [];
for (const quest of quests) {
const isEligibleForQuest = quest.isEligible(eligibility);

if (!isEligibleForQuest) continue;

for (const quest of questsRelatedToCampaignParticipation) {
const isEligible = quest.isEligible(eligibility);
if(!isEligible) continue;
const questResult = await rewardRepository.getByQuestAndUserId({ userId, quest });
questResults.push(questResult);
}
Expand Down
2 changes: 0 additions & 2 deletions api/src/quest/domain/usecases/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';

import * as campaignParticipationRepository from '../../../profile/infrastructure/repositories/campaign-participation-repository.js';
import { injectDependencies } from '../../../shared/infrastructure/utils/dependency-injection.js';
import { importNamedExportsFromDirectory } from '../../../shared/infrastructure/utils/import-named-exports-from-directory.js';
import { repositories } from '../../infrastructure/repositories/index.js';
Expand All @@ -18,7 +17,6 @@ const dependencies = {
rewardRepository: repositories.rewardRepository,
successRepository: repositories.successRepository,
questRepository,
campaignParticipationRepository,
};

const usecases = injectDependencies(usecasesWithoutInjectedDependencies, dependencies);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,14 @@ describe('Quest | Integration | Domain | Usecases | getQuestResultsForCampaignPa
{
type: 'campaignParticipations',
data: {
targetProfileIds: [firstTargetProfile.id, secondTargetProfile.id],
targetProfileIds: [firstTargetProfile.id],
},
comparison: COMPARISON.ALL,
},
{
type: 'campaignParticipations',
data: {
targetProfileIds: [secondTargetProfile.id],
},
comparison: COMPARISON.ALL,
},
Expand All @@ -80,15 +87,13 @@ describe('Quest | Integration | Domain | Usecases | getQuestResultsForCampaignPa
expect(result[0].id).to.equal(questId);
expect(result[0].reward.id).to.equal(rewardId);
});

it('should not return quest results for campaign participation if user has not participated to campaigns linked to all profiles target in quest requirement', async function () {
const organizationId = databaseBuilder.factory.buildOrganization({ type: 'SCO' }).id;
const { id: organizationLearnerId, userId } = databaseBuilder.factory.buildOrganizationLearner({
organizationId,
});

// build target profiles

const firstTargetProfile = databaseBuilder.factory.buildTargetProfile({
ownerOrganizationId: organizationId,
});
Expand All @@ -100,7 +105,6 @@ describe('Quest | Integration | Domain | Usecases | getQuestResultsForCampaignPa
});

// build campaigns

const firstCampaign = databaseBuilder.factory.buildCampaign({
organizationId,
targetProfileId: firstTargetProfile.id,
Expand All @@ -117,7 +121,6 @@ describe('Quest | Integration | Domain | Usecases | getQuestResultsForCampaignPa
});

// build campaign participations

databaseBuilder.factory.buildCampaignParticipation({
organizationLearnerId,
campaignId: firstCampaign.id,
Expand Down Expand Up @@ -146,7 +149,21 @@ describe('Quest | Integration | Domain | Usecases | getQuestResultsForCampaignPa
{
type: 'campaignParticipations',
data: {
targetProfileIds: [firstTargetProfile.id, secondTargetProfile.id, thirdTargetProfile.id],
targetProfileIds: [firstTargetProfile.id],
},
comparison: COMPARISON.ALL,
},
{
type: 'campaignParticipations',
data: {
targetProfileIds: [secondTargetProfile.id],
},
comparison: COMPARISON.ALL,
},
{
type: 'campaignParticipations',
data: {
targetProfileIds: [thirdTargetProfile.id],
},
comparison: COMPARISON.ALL,
},
Expand All @@ -160,7 +177,6 @@ describe('Quest | Integration | Domain | Usecases | getQuestResultsForCampaignPa
userId,
campaignParticipationId: secondCampaignParticipationId,
});

expect(result).to.be.empty;
});
});
Expand Down Expand Up @@ -236,7 +252,6 @@ describe('Quest | Integration | Domain | Usecases | getQuestResultsForCampaignPa
],
successRequirements: [],
}).id;

await databaseBuilder.commit();

// when
Expand Down
68 changes: 17 additions & 51 deletions api/tests/quest/unit/domain/models/Eligibility_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,61 +28,27 @@ describe('Quest | Unit | Domain | Models | Eligibility ', function () {
});
});

describe('getTargetProfileForCampaignParticipation', function () {
it('Should return target profile ID for campaign participation', function () {
describe('#scoperALaParticipationUniquement', function() {
it('doit retourner une nouvelle instance Eligibility scopée uniquement sur la participation passée en paramètre', function() {
// given
const campaignParticipations = [{ id: 1, targetProfileId: 10 }];
const eligiblity = new Eligibility({ campaignParticipations });
const organization = Symbol('orga');
const organizationLearner = Symbol('orgaLearner');
const campaignParticipations = [{ id: 1 }, { id: 2 }];
const eligibility = new Eligibility({
organization,
organizationLearner,
campaignParticipations,
});

// when
const result = eligiblity.getTargetProfileForCampaignParticipation(1);
const eligibilityScopee = eligibility.scoperALaParticipationUniquement({ campaignParticipationId: 2 });

// then
expect(result).to.equal(10);
});

it('Should return null when the campaign participation does not exist', function () {
// given
const campaignParticipations = [{ id: 1, targetProfileId: 10 }];
const eligiblity = new Eligibility({ campaignParticipations });

// when
const result = eligiblity.getTargetProfileForCampaignParticipation(2);

// then
expect(result).to.be.null;
});
});

describe('#hasCampaignParticipationForTargetProfileId', function () {
it('should return true if has campaign participation for given target profile', function () {
// given
const campaignParticipations = [
{ id: 1, targetProfileId: 10 },
{ id: 2, targetProfileId: 20 },
];
const eligiblity = new Eligibility({ campaignParticipations });

// when
const result = eligiblity.hasCampaignParticipationForTargetProfileId(10);

// then
expect(result).to.be.true;
});

it('should return false if there are no campaign participation for given target profile', function () {
// given
const campaignParticipations = [
{ id: 1, targetProfileId: 10 },
{ id: 2, targetProfileId: 20 },
];
const eligiblity = new Eligibility({ campaignParticipations });

// when
const result = eligiblity.hasCampaignParticipationForTargetProfileId(1);

// then
expect(result).to.be.false;
});
expect(eligibilityScopee).to.deepEqualInstance(new Eligibility({
organization,
organizationLearner,
campaignParticipations: [{ id: 2 }],
}))
})
});
});
Loading

0 comments on commit d4b5566

Please sign in to comment.