Skip to content

Commit

Permalink
[FEATURE] Afficher le détail d'un signalement validé lors d'une certi…
Browse files Browse the repository at this point in the history
…fication sur Pix Admin (PIX-10313).

 #7763
  • Loading branch information
pix-service-auto-merge authored Jan 2, 2024
2 parents e8d3e8f + 0733680 commit 6227412
Show file tree
Hide file tree
Showing 19 changed files with 243 additions and 54 deletions.
19 changes: 17 additions & 2 deletions admin/app/components/certifications/certification/details-v3.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,18 @@
>
<FaIcon @icon="eye" />
</a>

{{#if certificationChallenge.validatedLiveAlert}}
<button
title="Afficher le signalement de la question"
aria-label="Afficher le signalement de la question"
type="button"
{{on "click" (fn this.openModal certificationChallenge)}}
>
<FaIcon @icon="warning" />
</button>
{{/if}}

{{#if certificationChallenge.answerValue}}
<button
title="Afficher la réponse du candidat"
Expand All @@ -94,13 +106,16 @@
</section>

<PixModal
@title="Réponse question {{this.certificationChallenge.questionNumber}}"
@title="{{this.modalTitle}} question {{this.certificationChallenge.questionNumber}}"
@showModal={{this.showModal}}
@onCloseButtonClick={{this.closeModal}}
>
<:content>
{{#if this.certificationChallenge.validatedLiveAlert}}
<span class="certification-details-v3-modal__live-alert-subcategory">{{this.subCategory}}</span>
{{/if}}
<p>
{{this.certificationChallenge.answerValue}}
{{this.modalContent}}
</p>
</:content>
<:footer>
Expand Down
13 changes: 13 additions & 0 deletions admin/app/components/certifications/certification/details-v3.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
import { subcategoryToCode, subcategoryToLabel } from '../../../models/certification-issue-report';

const options = [
{ value: 'ok', label: 'OK', color: 'success' },
Expand All @@ -11,6 +12,9 @@ const options = [
export default class DetailsV3 extends Component {
@tracked showModal = false;
@tracked certificationChallenge = null;
@tracked modalTitle = null;
@tracked modalContent = null;
@tracked subCategory = null;

answerStatusLabel(status) {
return options.find((option) => option.value === status).label;
Expand All @@ -32,10 +36,19 @@ export default class DetailsV3 extends Component {
openModal(certificationChallenge) {
this.showModal = true;
this.certificationChallenge = certificationChallenge;
this.modalTitle = this._isReportedQuestion() ? 'Signalement' : 'Réponse';
this.modalContent = this._isReportedQuestion()
? subcategoryToLabel[this.certificationChallenge.validatedLiveAlert.issueReportSubcategory]
: this.certificationChallenge.answerValue;
this.subCategory = subcategoryToCode[this.certificationChallenge.validatedLiveAlert.issueReportSubcategory];
}

@action
closeModal() {
this.showModal = false;
}

_isReportedQuestion() {
return this.certificationChallenge.validatedLiveAlert;
}
}
16 changes: 11 additions & 5 deletions admin/app/models/certification-issue-report.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,17 @@ export const subcategoryToLabel = {
'Problème avec l’accessibilité de la question (ex : daltonisme)',
};

export const subcategoryToTextareaLabel = {
[certificationIssueReportSubcategories.LEFT_EXAM_ROOM]: 'Précisez et indiquez l’heure de sortie',
[certificationIssueReportSubcategories.SIGNATURE_ISSUE]: 'Précisez',
[certificationIssueReportSubcategories.NAME_OR_BIRTHDATE]: 'Précisez les informations à modifier',
[certificationIssueReportSubcategories.EXTRA_TIME_PERCENTAGE]: 'Précisez le temps majoré',
export const subcategoryToCode = {
[certificationIssueReportSubcategories.IMAGE_NOT_DISPLAYING]: 'E1',
[certificationIssueReportSubcategories.EMBED_NOT_WORKING]: 'E2',
[certificationIssueReportSubcategories.FILE_NOT_OPENING]: 'E3',
[certificationIssueReportSubcategories.WEBSITE_UNAVAILABLE]: 'E4',
[certificationIssueReportSubcategories.WEBSITE_BLOCKED]: 'E5',
[certificationIssueReportSubcategories.EXTRA_TIME_EXCEEDED]: 'E8',
[certificationIssueReportSubcategories.SOFTWARE_NOT_WORKING]: 'E9',
[certificationIssueReportSubcategories.UNINTENTIONAL_FOCUS_OUT]: 'E10',
[certificationIssueReportSubcategories.SKIP_ON_OOPS]: 'E11',
[certificationIssueReportSubcategories.ACCESSIBILITY_ISSUE]: 'E12',
};

export default class CertificationIssueReportModel extends Model {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,9 @@
}
}
}

.certification-details-v3-modal{
&__live-alert-subcategory {
font-weight: $font-bold;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,20 @@ module('Integration | Component | Certifications | certification > details v3',
assert.dom(screen.getByText('Réponse 1')).exists();
});

test('should not display the issue report button', async function (assert) {
// given
const store = this.owner.lookup('service:store');
this.model = store.createRecord('v3-certification-course-details-for-administration', {
certificationChallengesForAdministration: createChallengesForAdministration(['ok'], store),
});

// when
const screen = await render(hbs`<Certifications::Certification::DetailsV3 @details={{this.model}} />`);

// then
assert.dom(screen.queryByRole('button', { name: 'Afficher le signalement de la question' })).doesNotExist();
});

module('when there is a reported question (question without an answer)', function () {
test('should not display the response button', async function (assert) {
// given
Expand All @@ -191,6 +205,32 @@ module('Integration | Component | Certifications | certification > details v3',
// then
assert.dom(screen.queryByRole('button', { name: 'Afficher la réponse du candidat' })).doesNotExist();
});

test('displays the modal with the issue report subcategory', async function (assert) {
// given
const store = this.owner.lookup('service:store');
this.model = store.createRecord('v3-certification-course-details-for-administration', {
certificationChallengesForAdministration: createChallengesForAdministration(['ok', null], store),
});

// when
const screen = await render(hbs`<Certifications::Certification::DetailsV3 @details={{this.model}} />`);

const modalButton = screen.getByRole('button', { name: 'Afficher le signalement de la question' });
await click(modalButton);

// then
const modal = within(await screen.findByRole('dialog'));
assert.dom(modal.getByRole('heading', { name: 'Signalement question 2' })).exists();
assert.dom(screen.getByText('E5')).exists();
assert
.dom(
screen.getByText(
"Le site est bloqué par les restrictions réseau de l'établissement (réseaux sociaux par ex.)",
),
)
.exists();
});
});
});
});
Expand All @@ -202,7 +242,7 @@ function createChallengesForAdministration(answerStatuses, store) {
questionNumber: index + 1,
answerStatus,
answerValue: answerStatus ? `Réponse ${index + 1}` : null,
validatedLiveAlert: !answerStatus,
validatedLiveAlert: !answerStatus ? { id: index + 10, issueReportSubcategory: 'WEBSITE_BLOCKED' } : false,
}),
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const buildCertificationIssueReport = function ({
questionNumber = null,
resolvedAt = null,
resolution = null,
liveAlertId = null,
} = {}) {
certificationCourseId = _.isUndefined(certificationCourseId) ? buildCertificationCourse().id : certificationCourseId;
categoryId = _.isUndefined(categoryId) ? buildIssueReportCategory().id : categoryId;
Expand All @@ -29,6 +30,7 @@ const buildCertificationIssueReport = function ({
hasBeenAutomaticallyResolved,
resolvedAt,
resolution,
liveAlertId,
};
return databaseBuffer.pushInsertable({
tableName: 'certification-issue-reports',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const TABLE_NAME = 'certification-issue-reports';
const COLUMN_NAME = 'liveAlertId';

const up = async function (knex) {
await knex.schema.table(TABLE_NAME, function (table) {
table.integer(COLUMN_NAME).unsigned();
table.foreign(COLUMN_NAME).references('certification-challenge-live-alerts.id');
});
};

const down = async function (knex) {
await knex.schema.table(TABLE_NAME, function (table) {
table.dropColumn(COLUMN_NAME);
});
};

export { up, down };
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export class V3CertificationChallengeLiveAlertForAdministration {
constructor({ id }) {
constructor({ id, issueReportSubcategory }) {
this.id = id;
this.issueReportSubcategory = issueReportSubcategory;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,29 @@ import { V3CertificationChallengeLiveAlertForAdministration } from '../../domain
import { CertificationChallengeLiveAlertStatus } from '../../../session/domain/models/CertificationChallengeLiveAlert.js';

const getV3DetailsByCertificationCourseId = async function ({ certificationCourseId }) {
const certificationChallengesDetailsDTO = await knex
const liveAlertsDTO = await knex
.with('validated-live-alerts', (queryBuilder) => {
queryBuilder
.select('*')
.from('certification-challenge-live-alerts')
.where({ status: CertificationChallengeLiveAlertStatus.VALIDATED });
})
.select({
id: 'validated-live-alerts.id',
challengeId: 'validated-live-alerts.challengeId',
issueReportSubcategory: 'certification-issue-reports.subcategory',
})
.from('assessments')
.leftJoin('validated-live-alerts', 'validated-live-alerts.assessmentId', 'assessments.id')
.leftJoin('certification-issue-reports', 'certification-issue-reports.liveAlertId', 'validated-live-alerts.id')
.where({ 'assessments.certificationCourseId': certificationCourseId })
.orderBy('validated-live-alerts.createdAt', 'ASC');

const certificationChallengesDetailsDTO = await knex
.select({
challengeId: 'certification-challenges.challengeId',
answerStatus: 'answers.result',
answerValue: 'answers.value',
liveAlertId: 'validated-live-alerts.id',
answeredAt: 'answers.createdAt',
competenceId: 'certification-challenges.competenceId',
skillName: 'certification-challenges.associatedSkillName',
Expand All @@ -29,31 +40,26 @@ const getV3DetailsByCertificationCourseId = async function ({ certificationCours
'answers.challengeId': 'certification-challenges.challengeId',
});
})
.leftJoin('validated-live-alerts', function () {
this.on({ 'validated-live-alerts.assessmentId': 'assessments.id' }).andOn({
'validated-live-alerts.challengeId': 'certification-challenges.challengeId',
});
})
.where({
certificationCourseId,
'assessments.certificationCourseId': certificationCourseId,
})
.orderBy('certification-challenges.createdAt', 'asc');
return _toDomain({ certificationChallengesDetailsDTO, certificationCourseId });

return _toDomain({ certificationChallengesDetailsDTO, certificationCourseId, liveAlertsDTO });
};

function _toDomain({ certificationChallengesDetailsDTO, certificationCourseId }) {
function _toDomain({ certificationChallengesDetailsDTO, certificationCourseId, liveAlertsDTO }) {
const certificationChallengesForAdministration = certificationChallengesDetailsDTO.map(
(certificationChallengeDetailsDTO) =>
new V3CertificationChallengeForAdministration({
...certificationChallengeDetailsDTO,
answerStatus: certificationChallengeDetailsDTO.answerStatus
? new AnswerStatus({ status: certificationChallengeDetailsDTO.answerStatus })
: null,
validatedLiveAlert: certificationChallengeDetailsDTO.liveAlertId
? new V3CertificationChallengeLiveAlertForAdministration({
id: certificationChallengeDetailsDTO.liveAlertId,
})
: null,
validatedLiveAlert: _certificationChallengeLiveAlertToDomain({
liveAlertsDTO,
certificationChallengeDetailsDTO,
}),
}),
);

Expand All @@ -64,3 +70,16 @@ function _toDomain({ certificationChallengesDetailsDTO, certificationCourseId })
}

export { getV3DetailsByCertificationCourseId };

function _certificationChallengeLiveAlertToDomain({ liveAlertsDTO, certificationChallengeDetailsDTO }) {
const certificationChallengeLiveAlert = liveAlertsDTO.find(
(liveAlertDTO) => liveAlertDTO.challengeId === certificationChallengeDetailsDTO.challengeId,
);
if (!certificationChallengeLiveAlert) {
return null;
}
return new V3CertificationChallengeLiveAlertForAdministration({
id: certificationChallengeLiveAlert.id,
issueReportSubcategory: certificationChallengeLiveAlert.issueReportSubcategory,
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const validateLiveAlert = async ({
category: CertificationIssueReportCategory.IN_CHALLENGE,
subcategory,
categoryId: issueReportCategory.id,
liveAlertId: certificationChallengeLiveAlert.id,
});

await certificationIssueReportRepository.save(certificationIssueReport);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ class CertificationIssueReport {
subcategory,
questionNumber,
hasBeenAutomaticallyResolved,
liveAlertId,
resolvedAt,
resolution,
} = {}) {
Expand All @@ -111,6 +112,7 @@ class CertificationIssueReport {
this.hasBeenAutomaticallyResolved = hasBeenAutomaticallyResolved;
this.resolvedAt = resolvedAt;
this.resolution = resolution;
this.liveAlertId = liveAlertId;
this.isImpactful = _isImpactful({ category, subcategory });

if (
Expand All @@ -128,13 +130,23 @@ class CertificationIssueReport {
}
}

static create({ id, certificationCourseId, category, categoryId, description, subcategory, questionNumber }) {
static create({
id,
certificationCourseId,
category,
categoryId,
description,
subcategory,
questionNumber,
liveAlertId,
}) {
const certificationIssueReport = new CertificationIssueReport({
id,
certificationCourseId,
category,
categoryId,
description,
liveAlertId,
subcategory,
questionNumber,
hasBeenAutomaticallyResolved: null,
Expand Down
Loading

0 comments on commit 6227412

Please sign in to comment.