From d15472f4174faec8734c90c994174c0d32716e8e Mon Sep 17 00:00:00 2001 From: Cesar N Date: Thu, 3 Dec 2020 11:37:53 -0600 Subject: [PATCH] Fix object download (#466) If an object is within a folder the object downloaded now only has the object's name. Also, it now supports object version downloading. --- .../Objects/ListObjects/ListObjects.tsx | 15 ++++-------- .../ListBuckets/Objects/ListObjects/types.tsx | 1 + .../Objects/ObjectDetails/ObjectDetails.tsx | 8 +++---- .../Buckets/ListBuckets/Objects/utils.ts | 24 ++++++++++++------- restapi/embedded_spec.go | 6 ++--- .../user_api/download_object_parameters.go | 14 ++++------- .../user_api/download_object_urlbuilder.go | 7 ++++-- restapi/user_objects.go | 8 +++++-- swagger.yml | 2 +- 9 files changed, 43 insertions(+), 42 deletions(-) diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjects.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjects.tsx index a68398f90..176ce7656 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjects.tsx +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjects.tsx @@ -153,12 +153,9 @@ const ListObjects = ({ setLastAsFile, }: IListObjectsProps) => { const [records, setRecords] = useState([]); - const [totalRecords, setTotalRecords] = useState(0); const [loading, setLoading] = useState(true); - const [error, setError] = useState(""); const [deleteOpen, setDeleteOpen] = useState(false); const [createFolderOpen, setCreateFolderOpen] = useState(false); - const [deleteError, setDeleteError] = useState(""); const [selectedObject, setSelectedObject] = useState(""); const [selectedBucket, setSelectedBucket] = useState(""); const [filterObjects, setFilterObjects] = useState(""); @@ -179,8 +176,6 @@ const ListObjects = ({ .then((res: BucketObjectsList) => { setSelectedBucket(bucketName); setRecords(res.objects || []); - setTotalRecords(!res.objects ? 0 : res.total); - setError(""); // In case no objects were retrieved, We check if item is a file if (!res.objects && extraPath !== "") { verifyIfIsFile(); @@ -190,7 +185,6 @@ const ListObjects = ({ }) .catch((err: any) => { setLoading(false); - setError(err); }); }, [loading, match]); @@ -221,7 +215,6 @@ const ListObjects = ({ }) .catch((err: any) => { setLoading(false); - setError(err); }); }; @@ -263,7 +256,7 @@ const ListObjects = ({ xhr.open("POST", uploadUrl, true); xhr.withCredentials = false; - xhr.onload = function (event) { + xhr.onload = function(event) { // TODO: handle status if (xhr.status === 401 || xhr.status === 403) { showSnackBarMessage("An error occurred while uploading the file."); @@ -307,8 +300,8 @@ const ListObjects = ({ setSelectedObject(object); }; - const downloadObject = (object: string) => { - download(selectedBucket, object); + const downloadObject = (object: BucketObject) => { + download(selectedBucket, object.name, object.version_id); }; const openPath = (idElement: string) => { @@ -366,7 +359,7 @@ const ListObjects = ({ const tableActions = [ { type: "view", onClick: openPath, sendOnlyId: true }, - { type: "download", onClick: downloadObject, sendOnlyId: true }, + { type: "download", onClick: downloadObject }, { type: "delete", onClick: confirmDeleteObject, sendOnlyId: true }, ]; diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/types.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/types.tsx index 72867e23e..d0efa2780 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/types.tsx +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/types.tsx @@ -19,6 +19,7 @@ export interface BucketObject { size: number; last_modified: Date; content_type: string; + version_id: string; } export interface BucketObjectsList { diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ObjectDetails.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ObjectDetails.tsx index fb712eade..3eb254818 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ObjectDetails.tsx +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ObjectDetails.tsx @@ -223,13 +223,13 @@ const ObjectDetails = ({ setDeleteTagModalOpen(true); }; - const downloadObject = (path: string) => { - download(bucketName, path); + const downloadObject = (object: IFileInfo) => { + download(bucketName, pathInBucket, object.version_id); }; const tableActions = [ { type: "share", onClick: shareObject, sendOnlyId: true }, - { type: "download", onClick: downloadObject, sendOnlyId: true }, + { type: "download", onClick: downloadObject }, ]; const filteredRecords = versions.filter((version) => @@ -408,7 +408,7 @@ const ObjectDetails = ({ size="small" className={classes.actionsIcon} onClick={() => { - downloadObject(pathInBucket); + downloadObject(actualInfo); }} > diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/utils.ts b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/utils.ts index cabdc6e84..811f1bbd0 100644 --- a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/utils.ts +++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/utils.ts @@ -14,22 +14,28 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import storage from "local-storage-fallback"; +import { isNullOrUndefined } from "util"; -export const download = (bucketName: string, objectName: string) => { +export const download = ( + bucketName: string, + objectPath: string, + versionID: any +) => { const anchor = document.createElement("a"); document.body.appendChild(anchor); - const token: string = storage.getItem("token")!; const xhr = new XMLHttpRequest(); + const allPathData = objectPath.split("/"); + const objectName = allPathData[allPathData.length - 1]; - xhr.open( - "GET", - `/api/v1/buckets/${bucketName}/objects/download?prefix=${objectName}`, - true - ); + let path = `/api/v1/buckets/${bucketName}/objects/download?prefix=${objectPath}`; + if (!isNullOrUndefined(versionID) && versionID !== "null") { + path = path.concat(`&version_id=${versionID}`); + } + + xhr.open("GET", path, true); xhr.responseType = "blob"; - xhr.onload = function (e) { + xhr.onload = function(e) { if (this.status === 200) { const blob = new Blob([this.response], { type: "octet/stream", diff --git a/restapi/embedded_spec.go b/restapi/embedded_spec.go index 46c0a8b28..ec147bb24 100644 --- a/restapi/embedded_spec.go +++ b/restapi/embedded_spec.go @@ -550,8 +550,7 @@ func init() { { "type": "string", "name": "version_id", - "in": "query", - "required": true + "in": "query" } ], "responses": { @@ -5510,8 +5509,7 @@ func init() { { "type": "string", "name": "version_id", - "in": "query", - "required": true + "in": "query" } ], "responses": { diff --git a/restapi/operations/user_api/download_object_parameters.go b/restapi/operations/user_api/download_object_parameters.go index 21aa335eb..f60d20f80 100644 --- a/restapi/operations/user_api/download_object_parameters.go +++ b/restapi/operations/user_api/download_object_parameters.go @@ -59,10 +59,9 @@ type DownloadObjectParams struct { */ Prefix string /* - Required: true In: query */ - VersionID string + VersionID *string } // BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface @@ -135,21 +134,18 @@ func (o *DownloadObjectParams) bindPrefix(rawData []string, hasKey bool, formats // bindVersionID binds and validates parameter VersionID from query. func (o *DownloadObjectParams) bindVersionID(rawData []string, hasKey bool, formats strfmt.Registry) error { - if !hasKey { - return errors.Required("version_id", "query", rawData) - } var raw string if len(rawData) > 0 { raw = rawData[len(rawData)-1] } - // Required: true + // Required: false // AllowEmptyValue: false - if err := validate.RequiredString("version_id", "query", raw); err != nil { - return err + if raw == "" { // empty values pass all other validations + return nil } - o.VersionID = raw + o.VersionID = &raw return nil } diff --git a/restapi/operations/user_api/download_object_urlbuilder.go b/restapi/operations/user_api/download_object_urlbuilder.go index 3e6ef2ea4..9846c5ea3 100644 --- a/restapi/operations/user_api/download_object_urlbuilder.go +++ b/restapi/operations/user_api/download_object_urlbuilder.go @@ -34,7 +34,7 @@ type DownloadObjectURL struct { BucketName string Prefix string - VersionID string + VersionID *string _basePath string // avoid unkeyed usage @@ -82,7 +82,10 @@ func (o *DownloadObjectURL) Build() (*url.URL, error) { qs.Set("prefix", prefixQ) } - versionIDQ := o.VersionID + var versionIDQ string + if o.VersionID != nil { + versionIDQ = *o.VersionID + } if versionIDQ != "" { qs.Set("version_id", versionIDQ) } diff --git a/restapi/user_objects.go b/restapi/user_objects.go index a83d7cf0f..95c6d5879 100644 --- a/restapi/user_objects.go +++ b/restapi/user_objects.go @@ -228,10 +228,14 @@ func getDownloadObjectResponse(session *models.Principal, params user_api.Downlo return object, nil } -func downloadObject(ctx context.Context, client MCClient, versionID string) (io.ReadCloser, error) { +func downloadObject(ctx context.Context, client MCClient, versionID *string) (io.ReadCloser, error) { // TODO: handle encripted files var reader io.ReadCloser - reader, pErr := client.get(ctx, mc.GetOptions{VersionID: versionID}) + var version string + if versionID != nil { + version = *versionID + } + reader, pErr := client.get(ctx, mc.GetOptions{VersionID: version}) if pErr != nil { return nil, pErr.Cause } diff --git a/swagger.yml b/swagger.yml index 248bedb4b..8eeb521f3 100644 --- a/swagger.yml +++ b/swagger.yml @@ -330,7 +330,7 @@ paths: type: string - name: version_id in: query - required: true + required: false type: string responses: 200: