From a1162396a183cc1867b556e225dee19f2fb03d64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yannick=20Fran=C3=A7ois?= Date: Fri, 4 Oct 2024 15:29:52 +0200 Subject: [PATCH] :sparkles: feat: add card image url to mission --- ...0241004083929_add-card-image-to-mission.js | 21 +++++++++++++ api/db/seeds/data/missions.js | 4 +++ api/lib/domain/models/Mission.js | 2 ++ .../models/release/MissionForRelease.js | 2 ++ .../repositories/mission-repository.js | 2 ++ .../serializers/jsonapi/mission-serializer.js | 2 ++ .../application/mission/get_mission_test.js | 3 +- .../database-builder/factory/build-mission.js | 3 +- .../domain-builder/factory/build-mission.js | 2 ++ .../jsonapi/mission-serializer_test.js | 4 +++ pix-editor/app/adapters/mission.js | 1 + pix-editor/app/components/form/mission.hbs | 11 +++++++ pix-editor/app/components/form/mission.js | 31 +++++++++++++++++++ .../authenticated/missions/mission/edit.js | 1 + pix-editor/app/models/mission.js | 1 + .../missions/mission/details.hbs | 1 + .../acceptance/missions/creation-test.js | 3 ++ .../tests/acceptance/missions/edit_test.js | 7 +++++ .../components/form/missions-test.js | 3 ++ 19 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 api/db/migrations/20241004083929_add-card-image-to-mission.js diff --git a/api/db/migrations/20241004083929_add-card-image-to-mission.js b/api/db/migrations/20241004083929_add-card-image-to-mission.js new file mode 100644 index 000000000..235dcd855 --- /dev/null +++ b/api/db/migrations/20241004083929_add-card-image-to-mission.js @@ -0,0 +1,21 @@ +const TABLE_NAME = 'missions'; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +export async function up(knex) { + await knex.schema.alterTable(TABLE_NAME, function(table) { + table.string('cardImageUrl').notNullable(); + }); +} + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +export async function down(knex) { + await knex.schema.alterTable(TABLE_NAME, function(table) { + table.dropColumn('cardImageUrl'); + }); +} diff --git a/api/db/seeds/data/missions.js b/api/db/seeds/data/missions.js index 0b37e9b45..c0be23a93 100644 --- a/api/db/seeds/data/missions.js +++ b/api/db/seeds/data/missions.js @@ -3,6 +3,7 @@ import { Mission } from '../../../lib/domain/models/Mission.js'; export function buildMissions(databaseBuilder) { databaseBuilder.factory.buildMission({ name: 'Mission test active', + cardImageUrl: 'https://example.net/image.png', competenceId: 'competence1NC9NE3IIOa0ym', learningObjectives: 'Que tu sois le meilleur', thematicIds: 'recOO8OsMJpe5cZzi,recOO8OsMJpe5cZzi', @@ -13,6 +14,7 @@ export function buildMissions(databaseBuilder) { databaseBuilder.factory.buildMission({ name: 'Mission test inactive', + cardImageUrl: 'https://example.net/image.png', competenceId: 'competence2k2eVZ2GRLwqFL', learningObjectives: 'Y\'en a plus', thematicIds: 'rec98EBX88mkQR3gx,rec98EBX88mkQR3gx', @@ -22,6 +24,7 @@ export function buildMissions(databaseBuilder) { }); databaseBuilder.factory.buildMission({ name: 'Mission test inactive', + cardImageUrl: 'https://example.net/image.png', competenceId: 'competence1NC9NE3IIOa0ym', learningObjectives: 'Y\'en a plus', thematicIds: 'recOO8OsMJpe5cZzi,recj6ITlVfU0vByrR,recRSPkFIgrY6Ps61', @@ -31,6 +34,7 @@ export function buildMissions(databaseBuilder) { }); databaseBuilder.factory.buildMission({ name: 'Mission test expérimentale', + cardImageUrl: 'https://example.net/image.png', competenceId: 'competence1NC9NE3IIOa0ym', learningObjectives: 'Y\'en a plus', thematicIds: 'recOO8OsMJpe5cZzi,recj6ITlVfU0vByrR,recRSPkFIgrY6Ps61', diff --git a/api/lib/domain/models/Mission.js b/api/lib/domain/models/Mission.js index 8433b87da..1d3b86e8e 100644 --- a/api/lib/domain/models/Mission.js +++ b/api/lib/domain/models/Mission.js @@ -2,6 +2,7 @@ export class Mission { constructor({ id, name_i18n, + cardImageUrl, competenceId, thematicIds, createdAt, @@ -15,6 +16,7 @@ export class Mission { }) { this.id = id; this.name_i18n = name_i18n; + this.cardImageUrl = cardImageUrl; this.competenceId = competenceId; this.thematicIds = thematicIds; this.createdAt = createdAt; diff --git a/api/lib/domain/models/release/MissionForRelease.js b/api/lib/domain/models/release/MissionForRelease.js index 6de20c5c1..88c0e521a 100644 --- a/api/lib/domain/models/release/MissionForRelease.js +++ b/api/lib/domain/models/release/MissionForRelease.js @@ -2,6 +2,7 @@ export class MissionForRelease { constructor({ id, name_i18n, + cardImageUrl, competenceId, learningObjectives_i18n, validatedObjectives_i18n, @@ -14,6 +15,7 @@ export class MissionForRelease { }) { this.id = id; this.name_i18n = name_i18n; + this.cardImageUrl = cardImageUrl; this.competenceId = competenceId; this.learningObjectives_i18n = learningObjectives_i18n; this.validatedObjectives_i18n = validatedObjectives_i18n; diff --git a/api/lib/infrastructure/repositories/mission-repository.js b/api/lib/infrastructure/repositories/mission-repository.js index 04afda6dc..dfdb66e30 100644 --- a/api/lib/infrastructure/repositories/mission-repository.js +++ b/api/lib/infrastructure/repositories/mission-repository.js @@ -49,6 +49,7 @@ export async function listActive() { export async function save(mission) { const [insertedMission] = await knex('missions').insert({ id: mission.id, + cardImageUrl: mission.cardImageUrl, competenceId: mission.competenceId, thematicIds: mission.thematicIds, status: mission.status, @@ -69,6 +70,7 @@ function _toDomain(mission, translations) { const translationsByMissionId = _.groupBy(translations, 'entityId'); return new Mission({ id: mission.id, + cardImageUrl: mission.cardImageUrl, createdAt: mission.createdAt, status: mission.status, competenceId: mission.competenceId, diff --git a/api/lib/infrastructure/serializers/jsonapi/mission-serializer.js b/api/lib/infrastructure/serializers/jsonapi/mission-serializer.js index fc0e24aef..7f8ad945a 100644 --- a/api/lib/infrastructure/serializers/jsonapi/mission-serializer.js +++ b/api/lib/infrastructure/serializers/jsonapi/mission-serializer.js @@ -31,6 +31,7 @@ export function serializeMission(mission, warnings) { }, attributes: [ 'name', + 'cardImageUrl', 'competenceId', 'thematicIds', 'learningObjectives', @@ -51,6 +52,7 @@ export function deserializeMission(attributes) { return new Mission({ id: attributes.id, name_i18n: { fr: attributes.name }, + cardImageUrl: attributes['card-image-url'] || null, competenceId: attributes['competence-id'], thematicIds: attributes['thematic-ids'], learningObjectives_i18n: { fr: attributes['learning-objectives'] }, diff --git a/api/tests/acceptance/application/mission/get_mission_test.js b/api/tests/acceptance/application/mission/get_mission_test.js index 27e21b312..2d1c49872 100644 --- a/api/tests/acceptance/application/mission/get_mission_test.js +++ b/api/tests/acceptance/application/mission/get_mission_test.js @@ -18,7 +18,7 @@ describe('Acceptance | API | mission | GET /api/missions', function() { //given const user = databaseBuilder.factory.buildAdminUser(); - const mission = databaseBuilder.factory.buildMission({ name: 'Condor', status: Mission.status.VALIDATED, competenceId: 'recCompetence0', thematicIds: null, validatedObjectives: 'Être forte', learningObjectives: 'Être imbattable', createdAt: new Date('2024-01-01') }); + const mission = databaseBuilder.factory.buildMission({ name: 'Condor', cardImageUrl: 'https://example.com/image.png', status: Mission.status.VALIDATED, competenceId: 'recCompetence0', thematicIds: null, validatedObjectives: 'Être forte', learningObjectives: 'Être imbattable', createdAt: new Date('2024-01-01') }); await databaseBuilder.commit(); //when @@ -36,6 +36,7 @@ describe('Acceptance | API | mission | GET /api/missions', function() { id: mission.id.toString(), attributes: { name: 'Condor', + 'card-image-url': 'https://example.com/image.png', status: Mission.status.VALIDATED, 'competence-id': 'recCompetence0', 'thematic-ids': null, diff --git a/api/tests/tooling/database-builder/factory/build-mission.js b/api/tests/tooling/database-builder/factory/build-mission.js index ae328ddac..67457c2a7 100644 --- a/api/tests/tooling/database-builder/factory/build-mission.js +++ b/api/tests/tooling/database-builder/factory/build-mission.js @@ -5,6 +5,7 @@ import { buildTranslation } from './build-translation.js'; export function buildMission({ id = databaseBuffer.nextId++, name = 'Ma première mission', + cardImageUrl = 'https://images.pix.fr/badges/Pix_Plus-Donnee-Visualisation_des_donnees.svg.svg', competenceId = 'competenceId', learningObjectives = 'Que tu sois le meilleur', thematicIds = 'thematicIds', @@ -18,7 +19,7 @@ export function buildMission({ createdAt = new Date('2010-01-04'), } = {}) { - const values = { id, competenceId, thematicIds, createdAt, status, introductionMediaUrl, introductionMediaType, documentationUrl }; + const values = { id, cardImageUrl, competenceId, thematicIds, createdAt, status, introductionMediaUrl, introductionMediaType, documentationUrl }; buildTranslation({ key: `mission.${id}.name`, diff --git a/api/tests/tooling/domain-builder/factory/build-mission.js b/api/tests/tooling/domain-builder/factory/build-mission.js index e14673348..d0dda2699 100644 --- a/api/tests/tooling/domain-builder/factory/build-mission.js +++ b/api/tests/tooling/domain-builder/factory/build-mission.js @@ -3,6 +3,7 @@ import { Mission } from '../../../../lib/domain/models/index.js'; export function buildMission({ id = 3, name = 'Ma mission', + cardImageUrl = null, competenceId = 'recCompetence1', thematicIds = 'recThematic1', createdAt = new Date('2023-10-14'), @@ -17,6 +18,7 @@ export function buildMission({ return new Mission ({ id, name_i18n: { fr: name }, + cardImageUrl, competenceId, thematicIds, createdAt, diff --git a/api/tests/unit/infrastructure/serializers/jsonapi/mission-serializer_test.js b/api/tests/unit/infrastructure/serializers/jsonapi/mission-serializer_test.js index 9520c713c..570a459b0 100644 --- a/api/tests/unit/infrastructure/serializers/jsonapi/mission-serializer_test.js +++ b/api/tests/unit/infrastructure/serializers/jsonapi/mission-serializer_test.js @@ -10,6 +10,7 @@ describe('Unit | Serializer | JSONAPI | mission-serializer', () => { id: 12, learningObjectives_i18n: { fr: 'learning objectives-value', }, name_i18n: { fr: 'Mission Name', }, + cardImageUrl: 'card-image-url', validatedObjectives_i18n: { fr: 'validated-objectives-value', }, competenceId: 'rec12E12EFZF', thematicIds: 'someThematicIds', @@ -25,6 +26,7 @@ describe('Unit | Serializer | JSONAPI | mission-serializer', () => { const attributes = { id: expectedMission.id, name: expectedMission.name_i18n.fr, + 'card-image-url': expectedMission.cardImageUrl, 'competence-id': expectedMission.competenceId, 'thematic-ids': expectedMission.thematicIds, 'learning-objectives': expectedMission.learningObjectives_i18n.fr, @@ -46,12 +48,14 @@ describe('Unit | Serializer | JSONAPI | mission-serializer', () => { const attributes = { id: expectedMission.id, + 'card-image-url': '', 'introduction-media-url': '', 'introduction-media-type': '', 'documentation-url': '', }; const deserializedMission = deserializeMission(attributes); + expect(deserializedMission.cardMissionUrl).to.be.null; expect(deserializedMission.introductionMediaUrl).to.be.null; expect(deserializedMission.introductionMediaType).to.be.null; expect(deserializedMission.documentationUrl).to.be.null; diff --git a/pix-editor/app/adapters/mission.js b/pix-editor/app/adapters/mission.js index e56ca7d56..519cbeef6 100644 --- a/pix-editor/app/adapters/mission.js +++ b/pix-editor/app/adapters/mission.js @@ -12,6 +12,7 @@ function preparePayloadForCreateAndUpdate(payload, adapterOptions) { payload.data.attributes = {}; payload.data.attributes.name = adapterOptions.name; payload.data.attributes.status = adapterOptions.status; + payload.data.attributes['card-image-url'] = adapterOptions.cardImageUrl; payload.data.attributes['competence-id'] = adapterOptions.competenceId; payload.data.attributes['thematic-ids'] = adapterOptions.thematicIds; payload.data.attributes['learning-objectives'] = adapterOptions.learningObjectives; diff --git a/pix-editor/app/components/form/mission.hbs b/pix-editor/app/components/form/mission.hbs index 5c6c41e1f..adc711afc 100644 --- a/pix-editor/app/components/form/mission.hbs +++ b/pix-editor/app/components/form/mission.hbs @@ -11,6 +11,17 @@ > <:label>Nom de la mission + + <:label>URL de l'image de la carte + 0 + ? STATES.SUCCESS + : STATES.ERROR; + } + + getValueForSubmit() { return this.value.trim(); } +} + class CompetenceIdField extends FormField { constructor() { super(); diff --git a/pix-editor/app/controllers/authenticated/missions/mission/edit.js b/pix-editor/app/controllers/authenticated/missions/mission/edit.js index c66ea6601..d5d469ca8 100644 --- a/pix-editor/app/controllers/authenticated/missions/mission/edit.js +++ b/pix-editor/app/controllers/authenticated/missions/mission/edit.js @@ -10,6 +10,7 @@ export default class MissionEditController extends Controller { async submitMission(formData) { try { this.model.mission.name = formData.name; + this.model.mission.cardImageUrl = formData.cardImageUrl; this.model.mission.competenceId = formData.competenceId; this.model.mission.status = formData.status; this.model.mission.thematicIds = formData.thematicIds; diff --git a/pix-editor/app/models/mission.js b/pix-editor/app/models/mission.js index 2dd27a6d9..2c1c6afff 100644 --- a/pix-editor/app/models/mission.js +++ b/pix-editor/app/models/mission.js @@ -3,6 +3,7 @@ import { attr } from '@ember-data/model'; import MissionSummary from './mission-summary'; export default class Mission extends MissionSummary { + @attr cardImageUrl; @attr competenceId; @attr thematicIds; @attr learningObjectives; diff --git a/pix-editor/app/templates/authenticated/missions/mission/details.hbs b/pix-editor/app/templates/authenticated/missions/mission/details.hbs index 75e551351..435d3cf7b 100644 --- a/pix-editor/app/templates/authenticated/missions/mission/details.hbs +++ b/pix-editor/app/templates/authenticated/missions/mission/details.hbs @@ -17,6 +17,7 @@
  • Nom : {{this.model.mission.name}}
  • +
  • Image carte : {{this.model.mission.cardImageUrl}}
  • Compétence : {{this.model.competence}} diff --git a/pix-editor/tests/acceptance/missions/creation-test.js b/pix-editor/tests/acceptance/missions/creation-test.js index 677c93923..faa927e9b 100644 --- a/pix-editor/tests/acceptance/missions/creation-test.js +++ b/pix-editor/tests/acceptance/missions/creation-test.js @@ -73,6 +73,9 @@ module('Acceptance | Missions | Creation', function(hooks) { await fillByLabel('* Nom de la mission', 'Nouvelle mission de test'); await triggerEvent(find('#mission-name'), 'keyup', ''); + await fillByLabel('* URL de l\'image de la carte', 'https://example.pix.fr/ma-image.png'); + await triggerEvent(find('#mission-card-image-url'), 'keyup', ''); + await clickByText('Compétence'); await screen.findByRole('listbox'); diff --git a/pix-editor/tests/acceptance/missions/edit_test.js b/pix-editor/tests/acceptance/missions/edit_test.js index 01024af08..22b0f7657 100644 --- a/pix-editor/tests/acceptance/missions/edit_test.js +++ b/pix-editor/tests/acceptance/missions/edit_test.js @@ -24,6 +24,7 @@ module('Acceptance | Missions | Edit', function(hooks) { this.server.create('mission', { id: 2, name: 'Mission 1', + cardImageUrl: 'https://example.net/card-image.png', competenceId: 'recCompetence1.1', createdAt: '2023/12/11', status: 'VALIDATED', @@ -53,6 +54,7 @@ module('Acceptance | Missions | Edit', function(hooks) { this.server.create('mission', { id: 3, name: 'Mission', + cardImageUrl: 'https://example.net/card-image.png', competenceId: 'recCompetence1.1', thematicIds: '', createdAt: '2023/12/11', @@ -67,6 +69,9 @@ module('Acceptance | Missions | Edit', function(hooks) { await fillByLabel('* Nom de la mission', 'Nouvelle mission de test'); await triggerEvent(find('#mission-name'), 'keyup', ''); + await fillByLabel('* URL de l\'image de la carte', 'https://images.pix.fr/badges/Pix_Plus-Donnee-Visualisation_des_donnees.svg.svg'); + await triggerEvent(find('#mission-card-image-url'), 'keyup', ''); + await clickByText('Compétence'); await screen.findByRole('listbox'); await click(screen.getByRole('option', { name: 'Notre compétence' })); @@ -78,6 +83,7 @@ module('Acceptance | Missions | Edit', function(hooks) { // then assert.strictEqual(currentURL(), '/missions/3'); assert.dom(screen.getByText('Nouvelle mission de test')).exists(); + assert.dom(screen.getByText('https://images.pix.fr/badges/Pix_Plus-Donnee-Visualisation_des_donnees.svg.svg')).exists(); assert.dom(screen.getByText('http://example.com')).exists(); assert.dom(screen.getByText('http://doc.com')).exists(); }); @@ -87,6 +93,7 @@ module('Acceptance | Missions | Edit', function(hooks) { this.server.create('mission', { id: 3, name: 'Mission', + cardImageUrl: 'https://example.net/image.png', competenceId: 'recCompetence1.1', thematicIds: '', createdAt: '2023/12/11', diff --git a/pix-editor/tests/integration/components/form/missions-test.js b/pix-editor/tests/integration/components/form/missions-test.js index b2bf6e202..e28a2cf57 100644 --- a/pix-editor/tests/integration/components/form/missions-test.js +++ b/pix-editor/tests/integration/components/form/missions-test.js @@ -23,6 +23,9 @@ module('Integration | Component | mission', function(hooks) { await fillByLabel('* Nom de la mission', 'Nouvelle mission de test'); await triggerEvent(find('#mission-name'), 'keyup', ''); + await fillByLabel('* URL de l\'image de la carte', 'https://images.pix.fr/badges/Pix_Plus-Donnee-Visualisation_des_donnees.svg.svg'); + await triggerEvent(find('#mission-card-image-url'), 'keyup', ''); + await clickByText('Compétence'); await screen.findByRole('listbox'); await click(screen.getByRole('option', { name: 'Notre compétence' }));