diff --git a/admin/app/components/certifications/list.gjs b/admin/app/components/certifications/list.gjs deleted file mode 100644 index 0856f202e28..00000000000 --- a/admin/app/components/certifications/list.gjs +++ /dev/null @@ -1,65 +0,0 @@ -import PixPagination from '@1024pix/pix-ui/components/pix-pagination'; -import { LinkTo } from '@ember/routing'; - -import CertificationInfoPublished from './info-published'; -import CertificationStatus from './status'; - - diff --git a/admin/app/components/sessions/certifications/header.gjs b/admin/app/components/sessions/certifications/header.gjs new file mode 100644 index 00000000000..3a60aaf9ee2 --- /dev/null +++ b/admin/app/components/sessions/certifications/header.gjs @@ -0,0 +1,80 @@ +import PixButton from '@1024pix/pix-ui/components/pix-button'; +import PixTooltip from '@1024pix/pix-ui/components/pix-tooltip'; +import { action } from '@ember/object'; +import { service } from '@ember/service'; +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { not } from 'ember-truth-helpers'; + +import ConfirmPopup from '../../confirm-popup'; + +export default class CertificationsHeader extends Component { + @service accessControl; + + @tracked isModalDisplayed = false; + @tracked confirmMessage = null; + + get canPublish() { + return ( + !this.args.juryCertificationSummaries.some( + (certification) => certification.status === 'error' && !certification.isCancelled, + ) && this.args.session.isFinalized + ); + } + + @action + displayConfirmationModal() { + this.confirmMessage = this.args.session.isPublished + ? 'Souhaitez-vous dépublier la session ?' + : 'Souhaitez-vous publier la session ?'; + this.isModalDisplayed = true; + } + + @action + onModalCancel() { + this.isModalDisplayed = false; + } + + @action + async toggleSessionPublication() { + if (this.args.session.isPublished) { + await this.args.unpublishSession(); + } else { + await this.args.publishSession(); + } + this.isModalDisplayed = false; + } + + +} diff --git a/admin/app/components/certifications/info-published.gjs b/admin/app/components/sessions/certifications/info-published.gjs similarity index 100% rename from admin/app/components/certifications/info-published.gjs rename to admin/app/components/sessions/certifications/info-published.gjs diff --git a/admin/app/components/sessions/certifications/list.gjs b/admin/app/components/sessions/certifications/list.gjs new file mode 100644 index 00000000000..df923330d7e --- /dev/null +++ b/admin/app/components/sessions/certifications/list.gjs @@ -0,0 +1,72 @@ +import PixPagination from '@1024pix/pix-ui/components/pix-pagination'; +import { LinkTo } from '@ember/routing'; +import Component from '@glimmer/component'; + +import CertificationInfoPublished from './info-published'; +import CertificationStatus from './status'; + +export default class CertificationsHeader extends Component { + get sortedCertificationJurySummaries() { + return this.args.juryCertificationSummaries.sortBy('numberOfCertificationIssueReportsWithRequiredAction').reverse(); + } + + +} diff --git a/admin/app/components/certifications/status.gjs b/admin/app/components/sessions/certifications/status.gjs similarity index 100% rename from admin/app/components/certifications/status.gjs rename to admin/app/components/sessions/certifications/status.gjs diff --git a/admin/app/controllers/authenticated/sessions/session/certifications.js b/admin/app/controllers/authenticated/sessions/session/certifications.js index 0e194062197..e4d58b7d0cd 100644 --- a/admin/app/controllers/authenticated/sessions/session/certifications.js +++ b/admin/app/controllers/authenticated/sessions/session/certifications.js @@ -1,85 +1,38 @@ import Controller from '@ember/controller'; import { action } from '@ember/object'; import { service } from '@ember/service'; -import { tracked } from '@glimmer/tracking'; -const DEFAULT_PAGE_NUMBER = 1; export default class ListController extends Controller { @service pixToast; @service store; - @service accessControl; - - @tracked pageNumber = DEFAULT_PAGE_NUMBER; - @tracked pageSize = 10; - - @tracked displayConfirm = false; - @tracked confirmMessage = null; - - get canPublish() { - const juryCertificationSummaries = this.model.juryCertificationSummaries; - const { session } = this.model; - - return ( - !juryCertificationSummaries.some( - (certification) => certification.status === 'error' && !certification.isCancelled, - ) && session.isFinalized - ); - } - - get sortedCertificationJurySummaries() { - return this.model.juryCertificationSummaries - .sortBy('numberOfCertificationIssueReportsWithRequiredAction') - .reverse(); - } - - @action - displayCertificationStatusUpdateConfirmationModal() { - const sessionIsPublished = this.model.session.isPublished; - - if (!this.canPublish && !sessionIsPublished) return; - - this.confirmMessage = sessionIsPublished - ? 'Souhaitez-vous dépublier la session ?' - : 'Souhaitez-vous publier la session ?'; - this.displayConfirm = true; - } @action - async toggleSessionPublication() { - const isPublished = this.model.session.isPublished; - if (isPublished) { - await this.unpublishSession(); - } else { - await this.publishSession(); - } - } - - async unpublishSession() { + async publishSession() { try { - await this.model.session.save({ adapterOptions: { updatePublishedCertifications: true, toPublish: false } }); + await this.model.session.save({ adapterOptions: { updatePublishedCertifications: true, toPublish: true } }); await this.model.juryCertificationSummaries.reload(); - this.pixToast.sendSuccessNotification({ message: 'Les certifications ont été correctement dépubliées.' }); + this.pixToast.sendSuccessNotification({ message: 'Les certifications ont été correctement publiées.' }); } catch (e) { this.notifyError(e); + } finally { + await this.forceRefreshModelFromBackend(); } - this.hideConfirmationModal(); } - async publishSession() { + @action + async unpublishSession() { try { - await this.model.session.save({ adapterOptions: { updatePublishedCertifications: true, toPublish: true } }); + await this.model.session.save({ adapterOptions: { updatePublishedCertifications: true, toPublish: false } }); + await this.model.juryCertificationSummaries.reload(); + this.pixToast.sendSuccessNotification({ message: 'Les certifications ont été correctement dépubliées.' }); } catch (e) { this.notifyError(e); + } finally { await this.forceRefreshModelFromBackend(); } - - await this.model.juryCertificationSummaries.reload(); - if (this.model.session.isPublished) { - this.pixToast.sendSuccessNotification({ message: 'Les certifications ont été correctement publiées.' }); - } - this.hideConfirmationModal(); } + @action notifyError(error) { if (error.errors && error.errors[0] && error.errors[0].detail) { this.pixToast.sendErrorNotification({ message: error.errors[0].detail }); @@ -88,16 +41,8 @@ export default class ListController extends Controller { } } + @action async forceRefreshModelFromBackend() { await this.store.findRecord('session', this.model.session.id, { reload: true }); } - - hideConfirmationModal() { - this.displayConfirm = false; - } - - @action - onCancelConfirm() { - this.displayConfirm = false; - } } diff --git a/admin/app/models/session.js b/admin/app/models/session.js index 4afe137b1c0..edf4b21d369 100644 --- a/admin/app/models/session.js +++ b/admin/app/models/session.js @@ -64,9 +64,9 @@ export default class Session extends Model { return Boolean(this.hasIncident || this.hasJoiningIssue); } - @computed('status') + @computed('publishedAt') get isPublished() { - return this.status === PROCESSED; + return this.publishedAt !== null; } @computed('juryCertificationSummaries.[]') diff --git a/admin/app/templates/authenticated/sessions/session/certifications.hbs b/admin/app/templates/authenticated/sessions/session/certifications.hbs index 12172ce3599..a73b23e9fb6 100644 --- a/admin/app/templates/authenticated/sessions/session/certifications.hbs +++ b/admin/app/templates/authenticated/sessions/session/certifications.hbs @@ -3,53 +3,19 @@
-
-

Certifications

- {{#if this.accessControl.hasAccessToCertificationActionsScope}} -
- - {{#if this.model.session.isPublished}} - Dépublier la session - {{else}} - - {{#if this.canPublish}} - Publier la session - {{else}} - - - <:triggerElement> - - Publier la session - - - <:tooltip> - Vous ne pouvez pas publier la session tant qu'elle n'est pas finalisée ou qu'il reste des - certifications en erreur. - - - {{/if}} - - {{/if}} -
- {{/if}} -
+
-
-
- - \ No newline at end of file + \ No newline at end of file diff --git a/admin/mirage/config.js b/admin/mirage/config.js index e21c3b52a67..12fd8cb5235 100644 --- a/admin/mirage/config.js +++ b/admin/mirage/config.js @@ -114,7 +114,16 @@ function routes() { this.get('/admin/sessions', findPaginatedAndFilteredSessions); this.get('/admin/sessions/to-publish', getToBePublishedSessions); this.get('/admin/sessions/with-required-action', getWithRequiredActionSessions); - this.patch('/admin/sessions/:id/publish', () => { + this.patch('/admin/sessions/:id/publish', (schema, request) => { + const sessionId = request.params.id; + const session = schema.sessions.findBy({ id: sessionId }); + session.update({ publishedAt: new Date() }); + return new Response(204); + }); + this.patch('/admin/sessions/:id/unpublish', (schema, request) => { + const sessionId = request.params.id; + const session = schema.sessions.findBy({ id: sessionId }); + session.update({ publishedAt: null }); return new Response(204); }); this.get('/admin/sessions/:id'); diff --git a/admin/tests/acceptance/authenticated/sessions/session/certifications-test.js b/admin/tests/acceptance/authenticated/sessions/session/certifications-test.js index 4ad71ebe2a2..314fa333fea 100644 --- a/admin/tests/acceptance/authenticated/sessions/session/certifications-test.js +++ b/admin/tests/acceptance/authenticated/sessions/session/certifications-test.js @@ -11,30 +11,56 @@ module('Acceptance | authenticated/sessions/session/certifications', function (h setupApplicationTest(hooks); setupMirage(hooks); - module('When user has role metier', function () { - test('it should not show publish button', async function (assert) { - // given - await authenticateAdminMemberWithRole({ isMetier: true })(server); - server.create('session', { id: '1' }); + let session, juryCertificationSummaries; - // when - const screen = await visit('/sessions/1/certifications'); + hooks.beforeEach(async function () { + await authenticateAdminMemberWithRole({ isSuperAdmin: true })(server); - // then - assert.dom(screen.queryByText('Publier la session')).doesNotExist(); + juryCertificationSummaries = server.createList('jury-certification-summary', 11); + + session = server.create('session', 'finalized', { + id: '1', + juryCertificationSummaries, }); }); - module('When requesting next page from pagination', function () { - test('it should display next page jury certificate summary', async function (assert) { - await authenticateAdminMemberWithRole({ isSuperAdmin: true })(server); + module('When the session is finalized', function () { + module('When the session is not published', function () { + test('it should be possible to publish the session', async function (assert) { + // given + session.update({ + publishedAt: null, + }); + + // when + const screen = await visit('/sessions/1/certifications'); + click(screen.getByRole('button', { name: 'Publier la session' })); + click(await screen.findByRole('button', { name: 'Confirmer' })); - const juryCertificationSummaries = server.createList('jury-certification-summary', 11); - server.create('session', { - id: '1', - juryCertificationSummaries, + assert.dom(await screen.findByRole('button', { name: 'Dépublier la session' })).exists(); }); + }); + + module('When the session is published', function () { + test('it should be possible to unpublish the session', async function (assert) { + // given + session.update({ + publishedAt: new Date(), + }); + + // when + const screen = await visit('/sessions/1/certifications'); + click(screen.getByRole('button', { name: 'Dépublier la session' })); + click(await screen.findByRole('button', { name: 'Confirmer' })); + assert.dom(await screen.findByRole('button', { name: 'Publier la session' })).exists(); + }); + }); + }); + + // TODO : move to Certifications::List component integration level + module('When requesting next page from pagination', function () { + test('it should display next page jury certificate summary', async function (assert) { // when const screen = await visit('/sessions/1/certifications'); await click(screen.getByRole('button', { name: 'Aller à la page suivante' })); @@ -48,16 +74,9 @@ module('Acceptance | authenticated/sessions/session/certifications', function (h }); }); + // TODO : move to Certifications::List component integration level module('When requesting page 2 of certification from url', function () { test('it should display page 2 jury certificate summary', async function (assert) { - await authenticateAdminMemberWithRole({ isSuperAdmin: true })(server); - - const juryCertificationSummaries = server.createList('jury-certification-summary', 11); - server.create('session', { - id: '1', - juryCertificationSummaries, - }); - // when const screen = await visit('/sessions/1/certifications?pageNumber=2&pageSize=10'); diff --git a/admin/tests/acceptance/authenticated/sessions/session/informations-test.js b/admin/tests/acceptance/authenticated/sessions/session/informations-test.js index 354d0c0a8c0..e23716d62d6 100644 --- a/admin/tests/acceptance/authenticated/sessions/session/informations-test.js +++ b/admin/tests/acceptance/authenticated/sessions/session/informations-test.js @@ -45,6 +45,7 @@ module('Acceptance | authenticated/sessions/session/informations', function (hoo id: '3', finalizedAt: new Date('2023-01-31'), examinerGlobalComment: 'Vraiment, super session!', + publishedAt: null, status: 'finalized', }); const screen = await visit('/sessions/3'); diff --git a/admin/tests/acceptance/session-test.js b/admin/tests/acceptance/session-test.js index 46f9938b0b6..de4fe213788 100644 --- a/admin/tests/acceptance/session-test.js +++ b/admin/tests/acceptance/session-test.js @@ -122,7 +122,9 @@ module('Acceptance | Session pages', function (hooks) { module('When the session has not been published', function () { test('it show the disabled certificates download button', async function (assert) { // given - this.server.create('session'); + this.server.create('session', { + publishedAt: null, + }); // when const screen = await visit('/sessions/2'); diff --git a/admin/tests/integration/components/sessions/certifications/header-test.gjs b/admin/tests/integration/components/sessions/certifications/header-test.gjs new file mode 100644 index 00000000000..fe96bd6cd59 --- /dev/null +++ b/admin/tests/integration/components/sessions/certifications/header-test.gjs @@ -0,0 +1,232 @@ +import { fireEvent, render } from '@1024pix/ember-testing-library'; +import Service from '@ember/service'; +import { click } from '@ember/test-helpers'; +import CertificationsHeader from 'pix-admin/components/sessions/certifications/header'; +import { module, test } from 'qunit'; +import sinon from 'sinon'; + +import setupIntlRenderingTest from '../../../../helpers/setup-intl-rendering'; + +module('Integration | Component | certifications/header', function (hooks) { + setupIntlRenderingTest(hooks); + + let store; + + hooks.beforeEach(async function () { + store = this.owner.lookup('service:store'); + }); + + test('should display a title', async function (assert) { + // when + class SessionStub extends Service {} + this.owner.register('service:accessControl', SessionStub); + const screen = await render(); + + // then + assert.dom(screen.getByRole('heading', { level: 2, name: 'Certifications' })).exists(); + }); + + module('When user has not access to the certification session', function () { + test('it should not show the publication button', async function (assert) { + // given + class SessionStub extends Service { + hasAccessToCertificationActionsScope = false; + } + this.owner.register('service:accessControl', SessionStub); + + // when + const screen = await render(); + + // then + assert.dom(screen.queryByRole('button')).doesNotExist(); + }); + }); + + module('when user has access to the certification session', function (hooks) { + hooks.beforeEach(function () { + class SessionStub extends Service { + hasAccessToCertificationActionsScope = true; + } + this.owner.register('service:accessControl', SessionStub); + }); + + module('when session is published', function () { + test('should display an unpublication button', async function (assert) { + // given + const session = store.createRecord('session', { publishedAt: new Date() }); + + // when + const screen = await render(); + + // then + assert.dom(screen.getByRole('button', { name: 'Dépublier la session' })).exists(); + }); + }); + + module('when session is not published', function () { + module('when the session is not finalized', function () { + test('should display a disabled publication button with a tooltip', async function (assert) { + // given + const session = store.createRecord('session', { status: 'created', publishedAt: null }); + + const juryCertificationSummaries = []; + + // when + const screen = await render( + , + ); + + // then + assert.dom(screen.getByRole('button', { name: 'Publier la session' })).isDisabled(); + + fireEvent.mouseOver(screen.getByRole('button', { name: 'Publier la session' })); + const tooltipText = + "Vous ne pouvez pas publier la session tant qu'elle n'est pas finalisée ou qu'il reste des certifications en erreur."; + assert.dom(screen.getByText(tooltipText)).exists(); + }); + }); + + module('when there are no jury certification summaries', function () { + test('should display a disabled publication button', async function (assert) { + // given + const session = store.createRecord('session', { publishedAt: null }); + + const juryCertificationSummaries = []; + + // when + const screen = await render( + , + ); + + // then + assert.dom(screen.getByRole('button', { name: 'Publier la session' })).isDisabled(); + }); + }); + + module('when there is only invalid jury certification summaries', function () { + module('when the jury certification summary is cancelled', function () { + test('should display a disabled publication button', async function (assert) { + // given + const session = store.createRecord('session', { publishedAt: null }); + + const juryCertificationSummaries = [ + store.createRecord('jury-certification-summary', { isCancelled: true }), + ]; + + // when + const screen = await render( + , + ); + + // then + assert.dom(screen.getByRole('button', { name: 'Publier la session' })).isDisabled(); + }); + }); + + module('when the jury certification summary is on error', function () { + test('should display a disabled publication button', async function (assert) { + // given + const session = store.createRecord('session', { publishedAt: null }); + + const juryCertificationSummaries = [store.createRecord('jury-certification-summary', { status: 'error' })]; + + // when + const screen = await render( + , + ); + + // then + assert.dom(screen.getByRole('button', { name: 'Publier la session' })).isDisabled(); + }); + }); + }); + + module('when there is only valid jury certification summaries', function () { + test('should display an enabled publication button', async function (assert) { + // given + const session = store.createRecord('session', { publishedAt: null, status: 'finalized' }); + + const juryCertificationSummaries = [store.createRecord('jury-certification-summary', { isCancelled: false })]; + + // when + const screen = await render( + , + ); + + // then + assert.dom(screen.getByRole('button', { name: 'Publier la session' })).isNotDisabled(); + }); + + module('when confirmation modal is displayed', function () { + test('can cancel publication', async function (assert) { + // given + const publishSession = sinon.stub(); + const session = store.createRecord('session', { publishedAt: null, status: 'finalized' }); + + const juryCertificationSummaries = [ + store.createRecord('jury-certification-summary', { isCancelled: false }), + ]; + + // when + const screen = await render( + , + ); + const publishButton = screen.getByRole('button', { name: 'Publier la session' }); + await click(publishButton); + + const cancelButton = await screen.findByRole('button', { name: 'Annuler' }); + await click(cancelButton); + + // then + assert.ok(publishSession.notCalled); + }); + + test('can confirm publication', async function (assert) { + // given + const publishSession = sinon.stub(); + const session = store.createRecord('session', { publishedAt: null, status: 'finalized' }); + + const juryCertificationSummaries = [ + store.createRecord('jury-certification-summary', { isCancelled: false }), + ]; + + // when + const screen = await render( + , + ); + const publishButton = screen.getByRole('button', { name: 'Publier la session' }); + await click(publishButton); + + const confirmButton = await screen.findByRole('button', { name: 'Confirmer' }); + await click(confirmButton); + + // then + assert.ok(publishSession.calledOnce); + }); + }); + }); + }); + }); +}); diff --git a/admin/tests/integration/components/certifications/info-published-test.gjs b/admin/tests/integration/components/sessions/certifications/info-published-test.gjs similarity index 85% rename from admin/tests/integration/components/certifications/info-published-test.gjs rename to admin/tests/integration/components/sessions/certifications/info-published-test.gjs index 6153ac0ed08..c7136d00e0d 100644 --- a/admin/tests/integration/components/certifications/info-published-test.gjs +++ b/admin/tests/integration/components/sessions/certifications/info-published-test.gjs @@ -1,6 +1,6 @@ import { render } from '@ember/test-helpers'; import { setupRenderingTest } from 'ember-qunit'; -import InfoPublished from 'pix-admin/components/certifications/info-published'; +import InfoPublished from 'pix-admin/components/sessions/certifications/info-published'; import { module, test } from 'qunit'; module('Integration | Component | certifications/info-published', function (hooks) { diff --git a/admin/tests/integration/components/certifications/list-test.gjs b/admin/tests/integration/components/sessions/certifications/list-test.gjs similarity index 68% rename from admin/tests/integration/components/certifications/list-test.gjs rename to admin/tests/integration/components/sessions/certifications/list-test.gjs index 580f51ea0e2..7f6629f9aa8 100644 --- a/admin/tests/integration/components/certifications/list-test.gjs +++ b/admin/tests/integration/components/sessions/certifications/list-test.gjs @@ -1,6 +1,6 @@ import { render } from '@1024pix/ember-testing-library'; import { setupRenderingTest } from 'ember-qunit'; -import List from 'pix-admin/components/certifications/list'; +import List from 'pix-admin/components/sessions/certifications/list'; import { module, test } from 'qunit'; module('Integration | Component | certifications/list', function (hooks) { @@ -14,7 +14,7 @@ module('Integration | Component | certifications/list', function (hooks) { test('should display number of certification issue reports with required action', async function (assert) { // given - const certifications = [ + const juryCertificationSummaries = [ store.createRecord('jury-certification-summary', { id: 1, numberOfCertificationIssueReportsWithRequiredAction: 2, @@ -24,7 +24,11 @@ module('Integration | Component | certifications/list', function (hooks) { const pagination = {}; // when - await render(); + await render( + , + ); const numberOfCertificationIssueReportsWithRequiredAction = this.element.querySelector('tbody > tr td:nth-child(5)'); @@ -33,15 +37,18 @@ module('Integration | Component | certifications/list', function (hooks) { test('should display the complementary certification', async function (assert) { // given - const juryCertificationSummaryProcessed = store.createRecord('jury-certification-summary', { - complementaryCertificationTakenLabel: 'Pix+ Droit Maître', - }); - const certifications = [juryCertificationSummaryProcessed]; + const juryCertificationSummaries = [ + store.createRecord('jury-certification-summary', { + complementaryCertificationTakenLabel: 'Pix+ Droit Maître', + }), + ]; const pagination = {}; // when const screen = await render( - , + , ); // then @@ -51,17 +58,18 @@ module('Integration | Component | certifications/list', function (hooks) { module('when displayHasSeenEndTestScreenColumn is true', function () { test('it should display the "Ecran de fin de test vu" column', async function (assert) { // given - const juryCertificationSummaryProcessed = store.createRecord('jury-certification-summary', { - hasSeenEndTestScreen: true, - }); - const certifications = [juryCertificationSummaryProcessed]; + const juryCertificationSummaries = [ + store.createRecord('jury-certification-summary', { + hasSeenEndTestScreen: true, + }), + ]; const pagination = {}; // when const screen = await render(