Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
1c99ced
endpoint does not work, but new endpoint for multiple files added.
tcnichol Oct 13, 2023
e535ffb
iterate through files, upload all of them
tcnichol Oct 14, 2023
a54fcc9
tests sort of work, need to be cleaned up
tcnichol Oct 16, 2023
e16265b
multiple property now in, new views, does not upload yet
tcnichol Oct 19, 2023
48f9cdb
multiple select selects shows multiple files when they are selected t…
tcnichol Oct 19, 2023
5807033
logging files when we click finish
tcnichol Oct 19, 2023
6331c11
codegen, adding multiple files endpoint
tcnichol Oct 19, 2023
228a203
getting a 422 for uploading multiple files
tcnichol Oct 19, 2023
3c95835
does not work
tcnichol Oct 27, 2023
deca8b3
adding print statement back in
tcnichol Oct 27, 2023
f81b71e
adding print statement back in
tcnichol Oct 27, 2023
1c98d7e
some changes to how formdata is handled
tcnichol Nov 2, 2023
66b2237
clean up
longshuicy Nov 3, 2023
962ab88
734 view and modify list of metadata definitions in UI (#758)
longshuicy Oct 12, 2023
8333e57
stretched icon when extractor description long (#789)
tcnichol Oct 12, 2023
401baab
792 clear previous log before switching extraction logs (#793)
longshuicy Oct 19, 2023
3ae8b9a
Pagination for files & folder page under dataset (#797)
ddey2 Oct 19, 2023
5425dc5
Implement wordcloud visualization (#786)
ddey2 Oct 20, 2023
10a08a4
message if no datasets exist, button link to create (#767)
tcnichol Oct 20, 2023
f2b9311
Updated the labels for Share (#798)
ddey2 Oct 20, 2023
8e2a88c
778 page to display each metadata definition (#801)
longshuicy Oct 23, 2023
6369831
788 duplicated extractor registration when extractor version updated …
tcnichol Oct 23, 2023
e6cbd89
701 improve file version selection (#743)
tcnichol Oct 23, 2023
e38e81d
add swagger to traefik (#805)
longshuicy Oct 23, 2023
5c57eb4
Change hostname to edu (#813)
longshuicy Oct 31, 2023
82886e5
basic message for no metadata definitions (#766)
tcnichol Oct 31, 2023
cd18d65
endpoint logic is wrong; also fix pytest
longshuicy Nov 3, 2023
9397537
write better pytest
longshuicy Nov 3, 2023
88e2446
rewrite the structure of select file and construct the form in the fu…
longshuicy Nov 3, 2023
2a9eebc
back to bad request again
longshuicy Nov 3, 2023
622df73
everything working except redirect
longshuicy Nov 3, 2023
5ab6821
go to the first files route
longshuicy Nov 3, 2023
7d8d0ae
formatting
tcnichol Nov 3, 2023
750086e
codegen
tcnichol Nov 3, 2023
522aaea
fixing delete of files in tests
tcnichol Nov 3, 2023
7ed1f9a
fixing package lock
tcnichol Nov 3, 2023
b7248bd
Merge branch 'main' into 787-enable-uploading-multiple-files-at-once
longshuicy Nov 6, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 51 additions & 4 deletions backend/app/routers/datasets.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,21 @@
from typing import List, Optional

from beanie import PydanticObjectId
from beanie.operators import Or
from beanie.odm.operators.update.general import Inc
from beanie.operators import Or
from bson import ObjectId
from bson import json_util
from elasticsearch import Elasticsearch
from fastapi import (
APIRouter,
HTTPException,
Depends,
Security,
File,
UploadFile,
Request,
)
from fastapi.responses import StreamingResponse
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from fastapi.security import HTTPBearer
from minio import Minio
from pika.adapters.blocking_connection import BlockingChannel
from rocrate.model.person import Person
Expand All @@ -51,8 +50,8 @@
from app.models.folders import FolderOut, FolderIn, FolderDB, FolderDBViewList
from app.models.metadata import MetadataDB
from app.models.pyobjectid import PyObjectId
from app.models.users import UserOut
from app.models.thumbnails import ThumbnailDB
from app.models.users import UserOut
from app.rabbitmq.listeners import submit_dataset_job
from app.routers.files import add_file_entry, remove_file_entry
from app.search.connect import (
Expand Down Expand Up @@ -462,6 +461,54 @@ async def save_file(
raise HTTPException(status_code=404, detail=f"Dataset {dataset_id} not found")


@router.post("/{dataset_id}/filesMultiple", response_model=List[FileOut])
async def save_files(
dataset_id: str,
files: List[UploadFile],
folder_id: Optional[str] = None,
user=Depends(get_current_user),
fs: Minio = Depends(dependencies.get_fs),
es=Depends(dependencies.get_elasticsearchclient),
rabbitmq_client: BlockingChannel = Depends(dependencies.get_rabbitmq),
allow: bool = Depends(Authorization("uploader")),
):
if (dataset := await DatasetDB.get(PydanticObjectId(dataset_id))) is not None:
files_added = []
for file in files:
if user is None:
raise HTTPException(
status_code=401,
detail=f"User not found. Session might have expired.",
)

new_file = FileDB(name=file.filename, creator=user, dataset_id=dataset.id)

if folder_id is not None:
if (
folder := await FolderDB.get(PydanticObjectId(folder_id))
) is not None:
new_file.folder_id = folder.id
else:
raise HTTPException(
status_code=404, detail=f"Folder {folder_id} not found"
)

await add_file_entry(
new_file,
user,
fs,
es,
rabbitmq_client,
file.file,
content_type=file.content_type,
)
files_added.append(new_file.dict())
return files_added

else:
raise HTTPException(status_code=404, detail=f"Dataset {dataset_id} not found")


@router.post("/createFromZip", response_model=DatasetOut)
async def create_dataset_from_zip(
user=Depends(get_current_user),
Expand Down
12 changes: 10 additions & 2 deletions backend/app/tests/test_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,26 @@
from fastapi.testclient import TestClient

from app.config import settings
from app.tests.utils import create_dataset, upload_file, generate_png
from app.tests.utils import create_dataset, upload_file, upload_files, generate_png


def test_create_and_delete(client: TestClient, headers: dict):
dataset_id = create_dataset(client, headers).get("id")
response = upload_file(client, headers, dataset_id)
file = response
file_id = response["id"]
# DELETE FILE
response = client.delete(f"{settings.API_V2_STR}/files/{file_id}", headers=headers)
assert response.status_code == 200

response_multiple = upload_files(client, headers, dataset_id)
file_ids = [f["id"] for f in response_multiple]
# DELETE MULTIPLE FILES
for file_id in file_ids:
response = client.delete(
f"{settings.API_V2_STR}/files/{file_id}", headers=headers
)
assert response.status_code == 200


def test_get_one(client: TestClient, headers: dict):
temp_name = "testing file.txt"
Expand Down
41 changes: 37 additions & 4 deletions backend/app/tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,11 @@
"description": "a dataset is a container of files and metadata",
}

filename_example = "test_upload.csv"
file_content_example = "year,location,count\n2023,Atlanta,4"
filename_example_1 = "test_upload1.csv"
file_content_example_1 = "year,location,count\n2023,Atlanta,4"

filename_example_2 = "test_upload2.csv"
file_content_example_2 = "year,location,count\n2022,Seattle,2"

listener_v2_example = {
"name": "test.listener_v2_example",
Expand Down Expand Up @@ -143,8 +146,8 @@ def upload_file(
client: TestClient,
headers: dict,
dataset_id: str,
filename=filename_example,
content=file_content_example,
filename=filename_example_1,
content=file_content_example_1,
):
"""Uploads a dummy file (optionally with custom name/content) to a dataset and returns the JSON."""
with open(filename, "w") as tempf:
Expand All @@ -161,6 +164,36 @@ def upload_file(
return response.json()


def upload_files(
client: TestClient,
headers: dict,
dataset_id: str,
filenames=[filename_example_1, filename_example_2],
file_contents=[file_content_example_1, file_content_example_2],
):
"""Uploads a dummy file (optionally with custom name/content) to a dataset and returns the JSON."""
upload_files = []
for i in range(0, len(filenames)):
with open(filenames[i], "w") as tempf:
tempf.write(file_contents[i])
upload_files.append(filenames[i])
files = [
("files", open(filename_example_1, "rb")),
("files", open(filename_example_2, "rb")),
]
response = client.post(
f"{settings.API_V2_STR}/datasets/{dataset_id}/filesMultiple",
headers=headers,
files=files,
)
for f in upload_files:
os.remove(f)
assert response.status_code == 200
json_response = response.json()
assert len(json_response) == 2
return response.json()


def create_folder(
client: TestClient,
headers: dict,
Expand Down
47 changes: 47 additions & 0 deletions frontend/src/actions/file.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,42 @@ export function createFile(selectedDatasetId, folderId, selectedFile) {
};
}

export const CREATE_FILES = "CREATE_FILES";

export function createFiles(selectedDatasetId, selectedFiles, folderId) {
return (dispatch) => {
let formData = new FormData();
let tmp = [];
if (selectedFiles.length > 0) {
for (let i = 0; i < selectedFiles.length; i++) {
tmp.push(selectedFiles[i]);
}
}
formData["files"] = tmp;

return V2.DatasetsService.saveFilesApiV2DatasetsDatasetIdFilesMultiplePost(
selectedDatasetId,
formData,
folderId
)
.then((files) => {
dispatch({
type: CREATE_FILES,
files: files,
receivedAt: Date.now(),
});
})
.catch((reason) => {
dispatch(
handleErrors(
reason,
createFiles(selectedDatasetId, selectedFiles, folderId)
)
);
});
};
}

export const RESET_CREATE_FILE = "RESET_CREATE_FILE";

export function resetFileCreated() {
Expand All @@ -157,6 +193,17 @@ export function resetFileCreated() {
};
}

export const RESET_CREATE_FILES = "RESET_CREATE_FILES";

export function resetFilesCreated() {
return (dispatch) => {
dispatch({
type: RESET_CREATE_FILES,
receivedAt: Date.now(),
});
};
}

export const UPDATE_FILE = "UPDATE_FILE";

export function updateFile(selectedFile, fileId) {
Expand Down
30 changes: 30 additions & 0 deletions frontend/src/components/datasets/NewMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import React from "react";
import { useSelector } from "react-redux";
import { RootState } from "../../types/data";
import { UploadFile } from "../files/UploadFile";
import { UploadFileMultiple } from "../files/UploadFileMultiple";
import UploadIcon from "@mui/icons-material/Upload";
import { Folder } from "@material-ui/icons";

Expand All @@ -29,6 +30,8 @@ export const NewMenu = (props: ActionsMenuProps): JSX.Element => {

const [anchorEl, setAnchorEl] = React.useState<Element | null>(null);
const [createFileOpen, setCreateFileOpen] = React.useState<boolean>(false);
const [createMultipleFileOpen, setCreateMultipleFileOpen] =
React.useState<boolean>(false);
const [newFolder, setNewFolder] = React.useState<boolean>(false);

const handleCloseNewFolder = () => {
Expand Down Expand Up @@ -58,6 +61,22 @@ export const NewMenu = (props: ActionsMenuProps): JSX.Element => {
folderId={folderId}
/>
</Dialog>
<Dialog
open={createMultipleFileOpen}
onClose={() => {
setCreateMultipleFileOpen(false);
}}
fullWidth={true}
maxWidth="lg"
aria-labelledby="form-dialog"
>
<UploadFileMultiple
selectedDatasetId={datasetId}
selectedDatasetName={about.name}
setCreateMultipleFileOpen={setCreateMultipleFileOpen}
folderId={folderId}
/>
</Dialog>

<CreateFolder
datasetId={datasetId}
Expand Down Expand Up @@ -92,6 +111,17 @@ export const NewMenu = (props: ActionsMenuProps): JSX.Element => {
</ListItemIcon>
<ListItemText>Upload File</ListItemText>
</MenuItem>
<MenuItem
onClick={() => {
setCreateMultipleFileOpen(true);
handleOptionClose();
}}
>
<ListItemIcon>
<UploadIcon fontSize="small" />
</ListItemIcon>
<ListItemText>Upload Multiple Files</ListItemText>
</MenuItem>
<MenuItem
onClick={() => {
setNewFolder(true);
Expand Down
35 changes: 35 additions & 0 deletions frontend/src/components/files/UploadFileInputMultiple.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from "react";

import { Box, Input } from "@mui/material";

type UploadFileMultipleModalProps = {
setSelectedFiles: any;
};

// https://stackoverflow.com/questions/68213700/react-js-upload-multiple-files
export const UploadFileInputMultiple: React.FC<UploadFileMultipleModalProps> = (
props: UploadFileMultipleModalProps
) => {
const { setSelectedFiles } = props;

const handleMultipleFileChange = (event) => {
// let tempFormData = new FormData();
// for (let i = 0; i < event.target.files.length; i++) {
// tempFormData.append("files", event.target.files[i]);
// }
setSelectedFiles(event.target.files);
};

return (
<Box sx={{ width: "100%", padding: "1em 0em" }}>
<Input
id="file-input-multiple"
type="file"
inputProps={{ multiple: true }}
onChange={handleMultipleFileChange}
sx={{ width: "100%" }}
disableUnderline={true}
/>
</Box>
);
};
Loading