Skip to content

Commit

Permalink
[FEATURE] écouter/stopper la lecture d’une consigne (PIX-13754)
Browse files Browse the repository at this point in the history
  • Loading branch information
pix-service-auto-merge authored Nov 15, 2024
2 parents bb218c6 + c6227d3 commit 6d6ef0a
Show file tree
Hide file tree
Showing 40 changed files with 403 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,24 @@ const buildOrganizationLearnerFeature = function ({
});
};

export { buildOrganizationLearnerFeature };
const buildOrganizationLearnerFeatureWithFeatureKey = function ({
id = databaseBuffer.getNextId(),
organizationLearnerId,
featureKey,
} = {}) {
organizationLearnerId = organizationLearnerId === undefined ? buildOrganizationLearner().id : organizationLearnerId;
const featureId = buildFeature({ key: featureKey }).id;

const values = {
id,
organizationLearnerId,
featureId,
};

return databaseBuffer.pushInsertable({
tableName: 'organization-learner-features',
values,
});
};

export { buildOrganizationLearnerFeature, buildOrganizationLearnerFeatureWithFeatureKey };
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Make sure you properly test your migration, especially DDL (Data Definition Language)
// ! If the target table is large, and the migration take more than 20 minutes, the deployment will fail !

// You can design and test your migration to avoid this by following this guide
// https://1024pix.atlassian.net/wiki/spaces/EDTDT/pages/3849323922/Cr+er+une+migration

// If your migrations target `answers` or `knowledge-elements`
// contact @team-captains, because automatic migrations are not active on `pix-datawarehouse-production`
// this may prevent data replication to succeed the day after your migration is deployed on `pix-api-production`
const TABLE_NAME = 'organization-learner-features';
const COLUMN_NAME = 'organizationLearnerId';

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

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

export { down, up };
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
export class OrganizationLearner {
constructor({ id, firstName, lastName, organizationId, ...attributes }) {
constructor({ id, firstName, lastName, features, organizationId, ...attributes }) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.division = attributes['Libellé classe'];
this.features = features;
this.organizationId = organizationId;
this.division = attributes['Libellé classe'];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class OrganizationLearner {
organizationId,
certifiableAtFromLearner,
userId,
features,
} = {}) {
this.id = id;
this.firstName = firstName;
Expand All @@ -27,6 +28,7 @@ class OrganizationLearner {
this.authenticationMethods = authenticationMethods;
this.organizationId = organizationId;
this.userId = userId;
this.features = features;

this._buildCertificability({
isCertifiableFromCampaign,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ async function get({ organizationLearnerId }) {
'subquery.isCertifiableFromCampaign',
'subquery.certifiableAtFromCampaign',
knex.raw('array_remove(ARRAY_AGG("identityProvider"), NULL) AS "authenticationMethods"'),
knex.raw('array_remove(ARRAY_AGG(features.key), NULL) as features'),
'users.email',
'users.username',
)
Expand All @@ -55,6 +56,12 @@ async function get({ organizationLearnerId }) {
.leftJoin('subquery', 'subquery.organizationLearnerId', 'view-active-organization-learners.id')
.leftJoin('authentication-methods', 'authentication-methods.userId', 'view-active-organization-learners.userId')
.leftJoin('users', 'view-active-organization-learners.userId', 'users.id')
.leftJoin(
'organization-learner-features',
'view-active-organization-learners.id',
'organization-learner-features.organizationLearnerId',
)
.leftJoin('features', 'organization-learner-features.featureId', 'features.id')
.groupBy(
'view-active-organization-learners.id',
'view-active-organization-learners.firstName',
Expand Down
12 changes: 11 additions & 1 deletion api/src/school/domain/models/OrganizationLearner.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
class OrganizationLearner {
constructor({ id, lastName, firstName, division, organizationId, completedMissionIds, startedMissionIds } = {}) {
constructor({
id,
lastName,
firstName,
division,
features,
organizationId,
completedMissionIds,
startedMissionIds,
} = {}) {
this.id = id;
this.lastName = lastName;
this.firstName = firstName;
this.division = division;
this.features = features || [];
this.organizationId = organizationId;
this.completedMissionIds = completedMissionIds;
this.startedMissionIds = startedMissionIds;
Expand Down
12 changes: 11 additions & 1 deletion api/src/school/domain/read-models/OrganizationLearnerDTO.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
class OrganizationLearnerDTO {
constructor({ id, displayName, firstName, division, organizationId, startedMissionIds, completedMissionIds } = {}) {
constructor({
id,
displayName,
firstName,
division,
organizationId,
startedMissionIds,
completedMissionIds,
features,
} = {}) {
this.id = id;
this.displayName = displayName;
this.firstName = firstName;
this.division = division;
this.organizationId = organizationId;
this.startedMissionIds = startedMissionIds;
this.completedMissionIds = completedMissionIds;
this.features = features || [];
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@ import { Serializer } from 'jsonapi-serializer';

const serialize = function (organizationLearner) {
return new Serializer('organizationLearner', {
attributes: ['firstName', 'displayName', 'division', 'organizationId', 'completedMissionIds', 'startedMissionIds'],
attributes: [
'firstName',
'displayName',
'division',
'organizationId',
'completedMissionIds',
'startedMissionIds',
'features',
],
transform: function (organizationLearner) {
return {
...organizationLearner,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,23 @@ describe('Integration | Infrastructure | Repository | Organization Learner', fun
expect(organizationLearner.email).to.equal('[email protected]');
expect(organizationLearner.username).to.equal('sassouk');
expect(organizationLearner.organizationId).to.equal(organizationId);
expect(organizationLearner.features).to.be.empty;
});

it("Should return organization learner's features", async function () {
const organizationLearnerId = databaseBuilder.factory.buildOrganizationLearner().id;
databaseBuilder.factory.prescription.organizationLearners.buildOrganizationLearnerFeatureWithFeatureKey({
organizationLearnerId,
featureKey: 'ORALIZATION',
});
databaseBuilder.factory.prescription.organizationLearners.buildOrganizationLearnerFeatureWithFeatureKey({
organizationLearnerId,
featureKey: 'BLA',
});
await databaseBuilder.commit();

const organizationLearner = await organizationLearnerRepository.get({ organizationLearnerId });
expect(organizationLearner.features).to.deep.equal(['ORALIZATION', 'BLA']);
});

it('Should return the organization learner with a given ID', async function () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,30 @@ describe('Integration | Usecase | get-organization-learner-with-completed-missio
);
});

it('should return organization learner with features', async function () {
const organizationLearner =
databaseBuilder.factory.prescription.organizationLearners.buildOndeOrganizationLearner();
databaseBuilder.factory.prescription.organizationLearners.buildOrganizationLearnerFeatureWithFeatureKey({
organizationLearnerId: organizationLearner.id,
featureKey: 'ORALIZATION',
});
await databaseBuilder.commit();

const result = await usecases.getOrganizationLearnerWithMissionIdsByState({
organizationLearnerId: organizationLearner.id,
});

expect(result).to.deep.equal(
new OrganizationLearner({
...organizationLearner,
division: organizationLearner.attributes['Libellé classe'],
completedMissionIds: [],
startedMissionIds: [],
features: ['ORALIZATION'],
}),
);
});

it('should return only the good organization learner', async function () {
const organizationLearner =
databaseBuilder.factory.prescription.organizationLearners.buildOndeOrganizationLearner();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ describe('Unit | Controller | organization-learner-controller', function () {
organizationId: '345',
completedMissionIds: ['rec12344', 'rec435'],
startedMissionIds: undefined,
features: ['ORALIZATION'],
}),
);
const id = 4356;
Expand All @@ -33,6 +34,7 @@ describe('Unit | Controller | organization-learner-controller', function () {
'display-name': undefined,
division: 'CM2',
'organization-id': '345',
features: ['ORALIZATION'],
},
id: '4356',
type: 'organizationLearners',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ describe('Unit | Controller | school-controller', function () {
organizationId: 1,
completedMissionIds: [],
startedMissionIds: [],
features: [],
},
],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ describe('Unit | Serializer | JSONAPI | organization-learner', function () {
'organization-id': 'orga-1',
'completed-mission-ids': ['1'],
'started-mission-ids': ['2'],
features: [],
},
},
};
Expand Down
19 changes: 18 additions & 1 deletion junior/app/components/bubble.gjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import Component from '@glimmer/component';
import MarkdownToHtml from 'junior/components/markdown-to-html';
import * as markdownConverter from 'junior/utils/markdown-converter';

import OralizationButton from './oralization-button';

export default class Bubble extends Component {
get getClasses() {
Expand All @@ -9,5 +12,19 @@ export default class Bubble extends Component {
}
return className;
}
<template><MarkdownToHtml ...attributes @markdown={{@message}} @class={{this.getClasses}} /></template>

get textToRead() {
const parser = new DOMParser();
const parsedText = parser.parseFromString(markdownConverter.toHTML(this.args.message), 'text/html').body.innerText;
return parsedText;
}

<template>
<div class="bubble-container">
<MarkdownToHtml ...attributes @markdown={{@message}} @class={{this.getClasses}} />
{{#if @oralization}}
<OralizationButton @text={{this.textToRead}} />
{{/if}}
</div>
</template>
}
2 changes: 1 addition & 1 deletion junior/app/components/challenge/item/integration-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { module, test } from 'qunit';

import { setupRenderingTest, t } from '../../../helpers/tests';

module('Integration | Component | challenge', function (hooks) {
module('Integration | Component | challenge item', function (hooks) {
setupRenderingTest(hooks);
test('displays embed', async function (assert) {
this.set('challenge', { hasValidEmbedDocument: true, autoReply: true });
Expand Down
18 changes: 14 additions & 4 deletions junior/app/components/challenge/template.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,26 @@
<div class="container">
<RobotDialog @class={{this.robotMood}}>
{{#each @challenge.instruction as |instruction|}}
<Bubble @message={{instruction}} />
<Bubble @message={{instruction}} @oralization={{@oralization}} />
{{/each}}
{{#if (eq this.answer.result "ok")}}
<Bubble @message={{t "pages.challenge.messages.correct-answer"}} @status="success" aria-live="polite" />
<Bubble
@message={{t "pages.challenge.messages.correct-answer"}}
@status="success"
@oralization={{@oralization}}
aria-live="polite"
/>
{{/if}}
{{#if (eq this.answer.result "ko")}}
<Bubble @message={{t "pages.challenge.messages.wrong-answer"}} @status="error" aria-live="polite" />
<Bubble
@message={{t "pages.challenge.messages.wrong-answer"}}
@oralization={{@oralization}}
@status="error"
aria-live="polite"
/>
{{/if}}
{{#if this.displayValidationWarning}}
<Bubble @message={{this.validationWarning}} @status="warning" aria-live="polite" />
<Bubble @message={{this.validationWarning}} @status="warning" aria-live="polite" @oralization={{@oralization}} />
{{/if}}
</RobotDialog>
<Challenge::Item
Expand Down
Loading

0 comments on commit 6d6ef0a

Please sign in to comment.