-
Notifications
You must be signed in to change notification settings - Fork 29
[WIP] Live Collab M3 - Proofreading (without segments list) #8723
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
|
Note Reviews pausedUse the following commands to manage reviews:
📝 WalkthroughWalkthroughThis change introduces a mutex mechanism for annotation editing, enabling exclusive editing rights per user. It adds backend APIs for acquiring and releasing mutexes, updates the frontend to manage and display mutex state, and integrates Redux-Saga logic for continuous mutex acquisition and release. Related types and reducers are updated accordingly. Changes
Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Suggested labels
Suggested reviewers
Poem
✨ Finishing touches🧪 Generate unit tests (beta)
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. Comment |
| case _ => | ||
| Fox.successful(()) | ||
| } | ||
| } yield () |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since we’re not using any info from the findOne other than whether to delete, we could merge the two into one query and just DELETE WHERE annotationId = a AND userId = u
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed that now in 3f66d4e – note that I could not test end-to-end since the frontend isn’t ready. Let me know if this doesn’t work as expected :)
…essary because of unified annotation versioning)
mutex was acquired This reverts commit e5b1c95.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
♻️ Duplicate comments (1)
app/models/annotation/AnnotationMutexService.scala (1)
71-80: Consider optimizing the mutex release operation.The current approach uses separate
findOneanddeleteOneoperations. As noted in a previous review, this could be optimized into a single query that deletes only if the user owns the mutex.Consider replacing the current implementation with a single query:
-def release(annotationId: ObjectId, userId: ObjectId): Fox[Unit] = - for { - mutex <- annotationMutexDAO.findOne(annotationId).shiftBox - _ <- mutex match { - case Full(mutex) if mutex.userId == userId => - annotationMutexDAO.deleteOne(annotationId).map(_ => ()) - case _ => - Fox.successful(()) - } - } yield () +def release(annotationId: ObjectId, userId: ObjectId): Fox[Unit] = + annotationMutexDAO.deleteOneByUserAndAnnotation(annotationId, userId).map(_ => ())And add to the DAO:
def deleteOneByUserAndAnnotation(annotationId: ObjectId, userId: ObjectId): Fox[Int] = run(q"DELETE FROM webknossos.annotation_mutexes WHERE _annotation = $annotationId AND _user = $userId".asUpdate)This would be more efficient and eliminate the race condition between the find and delete operations.
🧹 Nitpick comments (3)
frontend/javascripts/viewer/model/sagas/volume/proofread_saga.ts (1)
361-361: Address unused variable warnings for preparatory mutex integration.The
annotationIdvariables are retrieved from state but not used because the mutex calls are commented out as part of the WIP development. To address the static analysis warnings while maintaining the preparatory code, consider prefixing the variables with underscore:-const annotationId = yield* select((state) => state.annotation.annotationId); +const _annotationId = yield* select((state) => state.annotation.annotationId);This clearly indicates the variables are intentionally unused in the current state while being prepared for future integration.
Also applies to: 445-446, 710-710, 796-797, 902-902, 957-958, 1142-1149
frontend/javascripts/viewer/model/sagas/saving/save_mutex_saga.tsx (2)
29-29: Use import type for type-only imports.The static analysis correctly identifies that
EnsureMaySaveNowActionis only used as a type.-import { EnsureMaySaveNowAction } from "viewer/model/actions/save_actions"; +import type { EnsureMaySaveNowAction } from "viewer/model/actions/save_actions";
93-106: Clarify the mutex acquisition flow for late-mutex approach.The conditional logic based on
DISABLE_EAGER_MUTEX_ACQUISITIONimplements different mutex acquisition strategies, but the flow could be clearer.Consider adding comments to explain:
- When
DISABLE_EAGER_MUTEX_ACQUISITIONis true, mutex is acquired only when saving (late acquisition)- When false, mutex is acquired eagerly when the annotation is opened
- The race condition between
tryAcquireMutexContinuouslyandDONE_SAVINGensures mutex is released after save completesThis aligns with the PR objective of implementing a "late-mutex approach" as an opt-in feature.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (19)
app/controllers/AnnotationController.scala(1 hunks)app/models/annotation/AnnotationMutexService.scala(2 hunks)conf/webknossos.latest.routes(1 hunks)frontend/javascripts/admin/rest_api.ts(1 hunks)frontend/javascripts/test/sagas/annotation_saga.spec.ts(1 hunks)frontend/javascripts/viewer/api/wk_dev.ts(2 hunks)frontend/javascripts/viewer/default_state.ts(1 hunks)frontend/javascripts/viewer/model/actions/annotation_actions.ts(3 hunks)frontend/javascripts/viewer/model/actions/save_actions.ts(3 hunks)frontend/javascripts/viewer/model/reducers/annotation_reducer.ts(1 hunks)frontend/javascripts/viewer/model/reducers/reducer_helpers.ts(1 hunks)frontend/javascripts/viewer/model/sagas/annotation_saga.tsx(2 hunks)frontend/javascripts/viewer/model/sagas/saving/save_mutex_saga.tsx(1 hunks)frontend/javascripts/viewer/model/sagas/saving/save_queue_draining.ts(5 hunks)frontend/javascripts/viewer/model/sagas/saving/save_saga.ts(1 hunks)frontend/javascripts/viewer/model/sagas/volume/proofread_saga.ts(7 hunks)frontend/javascripts/viewer/store.ts(1 hunks)webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/editablemapping/EditableMappingService.scala(1 hunks)webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/editablemapping/EditableMappingUpdateActions.scala(2 hunks)
🧰 Additional context used
🧠 Learnings (11)
📓 Common learnings
Learnt from: MichaelBuessemeyer
PR: scalableminds/webknossos#8352
File: conf/evolutions/126-credit-transactions.sql:89-130
Timestamp: 2025-01-27T15:01:17.868Z
Learning: Team prefers to discuss concurrency handling approaches (like transaction isolation) during the review phase rather than as individual suggestions.
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/test/sagas/annotation_saga.spec.ts (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/viewer/model/reducers/annotation_reducer.ts (1)
Learnt from: knollengewaechs
PR: scalableminds/webknossos#8492
File: frontend/javascripts/viewer/model/sagas/skeletontracing_saga.ts:671-674
Timestamp: 2025-05-15T19:44:16.110Z
Learning: Visibility updates for user bounding boxes are intentionally handled separately from other property updates, with dedicated update actions for visibility changes.
frontend/javascripts/viewer/model/sagas/saving/save_saga.ts (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`.
app/models/annotation/AnnotationMutexService.scala (3)
Learnt from: frcroth
PR: scalableminds/webknossos#8236
File: webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/mesh/MeshFileService.scala:170-173
Timestamp: 2025-04-23T08:51:57.756Z
Learning: In the webknossos codebase, classes extending `FoxImplicits` have access to an implicit conversion from `Option[A]` to `Fox[A]`, where `None` is converted to an empty Fox that fails gracefully in for-comprehensions.
Learnt from: MichaelBuessemeyer
PR: scalableminds/webknossos#8352
File: app/models/organization/CreditTransactionService.scala:0-0
Timestamp: 2025-01-27T12:06:42.865Z
Learning: In Scala's for-comprehension with Fox (Future-like type), the `<-` operator ensures sequential execution. If any step fails, the entire chain short-circuits and returns early, preventing subsequent operations from executing. This makes it safe to perform validation checks before database operations.
Learnt from: frcroth
PR: scalableminds/webknossos#8236
File: webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/mesh/NeuroglancerPrecomputedMeshFileService.scala:161-166
Timestamp: 2025-04-28T14:18:04.368Z
Learning: In Scala for-comprehensions with the Fox error handling monad, `Fox.fromBool()` expressions should use the `<-` binding operator instead of the `=` assignment operator to properly propagate error conditions. Using `=` will cause validation failures to be silently ignored.
frontend/javascripts/viewer/model/sagas/saving/save_mutex_saga.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/viewer/model/sagas/volume/proofread_saga.ts (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`.
webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/editablemapping/EditableMappingService.scala (6)
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`.
Learnt from: dieknolle3333
PR: scalableminds/webknossos#8168
File: frontend/javascripts/oxalis/model/sagas/volumetracing_saga.tsx:433-434
Timestamp: 2024-11-22T17:19:07.947Z
Learning: In the codebase, certain usages of `segmentationLayer.resolutions` are intentionally retained and should not be changed to `segmentationLayer.mags` during refactoring.
Learnt from: frcroth
PR: scalableminds/webknossos#8609
File: app/models/dataset/Dataset.scala:753-775
Timestamp: 2025-05-12T13:07:29.637Z
Learning: In the `updateMags` method of DatasetMagsDAO (Scala), the code handles different dataset types distinctly:
1. Non-WKW datasets have `magsOpt` populated and use the first branch which includes axisOrder, channelIndex, and credentialId.
2. WKW datasets will have `wkwResolutionsOpt` populated and use the second branch which includes cubeLength.
3. The final branch is a fallback for legacy data.
This ensures appropriate fields are populated for each dataset type.
Learnt from: philippotto
PR: scalableminds/webknossos#8602
File: frontend/javascripts/oxalis/model/volumetracing/volume_annotation_sampling.ts:365-366
Timestamp: 2025-05-07T06:17:32.810Z
Learning: The parameter in applyVoxelMap was renamed from `sliceCount` to `sliceOffset` to better reflect its purpose, but this doesn't affect existing call sites since JavaScript/TypeScript function calls are position-based.
Learnt from: dieknolle3333
PR: scalableminds/webknossos#8168
File: frontend/javascripts/oxalis/model/reducers/skeletontracing_reducer_helpers.ts:125-125
Timestamp: 2024-11-22T17:17:39.914Z
Learning: In the `createNode` function within `skeletontracing_reducer_helpers.ts`, the property `resolution` is intentionally kept even when the parameter is renamed to `mag`.
Learnt from: frcroth
PR: scalableminds/webknossos#8236
File: webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/mesh/MeshFileService.scala:170-173
Timestamp: 2025-04-23T08:51:57.756Z
Learning: In the webknossos codebase, classes extending `FoxImplicits` have access to an implicit conversion from `Option[A]` to `Fox[A]`, where `None` is converted to an empty Fox that fails gracefully in for-comprehensions.
frontend/javascripts/viewer/model/sagas/saving/save_queue_draining.ts (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/viewer/model/sagas/annotation_saga.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`.
webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/editablemapping/EditableMappingUpdateActions.scala (1)
Learnt from: frcroth
PR: scalableminds/webknossos#8609
File: app/models/dataset/Dataset.scala:753-775
Timestamp: 2025-05-12T13:07:29.637Z
Learning: In the `updateMags` method of DatasetMagsDAO (Scala), the code handles different dataset types distinctly:
1. Non-WKW datasets have `magsOpt` populated and use the first branch which includes axisOrder, channelIndex, and credentialId.
2. WKW datasets will have `wkwResolutionsOpt` populated and use the second branch which includes cubeLength.
3. The final branch is a fallback for legacy data.
This ensures appropriate fields are populated for each dataset type.
🧬 Code Graph Analysis (2)
app/models/annotation/AnnotationMutexService.scala (2)
util/src/main/scala/com/scalableminds/util/tools/Fox.scala (2)
shiftBox(296-296)successful(53-56)app/utils/sql/SqlInterpolation.scala (2)
q(19-38)asUpdate(73-73)
frontend/javascripts/viewer/model/sagas/saving/save_queue_draining.ts (2)
frontend/javascripts/viewer/model/actions/save_actions.ts (2)
dispatchEnsureMaySaveNowAsync(146-151)doneSavingAction(153-156)frontend/javascripts/libs/window.ts (2)
alert(18-18)location(60-60)
🪛 Biome (1.9.4)
frontend/javascripts/viewer/model/sagas/saving/save_saga.ts
[error] 55-55: Unexpected constant condition.
(lint/correctness/noConstantCondition)
frontend/javascripts/viewer/model/sagas/saving/save_mutex_saga.tsx
[error] 29-29: All these imports are only used as types.
Importing the types with import type ensures that they are removed by the compilers and avoids loading unnecessary modules.
Safe fix: Use import type.
(lint/style/useImportType)
frontend/javascripts/viewer/model/sagas/volume/proofread_saga.ts
[error] 361-361: This variable is unused.
Unused variables usually are result of incomplete refactoring, typos and other source of bugs.
Unsafe fix: If this is intentional, prepend annotationId with an underscore.
(lint/correctness/noUnusedVariables)
[error] 710-710: This variable is unused.
Unused variables usually are result of incomplete refactoring, typos and other source of bugs.
Unsafe fix: If this is intentional, prepend annotationId with an underscore.
(lint/correctness/noUnusedVariables)
[error] 902-902: This variable is unused.
Unused variables usually are result of incomplete refactoring, typos and other source of bugs.
Unsafe fix: If this is intentional, prepend annotationId with an underscore.
(lint/correctness/noUnusedVariables)
⏰ 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 (19)
webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/editablemapping/EditableMappingUpdateActions.scala (2)
14-14: Well-designed backward compatibility approach.The conversion of
agglomerateIdandmagfields toOptiontypes is a clean approach for supporting legacy update actions while maintaining type safety. The comment provides clear context for why these fields are now optional.Also applies to: 19-19
39-40: Consistent optional field implementation.The same backward compatibility pattern is correctly applied to
MergeAgglomerateUpdateActionwith bothagglomerateId1andagglomerateId2fields becoming optional, maintaining consistency with theSplitAgglomerateUpdateActionchanges.Also applies to: 45-45
webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/editablemapping/EditableMappingService.scala (2)
188-188: Proper method signature alignment with update actions.The parameter change from
mag: Vec3InttomagOpt: Option[Vec3Int]correctly aligns with the optional fields in the update actions, maintaining consistency across the codebase.Also applies to: 191-191
196-196: Effective validation for optional parameters.The validation logic properly handles the optional
magOptparameter by converting it to a Fox with a descriptive error message when required. This ensures that the method fails gracefully when mandatory data is missing while supporting the backward compatibility requirements.Also applies to: 199-199
frontend/javascripts/test/sagas/annotation_saga.spec.ts (1)
15-15: LGTM: Import path correctly updated for refactored saga.The import path update aligns with the refactoring where
acquireAnnotationMutexMaybewas moved to a dedicated mutex saga module, improving code organization.frontend/javascripts/viewer/store.ts (1)
149-149: LGTM: Mutex state flag properly added to Annotation type.The
isMutexAcquiredboolean property is correctly defined as readonly, maintaining Redux immutability principles and providing clear state tracking for mutex management.frontend/javascripts/viewer/default_state.ts (1)
181-181: LGTM: Appropriate default value for mutex state.Initializing
isMutexAcquiredtofalseis the correct default since annotations start without mutex acquisition, maintaining consistency with the expected initial state.conf/webknossos.latest.routes (1)
158-158: LGTM: Well-structured REST endpoint for mutex release.The DELETE route for
/annotations/:id/mutexcorrectly complements the existing POST route for mutex acquisition, following REST conventions and maintaining consistent route structure.frontend/javascripts/viewer/model/reducers/reducer_helpers.ts (1)
159-159: LGTM: Consistent mutex flag initialization in conversion function.Adding
isMutexAcquired: falseensures that server-to-frontend annotation conversions maintain consistent object structure with the default state initialization.frontend/javascripts/viewer/model/reducers/annotation_reducer.ts (1)
140-145: LGTM! Clean implementation of the mutex state reducer.The new action case follows the established patterns in the file and correctly uses the
updateKeyhelper function to maintain immutability. The implementation is consistent with other reducer cases in the file.frontend/javascripts/admin/rest_api.ts (1)
732-736: LGTM! Clean implementation of the mutex release API.The function follows the established patterns in the file and correctly uses the HTTP DELETE method for releasing the mutex. The implementation is consistent with the existing
acquireAnnotationMutexfunction and integrates well with the broader mutex management feature.frontend/javascripts/viewer/api/wk_dev.ts (1)
14-14: Good debugging setup for development.Enabling these debug flags will help with development and troubleshooting of the new mutex functionality. The enhanced logging and version display should provide valuable insights during the WIP development phase.
Also applies to: 31-31
app/controllers/AnnotationController.scala (1)
457-463: Clean implementation of the mutex release endpoint.The controller method follows established patterns in the codebase, uses appropriate error handling with Fox, and includes performance monitoring via
logTime. The implementation is straightforward and correctly delegates to the service layer.frontend/javascripts/viewer/model/actions/annotation_actions.ts (1)
55-55: Well-implemented action creator for mutex state management.The new
setIsMutexAcquiredActionfollows established patterns in the codebase, is properly typed, and correctly integrated into the action type union. The implementation is clean and straightforward.Also applies to: 91-91, 181-185
frontend/javascripts/viewer/model/sagas/annotation_saga.tsx (1)
2-2: Clean refactoring of mutex logic!The relocation of mutex acquisition logic to a dedicated module improves code organization and separation of concerns.
Also applies to: 9-9, 14-17, 34-34
frontend/javascripts/viewer/model/actions/save_actions.ts (1)
29-30: Well-structured save flow control actions!The new actions and async dispatch helper follow the established patterns in the codebase. The Deferred pattern for callback synchronization is consistent with existing implementations.
Also applies to: 43-45, 140-156
frontend/javascripts/viewer/model/sagas/saving/save_queue_draining.ts (2)
68-70: Good integration of save flow control!The addition of
dispatchEnsureMaySaveNowAsyncanddoneSavingActionproperly coordinates save operations with mutex state.Also applies to: 102-103
206-210: Verify the simplified 409 conflict handling behavior.The removal of the flag and indefinite sleep simplifies the code, but may change behavior:
- Previously, only one alert was shown due to the flag
- The indefinite sleep prevented further save attempts
- Now the error is thrown immediately after reload
Since multiple retries could trigger multiple alerts before the page reloads, consider if this behavior change is intentional. The immediate throw after reload seems correct as the page will be refreshed anyway.
frontend/javascripts/viewer/model/sagas/saving/save_mutex_saga.tsx (1)
104-104: Add missing yield operator.*The
calleffect requiresyield*to properly execute the saga.- if (doneSaving) { - yield call(releaseMutex); - } + if (doneSaving) { + yield* call(releaseMutex); + }Likely an incorrect or invalid review comment.
frontend/javascripts/viewer/model/sagas/saving/save_mutex_saga.tsx
Outdated
Show resolved
Hide resolved
…nymore; also invalidate complete mapping when incorporating merge and split for now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
♻️ Duplicate comments (4)
frontend/javascripts/viewer/model/sagas/saving/save_mutex_saga.tsx (3)
39-40: Fix TODO typo and make the flag configurable.
177-188: Consider using a finally block as mentioned in the comment.
225-235: Fix the releaseMutex implementation to actually release the mutex.frontend/javascripts/viewer/model/sagas/saving/save_saga.ts (1)
59-60: Fix the disabled early return condition and unclear comment.
🧹 Nitpick comments (1)
frontend/javascripts/viewer/model/sagas/volume/mapping_saga.ts (1)
158-205: Well-structured debugging feature with clear documentation.The addition of
DEBUG__RELOAD_HDF5_MAPPINGaction handler and supporting functions provides a useful debugging capability. The code is well-documented with clear comments explaining the debugging purpose.One minor issue: The error message on line 200 mentions "splitAgglomerate" but should be more generic since this is a general HDF5 reloading function:
- throw new Error("Could not apply splitAgglomerate because no active mapping was found."); + throw new Error("Could not reload HDF5 mapping because no active mapping was found.");
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
frontend/javascripts/viewer/api/wk_dev.ts(1 hunks)frontend/javascripts/viewer/model/actions/save_actions.ts(3 hunks)frontend/javascripts/viewer/model/sagas/saving/save_mutex_saga.tsx(1 hunks)frontend/javascripts/viewer/model/sagas/saving/save_queue_draining.ts(5 hunks)frontend/javascripts/viewer/model/sagas/saving/save_saga.ts(7 hunks)frontend/javascripts/viewer/model/sagas/volume/mapping_saga.ts(2 hunks)frontend/javascripts/viewer/model/sagas/volume/proofread_saga.ts(6 hunks)frontend/javascripts/viewer/model/sagas/volume/update_actions.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
- frontend/javascripts/viewer/api/wk_dev.ts
- frontend/javascripts/viewer/model/sagas/volume/proofread_saga.ts
- frontend/javascripts/viewer/model/sagas/saving/save_queue_draining.ts
- frontend/javascripts/viewer/model/actions/save_actions.ts
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: MichaelBuessemeyer
PR: scalableminds/webknossos#8352
File: conf/evolutions/126-credit-transactions.sql:89-130
Timestamp: 2025-01-27T15:01:17.868Z
Learning: Team prefers to discuss concurrency handling approaches (like transaction isolation) during the review phase rather than as individual suggestions.
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/viewer/model/sagas/saving/save_mutex_saga.tsx (2)
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`.
Learnt from: robert-oleynik
PR: scalableminds/webknossos#8393
File: frontend/javascripts/admin/auth/passkeys_view.tsx:29-35
Timestamp: 2025-07-09T07:30:57.149Z
Learning: In the webknossos project, error handling and user notifications are handled at the API function level (e.g., `removeWebAuthnKey`, `fetchPasskeys`) rather than in the UI components, so additional try-catch blocks in the UI layer are unnecessary.
frontend/javascripts/viewer/model/sagas/volume/update_actions.ts (4)
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`.
Learnt from: philippotto
PR: scalableminds/webknossos#8602
File: frontend/javascripts/oxalis/model/volumetracing/volume_annotation_sampling.ts:365-366
Timestamp: 2025-05-07T06:17:32.810Z
Learning: The parameter in applyVoxelMap was renamed from `sliceCount` to `sliceOffset` to better reflect its purpose, but this doesn't affect existing call sites since JavaScript/TypeScript function calls are position-based.
Learnt from: dieknolle3333
PR: scalableminds/webknossos#8168
File: frontend/javascripts/oxalis/model/reducers/skeletontracing_reducer_helpers.ts:125-125
Timestamp: 2024-11-22T17:17:39.914Z
Learning: In the `createNode` function within `skeletontracing_reducer_helpers.ts`, the property `resolution` is intentionally kept even when the parameter is renamed to `mag`.
Learnt from: dieknolle3333
PR: scalableminds/webknossos#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/viewer/model/sagas/volume/mapping_saga.ts (2)
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`.
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/viewer/model/sagas/saving/save_saga.ts (2)
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`.
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.
🧬 Code Graph Analysis (3)
frontend/javascripts/viewer/model/sagas/saving/save_mutex_saga.tsx (1)
frontend/javascripts/libs/window.ts (1)
location(60-60)
frontend/javascripts/viewer/model/sagas/volume/update_actions.ts (2)
frontend/javascripts/viewer/constants.ts (1)
Vector3(13-13)frontend/javascripts/viewer/store.ts (1)
NumberLike(362-362)
frontend/javascripts/viewer/model/sagas/volume/mapping_saga.ts (3)
frontend/javascripts/viewer/model.ts (2)
getSegmentationLayers(93-98)getLayerByName(270-276)frontend/javascripts/viewer/store.ts (1)
ActiveMappingInfo(366-374)frontend/javascripts/viewer/model/actions/settings_actions.ts (1)
setMappingAction(205-227)
🪛 Biome (1.9.4)
frontend/javascripts/viewer/model/sagas/saving/save_saga.ts
[error] 60-60: Unexpected constant condition.
(lint/correctness/noConstantCondition)
⏰ 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 (2)
frontend/javascripts/viewer/model/sagas/volume/update_actions.ts (1)
897-955: Good approach to API evolution with backward compatibility!The refactoring to make
agglomerateId,mag,agglomerateId1, andagglomerateId2optional fields clearly maintains backward compatibility while simplifying the current API. The comments explaining that these fields are "unused in back-end but may exist in older update actions" provide excellent documentation for future maintainers.frontend/javascripts/viewer/model/sagas/volume/mapping_saga.ts (1)
166-174: Clean implementation of clearActiveMapping.The function properly creates a new empty Map and dispatches the appropriate action to clear the mapping state. This will be useful for the deferred mapping updates in save_saga.ts.
|
@coderabbitai pause |
✅ Actions performedReviews paused. |
433deb9 to
e35c367
Compare
… is unshared annotation
…ching tool and othersMayEdit is turned off
…fix mocking of test checking this
frontend/javascripts/test/reducers/update_action_application/skeleton.spec.ts
Show resolved
Hide resolved
frontend/javascripts/test/reducers/update_action_application/skeleton.spec.ts
Outdated
Show resolved
Hide resolved
frontend/javascripts/test/reducers/update_action_application/skeleton.spec.ts
Outdated
Show resolved
Hide resolved
| if (process.env.IS_TESTING) { | ||
| // in test context, the mapping.ts code is not executed (which is usually responsible | ||
| // for finishing the initialization). | ||
| // todop: this is quite messy. refactor? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a new issue would make sense, because it's not strictly related to live is live.
frontend/javascripts/viewer/view/right-border-tabs/trees_tab/tree_hierarchy_view_helpers.ts
Outdated
Show resolved
Hide resolved
frontend/javascripts/viewer/store.ts
Outdated
| }; | ||
|
|
||
| // rebaseRelevantServerAnnotationState stores rebasing relevant information of the annotation. | ||
| // It always has the newest version of this information which is synced with the backend. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand this comment. can you make it more precise (and maybe add an example)? I also don't understand how the "always" invariant is ensured. theinformation really can't get outdated?
frontend/javascripts/viewer/store.ts
Outdated
| // SaveReducer needs to be behind Settings and Volumetracing reducer to react to changes SET MAPPING changes. | ||
| SaveReducer, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, the comment is not true anymore, but somehow it was necessary to change it up? maybe add a comment that explains why that is necessary now?
| segmentId1: number | undefined; | ||
| segmentId2: number | undefined; | ||
| // Needed in live collab setting to notice changes of loaded agglomerates done by other users. | ||
| // agglomerateId1 and agglomerateId2 are needed in live collab setting to notice changes of loaded agglomerates done by other users. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you add to the comment when will it be undefined?
| // Should never be sent to the backend as the backend does neither understand this action | ||
| // nor does it have the property activeTreeId for a skeletonTracing. | ||
| // Only used to correctly keep track of the active tree property during rebasing. | ||
| // Reason: There is a mismatch between the frontend store actions and the update actions. | ||
| // The store action createTree also sets the activeNodeId and activeTreeId, but the update action createTree does not. | ||
| // If we would change that via also applying activeNodeId and activeTreeId for createTree update actions | ||
| // this breaks the deleteEdge store action: This store action maps to the following update actions: | ||
| // createTree, moveTreeComponents, deleteEdge. Where the createTree update action should not | ||
| // set activeNodeId and activeTreeId when applied. Therefore, this action tracks whether the activeTreeId should change. | ||
| // Tracking of activeNodeId is already handled by updateActiveNode (see above). This action is understood by the backend. | ||
| // So no problem there. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unfortunately, I can't really follow 🙈 I'd suggest to clarify this in a call.
| expect(isUpdatingAllowed).toBe(true); | ||
| }); | ||
|
|
||
| it<WebknossosTestContext>("An annotation with isUpdatingCurrentlyAllowed = true and othersMayEdit = true should try to acquire the annotation mutex not in liveCollab.", async (context: WebknossosTestContext) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"not in liveCollab" is very hard to read. how about:
A not-liveCollab annotation with isUpdatingCurrentlyAllowed = true and othersMayEdit = true should try to acquire the annotation mutex.
| }, | ||
| ); | ||
| mockInitialBucketAndAgglomerateData(context); | ||
| // Give mutex saga time to potentially acquire the mutex. This should not happen! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why "this should not happen"? in othersMayEdit==true, it should happen, right?
| // Visibility updates are user-specific and should only be incorporated | ||
| // if reapplied during rebasing the users actions from the save queue. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| // Visibility updates are user-specific and should only be incorporated | |
| // if reapplied during rebasing the users actions from the save queue. | |
| // Visibility updates are user-specific and should only be incorporated | |
| // when rebasing the user's actions from the save queue. |
| .filter((g) => g.isExpanded) | ||
| .map((g) => g.groupId), | ||
| ); | ||
| const actionGroupIds = new Set(groupIds); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
adding "set" to the name makes the semantics of the variable way clearer than adding "action".
| const actionGroupIds = new Set(groupIds); | |
| const groupIdSet = new Set(groupIds); |
needs to be renamed in the other spots, too.
| // These update actions are user specific and don't need to be incorporated here | ||
| // because they are from another user. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about moving this comment to case "updateSegmentGroupsExpandedState": and changing it to "... user specific and only need to be applied if these actions originate from the current user (this happens when rebasing such actions)".
| try { | ||
| const { canEdit, blockedByUser } = yield* call(acquireAnnotationMutex, annotationId); | ||
| if (!canEdit) { | ||
| // TODOM: Think of a better way to handle this. This should usually never happen only if a client disconnects for a longer time while saving. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
todo
| if (newOnlyRequiredOnSave !== mutexLogicState.onlyRequiredOnSave) { | ||
| mutexLogicState.onlyRequiredOnSave = newOnlyRequiredOnSave; | ||
|
|
||
| // TODO: also check for livecollab flag and editable mapping locked and so on. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
todo
| groupId, | ||
| name, | ||
| children, | ||
| isExpanded: false, // TODOM: check whether this does not change the default behaviour |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
todo
| try { | ||
| const { canEdit, blockedByUser } = yield* call(acquireAnnotationMutex, annotationId); | ||
| if (!canEdit) { | ||
| // TODOM: Think of a better way to handle this. This should usually never happen only if a client disconnects for a longer time while saving. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
todo
| if (newOnlyRequiredOnSave !== mutexLogicState.onlyRequiredOnSave) { | ||
| mutexLogicState.onlyRequiredOnSave = newOnlyRequiredOnSave; | ||
|
|
||
| // TODO: also check for livecollab flag and editable mapping locked and so on. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
todo
|
|
||
| function* releaseMutex() { | ||
| const annotationId = yield* select((storeState) => storeState.annotation.annotationId); | ||
| // TODOM: Mutex is auto released after a Model.ensureSavedState or so, sometimes (e.g. a proofreading action), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, segment list related stuff sounds like m4.
| yield* retry( | ||
| RETRY_COUNT, | ||
| ACQUIRE_MUTEX_INTERVAL / RETRY_COUNT, | ||
| releaseAnnotationMutex, | ||
| annotationId, | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so, let's say the requests fail for some reason (e.g., connectivity) and then the mutex auto expires. will the backend reply with 200 to the next release request (I think this would be good because it's a noop)? if so, yes, I'd make infinite retries + backoff.
| prevTracing = yield* getTracing(); | ||
| }, | ||
| ); | ||
| // During rebasing the local users updates are replayed and thus the identity of skeleton nodes and edges diffable map entries change. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| // During rebasing the local users updates are replayed and thus the identity of skeleton nodes and edges diffable map entries change. | |
| // During rebasing, the local users updates are replayed and thus the identity of skeleton nodes and edges in the diffable map entries change. |
| (state) => | ||
| state.uiInformation.busyBlockingInfo.isBusy && | ||
| state.uiInformation.busyBlockingInfo.reason === REBASING_BUSY_BLOCK_REASON, | ||
| // if (!allowUpdate) continue; TODOM: remove line |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
todo
| // Ignore changes while rebasing as during this time actions are replayed to the rebased state to | ||
| // reapply the changes made by the user. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find "rebased state" not very clear. how about:
| // Ignore changes while rebasing as during this time actions are replayed to the rebased state to | |
| // reapply the changes made by the user. | |
| // Ignore changes while rebasing as during this time actions are simply replayed on top of the server's state. Therefore, these actions were already added to the save queue and should not be added again. |
| if (!allowUpdate || isCurrentlyRebasing) continue; | ||
| if (!allowUpdate || isRebasing) { | ||
| if (ensureAction) { | ||
| console.error("ignoring ensure action due to currently rebasing"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| console.error("ignoring ensure action due to currently rebasing"); | |
| console.error("Ignoring ensure action due to current rebase operation"); |
This PR enables experimental (opt-in) collaborative proofreading (ignoring segment lists and meshes). Multiple users can split and merge agglomerates "in parallel". Synchronization happens as follows:
¹ the incorporation of the update actions is not trivial, because the frontend maintains a partial mapping from segment ids to agglomerate ids. the update actions only encode which segment ids were split or merged. simply mapping the affected segment ids to their (old or new?) agglomerate ids and refreshing all segment ids that map to these agglomerate ids might not be enough, because the mapping is partial and might have changed in between.
therefore, a first iteration simply reloads all segment ids. when the rest of the pr works, we can re-think this.
URL of deployed dev instance (used for testing):
Steps to test:
TODOs:
buffers.sliding<EnsureHasNewestVersionAction>(1))state.save.needsMutexboolean which the sagas act on)WkDevFlags.liveCollab: The others may edit feature works just as expected.othersMayEdit = truetest that after the test the user does not still have the mutex. Check that releasing the mutex was called.annotation_saga.spec.ts. Tests seem to not have enoughno such vertex in graphtoast if a user tries to split an agglomerate that was already split. see [slack]forbid that update actions are emitted (i.e., users should not be allowed to do such actions) that can NOT be incorporated by other clients. otherwise,Due to the feature flag, this shouldn't be a problem. This won't be available in production anyway. Therefore, this needs to be done later. As well as supporting all update actions.ensureNewestVersionwould never terminate and other users will wait forever when trying to save. if this code version is never used in prod, it might not matter, though.. -> is fine for now, as this will only be available via the feature flag.
[ ] make the new late-mutex approach opt-in (beta flag in sharing modal?)[ ] discuss:IMO this only makes sense starting with milestone 4[ ] disable all other tools if activated[ ] disable segment list editing?WkDevFlags.liveCollabupdateMappingWithMergeinitialAllowUpdateMore needed tests
othersMayEditannotationsothersMayEditstateKnown Bugs:
othersMayEditis turned on due to rebase mechanism not supporting updateMappingName action. Makes the tests quirky -> first init the mapping and everything, and then the test can finally makeothersMayEdit = true.Issues:
(Please delete unneeded items, merge only when none are left open)
$PR_NUMBER.mdfile inunreleased_changesor use./tools/create-changelog-entry.py)