From 71387771772b35d48778631ddede21b9204b7b0d Mon Sep 17 00:00:00 2001 From: Will Kahn-Greene Date: Wed, 14 Aug 2024 16:11:31 -0400 Subject: [PATCH] bug-1909637: allow searching for files from regular and try symbol uploads Previously, you could sort of search for try uploads by searching for "try/" in the key. But you couldn't search for regular uploads. We recently changed the key data in the model, so "try/" no longer appears in the key which breaks this flimsy filtering method. This introduces a new upload_type filter field that lets you filter on nothing (""), try upload files ("try"), and regular upload files ("regular"). --- frontend/src/Files.js | 37 +++++++++++--- tecken/api/forms.py | 3 ++ tecken/api/views.py | 12 +++++ tecken/tests/test_api.py | 104 +++++++++++++++++++++++++++++++++++---- 4 files changed, 138 insertions(+), 18 deletions(-) diff --git a/frontend/src/Files.js b/frontend/src/Files.js index 76ea9fb9d..4a53cf8c2 100644 --- a/frontend/src/Files.js +++ b/frontend/src/Files.js @@ -201,6 +201,7 @@ class DisplayFiles extends React.PureComponent { const filter = this.props.filter; this.refs.key.value = filter.key || ""; this.refs.size.value = filter.size || ""; + this.refs.upload_type.value = filter.upload_type || ""; this.refs.created_at.value = filter.created_at || ""; this.refs.bucketName.value = filter.bucket_name || ""; } @@ -209,6 +210,7 @@ class DisplayFiles extends React.PureComponent { event.preventDefault(); const key = this.refs.key.value.trim(); const size = this.refs.size.value.trim(); + const upload_type = this.refs.upload_type.value.trim(); const created_at = this.refs.created_at.value.trim(); const bucketName = this.refs.bucketName.value.trim(); this.props.updateFilter({ @@ -217,6 +219,7 @@ class DisplayFiles extends React.PureComponent { size, created_at, bucket_name: bucketName, + upload_type: upload_type, }); }; @@ -224,6 +227,7 @@ class DisplayFiles extends React.PureComponent { this.refs.key.value = ""; this.refs.size.value = ""; this.refs.bucketName.value = ""; + this.refs.upload_type.value = ""; this.refs.created_at.value = ""; this.submitForm(event); }; @@ -238,6 +242,7 @@ class DisplayFiles extends React.PureComponent { Key Size Bucket + Upload type Uploaded Time to complete + @@ -282,6 +288,15 @@ class DisplayFiles extends React.PureComponent { style={{ width: 140 }} /> + + + + + {formatFileSize(file.size)} {file.bucket_name} + + {file.upload && file.upload.upload_type === "try" ? ( + + try + + ) : ( + + {file.upload.upload_type} + + )} + {file.upload ? ( )}{" "} - {file.upload && file.upload.try_symbols ? ( - - Try - - ) : null} {BooleanIcon(file.update)} {BooleanIcon(file.compressed)} diff --git a/tecken/api/forms.py b/tecken/api/forms.py index c466265be..2d4a8156f 100644 --- a/tecken/api/forms.py +++ b/tecken/api/forms.py @@ -209,6 +209,9 @@ class FileUploadsForm(UploadsForm): size = forms.CharField(required=False) created_at = forms.CharField(required=False) completed_at = forms.CharField(required=False) + upload_type = forms.ChoiceField( + choices=[("", ""), ("try", "try"), ("regular", "regular")], required=False + ) key = forms.CharField(required=False) update = forms.BooleanField(required=False) compressed = forms.BooleanField(required=False) diff --git a/tecken/api/views.py b/tecken/api/views.py index 76e4eec23..91b9094fd 100644 --- a/tecken/api/views.py +++ b/tecken/api/views.py @@ -387,16 +387,19 @@ def _upload_files_build_qs(request): form = forms.FileUploadsForm(request.GET) if not form.is_valid(): return http.JsonResponse({"errors": form.errors}, status=400) + qs = FileUpload.objects.all() for operator, value in form.cleaned_data["size"]: orm_operator = "size__{}".format(ORM_OPERATORS[operator]) qs = qs.filter(**{orm_operator: value}) + qs = filter_form_dates(qs, form, ("created_at", "completed_at")) if form.cleaned_data.get("key"): key_q = Q(key__icontains=form.cleaned_data["key"][0]) for other in form.cleaned_data["key"][1:]: key_q &= Q(key__icontains=other) qs = qs.filter(key_q) + include_bucket_names = [] for operator, bucket_name in form.cleaned_data["bucket_name"]: if operator == "!": @@ -405,6 +408,14 @@ def _upload_files_build_qs(request): include_bucket_names.append(bucket_name) if include_bucket_names: qs = qs.filter(bucket_name__in=include_bucket_names) + + if form.cleaned_data.get("upload_type", ""): + # NOTE(willkg): we have two upload types: try and regular. The try_symbols field + # is a boolean where try=True and regular=False, so we convert from a general + # upload types domain to the boolean domain of whether it's try upload or not. + # In the future, we may want to support other types by fixing the Upload model. + try_symbols = form.cleaned_data["upload_type"] == "try" + qs = qs.filter(upload__try_symbols=try_symbols) return qs @@ -455,6 +466,7 @@ def hydrate_upload(upload_id): upload = uploads[upload_id] uploads_cache[upload_id] = { "id": upload.id, + "upload_type": "try" if upload.try_symbols else "regular", "try_symbols": upload.try_symbols, "user": {"id": upload.user.id, "email": upload.user.email}, "created_at": upload.created_at, diff --git a/tecken/tests/test_api.py b/tecken/tests/test_api.py index 7a1f931c2..9e202812b 100644 --- a/tecken/tests/test_api.py +++ b/tecken/tests/test_api.py @@ -852,6 +852,62 @@ def test_upload_files(client, settings): assert [x["id"] for x in data["files"]] == [file_upload2.id] +@pytest.mark.django_db +def test_upload_files_filter_upload_type(client, settings): + url = reverse("api:upload_files") + + user = User.objects.create(username="user1", email="user1@example.com") + user.set_password("secret") + user.save() + assert client.login(username="user1", password="secret") + + permission = Permission.objects.get(codename="view_all_uploads") + user.user_permissions.add(permission) + + # Create upload data + regular_upload = Upload.objects.create(user=user, size=123_456) + regular_file = FileUpload.objects.create( + upload=regular_upload, + size=1234, + bucket_name="symbols-public", + key="bar.pdb/46A0ADB3F299A70B4C4C44205044422E1/bar.sym", + ) + + try_upload = Upload.objects.create(user=user, size=123_456, try_symbols=True) + try_file = FileUpload.objects.create( + upload=try_upload, + size=100, + bucket_name="symbols-public", + key="v1/libxul.so/A772CC9A3E852CF48965ED79FB65E3150/libxul.so.sym", + ) + + # Request all files--should return both files + response = client.get(url) + assert response.status_code == 200 + data = response.json() + assert data["files"] + all_ids = {regular_file.id, try_file.id} + assert {x["id"] for x in data["files"]} == all_ids + + # Request upload_type=""--should return both files + response = client.get(url, {"upload_type": ""}) + assert response.status_code == 200 + data = response.json() + assert {x["id"] for x in data["files"]} == all_ids + + # Request upload_type="try"--should return only try file + response = client.get(url, {"upload_type": "try"}) + assert response.status_code == 200 + data = response.json() + assert {x["id"] for x in data["files"]} == {try_file.id} + + # Request upload_type="regular"--should return only regular file + response = client.get(url, {"upload_type": "regular"}) + assert response.status_code == 200 + data = response.json() + assert {x["id"] for x in data["files"]} == {regular_file.id} + + @pytest.mark.django_db def test_upload_files_count(client): url = reverse("api:upload_files") @@ -863,14 +919,23 @@ def test_upload_files_count(client): assert client.login(username="user1", password="secret") # Create data - upload = Upload.objects.create(user=user, size=123_456) - file_upload_1 = FileUpload.objects.create( - upload=upload, + regular_upload = Upload.objects.create(user=user, size=123_456) + regular_file_upload = FileUpload.objects.create( + upload=regular_upload, size=1234, bucket_name="symbols-public", - key="v1/bar.pdb/46A0ADB3F299A70B4C4C44205044422E1/bar.sym", + key="bar.pdb/46A0ADB3F299A70B4C4C44205044422E1/bar.sym", + ) + + try_upload = Upload.objects.create(user=user, size=123_456, try_symbols=True) + try_file_upload = FileUpload.objects.create( + upload=try_upload, + size=50, + bucket_name="symbols-public", + key="libxul.so/9B6C6BD630483C5F453471EE0EEEB09A0/libxul.so.sym", ) - file_upload_2 = FileUpload.objects.create( + + file_upload = FileUpload.objects.create( size=100, bucket_name="symbols-public", key="libxul.so/A772CC9A3E852CF48965ED79FB65E3150/libxul.so.sym", @@ -883,7 +948,7 @@ def test_upload_files_count(client): data = response.json() assert data["files"] == [ { - "id": file_upload_2.id, + "id": file_upload.id, "key": "libxul.so/A772CC9A3E852CF48965ED79FB65E3150/libxul.so.sym", "update": False, "compressed": True, @@ -894,7 +959,24 @@ def test_upload_files_count(client): "upload": None, }, { - "id": file_upload_1.id, + "bucket_name": "symbols-public", + "completed_at": None, + "compressed": False, + "created_at": ANY, + "id": try_file_upload.id, + "key": "libxul.so/9B6C6BD630483C5F453471EE0EEEB09A0/libxul.so.sym", + "size": 50, + "update": False, + "upload": { + "created_at": ANY, + "id": try_upload.id, + "try_symbols": True, + "upload_type": "try", + "user": {"id": user.id, "email": "user1@example.com"}, + }, + }, + { + "id": regular_file_upload.id, "key": "bar.pdb/46A0ADB3F299A70B4C4C44205044422E1/bar.sym", "update": False, "compressed": False, @@ -903,8 +985,9 @@ def test_upload_files_count(client): "completed_at": None, "created_at": ANY, "upload": { - "id": upload.id, + "id": regular_upload.id, "try_symbols": False, + "upload_type": "regular", "user": {"id": user.id, "email": "user1@example.com"}, "created_at": ANY, }, @@ -925,14 +1008,15 @@ def test_upload_files_count(client): "completed_at": None, "compressed": False, "created_at": ANY, - "id": file_upload_1.id, + "id": regular_file_upload.id, "key": "bar.pdb/46A0ADB3F299A70B4C4C44205044422E1/bar.sym", "size": 1234, "update": False, "upload": { "created_at": ANY, - "id": upload.id, + "id": regular_upload.id, "try_symbols": False, + "upload_type": "regular", "user": { "email": "user1@example.com", "id": user.id,