Skip to content

Make user bounding box updates more efficient #8492

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 50 commits into from
May 27, 2025

Conversation

MichaelBuessemeyer
Copy link
Contributor

@MichaelBuessemeyer MichaelBuessemeyer commented Apr 1, 2025

This PR add more update actions for user bounding boxes. This makes it more efficient to update a single bounding box (including adding and deleting). Moreover, the version restore view can display more details on what was actually changed (e.g. color of the bounding box).

URL of deployed dev instance (used for testing):

  • https://___.webknossos.xyz

Steps to test:

  • create/mutate bounding boxes
  • save
  • reload and check that bboxes look like before

TODOs:

  • if code is kept: make saga code DRY in volumetracing_saga and skeletontracing_saga
  • use updateUserBoundingBoxVisibility actions for visibility updates
    • frontend
    • backend
  • consider renaming actions (no "action" in name field AND new action name, e.g. updateUserSpecificBoundingBox[Volume|Skeleton])
    • frontend (done: no action in name field)
    • backend

Issues:


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

Copy link
Contributor

coderabbitai bot commented Apr 1, 2025

📝 Walkthrough

"""

Walkthrough

This change refactors user bounding box update handling from bulk list-based updates to granular, incremental actions for adding, deleting, updating, and toggling visibility of individual bounding boxes. It updates frontend logic, backend API, serialization, and tests to support and verify these fine-grained operations, improving efficiency and clarity.

Changes

Files/Paths Change Summary
frontend/javascripts/viewer/model/helpers/compaction/compact_save_queue.ts Added compaction function for bounding box update actions; refactored compaction pipeline.
frontend/javascripts/viewer/model/reducers/reducer_helpers.ts Refactored bounding box conversion to handle single objects instead of arrays.
frontend/javascripts/viewer/model/sagas/skeletontracing_saga.ts Introduced granular diffing for bounding boxes; replaced bulk update with incremental actions.
frontend/javascripts/viewer/model/sagas/update_actions.ts Added/renamed action creators for add, delete, update, and visibility toggle for bounding boxes; legacy bulk kept.
frontend/javascripts/viewer/model/sagas/volumetracing_saga.tsx Switched to granular diffing for volume tracing bounding boxes.
frontend/javascripts/viewer/view/version_entry.tsx Expanded action descriptions for new granular bounding box actions.
webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/annotation/UpdateActions.scala Added serialization/deserialization support for new bounding box action types.
webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/skeleton/updating/SkeletonUpdateActions.scala Added case classes for add, delete, update user bounding boxes in skeleton tracing, with JSON implicits.
webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeUpdateActions.scala Added case classes for add, delete, update user bounding boxes in volume tracing, with JSON implicits.
test/backend/TristateJsonTestSuite.scala Added tests for tristate option JSON parsing/writing.
util/src/main/scala/com/scalableminds/util/tools/TristateOptionJsonHelper.scala Added documentation comment explaining tristate option helper usage.
CHANGELOG.unreleased.md Noted improved efficiency for saving bounding box changes.
frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx Changed data source comparison logic to use deep equality instead of diff size.
frontend/javascripts/libs/utils.ts Rewrote diffObjects to perform shallow diff using lodash, with new docs and signature.
frontend/javascripts/test/sagas/bounding_box_saving.spec.ts Added tests for bounding box diffing and compaction logic.
frontend/javascripts/viewer/model/sagas/save_saga.ts Updated internal doc comment for save function.

Assessment against linked issues

Objective (Issue #) Addressed Explanation
Incremental (not bulk) update actions for user bounding boxes (#7604)
Update frontend and backend APIs for granular bounding box actions (#7604)
Serialization/deserialization for new action types (#7604)
Add/modify tests to cover new incremental update logic (#7604)

Suggested labels

refactoring, testing

Suggested reviewers

  • knollengewaechs
  • fm3

Poem

Bounding boxes leap and hop,
No longer lumped in a single crop!
Add, delete, or tweak with glee,
Each update now as nimble as can be.
Actions granular, tests robust—
This rabbit’s code, you can trust!
🐇✨
"""


📜 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 1f70227 and 002025e.

📒 Files selected for processing (3)
  • CHANGELOG.unreleased.md (1 hunks)
  • frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx (2 hunks)
  • frontend/javascripts/viewer/model/reducers/reducer_helpers.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • CHANGELOG.unreleased.md
  • frontend/javascripts/viewer/model/reducers/reducer_helpers.ts
  • frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: build-smoketest-push
  • GitHub Check: backend-tests
  • GitHub Check: frontend-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.

@fm3
Copy link
Member

fm3 commented Apr 1, 2025

@MichaelBuessemeyer How would you feel about three new actions, Add/Delete/Update bbox? That way we don’t need an update action for every property.
It’s true that the version restore view will be a little less detailed then, but I’d say that’s a fair price for the reduced code complexity. Maybe @philippotto also has an opinion? TBH I think there’s already an overwhelming amount of update action classes…

@MichaelBuessemeyer
Copy link
Contributor Author

@MichaelBuessemeyer How would you feel about three new actions, Add/Delete/Update bbox? That way we don’t need an update action for every property. It’s true that the version restore view will be a little less detailed then, but I’d say that’s a fair price for the reduced code complexity. Maybe @philippotto also has an opinion? TBH I think there’s already an overwhelming amount of update action classes…

Yes, I agree that there are already a lot of update actions. My idea was to follow how it is done for skeleton update actions. There we also have the update action on such a detailed degree. Moreover, when thinking about the upcoming collab feature, the design doc states:

update actions of segment metadata (and maybe trees, too, for consistency) should be updated per key (so that setting two different keys does not overwrite each other)

I can image the same problem with bounding boxes:

  1. User A renames BBox with Id x
  2. User B re-colors the BBox with Id x
  3. User A saves
  4. User B receives update:

At 4. the frontend now needs to be really smart to first find out by a diff that the name of the bounding box was changed. A simple comparison with the current BBox state of User B is not possible as the diff would also reveal that the color changed (done by User B). But only the name did and only the name should be updated.

This complexity could be saved in case we have an action for each field making things easier implementation wise (at least that's how I see it on first thought 🤔)

Although as the bounding boxes state are saved at the time Users B last successful update was submitted to the backend, there should be a state that can be compared with to find out that actually only the name changed. But I am not sure how complicated such a code would look like compared to a "updateBBoxNameAction" and simply applying it 🤔.

To me more update actions sound more tedious code-wise but should be more straight forward to implement leading to more code lines but less code complexity

@philippotto
Copy link
Member

I'm also in favor of having less update action types. I think we should aim for update actions where not all properties have to be listed. For example, one could have UpdateBoundingBox(name="new name") and UpdateBoundingBox(color="new color"). then, we only have one class for the update, but the sets of changed keys can be different.

I think, we already had an issue for this, but I cannot find it anymore. from what I remember, the above is a bit tricky, because scala cannot distinguish between undefined and simply not listed in a JSON? or something like this? however, we should be able to work around this somehow (e.g., list the keys explicitly that should be set).

@fm3
Copy link
Member

fm3 commented Apr 3, 2025

You are probably referring to #6657 – there we didn’t consider this for update actions but it might just work there too.

@knollengewaechs
Copy link
Contributor

knollengewaechs commented Apr 3, 2025

what timestamp should be put into the updateAction? the moment the first frontend action was fired or the moment when the annotation is saved?

edit: this is done within update_actions.ts with the types AddServerValues, AsServerAction and ServerUpdateAction. No need to add this to the new actions explicitly.

@knollengewaechs
Copy link
Contributor

knollengewaechs commented Apr 3, 2025

however, we should be able to work around this somehow (e.g., list the keys explicitly that should be set).

my initial solution is to call the frontend updateAction with a Partial<UserBoundindBox> and when sending this to the backend, explicitly include the props in the partial as a list. if anyone thinks that this is a bad idea, please let me know!

edit: talked with @MichaelBuessemeyer, seems to be fine

@knollengewaechs knollengewaechs self-assigned this Apr 4, 2025
@knollengewaechs
Copy link
Contributor

knollengewaechs commented Apr 4, 2025

@MichaelBuessemeyer I updated the frontend code, so that new the new updateActions are called. I tested it a bit with the old backend code, and it seems like the right actions are dispatched if the bounding boxes change. Let me know if the code seems buggy in any way!
And as you can tell, I already added some actions for VolumeTracings aswell.

@MichaelBuessemeyer
Copy link
Contributor Author

@MichaelBuessemeyer I updated the frontend code, so that new the new updateActions are called. I tested it a bit with the old backend code, and it seems like the right actions are dispatched if the bounding boxes change.

Nice 👍 awesome. Thanks a lot 🙏
I changed the frontend code a little to not return single elemented lists of user bounding boxes for the add actions. See c680cf2. This was necessary for further testing.

Let me know if the code seems buggy in any way!

Yeah sorry there is a little buggy / unexpected behaviour:
When the bounds of a bbox are changed the changed bounds are not send as a bounding box object with topLeft, width, height, depth but as changes in the min max props. See the screenshot below (taken from the network tab showing a update actions transaction dispatched to the server):
image
Could you please adjust the frontend to always send the whole bounds as an object (topLeft, width, height, depth) in case some of them were updated? Thanks a lot 🙏

Moreover, I noticed that the frontend seems to send "empty" updates: The updatedPropKeys and updatedProps are empty. This is also shown in the screenshot in the first array entries value. Could you please investigate this and try to avoid such empty update?

Moreover, the cherry on top would be that multiple consecutive bounding box updates to the same bounding box would be combined into a single action. Something like this is already done by the frontend before sending the update actions. See compact_save_queue.ts ll. 100ff. But I am unaware how easy this is to implement. So maybe that's a follow up 🤔?

@knollengewaechs
Copy link
Contributor

@MichaelBuessemeyer thanks for the reports! I will work on this now and try to fix the three problems.

…bleminds/webknossos into more-efficient-bbox-update-actions
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeUpdateActions.scala (1)

158-193: Well-implemented tri-state option handling for partial updates.

The UpdateUserBoundingBoxVolumeAction correctly implements the tri-state option pattern where:

  • None = don't update the field
  • Some(None) = set field to null/None
  • Some(Some(value)) = set field to value

The logic in applyOn properly handles these cases and maintains existing values when updates aren't specified.

Consider extracting the tri-state update logic into helper methods for better readability:

+  private def updateOptionalField[T](currentValue: T, updateValue: Option[Option[T]]): T =
+    updateValue.getOrElse(currentValue)
+    
+  private def updateProtoField[T, P](currentValue: P, updateValue: Option[Option[T]], converter: Option[T] => P): P =
+    if (updateValue.isDefined) updateValue.flatMap(converter).getOrElse(currentValue) else currentValue

  override def applyOn(tracing: VolumeTracing): VolumeTracing = {
    def updateUserBoundingBoxes() =
      tracing.userBoundingBoxes.map { currentBoundingBox =>
        if (boundingBoxId == currentBoundingBox.id) {
          currentBoundingBox.copy(
-            name = name.getOrElse(currentBoundingBox.name),
-            color = if (color.isDefined) color.flatMap(colorOptToProto) else currentBoundingBox.color,
-            boundingBox =
-              if (boundingBox.isDefined)
-                boundingBox.flatMap(boundingBoxOptToProto).getOrElse(currentBoundingBox.boundingBox)
-              else currentBoundingBox.boundingBox
+            name = updateOptionalField(currentBoundingBox.name, name),
+            color = updateProtoField(currentBoundingBox.color, color, colorOptToProto),
+            boundingBox = updateProtoField(currentBoundingBox.boundingBox, boundingBox, boundingBoxOptToProto)
          )
        } else
          currentBoundingBox
      }
    tracing.withUserBoundingBoxes(updateUserBoundingBoxes())
  }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6f10597 and 10816e2.

📒 Files selected for processing (2)
  • webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/skeleton/updating/SkeletonUpdateActions.scala (3 hunks)
  • webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeUpdateActions.scala (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
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.
🔇 Additional comments (9)
webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeUpdateActions.scala (3)

122-138: LGTM! Well-structured action following established patterns.

The AddUserBoundingBoxVolumeAction correctly implements the pattern used by other volume update actions. The applyOn method properly appends the new bounding box to the existing list using the :+ operator.


140-156: LGTM! Clean deletion logic with appropriate no-op behavior.

The DeleteUserBoundingBoxVolumeAction correctly filters out the bounding box by ID. The no-op behavior when the ID doesn't exist is reasonable and consistent with similar actions in the codebase.


478-489: LGTM! Appropriate JSON formatting for all new actions.

The companion objects correctly provide JSON formatters following the established pattern. The use of tristateOptionParsing for UpdateUserBoundingBoxVolumeAction is appropriate for handling the tri-state option fields.

webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/skeleton/updating/SkeletonUpdateActions.scala (6)

4-6: LGTM!

The new imports are appropriately added to support the new bounding box update actions.


544-560: LGTM!

The AddUserBoundingBoxSkeletonAction correctly implements the addition of a new bounding box by appending it to the existing list.


562-578: LGTM!

The DeleteUserBoundingBoxSkeletonAction correctly implements bounding box deletion by filtering out the box with the specified ID.


580-615: LGTM!

The UpdateUserBoundingBoxSkeletonAction correctly implements partial updates using tristate options, allowing fine-grained property updates as discussed in the PR. The logic properly handles optional fields and preserves existing values when fields are not provided.


698-709: LGTM!

The JSON formatters are correctly implemented, with UpdateUserBoundingBoxSkeletonAction appropriately using TristateOptionJsonHelper to handle partial updates.


526-542:

❓ Verification inconclusive

Verify the coexistence of bulk and granular update actions.

The existing UpdateUserBoundingBoxesSkeletonAction performs bulk replacement of all bounding boxes, while the new actions provide granular add/delete/update operations. Please confirm that maintaining both approaches is intentional for backward compatibility or specific use cases.

Run the following script to check the usage of both bulk and granular bounding box update actions:


🏁 Script executed:

#!/bin/bash
# Description: Check usage patterns of bulk vs granular bounding box update actions

# Search for usage of bulk update action
echo "=== Bulk update action usage ==="
rg -A 3 "UpdateUserBoundingBoxesSkeletonAction"

# Search for usage of granular update actions
echo -e "\n=== Granular update actions usage ==="
rg -A 3 "(AddUserBoundingBoxSkeletonAction|DeleteUserBoundingBoxSkeletonAction|UpdateUserBoundingBoxSkeletonAction)"

Length of output: 12251


Verify coexistence of bulk and granular update actions

Both the bulk UpdateUserBoundingBoxesSkeletonAction and the new granular actions (AddUserBoundingBoxSkeletonAction, DeleteUserBoundingBoxSkeletonAction, UpdateUserBoundingBoxSkeletonAction, etc.) are still defined in

  • webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/skeleton/updating/SkeletonUpdateActions.scala
    and are all referenced in
  • webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/annotation/UpdateActions.scala
    for JSON (de)serialization.

Please confirm that retaining both the bulk replace action and the incremental update actions is intentional (e.g., for backward compatibility or distinct usage scenarios).

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)
webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeUpdateActions.scala (1)

11-11: Remove unused NamedBoundingBoxUpdate import

The NamedBoundingBoxUpdate import is still present but not used anywhere in the new code. This should be removed as flagged in the previous review.

🧹 Nitpick comments (1)
webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeUpdateActions.scala (1)

158-193: Complex but correct tristate option handling

The UpdateUserBoundingBoxVolumeAction implementation correctly handles the tristate option pattern (Option[Option[T]]) for partial updates. However, the logic in applyOn is quite complex and could benefit from better readability.

Consider extracting the update logic into helper methods:

  override def applyOn(tracing: VolumeTracing): VolumeTracing = {
-   def updateUserBoundingBoxes() =
-     tracing.userBoundingBoxes.map { currentBoundingBox =>
-       if (boundingBoxId == currentBoundingBox.id) {
-         currentBoundingBox.copy(
-           name = name.getOrElse(currentBoundingBox.name),
-           color = if (color.isDefined) color.flatMap(colorOptToProto) else currentBoundingBox.color,
-           boundingBox =
-             if (boundingBox.isDefined)
-               boundingBox.flatMap(boundingBoxOptToProto).getOrElse(currentBoundingBox.boundingBox)
-             else currentBoundingBox.boundingBox
-         )
-       } else
-         currentBoundingBox
-     }
+   def updateUserBoundingBoxes() =
+     tracing.userBoundingBoxes.map { currentBoundingBox =>
+       if (boundingBoxId == currentBoundingBox.id) {
+         updateBoundingBoxFields(currentBoundingBox)
+       } else {
+         currentBoundingBox
+       }
+     }
+
+   def updateBoundingBoxFields(current: NamedBoundingBoxProto) =
+     current.copy(
+       name = name.getOrElse(current.name),
+       color = updateColor(current.color),
+       boundingBox = updateBoundingBox(current.boundingBox)
+     )
+
+   def updateColor(currentColor: Option[ColorProto]) =
+     if (color.isDefined) color.flatMap(colorOptToProto) else currentColor
+
+   def updateBoundingBox(currentBBox: Option[BoundingBoxProto]) =
+     if (boundingBox.isDefined)
+       boundingBox.flatMap(boundingBoxOptToProto).getOrElse(currentBBox)
+     else currentBBox

    tracing.withUserBoundingBoxes(updateUserBoundingBoxes())
  }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 10816e2 and dd55178.

📒 Files selected for processing (1)
  • webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeUpdateActions.scala (3 hunks)
🔇 Additional comments (3)
webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeUpdateActions.scala (3)

122-138: LGTM! Clean implementation for adding user bounding boxes

The AddUserBoundingBoxVolumeAction implementation is straightforward and correct. The applyOn method properly appends the new bounding box to the existing list, and all required override methods are implemented consistently.


140-156: LGTM! Correct implementation for deleting user bounding boxes

The DeleteUserBoundingBoxVolumeAction correctly filters out the bounding box by ID. The implementation is clean and follows the established patterns in the codebase.


478-489: Appropriate JSON format implementations

The JSON format objects are correctly implemented:

  • Standard JSON.format for AddUserBoundingBoxVolumeAction and DeleteUserBoundingBoxVolumeAction
  • Proper use of TristateOptionJsonHelper and tristateOptionParsing for UpdateUserBoundingBoxVolumeAction to handle the tristate option pattern

This ensures proper serialization/deserialization of the complex nested option types.

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/viewer/view/version_entry.tsx (1)

91-102: Address the previous review comments about join separator

The previous review comments from philippotto suggested using ", " instead of "," for better readability. This hasn't been addressed yet.

Apply this diff to improve readability:

-    description: `Updated bounding boxes ${firstAction.value.boundingBoxes.map((bbox) => bbox.id).join()}.`,
+    description: `Updated bounding boxes ${firstAction.value.boundingBoxes.map((bbox) => bbox.id).join(", ")}.`,

Also for line 100:

-    description: `Updated bounding boxes ${firstAction.value.boundingBoxes.map((bbox) => bbox.id).join()}.`,
+    description: `Updated bounding boxes ${firstAction.value.boundingBoxes.map((bbox) => bbox.id).join(", ")}.`,
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between dd55178 and 5e3c3ce.

📒 Files selected for processing (7)
  • frontend/javascripts/viewer/model/reducers/reducer_helpers.ts (1 hunks)
  • frontend/javascripts/viewer/model/sagas/skeletontracing_saga.ts (5 hunks)
  • frontend/javascripts/viewer/model/sagas/update_actions.ts (4 hunks)
  • frontend/javascripts/viewer/model/sagas/volumetracing_saga.tsx (3 hunks)
  • frontend/javascripts/viewer/view/version_entry.tsx (4 hunks)
  • webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/annotation/UpdateActions.scala (5 hunks)
  • webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeUpdateActions.scala (3 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
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/update_actions.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.
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: frontend-tests
  • GitHub Check: build-smoketest-push
  • GitHub Check: backend-tests
🔇 Additional comments (11)
frontend/javascripts/viewer/model/sagas/volumetracing_saga.tsx (1)

491-496: LGTM! Good refactoring to use shared diffing logic.

The change to use diffBoundingBoxes for volume tracing aligns well with the skeleton tracing implementation, promoting code reuse and consistency across both tracing types.

webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/annotation/UpdateActions.scala (1)

57-60: JSON serialization correctly implemented for new bounding box actions.

The serialization and deserialization cases are properly matched and follow the established naming convention (without "Action" suffix in the string names).

Also applies to: 69-72, 153-161, 174-182

webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeUpdateActions.scala (4)

124-140: Add action correctly implemented.

The AddUserBoundingBoxVolumeAction properly appends the new bounding box to the existing list while maintaining immutability.


142-158: Delete action correctly filters by ID.

The implementation properly removes the bounding box with the matching ID while preserving all others.


160-195: Partial update implementation handles tri-state logic correctly.

The UpdateUserBoundingBoxVolumeAction properly uses the Option[Option[T]] pattern to distinguish between:

  • None: Don't update the field
  • Some(None): Clear the field
  • Some(Some(value)): Update the field with the value

The implementation correctly applies these semantics in the applyOn method.


546-549: JSON formatter correctly configured for tri-state options.

The use of TristateOptionJsonHelper and tristateOptionParsing ensures proper serialization/deserialization of the partial update fields.

frontend/javascripts/viewer/model/reducers/reducer_helpers.ts (1)

54-59: LGTM! Clean refactoring to handle single bounding box.

The function has been correctly renamed to singular form and refactored to process individual bounding boxes, aligning with the new granular update pattern.

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

608-614: Good implementation of bounding box diffing with proper optimizations.

The early return check and use of Utils.diffArrays for ID comparison is efficient and follows the suggested performance improvements.


666-670: Verify the intention of always sending complete bounds.

When the boundingBox changes, the code ensures both min and max are included in the update, even if only one property changed. This deviates from the granular update pattern where only changed properties are sent.

Is this intentional to ensure the backend always receives complete bounds, or should it respect the granular diff?

frontend/javascripts/viewer/model/sagas/update_actions.ts (1)

13-14: Good documentation for the type's purpose.

The comment clearly explains that this type contains only the properties that have changed, which helps prevent confusion.

frontend/javascripts/viewer/view/version_entry.tsx (1)

103-150: LGTM! Well-structured bounding box action handlers

The new bounding box action handlers are implemented consistently:

  • Clear, descriptive messages that include the bounding box ID
  • Appropriate icons for each action type (create, delete, update, visibility toggle)
  • Follows the existing pattern in the codebase
  • Aligns with the learnings about separate visibility update actions

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/viewer/model/sagas/update_actions.ts (1)

494-513: ⚠️ Potential issue

Fix logic error: use rest instead of updatedProps when boundingBox is null.

When boundingBox is null/undefined, the function returns updatedProps which still contains the boundingBox property. This should use rest to exclude the property entirely when it's not being updated.

  const { boundingBox, ...rest } = updatedProps;
  const updatedPropsForServer =
    boundingBox != null
      ? { ...rest, boundingBox: Utils.computeBoundingBoxObjectFromBoundingBox(boundingBox) }
-     : updatedProps;
+     : rest;
🧹 Nitpick comments (2)
frontend/javascripts/viewer/model/helpers/compaction/compact_save_queue.ts (2)

89-91: Consider using a Set for action name lookups.

For better performance, especially if this function is called frequently, consider using a Set for O(1) lookups instead of array includes().

+const BBOX_UPDATE_ACTION_NAMES = new Set([
+  "updateUserBoundingBoxInSkeletonTracing",
+  "updateUserBoundingBoxInVolumeTracing",
+]);
+
 export function removeSubsequentUpdateBBoxActions(updateActionsBatches: Array<SaveQueueEntry>) {
   // Actions are obsolete, if they are for the same bounding box and for the same prop.
   // E.g. when rezising a bounding box, multiple updateActions are sent during the resize, while only the last one is needed.
   const previousActionsById: Record<
     string,
     UpdateUserBoundingBoxInSkeletonTracingAction | UpdateUserBoundingBoxInVolumeTracingAction
   > = {};
   const relevantActions = [];
   for (let i = updateActionsBatches.length - 1; i >= 0; i--) {
     const currentActions = updateActionsBatches[i].actions;
     if (
       currentActions.length === 1 &&
-      ["updateUserBoundingBoxInSkeletonTracing", "updateUserBoundingBoxInVolumeTracing"].includes(
-        currentActions[0].name,
-      )
+      BBOX_UPDATE_ACTION_NAMES.has(currentActions[0].name)
     ) {

77-114: Well-implemented compaction logic with good backward iteration strategy.

The function correctly implements compaction for bounding box update actions using backward iteration to preserve order while removing redundant actions. The logic properly handles:

  • Different action types (skeleton vs volume tracing)
  • Different bounding box IDs
  • Different sets of updated properties
  • Non-bounding box actions

The key comparison using Set and _.isEqual on Object.keys(value) is a clever approach to determine if actions target the same properties.

One minor suggestion for improved readability:

-        !_.isEqual(
-          new Set(Object.keys(previousAction.value)),
-          new Set(Object.keys(currentAction.value)),
-        )
+        !_.isEqual(
+          new Set(Object.keys(previousAction.value)),
+          new Set(Object.keys(currentAction.value)),
+        )

Consider extracting the key comparison to a helper function for better readability:

const hasDifferentProperties = (action1: any, action2: any) =>
  !_.isEqual(
    new Set(Object.keys(action1.value)),
    new Set(Object.keys(action2.value)),
  );
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5e3c3ce and c5ddd56.

📒 Files selected for processing (3)
  • frontend/javascripts/test/model/compact_save_queue.spec.ts (1 hunks)
  • frontend/javascripts/viewer/model/helpers/compaction/compact_save_queue.ts (4 hunks)
  • frontend/javascripts/viewer/model/sagas/update_actions.ts (4 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
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/update_actions.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.
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: frontend-tests
  • GitHub Check: build-smoketest-push
  • GitHub Check: backend-tests
🔇 Additional comments (7)
frontend/javascripts/viewer/model/helpers/compaction/compact_save_queue.ts (4)

138-156: LGTM! Clean refactoring using functional composition.

The use of _.flow for composing the compaction functions is an excellent improvement that makes the code more maintainable and easier to extend.


2-5: LGTM: Clean import addition for bounding box actions.

The import statement correctly brings in the required types for the new bounding box compaction functionality.


138-145: Excellent refactoring with the pipeline approach.

The compactAll pipeline using _.flow greatly improves readability and follows functional programming principles. This addresses the "getting out of hand" concern mentioned in past review comments and makes the code more maintainable.


155-155: Clean integration of the new pipeline.

The refactored function call using compactAll(result) is much cleaner than the previous nested approach and integrates well with the new bounding box compaction functionality.

frontend/javascripts/test/model/compact_save_queue.spec.ts (1)

189-259: LGTM! Good test coverage for non-compactable scenarios.

The test correctly verifies that update actions with different properties are not compacted together, ensuring the compaction logic preserves necessary updates.

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

541-568: LGTM! Clean separation of visibility updates.

The dedicated functions for visibility updates properly separate this concern from other property updates, making the code more maintainable and explicit about intent.


505-512:

✅ Verification successful

Verify the structure of update action values.

The helper function spreads updatedPropsForServer directly into the value object, but based on the compaction logic in compact_save_queue.ts, it seems the structure should include updatedProps as a nested object. This discrepancy could affect the compaction functionality.

Run the following script to verify the expected structure of update actions:


🏁 Script executed:

#!/bin/bash
# Description: Check how update actions are structured in the codebase

# Search for how these actions are used in tests
echo "=== Checking test usage ==="
rg -A 10 "updateUserBoundingBoxIn(Skeleton|Volume)Tracing\(" frontend/javascripts/test/

# Check how the compaction logic accesses these values
echo -e "\n=== Checking compaction logic ==="
rg -A 5 "\.value\." frontend/javascripts/viewer/model/helpers/compaction/compact_save_queue.ts

# Check backend expectations
echo -e "\n=== Checking backend action definitions ==="
ast-grep --pattern 'case class Update.*BoundingBox.*Action' 

Length of output: 9780


🏁 Script executed:

#!/bin/bash
# Display a broader context around the helper in update_actions.ts
sed -n '400,600p' frontend/javascripts/viewer/model/sagas/update_actions.ts

Length of output: 5025


Update action value structure is correct

The _updateUserBoundingBoxHelper spreading updatedPropsForServer directly into the action’s value matches both the shape asserted in compact_save_queue.spec.ts and the compaction logic’s use of Object.keys(value). No changes are needed here.

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.

I adapted the front-end to the newest backend changes. the front-end should be good to go now in my opinion. leaving final approval to @fm3

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 (2)
frontend/javascripts/test/model/compact_save_queue.spec.ts (2)

158-160: Remove redundant null check.

This null check is unnecessary since the expect(volumeActionValue).not.toBeNull() assertion on line 157 would already fail the test if the value were null.

-    if (volumeActionValue == null) {
-      throw new Error("volumeActionValue must not be null");
-    }

173-175: Remove redundant null check.

This null check is redundant as skeletonActionValue was already verified to be non-null by the assertion on line 172.

-    if (skeletonActionValue == null) {
-      throw new Error("skeletonActionValue must not be null");
-    }
🧹 Nitpick comments (1)
frontend/javascripts/test/model/compact_save_queue.spec.ts (1)

13-146: Consider extracting a helper function to reduce code duplication.

The test data setup is quite verbose with repetitive SaveQueueEntry objects. Consider creating a helper function to make the test more maintainable and readable.

function createSaveQueueEntry(
  transactionId: string,
  timestamp: number,
  action: UpdateUserBoundingBoxInVolumeTracingAction | UpdateUserBoundingBoxInSkeletonTracingAction
): SaveQueueEntry {
  return {
    version: -1,
    transactionId,
    transactionGroupCount: 1,
    transactionGroupIndex: 0,
    timestamp,
    authorId: "123",
    actions: [action],
    stats: undefined,
    info: "",
  };
}

Then use it to create the test data more concisely:

const actions: SaveQueueEntry[] = [
  createSaveQueueEntry("eq3scfgvoa", 1747994347590, 
    updateUserBoundingBoxInVolumeTracing(3, { boundingBox: { min: [3224, 3955, 944], max: [3225, 4112, 956] } }, "volumeTracing1")
  ),
  // ... other entries
];
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 08932b0 and 3ed790f.

📒 Files selected for processing (2)
  • CHANGELOG.unreleased.md (1 hunks)
  • frontend/javascripts/test/model/compact_save_queue.spec.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.unreleased.md
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
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.
⏰ 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 (2)
frontend/javascripts/test/model/compact_save_queue.spec.ts (2)

12-183: Test coverage is comprehensive and logic is correct.

The test properly verifies that:

  • Multiple consecutive bounding box updates for the same tracing are compacted down to the latest values
  • Both volume and skeleton tracing actions are handled correctly
  • The final bounding box coordinates are correctly computed from min/max to topLeft/width/height/depth format

The assertions validate the expected behavior accurately.


185-254: Good test case for preventing over-compaction.

This test correctly verifies that actions updating different properties (name vs boundingBox) are not compacted together, which is important for maintaining the integrity of different types of updates.

Copy link
Member

@fm3 fm3 left a comment

Choose a reason for hiding this comment

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

Backend LGTM

@MichaelBuessemeyer
Copy link
Contributor Author

before merging @philippotto did you test this? At least I did not 🙈

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 3ed790f and ac859f3.

📒 Files selected for processing (1)
  • frontend/javascripts/viewer/view/version_entry.tsx (4 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
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.
🔇 Additional comments (5)
frontend/javascripts/viewer/view/version_entry.tsx (5)

26-28: LGTM! Comprehensive import additions for bounding box actions.

The imports correctly include all the new action types needed for the granular bounding box update handlers. The LEGACY_ prefix appropriately indicates backward compatibility support.

Also applies to: 37-40, 60-63


91-102: LGTM! Proper legacy action handling with backward compatibility.

The LEGACY_ prefix clearly indicates the deprecated status while maintaining the existing functionality for bulk bounding box updates. The join with ", " formatting follows the previous feedback.


103-114: LGTM! Consistent implementation of add bounding box actions.

The handlers follow the established pattern for creation actions with appropriate PlusOutlined icons and descriptive text that includes the bounding box ID as requested in previous reviews.


115-126: LGTM! Consistent implementation of delete bounding box actions.

The handlers correctly follow the established pattern for deletion actions with appropriate DeleteOutlined icons and clear descriptions that include the bounding box ID.


127-138: LGTM! Consistent implementation of update bounding box actions.

The handlers follow the established pattern for update actions with appropriate EditOutlined icons and clear descriptions that include the bounding box ID.

@philippotto
Copy link
Member

before merging @philippotto did you test this? At least I did not 🙈

yes, I tested it. will merge tomorrow for a better support window :)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between b0fba6f and 1f70227.

📒 Files selected for processing (5)
  • frontend/javascripts/dashboard/dataset/dataset_settings_view.tsx (2 hunks)
  • frontend/javascripts/libs/utils.ts (1 hunks)
  • frontend/javascripts/test/sagas/bounding_box_saving.spec.ts (1 hunks)
  • frontend/javascripts/viewer/model/sagas/save_saga.ts (1 hunks)
  • frontend/javascripts/viewer/model/sagas/skeletontracing_saga.ts (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/javascripts/viewer/model/sagas/skeletontracing_saga.ts
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
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.
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: build-smoketest-push
  • GitHub Check: backend-tests
🔇 Additional comments (3)
frontend/javascripts/viewer/model/sagas/save_saga.ts (1)

163-165: LGTM! Documentation improvement.

The JSDoc comment has been appropriately generalized to better reflect the function's actual behavior, removing the specific mention of tracingId which makes it more accurate.

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

1087-1108:

❓ Verification inconclusive

Verify the intentional change from deep to shallow diffing.

The diffObjects function has been completely rewritten with improved type safety and documentation. However, this changes the behavior from deep diffing to shallow diffing.

Please confirm that the change from deep to shallow diffing is intentional and won't break existing functionality:


🏁 Script executed:

#!/bin/bash
# Description: Search for all usages of diffObjects to verify the impact of changing from deep to shallow diffing
# Expected: Find all call sites to assess if shallow diffing is sufficient

echo "Searching for diffObjects usage..."
rg -A 3 -B 1 "diffObjects" --type ts --type tsx

echo "Searching for imports of diffObjects..."
rg "import.*diffObjects" --type ts --type tsx

Length of output: 319


🏁 Script executed:

#!/bin/bash
# Re-run search for diffObjects usages without relying on filetype detection
echo "Searching for diffObjects usage in .ts and .tsx files..."
rg -n "diffObjects" -g "*.ts" -g "*.tsx"

echo "Searching for imports of diffObjects in .ts and .tsx files..."
rg -n "import.*diffObjects" -g "*.ts" -g "*.tsx"

Length of output: 703


Confirm shallow-only diffing is intended

I found one usage of diffObjects in:

  • frontend/javascripts/viewer/model/sagas/skeletontracing_saga.ts:658 (comparing two flat bounding-box objects)

With the new implementation, any nested objects will no longer be diffed (only top-level keys are compared). Please verify that:

  • All call sites use only flat, primitive-valued objects
  • No existing consumers rely on recursive/deep diffing behavior

If deeper comparisons are still needed, consider renaming the function (e.g. shallowDiffObjects) or reintroducing recursive logic.

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

1-356: Excellent test coverage for bounding box functionality.

This comprehensive test suite effectively validates the new bounding box diffing and compaction logic with well-structured test cases:

  1. Bounding box diffing: Thoroughly tests the generation of correct update actions for deletions, additions, visibility changes, and property updates
  2. Compaction logic: Validates that multiple updates to the same bounding box are properly compacted
  3. Non-compaction edge case: Ensures different property updates are not incorrectly compacted

The test data is realistic, assertions are specific, and the test structure follows best practices.

@philippotto philippotto merged commit fb1c999 into master May 27, 2025
5 checks passed
@philippotto philippotto deleted the more-efficient-bbox-update-actions branch May 27, 2025 11:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

More efficiently update User Bounding Boxes
4 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy