From 237a7652139e1e0dec4e22be31056a7b90c3e617 Mon Sep 17 00:00:00 2001 From: hatim dinia Date: Wed, 18 Dec 2024 14:22:24 +0100 Subject: [PATCH 1/4] feat(ui-debug): add download of files in their original format --- .../Singlestudy/explore/Debug/Data/Json.tsx | 37 ++++++++++-------- .../Singlestudy/explore/Debug/Data/Text.tsx | 29 ++++++++------ .../explore/Debug/Data/Unsupported.tsx | 23 ++++------- webapp/src/services/api/studies/raw/index.ts | 38 +++++++++++++++++++ webapp/src/services/api/studies/raw/types.ts | 5 +++ 5 files changed, 90 insertions(+), 42 deletions(-) diff --git a/webapp/src/components/App/Singlestudy/explore/Debug/Data/Json.tsx b/webapp/src/components/App/Singlestudy/explore/Debug/Data/Json.tsx index f014626bf5..ed564d2d07 100644 --- a/webapp/src/components/App/Singlestudy/explore/Debug/Data/Json.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Debug/Data/Json.tsx @@ -24,13 +24,14 @@ import { downloadFile } from "../../../../../../utils/fileUtils"; import { useEffect, useState } from "react"; import { Filename, Flex, Menubar } from "./styles"; import UploadFileButton from "../../../../../common/buttons/UploadFileButton"; +import { getRawFile } from "@/services/api/studies/raw"; function Json({ filePath, filename, studyId, canEdit }: DataCompProps) { const [t] = useTranslation(); const { enqueueSnackbar } = useSnackbar(); const [currentJson, setCurrentJson] = useState(); - const res = usePromiseWithSnackbarError( + const fileRes = usePromiseWithSnackbarError( () => getStudyData(studyId, filePath, -1), { errorMessage: t("studies.error.retrieveData"), @@ -38,9 +39,24 @@ function Json({ filePath, filename, studyId, canEdit }: DataCompProps) { }, ); + const rawFileRes = usePromiseWithSnackbarError( + () => getRawFile(studyId, filePath), + { + errorMessage: t("studies.error.retrieveData"), + deps: [studyId, filePath], + }, + ); + useEffect(() => { - setCurrentJson(res.data); - }, [res.data]); + setCurrentJson(fileRes.data); + }, [fileRes.data]); + + const handleDownload = () => { + if (rawFileRes.data) { + const { data, filename } = rawFileRes.data; + downloadFile(data, filename); + } + }; //////////////////////////////////////////////////////////////// // Event Handlers @@ -58,17 +74,8 @@ function Json({ filePath, filename, studyId, canEdit }: DataCompProps) { }); }; - const handleDownload = () => { - if (currentJson !== undefined) { - downloadFile( - JSON.stringify(currentJson, null, 2), - filename.endsWith(".json") ? filename : `${filename}.json`, - ); - } - }; - const handleUploadSuccessful = () => { - res.reload(); + fileRes.reload(); }; //////////////////////////////////////////////////////////////// @@ -77,7 +84,7 @@ function Json({ filePath, filename, studyId, canEdit }: DataCompProps) { return ( ( @@ -93,7 +100,7 @@ function Json({ filePath, filename, studyId, canEdit }: DataCompProps) { getStudyData(studyId, filePath).then((text) => parseContent(text, { filePath, fileType }), @@ -86,21 +87,27 @@ function Text({ }, ); - //////////////////////////////////////////////////////////////// - // Event Handlers - //////////////////////////////////////////////////////////////// + const rawFileRes = usePromiseWithSnackbarError( + () => getRawFile(studyId, filePath), + { + errorMessage: t("studies.error.retrieveData"), + deps: [studyId, filePath], + }, + ); const handleDownload = () => { - if (res.data) { - downloadFile( - res.data, - filename.endsWith(".txt") ? filename : `${filename}.txt`, - ); + if (rawFileRes.data) { + const { data, filename } = rawFileRes.data; + downloadFile(data, filename); } }; + //////////////////////////////////////////////////////////////// + // Event Handlers + //////////////////////////////////////////////////////////////// + const handleUploadSuccessful = () => { - res.reload(); + fileRes.reload(); }; //////////////////////////////////////////////////////////////// @@ -109,7 +116,7 @@ function Text({ return ( ( diff --git a/webapp/src/components/App/Singlestudy/explore/Debug/Data/Unsupported.tsx b/webapp/src/components/App/Singlestudy/explore/Debug/Data/Unsupported.tsx index e4bcfccc5c..aaf91400bf 100644 --- a/webapp/src/components/App/Singlestudy/explore/Debug/Data/Unsupported.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Debug/Data/Unsupported.tsx @@ -20,14 +20,14 @@ import type { DataCompProps } from "../utils"; import DownloadButton from "@/components/common/buttons/DownloadButton"; import UploadFileButton from "@/components/common/buttons/UploadFileButton"; import usePromiseWithSnackbarError from "@/hooks/usePromiseWithSnackbarError"; -import { getStudyData } from "@/services/api/study"; import { downloadFile } from "@/utils/fileUtils"; +import { getRawFile } from "@/services/api/studies/raw"; function Unsupported({ studyId, filePath, filename, canEdit }: DataCompProps) { const { t } = useTranslation(); - const res = usePromiseWithSnackbarError( - () => getStudyData(studyId, filePath), + const rawFileRes = usePromiseWithSnackbarError( + () => getRawFile(studyId, filePath), { errorMessage: t("studies.error.retrieveData"), deps: [studyId, filePath], @@ -39,15 +39,12 @@ function Unsupported({ studyId, filePath, filename, canEdit }: DataCompProps) { //////////////////////////////////////////////////////////////// const handleDownload = () => { - if (res.data) { - downloadFile(res.data, filename); + if (rawFileRes.data) { + const { data, filename } = rawFileRes.data; + downloadFile(data, filename); } }; - const handleUploadSuccessful = () => { - res.reload(); - }; - //////////////////////////////////////////////////////////////// // JSX //////////////////////////////////////////////////////////////// @@ -56,13 +53,7 @@ function Unsupported({ studyId, filePath, filename, canEdit }: DataCompProps) { {filename} - {canEdit && ( - - )} + {canEdit && } diff --git a/webapp/src/services/api/studies/raw/index.ts b/webapp/src/services/api/studies/raw/index.ts index 85524560d5..b334611562 100644 --- a/webapp/src/services/api/studies/raw/index.ts +++ b/webapp/src/services/api/studies/raw/index.ts @@ -17,6 +17,7 @@ import type { DeleteFileParams, DownloadMatrixParams, ImportFileParams, + RawFile, } from "./types"; export async function downloadMatrix(params: DownloadMatrixParams) { @@ -51,3 +52,40 @@ export async function deleteFile(params: DeleteFileParams) { await client.delete(url, { params: { path } }); } + +/** + * Reads an original raw file from a study with its metadata. + * + * @param studyId - Unique identifier of the study + * @param filePath - Path to the file within the study + * @returns Promise containing the file data and metadata + */ +export async function getRawFile( + studyId: string, + filePath: string, +): Promise { + const response = await client.get( + `/v1/studies/${studyId}/raw/original-file`, + { + params: { + path: filePath, + }, + responseType: "blob", + }, + ); + + const contentDisposition = response.headers["content-disposition"]; + let filename = filePath.split("/").pop() || "file"; // fallback filename + + if (contentDisposition) { + const matches = /filename=([^;]+)/.exec(contentDisposition); + if (matches?.[1]) { + filename = matches[1].replace(/"/g, "").trim(); + } + } + + return { + data: response.data, + filename: filename, + }; +} diff --git a/webapp/src/services/api/studies/raw/types.ts b/webapp/src/services/api/studies/raw/types.ts index 937fd84119..43ff274183 100644 --- a/webapp/src/services/api/studies/raw/types.ts +++ b/webapp/src/services/api/studies/raw/types.ts @@ -39,3 +39,8 @@ export interface DeleteFileParams { studyId: StudyMetadata["id"]; path: string; } + +export interface RawFile { + data: Blob; + filename: string; +} From 652001fc12256fbeddc20417fed47a872d553131 Mon Sep 17 00:00:00 2001 From: hatim dinia Date: Thu, 19 Dec 2024 15:08:32 +0100 Subject: [PATCH 2/4] refactor(ui-raw): update api methods --- .../Singlestudy/explore/Debug/Data/Json.tsx | 38 ++------ .../Singlestudy/explore/Debug/Data/Text.tsx | 26 ++---- .../explore/Debug/Data/Unsupported.tsx | 17 +--- .../common/Matrix/hooks/useMatrix/index.ts | 5 +- .../Matrix/hooks/useMatrix/useMatrix.test.tsx | 4 +- .../common/buttons/DownloadMatrixButton.tsx | 6 +- .../common/buttons/UploadFileButton.tsx | 4 +- webapp/src/services/api/studies/raw/index.ts | 91 +++++++++++++------ webapp/src/services/api/studies/raw/types.ts | 10 +- 9 files changed, 102 insertions(+), 99 deletions(-) diff --git a/webapp/src/components/App/Singlestudy/explore/Debug/Data/Json.tsx b/webapp/src/components/App/Singlestudy/explore/Debug/Data/Json.tsx index ed564d2d07..6f04cb26f1 100644 --- a/webapp/src/components/App/Singlestudy/explore/Debug/Data/Json.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Debug/Data/Json.tsx @@ -21,7 +21,6 @@ import UsePromiseCond from "../../../../../common/utils/UsePromiseCond"; import type { DataCompProps } from "../utils"; import DownloadButton from "../../../../../common/buttons/DownloadButton"; import { downloadFile } from "../../../../../../utils/fileUtils"; -import { useEffect, useState } from "react"; import { Filename, Flex, Menubar } from "./styles"; import UploadFileButton from "../../../../../common/buttons/UploadFileButton"; import { getRawFile } from "@/services/api/studies/raw"; @@ -29,9 +28,8 @@ import { getRawFile } from "@/services/api/studies/raw"; function Json({ filePath, filename, studyId, canEdit }: DataCompProps) { const [t] = useTranslation(); const { enqueueSnackbar } = useSnackbar(); - const [currentJson, setCurrentJson] = useState(); - const fileRes = usePromiseWithSnackbarError( + const jsonRes = usePromiseWithSnackbarError( () => getStudyData(studyId, filePath, -1), { errorMessage: t("studies.error.retrieveData"), @@ -39,43 +37,27 @@ function Json({ filePath, filename, studyId, canEdit }: DataCompProps) { }, ); - const rawFileRes = usePromiseWithSnackbarError( - () => getRawFile(studyId, filePath), - { - errorMessage: t("studies.error.retrieveData"), - deps: [studyId, filePath], - }, - ); - - useEffect(() => { - setCurrentJson(fileRes.data); - }, [fileRes.data]); - - const handleDownload = () => { - if (rawFileRes.data) { - const { data, filename } = rawFileRes.data; - downloadFile(data, filename); - } - }; - //////////////////////////////////////////////////////////////// // Event Handlers //////////////////////////////////////////////////////////////// + const handleDownload = async () => { + const { data, filename } = await getRawFile({ studyId, path: filePath }); + downloadFile(data, filename); + }; + const handleSave: JSONEditorProps["onSave"] = (json) => { return editStudy(json, studyId, filePath); }; - const handleSaveSuccessful: JSONEditorProps["onSaveSuccessful"] = (json) => { - setCurrentJson(json); - + const handleSaveSuccessful: JSONEditorProps["onSaveSuccessful"] = () => { enqueueSnackbar(t("studies.success.saveData"), { variant: "success", }); }; const handleUploadSuccessful = () => { - fileRes.reload(); + jsonRes.reload(); }; //////////////////////////////////////////////////////////////// @@ -84,7 +66,7 @@ function Json({ filePath, filename, studyId, canEdit }: DataCompProps) { return ( ( @@ -100,7 +82,7 @@ function Json({ filePath, filename, studyId, canEdit }: DataCompProps) { getStudyData(studyId, filePath).then((text) => parseContent(text, { filePath, fileType }), @@ -87,27 +87,17 @@ function Text({ }, ); - const rawFileRes = usePromiseWithSnackbarError( - () => getRawFile(studyId, filePath), - { - errorMessage: t("studies.error.retrieveData"), - deps: [studyId, filePath], - }, - ); - - const handleDownload = () => { - if (rawFileRes.data) { - const { data, filename } = rawFileRes.data; - downloadFile(data, filename); - } - }; - //////////////////////////////////////////////////////////////// // Event Handlers //////////////////////////////////////////////////////////////// + const handleDownload = async () => { + const { data, filename } = await getRawFile({ studyId, path: filePath }); + downloadFile(data, filename); + }; + const handleUploadSuccessful = () => { - fileRes.reload(); + textRes.reload(); }; //////////////////////////////////////////////////////////////// @@ -116,7 +106,7 @@ function Text({ return ( ( diff --git a/webapp/src/components/App/Singlestudy/explore/Debug/Data/Unsupported.tsx b/webapp/src/components/App/Singlestudy/explore/Debug/Data/Unsupported.tsx index aaf91400bf..c56c9f89d2 100644 --- a/webapp/src/components/App/Singlestudy/explore/Debug/Data/Unsupported.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Debug/Data/Unsupported.tsx @@ -19,30 +19,19 @@ import { Filename, Flex, Menubar } from "./styles"; import type { DataCompProps } from "../utils"; import DownloadButton from "@/components/common/buttons/DownloadButton"; import UploadFileButton from "@/components/common/buttons/UploadFileButton"; -import usePromiseWithSnackbarError from "@/hooks/usePromiseWithSnackbarError"; import { downloadFile } from "@/utils/fileUtils"; import { getRawFile } from "@/services/api/studies/raw"; function Unsupported({ studyId, filePath, filename, canEdit }: DataCompProps) { const { t } = useTranslation(); - const rawFileRes = usePromiseWithSnackbarError( - () => getRawFile(studyId, filePath), - { - errorMessage: t("studies.error.retrieveData"), - deps: [studyId, filePath], - }, - ); - //////////////////////////////////////////////////////////////// // Event Handlers //////////////////////////////////////////////////////////////// - const handleDownload = () => { - if (rawFileRes.data) { - const { data, filename } = rawFileRes.data; - downloadFile(data, filename); - } + const handleDownload = async () => { + const { data, filename } = await getRawFile({ studyId, path: filePath }); + downloadFile(data, filename); }; //////////////////////////////////////////////////////////////// diff --git a/webapp/src/components/common/Matrix/hooks/useMatrix/index.ts b/webapp/src/components/common/Matrix/hooks/useMatrix/index.ts index 6558e764e8..e89f99be12 100644 --- a/webapp/src/components/common/Matrix/hooks/useMatrix/index.ts +++ b/webapp/src/components/common/Matrix/hooks/useMatrix/index.ts @@ -39,7 +39,7 @@ import { } from "../../shared/utils"; import useUndo from "use-undo"; import { GridCellKind } from "@glideapps/glide-data-grid"; -import { importFile } from "../../../../../services/api/studies/raw"; +import { uploadFile } from "../../../../../services/api/studies/raw"; import { fetchMatrixFn } from "../../../../App/Singlestudy/explore/Modelization/Areas/Hydro/utils"; import usePrompt from "../../../../../hooks/usePrompt"; import { Aggregate, Column, Operation } from "../../shared/constants"; @@ -251,7 +251,8 @@ export function useMatrix( const handleUpload = async (file: File) => { try { - await importFile({ file, studyId, path: url }); + await uploadFile({ file, studyId, path: url }); + // TODO: update the API to return the uploaded file data and remove this await fetchMatrix(); } catch (e) { enqueueErrorSnackbar(t("matrix.error.import"), e as Error); diff --git a/webapp/src/components/common/Matrix/hooks/useMatrix/useMatrix.test.tsx b/webapp/src/components/common/Matrix/hooks/useMatrix/useMatrix.test.tsx index fc1087a927..e4b61d8587 100644 --- a/webapp/src/components/common/Matrix/hooks/useMatrix/useMatrix.test.tsx +++ b/webapp/src/components/common/Matrix/hooks/useMatrix/useMatrix.test.tsx @@ -166,7 +166,7 @@ describe("useMatrix", () => { describe("File operations", () => { test("should handle file import", async () => { const mockFile = new File([""], "test.csv", { type: "text/csv" }); - vi.mocked(rawStudy.importFile).mockResolvedValue(); + vi.mocked(rawStudy.uploadFile).mockResolvedValue(); const hook = await setupHook(); @@ -174,7 +174,7 @@ describe("useMatrix", () => { await hook.result.current.handleUpload(mockFile); }); - expect(rawStudy.importFile).toHaveBeenCalledWith({ + expect(rawStudy.uploadFile).toHaveBeenCalledWith({ file: mockFile, studyId: DATA.studyId, path: DATA.url, diff --git a/webapp/src/components/common/buttons/DownloadMatrixButton.tsx b/webapp/src/components/common/buttons/DownloadMatrixButton.tsx index 55e0d029c3..0f27f75eef 100644 --- a/webapp/src/components/common/buttons/DownloadMatrixButton.tsx +++ b/webapp/src/components/common/buttons/DownloadMatrixButton.tsx @@ -12,7 +12,7 @@ * This file is part of the Antares project. */ -import { downloadMatrix } from "../../../services/api/studies/raw"; +import { getMatrixFile } from "../../../services/api/studies/raw"; import { downloadFile } from "../../../utils/fileUtils"; import { StudyMetadata } from "../../../common/types"; import { useTranslation } from "react-i18next"; @@ -51,7 +51,7 @@ function DownloadMatrixButton(props: DownloadMatrixButtonProps) { const isXlsx = format === "xlsx"; - const res = await downloadMatrix({ + const matrixFile = await getMatrixFile({ studyId, path, format, @@ -62,7 +62,7 @@ function DownloadMatrixButton(props: DownloadMatrixButtonProps) { const extension = format === "csv (semicolon)" ? "csv" : format; return downloadFile( - res, + matrixFile, `matrix_${studyId}_${path.replace("/", "_")}.${extension}`, ); }; diff --git a/webapp/src/components/common/buttons/UploadFileButton.tsx b/webapp/src/components/common/buttons/UploadFileButton.tsx index cc16594be0..872b05b01a 100644 --- a/webapp/src/components/common/buttons/UploadFileButton.tsx +++ b/webapp/src/components/common/buttons/UploadFileButton.tsx @@ -21,7 +21,7 @@ import { toError } from "../../../utils/fnUtils"; import { Accept, useDropzone } from "react-dropzone"; import { StudyMetadata } from "../../../common/types"; import { useSnackbar } from "notistack"; -import { importFile } from "../../../services/api/studies/raw"; +import { uploadFile } from "../../../services/api/studies/raw"; type ValidateResult = boolean | null | undefined; type Validate = (file: File) => ValidateResult | Promise; @@ -89,7 +89,7 @@ function UploadFileButton(props: UploadFileButtonProps) { const filePath = typeof path === "function" ? path(fileToUpload) : path; - await importFile({ + await uploadFile({ studyId, path: filePath, file: fileToUpload, diff --git a/webapp/src/services/api/studies/raw/index.ts b/webapp/src/services/api/studies/raw/index.ts index b334611562..4e2938c4a6 100644 --- a/webapp/src/services/api/studies/raw/index.ts +++ b/webapp/src/services/api/studies/raw/index.ts @@ -15,29 +15,56 @@ import client from "../../client"; import type { DeleteFileParams, - DownloadMatrixParams, - ImportFileParams, + GetMatrixFileParams, + GetRawFileParams, RawFile, + UploadFileParams, } from "./types"; -export async function downloadMatrix(params: DownloadMatrixParams) { +/** + * Gets a matrix file from a study's raw files. + * + * @param params - Parameters for getting the matrix + * @param params.studyId - Unique identifier of the study + * @param params.path - Path to the matrix file + * @param params.format - Optional. Export format for the matrix + * @param params.header - Optional. Whether to include headers + * @param params.index - Optional. Whether to include indices + * @returns Promise containing the matrix data as a Blob + */ +export async function getMatrixFile(params: GetMatrixFileParams) { const { studyId, ...queryParams } = params; - const url = `/v1/studies/${studyId}/raw/download`; - - const { data } = await client.get(url, { - params: queryParams, - responseType: "blob", - }); + const { data } = await client.get( + `/v1/studies/${studyId}/raw/download`, + { + params: queryParams, + responseType: "blob", + }, + ); return data; } -export async function importFile(params: ImportFileParams) { +/** + * Uploads a file to a study's raw storage, creating or updating it based on existence. + * + * !Warning: This endpoint currently uses a non-standard REST structure (/raw) which + * may lead to confusion. It handles both create and update operations through PUT, + * while directory creation is managed through a separate flag. + * + * @param params - Parameters for the file upload + * @param params.studyId - Unique identifier of the study + * @param params.path - Destination path for the file + * @param params.file - File content to upload + * @param params.createMissing - Optional. Whether to create missing parent directories + * @param params.onUploadProgress - Optional. Callback for upload progress updates + * @returns Promise that resolves when the upload is complete + */ +export async function uploadFile(params: UploadFileParams) { const { studyId, file, onUploadProgress, ...queryParams } = params; - const url = `/v1/studies/${studyId}/raw`; const body = { file }; - await client.putForm(url, body, { + await client.putForm(`/v1/studies/${studyId}/raw`, body, { params: { ...queryParams, create_missing: queryParams.createMissing, @@ -46,46 +73,54 @@ export async function importFile(params: ImportFileParams) { }); } +/** + * Deletes a raw file from a study. + * + * @param params - Parameters for deleting the file + * @param params.studyId - Unique identifier of the study + * @param params.path - Path to the file to delete + * @returns Promise that resolves when the deletion is complete + */ export async function deleteFile(params: DeleteFileParams) { const { studyId, path } = params; - const url = `/v1/studies/${studyId}/raw`; - - await client.delete(url, { params: { path } }); + await client.delete(`/v1/studies/${studyId}/raw`, { params: { path } }); } /** - * Reads an original raw file from a study with its metadata. + * Gets an original raw file from a study with its metadata. * - * @param studyId - Unique identifier of the study - * @param filePath - Path to the file within the study + * @param params - Parameters for getting the raw file and name + * @param params.studyId - Unique identifier of the study + * @param params.path - Path to the file within the study * @returns Promise containing the file data and metadata */ -export async function getRawFile( - studyId: string, - filePath: string, -): Promise { - const response = await client.get( +export async function getRawFile(params: GetRawFileParams): Promise { + const { studyId, path } = params; + + const { data, headers } = await client.get( `/v1/studies/${studyId}/raw/original-file`, { params: { - path: filePath, + path, }, responseType: "blob", }, ); - const contentDisposition = response.headers["content-disposition"]; - let filename = filePath.split("/").pop() || "file"; // fallback filename + // Get the original file name from the response Headers + const contentDisposition = headers["content-disposition"]; + let filename = path.split("/").pop() || "file"; // fallback filename if (contentDisposition) { const matches = /filename=([^;]+)/.exec(contentDisposition); + if (matches?.[1]) { filename = matches[1].replace(/"/g, "").trim(); } } return { - data: response.data, - filename: filename, + data, + filename, }; } diff --git a/webapp/src/services/api/studies/raw/types.ts b/webapp/src/services/api/studies/raw/types.ts index 43ff274183..13bece37f8 100644 --- a/webapp/src/services/api/studies/raw/types.ts +++ b/webapp/src/services/api/studies/raw/types.ts @@ -17,9 +17,10 @@ import type { StudyMetadata } from "../../../../common/types"; import { O } from "ts-toolbelt"; import { TableExportFormat } from "./constants"; +// Available export formats for matrix tables export type TTableExportFormat = O.UnionOf; -export interface DownloadMatrixParams { +export interface GetMatrixFileParams { studyId: StudyMetadata["id"]; path: string; format?: TTableExportFormat; @@ -27,10 +28,11 @@ export interface DownloadMatrixParams { index?: boolean; } -export interface ImportFileParams { +export interface UploadFileParams { studyId: StudyMetadata["id"]; path: string; file: File; + // Flag to indicate whether to create file and directories if missing createMissing?: boolean; onUploadProgress?: AxiosRequestConfig["onUploadProgress"]; } @@ -40,6 +42,10 @@ export interface DeleteFileParams { path: string; } +export interface GetRawFileParams { + studyId: string; + path: string; +} export interface RawFile { data: Blob; filename: string; From c6b223764f2df82eaf794afec2922ebdfe36aad7 Mon Sep 17 00:00:00 2001 From: hatim dinia Date: Fri, 20 Dec 2024 16:44:28 +0100 Subject: [PATCH 3/4] feat(ui-matrix): add raw file download option for matrices --- webapp/public/locales/en/main.json | 1 + webapp/public/locales/fr/main.json | 1 + .../components/common/buttons/DownloadMatrixButton.tsx | 8 +++++++- webapp/src/services/api/studies/raw/constants.ts | 1 + 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/webapp/public/locales/en/main.json b/webapp/public/locales/en/main.json index 250a75f2e6..7c6a13307a 100644 --- a/webapp/public/locales/en/main.json +++ b/webapp/public/locales/en/main.json @@ -44,6 +44,7 @@ "global.date": "Date", "global.general": "General", "global.files": "Files", + "global.rawFile": "Raw file", "global.none": "None", "global.upload": "Upload", "global.key": "Key", diff --git a/webapp/public/locales/fr/main.json b/webapp/public/locales/fr/main.json index 98d6ee71b9..395ed91e7e 100644 --- a/webapp/public/locales/fr/main.json +++ b/webapp/public/locales/fr/main.json @@ -44,6 +44,7 @@ "global.date": "Date", "global.general": "Général", "global.files": "Fichiers", + "global.rawFile": "Fichier brut", "global.none": "Aucun", "global.upload": "Charger", "global.key": "Clé", diff --git a/webapp/src/components/common/buttons/DownloadMatrixButton.tsx b/webapp/src/components/common/buttons/DownloadMatrixButton.tsx index 0f27f75eef..30e4db88b0 100644 --- a/webapp/src/components/common/buttons/DownloadMatrixButton.tsx +++ b/webapp/src/components/common/buttons/DownloadMatrixButton.tsx @@ -12,7 +12,7 @@ * This file is part of the Antares project. */ -import { getMatrixFile } from "../../../services/api/studies/raw"; +import { getMatrixFile, getRawFile } from "../../../services/api/studies/raw"; import { downloadFile } from "../../../utils/fileUtils"; import { StudyMetadata } from "../../../common/types"; import { useTranslation } from "react-i18next"; @@ -38,6 +38,7 @@ function DownloadMatrixButton(props: DownloadMatrixButtonProps) { }, { label: "TSV", value: "tsv" }, { label: "XLSX", value: "xlsx" }, + { label: `${t("global.rawFile")}`, value: "raw" }, ]; //////////////////////////////////////////////////////////////// @@ -49,6 +50,11 @@ function DownloadMatrixButton(props: DownloadMatrixButtonProps) { return; } + if (format === "raw") { + const { data, filename } = await getRawFile({ studyId, path }); + return downloadFile(data, filename); + } + const isXlsx = format === "xlsx"; const matrixFile = await getMatrixFile({ diff --git a/webapp/src/services/api/studies/raw/constants.ts b/webapp/src/services/api/studies/raw/constants.ts index 9481e0557c..d4a701d611 100644 --- a/webapp/src/services/api/studies/raw/constants.ts +++ b/webapp/src/services/api/studies/raw/constants.ts @@ -18,4 +18,5 @@ export const TableExportFormat = { TSV: "tsv", CSV: "csv", CSV_SEMICOLON: "csv (semicolon)", + RAW: "raw", } as const; From 98332b8f57e1f6a031565eda5a3a8b2c84d6e712 Mon Sep 17 00:00:00 2001 From: hatim dinia Date: Fri, 20 Dec 2024 16:58:32 +0100 Subject: [PATCH 4/4] refactor(ui-raw): update `getRawFile` to return File instance instead of custom RawFile type --- .../App/Singlestudy/explore/Debug/Data/Json.tsx | 4 ++-- .../App/Singlestudy/explore/Debug/Data/Text.tsx | 4 ++-- .../Singlestudy/explore/Debug/Data/Unsupported.tsx | 4 ++-- .../common/buttons/DownloadMatrixButton.tsx | 10 ++++++---- webapp/src/services/api/studies/raw/constants.ts | 1 - webapp/src/services/api/studies/raw/index.ts | 13 ++++++------- webapp/src/services/api/studies/raw/types.ts | 4 ---- 7 files changed, 18 insertions(+), 22 deletions(-) diff --git a/webapp/src/components/App/Singlestudy/explore/Debug/Data/Json.tsx b/webapp/src/components/App/Singlestudy/explore/Debug/Data/Json.tsx index 6f04cb26f1..5df699950b 100644 --- a/webapp/src/components/App/Singlestudy/explore/Debug/Data/Json.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Debug/Data/Json.tsx @@ -42,8 +42,8 @@ function Json({ filePath, filename, studyId, canEdit }: DataCompProps) { //////////////////////////////////////////////////////////////// const handleDownload = async () => { - const { data, filename } = await getRawFile({ studyId, path: filePath }); - downloadFile(data, filename); + const file = await getRawFile({ studyId, path: filePath }); + downloadFile(file, file.name); }; const handleSave: JSONEditorProps["onSave"] = (json) => { diff --git a/webapp/src/components/App/Singlestudy/explore/Debug/Data/Text.tsx b/webapp/src/components/App/Singlestudy/explore/Debug/Data/Text.tsx index 3ea722e4d9..1ee8be74c0 100644 --- a/webapp/src/components/App/Singlestudy/explore/Debug/Data/Text.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Debug/Data/Text.tsx @@ -92,8 +92,8 @@ function Text({ //////////////////////////////////////////////////////////////// const handleDownload = async () => { - const { data, filename } = await getRawFile({ studyId, path: filePath }); - downloadFile(data, filename); + const file = await getRawFile({ studyId, path: filePath }); + downloadFile(file, file.name); }; const handleUploadSuccessful = () => { diff --git a/webapp/src/components/App/Singlestudy/explore/Debug/Data/Unsupported.tsx b/webapp/src/components/App/Singlestudy/explore/Debug/Data/Unsupported.tsx index c56c9f89d2..81307a4edf 100644 --- a/webapp/src/components/App/Singlestudy/explore/Debug/Data/Unsupported.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Debug/Data/Unsupported.tsx @@ -30,8 +30,8 @@ function Unsupported({ studyId, filePath, filename, canEdit }: DataCompProps) { //////////////////////////////////////////////////////////////// const handleDownload = async () => { - const { data, filename } = await getRawFile({ studyId, path: filePath }); - downloadFile(data, filename); + const file = await getRawFile({ studyId, path: filePath }); + downloadFile(file, file.name); }; //////////////////////////////////////////////////////////////// diff --git a/webapp/src/components/common/buttons/DownloadMatrixButton.tsx b/webapp/src/components/common/buttons/DownloadMatrixButton.tsx index 30e4db88b0..b50e3e81da 100644 --- a/webapp/src/components/common/buttons/DownloadMatrixButton.tsx +++ b/webapp/src/components/common/buttons/DownloadMatrixButton.tsx @@ -19,6 +19,8 @@ import { useTranslation } from "react-i18next"; import DownloadButton from "./DownloadButton"; import type { TTableExportFormat } from "@/services/api/studies/raw/types"; +type ExportFormat = TTableExportFormat | "raw"; + export interface DownloadMatrixButtonProps { studyId: StudyMetadata["id"]; path: string; @@ -30,7 +32,7 @@ function DownloadMatrixButton(props: DownloadMatrixButtonProps) { const { t } = useTranslation(); const { studyId, path, disabled, label = t("global.export") } = props; - const options: Array<{ label: string; value: TTableExportFormat }> = [ + const options: Array<{ label: string; value: ExportFormat }> = [ { label: "CSV", value: "csv" }, { label: `CSV (${t("global.semicolon").toLowerCase()})`, @@ -45,14 +47,14 @@ function DownloadMatrixButton(props: DownloadMatrixButtonProps) { // Event Handlers //////////////////////////////////////////////////////////////// - const handleDownload = async (format: TTableExportFormat) => { + const handleDownload = async (format: ExportFormat) => { if (!path) { return; } if (format === "raw") { - const { data, filename } = await getRawFile({ studyId, path }); - return downloadFile(data, filename); + const file = await getRawFile({ studyId, path }); + return downloadFile(file, file.name); } const isXlsx = format === "xlsx"; diff --git a/webapp/src/services/api/studies/raw/constants.ts b/webapp/src/services/api/studies/raw/constants.ts index d4a701d611..9481e0557c 100644 --- a/webapp/src/services/api/studies/raw/constants.ts +++ b/webapp/src/services/api/studies/raw/constants.ts @@ -18,5 +18,4 @@ export const TableExportFormat = { TSV: "tsv", CSV: "csv", CSV_SEMICOLON: "csv (semicolon)", - RAW: "raw", } as const; diff --git a/webapp/src/services/api/studies/raw/index.ts b/webapp/src/services/api/studies/raw/index.ts index 4e2938c4a6..663877aafc 100644 --- a/webapp/src/services/api/studies/raw/index.ts +++ b/webapp/src/services/api/studies/raw/index.ts @@ -17,7 +17,6 @@ import type { DeleteFileParams, GetMatrixFileParams, GetRawFileParams, - RawFile, UploadFileParams, } from "./types"; @@ -94,10 +93,10 @@ export async function deleteFile(params: DeleteFileParams) { * @param params.path - Path to the file within the study * @returns Promise containing the file data and metadata */ -export async function getRawFile(params: GetRawFileParams): Promise { +export async function getRawFile(params: GetRawFileParams) { const { studyId, path } = params; - const { data, headers } = await client.get( + const { data, headers } = await client.get( `/v1/studies/${studyId}/raw/original-file`, { params: { @@ -119,8 +118,8 @@ export async function getRawFile(params: GetRawFileParams): Promise { } } - return { - data, - filename, - }; + return new File([data], filename, { + type: data.type, // Preserve the MIME type from the Blob + lastModified: new Date().getTime(), + }); } diff --git a/webapp/src/services/api/studies/raw/types.ts b/webapp/src/services/api/studies/raw/types.ts index 13bece37f8..c74958c376 100644 --- a/webapp/src/services/api/studies/raw/types.ts +++ b/webapp/src/services/api/studies/raw/types.ts @@ -46,7 +46,3 @@ export interface GetRawFileParams { studyId: string; path: string; } -export interface RawFile { - data: Blob; - filename: string; -}