Skip to content

🎨 [Frontend] Enh: batch delete files #7458

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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 46 commits into from
Apr 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
53460d3
initial
sanderegg Mar 31, 2025
d743d39
new entrypoint for batch deletion
sanderegg Mar 31, 2025
bd7206f
services/webserver api version: 0.61.4 → 0.61.5
sanderegg Mar 31, 2025
283da3d
added test to batch delete
sanderegg Mar 31, 2025
2de4d68
test runs
sanderegg Mar 31, 2025
0b0c018
done
sanderegg Mar 31, 2025
3f73d4c
initial
sanderegg Mar 31, 2025
5dfa045
new entrypoint for batch deletion
sanderegg Mar 31, 2025
7c5e303
services/webserver api version: 0.61.4 → 0.61.5
sanderegg Mar 31, 2025
28f8c80
added test to batch delete
sanderegg Mar 31, 2025
15a066c
test runs
sanderegg Mar 31, 2025
0708150
done
sanderegg Mar 31, 2025
ac0ec52
mypy
sanderegg Mar 31, 2025
452cc75
sonar
sanderegg Mar 31, 2025
de2344f
delete files
odeimaiz Mar 31, 2025
5070d13
paths
odeimaiz Mar 31, 2025
a1b89e1
Merge branch 'storage/batchDelete' of github.com:sanderegg/osparc-sim…
odeimaiz Mar 31, 2025
6763e0b
It returns a long running task
odeimaiz Mar 31, 2025
297041d
createPollingTask
odeimaiz Mar 31, 2025
7471c05
batch delete and callback
odeimaiz Mar 31, 2025
00fe3e6
follow Long Running Task
odeimaiz Mar 31, 2025
7d06edc
initial
sanderegg Mar 31, 2025
6893e49
new entrypoint for batch deletion
sanderegg Mar 31, 2025
cef40ac
services/webserver api version: 0.61.4 → 0.61.5
sanderegg Mar 31, 2025
26804cd
added test to batch delete
sanderegg Mar 31, 2025
1ab969c
test runs
sanderegg Mar 31, 2025
e871ef9
done
sanderegg Mar 31, 2025
957cd61
mypy
sanderegg Mar 31, 2025
b7fb617
sonar
sanderegg Mar 31, 2025
0bea039
pylint
sanderegg Apr 1, 2025
b7c211a
Merge branch 'storage/batchDelete' of github.com:sanderegg/osparc-sim…
odeimaiz Apr 1, 2025
fe0949b
openPath
odeimaiz Apr 1, 2025
85d1916
getAllItems
odeimaiz Apr 1, 2025
5eb2c45
Merge branch 'master' into feature/batch-delete-files
odeimaiz Apr 1, 2025
6a4f901
bad merge
odeimaiz Apr 1, 2025
a6fb5d3
Merge branch 'feature/batch-delete-files' of github.com:odeimaiz/ospa…
odeimaiz Apr 1, 2025
32bb703
bad merge II
odeimaiz Apr 1, 2025
e083f3b
UX
odeimaiz Apr 1, 2025
9341aba
minor refactoring
odeimaiz Apr 1, 2025
9969ab1
pathsDeleted
odeimaiz Apr 1, 2025
a80b336
resetSelection after deletion
odeimaiz Apr 1, 2025
eb89050
cleanup
odeimaiz Apr 1, 2025
eff2cb0
more cleanup
odeimaiz Apr 1, 2025
5628999
minor fix
odeimaiz Apr 1, 2025
2f6cd6d
extra feedback
odeimaiz Apr 1, 2025
86260c8
soft comparison
odeimaiz Apr 1, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ qx.Class.define("osparc.dashboard.DataBrowser", {
reloadButton.addListener("execute", () => this.__reloadTree(), this);

const selectedFileLayout = treeFolderView.getChildControl("folder-viewer").getChildControl("selected-file-layout");
selectedFileLayout.addListener("fileDeleted", e => this.__fileDeleted(e.getData()), this);
selectedFileLayout.addListener("pathsDeleted", e => treeFolderView.pathsDeleted(e.getData()), this);
},

__reloadTree: function() {
Expand All @@ -89,34 +89,5 @@ qx.Class.define("osparc.dashboard.DataBrowser", {
const folderViewer = treeFolderView.getChildControl("folder-viewer");
folderViewer.resetFolder();
},

__fileDeleted: function(fileMetadata) {
// After deleting a file, try to keep the user in the same folder.
// If the folder doesn't longer exist, open the closest available parent

const pathParts = fileMetadata["fileUuid"].split("/");

const treeFolderView = this.getChildControl("tree-folder-view");
const foldersTree = treeFolderView.getChildControl("folder-tree");
const folderViewer = treeFolderView.getChildControl("folder-viewer");

const openSameFolder = () => {
// drop last, which is the file
pathParts.pop();
treeFolderView.openPath(pathParts);
};

folderViewer.resetFolder();
const locationId = fileMetadata["locationId"];
const path = pathParts[0];
foldersTree.resetCache();
foldersTree.populateLocations()
.then(datasetPromises => {
Promise.all(datasetPromises)
.then(() => foldersTree.requestPathItems(locationId, path))
.then(() => openSameFolder());
})
.catch(err => console.error(err));
}
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -1190,10 +1190,6 @@ qx.Class.define("osparc.data.Resources", {
copy: {
method: "PUT",
url: statics.API + "/storage/locations/{toLoc}/files/{fileName}?extra_location={fromLoc}&extra_source={fileUuid}"
},
delete: {
method: "DELETE",
url: statics.API + "/storage/locations/{locationId}/files/{fileUuid}"
}
}
},
Expand All @@ -1219,6 +1215,10 @@ qx.Class.define("osparc.data.Resources", {
method: "GET",
url: statics.API + "/storage/locations/{locationId}/paths?file_filter={path}&cursor={cursor}&size=1000"
},
batchDelete: {
method: "POST",
url: statics.API + "/storage/locations/{locationId}/-/paths:batchDelete"
},
requestSize: {
method: "POST",
url: statics.API + "/storage/locations/0/paths/{pathId}:size"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
* It is used together with a virtual tree of files where the selection is displayed
* in the text field and the download and delete are related to that selection.
* Download and deleted methods are also provided.
* If a file is deleted it fires "fileDeleted" data event
* If a file is deleted it fires "pathsDeleted" data event
*
* *Example*
*
Expand Down Expand Up @@ -54,7 +54,7 @@ qx.Class.define("osparc.file.FileLabelWithActions", {
},

events: {
"fileDeleted": "qx.event.type.Data"
"pathsDeleted": "qx.event.type.Data",
},

properties: {
Expand Down Expand Up @@ -188,6 +188,13 @@ qx.Class.define("osparc.file.FileLabelWithActions", {
}
}
}
if (toBeDeleted.length === 0) {
return;
}
if (toBeDeleted[0].getLocation() != 0) {
osparc.FlashMessenger.logAs(this.tr("You can only delete files in the local storage"), "WARNING");
return;
}

let msg = this.tr("This action cannot be undone.");
msg += isFolderSelected ? ("<br>"+this.tr("All contents within the folders will be deleted.")) : "";
Expand All @@ -207,36 +214,24 @@ qx.Class.define("osparc.file.FileLabelWithActions", {
},

__doDeleteSelected: function(toBeDeleted) {
const requests = [];
toBeDeleted.forEach(selection => {
if (selection) {
let request = null;
if (osparc.file.FilesTree.isFile(selection)) {
request = this.__deleteItem(selection.getFileId(), selection.getLocation());
} else {
request = this.__deleteItem(selection.getPath(), selection.getLocation());
}
if (request) {
requests.push(request);
}
}
});
Promise.all(requests)
.then(datas => {
if (datas.length) {
this.fireDataEvent("fileDeleted", datas[0]);
osparc.FlashMessenger.logAs(this.tr("Items successfully deleted"), "INFO");
}
});
},

__deleteItem: function(itemId, locationId) {
if (locationId !== 0 && locationId !== "0") {
osparc.FlashMessenger.logAs(this.tr("Externally managed items cannot be deleted"));
return null;
if (toBeDeleted.length === 0) {
osparc.FlashMessenger.logAs(this.tr("Nothing to delete"), "ERROR");
return;
} else if (toBeDeleted.length > 0) {
const paths = toBeDeleted.map(item => item.getPath());
const dataStore = osparc.store.Data.getInstance();
const fetchPromise = dataStore.deleteFiles(paths);
const pollTasks = osparc.store.PollTasks.getInstance();
const interval = 1000;
pollTasks.createPollingTask(fetchPromise, interval)
.then(task => {
task.addListener("resultReceived", e => {
this.fireDataEvent("pathsDeleted", paths);
osparc.FlashMessenger.logAs(this.tr("Items successfully deleted"), "INFO");
});
})
.catch(err => osparc.FlashMessenger.logError(err, this.tr("Unsuccessful files deletion")));
}
const dataStore = osparc.store.Data.getInstance();
return dataStore.deleteFile(locationId, itemId);
},
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -152,16 +152,12 @@ qx.Class.define("osparc.file.FilePicker", {
getOutputFileMetadata: function(node) {
return new Promise((resolve, reject) => {
const outValue = osparc.file.FilePicker.getOutput(node.getOutputs());
const params = {
url: {
locationId: outValue.store,
path: outValue.path
}
};
osparc.data.Resources.fetch("storagePaths", "getPaths", params)
.then(pagResp => {
if (pagResp["items"]) {
const file = pagResp["items"].find(item => item.path === outValue.path);
const locationId = outValue.store;
const path = outValue.path;
osparc.store.Data.getAllItems(locationId, path)
.then(items => {
if (items && items.length) {
const file = items.find(item => item.path === outValue.path);
if (file) {
resolve(file["file_meta_data"]);
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,16 @@ qx.Class.define("osparc.file.FolderContent", {
}
},

resetSelection: function() {
if (this.getMode() === "list") {
const table = this.getChildControl("table");
table.getSelectionModel().resetSelection();
} else if (this.getMode() === "icons") {
const iconsLayout = this.getChildControl("icons-layout");
iconsLayout.getChildren().forEach(btn => btn.setValue(false));
}
},

__selectionChanged: function(selection) {
if (this.isMultiSelect()) {
this.fireDataEvent("multiSelectionChanged", selection);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,14 @@ qx.Class.define("osparc.file.FolderViewer", {

__applyFolder: function() {
this.getChildControl("selected-file-layout").resetSelection();
}
},

resetSelection: function() {
const folderContent = this.getChildControl("folder-content");
folderContent.resetSelection();

const selectedFileLayout = this.getChildControl("selected-file-layout");
selectedFileLayout.resetSelection();
},
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,9 @@ qx.Class.define("osparc.file.TreeFolderView", {
if (osparc.file.FilesTree.isDir(selectedModel)) {
folderViewer.setFolder(selectedModel);
}
// this will trigger the fetching of the content
folderTree.openNodeAndParents(selectedModel);
folderTree.setSelection(new qx.data.Array([selectedModel]));
if (selectedModel.getPath() && !selectedModel.getLoaded()) {
selectedModel.setLoaded(true);
folderTree.requestPathItems(selectedModel.getLocation(), selectedModel.getPath());
}
}
}, this);

Expand All @@ -139,22 +136,18 @@ qx.Class.define("osparc.file.TreeFolderView", {
}, this);
},

openPath: function(path) {
const foldersTree = this.getChildControl("folder-tree");
const folderViewer = this.getChildControl("folder-viewer");
let found = false;
while (!found && path.length) {
found = foldersTree.findItemId(path.join("/"));
// look for next parent
path.pop();
}
if (found) {
foldersTree.openNodeAndParents(found);
foldersTree.setSelection(new qx.data.Array([found]));
foldersTree.fireEvent("selectionChanged");
} else {
folderViewer.resetFolder();
}
pathsDeleted: function(paths) {
this.getChildControl("folder-viewer").resetSelection();

const folderTree = this.getChildControl("folder-tree");
const selectedFolder = folderTree.getSelectedItem();
const children = selectedFolder.getChildren();
paths.forEach(path => {
const found = children.toArray().find(child => child.getPath() === path);
if (found) {
children.remove(found);
}
});
},

requestSize: function(pathId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,29 +234,20 @@ qx.Class.define("osparc.store.Data", {
return true;
},

// if folder path is provided as fileUuid, it can also be deleted
deleteFile: function(locationId, fileUuid) {
deleteFiles: function(paths) {
if (!osparc.data.Permissions.getInstance().canDo("study.node.data.delete", true)) {
return null;
}

// Deletes File
const params = {
url: {
locationId,
fileUuid: encodeURIComponent(fileUuid)
locationId: 0,
},
data: {
paths,
}
};
return osparc.data.Resources.fetch("storageFiles", "delete", params)
.then(files => {
const data = {
data: files,
locationId: locationId,
fileUuid: fileUuid
};
return data;
})
.catch(err => osparc.FlashMessenger.logError(err, this.tr("Unsuccessful file deletion")));
}
return osparc.data.Resources.fetch("storagePaths", "batchDelete", params);
},
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ qx.Class.define("osparc.widget.StudyDataManager", {
treeFolderView.getChildControl("folder-tree").setBackgroundColor("window-popup-background");

const selectedFileLayout = treeFolderView.getChildControl("folder-viewer").getChildControl("selected-file-layout");
selectedFileLayout.addListener("fileDeleted", e => this.__fileDeleted(e.getData()), this);
selectedFileLayout.addListener("pathsDeleted", e => treeFolderView.pathsDeleted(e.getData()), this);
},

__reloadTree: function() {
Expand All @@ -115,36 +115,5 @@ qx.Class.define("osparc.widget.StudyDataManager", {
const folderViewer = treeFolderView.getChildControl("folder-viewer");
folderViewer.resetFolder();
},

__fileDeleted: function(fileMetadata) {
// After deleting a file, try to keep the user in the same folder.
// If the folder doesn't longer exist, open the closest available parent

const path = fileMetadata["fileUuid"].split("/");

const treeFolderView = this.getChildControl("tree-folder-view");
const foldersTree = treeFolderView.getChildControl("folder-tree");
foldersTree.resetCache();

const openSameFolder = () => {
if (!this.getStudyId()) {
// drop first, which is the study id
path.shift();
}
// drop last, which is the file
path.pop();
treeFolderView.openPath(path);
};

if (this.getNodeId()) {
foldersTree.populateNodeTree(this.getStudyId(), this.getNodeId())
.then(() => openSameFolder())
.catch(err => console.error(err));
} else if (this.getStudyId()) {
foldersTree.populateStudyTree(this.getStudyId())
.then(() => openSameFolder())
.catch(err => console.error(err));
}
}
}
});
Loading