Skip to content

Commit db31d29

Browse files
authored
[FEATURE] Conditionner l'affichage des onglets du catalogue en fonction de la présence de schema dans l'organisation (PIX-23525) (#16881)
## 🪧 Problème Actuellement, les onglets “Évaluation” et “Apprentissage” sont visibles pour toutes les organisations, peu importe si elles disposent de PC ou schémas. Un problème se pose pour l’international. Les parcours apprenants ne sont pas massivement diffusés à l'étranger. Les parcours d'évaluation sont le seul outil de développement de compétences. Le métier nous remonte que cet onglet “Apprentissage” vide risque de créer de la confusion, ou de susciter des demandes que Pix ne pourra pas adresser. ## 🌈 Proposition Conditionner l’affichage des onglets aux objets disponibles dans l’organisation. ## 🎉 Pour tester - Se connecter à Pix Orga. - Aller sur le catalogue de l'organisation `SCO SIECLE` et constater que tous les onglets sont affichés. - Aller sur le catalogue de l'organisation `Attestation` et constater qu'on est redirigé sur l'onglet Evaluation et que c'est le seul onglet visible.
1 parent e90e3c8 commit db31d29

5 files changed

Lines changed: 173 additions & 29 deletions

File tree

orga/app/components/catalogue/list.gjs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,17 +115,29 @@ export default class List extends Component {
115115
);
116116
}
117117

118+
get displayAllTab() {
119+
return this.args.hasBlueprints;
120+
}
121+
122+
get displayBlueprintTab() {
123+
return this.args.hasBlueprints;
124+
}
125+
118126
<template>
119127
<div class="catalogue">
120128
<PixTabs @variant="orga" class="catalogue__nav" @ariaLabel={{t "pages.catalogue.tab-filters.label"}}>
121-
<LinkTo @route="authenticated.catalogue.list" @model="all">
122-
{{t "pages.catalogue.tab-filters.all"}}</LinkTo>
129+
{{#if this.displayAllTab}}
130+
<LinkTo @route="authenticated.catalogue.list" @model="all">
131+
{{t "pages.catalogue.tab-filters.all"}}</LinkTo>
132+
{{/if}}
123133
<LinkTo @route="authenticated.catalogue.list" @model="targetProfile">{{t
124134
"pages.catalogue.tab-filters.target-profiles"
125135
}}</LinkTo>
126-
<LinkTo @route="authenticated.catalogue.list" @model="blueprint">{{t
127-
"pages.catalogue.tab-filters.blueprints"
128-
}}</LinkTo>
136+
{{#if this.displayBlueprintTab}}
137+
<LinkTo @route="authenticated.catalogue.list" @model="blueprint">{{t
138+
"pages.catalogue.tab-filters.blueprints"
139+
}}</LinkTo>
140+
{{/if}}
129141
</PixTabs>
130142
<PixFilterBanner
131143
class="catalogue__filters"

orga/app/routes/authenticated/catalogue/list.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,17 @@ export default class AuthenticatedCatalogueFilter extends Route {
4444
});
4545
}
4646

47-
return { courses, currentCourse, type };
47+
const hasBlueprints = courses.some((course) => course.type === 'blueprint');
48+
49+
return { courses, currentCourse, type, hasBlueprints };
50+
}
51+
52+
afterModel({ hasBlueprints }, transition) {
53+
if (transition.to.params?.type === 'all') {
54+
if (!hasBlueprints) {
55+
return this.router.replaceWith('authenticated.catalogue.list', 'targetProfile');
56+
}
57+
}
4858
}
4959

5060
handleCache() {

orga/app/templates/authenticated/catalogue/list.gjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ import List from 'pix-orga/components/catalogue/list';
1111
@areas={{@controller.areas}}
1212
@competences={{@controller.competences}}
1313
@currentCourse={{@model.currentCourse}}
14+
@hasBlueprints={{@model.hasBlueprints}}
1415
/>
1516
</template>

orga/tests/integration/components/catalogue/list-test.gjs

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -55,26 +55,55 @@ module('Integration | Component | Catalogue::List', function (hooks) {
5555
});
5656

5757
module('type filters', function () {
58-
test('it displays navigation links to filter by type', async function (assert) {
59-
// given
60-
const courses = [
61-
{ name: 'Ma super formation', type: 'targetProfile', nbTubes: 5, category: 'PREDEFINED' },
62-
{ name: 'Mon parcours combiné', type: 'blueprint', nbModules: 2 },
63-
];
64-
const updateFilter = sinon.stub();
58+
module('when hasBlueprints is true', function () {
59+
test('it displays all navigation links to filter by type', async function (assert) {
60+
// given
61+
const courses = [
62+
{ name: 'Ma super formation', type: 'targetProfile', nbTubes: 5, category: 'PREDEFINED' },
63+
{ name: 'Mon parcours combiné', type: 'blueprint', nbModules: 2 },
64+
];
65+
const updateFilter = sinon.stub();
6566

66-
// when
67-
const screen = await render(
68-
<template><List @courses={{courses}} @updateFilter={{updateFilter}} @type="blueprint" /></template>,
69-
);
67+
// when
68+
const screen = await render(
69+
<template>
70+
<List @courses={{courses}} @updateFilter={{updateFilter}} @type="blueprint" @hasBlueprints={{true}} />
71+
</template>,
72+
);
7073

71-
// then
72-
const allLink = screen.getByRole('link', { name: t('pages.catalogue.tab-filters.all') });
73-
assert.ok(allLink.href.endsWith('/catalogue/all'));
74-
const targetProfilLink = screen.getByRole('link', { name: t('pages.catalogue.tab-filters.target-profiles') });
75-
assert.ok(targetProfilLink.href.endsWith('/catalogue/targetProfile'));
76-
const blueprintLink = screen.getByRole('link', { name: t('pages.catalogue.tab-filters.blueprints') });
77-
assert.ok(blueprintLink.href.endsWith('/catalogue/blueprint'));
74+
// then
75+
const allLink = screen.getByRole('link', { name: t('pages.catalogue.tab-filters.all') });
76+
assert.ok(allLink.href.endsWith('/catalogue/all'));
77+
const targetProfilLink = screen.getByRole('link', { name: t('pages.catalogue.tab-filters.target-profiles') });
78+
assert.ok(targetProfilLink.href.endsWith('/catalogue/targetProfile'));
79+
const blueprintLink = screen.getByRole('link', { name: t('pages.catalogue.tab-filters.blueprints') });
80+
assert.ok(blueprintLink.href.endsWith('/catalogue/blueprint'));
81+
});
82+
});
83+
84+
module('when hasBlueprints is false', function () {
85+
test('it display only targetProfile navigation link', async function (assert) {
86+
// given
87+
const courses = [{ name: 'Ma super formation', type: 'targetProfile', nbTubes: 5, category: 'PREDEFINED' }];
88+
const updateFilter = sinon.stub();
89+
90+
// when
91+
const screen = await render(
92+
<template>
93+
<List
94+
@courses={{courses}}
95+
@updateFilter={{updateFilter}}
96+
@type="targetProfile"
97+
@hasBlueprints={{false}}
98+
/>
99+
</template>,
100+
);
101+
102+
// then
103+
assert.dom(screen.queryByRole('link', { name: t('pages.catalogue.tab-filters.target-profiles') })).exists();
104+
assert.dom(screen.queryByRole('link', { name: t('pages.catalogue.tab-filters.all') })).doesNotExist();
105+
assert.dom(screen.queryByRole('link', { name: t('pages.catalogue.tab-filters.blueprints') })).doesNotExist();
106+
});
78107
});
79108

80109
test('it filters list by type', async function (assert) {

orga/tests/unit/routes/authenticated/catalogue/list-test.js

Lines changed: 97 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ module('Unit | Route | authenticated/catalogue/list', function (hooks) {
4949
const currentUser = this.owner.lookup('service:current-user');
5050
const organizationId = Symbol('organizationId');
5151
sinon.stub(currentUser, 'organization').value({ id: organizationId });
52-
const courses = Symbol('Courses');
52+
const courses = [Symbol('Course')];
5353

5454
sinon.stub(store, 'peekAll').withArgs('course').returns([]);
5555
sinon
@@ -62,8 +62,14 @@ module('Unit | Route | authenticated/catalogue/list', function (hooks) {
6262

6363
// then
6464
assert.ok(store.findAll.calledOnce);
65-
assert.deepEqual(result, { courses, currentCourse: undefined, type: 'all' });
65+
assert.deepEqual(result, {
66+
courses,
67+
currentCourse: undefined,
68+
type: 'all',
69+
hasBlueprints: false,
70+
});
6671
});
72+
6773
test('it returns cached courses without calling the API when the store is not empty', async function (assert) {
6874
// given
6975
const currentUser = this.owner.lookup('service:current-user');
@@ -80,8 +86,15 @@ module('Unit | Route | authenticated/catalogue/list', function (hooks) {
8086
const result = await route.model({ type: 'all' });
8187
// then
8288
assert.ok(store.findAll.notCalled);
83-
assert.deepEqual(result, { courses, currentCourse: undefined, type: 'all' });
89+
assert.deepEqual(result, {
90+
courses,
91+
currentCourse: undefined,
92+
type: 'all',
93+
94+
hasBlueprints: false,
95+
});
8496
});
97+
8598
test('it unload cached courses when organization change', async function (assert) {
8699
// given
87100
const currentUser = this.owner.lookup('service:current-user');
@@ -98,7 +111,13 @@ module('Unit | Route | authenticated/catalogue/list', function (hooks) {
98111
const result = await route.model({ type: 'all' });
99112
// then
100113
assert.ok(store.findAll.notCalled);
101-
assert.deepEqual(result, { courses, currentCourse: undefined, type: 'all' });
114+
assert.deepEqual(result, {
115+
courses,
116+
currentCourse: undefined,
117+
type: 'all',
118+
119+
hasBlueprints: false,
120+
});
102121
});
103122
});
104123

@@ -125,7 +144,53 @@ module('Unit | Route | authenticated/catalogue/list', function (hooks) {
125144

126145
// then
127146
assert.ok(store.findRecord.calledOnce);
128-
assert.deepEqual(result, { courses, currentCourse, type: 'all' });
147+
assert.deepEqual(result, {
148+
courses,
149+
currentCourse,
150+
type: 'all',
151+
152+
hasBlueprints: false,
153+
});
154+
});
155+
});
156+
157+
module('hasBlueprints', function () {
158+
test('it should return true if at least one of the courses is a blueprint', async function (assert) {
159+
// given
160+
const route = this.owner.lookup('route:authenticated/catalogue/list');
161+
const store = this.owner.lookup('service:store');
162+
const currentUser = this.owner.lookup('service:current-user');
163+
const organizationId = Symbol('organizationId');
164+
sinon.stub(currentUser, 'organization').value({ id: organizationId });
165+
const courses = [{ type: 'blueprint' }, { type: 'targetProfile' }];
166+
167+
sinon.stub(store, 'peekAll').withArgs('course').returns(courses);
168+
sinon.stub(store, 'findAll');
169+
170+
// when
171+
const result = await route.model({ type: 'all' });
172+
173+
// then
174+
assert.true(result.hasBlueprints);
175+
});
176+
177+
test('it should return false otherwise', async function (assert) {
178+
// given
179+
const route = this.owner.lookup('route:authenticated/catalogue/list');
180+
const store = this.owner.lookup('service:store');
181+
const currentUser = this.owner.lookup('service:current-user');
182+
const organizationId = Symbol('organizationId');
183+
sinon.stub(currentUser, 'organization').value({ id: organizationId });
184+
const courses = [{ type: 'targetProfile' }, { type: 'targetProfile' }];
185+
186+
sinon.stub(store, 'peekAll').withArgs('course').returns(courses);
187+
sinon.stub(store, 'findAll');
188+
189+
// when
190+
const result = await route.model({ type: 'all' });
191+
192+
// then
193+
assert.false(result.hasBlueprints);
129194
});
130195
});
131196
});
@@ -181,4 +246,31 @@ module('Unit | Route | authenticated/catalogue/list', function (hooks) {
181246
assert.true(controller.set.secondCall.calledWithExactly('blueprintId', null));
182247
});
183248
});
249+
250+
module('afterModel', function () {
251+
module('when there is a transition to "all"', function () {
252+
const transition = {
253+
to: {
254+
params: {
255+
type: 'all',
256+
},
257+
},
258+
};
259+
260+
test('it should redirect to "targetProfile" if there is no blueprint', async function (assert) {
261+
// given
262+
const route = this.owner.lookup('route:authenticated/catalogue/list');
263+
const routerService = this.owner.lookup('service:router');
264+
sinon.stub(routerService, 'replaceWith');
265+
266+
const hasBlueprints = false;
267+
268+
// when
269+
route.afterModel({ hasBlueprints }, transition);
270+
271+
// then
272+
assert.ok(routerService.replaceWith.calledWith('authenticated.catalogue.list', 'targetProfile'));
273+
});
274+
});
275+
});
184276
});

0 commit comments

Comments
 (0)