Skip to content

Commit

Permalink
refactor(ui-raw): update api methods
Browse files Browse the repository at this point in the history
  • Loading branch information
hdinia committed Dec 19, 2024
1 parent 77c0cf9 commit 04e415e
Show file tree
Hide file tree
Showing 9 changed files with 98 additions and 87 deletions.
26 changes: 8 additions & 18 deletions webapp/src/components/App/Singlestudy/explore/Debug/Data/Json.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,31 +31,21 @@ function Json({ filePath, filename, studyId, canEdit }: DataCompProps) {
const { enqueueSnackbar } = useSnackbar();
const [currentJson, setCurrentJson] = useState<JSONEditorProps["json"]>();

const fileRes = usePromiseWithSnackbarError(
const jsonRes = usePromiseWithSnackbarError(
() => getStudyData(studyId, filePath, -1),
{
errorMessage: t("studies.error.retrieveData"),
deps: [studyId, filePath],
},
);

const rawFileRes = usePromiseWithSnackbarError(
() => getRawFile(studyId, filePath),
{
errorMessage: t("studies.error.retrieveData"),
deps: [studyId, filePath],
},
);

useEffect(() => {
setCurrentJson(fileRes.data);
}, [fileRes.data]);
setCurrentJson(jsonRes.data);
}, [jsonRes.data]);

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);
};

////////////////////////////////////////////////////////////////
Expand All @@ -75,7 +65,7 @@ function Json({ filePath, filename, studyId, canEdit }: DataCompProps) {
};

const handleUploadSuccessful = () => {
fileRes.reload();
jsonRes.reload();
};

////////////////////////////////////////////////////////////////
Expand All @@ -84,7 +74,7 @@ function Json({ filePath, filename, studyId, canEdit }: DataCompProps) {

return (
<UsePromiseCond
response={fileRes}
response={jsonRes}
ifFulfilled={(json) => (
<Flex>
<Menubar>
Expand Down
22 changes: 6 additions & 16 deletions webapp/src/components/App/Singlestudy/explore/Debug/Data/Text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ function Text({
const { t } = useTranslation();
const theme = useTheme();

const fileRes = usePromiseWithSnackbarError(
const textRes = usePromiseWithSnackbarError(
() =>
getStudyData<string>(studyId, filePath).then((text) =>
parseContent(text, { filePath, fileType }),
Expand All @@ -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);
}
const handleDownload = async () => {
const { data, filename } = await getRawFile({ studyId, path: filePath });
downloadFile(data, filename);
};

////////////////////////////////////////////////////////////////
// Event Handlers
////////////////////////////////////////////////////////////////

const handleUploadSuccessful = () => {
fileRes.reload();
textRes.reload();
};

////////////////////////////////////////////////////////////////
Expand All @@ -116,7 +106,7 @@ function Text({

return (
<UsePromiseCond
response={fileRes}
response={textRes}
ifFulfilled={(text) => (
<Flex>
<Menubar>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};

////////////////////////////////////////////////////////////////
Expand Down
5 changes: 3 additions & 2 deletions webapp/src/components/common/Matrix/hooks/useMatrix/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,15 +166,15 @@ 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();

await act(async () => {
await hook.result.current.handleUpload(mockFile);
});

expect(rawStudy.importFile).toHaveBeenCalledWith({
expect(rawStudy.uploadFile).toHaveBeenCalledWith({
file: mockFile,
studyId: DATA.studyId,
path: DATA.url,
Expand Down
6 changes: 3 additions & 3 deletions webapp/src/components/common/buttons/DownloadMatrixButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -51,7 +51,7 @@ function DownloadMatrixButton(props: DownloadMatrixButtonProps) {

const isXlsx = format === "xlsx";

const res = await downloadMatrix({
const matrixFile = await getMatrixFile({
studyId,
path,
format,
Expand All @@ -62,7 +62,7 @@ function DownloadMatrixButton(props: DownloadMatrixButtonProps) {
const extension = format === "csv (semicolon)" ? "csv" : format;

return downloadFile(
res,
matrixFile,
`matrix_${studyId}_${path.replace("/", "_")}.${extension}`,
);
};
Expand Down
4 changes: 2 additions & 2 deletions webapp/src/components/common/buttons/UploadFileButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<ValidateResult>;
Expand Down Expand Up @@ -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,
Expand Down
91 changes: 63 additions & 28 deletions webapp/src/services/api/studies/raw/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Blob>(url, {
params: queryParams,
responseType: "blob",
});

const { data } = await client.get<Blob>(
`/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<void>(url, body, {
await client.putForm(`/v1/studies/${studyId}/raw`, body, {
params: {
...queryParams,
create_missing: queryParams.createMissing,
Expand All @@ -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<void>(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<RawFile> {
const response = await client.get(
export async function getRawFile(params: GetRawFileParams): Promise<RawFile> {
const { studyId, path } = params;

const { data, headers } = await client.get<RawFile["data"]>(
`/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,
};
}
10 changes: 8 additions & 2 deletions webapp/src/services/api/studies/raw/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,22 @@ 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<typeof TableExportFormat>;

export interface DownloadMatrixParams {
export interface GetMatrixFileParams {
studyId: StudyMetadata["id"];
path: string;
format?: TTableExportFormat;
header?: boolean;
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"];
}
Expand All @@ -40,6 +42,10 @@ export interface DeleteFileParams {
path: string;
}

export interface GetRawFileParams {
studyId: string;
path: string;
}
export interface RawFile {
data: Blob;
filename: string;
Expand Down

0 comments on commit 04e415e

Please sign in to comment.