Skip to content

Commit

Permalink
fix: drinking water test display (#512)
Browse files Browse the repository at this point in the history
  • Loading branch information
arnaudambro authored Apr 5, 2024
1 parent 5d29707 commit ab7b8fe
Show file tree
Hide file tree
Showing 6 changed files with 311 additions and 1 deletion.
56 changes: 55 additions & 1 deletion api-node/src/aggregators/drinking_water.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
type ShortPrelevementResult,
type HubEAUResultsParameters,
type ExtendedShortPrelevementResult,
type PrelevementResult,
type HubEAUCompleteResponse,
ConformityEnum,
} from '~/types/api/drinking_water';
import {
Expand Down Expand Up @@ -294,7 +296,7 @@ async function fetchDrinkingWaterData(udi: User['udi']) {
}
}
if (hubeauUdiResponse.next) {
getHubeauDataRecursive(hubeauUdiResponse.next);
await getHubeauDataRecursive(hubeauUdiResponse.next);
} else {
if (currentPrelevementConclusions && currentPrelevementParametersCount) {
prelevements.push({
Expand Down Expand Up @@ -452,8 +454,60 @@ async function fetchDrinkingWaterDataCascade(udi: User['udi']) {
};
}

async function fetchDrinkingWaterPrelevement(code_prelevement: string) {
const hubeEauEndpoint =
'https://hubeau.eaufrance.fr/api/v1/qualite_eau_potable/resultats_dis';

// there is new data to fetch ! let's do it
const hubEauURL = new URL(hubeEauEndpoint);

const hubEauQuery: HubEAUResultsParameters = {
size: 1000,
code_prelevement,
};

Object.keys(hubEauQuery).forEach((key) => {
const value = hubEauQuery[key as keyof typeof hubEauQuery];
if (value) {
hubEauURL.searchParams.append(
key,
Array.isArray(value) ? value.join(',') : `${value}`,
);
}
});

const hubeau_first_url = hubEauURL.toString();
let hubeauUdiFirstResponse: HubEAUCompleteResponse | null = null;

const parametersTested: Array<PrelevementResult> = [];

async function getHubeauDataRecursive(hubeau_url: string) {
const hubeauUdiResponse: HubEAUCompleteResponse = await fetch(hubeau_url, {
retryDelay: 1000,
retries: 3,
}).then(async (res) => res.json());
if (hubeau_url === hubeau_first_url) {
hubeauUdiFirstResponse = hubeauUdiResponse;
}
if (hubeauUdiResponse.data?.length > 0) {
parametersTested.push(...hubeauUdiResponse.data);
}
if (hubeauUdiResponse.next) {
await getHubeauDataRecursive(hubeauUdiResponse.next);
}
}

await getHubeauDataRecursive(hubeau_first_url);

return {
...(hubeauUdiFirstResponse as unknown as HubEAUCompleteResponse),
data: parametersTested,
};
}

export {
getDrinkingWaterIndicator,
fetchDrinkingWaterData,
fetchDrinkingWaterDataCascade,
fetchDrinkingWaterPrelevement,
};
140 changes: 140 additions & 0 deletions api-node/src/controllers/hubeau-prelevement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import express from 'express';
import helmet from 'helmet';
import { z } from 'zod';
import fs from 'fs';
import { catchErrors } from '../middlewares/errors';
import { fetchDrinkingWaterPrelevement } from '~/aggregators/drinking_water';
import dayjs from 'dayjs';

const router = express.Router();
const drinkingWater403 = fs.readFileSync(
'./src/templates/drinking-water-403-no-code-provided.html',
'utf8',
);
const drinkingWater = fs.readFileSync(
'./src/templates/drinking-water-test-result.html',
'utf8',
);

router.get(
'/',
helmet.contentSecurityPolicy({
useDefaults: true,
directives: {
'default-src': ["'self'"],
'script-src': ["'self'", 'https://cdn.tailwindcss.com'],
'img-src': [
"'self'",
'https://j.gifs.com/VPXDoz.gif',
'https://recosante.beta.gouv.fr/favicon.ico',
],
'object-src': ["'none'"],
'upgrade-insecure-requests': [],
},
}),
catchErrors(
async (
req: express.Request,
res: express.Response,
next: express.NextFunction,
) => {
try {
z.object({
code_prelevement: z.string(),
}).parse(req.query);
} catch (zodError) {
res.status(403).send(drinkingWater403);
return;
}
const { code_prelevement } = req.query;

if (!code_prelevement) {
res.status(403).send(drinkingWater403);
return;
}
const hubeauResponse = await fetchDrinkingWaterPrelevement(
code_prelevement as string,
);
const prelevement = hubeauResponse.data;
const metadata = prelevement[0];

const testDisplay = drinkingWater
.replace('{{CODE_PRELEVEMENT}}', metadata.code_prelevement)
.replace(
'{{PLACE_PRELEVEMENT}}',
`${metadata.nom_commune} (${metadata.code_departement})`,
)
.replace('{{NOM_DISTRIBUTEUR}}', metadata.nom_distributeur)
.replace(
'{{CONCLUSION_CONFORMITE_PRELEVEMENT}}',
metadata.conclusion_conformite_prelevement,
)
.replace(
'{{HUBEAU_LINK}}',
`https://hubeau.eaufrance.fr/api/v1/qualite_eau_potable/resultats_dis?code_prelevement=${metadata.code_prelevement}&size=1000`,
)
.replace(
'{{DATE_PRELEVEMENT}}',
dayjs(metadata.date_prelevement).format('DD/MM/YYYY HH:mm'),
)
.replace('{{COUNT_PARAMETERS}}', prelevement.length.toString())
.replace(
'{{UDIS}}',
metadata.reseaux
?.map(
(reseau) => `${reseau.code} - ${reseau.nom} (${reseau.debit})`,
)
.join('<br />') ?? '',
)
.replace(
'{{RESULTS}}',
prelevement
.map((paramTested) => {
return `
<tr>
<td class="w-full max-w-0 py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:w-auto sm:max-w-none sm:pl-0">
${
paramTested.libelle_parametre
} <span class="text-xs text-gray-400">(${
paramTested.code_parametre_se
})</span>
<dl class="font-normal lg:hidden">
<dt class="sr-only">Résultat</dt>
<dd class="mt-1 truncate text-gray-700">${
paramTested.resultat_alphanumerique
} ${
paramTested.libelle_unite !== 'SANS OBJET'
? paramTested.libelle_unite
: ''
}</dd>
<dt class="sr-only sm:hidden">Limite</dt>
<dd class="mt-1 truncate text-gray-500 sm:hidden">${
paramTested.limite_qualite_parametre
}</dd>
<dt class="sr-only sm:hidden">Référence</dt>
<dd class="mt-1 truncate text-gray-500 sm:hidden">${
paramTested.reference_qualite_parametre
}</dd>
</dl>
</td>
<td class="hidden px-3 py-4 text-sm text-gray-500 lg:table-cell">${
paramTested.resultat_alphanumerique
} (${paramTested.libelle_unite})</td>
<td class="hidden px-3 py-4 text-sm text-gray-500 sm:table-cell">${
paramTested.limite_qualite_parametre ?? 'N/A'
}</td>
<td class="hidden px-3 py-4 text-sm text-gray-500 sm:table-cell">${
paramTested.reference_qualite_parametre ?? 'N/A'
}</td>
</tr>
`;
})
.join('\n') || '',
);

res.status(200).send(testDisplay);
},
),
);

export default router;
1 change: 1 addition & 0 deletions api-node/src/getters/drinking_water.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ async function getDrinkingWaterFromUdi({
bacteriological:
checkPrelevementConformityBacteriological(test_result),
},
link: `https://recosante-api-node.fabrique.social.gouv.fr/hubeau-prelevement?code_prelevement=${test_result.code_prelevement}`,
drinkingWater: {
parameters_count: test_result.parameters_count,
prelevement_code: test_result.code_prelevement,
Expand Down
2 changes: 2 additions & 0 deletions api-node/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import mailRouter from './controllers/mail.ts';
import feedbackRouter from './controllers/feedback.ts';
import notificationRouter from './controllers/notification.ts';
import udiRouter from './controllers/udi.ts';
import hubeauPrelevementRouter from './controllers/hubeau-prelevement.ts';
import callToActionRouter from './controllers/call-to-action.ts';

import packageJson from '../package.json';
Expand Down Expand Up @@ -113,6 +114,7 @@ app.use('/feedback', versionCheck, feedbackRouter);
app.use('/notification', versionCheck, notificationRouter);
app.use('/call-to-action', versionCheck, callToActionRouter);
app.use('/udi', udiRouter);
app.use('/hubeau-prelevement', hubeauPrelevementRouter);

app.use(Sentry.Handlers.errorHandler());
app.use(sendError);
Expand Down
69 changes: 69 additions & 0 deletions api-node/src/templates/drinking-water-403-no-code-provided.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<!DOCTYPE html>
<html class=""h-full w-full">
<head>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="https://recosante.beta.gouv.fr/favicon.ico">
<script src="https://cdn.tailwindcss.com"></script>
</head>
</head>
<body class=""h-full w-full">
<div
class="grid min-h-full grid-cols-1 grid-rows-[1fr,auto,1fr] bg-white lg:grid-cols-[max(50%,36rem),1fr]"
>
<!-- <header
class="mx-auto w-full max-w-7xl px-6 pt-6 sm:pt-10 lg:col-span-2 lg:col-start-1 lg:row-start-1 lg:px-8"
>
<a href="#">
<span class="sr-only">Recosanté</span>
<img
class="h-10 w-auto sm:h-12"
src=""
alt="Logo Recosanté"
/>
</a>
</header> -->
<main
class="mx-auto w-full max-w-7xl px-6 py-24 sm:py-32 lg:col-span-2 lg:col-start-1 lg:row-start-2 lg:px-8"
>
<div class="max-w-lg">
<p class="text-base font-semibold leading-8 text-indigo-600">403</p>
<h1
class="mt-4 text-3xl font-bold tracking-tight text-gray-900 sm:text-5xl"
>
Test non trouvé
</h1>
<p class="mt-6 text-base leading-7 text-gray-600">
Vous devez fournir un `code_prelevement` en paramètre d'URL
</p>
</div>
</main>
<footer class="self-end lg:col-span-2 lg:col-start-1 lg:row-start-3">
<div class="border-t border-gray-100 bg-gray-50 py-10">
<nav
class="mx-auto flex w-full max-w-7xl items-center gap-x-4 px-6 text-sm leading-7 text-gray-600 lg:px-8"
>
<a href="https://recosante.beta.gouv.fr">Retour sur Recosanté</a>
<svg
viewBox="0 0 2 2"
aria-hidden="true"
class="h-0.5 w-0.5 fill-gray-300"
>
<circle cx="1" cy="1" r="1" />
</svg>
</nav>
</div>
</footer>
<div
class="hidden lg:relative lg:col-start-2 lg:row-start-1 lg:row-end-4 lg:block"
>
<img
src="https://j.gifs.com/VPXDoz.gif"
alt=""
class="absolute inset-0 h-full w-full object-cover"
/>
</div>
</div>
</body>
</html>
44 changes: 44 additions & 0 deletions api-node/src/templates/drinking-water-test-result.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html class=""h-full w-full">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="https://recosante.beta.gouv.fr/favicon.ico">
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class=""h-full w-full">
<div class="px-4 sm:px-6 lg:px-8 mt-8">
<div class="sm:flex sm:items-center">
<div class="sm:flex-auto">
<h1 class="text-base font-semibold leading-6 text-gray-900">Test {{CODE_PRELEVEMENT}}</h1>
<p class="mt-2 text-sm text-gray-700"><b>Date de prélèvement&nbsp;:</b> {{DATE_PRELEVEMENT}}</p>
<p class="mt-2 text-sm text-gray-700"><b>Lieu de prélèvement&nbsp;:</b> {{PLACE_PRELEVEMENT}}</p>
<p class="mt-2 text-sm text-gray-700"><b>Nombre de paramètres testés&nbsp;:</b> {{COUNT_PARAMETERS}} (<a href="{{HUBEAU_LINK}}"
target="_blank"
rel="noopener noreferrer"
class="inline-flex items-center text-indigo-600 hover:text-indigo-900">lien vers les données brutes <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-3 h-3 inline ml-2">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />
</svg></a>)</p>
<p class="mt-2 text-sm text-gray-700"><b>Distributeur&nbsp;:</b> {{NOM_DISTRIBUTEUR}}</p>
<p class="mt-2 text-sm text-gray-700"><b>Réseaux concernés&nbsp;:</b>{{UDIS}}</p>
<p class="mt-2 text-sm text-gray-700"><b>Conclusion conformité prélèvement&nbsp;:</b> {{CONCLUSION_CONFORMITE_PRELEVEMENT}}</p>
</div>
</div>
<div class="-mx-4 mt-8 sm:-mx-0">
<table class="min-w-full divide-y divide-gray-300">
<thead>
<tr>
<th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-0" data-field="libelle_parametre,code_parametre_se">Paramètre</th>
<th scope="col" class="hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell" data-field="resultat_alphanumerique,libelle_unite">Résultat</th>
<th scope="col" class="hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell" data-field="limite_qualite_parametre">Limite(s) de qualité </th>
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900" data-field="reference_qualite_parametre">Référence(s) de qualité</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
{{RESULTS}}
</tbody>
</table>
</div>
</div>
</body>
</html>

0 comments on commit ab7b8fe

Please sign in to comment.