Skip to content

Commit 095bb3f

Browse files
pix-service-auto-mergeHEYGUL
authored andcommitted
[FEATURE] Donner la possibilitée aux organisations sans imports de télécharger des attestations (PIX-15612)
#10884
2 parents a8a4949 + 02212ff commit 095bb3f

File tree

13 files changed

+295
-115
lines changed

13 files changed

+295
-115
lines changed

Diff for: api/db/seeds/data/common/organization-builder.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ async function _createScoOrganization(databaseBuilder) {
3333
features: [
3434
{ id: FEATURE_COMPUTE_ORGANIZATION_LEARNER_CERTIFICABILITY_ID },
3535
{ id: FEATURE_MULTIPLE_SENDING_ASSESSMENT_ID },
36-
{ id: FEATURE_ATTESTATIONS_MANAGEMENT_ID },
3736
],
3837
});
3938

@@ -46,7 +45,7 @@ async function _createScoOrganization(databaseBuilder) {
4645
externalId: 'SCO_NOT_MANAGING',
4746
adminIds: [USER_ID_ADMIN_ORGANIZATION],
4847
memberIds: [USER_ID_MEMBER_ORGANIZATION],
49-
features: [{ id: FEATURE_MULTIPLE_SENDING_ASSESSMENT_ID }],
48+
features: [{ id: FEATURE_MULTIPLE_SENDING_ASSESSMENT_ID }, { id: FEATURE_ATTESTATIONS_MANAGEMENT_ID }],
5049
});
5150

5251
await organization.createOrganization({

Diff for: api/db/seeds/data/team-prescription/build-quests.js

+17
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { temporaryStorage } from '../../../../src/shared/infrastructure/temporar
66
import {
77
AEFE_TAG,
88
FEATURE_ATTESTATIONS_MANAGEMENT_ID,
9+
SCO_ORGANIZATION_ID,
910
USER_ID_ADMIN_ORGANIZATION,
1011
USER_ID_MEMBER_ORGANIZATION,
1112
} from '../common/constants.js';
@@ -399,4 +400,20 @@ export const buildQuests = async (databaseBuilder) => {
399400

400401
// Insert job count in temporary storage for pending user
401402
await profileRewardTemporaryStorage.increment(pendingUser.id);
403+
404+
// Create learner with profile rewards for SCO organization without import
405+
const { id: otherUserId } = databaseBuilder.factory.buildUser({ firstName: 'Alex', lastName: 'Tension' });
406+
databaseBuilder.factory.buildOrganizationLearner({
407+
organizationId: SCO_ORGANIZATION_ID,
408+
userId: otherUserId,
409+
});
410+
const { id: otherUserProfileRewardId } = databaseBuilder.factory.buildProfileReward({
411+
userId: otherUserId,
412+
rewardType: REWARD_TYPES.ATTESTATION,
413+
rewardId,
414+
});
415+
databaseBuilder.factory.buildOrganizationsProfileRewards({
416+
organizationId: SCO_ORGANIZATION_ID,
417+
profileRewardId: otherUserProfileRewardId,
418+
});
402419
};

Diff for: api/src/prescription/organization-learner/infrastructure/repositories/organization-learner-repository.js

+8-4
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,15 @@ async function findPaginatedLearners({ organizationId, page, filter }) {
114114
}
115115

116116
async function findOrganizationLearnersByDivisions({ organizationId, divisions }) {
117+
let organizationLearners;
118+
117119
const knexConnection = DomainTransaction.getConnection();
118-
const organizationLearners = await knexConnection
119-
.from('view-active-organization-learners')
120-
.where({ organizationId })
121-
.whereIn('division', divisions);
120+
const queryBuilder = knexConnection.from('view-active-organization-learners').where({ organizationId });
121+
if (divisions.length > 0) {
122+
organizationLearners = await queryBuilder.whereIn('division', divisions);
123+
} else {
124+
organizationLearners = await queryBuilder;
125+
}
122126
return organizationLearners.map((organizationLearner) => new OrganizationLearner(organizationLearner));
123127
}
124128

Diff for: api/tests/prescription/organization-learner/integration/infrastructure/repositories/organization-learner-repository_test.js

+18
Original file line numberDiff line numberDiff line change
@@ -724,5 +724,23 @@ describe('Integration | Infrastructure | Repository | Organization Learner', fun
724724
expect(result).to.be.empty;
725725
});
726726
});
727+
context('when divisions is an empty array', function () {
728+
it('should return all learners', async function () {
729+
// given
730+
databaseBuilder.factory.buildOrganizationLearner({ organizationId, division: '6eme A' });
731+
databaseBuilder.factory.buildOrganizationLearner({ organizationId, division: '6eme B' });
732+
733+
await databaseBuilder.commit();
734+
735+
// when
736+
const result = await organizationLearnerRepository.findOrganizationLearnersByDivisions({
737+
organizationId,
738+
divisions: [],
739+
});
740+
741+
// then
742+
expect(result).to.have.lengthOf(2);
743+
});
744+
});
727745
});
728746
});

Diff for: orga/app/components/attestations/sixth-grade.gjs

+32-19
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@ import PixButton from '@1024pix/pix-ui/components/pix-button';
22
import PixMultiSelect from '@1024pix/pix-ui/components/pix-multi-select';
33
import { on } from '@ember/modifier';
44
import { action } from '@ember/object';
5+
import { service } from '@ember/service';
56
import Component from '@glimmer/component';
67
import { tracked } from '@glimmer/tracking';
78
import { t } from 'ember-intl';
9+
import { eq } from 'ember-truth-helpers';
810

911
import PageTitle from '../ui/page-title';
1012

1113
export default class AttestationsSixthGrade extends Component {
1214
@tracked selectedDivisions = [];
15+
@service currentUser;
1316

1417
@action
1518
onSubmit(event) {
@@ -30,27 +33,37 @@ export default class AttestationsSixthGrade extends Component {
3033
<template>
3134
<PageTitle>
3235
<:title>{{t "pages.attestations.title"}}</:title>
33-
<:subtitle>
36+
</PageTitle>
37+
38+
{{#if (eq @divisions undefined)}}
39+
<div>
3440
<p class="attestations-page__text">
35-
{{t "pages.attestations.description"}}
41+
{{t "pages.attestations.basic-description"}}
3642
</p>
37-
</:subtitle>
38-
</PageTitle>
43+
<PixButton @triggerAction={{this.onSubmit}} @size="small">
44+
{{t "pages.attestations.download-attestations-button"}}
45+
</PixButton>
46+
</div>
47+
{{else}}
48+
<p class="attestations-page__text">
49+
{{t "pages.attestations.divisions-description"}}
50+
</p>
3951

40-
<form class="attestations-page__action" {{on "submit" this.onSubmit}}>
41-
<PixMultiSelect
42-
@isSearchable={{true}}
43-
@options={{@divisions}}
44-
@values={{this.selectedDivisions}}
45-
@onChange={{this.onSelectDivision}}
46-
@placeholder={{t "common.filters.placeholder"}}
47-
>
48-
<:label>{{t "pages.attestations.select-label"}}</:label>
49-
<:default as |option|>{{option.label}}</:default>
50-
</PixMultiSelect>
51-
<PixButton @type="submit" id="download_attestations" @size="small" @isDisabled={{this.isDisabled}}>
52-
{{t "pages.attestations.download-attestations-button"}}
53-
</PixButton>
54-
</form>
52+
<form class="attestations-page__action" {{on "submit" this.onSubmit}}>
53+
<PixMultiSelect
54+
@isSearchable={{true}}
55+
@options={{@divisions}}
56+
@values={{this.selectedDivisions}}
57+
@onChange={{this.onSelectDivision}}
58+
@placeholder={{t "common.filters.placeholder"}}
59+
>
60+
<:label>{{t "pages.attestations.select-label"}}</:label>
61+
<:default as |option|>{{option.label}}</:default>
62+
</PixMultiSelect>
63+
<PixButton @type="submit" id="download_attestations" @size="small" @isDisabled={{this.isDisabled}}>
64+
{{t "pages.attestations.download-attestations-button"}}
65+
</PixButton>
66+
</form>
67+
{{/if}}
5568
</template>
5669
}

Diff for: orga/app/controllers/authenticated/attestations.js

+10-4
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,18 @@ export default class AuthenticatedAttestationsController extends Controller {
1515
@action
1616
async downloadSixthGradeAttestationsFile(selectedDivisions) {
1717
try {
18+
let url;
1819
const organizationId = this.currentUser.organization.id;
19-
const formatedDivisionsForQuery = selectedDivisions
20-
.map((division) => `divisions[]=${encodeURIComponent(division)}`)
21-
.join('&');
20+
const baseUrl = `/api/organizations/${organizationId}/attestations/${SIXTH_GRADE_ATTESTATION_KEY}`;
21+
if (selectedDivisions.length > 0) {
22+
const formatedDivisionsForQuery = selectedDivisions
23+
.map((division) => `divisions[]=${encodeURIComponent(division)}`)
24+
.join('&');
2225

23-
const url = `/api/organizations/${organizationId}/attestations/${SIXTH_GRADE_ATTESTATION_KEY}?${formatedDivisionsForQuery}`;
26+
url = baseUrl + `?${formatedDivisionsForQuery}`;
27+
} else {
28+
url = baseUrl;
29+
}
2430

2531
const token = this.session.isAuthenticated ? this.session.data.authenticated.access_token : '';
2632

Diff for: orga/app/routes/authenticated/attestations.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ export default class AuthenticatedAttestationsRoute extends Route {
1212
}
1313

1414
async model() {
15-
const divisions = await this.currentUser.organization.divisions;
16-
const options = divisions.map(({ name }) => ({ label: name, value: name }));
17-
return { options };
15+
if (this.currentUser.organization.isManagingStudents) {
16+
const divisions = await this.currentUser.organization.divisions;
17+
const options = divisions.map(({ name }) => ({ label: name, value: name }));
18+
return { options };
19+
}
1820
}
1921
}

Diff for: orga/tests/integration/components/attestations/sixth-grade-test.gjs

+81-43
Original file line numberDiff line numberDiff line change
@@ -10,61 +10,99 @@ import setupIntlRenderingTest from '../../../helpers/setup-intl-rendering';
1010
module('Integration | Component | Attestations | Sixth-grade', function (hooks) {
1111
setupIntlRenderingTest(hooks);
1212

13-
test('it should display all basics informations', async function (assert) {
14-
// given
15-
const onSubmit = sinon.stub();
16-
const divisions = [];
17-
18-
// when
19-
const screen = await render(<template><SixthGrade @divisions={{divisions}} @onSubmit={{onSubmit}} /></template>);
20-
// then
21-
assert.ok(screen.getByRole('heading', { name: t('pages.attestations.title') }));
22-
assert.ok(screen.getByText(t('pages.attestations.description')));
23-
assert.ok(screen.getByRole('textbox', { name: t('pages.attestations.select-label') }));
24-
assert.ok(screen.getByPlaceholderText(t('common.filters.placeholder')));
25-
assert.ok(screen.getByRole('button', { name: t('pages.attestations.download-attestations-button') }));
26-
});
13+
module('when organization has divisions', function () {
14+
test('it should display all specifics informations for divisions', async function (assert) {
15+
// given
16+
const onSubmit = sinon.stub();
17+
const divisions = [];
18+
19+
// when
20+
const screen = await render(<template><SixthGrade @divisions={{divisions}} @onSubmit={{onSubmit}} /></template>);
21+
// then
22+
assert.ok(screen.getByRole('heading', { name: t('pages.attestations.title') }));
23+
assert.ok(screen.getByText(t('pages.attestations.divisions-description')));
24+
assert.ok(screen.getByRole('textbox', { name: t('pages.attestations.select-label') }));
25+
assert.ok(screen.getByPlaceholderText(t('common.filters.placeholder')));
26+
assert.ok(screen.getByRole('button', { name: t('pages.attestations.download-attestations-button') }));
27+
});
2728

28-
test('download button is disabled if there is no selected divisions', async function (assert) {
29-
// given
30-
const onSubmit = sinon.stub();
31-
const divisions = [];
29+
test('download button is disabled if there is no selected divisions', async function (assert) {
30+
// given
31+
const onSubmit = sinon.stub();
32+
const divisions = [];
3233

33-
// when
34-
const screen = await render(<template><SixthGrade @divisions={{divisions}} @onSubmit={{onSubmit}} /></template>);
34+
// when
35+
const screen = await render(<template><SixthGrade @divisions={{divisions}} @onSubmit={{onSubmit}} /></template>);
3536

36-
// then
37-
const downloadButton = await screen.getByRole('button', {
38-
name: t('pages.attestations.download-attestations-button'),
37+
// then
38+
const downloadButton = await screen.getByRole('button', {
39+
name: t('pages.attestations.download-attestations-button'),
40+
});
41+
assert.dom(downloadButton).isDisabled();
3942
});
40-
assert.dom(downloadButton).isDisabled();
41-
});
4243

43-
test('it should call onSubmit action with selected divisions', async function (assert) {
44-
// given
45-
const onSubmit = sinon.stub();
44+
test('it should call onSubmit action with selected divisions', async function (assert) {
45+
// given
46+
const onSubmit = sinon.stub();
4647

47-
const divisions = [{ label: 'division1', value: 'division1' }];
48+
const divisions = [{ label: 'division1', value: 'division1' }];
4849

49-
// when
50-
const screen = await render(<template><SixthGrade @divisions={{divisions}} @onSubmit={{onSubmit}} /></template>);
50+
// when
51+
const screen = await render(<template><SixthGrade @divisions={{divisions}} @onSubmit={{onSubmit}} /></template>);
5152

52-
const multiSelect = await screen.getByRole('textbox', { name: t('pages.attestations.select-label') });
53-
await click(multiSelect);
53+
const multiSelect = await screen.getByRole('textbox', { name: t('pages.attestations.select-label') });
54+
await click(multiSelect);
5455

55-
const firstDivisionOption = await screen.findByRole('checkbox', { name: 'division1' });
56-
await click(firstDivisionOption);
56+
const firstDivisionOption = await screen.findByRole('checkbox', { name: 'division1' });
57+
await click(firstDivisionOption);
5758

58-
const downloadButton = await screen.getByRole('button', {
59-
name: t('pages.attestations.download-attestations-button'),
59+
const downloadButton = await screen.getByRole('button', {
60+
name: t('pages.attestations.download-attestations-button'),
61+
});
62+
63+
// we need to get out of input choice to click on download button, so we have to click again on the multiselect to close it
64+
await click(multiSelect);
65+
await click(downloadButton);
66+
67+
// then
68+
sinon.assert.calledWithExactly(onSubmit, ['division1']);
69+
assert.ok(true);
6070
});
71+
});
6172

62-
// we need to get out of input choice to click on download button, so we have to click again on the multiselect to close it
63-
await click(multiSelect);
64-
await click(downloadButton);
73+
module('when organization does not have divisions', function () {
74+
test('it should display all basics informations', async function (assert) {
75+
// given
76+
const onSubmit = sinon.stub();
77+
const divisions = undefined;
6578

66-
// then
67-
sinon.assert.calledWithExactly(onSubmit, ['division1']);
68-
assert.ok(true);
79+
// when
80+
const screen = await render(<template><SixthGrade @divisions={{divisions}} @onSubmit={{onSubmit}} /></template>);
81+
// then
82+
assert.notOk(screen.queryByRole('textbox', { name: t('pages.attestations.select-label') }));
83+
assert.ok(screen.getByRole('heading', { name: t('pages.attestations.title') }));
84+
assert.ok(screen.getByText(t('pages.attestations.basic-description')));
85+
assert.ok(screen.getByRole('button', { name: t('pages.attestations.download-attestations-button') }));
86+
});
87+
88+
test('it should call onSubmit action with empty divisions', async function (assert) {
89+
// given
90+
const onSubmit = sinon.stub();
91+
92+
const divisions = undefined;
93+
94+
// when
95+
const screen = await render(<template><SixthGrade @divisions={{divisions}} @onSubmit={{onSubmit}} /></template>);
96+
97+
const downloadButton = await screen.getByRole('button', {
98+
name: t('pages.attestations.download-attestations-button'),
99+
});
100+
101+
await click(downloadButton);
102+
103+
// then
104+
sinon.assert.calledWithExactly(onSubmit, []);
105+
assert.ok(true);
106+
});
69107
});
70108
});

0 commit comments

Comments
 (0)