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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

806 batch upload #811

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 10 additions & 12 deletions frontend/app/data/@add/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, Di
import { Input } from "@/components/ui/input";
import {
endLoading,
saveDataFileAsync,
selectCID,
saveDataFilesAsync,
selectDataFileError,
selectDataFileIsLoading,
selectWalletAddress,
Expand All @@ -30,12 +29,11 @@ export default function DataFileForm() {
const isLoading = useSelector(selectDataFileIsLoading);
const walletAddress = useSelector(selectWalletAddress);

const [file, setFile] = useState<File | null>(null);
const [files, setFiles] = useState<File[]>([]);

const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const uploadedFile = e.target.files && e.target.files[0];
if (uploadedFile) {
setFile(uploadedFile);
if (e.target.files) {
setFiles(Array.from(e.target.files));
}
};

Expand All @@ -46,8 +44,8 @@ export default function DataFileForm() {

const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (file === null) {
dispatch(setError("Please select a file"));
if (files.length === 0) {
dispatch(setError("Please select at least one file"));
return;
}

Expand All @@ -56,10 +54,10 @@ export default function DataFileForm() {
const metadata = { walletAddress };

try {
await dispatch(saveDataFileAsync({ file, metadata, handleSuccess }));
dispatch(endLoading());
await dispatch(saveDataFilesAsync({ files, metadata, handleSuccess }));
} catch (error) {
dispatch(setError("Error uploading file"));
dispatch(setError("Error uploading files"));
} finally {
dispatch(endLoading());
}
};
Expand All @@ -74,7 +72,7 @@ export default function DataFileForm() {
<DialogTitle>Add Data</DialogTitle>
<DialogDescription>
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
<Input type="file" onChange={handleFileChange} />
<Input type="file" onChange={handleFileChange} multiple/>
<Button type="submit">{isLoading ? "Submitting..." : "Submit"}</Button>
{errorMessage && <Alert variant="destructive">{errorMessage}</Alert>}
</form>
Expand Down
59 changes: 31 additions & 28 deletions frontend/lib/redux/slices/dataFileAddSlice/actions.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,39 @@
import backendUrl from "lib/backendUrl";

export const saveDataFileToServer = async (
file: File,
metadata: { [key: string]: any }
): Promise<{ filename: string, cid: string }> => {
const formData = new FormData();
formData.append('file', file, file.name);
formData.append('filename', file.name)
files: File[] | FileList,
metadata: { [key: string]: any }
): Promise<{ filename: string, cid: string }[]> => {
const formData = new FormData();

for (const key in metadata) {
formData.append(key, metadata[key]);
}
const filesArray = Array.isArray(files) ? files : Array.from(files);

const response = await fetch(`${backendUrl()}/datafiles`, {
method: 'POST',
body: formData,
})
filesArray.forEach((file) => {
formData.append('files', file, file.name);
});

if (!response.ok) {
let errorMsg = 'An error occurred while uploading the data file'
try {
const errorResult = await response.json()
errorMsg = errorResult.message || errorMsg;
} catch (e) {
// Parsing JSON failed, retain the default error message.
}
console.log('errorMsg', errorMsg)
throw new Error(errorMsg)
}
Object.keys(metadata).forEach(key => {
formData.append(key, metadata[key]);
});

const response = await fetch(`${backendUrl()}/datafiles`, {
method: 'POST',
body: formData,
});

const result = await response.json()
console.log('result', result)
return result;
if (!response.ok) {
let errorMsg = 'An error occurred while uploading the data files';
try {
const errorResult = await response.json();
errorMsg = errorResult.message || errorMsg;
} catch (e) {
// Parsing JSON failed, retain the default error message.
}
console.error('errorMsg', errorMsg);
throw new Error(errorMsg);
}


const result = await response.json();
console.log('result', result);
return result;
};
88 changes: 51 additions & 37 deletions frontend/lib/redux/slices/dataFileAddSlice/dataSlice.ts
Original file line number Diff line number Diff line change
@@ -1,74 +1,88 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { createSlice, PayloadAction } from '@reduxjs/toolkit';

import { saveDataFileAsync } from './thunks'
import { saveDataFilesAsync } from './thunks';

interface DataFileSliceState {
filename: string
cid: string // Content Identifier in IPFS
isLoading: boolean
error: string | null
isUploaded: boolean
filenames: string[];
cids: string[];
isLoading: boolean;
error: string | null;
isUploaded: boolean;
}

const initialState: DataFileSliceState = {
filename: '',
cid: '',
filenames: [],
cids: [],
isLoading: false,
error: null,
isUploaded: false,
}
};

export const dataFileAddSlice = createSlice({
name: 'dataFile',
initialState,
reducers: {
setFilenameDataSlice: (state, action: PayloadAction<string>) => {
state.filename = action.payload
addFilename: (state, action: PayloadAction<string>) => {
state.filenames.push(action.payload);
},
setCidDataSlice: (state, action: PayloadAction<string>) => {
state.cid = action.payload
addCid: (state, action: PayloadAction<string>) => {
state.cids.push(action.payload);
},
setDataFileError: (state, action: PayloadAction<string | null>) => {
state.error = action.payload
state.error = action.payload;
},
startFileUploadDataSlice: (state) => {
state.isLoading = true
state.isLoading = true;
},
endFileUploadDataSlice: (state) => {
state.isLoading = false
state.isLoading = false;
},
setIsUploadedDataSlice: (state, action: PayloadAction<boolean>) => {
state.isUploaded = action.payload
state.isUploaded = action.payload;
},
resetDataFileSlice: (state) => {
state.filenames = [];
state.cids = [];
state.isLoading = false;
state.error = null;
state.isUploaded = false;
},
},
extraReducers: (builder) => {
builder
.addCase(saveDataFileAsync.pending, (state) => {
state.isLoading = true
state.error = null
.addCase(saveDataFilesAsync.pending, (state) => {
state.isLoading = true;
state.error = null;
})
.addCase(saveDataFileAsync.fulfilled, (state, action) => {
state.isLoading = false
if (action.payload) {
state.cid = action.payload.cid
state.filename = action.payload.filename
.addCase(saveDataFilesAsync.fulfilled, (state, action: PayloadAction<{ filename: string; cid: string }[]>) => {
state.isLoading = false;
if (action.payload && Array.isArray(action.payload) && action.payload.length > 0) {
action.payload.forEach(fileResponse => {
state.cids.push(fileResponse.cid);
state.filenames.push(fileResponse.filename);
});
state.isUploaded = true;
} else {
state.error = 'No files were uploaded successfully.';
state.isUploaded = false;
}
state.isUploaded = true
})
.addCase(saveDataFileAsync.rejected, (state, action) => {
state.isLoading = false
state.error = action.error.message || 'An error occurred while saving data file.'
})
}
})
.addCase(saveDataFilesAsync.rejected, (state, action) => {
state.isLoading = false;
state.error = action.error.message || 'An error occurred while saving data files.';
state.isUploaded = false;
});
},
});

export const {
setFilenameDataSlice,
setCidDataSlice,
addFilename,
addCid,
setDataFileError,
startFileUploadDataSlice,
endFileUploadDataSlice,
setIsUploadedDataSlice,
} = dataFileAddSlice.actions
resetDataFileSlice,
} = dataFileAddSlice.actions;

export default dataFileAddSlice.reducer
export default dataFileAddSlice.reducer;
4 changes: 2 additions & 2 deletions frontend/lib/redux/slices/dataFileAddSlice/selectors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ReduxState } from '@/lib/redux'

export const selectFilename = (state: ReduxState) => state.dataFileAdd.filename
export const selectCID = (state: ReduxState) => state.dataFileAdd.cid
export const selectFilenames = (state: ReduxState) => state.dataFileAdd.filenames
export const selectCIDs = (state: ReduxState) => state.dataFileAdd.cids
export const selectDataFileError = (state: ReduxState) => state.dataFileAdd.error
export const selectDataFileIsLoading = (state: ReduxState) => state.dataFileAdd.isLoading
58 changes: 36 additions & 22 deletions frontend/lib/redux/slices/dataFileAddSlice/thunks.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,46 @@
import { createAppAsyncThunk } from '@/lib/redux/createAppAsyncThunk'
import { createAsyncThunk } from '@reduxjs/toolkit';

import { saveDataFileToServer } from './actions'
import { setCidDataSlice, setDataFileError,setFilenameDataSlice } from './dataSlice'
import { saveDataFileToServer } from './actions';
import { addCid, addFilename, setDataFileError } from './dataSlice';

interface DataFilePayload {
file: File,
metadata: { [key: string]: any }
files: File[],
metadata: { [key: string]: any },
handleSuccess: () => void
}

export const saveDataFileAsync = createAppAsyncThunk(
'dataFile/saveDataFile',
async ({ file, metadata, handleSuccess }: DataFilePayload, { dispatch }) => {
interface FileResponse {
filename: string;
cid: string;
}

interface ServerResponse {
cids: string[];
}

export const saveDataFilesAsync = createAsyncThunk<FileResponse[], DataFilePayload>(
'dataFile/saveDataFiles',
async ({ files, metadata, handleSuccess }, { dispatch }) => {
try {
const response = await saveDataFileToServer(file, metadata);
console.log("Response:", response)
if (response.cid) {
handleSuccess()
} else {
dispatch(setDataFileError('Failed to save data file.'))
}
return response;
} catch (error: unknown) {
const errorMessage = typeof error === 'object' && error !== null && 'message' in error
? (error as { message?: string }).message
: 'An error occurred while saving data file.';
const serverResponse = await saveDataFileToServer(files, metadata);
console.log("Server Response:", serverResponse);

// @ts-ignore
const fileResponses: FileResponse[] = serverResponse.cids.map((cid: string, index: number) => {
return { cid: cid, filename: files[index].name };
});

dispatch(setDataFileError(errorMessage || 'An error occurred while saving data file.'));
fileResponses.forEach(fileResponse => {
dispatch(addCid(fileResponse.cid));
dispatch(addFilename(fileResponse.filename));
});

handleSuccess();
return fileResponses;
} catch (error: unknown) {
const errorMessage = (error as { message?: string }).message ?? 'An error occurred while saving data files.';
dispatch(setDataFileError(errorMessage));
throw error;
}
}
)
);