Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add feedback email adress after simulation in stdcm #11031

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions front/public/locales/en/stdcm.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"cancelRequest": "Cancel request",
"consist": {
"consist": "Consist",
"compositionCode": "Composition code",
"length": "Length",
"tonnage": "Tonnage",
"maxSpeed": "Max speed",
Expand Down Expand Up @@ -49,6 +50,14 @@
"anterior": "Indicate anterior train",
"posterior": "Indicate posterior train"
},
"mailFeedback": {
"body": "%0A%0AWe%20invite%20you%20to%20send%20us%20a%20message.%0AWhenever%20you%20have%20a%20concern%20or%20a%20remark,%0Aeven%20if%20it%20is%20not%20perfectly%20worded.%0A%0AThank%20you%20in%20advance,%0AThe%20LMR%20project%20team%0A%0A(you%20may%20delete%20this%20message)%0A%0A********",
"description": "Help us improve ST DCM by sending us your feedback and suggestions.",
"simulationDetails": "Simulation details",
"subject": "LMR - feedback, suggestions",
"title": "Any feedback or suggestions?",
"writeButton": "Write"
},
"noConfigurationFound": {
"title": "A configuration problem prevents you from performing a search",
"text": "Please contact the maintenance team so they can get you back on track."
Expand Down
9 changes: 9 additions & 0 deletions front/public/locales/fr/stdcm.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"cancelRequest": "Annuler la requête",
"consist": {
"consist": "Convoi",
"compositionCode": "Composition code",
"length": "Longueur",
"tonnage": "Tonnage",
"maxSpeed": "Vitesse max.",
Expand Down Expand Up @@ -49,6 +50,14 @@
"anterior": "Indiquer le sillon antérieur",
"posterior": "Indiquer le sillon postérieur"
},
"mailFeedback": {
"body": "%0A********%0A%0ANous%20vous%20invitons%20à%20nous%20envoyer%20un%20message.%0ADès%20que%20vous%20avez%20un%20gêne%20ou%20une%20remarque,%0Amême%20si%20vous%20n’y%20mettez%20pas%20la%20forme.%0A%0AMerci%20à%20vous%20par%20avance,%0AL’équipe%20du%20projet%20LMR%0A%0A(vous%20pouvez%20effacer%20ce%20message)%0A%0A********",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The translation key contains formatting that should be handled in the component:

This way:

  • Translation file only contains content
  • Layout/formatting is managed in the component
  • Easier to maintain and update translations
Suggested change
"body": "%0A********%0A%0ANous%20vous%20invitons%20à%20nous%20envoyer%20un%20message.%0ADès%20que%20vous%20avez%20un%20gêne%20ou%20une%20remarque,%0Amême%20si%20vous%20n’y%20mettez%20pas%20la%20forme.%0A%0AMerci%20à%20vous%20par%20avance,%0AL’équipe%20du%20projet%20LMR%0A%0A(vous%20pouvez%20effacer%20ce%20message)%0A%0A********",
"body": "Nous vous invitons à nous envoyer un message.\nDès que vous avez une gêne ou une remarque,\nmême si vous n'y mettez pas la forme.\n\nMerci à vous par avance,\nL'équipe du projet LMR\n\n(vous pouvez effacer ce message)"

"description": "Aidez-nous à améliorer ST DCM en transmettant vos critiques et propositions à l’équipe.",
"simulationDetails": "Détails de la simulation",
"subject": "LMR - remarques, suggestions",
"title": "Une remarque, une suggestion ?",
"writeButton": "Écrire"
},
"noConfigurationFound": {
"title": "Un problème de configuration vous empêche de faire une recherche",
"text": "Veuillez contacter l'équipe de maintenance pour qu'elle puisse vous remettre sur les rails."
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Button } from '@osrd-project/ui-core';
import { Comment } from '@osrd-project/ui-icons';
import dayjs from 'dayjs';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';

import type { StdcmResultsOutput } from 'applications/stdcm/types';
import { getSelectedSimulation } from 'reducers/osrdconf/stdcmConf/selectors';
import { dateTimeFormatting } from 'utils/date';

const FeedbackCard = () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to keep the same name used for the file

Suggested change
const FeedbackCard = () => {
const StdcmFeedback = () => {

const { t } = useTranslation('stdcm');
const selectedSimulation = useSelector(getSelectedSimulation);

const resultsOutput = selectedSimulation.outputs as StdcmResultsOutput;
const { rollingStock } = resultsOutput.results;
const trainName = rollingStock.name;
const consistCode = rollingStock?.metadata?.series;
const consistLength = `${rollingStock.length} m`;
const consistMass = `${rollingStock.mass / 1000} t`;
const maxSpeed = `${Math.round(rollingStock.max_speed * 3.6)} km/h`;
const origin = resultsOutput.results.simulationPathSteps[0]?.location?.name;
const destination = resultsOutput.results.simulationPathSteps.at(-1)?.location?.name;
const departureTime = dateTimeFormatting(
dayjs.utc(resultsOutput.results.departure_time).toDate()
);

const handleEmailClick = () => {
const subject = encodeURIComponent(t('mailFeedback.subject'));

const trainInfo = encodeURIComponent(`

********

${t('mailFeedback.simulationDetails')}:

${t('consist.tractionEngine')}: ${trainName}
${t('consist.compositionCode')}: ${consistCode}
${t('consist.tonnage')}: ${consistMass}
${t('consist.length')}: ${consistLength}
${t('consist.maxSpeed')}: ${maxSpeed}

${t('trainPath.origin')}: ${origin}
${t('trainPath.destination')}: ${destination}
${t('departureTime')}: ${departureTime}
`);
const body = `${trainInfo}${t('mailFeedback.body')}`;

window.location.href = `mailto:[email protected]?subject=${subject}&body=${body}`;
};
Comment on lines +28 to +50
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The email content formatting could be simplified:

Suggested change
const handleEmailClick = () => {
const subject = encodeURIComponent(t('mailFeedback.subject'));
const trainInfo = encodeURIComponent(`
********
${t('mailFeedback.simulationDetails')}:
${t('consist.tractionEngine')}: ${trainName}
${t('consist.compositionCode')}: ${consistCode}
${t('consist.tonnage')}: ${consistMass}
${t('consist.length')}: ${consistLength}
${t('consist.maxSpeed')}: ${maxSpeed}
${t('trainPath.origin')}: ${origin}
${t('trainPath.destination')}: ${destination}
${t('departureTime')}: ${departureTime}
`);
const body = `${trainInfo}${t('mailFeedback.body')}`;
window.location.href = `mailto:[email protected]?subject=${subject}&body=${body}`;
};
const handleEmailClick = () => {
const subject = encodeURIComponent(t('mailFeedback.subject'));
const separator = '********';
const messageContent = `
${separator}
${t('mailFeedback.simulationDetails')}:
${t('consist.tractionEngine')}: ${trainName}
${t('consist.compositionCode')}: ${consistCode}
${t('consist.tonnage')}: ${consistMass}
${t('consist.length')}: ${consistLength}
${t('consist.maxSpeed')}: ${maxSpeed}
${t('trainPath.origin')}: ${origin}
${t('trainPath.destination')}: ${destination}
${t('departureTime')}: ${departureTime}
${separator}
${t('mailFeedback.body')}
${separator}`;

This:

  • Moves formatting out of translation files
  • Layout/formatting is managed in the component


return (
<div className="feedback-card">
<div className="feedback-separator" />
<div className="feedback-card-header">
<h3 data-testid="feedback-title">
{t('mailFeedback.title')}
<Comment className="feedback-card-header-icon" size="sm" />
</h3>
</div>
<p className="feedback-card-text">{t('mailFeedback.description')}</p>
<Button
data-testid="feedback-button"
label={t('mailFeedback.writeButton')}
variant="Cancel"
size="medium"
onClick={handleEmailClick}
/>
</div>
);
};

export default FeedbackCard;
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import useDeploymentSettings from 'utils/hooks/useDeploymentSettings';

import SimulationReportSheet from './SimulationReportSheet';
import StdcmDebugResults from './StdcmDebugResults';
import FeedbackCard from './StdcmFeedback';
import StcdmResultsTable from './StdcmResultsTable';
import StdcmSimulationNavigator from './StdcmSimulationNavigator';

Expand Down Expand Up @@ -144,6 +145,7 @@ const StcdmResults = ({
/>
</div>
)}
<FeedbackCard />
</div>
) : (
<div className="simulation-failure">
Expand Down
39 changes: 39 additions & 0 deletions front/src/styles/scss/applications/stdcm/_results.scss
Original file line number Diff line number Diff line change
Expand Up @@ -380,3 +380,42 @@
position: relative;
}
}

.feedback-card {
margin-bottom: 8px;
.feedback-card-header {
h3 {
color: var(--black100);
font-size: 600;
line-height: 24px;
margin-bottom: 0;
}
.feedback-card-header-icon {
color: var(--grey30);
padding-left: 8px;
}
}
.feedback-separator {
color: var(--grey20);
width: 804px;
margin: 56px 0 30px;
border-bottom: 5px solid var(--grey20);
}
.feedback-card-text {
color: var(--black100);
}
.feedback-card-button {
display: flex;
justify-content: center;
align-items: center;
background-color: var(--white100);
border-radius: 5px;
color: var(--grey70);
font-weight: 500;
width: 67px;
height: 32px;
padding: 12px 24px;
margin-right: 16px;
border: 1.5px solid var(--grey50);
}
}
64 changes: 64 additions & 0 deletions front/tests/015-stdcm-feedback-mail.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { test } from '@playwright/test';

import type { Infra } from 'common/api/osrdEditoastApi';

import { expectedBody, expectedSubject } from './assets/constants/mail-feedback-const';
import ConsistSection from './pages/stdcm/consist-section';
import DestinationSection from './pages/stdcm/destination-section';
import OriginSection from './pages/stdcm/origin-section';
import SimulationResultPage from './pages/stdcm/simulation-results-page';
import STDCMPage from './pages/stdcm/stdcm-page';
import { waitForInfraStateToBeCached } from './utils';
import { getInfra } from './utils/api-utils';
import type { ConsistFields } from './utils/types';

test.describe('FeedbackCard Tests', () => {
test.slow(); // Mark test as slow due to multiple steps
test.use({ viewport: { width: 1920, height: 1080 } });

let stdcmPage: STDCMPage;
let consistSection: ConsistSection;
let originSection: OriginSection;
let destinationSection: DestinationSection;
let simulationResultPage: SimulationResultPage;
let infra: Infra;

const consistDetails: ConsistFields = {
tractionEngine: 'electricRollingStockName',
towedRollingStock: 'HLP',
tonnage: '180',
length: '40',
speedLimitTag: 'MA100',
};

test.beforeAll('Fetch infrastructure', async () => {
infra = await getInfra();
});

test.beforeEach('Navigate to the STDCM page', async ({ page }) => {
[stdcmPage, consistSection, originSection, destinationSection, simulationResultPage] = [
new STDCMPage(page),
new ConsistSection(page),
new OriginSection(page),
new DestinationSection(page),
new SimulationResultPage(page),
];
await page.goto('/stdcm');
await page.waitForLoadState('networkidle');
await stdcmPage.removeViteOverlay();
// Wait for infra to be in 'CACHED' state before proceeding
await waitForInfraStateToBeCached(infra.id);
});

test('Verify FeedbackCard visibility', async () => {
await consistSection.fillAndVerifyConsistDetails(consistDetails, '180', '40');
await originSection.fillAndVerifyOriginDetails();
await destinationSection.fillAndVerifyDestinationDetails();
await stdcmPage.launchSimulation();

await simulationResultPage.verifyFeedbackCardVisibility();
await simulationResultPage.clickFeedbackButton();

await simulationResultPage.verifyMailRedirection(expectedSubject, expectedBody);
});
});
18 changes: 18 additions & 0 deletions front/tests/assets/constants/mail-feedback-const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export const expectedSubject = 'Feedback on the STDCM simulator';

export const expectedBody = `
********
Simulation details:

Traction Engine: electricRollingStockName
Composition Code: HLP
Tonnage: 180 t
Length: 40 m
Max Speed: 100 km/h

Origin: Perrigny BV
Destination: Miramas BV
Departure Time: 14:30

Please share your feedback here.
`;
33 changes: 33 additions & 0 deletions front/tests/pages/stdcm/simulation-results-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ class SimulationResultPage extends STDCMPage {

private readonly startNewQueryWithDataButton: Locator;

private readonly feedbackCardContainer: Locator;

private readonly feedbackTitle: Locator;

private readonly feedbackDescription: Locator;

private readonly feedbackButton: Locator;

constructor(page: Page) {
super(page);
this.mapResultContainer = page.locator('#stdcm-map-result');
Expand All @@ -55,6 +63,10 @@ class SimulationResultPage extends STDCMPage {
this.startNewQueryButton = page.getByTestId('start-new-query-button');
this.startNewQueryWithDataButton = page.getByTestId('start-new-query-with-data-button');
this.simulationList = page.locator('.stdcm-results .simulation-list');
this.feedbackCardContainer = page.locator('.feedback-card');
this.feedbackTitle = page.getByTestId('feedback-title');
this.feedbackDescription = this.feedbackCardContainer.locator('.feedback-card-text');
this.feedbackButton = page.getByTestId('feedback-button');
}

private getSimulationLengthAndDurationLocator(simulationNumber: number): Locator {
Expand Down Expand Up @@ -195,5 +207,26 @@ class SimulationResultPage extends STDCMPage {
// Validate length and duration
expect(actualLengthAndDuration).toEqual(expectedLengthAndDuration);
}

async verifyFeedbackCardVisibility() {
await this.launchSimulation();
await expect(this.simulationResultTable).toBeVisible();
await expect(this.feedbackCardContainer).toBeVisible();
await expect(this.feedbackTitle).toBeVisible();
await expect(this.feedbackDescription).toBeVisible();
await expect(this.feedbackButton).toBeVisible();
}

async clickFeedbackButton() {
await expect(this.feedbackButton).toBeEnabled();
await this.feedbackButton.click();
}

async verifyMailRedirection(expectedSubject: string, expectedBody: string) {
await this.clickFeedbackButton();
const mailtoUrl = await this.page.evaluate(() => window.location.href);
const expectedMailto = `mailto:[email protected]?subject=${encodeURIComponent(expectedSubject)}&body=${encodeURIComponent(expectedBody)}`;
expect(mailtoUrl).toContain(expectedMailto);
}
}
export default SimulationResultPage;
Loading