-
Notifications
You must be signed in to change notification settings - Fork 1
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
Poc classe Import #4529
Open
C2Chandelier
wants to merge
18
commits into
main
Choose a base branch
from
POC-classe
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Poc classe Import #4529
Changes from 10 commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
97a82e3
up
7c18d3b
up
2ddbd0b
up
4db6980
up
d0961b7
test
bc7eb79
final test
03a0e85
Merge branch 'main' into POC-classe
C2Chandelier 89606a6
up
5f58f2d
fix import
c04307c
up
0c66fd3
Merge branch 'main' into POC-classe
C2Chandelier 59be08d
up
0842bc2
up
dd39e51
feat(api): anonymise newfields not specified by default (#4351)
michaelgarcia75 dfb09f1
Merge branch 'main' into POC-classe
C2Chandelier c8532b4
Merge branch 'main' into POC-classe
C2Chandelier f95ffa2
up
afba60f
Merge branch 'main' into POC-classe
C2Chandelier File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import React, { ChangeEvent, useRef, useState } from "react"; | ||
import ReactLoading from "react-loading"; | ||
import { HiOutlineDocumentAdd } from "react-icons/hi"; | ||
|
||
import { MIME_TYPES, translate } from "snu-lib"; | ||
|
||
import api from "@/services/api"; | ||
import { capture } from "@/sentry"; | ||
|
||
import { Button, Container, Header, Page } from "@snu/ds/admin"; | ||
|
||
import { toastr } from "react-redux-toastr"; | ||
|
||
const FILE_SIZE_LIMIT = 5 * 1024 * 1024; | ||
|
||
export default function Import() { | ||
const [isUploading, setIsUploading] = useState(false); | ||
const [uploadError, setUploadError] = useState<string | null>(null); | ||
const fileInput = useRef<HTMLInputElement>(null); | ||
|
||
function importFile(e: React.MouseEvent<HTMLButtonElement>) { | ||
e.preventDefault(); | ||
setUploadError(null); | ||
if (fileInput && fileInput.current) { | ||
fileInput.current.click(); | ||
} | ||
} | ||
|
||
async function handleUpload(e: ChangeEvent<HTMLInputElement>) { | ||
if (!e?.target?.files?.length) return; | ||
const file = e.target.files[0]; | ||
setUploadError(null); | ||
if (file.type !== MIME_TYPES.EXCEL) { | ||
setUploadError("Le fichier doit être au format Excel."); | ||
return; | ||
} | ||
if (file.size > FILE_SIZE_LIMIT) { | ||
setUploadError("Votre fichier dépasse la limite de 5Mo."); | ||
return; | ||
} | ||
|
||
setIsUploading(true); | ||
try { | ||
const res = await api.uploadFiles(`/cle/classe/importAuto/classe-importAuto`, [file]); | ||
if (res.code === "FILE_CORRUPTED") { | ||
setUploadError("Le fichier semble corrompu. Pouvez-vous changer le format ou regénérer votre fichier ? Si vous rencontrez toujours le problème, contactez le support."); | ||
} else if (!res.ok) { | ||
toastr.error("Une erreur est survenue lors de l'import du fichier", translate(res.code)); | ||
capture(res.code); | ||
setUploadError("Une erreur s'est produite lors du téléversement de votre fichier."); | ||
} else { | ||
toastr.success("Succès", "Fichier importé avec succès"); | ||
const { data: base64Data, mimeType, fileName } = res; | ||
const binaryData = Uint8Array.from(atob(base64Data), (c) => c.charCodeAt(0)); | ||
|
||
const blob = new Blob([binaryData], { type: mimeType }); | ||
const url = window.URL.createObjectURL(blob); | ||
|
||
const a = document.createElement("a"); | ||
a.href = url; | ||
a.download = fileName; | ||
document.body.appendChild(a); | ||
a.click(); | ||
|
||
a.remove(); | ||
window.URL.revokeObjectURL(url); | ||
} | ||
} catch (err) { | ||
setUploadError("Une erreur est survenue. Nous n'avons pu enregistrer le fichier. Veuillez réessayer dans quelques instants."); | ||
} | ||
setIsUploading(false); | ||
} | ||
|
||
return ( | ||
<Page> | ||
<Header title="Import des classes" breadcrumb={[{ title: "Séjours" }, { title: "Classes" }, { title: "Import" }]} /> | ||
<Container> | ||
<div className="mt-8 flex w-full flex-col items-center justify-center bg-white rounded-xl px-8 pt-12 pb-24"> | ||
<div className="bg-gray-50 w-full flex-col pb-5"> | ||
<h1 className="text-lg leading-6 font-medium text-gray-900 text-center mt-12 mb-2">Mettre à jour les classes</h1> | ||
<p className="text-sm leading-5 font-normal text-gray-500 text-center mb-12">Importez votre fichier (au format .xlsx jusqu’à 5Mo)</p> | ||
|
||
{!isUploading ? ( | ||
<> | ||
<Button | ||
onClick={importFile} | ||
className="cursor-pointer text-center mx-auto text-blue-600" | ||
leftIcon={<HiOutlineDocumentAdd className="mt-0.5 mr-2" size={20} />} | ||
title="Téléversez votre fichier"></Button> | ||
<input type="file" accept={MIME_TYPES.EXCEL} ref={fileInput} onChange={handleUpload} className="hidden" /> | ||
{uploadError && <div className="mt-8 text-center text-sm font-bold text-red-900">{uploadError}</div>} | ||
</> | ||
) : ( | ||
<> | ||
<div className="mx-auto flex justify-center mb-2"> | ||
<p>Temps Estimé : 1 minute</p> | ||
</div> | ||
<ReactLoading className="mx-auto" type="spin" color="#2563EB" width={"40px"} height={"40px"} /> | ||
</> | ||
)} | ||
</div> | ||
</div> | ||
</Container> | ||
</Page> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -101,6 +101,6 @@ | |
"supertest": "6.3.4", | ||
"ts-jest": "^29.2.5", | ||
"typescript": "5.4.5", | ||
"xlsx": "~0.18.5" | ||
"xlsx": "^0.18.5" | ||
} | ||
} |
80 changes: 80 additions & 0 deletions
80
api/src/cle/classe/importAuto/classeImportAutoController.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import express from "express"; | ||
import fileUpload from "express-fileupload"; | ||
import { UploadedFile } from "express-fileupload"; | ||
import fs from "fs"; | ||
|
||
import { isSuperAdmin, ROLES } from "snu-lib"; | ||
import { ERRORS, uploadFile } from "../../../utils"; | ||
import { UserRequest } from "../../../controllers/request"; | ||
import { readCSVBuffer } from "../../../services/fileService"; | ||
import { capture } from "../../../sentry"; | ||
import { generateCSVStream, getHeaders, streamToBuffer, XLSXToCSVBuffer } from "../../../services/fileService"; | ||
import { accessControlMiddleware } from "../../../middlewares/accessControlMiddleware"; | ||
import { authMiddleware } from "../../../middlewares/authMiddleware"; | ||
|
||
import { updateClasseFromExport } from "./classeImportAutoService"; | ||
import { ClasseFromCSV } from "./classeImportAutoType"; | ||
|
||
const router = express.Router(); | ||
router.use(authMiddleware("referent")); | ||
const xlsxMimetype = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; | ||
|
||
router.post( | ||
"/classe-importAuto", | ||
[accessControlMiddleware([ROLES.ADMIN])], | ||
fileUpload({ limits: { fileSize: 5 * 1024 * 1024 }, useTempFiles: true, tempFileDir: "/tmp/" }), | ||
async (req: UserRequest, res) => { | ||
if (!isSuperAdmin(req.user)) { | ||
return res.status(403).send({ ok: false, code: ERRORS.OPERATION_UNAUTHORIZED }); | ||
} | ||
|
||
try { | ||
const files = Object.values(req.files || {}); | ||
if (files.length === 0) { | ||
return res.status(400).send({ ok: false, code: ERRORS.INVALID_BODY }); | ||
} | ||
|
||
const file: UploadedFile = files[0]; | ||
if (file.mimetype !== xlsxMimetype) { | ||
return res.status(400).send({ ok: false, code: ERRORS.INVALID_BODY }); | ||
} | ||
|
||
const filePath = file.tempFilePath; | ||
const timestamp = `${new Date().toISOString()?.replaceAll(":", "-")?.replace(".", "-")}`; | ||
|
||
const data = fs.readFileSync(filePath); | ||
|
||
uploadFile(`file/appelAProjet/import/import-classe/import-${timestamp}/${timestamp}-exported-classes.xlsx`, { | ||
data: data, | ||
encoding: "", | ||
mimetype: xlsxMimetype, | ||
}); | ||
|
||
const csvBuffer = XLSXToCSVBuffer(filePath); | ||
const parsedContent: ClasseFromCSV[] = await readCSVBuffer(csvBuffer); | ||
const importedClasseCohort = await updateClasseFromExport(parsedContent); | ||
|
||
const headers = getHeaders(importedClasseCohort); | ||
const csvDataReponse = generateCSVStream(importedClasseCohort, headers); | ||
uploadFile(`file/appelAProjet/import/import-classe/import-${timestamp}/${timestamp}-updated-classes.csv`, { | ||
data: csvDataReponse, | ||
encoding: "", | ||
mimetype: "text/csv", | ||
}); | ||
|
||
const csvBufferResponse = Buffer.from(await streamToBuffer(csvDataReponse)); | ||
|
||
return res.status(200).send({ | ||
data: csvBufferResponse.toString("base64"), | ||
mimeType: "text/csv", | ||
fileName: `${timestamp}-updated-classes.csv`, | ||
ok: true, | ||
}); | ||
} catch (error) { | ||
capture(error); | ||
return res.status(422).send({ ok: false, code: error.message }); | ||
} | ||
}, | ||
); | ||
|
||
export default router; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { ClasseFromCSV, ClasseMapped } from "./classeImportAutoType"; | ||
|
||
export const mapClassesForUpdate = (classesChortes: ClasseFromCSV[]): ClasseMapped[] => { | ||
return classesChortes.map((classeCohorte) => { | ||
const classeCohorteMapped: ClasseMapped = { | ||
classeId: classeCohorte["Identifiant de la classe engagée"], | ||
cohortCode: classeCohorte["Session formule"], | ||
classeTotalSeats: classeCohorte["Effectif de jeunes concernés"], | ||
centerCode: classeCohorte["Désignation du centre"], | ||
pdrCode: classeCohorte["Code point de rassemblement initial"], | ||
sessionCode: `${classeCohorte["Session : Code de la session"]}_${classeCohorte["Désignation du centre"]}`, | ||
}; | ||
return classeCohorteMapped; | ||
}); | ||
}; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pour un superAdmin tu peux faire : accessControlMiddleware([])