Skip to content

Conversation

philippotto
Copy link
Member

@philippotto philippotto commented Jun 5, 2025

When splitting an agglomerate in the proofreading mode while being in a saved state and without any editable mapping yet being created, wk used to crash.
What happened? The mapping name was updated in the store. This would trigger the sagas that push update actions into the save queue. However, this did not happen synchronously. Instead, the proofreading saga would immediately call "ensureSavedState". As the save queue was not filled yet, this function call would immediately return.

As a solution to this problem, this PR implements a ping-pong mechanism in which "ensureSavedState" dispatches an action to which all diffing sagas have to respond. By responding to the action, we know that the diffing sagas have already run.

An alternative approach would have been to update some sort of version counter every time the annotation in the store changes. Then, the diffing sagas could store the newest version they have seen somewhere. The downside of this approach is that we would need to store these versions (and the "seen versions") per tracing. also, lots of store updates for these counters would happen. All of this can be avoided with the other approach described above.

Additionally, this PR changes 4 saga tests from a generator-style to an integrated style where the saga middleware is used.

URL of deployed dev instance (used for testing):

Steps to test:

  • create a new annotation
  • use the proofreading tool to split a segment (do this while no editable mapping exists yet and while being in a saved state before clicking "split")

Issues:


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

@philippotto philippotto self-assigned this Jun 5, 2025
Copy link
Contributor

coderabbitai bot commented Jun 5, 2025

📝 Walkthrough

Walkthrough

This update refactors save request handling and testing for tracing features. It introduces a new utility for sending save requests with authentication tokens, updates action handling in sagas for improved synchronization, and enhances test fixtures and helpers for consistent ID usage. Several test files are refactored for integration-style saga testing.

Changes

Files/Groups Change Summary
frontend/javascripts/admin/rest_api.ts Added sendSaveRequestWithToken for save requests with tokens.
frontend/javascripts/libs/utils.ts Added generic utility function areSetsEqual.
frontend/javascripts/viewer/model.ts Enhanced ensureSavedState to await diffing sagas before confirming save.
frontend/javascripts/viewer/model/actions/save_actions.ts Added ensureTracingsWereDiffedToSaveQueueAction and related types to action set.
frontend/javascripts/viewer/model/sagas/save_saga.ts Refactored to use sendSaveRequestWithToken, action channels, and improved control flow. Removed sendRequestWithToken.
frontend/javascripts/viewer/model/sagas/annotation_saga.tsx Added test-environment error logging in mutex acquisition saga.
frontend/javascripts/test/helpers/apiHelpers.ts Mocked and tracked calls to sendSaveRequestWithToken; extended test context.
frontend/javascripts/test/fixtures/skeletontracing_server_objects.ts
frontend/javascripts/test/fixtures/tasktracing_server_objects.ts
frontend/javascripts/test/fixtures/volumetracing_object.ts
frontend/javascripts/test/fixtures/volumetracing_server_objects.ts
Standardized and prefixed tracing/test IDs; centralized ID constants.
frontend/javascripts/test/global_mocks.ts Added global mocks for "antd" and "libs/render_independently" modules.
frontend/javascripts/test/libs/nml.spec.ts Updated expected filename to use prefixed task ID.
frontend/javascripts/test/reducers/volumetracing_reducer.spec.ts Replaced string IDs with imported VOLUME_TRACING_ID constant.
frontend/javascripts/test/backend-snapshot-tests/annotations.e2e.ts
frontend/javascripts/test/sagas/save_saga.spec.ts
Updated to use sendSaveRequestWithToken from API module.
frontend/javascripts/test/sagas/skeletontracing_saga.spec.ts
frontend/javascripts/test/sagas/volumetracing/volumetracing_saga.spec.ts
Refactored to integration-style saga tests using real store/middleware.
frontend/javascripts/test/sagas/saga_integration.mock.ts (deleted) Removed obsolete saga and UI module mocks.
frontend/javascripts/test/sagas/saga_integration.spec.ts
frontend/javascripts/test/sagas/volumetracing/bucket_eviction_helper.ts
frontend/javascripts/test/sagas/volumetracing/bucket_eviction_with_saving.spec.ts
frontend/javascripts/test/sagas/volumetracing/bucket_eviction_without_saving.spec.ts
frontend/javascripts/test/sagas/volumetracing/volumetracing_saga_integration_1.spec.ts
frontend/javascripts/test/sagas/volumetracing/volumetracing_saga_integration_2.spec.ts
Removed imports of obsolete saga integration mocks and manual saga setup dispatches.

Possibly related PRs

  • scalableminds/webknossos#8139: Modifies doWithToken for improved token retry logic, which is directly used by the new sendSaveRequestWithToken function added here.

Suggested labels

frontend, backend, bug, testing

Suggested reviewers

  • MichaelBuessemeyer

Poem

In the warren of code, the tracings align,
With tokens in paw, save requests now shine.
IDs wear new hats, all prefixed with pride,
While sagas and tests hop side by side.
The burrow feels fresh, integration in bloom—
A bunny’s delight in the code’s tidy room! 🐇✨


📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 43dd43a and 6630dc3.

📒 Files selected for processing (10)
  • frontend/javascripts/test/api/api_skeleton_latest.spec.ts (1 hunks)
  • frontend/javascripts/test/api/api_volume_latest.spec.ts (0 hunks)
  • frontend/javascripts/test/helpers/apiHelpers.ts (6 hunks)
  • frontend/javascripts/test/sagas/saga_integration.spec.ts (0 hunks)
  • frontend/javascripts/test/sagas/skeletontracing_saga.spec.ts (2 hunks)
  • frontend/javascripts/test/sagas/volumetracing/bucket_eviction_with_saving.spec.ts (0 hunks)
  • frontend/javascripts/test/sagas/volumetracing/bucket_eviction_without_saving.spec.ts (0 hunks)
  • frontend/javascripts/test/sagas/volumetracing/volumetracing_saga.spec.ts (2 hunks)
  • frontend/javascripts/test/sagas/volumetracing/volumetracing_saga_integration_1.spec.ts (1 hunks)
  • frontend/javascripts/test/sagas/volumetracing/volumetracing_saga_integration_2.spec.ts (1 hunks)
💤 Files with no reviewable changes (4)
  • frontend/javascripts/test/api/api_volume_latest.spec.ts
  • frontend/javascripts/test/sagas/volumetracing/bucket_eviction_without_saving.spec.ts
  • frontend/javascripts/test/sagas/volumetracing/bucket_eviction_with_saving.spec.ts
  • frontend/javascripts/test/sagas/saga_integration.spec.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • frontend/javascripts/test/sagas/volumetracing/volumetracing_saga_integration_1.spec.ts
  • frontend/javascripts/test/sagas/volumetracing/volumetracing_saga_integration_2.spec.ts
  • frontend/javascripts/test/sagas/volumetracing/volumetracing_saga.spec.ts
  • frontend/javascripts/test/sagas/skeletontracing_saga.spec.ts
  • frontend/javascripts/test/helpers/apiHelpers.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
frontend/javascripts/test/api/api_skeleton_latest.spec.ts (1)
frontend/javascripts/test/helpers/apiHelpers.ts (1)
  • setupWebknossosForTesting (196-273)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: backend-tests
  • GitHub Check: build-smoketest-push
  • GitHub Check: frontend-tests
🔇 Additional comments (1)
frontend/javascripts/test/api/api_skeleton_latest.spec.ts (1)

13-14: Disabling wkReady dispatch may leave sagas idle – double-check that every API call used here still works without saga side-effects

By passing { dontDispatchWkReady: true }, none of the application sagas are started during test setup.
Most of the assertions here manipulate store state synchronously, but a few (setMappingEnabledAction, setTreeGroupsAction, api.tracing.setTreeGroup, etc.) normally trigger side-effect handlers that live in sagas (e.g. save-queue handling, diffing, pulling buckets).

If any implementation detail in the future begins to rely on those sagas (e.g. to populate defaults or enqueue saves) these tests may silently diverge from production behaviour and give false confidence.

Consider:

- await setupWebknossosForTesting(context, "skeleton", { dontDispatchWkReady: true });
+ await setupWebknossosForTesting(context, "skeleton");
+ // If a particular test needs sagas suppressed, do it locally:
+ // Store.dispatch(pauseAllSagasForTestAction());

or add an explicit comment explaining why sagas must stay idle and verify critical saga-produced state with targeted unit tests.

✨ 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.

@philippotto philippotto marked this pull request as ready for review June 6, 2025 14:28
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
frontend/javascripts/admin/rest_api.ts (1)

2403-2410: Well-implemented centralized save request function.

The function properly uses doWithToken for authentication and handles the JSON request correctly. The comment about ideally generating the full URL internally is a good architectural note for future refactoring.

Consider the architectural improvement mentioned in the comment:

-export function sendSaveRequestWithToken(
-  urlWithoutToken: string,
-  data: RequestOptionsWithData<Array<SaveQueueEntry>>,
-): Promise<void> {
-  // Ideally, this function should be refactored further so that it generates the
-  // correct urlWithoutToken itself.
-  return doWithToken((token) => Request.sendJSONReceiveJSON(`${urlWithoutToken}${token}`, data));
-}
+export function sendSaveRequestWithToken(
+  tracingStoreUrl: string,
+  annotationId: string,
+  data: RequestOptionsWithData<Array<SaveQueueEntry>>,
+): Promise<void> {
+  return doWithToken((token) => 
+    Request.sendJSONReceiveJSON(
+      `${tracingStoreUrl}/tracings/annotation/${annotationId}/update?token=${token}`, 
+      data
+    )
+  );
+}

This would eliminate the need for callers to construct the URL prefix and make the function more self-contained.

frontend/javascripts/test/sagas/volumetracing/volumetracing_saga.spec.ts (1)

130-453: Consider migrating remaining manual saga tests to integration style.

The file now contains a mix of integration-style tests (lines 84-128) and manual saga generator tests (lines 130-453). While this might be intentional for gradual migration, maintaining two different testing approaches in the same file could lead to confusion and maintenance overhead.

Consider either:

  1. Migrating all tests to the integration style for consistency
  2. Splitting into separate files if both approaches need to be maintained
  3. Adding a comment explaining why both approaches are kept
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between c240321 and 4d9e7f3.

⛔ Files ignored due to path filters (1)
  • frontend/javascripts/test/libs/__snapshots__/nml.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (25)
  • frontend/javascripts/admin/rest_api.ts (3 hunks)
  • frontend/javascripts/libs/utils.ts (1 hunks)
  • frontend/javascripts/test/backend-snapshot-tests/annotations.e2e.ts (2 hunks)
  • frontend/javascripts/test/fixtures/skeletontracing_server_objects.ts (1 hunks)
  • frontend/javascripts/test/fixtures/tasktracing_server_objects.ts (7 hunks)
  • frontend/javascripts/test/fixtures/volumetracing_object.ts (1 hunks)
  • frontend/javascripts/test/fixtures/volumetracing_server_objects.ts (1 hunks)
  • frontend/javascripts/test/global_mocks.ts (1 hunks)
  • frontend/javascripts/test/helpers/apiHelpers.ts (4 hunks)
  • frontend/javascripts/test/libs/nml.spec.ts (1 hunks)
  • frontend/javascripts/test/reducers/volumetracing_reducer.spec.ts (4 hunks)
  • frontend/javascripts/test/sagas/saga_integration.mock.ts (0 hunks)
  • frontend/javascripts/test/sagas/saga_integration.spec.ts (0 hunks)
  • frontend/javascripts/test/sagas/save_saga.spec.ts (4 hunks)
  • frontend/javascripts/test/sagas/skeletontracing_saga.spec.ts (2 hunks)
  • frontend/javascripts/test/sagas/volumetracing/bucket_eviction_helper.ts (0 hunks)
  • frontend/javascripts/test/sagas/volumetracing/bucket_eviction_with_saving.spec.ts (0 hunks)
  • frontend/javascripts/test/sagas/volumetracing/bucket_eviction_without_saving.spec.ts (0 hunks)
  • frontend/javascripts/test/sagas/volumetracing/volumetracing_saga.spec.ts (2 hunks)
  • frontend/javascripts/test/sagas/volumetracing/volumetracing_saga_integration_1.spec.ts (0 hunks)
  • frontend/javascripts/test/sagas/volumetracing/volumetracing_saga_integration_2.spec.ts (0 hunks)
  • frontend/javascripts/viewer/model.ts (3 hunks)
  • frontend/javascripts/viewer/model/actions/save_actions.ts (3 hunks)
  • frontend/javascripts/viewer/model/sagas/annotation_saga.tsx (1 hunks)
  • frontend/javascripts/viewer/model/sagas/save_saga.ts (5 hunks)
💤 Files with no reviewable changes (7)
  • frontend/javascripts/test/sagas/volumetracing/bucket_eviction_with_saving.spec.ts
  • frontend/javascripts/test/sagas/volumetracing/bucket_eviction_without_saving.spec.ts
  • frontend/javascripts/test/sagas/volumetracing/volumetracing_saga_integration_1.spec.ts
  • frontend/javascripts/test/sagas/volumetracing/volumetracing_saga_integration_2.spec.ts
  • frontend/javascripts/test/sagas/saga_integration.spec.ts
  • frontend/javascripts/test/sagas/volumetracing/bucket_eviction_helper.ts
  • frontend/javascripts/test/sagas/saga_integration.mock.ts
🧰 Additional context used
🧬 Code Graph Analysis (8)
frontend/javascripts/test/reducers/volumetracing_reducer.spec.ts (1)
frontend/javascripts/test/fixtures/volumetracing_object.ts (1)
  • VOLUME_TRACING_ID (6-6)
frontend/javascripts/admin/rest_api.ts (3)
frontend/javascripts/libs/request.ts (1)
  • RequestOptionsWithData (33-35)
frontend/javascripts/viewer/store.ts (1)
  • SaveQueueEntry (399-409)
frontend/javascripts/admin/api/token.ts (1)
  • doWithToken (39-74)
frontend/javascripts/test/fixtures/skeletontracing_server_objects.ts (2)
frontend/javascripts/test/fixtures/tasktracing_server_objects.ts (1)
  • tracing (9-69)
frontend/javascripts/types/api_types.ts (1)
  • ServerSkeletonTracing (904-914)
frontend/javascripts/test/fixtures/tasktracing_server_objects.ts (3)
frontend/javascripts/test/fixtures/skeletontracing_server_objects.ts (1)
  • annotation (167-219)
frontend/javascripts/test/fixtures/volumetracing_server_objects.ts (1)
  • annotation (75-126)
frontend/javascripts/types/api_types.ts (1)
  • APIAnnotation (584-586)
frontend/javascripts/viewer/model.ts (1)
frontend/javascripts/viewer/model/actions/save_actions.ts (1)
  • ensureTracingsWereDiffedToSaveQueueAction (130-134)
frontend/javascripts/test/helpers/apiHelpers.ts (2)
frontend/javascripts/viewer/store.ts (1)
  • SaveQueueEntry (399-409)
frontend/javascripts/admin/rest_api.ts (1)
  • sendSaveRequestWithToken (2403-2410)
frontend/javascripts/test/sagas/save_saga.spec.ts (1)
frontend/javascripts/admin/rest_api.ts (1)
  • sendSaveRequestWithToken (2403-2410)
frontend/javascripts/test/sagas/skeletontracing_saga.spec.ts (4)
frontend/javascripts/test/helpers/apiHelpers.ts (2)
  • WebknossosTestContext (47-56)
  • setupWebknossosForTesting (193-253)
frontend/javascripts/viewer/model/actions/save_actions.ts (1)
  • discardSaveQueuesAction (75-78)
frontend/javascripts/viewer/model/sagas/root_saga.ts (1)
  • hasRootSagaCrashed (50-52)
frontend/javascripts/viewer/model/actions/skeletontracing_actions.tsx (1)
  • createNodeAction (189-213)
🔇 Additional comments (28)
frontend/javascripts/viewer/model/actions/save_actions.ts (2)

26-28: LGTM! New action type properly integrated.

The new EnsureTracingsWereDiffedToSaveQueueAction type is correctly defined and added to the SaveAction union type, maintaining type safety.

Also applies to: 40-41


129-134: Well-implemented ping-pong mechanism for race condition fix.

The new action creator follows the established callback pattern used by undoAction and redoAction, providing a clean solution for the synchronization issue described in the PR objectives. The callback signature with tracingId parameter aligns well with the diffing saga requirements.

frontend/javascripts/test/backend-snapshot-tests/annotations.e2e.ts (2)

177-184: LGTM! Function call updated consistently with import change.

The function call has been properly updated to use the new api.sendSaveRequestWithToken while maintaining the same URL construction and request options.


14-14:

✅ Verification successful

Verify the API compatibility for the refactored save request function.

The import has been changed from sendRequestWithToken (save_saga) to sendSaveRequestWithToken (admin/rest_api). Ensure the new function maintains the same signature and behavior.


🏁 Script executed:

#!/bin/bash
# Description: Verify the signature compatibility between old and new save request functions

# Search for the new function definition in admin/rest_api
echo "=== New function signature in admin/rest_api ==="
ast-grep --pattern 'export $_ sendSaveRequestWithToken($$$) {
  $$$
}'

# Search for any remaining usage of the old function
echo "=== Check for remaining usage of sendRequestWithToken ==="
rg "sendRequestWithToken" --type ts

Length of output: 340


The initial ast-grep didn’t locate the new function definition. Let’s search for both functions to compare their signatures:


🏁 Script executed:

#!/bin/bash
echo "=== sendSaveRequestWithToken occurrences ==="
rg -n "sendSaveRequestWithToken" -A 5 -B 2 --type ts

echo "=== sendRequestWithToken occurrences ==="
rg -n "sendRequestWithToken" -A 5 -B 2 --type ts

Length of output: 8024


API compatibility confirmed for sendSaveRequestWithToken

The new function in frontend/javascripts/admin/rest_api.ts

export function sendSaveRequestWithToken(
  urlWithoutToken: string,
  data: RequestOptionsWithData<Array<SaveQueueEntry>>,
): Promise<void> {  }

matches all existing call sites (sagas, e2e tests, and unit tests), and there are no remaining references to the old sendRequestWithToken. This refactoring maintains the same signature and behavior—no further changes needed.

frontend/javascripts/test/fixtures/volumetracing_object.ts (2)

6-6: Good refactoring: Centralized tracing ID constant.

Extracting the volume tracing ID into a named constant improves maintainability and consistency across the codebase.


15-15: LGTM! Property updated to use the centralized constant.

The tracingId property now correctly references the exported VOLUME_TRACING_ID constant, maintaining consistency.

frontend/javascripts/test/fixtures/volumetracing_server_objects.ts (1)

8-8: LGTM! Improved naming convention for consistency.

The renaming from "tracingId-1234" to "volumeTracingId-1234" aligns with the explicit naming conventions used in other test fixtures, making the tracing type more clear and consistent.

frontend/javascripts/test/libs/nml.spec.ts (1)

558-560: LGTM! Test expectation updated to match standardized ID format.

The test correctly expects the new filename format with the "taskId-" prefix, which aligns with the broader standardization of ID formats across test fixtures.

frontend/javascripts/libs/utils.ts (1)

1307-1313: LGTM! Well-implemented set equality utility function.

The implementation is efficient with early size checking and correctly compares set equality. The generic typing makes it reusable across different data types.

frontend/javascripts/test/reducers/volumetracing_reducer.spec.ts (2)

9-9: LGTM! Good refactoring to use centralized test constants.

Importing the VOLUME_TRACING_ID constant improves test maintainability and consistency.


116-116: LGTM! Consistent replacement of hardcoded strings with constants.

Replacing hardcoded "tracingId" strings with the VOLUME_TRACING_ID constant eliminates potential typos and improves test maintainability. All replacements are consistent and appropriate.

Also applies to: 163-163, 233-233

frontend/javascripts/viewer/model/sagas/annotation_saga.tsx (1)

314-320: LGTM! Improves test error visibility without affecting production.

The conditional error logging for test environments addresses a real issue where console.error output after yield might not be visible due to test assertions throwing. The change is well-commented and isolated to test environments only.

frontend/javascripts/test/global_mocks.ts (1)

116-143: LGTM! Well-structured UI component mocks.

The antd mock provides comprehensive coverage of commonly used UI components with appropriate stub implementations. The message methods correctly return hide functions matching the real API, and the simple mock for libs/render_independently is suitable for test isolation.

frontend/javascripts/test/fixtures/skeletontracing_server_objects.ts (2)

8-8: Good practice: Using semantic ID prefixes for test consistency.

The prefixed ID format improves clarity about the entity type and aligns with similar updates across other test fixtures.


12-12: LGTM! Using constant instead of hardcoded string.

Referencing the TRACING_ID constant improves maintainability and reduces duplication.

frontend/javascripts/test/sagas/save_saga.spec.ts (2)

21-21: LGTM! Updated import for centralized save request function.

Correctly imports the new sendSaveRequestWithToken function from the admin REST API module.


142-142: Consistent update across all test cases.

All test references have been properly updated to use the centralized sendSaveRequestWithToken function while maintaining the same call signature and behavior.

Also applies to: 165-165, 213-213

frontend/javascripts/admin/rest_api.ts (1)

3-3: LGTM! Proper type imports for the new function.

The RequestOptionsWithData and SaveQueueEntry types are correctly imported to support the new sendSaveRequestWithToken function.

Also applies to: 93-93

frontend/javascripts/test/helpers/apiHelpers.ts (3)

32-32: LGTM! Appropriate imports for test mocking.

The imports are correctly structured to support mocking the save request functionality with proper type safety.

Also applies to: 40-40


72-85: Well-structured mock implementation for save request tracking.

The mock effectively captures all save request payloads in receivedDataPerRequest array, enabling tests to verify the data sent through the saga. The approach of attaching the array to the mock function is a clean pattern for test data access.


55-55: Clean integration of mock data into test context.

The interface extension and test context setup provide type-safe access to the captured save request data, making it straightforward for tests to verify saga behavior.

Also applies to: 212-212

frontend/javascripts/test/fixtures/tasktracing_server_objects.ts (1)

8-8: Good improvement to test fixture clarity with ID prefixes.

The addition of descriptive prefixes to all IDs (e.g., "skeletonTracingId-", "datasetId-", etc.) enhances test readability and debugging. The comment clarifying the empty name test case is also helpful.

Also applies to: 45-45, 71-71, 74-74, 80-80, 88-88, 101-101, 159-159, 168-168, 179-179

frontend/javascripts/viewer/model.ts (2)

13-16: Appropriate imports for the synchronization mechanism.

The imports provide the necessary actions and utilities for implementing the ping-pong synchronization between the model and sagas.

Also applies to: 25-25


346-388: Excellent implementation of the ping-pong synchronization mechanism.

The new ensureSavedState implementation effectively addresses the race condition by:

  1. Ensuring all diffing sagas have processed updates before checking save state
  2. Using a callback mechanism to track responses from all active tracings
  3. Properly handling both skeleton and volume tracings with compact() to filter nulls
  4. Using areSetsEqual for robust set comparison

The solution is clean, well-documented, and should reliably prevent the premature return issue described in the PR objectives.

frontend/javascripts/test/sagas/volumetracing/volumetracing_saga.spec.ts (1)

84-128: Great refactoring to integration-style testing!

The migration from manual saga generator testing to integration tests using real saga middleware provides better confidence in the actual behavior of the system. The tests now properly verify the real save requests sent to the backend.

frontend/javascripts/test/sagas/skeletontracing_saga.spec.ts (1)

140-179: Well-implemented integration tests with proper saga lifecycle management.

The integration tests correctly:

  • Set up the Webknossos environment with skeleton tracing
  • Dispatch necessary actions to initialize the sagas
  • Use Model.ensureSavedState() to wait for save completion
  • Verify the actual save requests sent to the backend
  • Clean up resources and verify saga health after each test
frontend/javascripts/viewer/model/sagas/save_saga.ts (2)

375-411: Excellent implementation of the ping-pong synchronization mechanism!

The refactored setupSavingForTracingType saga effectively addresses the race condition by:

  1. Using an expanding buffer to queue tracing actions, preventing action loss
  2. Creating a separate channel for ENSURE_TRACINGS_WERE_DIFFED_TO_SAVE_QUEUE actions
  3. Prioritizing user actions over synchronization requests to ensure all pending changes are processed

This implementation correctly ensures that ensureSavedState waits for all diffing sagas to complete before returning.


413-429: Smart prioritization logic prevents race conditions.

The conditional logic that prioritizes consuming buffered user actions before responding to synchronization requests is well-designed. This ensures that ensureSavedState only gets a response after all pending tracing actions have been processed and diffed.

The use of race when the buffer is empty allows the saga to respond to either new user actions or synchronization requests, maintaining responsiveness.

@philippotto philippotto requested a review from daniel-wer June 6, 2025 19:18
Copy link
Member

@daniel-wer daniel-wer left a comment

Choose a reason for hiding this comment

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

Nice fix, also good job converting those saga tests to integration tests - much easier to understand and also more robust for the future. Testing went well, didn't spot any issues!

Comment on lines 86 to 94
// Setup Webknossos
// this will execute model.fetch(...) and initialize the store with the tracing, etc.
Store.dispatch(restartSagaAction());
Store.dispatch(discardSaveQueuesAction());
Store.dispatch(setActiveUserAction(dummyUser));
await setupWebknossosForTesting(context, "volume");

// Dispatch the wkReadyAction, so the sagas are started
Store.dispatch(wkReadyAction());
Copy link
Member

Choose a reason for hiding this comment

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

At some point it probably makes sense to extract this and the teardown code into a test helper function, but this is not a blocker for this PR

Copy link
Member Author

Choose a reason for hiding this comment

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

good point! I found that I can integrate the code simply into setupWebknossosForTesting.

@philippotto philippotto enabled auto-merge (squash) June 12, 2025 12:19
@philippotto philippotto merged commit 723a5dc into master Jun 12, 2025
5 checks passed
@philippotto philippotto deleted the fix-await-saving branch June 12, 2025 12:24
philippotto added a commit that referenced this pull request Jun 16, 2025
Don't wait for differ response if there are no tracings, for example in
view mode.

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

### Steps to test:
- Open a dataset in view mode and activate a mapping. Previously, the
mapping activation was stuck and the mapping never got activated.
- Follow the testing instructions in
#8676 to check that the
race condition is still fixed

------
(Please delete unneeded items, merge only when none are left open)
- [x] Updated
[changelog](../blob/master/CHANGELOG.unreleased.md#unreleased)
- [x] Removed dev-only changes like prints and application.conf edits
- [x] Considered [common edge
cases](../blob/master/.github/common_edge_cases.md)

---------

Co-authored-by: Philipp Otto <philipp.4096@gmail.com>
@fm3 fm3 mentioned this pull request Jun 23, 2025
28 tasks
fm3 added a commit that referenced this pull request Jun 23, 2025
Agglomerate Mappings can now also be read from the new zarr3-based
format, and from remote object storage.

- AgglomerateFileKey, which identifies an agglomerate file, now holds a
LayerAttachment (which can specify a remote URI) so we can use
VaultPaths for accessing remote agglomerate files
- interface of AgglomerateService methods changed (take the new
AgglomerateFileKey)
AgglomerateService is no longer injected, instead it is explicitly
created in BinaryDataServiceHolder so we can pass it the
sharedChunkContentsCache
- AgglomerateService now delegates to either Hdf5AgglomerateService
(basically the old code) or ZarrAgglomerateService (a lot of duplication
from the other one, but unifying it at this time would sacrifice
performance for hdf5)
- DatasetArray has new public method ReadAsMultiArray, which does not do
any axisorder but instead yields a MultiArray in the order of the
DatasetArray
- removed unused route agglomerateIdsForAllSegmentIds

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

### Steps to test:
- With test dataset from
https://www.notion.so/scalableminds/Test-Datasets-c0563be9c4a4499dae4e16d9b2497cfb?source=copy_link#209b51644c6380ac85e0f6b0c7e339cf
select agglomerate mapping, should be displayed correctly
- Import agglomerate skeleton, should look plausible
- Do some proofreading (splits + merges) should work
- Also test that the old format still works (e.g. use older
test-agglomerate-file dataset with hdf5 agglomerate files)

### TODOs:
<details>
<summary>Backend</summary>

- [x] open zarr agglomerate as zarr array and read contents
  - [x] read MultiArray without caring about AxisOrder
  - [x] test with 2D
- [x] Read agglomerate arrays with correct types
- [x] Re-Implement public functions of agglomerate service
  - [x] applyAgglomerate
  - [x] generate agglomerate skeleton
  - [x] largest agglomerate id
  - [x] generate agglomerate graph
  - [x] segmentIdsForAgglomerateId
  - [x] agglomerateIdsForSegmentIds
  - [x] positionForSegmentId
- [x] What’s up with zarr streaming in the tests? reproduce with
test-dataset, ids are wrong, also in normal data loading
- [x] Create indirection for selecting the zarr agglomerates OR hdf5
agglomerates
- [x] reduce code duplication btw hdf5 and zarr
- [x] Error handling (index lookups always in tryo. abstraction?)
- [x] Read remote (Build VaultPath for URI)
- [x] Discover files?
- [x] Adapt requests to specify which agglomerate file should be read
from (type? full path?)
- [x] Caching / Speedup (added some caching but did not test on
large-scale DS. will be follow-up)
- [x] Clear caches on layer/DS reload
- [x] Make sure the agglomerate zarr directories don’t blow up dataset
exploring
- [x] Code Cleanup
</details>

<details>
<summary>Frontend</summary>

- [x] Starting proofreading with a split action doesn’t properly flush
updateMappingName before calling minCut route → fixed by
#8676
</details>

### Issues:
- contributes to #8618
- contributes to #8567

------
- [x] Updated
[changelog](../blob/master/CHANGELOG.unreleased.md#unreleased)
- [x] Removed dev-only changes like prints and application.conf edits
- [x] Considered [common edge
cases](../blob/master/.github/common_edge_cases.md)
- [x] Needs datastore update after deployment
@coderabbitai coderabbitai bot mentioned this pull request Jun 27, 2025
46 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants