Skip to content

Conversation

@knollengewaechs
Copy link
Contributor

@knollengewaechs knollengewaechs commented Nov 27, 2025

Steps to test:

  • Prerequisite: duplicate some datasets on disk :)
  • Go to dashboard
  • Select multiple datasets and delete them
  • check that dashboard updates automatically (without reloading)
  • check that this is only possible when datasets are editable (e.g. when current user is an admin)

TODOs:

  • ds from multiple folders -> see code rabbit below

Issues:


(Please delete unneeded items, merge only when none are left open)

  • Added changelog entry (create a $PR_NUMBER.md file in unreleased_changes or use ./tools/create-changelog-entry.py)
  • Added migration guide entry if applicable (edit the same file as for the changelog)
  • Updated documentation if applicable
  • Adapted wk-libs python client if relevant API parts change
  • Removed dev-only changes like prints and application.conf edits
  • Considered common edge cases
  • Needs datastore update after deployment

@knollengewaechs knollengewaechs self-assigned this Nov 27, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 27, 2025

📝 Walkthrough

Walkthrough

Adds a bulk dataset deletion workflow in the folder sidebar (frontend mutation, per-item progress, confirmation modal), relaxes frontend Delete-menu visibility checks, removes an organization-admin authorization check in the backend delete controller, updates access-rights docs, and adds an unreleased changelog entry for multi-dataset deletion.

Changes

Cohort / File(s) Summary
Folder sidebar — bulk delete
frontend/javascripts/dashboard/folders/details_sidebar.tsx
Implements bulk deletion: imports UI & React Query utilities, adds local state (progressInPercent, showConfirmDeleteModal), computes deletableDatasets/numberOfUndeletableDatasets, adds invalidateQueries, deleteDatasetsMutation that calls deleteDatasetOnDisk with per-item progress updates, deleteDatasets trigger, Delete Button and confirmation Modal with progress bar and undeletable warnings.
Dataset settings — visibility & text
frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx, frontend/javascripts/dashboard/dataset/dataset_settings_delete_tab.tsx
Removes admin-only gating so Delete menu visibility depends on features().allowDeleteDatasets; removes useWkSelector/isUserAdmin; updates explanatory text to include admins, dataset managers, and team managers.
Backend controller — authorization change
app/controllers/DatasetController.scala
Removes the extra authorization check requiring the requester to be an organization admin before deletion; deletion now proceeds relying on isEditableBy and feature flags.
Hook signature & callers
frontend/javascripts/libs/react_hooks.ts, frontend/javascripts/viewer/view/action-bar/tools/volume_specific_ui.tsx, frontend/javascripts/dashboard/dataset/dataset_collection_context.tsx
usePrevious now returns [previousValue, clearFn]; callers updated to destructure the tuple and, where relevant, call clearMostRecentlyUsedActiveFolderId. Also added useQueryClient usage and query invalidation on folder-loading error.
Toast behavior
frontend/javascripts/libs/toast.tsx
Error toasts changed from sticky: true to timeout: 13000 (auto-hide after 13s).
Docs & changelog
docs/users/access_rights.md, unreleased_changes/9107.md
Adds permission rows clarifying dataset-deletion rights (own teams vs other teams) and adds an unreleased changelog entry documenting multi-dataset deletion from the sidebar.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Review deletion loop in details_sidebar.tsx: confirm per-item error handling (abort vs continue) and whether partial failures are surfaced correctly.
  • Validate React Query invalidation keys used in invalidateQueries cover both folder-scoped lists and global dataset search.
  • Verify UI state lifecycle: progress reset, modal visibility, and toast/error flows.
  • Check backend DatasetController.scala authorization removal to ensure it matches documented access-rights and frontend visibility (avoid privilege escalation).
  • Confirm usePrevious signature change doesn't break other callers beyond the updated files.

Possibly related PRs

  • Update Tanstack-query to v5 #8778 — modifies the same frontend/javascripts/dashboard/folders/details_sidebar.tsx and adjusts React Query hook usage; likely overlaps with the bulk-delete changes.

Suggested labels

enhancement, frontend, backend

Suggested reviewers

  • hotzenklotz
  • philippotto

Poem

🐰 I hop through lists and watch the numbers fall,

A progress bar hums as I tidy them all.
A modal blink, a gentle toast — goodbye,
One-by-one they vanish under moonlit sky.
I twitch my whiskers, proud of the small fry.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and concisely describes the main feature added: the ability to delete multiple datasets in one action.
Description check ✅ Passed The description provides clear testing steps, references the linked issue #8817, includes a changelog entry, and outlines implementation considerations.
Linked Issues check ✅ Passed The PR implements the core objective from #8817 by adding bulk dataset deletion UI in the dashboard, though the trash/restore feature (#9061) is not included.
Out of Scope Changes check ✅ Passed All changes support the bulk deletion feature: UI components, backend authorization updates, hook refactoring for state management, and documentation updates are all within scope.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bulk-delete-datasets

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
frontend/javascripts/dashboard/folders/details_sidebar.tsx (2)

300-303: Remove unnecessary whitespace.

Line 301 has {" "} which is unnecessary since the Button component handles spacing between icon and text internally.

Apply this diff:

-        <Button onClick={() => setShowConfirmDeleteModal(true)}>
-          {" "}
-          <DeleteOutlined /> Delete {selectedDatasets.length} datasets
-        </Button>
+        <Button 
+          icon={<DeleteOutlined />} 
+          onClick={() => setShowConfirmDeleteModal(true)}
+        >
+          Delete {selectedDatasets.length} datasets
+        </Button>

243-253: Consider parallel deletion for better performance.

Sequential deletion may be slow for large selections. Once the API call is implemented, consider using Promise.allSettled to delete datasets in parallel (respecting reasonable concurrency limits to avoid overloading the backend).

Example approach:

const results = await Promise.allSettled(
  selectedDatasets.map(dataset => deleteDatasetOnDisk(dataset.id))
);

results.forEach((result, i) => {
  if (result.status === 'rejected') {
    Toast.error(`Failed to delete dataset ${selectedDatasets[i].name}.`);
  }
  setProgressInPercent(Math.round(((i + 1) / selectedDatasets.length) * 100));
});
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ac234ea and 0739b07.

📒 Files selected for processing (1)
  • frontend/javascripts/dashboard/folders/details_sidebar.tsx (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-smoketest-push
  • GitHub Check: backend-tests
🔇 Additional comments (3)
frontend/javascripts/dashboard/folders/details_sidebar.tsx (3)

3-3: LGTM!

The new imports are appropriate for the bulk delete functionality being added.

Also applies to: 12-12, 17-17, 19-19


227-229: LGTM!

The state management setup appropriately tracks the deletion flow and modal visibility.


265-268: Ensure soft-delete implementation is tracked.

The TODO indicates that this hard-delete warning is temporary until soft-delete is implemented. Ensure this is tracked in issue #9061 (mentioned in PR objectives) or in the PR's TODO checklist.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0739b07 and 4a33dcf.

📒 Files selected for processing (1)
  • frontend/javascripts/dashboard/folders/details_sidebar.tsx (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/javascripts/dashboard/folders/details_sidebar.tsx (2)
frontend/javascripts/types/api_types.ts (1)
  • APIDatasetCompact (271-276)
frontend/javascripts/admin/rest_api.ts (1)
  • deleteDatasetOnDisk (1387-1391)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: backend-tests
  • GitHub Check: build-smoketest-push
  • GitHub Check: frontend-tests
🔇 Additional comments (3)
frontend/javascripts/dashboard/folders/details_sidebar.tsx (3)

3-3: LGTM! Imports correctly support the new deletion functionality.

All necessary dependencies for the bulk delete feature are properly imported.

Also applies to: 10-12, 19-19


227-230: LGTM! State management is properly initialized.

The state variables correctly support the deletion flow, progress tracking, and modal visibility.


266-320: LGTM! Modal and UI implementation is well-structured.

The deletion confirmation flow properly:

  • Lists datasets to be deleted for user verification
  • Shows progress during deletion with a progress bar
  • Prevents modal closure during deletion
  • Includes clear warning about the action being non-reversible
  • Uses appropriate Ant Design components and patterns

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
frontend/javascripts/dashboard/folders/details_sidebar.tsx (1)

278-312: Consider adding real cancellation support for long-running deletions

onCancel ignores requests while isDeleting is true, and the modal footer is removed in that state, so users can neither close the dialog nor abort the loop once deletion has started. For large selections this can feel like being “trapped” until all requests finish or time out.

A lightweight improvement would be to introduce a cancellation flag checked inside the loop, e.g.:

 const [isDeleting, setIsDeleting] = useState(false);
+const [isCancelled, setIsCancelled] = useState(false);
 ...
 const deleteDatasets = async () => {
   setIsDeleting(true);
-  const deletedIds: string[] = [];
+  setIsCancelled(false);
+  const deletedIds: string[] = [];
   for (let i = 0; i < deletableDatasets.length; i++) {
+    if (isCancelled) {
+      break;
+    }
     const dataset = deletableDatasets[i];
     ...
   }
   ...
 };
 
 const onCancel = () => {
-  if (!isDeleting) {
-    setShowConfirmDeleteModal(false);
-  }
+  if (!isDeleting) {
+    setShowConfirmDeleteModal(false);
+  } else {
+    setIsCancelled(true);
+  }
 };

This preserves the invariant that you can’t close the modal immediately while requests are in flight, but it lets users stop further deletions in a large batch.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 972bc76 and 80247eb.

📒 Files selected for processing (1)
  • frontend/javascripts/dashboard/folders/details_sidebar.tsx (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/javascripts/dashboard/folders/details_sidebar.tsx (2)
frontend/javascripts/types/api_types.ts (1)
  • APIDatasetCompact (271-276)
frontend/javascripts/admin/rest_api.ts (1)
  • deleteDatasetOnDisk (1387-1391)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: backend-tests
  • GitHub Check: frontend-tests
  • GitHub Check: build-smoketest-push
🔇 Additional comments (2)
frontend/javascripts/dashboard/folders/details_sidebar.tsx (2)

3-19: Imports and hooks align with new bulk-delete behaviour

New imports (DeleteOutlined, useQueryClient, deleteDatasetOnDisk, extra antd components, useState) are all used correctly in DatasetsDetails and keep dependencies localized; no issues here.


227-246: Cross-folder cache updates look correct

The invalidateQueries helper now derives uniqueFolderIds from selectedDatasets and filters out deletedIds per ["datasetsByFolder", folderId] entry, plus invalidates the dataset search queries. This addresses the earlier cross-folder stale-cache problem and keeps folder and search views consistent after multi-folder deletions.

you do not have sufficient permissions.
</div>
)}
{cantBeUndoneMessage}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe inline that warning. There is nothing special about it to move it into a separate variable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought it would be removed soon anyway, with the trash implemented in #9061, and I thought it would be cleaner if one can just remove the variable

<Modal
open={showConfirmDeleteModal}
title="Delete Datasets"
footer={isDeleting ? null : [cancelButton, okayButton]}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of defining a custom footer for your two button, I would use the onOK, okType, okText, okButtonProps cancelText,onCancel props of the modal for the simple styling that you are doing anyway.

https://5x.ant.design/components/modal#api

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, I know its super basic styling but I want to hide the footer when the progress bar is shown. I think this is only possible with the custom footer (at least thats whats recommended in the docs)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
frontend/javascripts/dashboard/folders/details_sidebar.tsx (1)

249-273: Consider tracking progress by attempts rather than successes.

Currently, setProgressInPercent (line 257) is inside the try block, so progress only advances for successful deletions. If several deletions fail consecutively, the progress bar may appear stuck even though the loop is actively processing items.

Move the progress update outside the try block to reflect overall processing progress:

       try {
         await deleteDatasetOnDisk(dataset.id);
         deletedIds.push(dataset.id);
-        setProgressInPercent(Math.round(((i + 1) / datasets.length) * 100));
       } catch (_e) {
         Toast.error(`Failed to delete dataset ${dataset.name}.`);
       }
+      setProgressInPercent(Math.round(((i + 1) / datasets.length) * 100));
     }

Additionally, consider logging the caught error for debugging:

-      } catch (_e) {
+      } catch (error) {
         Toast.error(`Failed to delete dataset ${dataset.name}.`);
+        console.error("Dataset deletion failed:", error);
       }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between be61d92 and 945e8e7.

📒 Files selected for processing (1)
  • frontend/javascripts/dashboard/folders/details_sidebar.tsx (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-03T17:50:57.587Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 9025
File: frontend/javascripts/dashboard/advanced_dataset/dataset_table.tsx:10-10
Timestamp: 2025-11-03T17:50:57.587Z
Learning: In the webknossos codebase, both `import { Component } from "antd";` and `import { Component } from "antd/lib";` are valid import paths for antd components. Do not flag imports from "antd/lib" as errors.

Applied to files:

  • frontend/javascripts/dashboard/folders/details_sidebar.tsx
🧬 Code graph analysis (1)
frontend/javascripts/dashboard/folders/details_sidebar.tsx (3)
frontend/javascripts/types/api_types.ts (1)
  • APIDatasetCompact (271-276)
frontend/javascripts/admin/rest_api.ts (1)
  • deleteDatasetOnDisk (1387-1391)
frontend/javascripts/libs/utils.ts (1)
  • pluralize (1171-1179)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: backend-tests
  • GitHub Check: build-smoketest-push
🔇 Additional comments (5)
frontend/javascripts/dashboard/folders/details_sidebar.tsx (5)

3-3: LGTM! Imports are appropriate.

All new imports are necessary for the bulk deletion functionality and are correctly used throughout the implementation.

Also applies to: 10-12, 19-19


227-231: LGTM! State and filtering logic correctly implemented.

The component properly separates deletable from undeletable datasets based on the isEditable property, ensuring permissions are respected throughout the deletion flow.


233-247: LGTM! Cache invalidation correctly handles cross-folder deletions.

The logic properly updates all affected folder caches and invalidates search results. Only successfully deleted datasets (those in deletedIds) are removed from the cache, ensuring correctness even when some deletions fail.


275-328: LGTM! Modal and confirmation flow well-implemented.

The confirmation modal provides clear feedback with:

  • Progress tracking during deletion
  • Explicit list of datasets to be deleted (only deletable ones)
  • Warning when some selected datasets cannot be deleted due to permissions
  • Proper button states and modal behavior

The implementation correctly operates on deletableDatasets throughout, ensuring the UI messaging matches the actual deletion behavior.


330-345: LGTM! Render logic is clean and user-friendly.

The UI correctly displays the delete action only when there are deletable datasets and uses the appropriate icon prop for the button. Selection state is automatically managed—when datasets are deleted and the cache is invalidated, the parent component's useEffect syncs selected datasets against the fresh data, automatically filtering out any deleted items.

@knollengewaechs
Copy link
Contributor Author

@hotzenklotz thanks for the quick review! I incorporated the feedback but left two threads open because I wanted to explain my train of thought.
During testing and reading code in dataset_settings_delete_tab I understood that only admins are allowed to delete datasets. Thus I changed some of the logic regarding the rendering of the delete button.

</Modal>
);

const showDeleteDatasetsButton = isUserAdmin && mayDeleteDatasets && selectedDatasets.length > 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See discussion about permissions rights: https://scm.slack.com/archives/C5AKLAV0B/p1764759287923069

Copy link
Member

@hotzenklotz hotzenklotz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your changes LGTM.

Please see the discussion around user permissions in Slack and update the PR and docs accordingly.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cd943cc and 614f967.

📒 Files selected for processing (4)
  • docs/users/access_rights.md (1 hunks)
  • frontend/javascripts/dashboard/dataset/dataset_settings_delete_tab.tsx (1 hunks)
  • frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx (1 hunks)
  • frontend/javascripts/dashboard/folders/details_sidebar.tsx (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-03T17:50:57.587Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 9025
File: frontend/javascripts/dashboard/advanced_dataset/dataset_table.tsx:10-10
Timestamp: 2025-11-03T17:50:57.587Z
Learning: In the webknossos codebase, both `import { Component } from "antd";` and `import { Component } from "antd/lib";` are valid import paths for antd components. Do not flag imports from "antd/lib" as errors.

Applied to files:

  • frontend/javascripts/dashboard/folders/details_sidebar.tsx
🧬 Code graph analysis (1)
frontend/javascripts/dashboard/folders/details_sidebar.tsx (3)
frontend/javascripts/types/api_types.ts (1)
  • APIDatasetCompact (271-276)
frontend/javascripts/admin/rest_api.ts (1)
  • deleteDatasetOnDisk (1387-1391)
frontend/javascripts/libs/utils.ts (1)
  • pluralize (1171-1179)
🪛 markdownlint-cli2 (0.18.1)
docs/users/access_rights.md

34-34: Hard tabs
Column: 54

(MD010, no-hard-tabs)


34-34: Hard tabs
Column: 63

(MD010, no-hard-tabs)


34-34: Hard tabs
Column: 82

(MD010, no-hard-tabs)


34-34: Hard tabs
Column: 98

(MD010, no-hard-tabs)


34-34: Hard tabs
Column: 113

(MD010, no-hard-tabs)


35-35: Hard tabs
Column: 54

(MD010, no-hard-tabs)


35-35: Hard tabs
Column: 63

(MD010, no-hard-tabs)


35-35: Hard tabs
Column: 82

(MD010, no-hard-tabs)


35-35: Hard tabs
Column: 98

(MD010, no-hard-tabs)


35-35: Hard tabs
Column: 113

(MD010, no-hard-tabs)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build-smoketest-push
  • GitHub Check: frontend-tests
  • GitHub Check: backend-tests
🔇 Additional comments (7)
frontend/javascripts/dashboard/dataset/dataset_settings_delete_tab.tsx (1)

64-67: LGTM - Text accurately reflects expanded permissions.

The updated text correctly documents that admins, dataset managers, and team managers can delete datasets, aligning with the access rights documentation changes.

frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx (1)

154-154: LGTM - Menu visibility broadened while maintaining backend permission checks.

Removing the admin-only check allows the delete menu item to appear for all users when the feature flag is enabled. The actual deletion capability remains protected by backend permissions (the isEditable property), ensuring users without sufficient privileges cannot delete datasets even if they can see the menu item.

frontend/javascripts/dashboard/folders/details_sidebar.tsx (5)

2-2: LGTM - Imports support the new deletion workflow.

All added imports are necessary for implementing the bulk dataset deletion feature with progress tracking, modal UI, and cache invalidation.

Also applies to: 9-11, 16-16, 19-19


219-223: LGTM - Proper separation of deletable vs. undeletable datasets.

The code correctly filters datasets by isEditable to identify which ones can be deleted, preventing unauthorized deletion attempts and enabling clear user feedback about mixed selections.


225-239: LGTM - Correctly handles multi-folder cache invalidation.

The function properly updates caches for all affected folders by computing unique folder IDs and filtering deleted datasets from each folder's query data. This addresses the cross-folder deletion scenario mentioned in previous reviews.


241-265: LGTM - Well-structured mutation with proper error handling.

The deletion mutation correctly:

  • Uses useMutation for state management and lifecycle handling
  • Explicitly types deletedIds as string[]
  • Handles individual deletion failures gracefully with error toasts while continuing the loop
  • Provides per-item progress updates
  • Cleans up state and invalidates queries in the onSuccess callback

267-322: LGTM - Modal and UI implementation follow best practices.

The modal and UI components are well-implemented:

  • Modal footer is properly hidden during deletion to prevent premature closure
  • Warning message uses Typography.Text for theme compatibility
  • Button icon is passed via the icon prop for proper spacing
  • Clear distinction between deletable and undeletable datasets in the UI
  • Appropriate user feedback throughout the deletion process

Also applies to: 324-339

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
frontend/javascripts/dashboard/folders/details_sidebar.tsx (1)

273-323: Clarify modal behavior while deletion is pending (closing vs. cancelling)

While deleteDatasetsMutation.isPending, the footer is hidden and onCancel ignores cancel attempts, but the default close controls (close icon / mask click) still invoke onCancel, which then does nothing. This can feel like a broken close button with no feedback, and there’s still no way to cancel the remaining deletions.

Two possible improvements:

  • If deletions should not be cancellable: disable closing while pending (e.g., closable={!isPending}, maskClosable={!isPending}) so the user is clearly locked in until completion.
  • If you want cancellability: introduce a cancellation flag checked inside mutationFn and let onCancel set it, then close the modal once the loop stops.

Either approach would make the UX more explicit and avoid “dead” close actions during a pending mutation.

🧹 Nitpick comments (1)
frontend/javascripts/dashboard/folders/details_sidebar.tsx (1)

225-239: Make cache invalidation independent of current selection to avoid rare stale-folder cases

invalidateQueries derives uniqueFolderIds from deletableDatasets, which reflects the current selection, not necessarily the datasets that were actually passed to mutate and deleted. If the selection changes while the mutation is running, some folders of deleted datasets might not be updated.

Consider basing folder IDs on the datasets passed into the mutation (e.g., have mutationFn return both deletedIds and the set of affected folder IDs, or return { deletedIds, datasets } and compute folder IDs from that in onSuccess) so cache updates are always derived from the actual deletion input, independent of later selection changes.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8064861 and 98a6f98.

📒 Files selected for processing (1)
  • frontend/javascripts/dashboard/folders/details_sidebar.tsx (2 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-12-04T13:41:44.912Z
Learnt from: hotzenklotz
Repo: scalableminds/webknossos PR: 9117
File: frontend/javascripts/viewer/view/nml_upload_zone_container.tsx:186-186
Timestamp: 2025-12-04T13:41:44.912Z
Learning: In Ant Design v6, the Alert component's `message` prop has been deprecated and replaced by `title`. When migrating from v5 to v6, Alert components should use `title` for the main content (and optionally `description` for additional details).

Applied to files:

  • frontend/javascripts/dashboard/folders/details_sidebar.tsx
📚 Learning: 2025-11-03T17:50:57.587Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 9025
File: frontend/javascripts/dashboard/advanced_dataset/dataset_table.tsx:10-10
Timestamp: 2025-11-03T17:50:57.587Z
Learning: In the webknossos codebase, both `import { Component } from "antd";` and `import { Component } from "antd/lib";` are valid import paths for antd components. Do not flag imports from "antd/lib" as errors.

Applied to files:

  • frontend/javascripts/dashboard/folders/details_sidebar.tsx
🧬 Code graph analysis (1)
frontend/javascripts/dashboard/folders/details_sidebar.tsx (3)
frontend/javascripts/types/api_types.ts (1)
  • APIDatasetCompact (271-276)
frontend/javascripts/admin/rest_api.ts (1)
  • deleteDatasetOnDisk (1387-1391)
frontend/javascripts/libs/utils.ts (1)
  • pluralize (1171-1179)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build-smoketest-push
  • GitHub Check: backend-tests
  • GitHub Check: frontend-tests
🔇 Additional comments (1)
frontend/javascripts/dashboard/folders/details_sidebar.tsx (1)

219-271: Bulk delete flow and permission handling look solid

The overall mutation setup (per-item delete loop, progress tracking, success/error toasts) and the use of deletableDatasets throughout the UI and mutation entry point align well with the permissions model and earlier feedback. No blocking issues here.

@knollengewaechs
Copy link
Contributor Author

todo: fix bug where there is an endless redirect after deleting all datasets in a folder

const deletableDatasets = selectedDatasets.filter((ds) => ds.isEditable);
const numberOfUndeletableDatasets = selectedDatasets.length - deletableDatasets.length;

const invalidateQueries = (deletedIds: string[]) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updateAndInv

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • test last commit

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
frontend/javascripts/dashboard/dataset/dataset_collection_context.tsx (1)

1-1: Folder error handling + MRU clearing look good; minor note on effect deps

The new flow around mostRecentlyUsedActiveFolderId and query invalidation is sensible:

  • Tracking mostRecentlyUsedActiveFolderId via usePrevious(activeFolderId, true) preserves the last non-null folder id, and
  • Clearing it in the error branch (didFolderLoadingError) together with setActiveFolderId(null) avoids keeping around an invalid folder id and should help with redirect/selection edge cases.
  • Guarding queryClient.invalidateQueries({ queryKey: ["folders"] }) behind !folderHierarchyQuery.isFetching is a reasonable way to avoid redundant refreshes.

One small refinement:

  • clearMostRecentlyUsedActiveFolderId is currently recreated on every render by usePrevious, so including it in the effect dependency array effectively makes this effect run on every render (repeating the localStorage set/remove work even when activeFolderId/folder/didFolderLoadingError haven’t changed).
  • Once usePrevious memoizes its clearFn (e.g., via useCallback with a stable ref, similar to useIsMounted in libs/react_hooks.ts), this effect will naturally only re-run when the relevant state changes.

Also, please double‑check that the "folders" query key here matches the key used by useFolderHierarchyQuery (and related folder queries) so the invalidation hits the intended cache entries.

Also applies to: 84-88, 101-135

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 98a6f98 and e6b7f81.

📒 Files selected for processing (4)
  • frontend/javascripts/dashboard/dataset/dataset_collection_context.tsx (4 hunks)
  • frontend/javascripts/libs/react_hooks.ts (2 hunks)
  • frontend/javascripts/libs/toast.tsx (1 hunks)
  • frontend/javascripts/viewer/view/action-bar/tools/volume_specific_ui.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2025-12-11T15:25:53.526Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 9117
File: frontend/javascripts/admin/statistic/time_tracking_overview.tsx:261-279
Timestamp: 2025-12-11T15:25:53.526Z
Learning: Ant Design v6 Select: when using the Select component, consider supplying a prefix prop to render an icon or element before the input for better visual context. Apply this guideline to TS and TSX files across the codebase where Ant Design Select is used; ensure prefix usage is accessible (e.g., provide meaningful aria-label if needed) and avoid unnecessary prefixes on simple inputs.

Applied to files:

  • frontend/javascripts/libs/toast.tsx
  • frontend/javascripts/viewer/view/action-bar/tools/volume_specific_ui.tsx
  • frontend/javascripts/libs/react_hooks.ts
  • frontend/javascripts/dashboard/dataset/dataset_collection_context.tsx
📚 Learning: 2025-12-11T15:33:06.880Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 9117
File: frontend/javascripts/admin/task/task_search_form.tsx:151-151
Timestamp: 2025-12-11T15:33:06.880Z
Learning: In Ant Design v6, do not use optionFilterProp as a standalone prop on Select. Instead, pass it inside showSearch as optionFilterProp, e.g. showSearch={{ optionFilterProp: 'label' }}. showSearch accepts boolean or an object with keys like optionFilterProp, filterOption, autoClearSearchValue. Update all Select components that use optionFilterProp to adopt the new pattern and adjust types accordingly to maintain compatibility and avoid deprecation warnings.

Applied to files:

  • frontend/javascripts/libs/toast.tsx
  • frontend/javascripts/viewer/view/action-bar/tools/volume_specific_ui.tsx
  • frontend/javascripts/dashboard/dataset/dataset_collection_context.tsx
📚 Learning: 2025-12-11T15:54:47.778Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 9117
File: frontend/javascripts/dashboard/advanced_dataset/dataset_table.tsx:131-134
Timestamp: 2025-12-11T15:54:47.778Z
Learning: In Ant Design v6, Dropdown uses a flat classNames structure: classNames={{ root: '…' }}. Other components (Select, AutoComplete, Cascader, TreeSelect) use a nested structure. The deprecated overlayClassName prop for Dropdown should be replaced with classNames.root. In reviews, flag Dropdown usage that relies on overlayClassName and replace it with classNames={{ root: '…' }}. If you encounter related components, verify the correct classNames shape (flat for Dropdown, nested for others) and update accordingly. This guideline covers TSX files under the frontend codebase where Ant Design components are used.

Applied to files:

  • frontend/javascripts/libs/toast.tsx
  • frontend/javascripts/viewer/view/action-bar/tools/volume_specific_ui.tsx
  • frontend/javascripts/dashboard/dataset/dataset_collection_context.tsx
📚 Learning: 2024-11-22T17:18:04.217Z
Learnt from: dieknolle3333
Repo: scalableminds/webknossos PR: 8168
File: frontend/javascripts/oxalis/model/sagas/proofread_saga.ts:1039-1039
Timestamp: 2024-11-22T17:18:04.217Z
Learning: In `frontend/javascripts/oxalis/model/sagas/proofread_saga.ts`, when calling `getMagInfo`, the use of `volumeTracingLayer.resolutions` is intentional and should not be changed to `volumeTracingLayer.mags`.

Applied to files:

  • frontend/javascripts/viewer/view/action-bar/tools/volume_specific_ui.tsx
📚 Learning: 2025-09-04T10:01:56.727Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 8895
File: frontend/javascripts/viewer/view/action_bar_view.tsx:0-0
Timestamp: 2025-09-04T10:01:56.727Z
Learning: In frontend/javascripts/viewer/view/action_bar_view.tsx, knollengewaechs prefers not to use onKeyUpCapture for preventing modal close on modifier keys because: 1) holding ctrl while opening modal will still close on keyup, 2) normal case of opening modal then pressing ctrl doesn't cause issues with just onKeyDownCapture.

Applied to files:

  • frontend/javascripts/viewer/view/action-bar/tools/volume_specific_ui.tsx
📚 Learning: 2025-10-21T10:54:16.468Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 8961
File: frontend/javascripts/viewer/model/actions/annotation_actions.ts:80-82
Timestamp: 2025-10-21T10:54:16.468Z
Learning: In frontend/javascripts/viewer/model/actions/annotation_actions.ts, the ShowManyBucketUpdatesWarningAction is intentionally not included in the AnnotationActionTypes union because it's a UI-only action that doesn't modify the annotation state through reducers.

Applied to files:

  • frontend/javascripts/viewer/view/action-bar/tools/volume_specific_ui.tsx
📚 Learning: 2025-05-30T12:36:09.930Z
Learnt from: philippotto
Repo: scalableminds/webknossos PR: 8542
File: frontend/javascripts/viewer/model/sagas/volumetracing_saga.tsx:0-0
Timestamp: 2025-05-30T12:36:09.930Z
Learning: The WebKnossos frontend follows Redux immutability principles where reducers act immutably and create new state objects when changes occur. This makes reference equality checks (like `prevVolumeTracing.segments !== volumeTracing.segments`) reliable and efficient for detecting actual state changes, allowing memoization to work correctly.

Applied to files:

  • frontend/javascripts/viewer/view/action-bar/tools/volume_specific_ui.tsx
📚 Learning: 2025-02-05T20:16:45.712Z
Learnt from: knollengewaechs
Repo: scalableminds/webknossos PR: 8321
File: frontend/javascripts/components/slider.tsx:0-0
Timestamp: 2025-02-05T20:16:45.712Z
Learning: When tracking focus state that needs to be accessed by native event listeners (e.g., wheel events), use `useRef` instead of `useState` to ensure the event handlers always have access to the current value. This is because event listeners added via `addEventListener` would capture the initial state value if `useState` were used.

Applied to files:

  • frontend/javascripts/libs/react_hooks.ts
🧬 Code graph analysis (2)
frontend/javascripts/viewer/view/action-bar/tools/volume_specific_ui.tsx (1)
frontend/javascripts/libs/react_hooks.ts (1)
  • usePrevious (15-37)
frontend/javascripts/dashboard/dataset/dataset_collection_context.tsx (1)
frontend/javascripts/libs/react_hooks.ts (1)
  • usePrevious (15-37)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: frontend-tests
  • GitHub Check: build-smoketest-push
  • GitHub Check: backend-tests
🔇 Additional comments (2)
frontend/javascripts/libs/toast.tsx (1)

77-77: Verify that changing error toasts from sticky to auto-dismiss is intentional for bulk operations.

The change on line 77 replaces sticky: true with timeout: 13000, making error toasts auto-dismiss after 13 seconds instead of persisting until manually dismissed. While 13 seconds is longer than the default 6-second timeout and the implementation includes a sophisticated manual timeout mechanism that intelligently pauses during browser tab inactivity, this is still a behavioral change with UX implications.

In bulk deletion scenarios where multiple errors could occur, users previously had persistent error messages to review. Confirm that the shift to auto-dismissing toasts aligns with the expected user experience for this feature.

frontend/javascripts/viewer/view/action-bar/tools/volume_specific_ui.tsx (1)

115-116: usePrevious tuple adaptation looks correct

Destructuring only the first element from usePrevious for the modifier flags keeps the previous-value semantics unchanged and correctly ignores the new clearFn in this context. No issues from a logic or typing perspective.

Comment on lines +15 to +18
export function usePrevious<T>(
value: T,
ignoreNullAndUndefined: boolean = false,
): [T | null, () => void] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat frontend/javascripts/libs/react_hooks.ts

Repository: scalableminds/webknossos

Length of output: 10691


🏁 Script executed:

rg -n "usePrevious\(" frontend/javascripts -A 2

Repository: scalableminds/webknossos

Length of output: 994


🏁 Script executed:

rg -n "clearMostRecentlyUsedActiveFolderId" frontend/javascripts/dashboard/dataset/dataset_collection_context.tsx -A 5 -B 2

Repository: scalableminds/webknossos

Length of output: 929


New usePrevious API is fine functionally; consider memoizing clearFn and updating docs

The new API [previousValue, clearFn] keeps the previous‑value semantics intact and exposes a useful reset hook, but there are two small improvements worth making:

  1. Stabilize clearFn's identity

    Right now clearFn is recreated on every render. Since it's included in the dependency array of the effect in DatasetCollectionContextProvider (line 133 of dataset_collection_context.tsx), this causes that effect to re-run on every render, unnecessarily re-touching localStorage.

    You can make clearFn stable, matching the pattern already used in useIsMounted, by wrapping it in useCallback:

    -  const clearFn = () => {
    -    ref.current = null;
    -  };
    +  const clearFn = useCallback(() => {
    +    ref.current = null;
    +  }, []);
  2. Update the JSDoc return description

    The doc still says @returns The previous value, or null if no previous value exists, but the function now returns a tuple [previousValue, clearFn]. Update it to reflect the new signature, e.g., "Returns a tuple [previousValue, clearFn], where previousValue is the value from the previous render (or null if none) and clearFn resets that stored value."

All usePrevious call sites are already correctly destructuring the tuple.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In frontend/javascripts/libs/react_hooks.ts around lines 15 to 18, the newly
exposed clearFn is recreated every render which destabilizes effects that depend
on it; wrap clearFn in useCallback so its identity is stable across renders
(matching the useIsMounted pattern) and update the JSDoc @returns to describe
the new tuple return type, e.g. "Returns a tuple [previousValue, clearFn], where
previousValue is the value from the previous render (or null if none) and
clearFn resets that stored value."

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add option to delete multiple datasets at once

4 participants