Skip to content

Conversation

hotzenklotz
Copy link
Member

@hotzenklotz hotzenklotz commented Jun 25, 2025

In preparation of restyling this component in a similar fashion as the account and organization settings, I first refactored several datasets settings & tab components as React functional components to use modern hook APIs etc.

I move the beforeUnload into it's own hook. This might be related to issue #5977

URL of deployed dev instance (used for testing):

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

@hotzenklotz hotzenklotz self-assigned this Jun 25, 2025
Copy link
Contributor

coderabbitai bot commented Jun 25, 2025

📝 Walkthrough

Walkthrough

This update refactors several dataset settings components in the frontend from class-based or higher-order-component (HOC) patterns to modern React functional components using hooks and context. It introduces a new DatasetSettingsProvider context for centralized state management, a custom useBeforeUnload hook for unsaved changes, restructures UI with SettingsCard components, and replaces Redux/connect and router HOCs with hooks. Additionally, it removes legacy remote dataset layer addition UI, adds a new component for remote layer addition with authentication, updates routing for nested dataset settings tabs, and standardizes prop naming in SettingsCard usage.

Changes

Files/Paths Change Summary
frontend/javascripts/dashboard/dataset/dataset_settings_delete_tab.tsx Refactored to use useDatasetSettingsContext hook, replaced withRouter with useNavigate, restructured UI with SettingsCard and layout.
frontend/javascripts/dashboard/dataset/dataset_settings_sharing_tab.tsx Converted to functional component using context and hooks, replaced Redux/connect/withRouter, memoized handlers, restructured UI with cards.
frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx Complete rewrite from class component to functional component using hooks and context, replaced tabs with nested routes and Outlet.
frontend/javascripts/dashboard/dataset/team_selection_component.tsx Converted from class to functional component using hooks, replaced lifecycle methods with effects, memoized callbacks, updated props type.
frontend/javascripts/dashboard/dataset/useBeforeUnload_hook.ts New hook to handle browser and React Router navigation unload events with unsaved changes, managing native and router blockers.
frontend/javascripts/messages.tsx Added tooltip for segmentation opacity setting; updated unsaved changes warning message text ("Cancel" → "Ok").
unreleased_changes/8719.md Changelog entry for refactoring dataset settings components to functional components using hooks and context.
frontend/javascripts/admin/account/account_auth_token_view.tsx Renamed value to content and explanation to tooltip in APIitems array; aligned with SettingsCardProps.
frontend/javascripts/admin/account/account_password_view.tsx Renamed value to content in passKeyList array; updated SettingsCard props accordingly; imported SettingsCardProps type.
frontend/javascripts/admin/account/account_profile_view.tsx Renamed value to content and explanation to tooltip in profileItems array; updated SettingsCard usage; imported SettingsCardProps.
frontend/javascripts/admin/account/helpers/settings_card.tsx Renamed props: descriptioncontent, explanationtooltip; replaced Popover with Tooltip; added optional style prop; exported type.
frontend/javascripts/admin/account/account_settings_view.tsx Refined pathname filtering logic for selectedKey derivation; no functional change.
frontend/javascripts/admin/organization/organization_danger_zone_view.tsx Replaced description prop with content on SettingsCard for delete button.
frontend/javascripts/admin/organization/organization_notifications_view.tsx Replaced explanation and description props with tooltip and content on SettingsCard components.
frontend/javascripts/admin/organization/organization_overview_view.tsx Renamed value to content and explanation to tooltip in orgaStats array; updated SettingsCard usage and typings.
frontend/javascripts/admin/organization/organization_view.tsx Refined pathname filtering logic for selectedKey derivation; no functional change.
frontend/javascripts/components/async_clickables.tsx Added detailed JSDoc comments to async clickable components and hooks; no functional changes.
frontend/javascripts/dashboard/dataset/color_layer_ordering_component.tsx Removed Collapse and Tooltip UI; simplified drag-and-drop handlers with useCallback; simplified conditional rendering.
frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx Updated Form.useWatch usage with options object including preserve: true.
frontend/javascripts/dashboard/dataset/dataset_settings_context.tsx Added new React context and hook for dataset settings state management, including form, dataset info, edit mode, and handlers.
frontend/javascripts/dashboard/dataset/dataset_settings_data_tab.tsx Refactored to use context hook; restructured UI with SettingsCard components; updated form watch usage; removed props and sync function.
frontend/javascripts/dashboard/dataset/dataset_settings_metadata_tab.tsx Replaced individual form items with array of SettingsCard components; removed "Display Name" field; updated layout and titles.
frontend/javascripts/dashboard/dataset/dataset_settings_provider.tsx New context provider component managing dataset settings state, form synchronization, loading, submission, validation, and navigation.
frontend/javascripts/dashboard/dataset/dataset_settings_viewconfig_tab.tsx Refactored to use context hook; restructured UI with SettingsCard components; replaced checkboxes with switches; introduced internal component.
frontend/javascripts/dashboard/dataset/helper_components.tsx Replaced lodash full import with direct sum import; updated usage accordingly.
frontend/javascripts/router/route_wrappers.tsx Wrapped DatasetSettingsView inside new DatasetSettingsProvider; removed props from DatasetSettingsView.
frontend/javascripts/router/router.tsx Changed dataset settings edit route to nested routes with tabs for data, sharing, metadata, defaultConfig, and delete.
frontend/javascripts/admin/dataset/dataset_add_remote_view.tsx Removed entire remote layer addition and exploration UI and logic; simplified dataset addition form; removed related imports and components.
frontend/javascripts/admin/dataset/remote/add_remote_layer.tsx New component for adding remote dataset layers with URL input, authentication, exploration, and integration; includes helper functions.
frontend/javascripts/admin/dataset/remote/google_auth_form_item.tsx New component for Google Cloud service account key file upload with drag-and-drop UI and optional hint.
frontend/javascripts/admin/onboarding.tsx Wrapped DatasetSettingsView with DatasetSettingsProvider inside onboarding modal; removed props from DatasetSettingsView.

Estimated code review effort

4 (~90 minutes)

Possibly related PRs

Suggested reviewers

  • philippotto
  • MichaelBuessemeyer

Poem

🐇
In code’s green fields we leap and bound,
Hooks and context now surround.
Tabs and cards in tidy rows,
A rabbit’s dance where clean code grows.
From classes old to functions new,
We hop with joy—fresh paths in view! 🌿✨

✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@hotzenklotz
Copy link
Member Author

@coderabbitai Review!

Copy link
Contributor

coderabbitai bot commented Jun 25, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@hotzenklotz I'll review this PR for you! Let me gather information about the changes first.


🏁 Script executed:

#!/bin/bash
# Get information about the PR and the files that were changed
echo "=== PR Information ==="
gh pr view 8719 --json title,body,headRefName,baseRefName,additions,deletions,changedFiles

echo -e "\n=== Files Changed ==="
gh pr diff 8719 --name-only

echo -e "\n=== PR Diff Summary ==="
gh pr diff 8719 --stat

Length of output: 2349


Let me examine the actual diff content to see the refactoring changes:


🏁 Script executed:

#!/bin/bash
# Get the actual diff content for the main component files
echo "=== Diff for dataset_settings_delete_tab.tsx ==="
gh pr diff 8719 -- frontend/javascripts/dashboard/dataset/dataset_settings_delete_tab.tsx

echo -e "\n=== Diff for dataset_settings_sharing_tab.tsx ==="
gh pr diff 8719 -- frontend/javascripts/dashboard/dataset/dataset_settings_sharing_tab.tsx

echo -e "\n=== Diff for dataset_settings_view.tsx ==="
gh pr diff 8719 -- frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx

Length of output: 704


Let me get the full diff content to examine the refactoring:


🏁 Script executed:

#!/bin/bash
# Get the full PR diff content
echo "=== Full PR Diff ==="
gh pr diff 8719

echo -e "\n=== Changelog Entry ==="
cat unreleased_changes/8719.md 2>/dev/null || echo "Changelog file not found in current directory"

Length of output: 46894


@hotzenklotz I've reviewed this comprehensive refactoring of dataset settings components from class components to functional components. This is excellent modernization work! Here's my detailed analysis:

🎯 Overall Assessment: Excellent Refactoring

This is a well-executed conversion that properly modernizes the React components using current best practices. The code is cleaner, more readable, and follows modern React patterns.

What's Done Well

DatasetSettingsView Component

  • State Management: Properly converted class state to multiple useState hooks with appropriate state separation
  • Lifecycle Conversion: componentDidMountuseEffect with proper dependency arrays
  • Method Optimization: Class methods → useCallback hooks with correct dependencies to prevent unnecessary re-renders
  • Cleanup Handling: Proper cleanup with useRef for unblock callbacks and timeouts

HOC Replacement

  • Redux: connect HOC → useWkSelector hook ✓
  • Router: withRouter HOC → useHistory hook ✓
  • React Query: Context consumption → useQueryClient hook ✓

Custom Hooks Usage

  • useFetch for data fetching in delete tab
  • useEffectOnlyOnce in team selection component
  • useWkSelector for state access

🔍 Potential Areas for Attention

1. useEffect Dependencies

// In DatasetSettingsView - this looks correct but worth double-checking
useEffect(() => {
  fetchData();
  sendAnalyticsEvent("open_dataset_settings", {
    datasetName: dataset ? dataset.name : "Not found dataset",
  });
}, [fetchData]); // dataset dependency removed to avoid infinite loop

The comment indicates dataset dependency was removed to avoid infinite loops. This is handled correctly by accessing dataset inside the fetchData callback.

2. Form Reference Pattern

The component now uses const [form] = Form.useForm() instead of formRef, which is the modern approach. The form is properly passed down to child components.

3. Force Update Replacement

// In handleSubmit - clever solution for force update in functional components
setActiveDataSourceEditMode((prev) => prev); // Force re-render
setTimeout(afterForceUpdateCallback, 0);

This is a creative solution to replace this.forceUpdate(). Consider documenting this pattern more clearly for future maintainers.

💡 Minor Suggestions

1. Consider Extracting Custom Hook

The beforeunload logic in DatasetSettingsView could be extracted into a custom hook:

// Could create useBeforeUnload hook
const useBeforeUnload = (hasUnsavedChanges: boolean, message: string) => {
  // beforeunload logic here
};

2. Memoization Opportunity

Consider memoizing the tabs array in DatasetSettingsView since it's recreated on every render:

const tabs = useMemo(() => [
  // tab definitions
], [activeTabKey, formErrors, dataset, /* other dependencies */]);

📋 Code Quality Highlights

Proper Hook Rules: All hooks are called at the top level and conditionally
Dependency Arrays: Correctly specified for useEffect and useCallback
State Updates: Proper functional updates where needed
Type Safety: TypeScript types maintained throughout
Performance: useCallback used appropriately to prevent unnecessary re-renders

🚀 Technical Debt Reduction

This refactoring significantly reduces technical debt by:

  • Removing deprecated HOC patterns
  • Adopting modern React patterns
  • Improving code readability and maintainability
  • Setting up the codebase for future React features

Changelog

The changelog entry has a small typo: "Reactored" should be "Refactored":

- Reactored dataset settings and child tabs as React functional components.
+ Refactored dataset settings and child tabs as React functional components.

Overall: This is exemplary refactoring work that modernizes the codebase while maintaining functionality. Great job! 🎉

   (\   /)
  (  ._.)
 o_(")(")

The code looks so much cleaner now! This refactoring really showcases modern React best practices.

@hotzenklotz hotzenklotz marked this pull request as ready for review June 26, 2025 11:29
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 (4)
frontend/javascripts/dashboard/dataset/useBeforeUnload_hook.ts (1)

34-37: Consider simplifying the timeout logic.

The current approach temporarily disables the window.onbeforeunload handler and re-enables it after 500ms. This adds complexity and potential race conditions.

Consider a simpler approach that avoids the timeout mechanism:

-          window.onbeforeunload = null;
-          blockTimeoutIdRef.current = window.setTimeout(() => {
-            // @ts-ignore
-            window.onbeforeunload = beforeUnload;
-          }, 500);
+          // Return the message directly without manipulating window.onbeforeunload

The timeout appears to be working around duplicate prompts, but this might be better handled by checking the navigation state or using a flag to track whether a prompt is already showing.

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

81-81: Simplify the disabled prop.

The ternary operator is redundant here.

-      disabled={disabled ? disabled : false}
+      disabled={disabled}
frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx (2)

375-380: Document the force update workaround for clarity.

The pattern of using setActiveDataSourceEditMode((prev) => prev) with setTimeout to force a re-render is clever but non-obvious. Consider adding a more detailed comment.

    // Need to force update of the SimpleAdvancedDataForm as removing a layer in the advanced tab does not update
    // the form items in the simple tab (only the values are updated). The form items automatically update once
    // the simple tab renders, but this is not the case when the user directly submits the changes.
-   // In functional components, we can trigger a re-render by updating state
-   setActiveDataSourceEditMode((prev) => prev); // Force re-render
+   // In functional components, we can trigger a re-render by updating state.
+   // This setState call with the same value forces React to re-render the component,
+   // which is necessary to update the form items before validation.
+   setActiveDataSourceEditMode((prev) => prev);
    setTimeout(afterForceUpdateCallback, 0);

503-576: Consider memoizing the tabs array for performance.

The tabs array is recreated on every render. Since it contains complex JSX and conditional logic, memoizing it could improve performance.

Add at the top of the component:

import { useMemo } from "react";

Then wrap the tabs array:

- const tabs = [
+ const tabs = useMemo(() => [
    {
      label: <span> Data {formErrors.data ? errorIcon : ""}</span>,
      key: "data",
      // ... rest of tabs
    },
    // ... other tabs
- ];
+ ], [formErrors, errorIcon, activeTabKey, dataset, form, activeDataSourceEditMode, 
+     handleDataSourceEditModeChange, datasetId, isUserAdmin, maybeDataSourceId]);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c71944c and 40cfa40.

📒 Files selected for processing (7)
  • frontend/javascripts/dashboard/dataset/dataset_settings_delete_tab.tsx (2 hunks)
  • frontend/javascripts/dashboard/dataset/dataset_settings_sharing_tab.tsx (1 hunks)
  • frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx (10 hunks)
  • frontend/javascripts/dashboard/dataset/team_selection_component.tsx (1 hunks)
  • frontend/javascripts/dashboard/dataset/useBeforeUnload_hook.ts (1 hunks)
  • frontend/javascripts/messages.tsx (1 hunks)
  • unreleased_changes/8719.md (1 hunks)
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: philippotto
PR: scalableminds/webknossos#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.
frontend/javascripts/messages.tsx (1)
Learnt from: dieknolle3333
PR: scalableminds/webknossos#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`.
frontend/javascripts/dashboard/dataset/dataset_settings_sharing_tab.tsx (1)
Learnt from: philippotto
PR: scalableminds/webknossos#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.
frontend/javascripts/dashboard/dataset/useBeforeUnload_hook.ts (1)
Learnt from: knollengewaechs
PR: scalableminds/webknossos#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.
frontend/javascripts/dashboard/dataset/team_selection_component.tsx (1)
Learnt from: knollengewaechs
PR: scalableminds/webknossos#8266
File: frontend/javascripts/components/mag_selection.tsx:51-60
Timestamp: 2024-12-16T15:48:37.065Z
Learning: In the `MagSelection` component (`frontend/javascripts/components/mag_selection.tsx`), the selected magnification `value` will always be present in `allMags` because only existing values can be selected by the user.
frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx (1)
Learnt from: MichaelBuessemeyer
PR: scalableminds/webknossos#8485
File: frontend/javascripts/oxalis/model/accessors/dataset_layer_transformation_accessor.ts:384-392
Timestamp: 2025-04-01T09:45:17.527Z
Learning: The function `isRotationAndMirrorMaybeOnly` in the dataset_layer_transformation_accessor.ts is intentionally designed to allow mirroring transformations (negative scale values). It uses the length comparison (`scale.length() === NON_SCALED_VECTOR.length()`) rather than component equality to permit mirrored axes while ensuring the overall scale magnitude remains the same.
🧬 Code Graph Analysis (1)
frontend/javascripts/dashboard/dataset/useBeforeUnload_hook.ts (1)
frontend/javascripts/admin/dataset/dataset_upload_view.tsx (1)
  • unblockHistory (269-280)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: frontend-tests
  • GitHub Check: backend-tests
  • GitHub Check: build-smoketest-push
🔇 Additional comments (11)
frontend/javascripts/dashboard/dataset/useBeforeUnload_hook.ts (1)

31-31: Clarify the action === undefined condition.

The condition action === undefined seems unusual since history actions should typically be defined (PUSH, REPLACE, POP). This might indicate an edge case or potential bug.

Run this script to check how the history.block callback is used elsewhere in the codebase:

#!/bin/bash
# Search for other uses of history.block to understand expected action values
rg -A 5 -B 2 'history\.block'

# Also search for beforeUnload patterns
rg -A 10 'beforeUnload.*action.*undefined'
frontend/javascripts/messages.tsx (1)

336-336: LGTM! Improved user experience with clearer button text.

Changing "Cancel" to "Ok" better reflects the action of discarding unsaved changes, making the user interface more intuitive.

unreleased_changes/8719.md (1)

1-2: LGTM! Clear and accurate changelog entry.

The changelog entry accurately summarizes the refactoring work performed in this PR.

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

4-4: Excellent refactoring to modern React patterns!

The conversion from class/HOC patterns to functional components with hooks significantly improves code readability and maintainability:

  • useFetch hook simplifies dataset fetching and state management
  • useHistory hook eliminates the need for withRouter HOC
  • Functional component syntax is more concise and easier to understand

Also applies to: 7-8, 15-20

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

9-9: Excellent modernization removing HOC dependencies!

The refactoring successfully eliminates HOC patterns in favor of direct hook usage:

  • useWkSelector hook provides direct Redux store access, removing the need for connect HOC
  • Functional component syntax improves readability
  • Core functionality and UI remain unchanged

This aligns well with modern React best practices and the overall modernization effort in this PR.

Also applies to: 25-27

frontend/javascripts/dashboard/dataset/team_selection_component.tsx (3)

10-17: LGTM! Good naming convention for component props.

The rename from Props to TeamSelectionComponentProps follows React TypeScript best practices for more descriptive type names.


27-39: Excellent conversion of lifecycle methods to hooks!

The state initialization and effect hooks are properly implemented:

  • useEffect with [value] dependency correctly syncs state with prop changes
  • useEffectOnlyOnce appropriately replaces componentDidMount for initial data fetch

55-57: Good memoization of getAllTeams function!

Proper use of useCallback with correct dependencies prevents unnecessary re-renders in child components.

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

65-73: Excellent modernization to functional component with hooks!

Great job replacing HOCs with hooks:

  • useWkSelector for Redux state access
  • useQueryClient for React Query cache management
  • Form.useForm() for modern Ant Design form handling

405-411: Good handling of the infinite loop prevention!

The intentional exclusion of dataset from the dependency array with clear documentation is the correct approach here. The effect correctly accesses the latest dataset value when needed without causing re-execution loops.


65-620: Outstanding refactoring work!

This is an exemplary conversion from class to functional component:

  • ✅ Proper use of all React hooks with correct dependencies
  • ✅ Clean state management with multiple useState calls
  • ✅ Appropriate memoization with useCallback
  • ✅ Modern form handling with Form.useForm()
  • ✅ Excellent integration of custom hooks like useBeforeUnload
  • ✅ Maintains all original functionality while improving code quality

The code is now more maintainable and aligns with modern React best practices.

@hotzenklotz hotzenklotz requested a review from philippotto June 26, 2025 11:55
Copy link
Member

@philippotto philippotto left a comment

Choose a reason for hiding this comment

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

cool stuff 👍 I will do a final test after you addressed the small bit of feedback I left.

import { useCallback, useEffect, useRef } from "react";
import { useHistory } from "react-router-dom";

const useBeforeUnload = (hasUnsavedChanges: boolean, message: string) => {
Copy link
Member

Choose a reason for hiding this comment

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

the old code had some comments (which are needed in my opinion because the code is not straightforward):

// Only show the prompt if this is a proper beforeUnload event from the browser
// or the pathname changed
// This check has to be done because history.block triggers this function even if only the url hash changed
...
// clear the event handler otherwise it would be called twice. Once from history.block once from the beforeunload event
...
// restore the event handler in case a user chose to stay on the page

please restore them (or some adapted version of it).

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

if (this.props.onChange) {
this.props.onChange(Array.isArray(selectedTeamIdsOrId) ? selectedTeams : selectedTeams[0]);
const allTeams = getAllTeams();
const selected = _.compact(selectedTeamIds.map((id) => allTeams.find((t) => t.id === id)));
Copy link
Member

Choose a reason for hiding this comment

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

I find selectedTeams clearer 🤔

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

onChange={onSelectTeams}
value={selectedTeams.map((t) => t.id)}
filterOption
disabled={disabled ? disabled : false}
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
disabled={disabled ? disabled : false}
disabled={disabled ?? false}

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

afterFetchedTeams(possibleTeams);
}
} catch (_exception) {
console.error("Could not load teams.");
Copy link
Member

Choose a reason for hiding this comment

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

it's independent of the functional refactoring, but I'd use Toast.error here and setIsFetchingData should also be called with false to avoid an infinite loading state.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

Comment on lines 89 to 92
const unblockRef = useRef<UnregisterCallback | null>(null);
const blockTimeoutIdRef = useRef<number | null>(null);

const unblockHistory = useCallback(() => {
Copy link
Member

Choose a reason for hiding this comment

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

I think, this can be deleted completely. the refs are only set to null (and never anything else) and the unblock stuff should happen by the new useBeforeUnload hook once the component is unmounted (which should happen on cancel).

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

Comment on lines 375 to 380
// Need to force update of the SimpleAdvancedDataForm as removing a layer in the advanced tab does not update
// the form items in the simple tab (only the values are updated). The form items automatically update once
// the simple tab renders, but this is not the case when the user directly submits the changes.
this.forceUpdate(afterForceUpdateCallback);
};

submit = async (formValues: FormData) => {
const { dataset, datasetDefaultConfiguration } = this.state;
const datasetChangeValues = { ...formValues.dataset };

if (datasetChangeValues.sortingKey != null) {
datasetChangeValues.sortingKey = datasetChangeValues.sortingKey.valueOf();
}

const teamIds = formValues.dataset.allowedTeams.map((t) => t.id);
await updateDatasetPartial(this.props.datasetId, datasetChangeValues);

if (datasetDefaultConfiguration != null) {
await updateDatasetDefaultConfiguration(
this.props.datasetId,
_.extend({}, datasetDefaultConfiguration, formValues.defaultConfiguration, {
layers: JSON.parse(formValues.defaultConfigurationLayersJson),
}),
);
}

await updateDatasetTeams(this.props.datasetId, teamIds);
const dataSource = JSON.parse(formValues.dataSourceJson);

if (dataset != null && this.didDatasourceChange(dataSource)) {
if (this.didDatasourceIdChange(dataSource)) {
Toast.warning(messages["dataset.settings.updated_datasource_id_warning"]);
}
await updateDatasetDatasource(dataset.directoryName, dataset.dataStore.url, dataSource);
this.setState({
savedDataSourceOnServer: dataSource,
});
}

const verb = this.props.isEditingMode ? "updated" : "imported";
Toast.success(`Successfully ${verb} ${dataset?.name || this.props.datasetId}.`);
this.setState({
hasUnsavedChanges: false,
// In functional components, we can trigger a re-render by updating state
setActiveDataSourceEditMode((prev) => prev); // Force re-render
setTimeout(afterForceUpdateCallback, 0);
Copy link
Member

Choose a reason for hiding this comment

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

note to self to test this.

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/dataset/dataset_settings_view.tsx (1)

357-362: The force update pattern won't trigger a re-render

Setting state to the same value (setActiveDataSourceEditMode((prev) => prev)) doesn't trigger a re-render in React functional components. This won't achieve the intended forced update of SimpleAdvancedDataForm.

Consider using a proper force update pattern:

+  const [updateKey, setUpdateKey] = useState(0);

   const handleSubmit = useCallback(() => {
     // Ensure that all form fields are in sync
     syncDataSourceFields(form, activeDataSourceEditMode === "simple" ? "advanced" : "simple");

     const afterForceUpdateCallback = () => {
       // Trigger validation manually...
       setTimeout(
         () =>
           form
             .validateFields()
             .then((formValues) => submit(formValues))
             .catch((errorInfo) => handleValidationFailed(errorInfo)),
         0,
       );
     };

-    // In functional components, we can trigger a re-render by updating state
-    setActiveDataSourceEditMode((prev) => prev); // Force re-render
+    // Force re-render by incrementing a key
+    setUpdateKey((prev) => prev + 1);
     setTimeout(afterForceUpdateCallback, 0);
-  }, [form, activeDataSourceEditMode, submit, handleValidationFailed]);
+  }, [form, activeDataSourceEditMode, submit, handleValidationFailed]);

Then pass updateKey as a key prop to the component that needs to re-render:

<DatasetSettingsDataTab
-  key="SimpleAdvancedDataForm"
+  key={`SimpleAdvancedDataForm-${updateKey}`}
  dataset={dataset}
  form={form}
  activeDataSourceEditMode={activeDataSourceEditMode}
  onChange={handleDataSourceEditModeChange}
/>
🧹 Nitpick comments (1)
frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx (1)

480-553: Consider memoizing the tabs array for performance

The tabs array is recreated on every render. Since it contains complex JSX structures and conditional logic, memoizing it would improve performance.

Wrap the tabs array construction in useMemo:

-  const tabs = [
+  const tabs = useMemo(() => {
+    const tabItems = [
     {
       label: <span> Data {formErrors.data ? errorIcon : ""}</span>,
       key: "data",
       // ... rest of tabs
     },
   ];

   if (isUserAdmin && features().allowDeleteDatasets)
-    tabs.push({
+    tabItems.push({
       label: <span> Delete Dataset </span>,
       key: "deleteDataset",
       // ... rest of tab config
     });
+
+    return tabItems;
+  }, [formErrors, activeTabKey, dataset, form, activeDataSourceEditMode, 
+      handleDataSourceEditModeChange, datasetId, maybeDataSourceId, isUserAdmin]);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 40cfa40 and 72d4be6.

📒 Files selected for processing (3)
  • frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx (10 hunks)
  • frontend/javascripts/dashboard/dataset/team_selection_component.tsx (1 hunks)
  • frontend/javascripts/dashboard/dataset/useBeforeUnload_hook.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/javascripts/dashboard/dataset/useBeforeUnload_hook.ts
  • frontend/javascripts/dashboard/dataset/team_selection_component.tsx
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: philippotto
PR: scalableminds/webknossos#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.
frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx (1)
Learnt from: MichaelBuessemeyer
PR: scalableminds/webknossos#8485
File: frontend/javascripts/oxalis/model/accessors/dataset_layer_transformation_accessor.ts:384-392
Timestamp: 2025-04-01T09:45:17.527Z
Learning: The function `isRotationAndMirrorMaybeOnly` in the dataset_layer_transformation_accessor.ts is intentionally designed to allow mirroring transformations (negative scale values). It uses the length comparison (`scale.length() === NON_SCALED_VECTOR.length()`) rather than component equality to permit mirrored axes while ensuring the overall scale magnitude remains the same.
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: build-smoketest-push
  • GitHub Check: frontend-tests
  • GitHub Check: backend-tests
🔇 Additional comments (6)
frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx (6)

1-42: LGTM! Clean refactoring of imports

The imports have been properly updated to support the functional component refactoring, including the addition of React hooks and the custom useBeforeUnload hook.


64-86: Excellent conversion to functional component with hooks

The state management has been properly migrated from class state to individual useState hooks, and the form management uses the modern Form.useForm() pattern. The Redux state access via useWkSelector is also the correct approach.


88-184: Well-structured data fetching with proper error handling

The fetchData callback is correctly implemented with:

  • Appropriate dependency array [datasetId, form]
  • Proper loading state management
  • Comprehensive error handling
  • Correct form field initialization sequence

186-265: Validation logic properly migrated to callbacks

All validation and state-checking callbacks are correctly wrapped in useCallback with appropriate dependency arrays. The logic has been preserved from the original implementation while adapting to the functional component pattern.


378-388: Proper lifecycle management with hooks

Good use of the custom useBeforeUnload hook for handling unsaved changes. The useEffect correctly excludes dataset from dependencies with a clear comment explaining the reasoning to prevent infinite loops.


555-597: Excellent modernization of the component

The refactoring from class-based to functional component is comprehensive and well-executed:

  • Clean JSX structure with proper form handling
  • Correct usage of all React hooks
  • Maintained all original functionality
  • Improved code readability and maintainability

Great work on this modernization effort! 🎉

@hotzenklotz
Copy link
Member Author

@philippotto Thanks for starting a review here. A lot of this code has been used and further modified in the full PR for the dataset settings. So, some of these changes might be outdated already and not worth digging to deep. See ##8732

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/dataset/useBeforeUnload_hook.ts (1)

5-6: Consider addressing TypeScript suppression if possible.

The @ts-ignore comment indicates a type mismatch between the beforeUnload function signature and what useBlocker expects. While this might be legitimate due to overloaded signatures, consider if the typing can be improved.

Since this was flagged in previous reviews, the TypeScript suppression should be addressed by creating properly typed wrapper functions rather than using @ts-ignore.

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

479-552: Memoize the tabs array to avoid unnecessary re-renders.

The tabs array is recreated on every render, which could impact performance. Consider memoizing it with useMemo since it depends on several state values.

+ import { useMemo } from "react";

- const tabs = [
+ const tabs = useMemo(() => [
    {
      label: <span> Data {formErrors.data ? errorIcon : ""}</span>,
      key: "data",
      forceRender: true,
      children: (
        <Hideable hidden={activeTabKey !== "data"}>
          <DatasetSettingsDataTab
            key="SimpleAdvancedDataForm"
            dataset={dataset}
            form={form}
            activeDataSourceEditMode={activeDataSourceEditMode}
            onChange={handleDataSourceEditModeChange}
          />
        </Hideable>
      ),
    },
    // ... rest of tabs
- ];
+ ], [
+   formErrors.data,
+   formErrors.general,
+   formErrors.defaultConfig,
+   errorIcon,
+   activeTabKey,
+   dataset,
+   form,
+   activeDataSourceEditMode,
+   handleDataSourceEditModeChange,
+   datasetId,
+   maybeDataSourceId,
+   isUserAdmin
+ ]);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a5ac933 and c4a0fb3.

📒 Files selected for processing (4)
  • frontend/javascripts/dashboard/dataset/dataset_settings_delete_tab.tsx (2 hunks)
  • frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx (10 hunks)
  • frontend/javascripts/dashboard/dataset/useBeforeUnload_hook.ts (1 hunks)
  • frontend/javascripts/messages.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/javascripts/messages.tsx
  • frontend/javascripts/dashboard/dataset/dataset_settings_delete_tab.tsx
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: philippotto
PR: scalableminds/webknossos#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.
frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx (1)
Learnt from: MichaelBuessemeyer
PR: scalableminds/webknossos#8485
File: frontend/javascripts/oxalis/model/accessors/dataset_layer_transformation_accessor.ts:384-392
Timestamp: 2025-04-01T09:45:17.527Z
Learning: The function `isRotationAndMirrorMaybeOnly` in the dataset_layer_transformation_accessor.ts is intentionally designed to allow mirroring transformations (negative scale values). It uses the length comparison (`scale.length() === NON_SCALED_VECTOR.length()`) rather than component equality to permit mirrored axes while ensuring the overall scale magnitude remains the same.
frontend/javascripts/dashboard/dataset/useBeforeUnload_hook.ts (1)
Learnt from: knollengewaechs
PR: scalableminds/webknossos#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.
🧬 Code Graph Analysis (1)
frontend/javascripts/dashboard/dataset/useBeforeUnload_hook.ts (1)
frontend/javascripts/admin/dataset/dataset_upload_view.tsx (1)
  • unblockHistory (265-273)
🪛 Biome (1.9.4)
frontend/javascripts/dashboard/dataset/useBeforeUnload_hook.ts

[error] 43-43: beforeUnload changes on every re-render and should not be used as a hook dependency.

To fix this, wrap the definition of beforeUnload in its own useCallback() hook.

(lint/correctness/useExhaustiveDependencies)

⏰ 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 (4)
frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx (4)

377-378: Excellent integration of the useBeforeUnload hook.

The integration of the custom useBeforeUnload hook is clean and follows React best practices. It properly handles unsaved changes warnings during navigation.


63-86: Well-structured state management conversion.

The conversion from class state to multiple useState hooks is well-organized and follows React functional component best practices. Each piece of state is properly separated and initialized.


380-387: Acknowledge the intentional dependency omission.

The biome ignore comment correctly identifies that removing dataset from the dependency array is intentional to avoid infinite loops. This is a valid pattern when the effect accesses state inside the callback without needing to re-run when that state changes.

The pattern of accessing dataset inside the effect callback while omitting it from dependencies is correct here to prevent unnecessary re-fetches.


266-321: Comprehensive form submission logic with proper memoization.

The submit function conversion to useCallback is well-implemented with all necessary dependencies included. The function properly handles dataset updates, team management, data source changes, and cache invalidation.

The callback dependencies are correctly specified and the async logic maintains the original functionality while benefiting from proper memoization.

hotzenklotz and others added 4 commits July 16, 2025 14:21
This PR reworks the appearance of the dataset settings to be similar to
the account & orga settings pages.

- `DatasetSettingView`
- move form state, validation and helpers into a React.Context
(`DatasetSettingsContextProvider`)
- reworked all settings/inputs to use the `SettingsCards` to use the new
UI style
- defined each settings tab as a route. Let React router handle
displaying the correct tab
- refine all tabs as functional style components, i.e. more
`useCallback` etc
- rename "Data" tab to "Data Source" to differentiate it from metadata
and put an emphasis on configuring the origin and layer details. This is
only cosmetic in the page title and menu (and not the underlying data
structures).
- remove deprecated "display name" form field which was left over from
the new Dataset renaming PRs.
- handle synching between "simple" and "advanced" datasource "locally"
within the `<DatasetSettingsDataTab>` and Context provider and stop the
spreading of sync code in other components
- `SettingsCard`
   - renamed several props to be more general
- added proper typing to all setting items in the account & organization
views to avoid issues with refactoring/renaming in the future
 - Remote Dataset Upload
- split the components from file `dataset_add_remote_view.tsx` into
separate files:
     - No changes to `<GoogleAuthFormItem>` 
- Removed "simple"/"advanced" datasource view related props from
`<AddRemoteLayer>`

<img width="1597" height="993" alt="Screenshot 2025-07-16 at 15 31 33"
src="https://github.com/user-attachments/assets/e00bc7d1-4a56-472a-b164-0c0fe3936ee5"
/>


### URL of deployed dev instance (used for testing):
- https://datasetsettingscontext.webknossos.xyz/

### Testing
Editing and an existing dataset's settings:
1. Edit the setting of an existing dataset
2. switch between the simple/advanced view and make changes. Verify the
settings were synced between UI and JSON
3. switch between the different settings tabs, make changes and save.
Verify your changes were applied correctly.

Uploading new Remote dataset:
1. Add a new Remote dataset, e.g.
`gs://h01-release/data/20210601/4nm_raw`
2. Verify that the settings form is pre-filled correctly
3. Finish the importing of the remote dataset successfully


### Issues:
- fixes #8737

------
(Please delete unneeded items, merge only when none are left open)
- [x] 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](../blob/master/docs) if applicable
- [ ] Adapted [wk-libs python
client](https://github.com/scalableminds/webknossos-libs/tree/master/webknossos/webknossos/client)
if relevant API parts change
- [ ] Removed dev-only changes like prints and application.conf edits
- [ ] Considered [common edge
cases](../blob/master/.github/common_edge_cases.md)
- [ ] Needs datastore update after deployment

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@hotzenklotz
Copy link
Member Author

All changes were reviewed and tested as part of PR #8732

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 (5)
frontend/javascripts/dashboard/dataset/dataset_settings_provider.tsx (3)

175-178: Consider adding a TODO comment for the TypeScript issue

The @ts-ignore comment addresses a known Ant Design DatePicker type issue. Consider adding a TODO comment to track this for future resolution when Ant Design fixes the type definitions.

-          // @ts-ignore: The Antd DatePicker component requires a daysjs date object instead of plain number timestamp
+          // TODO: Remove @ts-ignore when Ant Design DatePicker types are fixed to accept dayjs objects
+          // @ts-ignore: The Antd DatePicker component requires a dayjs date object instead of plain number timestamp

380-381: Document the force update workaround pattern

This creative workaround for forcing form re-validation deserves better documentation for future maintainers.

+    // Force update pattern: Setting state to its current value triggers a re-render
+    // This ensures all form fields are mounted before validation
     setActiveDataSourceEditMode((prev) => prev);

148-149: Use optional chaining for cleaner code

The fallback assignments can be simplified using the nullish coalescing operator.

-  onComplete = onComplete ? onComplete : () => navigate("/dashboard");
-  onCancel = onCancel ? onCancel : () => navigate("/dashboard");
+  onComplete = onComplete ?? (() => navigate("/dashboard"));
+  onCancel = onCancel ?? (() => navigate("/dashboard"));
frontend/javascripts/dashboard/dataset/dataset_settings_viewconfig_tab.tsx (1)

41-42: Clarify the biome-ignore comment to reflect all dependencies.

The comment "validate on dataset change" is misleading since the dependency array correctly includes both availableMappingsPerLayerCache and dataset. Consider updating the comment to be more accurate.

-  // biome-ignore lint/correctness/useExhaustiveDependencies: validate on dataset change
+  // biome-ignore lint/correctness/useExhaustiveDependencies: Intentionally including specific dependencies for validation

Also applies to: 92-92

frontend/javascripts/admin/dataset/remote/add_remote_layer.tsx (1)

173-209: Consider extracting the credential handling logic.

The nested async IIFE for handling credentials is complex. Consider extracting this into a separate function for better readability and testability.

async function getExploreParams(
  showCredentialsFields: boolean,
  selectedProtocol: string,
  fileList: FileList,
  usernameOrAccessKey: string,
  passwordOrSecretKey: string,
  datasourceUrl: string,
  datastoreName: string,
  preferredVoxelSize: any
) {
  if (!showCredentialsFields) {
    return exploreRemoteDataset([datasourceUrl], datastoreName, null, preferredVoxelSize);
  }

  if (selectedProtocol === "gs") {
    const credentials = fileList.length > 0 ? await parseCredentials(fileList[0]?.originFileObj) : null;
    if (credentials) {
      return exploreRemoteDataset(
        [datasourceUrl],
        datastoreName,
        { username: "", pass: JSON.stringify(credentials) },
        preferredVoxelSize
      );
    }
  } else if (usernameOrAccessKey && passwordOrSecretKey) {
    return exploreRemoteDataset(
      [datasourceUrl],
      datastoreName,
      { username: usernameOrAccessKey, pass: passwordOrSecretKey },
      preferredVoxelSize
    );
  }

  return exploreRemoteDataset([datasourceUrl], datastoreName, null, preferredVoxelSize);
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c4a0fb3 and a3d0309.

📒 Files selected for processing (30)
  • frontend/javascripts/admin/account/account_auth_token_view.tsx (5 hunks)
  • frontend/javascripts/admin/account/account_password_view.tsx (4 hunks)
  • frontend/javascripts/admin/account/account_profile_view.tsx (3 hunks)
  • frontend/javascripts/admin/account/account_settings_view.tsx (1 hunks)
  • frontend/javascripts/admin/account/helpers/settings_card.tsx (1 hunks)
  • frontend/javascripts/admin/dataset/dataset_add_remote_view.tsx (3 hunks)
  • frontend/javascripts/admin/dataset/remote/add_remote_layer.tsx (1 hunks)
  • frontend/javascripts/admin/dataset/remote/google_auth_form_item.tsx (1 hunks)
  • frontend/javascripts/admin/onboarding.tsx (2 hunks)
  • frontend/javascripts/admin/organization/organization_danger_zone_view.tsx (1 hunks)
  • frontend/javascripts/admin/organization/organization_notifications_view.tsx (1 hunks)
  • frontend/javascripts/admin/organization/organization_overview_view.tsx (5 hunks)
  • frontend/javascripts/admin/organization/organization_view.tsx (1 hunks)
  • frontend/javascripts/components/async_clickables.tsx (3 hunks)
  • frontend/javascripts/dashboard/dataset/color_layer_ordering_component.tsx (2 hunks)
  • frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx (1 hunks)
  • frontend/javascripts/dashboard/dataset/dataset_settings_context.tsx (1 hunks)
  • frontend/javascripts/dashboard/dataset/dataset_settings_data_tab.tsx (7 hunks)
  • frontend/javascripts/dashboard/dataset/dataset_settings_delete_tab.tsx (2 hunks)
  • frontend/javascripts/dashboard/dataset/dataset_settings_metadata_tab.tsx (1 hunks)
  • frontend/javascripts/dashboard/dataset/dataset_settings_provider.tsx (1 hunks)
  • frontend/javascripts/dashboard/dataset/dataset_settings_sharing_tab.tsx (1 hunks)
  • frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx (3 hunks)
  • frontend/javascripts/dashboard/dataset/dataset_settings_viewconfig_tab.tsx (3 hunks)
  • frontend/javascripts/dashboard/dataset/helper_components.tsx (2 hunks)
  • frontend/javascripts/dashboard/dataset/useBeforeUnload_hook.ts (1 hunks)
  • frontend/javascripts/messages.tsx (3 hunks)
  • frontend/javascripts/router/route_wrappers.tsx (2 hunks)
  • frontend/javascripts/router/router.tsx (2 hunks)
  • unreleased_changes/8732.md (1 hunks)
🧠 Learnings (8)
📓 Common learnings
Learnt from: philippotto
PR: scalableminds/webknossos#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.
frontend/javascripts/admin/organization/organization_notifications_view.tsx (1)

Learnt from: MichaelBuessemeyer
PR: #8221
File: CHANGELOG.unreleased.md:14-14
Timestamp: 2024-11-25T14:52:23.201Z
Learning: For the webknossos project, changelog entries in CHANGELOG.unreleased.md should be concise and avoid unnecessary detailed explanations.

frontend/javascripts/admin/account/account_settings_view.tsx (1)

Learnt from: philippotto
PR: #8672
File: frontend/javascripts/admin/account/account_settings_view.tsx:50-50
Timestamp: 2025-06-25T13:16:26.966Z
Learning: When parsing URL pathnames in React Router applications, avoid using pathname.split("/").pop() as it fails with trailing slashes. Instead, use pathname.split("/").filter(Boolean) to remove empty segments and explicitly find the desired segment by index for more robust URL parsing.

frontend/javascripts/dashboard/dataset/dataset_settings_viewconfig_tab.tsx (2)

Learnt from: dieknolle3333
PR: #8229
File: frontend/javascripts/oxalis/model/accessors/dataset_accessor.ts:348-354
Timestamp: 2024-11-25T14:38:49.345Z
Learning: For the getDatasetExtentAsProduct function in dataset_accessor.ts, input validation for negative or zero dimensions is not necessary.

Learnt from: MichaelBuessemeyer
PR: #8221
File: frontend/javascripts/oxalis/view/action-bar/starting_job_modals.tsx:584-584
Timestamp: 2024-11-25T14:52:23.692Z
Learning: When evaluation is done, ensure that useAnnotation is always set to true in the form data by using a hidden Form.Item with initialValue={true}.

frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx (3)

Learnt from: MichaelBuessemeyer
PR: #8485
File: frontend/javascripts/oxalis/model/accessors/dataset_layer_transformation_accessor.ts:384-392
Timestamp: 2025-04-01T09:45:17.527Z
Learning: The function isRotationAndMirrorMaybeOnly in the dataset_layer_transformation_accessor.ts is intentionally designed to allow mirroring transformations (negative scale values). It uses the length comparison (scale.length() === NON_SCALED_VECTOR.length()) rather than component equality to permit mirrored axes while ensuring the overall scale magnitude remains the same.

Learnt from: MichaelBuessemeyer
PR: #8221
File: frontend/javascripts/oxalis/view/action-bar/starting_job_modals.tsx:584-584
Timestamp: 2024-11-25T14:52:23.692Z
Learning: When evaluation is done, ensure that useAnnotation is always set to true in the form data by using a hidden Form.Item with initialValue={true}.

Learnt from: dieknolle3333
PR: #8168
File: frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts:568-585
Timestamp: 2024-11-22T17:18:43.411Z
Learning: In the file frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts, the uniform name allResolutions should remain unchanged to maintain consistency with the shader code.

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

Learnt from: MichaelBuessemeyer
PR: #8485
File: frontend/javascripts/oxalis/model/accessors/dataset_layer_transformation_accessor.ts:384-392
Timestamp: 2025-04-01T09:45:17.527Z
Learning: The function isRotationAndMirrorMaybeOnly in the dataset_layer_transformation_accessor.ts is intentionally designed to allow mirroring transformations (negative scale values). It uses the length comparison (scale.length() === NON_SCALED_VECTOR.length()) rather than component equality to permit mirrored axes while ensuring the overall scale magnitude remains the same.

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

Learnt from: MichaelBuessemeyer
PR: #8221
File: frontend/javascripts/oxalis/view/action-bar/starting_job_modals.tsx:584-584
Timestamp: 2024-11-25T14:52:23.692Z
Learning: When evaluation is done, ensure that useAnnotation is always set to true in the form data by using a hidden Form.Item with initialValue={true}.

frontend/javascripts/admin/dataset/dataset_add_remote_view.tsx (2)

Learnt from: dieknolle3333
PR: #8229
File: frontend/javascripts/oxalis/model/accessors/dataset_accessor.ts:348-354
Timestamp: 2024-11-25T14:38:49.345Z
Learning: For the getDatasetExtentAsProduct function in dataset_accessor.ts, input validation for negative or zero dimensions is not necessary.

Learnt from: MichaelBuessemeyer
PR: #8221
File: frontend/javascripts/oxalis/view/action-bar/starting_job_modals.tsx:584-584
Timestamp: 2024-11-25T14:52:23.692Z
Learning: When evaluation is done, ensure that useAnnotation is always set to true in the form data by using a hidden Form.Item with initialValue={true}.

🧬 Code Graph Analysis (10)
frontend/javascripts/admin/onboarding.tsx (1)
frontend/javascripts/dashboard/dataset/dataset_settings_provider.tsx (1)
  • DatasetSettingsProvider (122-436)
frontend/javascripts/admin/organization/organization_notifications_view.tsx (1)
frontend/javascripts/admin/account/helpers/settings_card.tsx (1)
  • SettingsCard (12-32)
frontend/javascripts/admin/account/account_password_view.tsx (1)
frontend/javascripts/admin/account/helpers/settings_card.tsx (2)
  • SettingsCardProps (4-10)
  • SettingsCard (12-32)
frontend/javascripts/dashboard/dataset/dataset_settings_metadata_tab.tsx (2)
frontend/javascripts/admin/account/helpers/settings_card.tsx (2)
  • SettingsCardProps (4-10)
  • SettingsCard (12-32)
frontend/javascripts/admin/account/helpers/settings_title.tsx (1)
  • SettingsTitle (5-21)
frontend/javascripts/admin/account/account_settings_view.tsx (1)
frontend/javascripts/libs/window.ts (1)
  • location (60-60)
frontend/javascripts/dashboard/dataset/dataset_settings_context.tsx (3)
frontend/javascripts/types/api_types.ts (2)
  • APIDataSource (152-152)
  • APIDataset (242-245)
frontend/javascripts/viewer/store.ts (1)
  • DatasetConfiguration (263-288)
frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx (1)
  • DatasetRotationAndMirroringSettings (180-184)
frontend/javascripts/admin/dataset/remote/google_auth_form_item.tsx (2)
frontend/javascripts/admin/dataset/composition_wizard/common.ts (1)
  • FileList (8-8)
frontend/javascripts/viewer/constants.ts (1)
  • Unicode (279-283)
frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx (1)
frontend/javascripts/types/api_types.ts (1)
  • APIDataLayer (113-113)
frontend/javascripts/dashboard/dataset/dataset_settings_provider.tsx (10)
frontend/javascripts/dashboard/dataset/dataset_settings_context.tsx (4)
  • FormData (9-16)
  • DataSourceEditMode (7-7)
  • DatasetSettingsContextValue (18-32)
  • DatasetSettingsContext (34-36)
frontend/javascripts/libs/utils.ts (2)
  • jsonStringify (180-182)
  • parseMaybe (137-149)
frontend/javascripts/types/globals.d.ts (1)
  • ArbitraryObject (32-32)
frontend/javascripts/types/api_types.ts (4)
  • MutableAPIDataSource (148-151)
  • APIDataSource (152-152)
  • APIDataset (242-245)
  • MutableAPIDataset (238-241)
frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx (1)
  • DatasetRotationAndMirroringSettings (180-184)
frontend/javascripts/viewer/store.ts (1)
  • DatasetConfiguration (263-288)
frontend/javascripts/admin/rest_api.ts (7)
  • readDatasetDatasource (978-984)
  • getDatasetDefaultConfiguration (1081-1083)
  • updateDatasetPartial (1042-1050)
  • updateDatasetDefaultConfiguration (1085-1095)
  • updateDatasetTeams (1312-1320)
  • updateDatasetDatasource (986-1000)
  • sendAnalyticsEvent (113-124)
frontend/javascripts/types/schemas/dataset_view_configuration_defaults.ts (1)
  • enforceValidatedDatasetViewConfiguration (60-108)
frontend/javascripts/libs/error_handling.ts (1)
  • handleGenericError (42-55)
frontend/javascripts/dashboard/dataset/helper_components.tsx (1)
  • hasFormError (123-129)
frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx (3)
frontend/javascripts/dashboard/dataset/dataset_settings_context.tsx (1)
  • useDatasetSettingsContext (38-44)
frontend/javascripts/libs/react_hooks.ts (1)
  • useWkSelector (292-294)
frontend/javascripts/viewer/constants.ts (1)
  • Unicode (279-283)
✅ Files skipped from review due to trivial changes (7)
  • unreleased_changes/8732.md
  • frontend/javascripts/dashboard/dataset/helper_components.tsx
  • frontend/javascripts/admin/account/account_profile_view.tsx
  • frontend/javascripts/admin/organization/organization_view.tsx
  • frontend/javascripts/components/async_clickables.tsx
  • frontend/javascripts/admin/account/account_auth_token_view.tsx
  • frontend/javascripts/admin/organization/organization_overview_view.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • frontend/javascripts/messages.tsx
  • frontend/javascripts/dashboard/dataset/dataset_settings_delete_tab.tsx
  • frontend/javascripts/dashboard/dataset/useBeforeUnload_hook.ts
🧰 Additional context used
🧠 Learnings (8)
📓 Common learnings
Learnt from: philippotto
PR: scalableminds/webknossos#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.
frontend/javascripts/admin/organization/organization_notifications_view.tsx (1)

Learnt from: MichaelBuessemeyer
PR: #8221
File: CHANGELOG.unreleased.md:14-14
Timestamp: 2024-11-25T14:52:23.201Z
Learning: For the webknossos project, changelog entries in CHANGELOG.unreleased.md should be concise and avoid unnecessary detailed explanations.

frontend/javascripts/admin/account/account_settings_view.tsx (1)

Learnt from: philippotto
PR: #8672
File: frontend/javascripts/admin/account/account_settings_view.tsx:50-50
Timestamp: 2025-06-25T13:16:26.966Z
Learning: When parsing URL pathnames in React Router applications, avoid using pathname.split("/").pop() as it fails with trailing slashes. Instead, use pathname.split("/").filter(Boolean) to remove empty segments and explicitly find the desired segment by index for more robust URL parsing.

frontend/javascripts/dashboard/dataset/dataset_settings_viewconfig_tab.tsx (2)

Learnt from: dieknolle3333
PR: #8229
File: frontend/javascripts/oxalis/model/accessors/dataset_accessor.ts:348-354
Timestamp: 2024-11-25T14:38:49.345Z
Learning: For the getDatasetExtentAsProduct function in dataset_accessor.ts, input validation for negative or zero dimensions is not necessary.

Learnt from: MichaelBuessemeyer
PR: #8221
File: frontend/javascripts/oxalis/view/action-bar/starting_job_modals.tsx:584-584
Timestamp: 2024-11-25T14:52:23.692Z
Learning: When evaluation is done, ensure that useAnnotation is always set to true in the form data by using a hidden Form.Item with initialValue={true}.

frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx (3)

Learnt from: MichaelBuessemeyer
PR: #8485
File: frontend/javascripts/oxalis/model/accessors/dataset_layer_transformation_accessor.ts:384-392
Timestamp: 2025-04-01T09:45:17.527Z
Learning: The function isRotationAndMirrorMaybeOnly in the dataset_layer_transformation_accessor.ts is intentionally designed to allow mirroring transformations (negative scale values). It uses the length comparison (scale.length() === NON_SCALED_VECTOR.length()) rather than component equality to permit mirrored axes while ensuring the overall scale magnitude remains the same.

Learnt from: MichaelBuessemeyer
PR: #8221
File: frontend/javascripts/oxalis/view/action-bar/starting_job_modals.tsx:584-584
Timestamp: 2024-11-25T14:52:23.692Z
Learning: When evaluation is done, ensure that useAnnotation is always set to true in the form data by using a hidden Form.Item with initialValue={true}.

Learnt from: dieknolle3333
PR: #8168
File: frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts:568-585
Timestamp: 2024-11-22T17:18:43.411Z
Learning: In the file frontend/javascripts/oxalis/geometries/materials/plane_material_factory.ts, the uniform name allResolutions should remain unchanged to maintain consistency with the shader code.

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

Learnt from: MichaelBuessemeyer
PR: #8485
File: frontend/javascripts/oxalis/model/accessors/dataset_layer_transformation_accessor.ts:384-392
Timestamp: 2025-04-01T09:45:17.527Z
Learning: The function isRotationAndMirrorMaybeOnly in the dataset_layer_transformation_accessor.ts is intentionally designed to allow mirroring transformations (negative scale values). It uses the length comparison (scale.length() === NON_SCALED_VECTOR.length()) rather than component equality to permit mirrored axes while ensuring the overall scale magnitude remains the same.

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

Learnt from: MichaelBuessemeyer
PR: #8221
File: frontend/javascripts/oxalis/view/action-bar/starting_job_modals.tsx:584-584
Timestamp: 2024-11-25T14:52:23.692Z
Learning: When evaluation is done, ensure that useAnnotation is always set to true in the form data by using a hidden Form.Item with initialValue={true}.

frontend/javascripts/admin/dataset/dataset_add_remote_view.tsx (2)

Learnt from: dieknolle3333
PR: #8229
File: frontend/javascripts/oxalis/model/accessors/dataset_accessor.ts:348-354
Timestamp: 2024-11-25T14:38:49.345Z
Learning: For the getDatasetExtentAsProduct function in dataset_accessor.ts, input validation for negative or zero dimensions is not necessary.

Learnt from: MichaelBuessemeyer
PR: #8221
File: frontend/javascripts/oxalis/view/action-bar/starting_job_modals.tsx:584-584
Timestamp: 2024-11-25T14:52:23.692Z
Learning: When evaluation is done, ensure that useAnnotation is always set to true in the form data by using a hidden Form.Item with initialValue={true}.

🧬 Code Graph Analysis (10)
frontend/javascripts/admin/onboarding.tsx (1)
frontend/javascripts/dashboard/dataset/dataset_settings_provider.tsx (1)
  • DatasetSettingsProvider (122-436)
frontend/javascripts/admin/organization/organization_notifications_view.tsx (1)
frontend/javascripts/admin/account/helpers/settings_card.tsx (1)
  • SettingsCard (12-32)
frontend/javascripts/admin/account/account_password_view.tsx (1)
frontend/javascripts/admin/account/helpers/settings_card.tsx (2)
  • SettingsCardProps (4-10)
  • SettingsCard (12-32)
frontend/javascripts/dashboard/dataset/dataset_settings_metadata_tab.tsx (2)
frontend/javascripts/admin/account/helpers/settings_card.tsx (2)
  • SettingsCardProps (4-10)
  • SettingsCard (12-32)
frontend/javascripts/admin/account/helpers/settings_title.tsx (1)
  • SettingsTitle (5-21)
frontend/javascripts/admin/account/account_settings_view.tsx (1)
frontend/javascripts/libs/window.ts (1)
  • location (60-60)
frontend/javascripts/dashboard/dataset/dataset_settings_context.tsx (3)
frontend/javascripts/types/api_types.ts (2)
  • APIDataSource (152-152)
  • APIDataset (242-245)
frontend/javascripts/viewer/store.ts (1)
  • DatasetConfiguration (263-288)
frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx (1)
  • DatasetRotationAndMirroringSettings (180-184)
frontend/javascripts/admin/dataset/remote/google_auth_form_item.tsx (2)
frontend/javascripts/admin/dataset/composition_wizard/common.ts (1)
  • FileList (8-8)
frontend/javascripts/viewer/constants.ts (1)
  • Unicode (279-283)
frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx (1)
frontend/javascripts/types/api_types.ts (1)
  • APIDataLayer (113-113)
frontend/javascripts/dashboard/dataset/dataset_settings_provider.tsx (10)
frontend/javascripts/dashboard/dataset/dataset_settings_context.tsx (4)
  • FormData (9-16)
  • DataSourceEditMode (7-7)
  • DatasetSettingsContextValue (18-32)
  • DatasetSettingsContext (34-36)
frontend/javascripts/libs/utils.ts (2)
  • jsonStringify (180-182)
  • parseMaybe (137-149)
frontend/javascripts/types/globals.d.ts (1)
  • ArbitraryObject (32-32)
frontend/javascripts/types/api_types.ts (4)
  • MutableAPIDataSource (148-151)
  • APIDataSource (152-152)
  • APIDataset (242-245)
  • MutableAPIDataset (238-241)
frontend/javascripts/dashboard/dataset/dataset_rotation_form_item.tsx (1)
  • DatasetRotationAndMirroringSettings (180-184)
frontend/javascripts/viewer/store.ts (1)
  • DatasetConfiguration (263-288)
frontend/javascripts/admin/rest_api.ts (7)
  • readDatasetDatasource (978-984)
  • getDatasetDefaultConfiguration (1081-1083)
  • updateDatasetPartial (1042-1050)
  • updateDatasetDefaultConfiguration (1085-1095)
  • updateDatasetTeams (1312-1320)
  • updateDatasetDatasource (986-1000)
  • sendAnalyticsEvent (113-124)
frontend/javascripts/types/schemas/dataset_view_configuration_defaults.ts (1)
  • enforceValidatedDatasetViewConfiguration (60-108)
frontend/javascripts/libs/error_handling.ts (1)
  • handleGenericError (42-55)
frontend/javascripts/dashboard/dataset/helper_components.tsx (1)
  • hasFormError (123-129)
frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx (3)
frontend/javascripts/dashboard/dataset/dataset_settings_context.tsx (1)
  • useDatasetSettingsContext (38-44)
frontend/javascripts/libs/react_hooks.ts (1)
  • useWkSelector (292-294)
frontend/javascripts/viewer/constants.ts (1)
  • Unicode (279-283)
⏰ 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 (40)
frontend/javascripts/admin/account/account_settings_view.tsx (1)

47-51: Good improvement in path parsing clarity.

The change from filter(Boolean) to filter((p) => p.length > 0) makes the intent more explicit by filtering only empty strings rather than all falsy values. This aligns with the established pattern across similar admin views and improves code readability.

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

45-48: Good enhancement to form state preservation.

Adding the preserve: true option to Form.useWatch ensures that watched form values are maintained across form resets or unmounts. This is particularly important for nested form fields like ["dataSource", "dataLayers"] and aligns with the improved form state management in the dataset settings refactoring.

frontend/javascripts/admin/organization/organization_danger_zone_view.tsx (1)

57-57: Consistent prop naming standardization.

The change from description to content aligns with the standardized SettingsCard component interface used throughout the admin UI. This improves consistency and type safety across all SettingsCard usages.

frontend/javascripts/admin/organization/organization_notifications_view.tsx (3)

101-102: Consistent SettingsCard prop standardization.

The prop names have been updated from explanation and description to tooltip and content respectively, aligning with the standardized SettingsCard component interface used throughout the admin UI.


108-109: Consistent SettingsCard prop standardization.

The prop names have been updated from explanation and description to tooltip and content respectively, maintaining consistency with the updated SettingsCard interface.


115-116: Consistent SettingsCard prop standardization.

The prop names have been updated from explanation and description to tooltip and content respectively, completing the consistent standardization across all SettingsCard usages in this file.

frontend/javascripts/admin/account/account_password_view.tsx (4)

10-10: Good addition of type safety.

Adding the SettingsCardProps type import improves type safety for the passKeyList array and ensures consistency with the SettingsCard component interface.


158-165: Excellent type safety and prop standardization.

The explicit typing of passKeyList as SettingsCardProps[] and the property rename from value to content aligns with the standardized SettingsCard interface, improving both type safety and consistency.


174-174: Consistent SettingsCard prop usage.

The prop name change from description to content maintains consistency with the updated SettingsCard component interface used throughout the admin UI.


192-192: Proper property access after rename.

Correctly accessing item.content instead of item.value after the property rename in the passKeyList array structure.

frontend/javascripts/router/router.tsx (2)

55-59: LGTM: Clean imports for dataset settings tab components

The new imports are well-organized and follow consistent naming patterns for the refactored dataset settings tabs.


278-285: Excellent nested routing structure for dataset settings

The conversion from a self-closing route to nested routes with child routes is well-implemented:

  • Index route properly redirects to the "data" tab as the default
  • Each tab has a semantic route path that matches its functionality
  • The structure integrates cleanly with React Router's Outlet pattern
  • Route organization is consistent and maintainable

This aligns perfectly with the context-based refactoring and enables proper tabbed navigation.

frontend/javascripts/admin/onboarding.tsx (2)

23-23: LGTM: Proper import for context provider

The import follows the established pattern and is correctly placed with other dashboard imports.


531-538: Excellent integration with DatasetSettingsProvider

The wrapping of DatasetSettingsView with DatasetSettingsProvider is properly implemented:

  • isEditingMode={false} is appropriate for the import scenario during onboarding
  • datasetIdToImport is correctly passed as the datasetId prop
  • The onComplete and onCancel handlers are properly passed to the provider
  • DatasetSettingsView no longer receives props directly, consistent with the context-based refactoring

This change aligns perfectly with the broader refactoring to use context for state management.

frontend/javascripts/router/route_wrappers.tsx (2)

9-9: LGTM: Correct import for dataset settings provider

The import is properly placed and follows the established import organization.


103-106: Perfect implementation of provider pattern in route wrapper

The refactoring correctly wraps DatasetSettingsView with DatasetSettingsProvider:

  • isEditingMode={true} is appropriate for the dataset edit route context
  • datasetId is properly extracted from URL parameters and passed to the provider
  • DatasetSettingsView no longer receives direct props, consistent with the context-based architecture
  • The change maintains the existing route structure while enabling the new context-driven state management

This implementation is consistent with the pattern established in the onboarding modal and aligns with the overall refactoring objectives.

frontend/javascripts/dashboard/dataset/dataset_settings_metadata_tab.tsx (3)

1-3: LGTM: Clean imports for refactored UI components

The imports are well-organized and bring in the necessary components for the new SettingsCard-based layout.


6-27: Excellent structured approach to metadata form items

The metadataItems array is well-designed:

  • Clear separation of concerns with each item having title, tooltip, and content
  • Informative tooltips that provide helpful context to users
  • Proper form field nesting with ["dataset", "sortingKey"] and ["dataset", "description"]
  • Appropriate form components (DatePicker for dates, TextArea for descriptions)
  • Publication Date tooltip clearly explains the sorting functionality

This structured approach makes the code maintainable and follows good React patterns.


29-40: Perfect implementation of consistent UI layout

The layout implementation is excellent:

  • SettingsTitle provides consistent section header with description
  • Responsive grid layout with gutter={[24, 24]} for proper spacing
  • Col span={12} creates a clean two-column layout
  • Dynamic rendering of metadata items maintains DRY principles
  • Proper key assignment using item.title for React list rendering

The component follows the established SettingsCard pattern and integrates seamlessly with the context-based form management.

frontend/javascripts/admin/dataset/remote/google_auth_form_item.tsx (4)

1-6: LGTM: Well-organized imports

The imports are properly structured and include all necessary dependencies. Good use of type imports for better tree-shaking.


10-18: Excellent TypeScript interface design

The component props are well-typed:

  • FileList type from the composition wizard maintains consistency
  • UploadChangeParam provides proper typing for the change handler
  • Optional showOptionalHint flag enables flexible usage across different contexts

20-36: Great accessibility and user experience in form label

The label implementation is excellent:

  • Uses Unicode.NonBreakingSpace for proper spacing
  • External link to Google Cloud documentation with proper target="_blank" and rel="noopener noreferrer" for security
  • Conditional "(Optional)" hint improves user understanding
  • hasFeedback provides visual feedback for form validation

37-58: Perfect drag-and-drop upload implementation

The Upload.Dragger is well-configured:

  • beforeUpload={() => false} correctly prevents automatic upload, allowing manual control
  • Clear visual hierarchy with unlock icon and descriptive text
  • Informative hint text explains when the authentication is needed
  • Proper styling with appropriate icon size and margins
  • User-friendly copy that explains the drag-and-drop functionality

This provides an intuitive user experience for uploading Google Cloud authentication files.

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

1-78: Well-executed UI simplification!

The refactoring successfully removes unnecessary wrapper components while preserving all functionality. The use of useCallback hooks with correct dependencies prevents unnecessary re-renders, and the simplified conditional rendering improves code readability.

frontend/javascripts/admin/account/helpers/settings_card.tsx (1)

1-32: Clean prop renaming and UI improvements!

The prop names are now more intuitive (content and tooltip better describe their purpose). The switch from Popover to Tooltip is appropriate for simple explanatory text, and the added style prop provides useful customization flexibility.

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

1-44: Excellent context implementation with proper TypeScript typing!

The context structure follows React best practices with clear type definitions, proper error handling in the custom hook, and well-organized interfaces. This provides a solid foundation for centralized dataset settings management.

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

1-149: Excellent modernization to functional component!

The refactoring successfully converts the component to use hooks and context, with proper memoization of computed values and callbacks. The React Query integration for data fetching is clean, and the UI restructuring with SettingsCard components improves consistency.

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

122-436: Outstanding provider implementation with comprehensive state management!

This provider successfully centralizes all dataset settings logic with proper error handling, form validation, and API integration. The use of React Query for cache invalidation and the custom useBeforeUnload hook for unsaved changes are particularly well done.

frontend/javascripts/dashboard/dataset/dataset_settings_viewconfig_tab.tsx (2)

22-28: LGTM! Clean separation of concerns.

The pattern of checking for null dataset and delegating to a component with explicit props is a good approach for type safety and clear data flow.


159-303: Excellent UI restructuring with consistent card-based layout.

The refactoring to use SettingsCard components with consistent structure improves code maintainability and provides a uniform user experience. The switch from checkboxes to Switch components for boolean settings is a good modern UI choice.

frontend/javascripts/admin/dataset/dataset_add_remote_view.tsx (2)

140-142: Good documentation of the dataset name handling distinction.

The comment clearly explains why the dataset name is retrieved separately from the form field rather than from datasource.id.name, which helps future maintainers understand this important distinction.


231-233: Proper integration with the new context-based architecture.

Wrapping DatasetSettingsDataTab with DatasetSettingsProvider correctly integrates this component with the new context-based state management system.

frontend/javascripts/dashboard/dataset/dataset_settings_data_tab.tsx (4)

319-326: Well-documented workaround for form validation.

The biome-ignore comment clearly explains why only dataLayers is included in the dependencies. This is a reasonable workaround for the shouldUpdate limitation.


183-196: Great UX improvement with read-only dataset ID and copy functionality.

The addition of a copy button for the dataset ID improves user experience, and making the field read-only prevents accidental modifications.


634-634: Good simplification of React API usage.

Using the imported cloneElement directly instead of React.cloneElement is cleaner and follows modern import practices.


48-49: Address the TODO comment about useWatch stale values.

The comment suggests using the preserve option to fix stale values. This should be implemented to ensure data consistency.

-  // TODO: The "preserve" option probably fixes this, e.g. useWatch("dataSource", { form, preserve: true });
-  Form.useWatch("dataSource", { form, preserve: true });
+  Form.useWatch("dataSource", { form, preserve: true });
⛔ Skipped due to learnings
Learnt from: MichaelBuessemeyer
PR: scalableminds/webknossos#8221
File: frontend/javascripts/oxalis/view/action-bar/starting_job_modals.tsx:584-584
Timestamp: 2024-11-25T14:52:23.692Z
Learning: When evaluation is done, ensure that `useAnnotation` is always set to `true` in the form data by using a hidden `Form.Item` with `initialValue={true}`.
Learnt from: knollengewaechs
PR: scalableminds/webknossos#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.
frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx (3)

35-55: Excellent refactoring to functional component with hooks.

The conversion from class component to functional component is well-executed. The use of context for state management and the extraction of the selected tab from the URL path are clean implementations.


131-165: Great UX enhancement with validation error indicators.

The menu items showing error icons for tabs with validation errors is an excellent user experience improvement, making it immediately clear which tabs need attention.


197-203: Consider memoizing the navigation callback.

While the current implementation works, memoizing the navigateToTab callback could prevent unnecessary re-renders of child components if this callback is passed down as a prop in the future.

The current useCallback implementation is already optimal with correct dependencies.

frontend/javascripts/admin/dataset/remote/add_remote_layer.tsx (1)

25-69: Well-structured helper functions with good edge case handling.

The helper functions for ensuring largest segment IDs, merging layers with automatic renaming of duplicates, and parsing credentials are well-implemented with appropriate error handling.

Comment on lines +110 to +121
useEffect(() => {
if (defaultUrl != null) {
// only set datasourceUrl in the first render
if (datasourceUrl == null) {
form.setFieldValue("url", defaultUrl);
form.validateFields(["url"]);
} else {
handleExplore();
}
}
}, [defaultUrl, datasourceUrl, form.setFieldValue, form.validateFields]);

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

Fix missing dependencies in useEffect.

The useEffect hook is missing handleExplore in its dependencies, which could lead to stale closures.

-  }, [defaultUrl, datasourceUrl, form.setFieldValue, form.validateFields]);
+  }, [defaultUrl, datasourceUrl, form, handleExplore]);

However, this would cause an infinite loop since handleExplore is defined inline. Consider wrapping handleExplore in useCallback or moving the logic inside the effect.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
if (defaultUrl != null) {
// only set datasourceUrl in the first render
if (datasourceUrl == null) {
form.setFieldValue("url", defaultUrl);
form.validateFields(["url"]);
} else {
handleExplore();
}
}
}, [defaultUrl, datasourceUrl, form.setFieldValue, form.validateFields]);
useEffect(() => {
if (defaultUrl != null) {
// only set datasourceUrl in the first render
if (datasourceUrl == null) {
form.setFieldValue("url", defaultUrl);
form.validateFields(["url"]);
} else {
handleExplore();
}
}
}, [defaultUrl, datasourceUrl, form, handleExplore]);
🤖 Prompt for AI Agents
In frontend/javascripts/admin/dataset/remote/add_remote_layer.tsx around lines
110 to 121, the useEffect hook is missing handleExplore in its dependency array,
which can cause stale closures. To fix this, either wrap handleExplore in a
useCallback hook to memoize it and then add it to the dependencies, or move the
logic inside handleExplore directly into the useEffect to avoid the infinite
loop caused by inline function definitions.

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 (4)
frontend/javascripts/dashboard/dataset/dataset_settings_provider.tsx (4)

75-78: Consider adding validation after parsing JSON

The parsed JSON is cast to ArbitraryObject | null without validation. While parseMaybe handles parsing errors, consider validating the parsed structure before using it as a data source.

 const dataSourceFromAdvancedTab = parseMaybe(
   form.getFieldValue("dataSourceJson"),
 ) as ArbitraryObject | null;
+// Validate that parsed object has expected structure
+if (dataSourceFromAdvancedTab && !dataSourceFromAdvancedTab.dataLayers) {
+  console.warn("Parsed data source is missing required dataLayers property");
+}

148-149: Memoize default callback functions

The default callbacks are recreated on every render. Consider using useMemo to optimize performance.

-onComplete = onComplete ? onComplete : () => navigate("/dashboard");
-onCancel = onCancel ? onCancel : () => navigate("/dashboard");
+const memoizedOnComplete = useCallback(
+  onComplete || (() => navigate("/dashboard")),
+  [onComplete, navigate]
+);
+const memoizedOnCancel = useCallback(
+  onCancel || (() => navigate("/dashboard")),
+  [onCancel, navigate]
+);

Then use memoizedOnComplete and memoizedOnCancel throughout the component.


380-384: Document the force update pattern

The force update pattern is clever but non-obvious. Consider adding a more detailed comment explaining the mechanism.

 // Force update pattern: Setting state to its current value triggers a re-render
 // This ensures all form fields are mounted before validation
+// This workaround is necessary because form.validateFields() only validates mounted fields,
+// and we need to validate fields from all tabs, not just the currently visible one.
 setActiveDataSourceEditMode((prev) => prev);
 setTimeout(afterForceUpdateCallback, 0);

215-215: Fix typo in dependency array

There's an inconsistency in the dependency array formatting.

-}, [datasetId, form.setFieldsValue, form.validateFields]);
+}, [datasetId, form]);

Since you're using multiple form methods, it's cleaner to depend on the entire form object.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3d0309 and bfe1950.

📒 Files selected for processing (1)
  • frontend/javascripts/dashboard/dataset/dataset_settings_provider.tsx (1 hunks)
🧠 Learnings (2)
📓 Common learnings
Learnt from: philippotto
PR: scalableminds/webknossos#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.
frontend/javascripts/dashboard/dataset/dataset_settings_provider.tsx (1)

Learnt from: MichaelBuessemeyer
PR: #8485
File: frontend/javascripts/oxalis/model/accessors/dataset_layer_transformation_accessor.ts:384-392
Timestamp: 2025-04-01T09:45:17.527Z
Learning: The function isRotationAndMirrorMaybeOnly in the dataset_layer_transformation_accessor.ts is intentionally designed to allow mirroring transformations (negative scale values). It uses the length comparison (scale.length() === NON_SCALED_VECTOR.length()) rather than component equality to permit mirrored axes while ensuring the overall scale magnitude remains the same.

🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: philippotto
PR: scalableminds/webknossos#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.
frontend/javascripts/dashboard/dataset/dataset_settings_provider.tsx (1)

Learnt from: MichaelBuessemeyer
PR: #8485
File: frontend/javascripts/oxalis/model/accessors/dataset_layer_transformation_accessor.ts:384-392
Timestamp: 2025-04-01T09:45:17.527Z
Learning: The function isRotationAndMirrorMaybeOnly in the dataset_layer_transformation_accessor.ts is intentionally designed to allow mirroring transformations (negative scale values). It uses the length comparison (scale.length() === NON_SCALED_VECTOR.length()) rather than component equality to permit mirrored axes while ensuring the overall scale magnitude remains the same.

⏰ 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 (2)
frontend/javascripts/dashboard/dataset/dataset_settings_provider.tsx (2)

409-413: Well-implemented analytics tracking

Good practice to track when dataset settings are opened, with appropriate fallback for dataset name.


273-286: Good defensive programming in validation check

The isOnlyDatasourceIncorrectAndNotEdited function properly handles JSON parsing errors and validates the specific scenario where only the datasource has errors but hasn't been edited.

Comment on lines +176 to +177
// @ts-ignore: The Antd DatePicker component requires a daysjs date object instead of plain number timestamp
sortingKey: dayjs(fetchedDataset.sortingKey as any as Dayjs),
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Replace @ts-ignore with proper typing

Instead of using @ts-ignore, consider properly typing the date field.

-// @ts-ignore: The Antd DatePicker component requires a daysjs date object instead of plain number timestamp
-sortingKey: dayjs(fetchedDataset.sortingKey as any as Dayjs),
+sortingKey: dayjs(fetchedDataset.sortingKey),

The dayjs constructor accepts timestamps natively, so the cast to Dayjs is unnecessary.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// @ts-ignore: The Antd DatePicker component requires a daysjs date object instead of plain number timestamp
sortingKey: dayjs(fetchedDataset.sortingKey as any as Dayjs),
sortingKey: dayjs(fetchedDataset.sortingKey),
🤖 Prompt for AI Agents
In frontend/javascripts/dashboard/dataset/dataset_settings_provider.tsx at lines
176-177, remove the @ts-ignore comment and the unnecessary cast to Dayjs when
calling dayjs with fetchedDataset.sortingKey. Instead, ensure the type of
sortingKey is correctly defined as a number or compatible timestamp type so that
dayjs can accept it directly without type errors, eliminating the need for
ignoring TypeScript checks.

@hotzenklotz hotzenklotz enabled auto-merge (squash) July 22, 2025 13:06
@hotzenklotz hotzenklotz disabled auto-merge July 22, 2025 13:06
@hotzenklotz hotzenklotz merged commit 91e81ee into master Jul 22, 2025
5 checks passed
@hotzenklotz hotzenklotz deleted the functional_dataset_settings branch July 22, 2025 13:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants