Skip to content

Commit

Permalink
[FEATURE] Afficher le nombre de partages de profils dans les résultat…
Browse files Browse the repository at this point in the history
…s d'un campagne collecte de profils (PIX-15266)

 #10541
  • Loading branch information
pix-service-auto-merge authored Nov 14, 2024
2 parents 8f772d8 + e0bae0b commit 420bb90
Show file tree
Hide file tree
Showing 12 changed files with 232 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class CampaignProfilesCollectionParticipationSummary {
participantExternalId,
sharedAt,
pixScore,
sharedProfileCount,
previousPixScore,
previousSharedAt,
certifiable,
Expand All @@ -17,6 +18,7 @@ class CampaignProfilesCollectionParticipationSummary {
this.participantExternalId = participantExternalId;
this.sharedAt = sharedAt;
this.pixScore = pixScore;
this.sharedProfileCount = sharedProfileCount;
this.previousPixScore = previousPixScore ?? null;
this.previousSharedAt = previousSharedAt ?? null;
this.evolution = this.#computeEvolution(this.pixScore, this.previousPixScore);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@ async function _getParticipations(qb, campaignId, filters) {
.whereNull('campaign-participations.deletedAt')
.orderBy('sharedAt', 'desc');
})
.with('participationsCount', (qb) => {
qb.select('organizationLearnerId')
.count('organizationLearnerId AS sharedProfileCount')
.from('campaign-participations')
.groupBy('organizationLearnerId')
.where('campaignId', campaignId)
.whereNotNull('campaign-participations.sharedAt')
.whereNull('campaign-participations.deletedAt');
})
.select(
'campaign-participations.id AS campaignParticipationId',
'campaign-participations.userId AS userId',
Expand All @@ -66,6 +75,7 @@ async function _getParticipations(qb, campaignId, filters) {
'campaign-participations.pixScore AS pixScore',
'previousParticipationsInfos.previousPixScore',
'previousParticipationsInfos.previousSharedAt',
'participationsCount.sharedProfileCount',
)
.distinctOn('campaign-participations.organizationLearnerId')
.from('campaign-participations')
Expand All @@ -80,6 +90,11 @@ async function _getParticipations(qb, campaignId, filters) {
'campaign-participations.organizationLearnerId',
).andOn('campaign-participations.id', '!=', 'previousParticipationsInfos.id');
})
.join(
'participationsCount',
'participationsCount.organizationLearnerId',
'campaign-participations.organizationLearnerId',
)
.where('campaign-participations.campaignId', campaignId)
.whereNull('campaign-participations.deletedAt')
.whereNotNull('campaign-participations.sharedAt')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const serialize = function ({ data, pagination }) {
'participantExternalId',
'sharedAt',
'pixScore',
'sharedProfileCount',
'previousPixScore',
'previousSharedAt',
'evolution',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ describe('Integration | Repository | Campaign Profiles Collection Participation
await databaseBuilder.commit();
});

it('should return the certification profile info and pix score', async function () {
it('should return the certification profile info, pix score and count', async function () {
// when
const results =
await campaignProfilesCollectionParticipationSummaryRepository.findPaginatedByCampaignId(campaignId);
Expand All @@ -191,6 +191,7 @@ describe('Integration | Repository | Campaign Profiles Collection Participation
participantExternalId: 'JeBu',
sharedAt,
pixScore: 46,
sharedProfileCount: 1,
certifiable: false,
certifiableCompetencesCount: 1,
}),
Expand Down Expand Up @@ -260,6 +261,151 @@ describe('Integration | Repository | Campaign Profiles Collection Participation
});
});

context('participations count', function () {
beforeEach(async function () {
databaseBuilder.factory.buildCampaignParticipation({
campaignId,
sharedAt: new Date('2020-01-02'),
createdAt: new Date('2020-01-02'),
isImproved: true,
userId: organizationLearner.userId,
organizationLearnerId: organizationLearner.id,
});

await databaseBuilder.commit();
});

describe('when participant has only one shared participation', function () {
it('should count one participation', async function () {
// when
const results =
await campaignProfilesCollectionParticipationSummaryRepository.findPaginatedByCampaignId(campaignId);

// then
expect(results.data[0].sharedProfileCount).to.equal(1);
});
});
describe('when participant has multiple participations', function () {
it('should return the count of shared participations only', async function () {
// given
databaseBuilder.factory.buildCampaignParticipation({
isImproved: true,
sharedAt: new Date('2022-01-02'),
createdAt: new Date('2022-01-02'),
status: SHARED,
campaignId,
userId: organizationLearner.userId,
organizationLearnerId: organizationLearner.id,
});

databaseBuilder.factory.buildCampaignParticipation({
isImproved: false,
sharedAt: null,
createdAt: new Date('2022-01-02'),
status: TO_SHARE,
campaignId,
userId: organizationLearner.userId,
organizationLearnerId: organizationLearner.id,
});

await databaseBuilder.commit();

// when
const results =
await campaignProfilesCollectionParticipationSummaryRepository.findPaginatedByCampaignId(campaignId);

// then
expect(results.data[0].sharedProfileCount).to.equal(2);
});

it('should not count a deleted participation', async function () {
// given deleted participation
databaseBuilder.factory.buildCampaignParticipation({
isImproved: true,
sharedAt: new Date('2022-01-02'),
createdAt: new Date('2022-01-02'),
deletedAt: new Date('2022-01-02'),
status: SHARED,
campaignId,
userId: organizationLearner.userId,
organizationLearnerId: organizationLearner.id,
});

// given not shared participation
databaseBuilder.factory.buildCampaignParticipation({
isImproved: false,
sharedAt: null,
createdAt: new Date('2022-01-02'),
status: TO_SHARE,
campaignId,
userId: organizationLearner.userId,
organizationLearnerId: organizationLearner.id,
});

await databaseBuilder.commit();

// when
const results =
await campaignProfilesCollectionParticipationSummaryRepository.findPaginatedByCampaignId(campaignId);

// then
expect(results.data[0].sharedProfileCount).to.equal(1);
});
});

describe('when there is another participant for same campaign', function () {
it('should only count shared participations for same learner', async function () {
// given second organisation learner and his participation
const secondOrganizationLearner = databaseBuilder.factory.buildOrganizationLearner({ organizationId });
databaseBuilder.factory.buildCampaignParticipation({
isImproved: false,
sharedAt: new Date('2022-01-02'),
createdAt: new Date('2022-01-02'),
status: SHARED,
campaignId,
userId: secondOrganizationLearner.userId,
organizationLearnerId: secondOrganizationLearner.id,
});

await databaseBuilder.commit();

// when
const results =
await campaignProfilesCollectionParticipationSummaryRepository.findPaginatedByCampaignId(campaignId);

// then
expect(results.data[0].sharedProfileCount).to.equal(1);
expect(results.data[1].sharedProfileCount).to.equal(1);
});
});

describe('when participant has participations to different campaigns', function () {
it('should only count shared participations for same campaign', async function () {
// given second campaign and participation
const secondCampaignId = databaseBuilder.factory.buildCampaign({ organizationId }).id;

databaseBuilder.factory.buildCampaignParticipation({
isImproved: false,
sharedAt: new Date('2022-01-02'),
createdAt: new Date('2022-01-02'),
status: SHARED,
campaignId: secondCampaignId,
userId: organizationLearner.userId,
organizationLearnerId: organizationLearner.id,
});

await databaseBuilder.commit();

// when
const results =
await campaignProfilesCollectionParticipationSummaryRepository.findPaginatedByCampaignId(campaignId);

// then
expect(results.data[0].sharedProfileCount).to.equal(1);
});
});
});

context('additionnal informations about previous participation and evolution', function () {
let userId,
organizationId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ describe('Unit | Domain | Read-Models | CampaignResults | CampaignProfilesCollec
participantExternalId: 'Sarah2024',
sharedAt: '2024-10-28',
pixScore: 20,
sharedProfileCount: 2,
previousPixScore: null,
previousSharedAt: null,
certifiable: true,
Expand Down Expand Up @@ -40,6 +41,7 @@ describe('Unit | Domain | Read-Models | CampaignResults | CampaignProfilesCollec
participantExternalId: 'Sarah2024',
certifiable: true,
certifiableCompetencesCount: 9,
sharedProfileCount: 2,
};

describe('when previous participation pixScore and shared date are undefined', function () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe('Unit | Serializer | JSONAPI | campaign-profiles-collection-participati
participantExternalId: 'abo',
sharedAt: new Date(2020, 2, 2),
pixScore: 1024,
sharedProfileCount: 1,
previousPixScore: 512,
previousSharedAt: new Date(2024, 10, 29),
certifiable: true,
Expand All @@ -29,6 +30,7 @@ describe('Unit | Serializer | JSONAPI | campaign-profiles-collection-participati
'participant-external-id': 'abo',
'shared-at': new Date(2020, 2, 2),
'pix-score': 1024,
'shared-profile-count': 1,
'previous-pix-score': 512,
'previous-shared-at': new Date(2024, 10, 29),
evolution: 'increase',
Expand Down
10 changes: 10 additions & 0 deletions orga/app/components/campaign/results/profile-list.gjs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import ParticipationEvolutionIcon from './participation-evolution-icon';
{{/if}}
<col class="hide-on-mobile" />
<col class="hide-on-mobile" />
<col />
</colgroup>
<thead>
<tr>
Expand All @@ -67,6 +68,12 @@ import ParticipationEvolutionIcon from './participation-evolution-icon';
<TableHeader @align="center" class="hide-on-mobile">{{t
"pages.profiles-list.table.column.competences-certifiables"
}}</TableHeader>

{{#if @campaign.multipleSendings}}
<TableHeader @align="center" aria-label={{t "pages.profiles-list.table.column.ariaSharedProfileCount"}}>
{{t "pages.profiles-list.table.column.sharedProfileCount"}}
</TableHeader>
{{/if}}
</tr>
</thead>

Expand Down Expand Up @@ -116,6 +123,9 @@ import ParticipationEvolutionIcon from './participation-evolution-icon';
<td class="table__column--center hide-on-mobile">
{{profile.certifiableCompetencesCount}}
</td>
<td class="table__column--center">
{{profile.sharedProfileCount}}
</td>
</tr>
{{/each}}
</tbody>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export default class CampaignProfilesCollectionParticipationSummary extends Mode
@attr('string') participantExternalId;
@attr('date') sharedAt;
@attr('number') pixScore;
@attr('number') sharedProfileCount;
@attr('nullable-string') evolution;
@attr('boolean') certifiable;
@attr('number') certifiableCompetencesCount;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ module('Integration | Component | Campaign::Results::ProfileList', function (hoo
});
});
module('table headers for multiple sendings campaign', function () {
test('it should display evolution header and tooltip when campaign is multiple sendings', async function (assert) {
test('it should display evolution header and tooltip and shared profile count when campaign is multiple sendings', async function (assert) {
// given
this.campaign = store.createRecord('campaign', {
id: '1',
Expand Down Expand Up @@ -74,9 +74,12 @@ module('Integration | Component | Campaign::Results::ProfileList', function (hoo
name: t('pages.profiles-list.table.column.evolution'),
});
assert.ok(within(evolutionHeader).getByText(t('pages.profiles-list.table.evolution-tooltip.content')));
assert.ok(
screen.getByRole('columnheader', { name: t('pages.profiles-list.table.column.ariaSharedProfileCount') }),
);
});

test('it should not display evolution header if campaign is not multiple sendings', async function (assert) {
test('it should not display evolution header or shared profile count if campaign is not multiple sendings', async function (assert) {
// given
this.campaign = store.createRecord('campaign', {
id: '1',
Expand All @@ -101,6 +104,9 @@ module('Integration | Component | Campaign::Results::ProfileList', function (hoo

// then
assert.notOk(screen.queryByRole('columnheader', { name: t('pages.profiles-list.table.column.evolution') }));
assert.notOk(
screen.queryByRole('columnheader', { name: t('pages.profiles-list.table.column.ariaSharedProfileCount') }),
);
});
});

Expand Down Expand Up @@ -209,6 +215,42 @@ module('Integration | Component | Campaign::Results::ProfileList', function (hoo
assert.ok(screen.getByRole('cell', { name: t('pages.profiles-list.table.evolution.unavailable') }));
});

test('it should display number of profiles shares', async function (assert) {
// given
this.campaign = store.createRecord('campaign', {
id: '1',
name: 'campagne 1',
participationsCount: 1,
multipleSendings: true,
});
this.profiles = [
{
firstName: 'John',
lastName: 'Doe',
participantExternalId: '123',
sharedProfileCount: 3,
evolution: 'decrease',
sharedAt: new Date(2020, 1, 1),
},
];
this.profiles.meta = { rowCount: 1 };

// when
const screen = await render(
hbs`<Campaign::Results::ProfileList
@campaign={{this.campaign}}
@profiles={{this.profiles}}
@onClickParticipant={{this.noop}}
@onFilter={{this.noop}}
@selectedDivisions={{this.divisions}}
@selectedGroups={{this.groups}}
/>`,
);

// then
assert.ok(screen.getByRole('cell', { name: '3' }));
});

test('it should display the profile list with external id', async function (assert) {
// given
this.campaign = store.createRecord('campaign', {
Expand Down
4 changes: 3 additions & 1 deletion orga/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1270,6 +1270,7 @@
"title": "List of submitted profiles",
"caption": "This table contains the list of participants who have shared their profiles. It indicates for each participant their last name, first name, the sent date, the Pix score, the evolution, the certifiability status and the number of certifiable competences.",
"column": {
"ariaSharedProfileCount": "Number of profiles shared",
"certifiable": "Eligible for certification",
"competences-certifiables": "Competences eligible for certification",
"evolution": "Evolution",
Expand All @@ -1282,7 +1283,8 @@
"sending-date": {
"label": "Sent on",
"on-hold": "Pending"
}
},
"sharedProfileCount": "No. profiles shared"
},
"empty": "No profiles yet",
"evolution": {
Expand Down
Loading

0 comments on commit 420bb90

Please sign in to comment.