-
Notifications
You must be signed in to change notification settings - Fork 120
core:frontend:CloudTrayMenu: Add file tracking #3699
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
patrickelectric
merged 1 commit into
bluerobotics:master
from
joaomariolago:improve-cloud-tray
Dec 19, 2025
Merged
core:frontend:CloudTrayMenu: Add file tracking #3699
patrickelectric
merged 1 commit into
bluerobotics:master
from
joaomariolago:improve-cloud-tray
Dec 19, 2025
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Reviewer's GuideImplements cloud file upload tracking and a new tabbed Cloud tray UI, wiring Zenoh-based file sync state into reusable Account and Uploads tabs with badge + status summaries. Sequence diagram for Zenoh-based file sync tracking in CloudTrayMenusequenceDiagram
actor User
participant CloudTrayMenu
participant ZenohSession
participant MajorTomFileSyncService
participant CloudSyncStatusTab
User->>CloudTrayMenu: Open_cloud_tray_menu
CloudTrayMenu->>CloudTrayMenu: menu_opened=true
CloudTrayMenu->>CloudTrayMenu: initializeFileSyncTracking()
alt Major_Tom_ready
CloudTrayMenu->>CloudTrayMenu: ensureFileSyncSession()
alt No_existing_session
CloudTrayMenu->>ZenohSession: Session.open(Config(ws_or_wss_url))
ZenohSession-->>CloudTrayMenu: file_sync_session
else Session_already_open
CloudTrayMenu-->>CloudTrayMenu: Reuse_existing_session
end
CloudTrayMenu->>CloudTrayMenu: fetchFileSyncQueues()
CloudTrayMenu->>ZenohSession: get(pending_topic)
ZenohSession-->>CloudTrayMenu: pending_queue_payload
CloudTrayMenu->>CloudTrayMenu: parseFileSyncQueue()
CloudTrayMenu->>ZenohSession: get(completed_topic)
ZenohSession-->>CloudTrayMenu: completed_queue_payload
CloudTrayMenu->>CloudTrayMenu: parseFileSyncQueue()
CloudTrayMenu->>CloudTrayMenu: ensureUploadingSubscriber()
CloudTrayMenu->>ZenohSession: declare_subscriber(uploading_topic)
ZenohSession-->>CloudTrayMenu: uploading_subscriber
else Major_Tom_not_ready
CloudTrayMenu-->>CloudTrayMenu: initializeFileSyncTracking_returns
end
loop For_each_uploading_event
MajorTomFileSyncService-->>ZenohSession: Publish_uploading_sample
ZenohSession-->>CloudTrayMenu: Sample(payload)
CloudTrayMenu->>CloudTrayMenu: handleUploadingSample(sample)
CloudTrayMenu->>CloudTrayMenu: normalizeUploadingEvent(event)
CloudTrayMenu->>CloudTrayMenu: resetUploadingIdleWatchdog()
alt Transfer_not_completed
CloudTrayMenu->>CloudTrayMenu: Update_cloud_uploading_transfers
else Transfer_completed
CloudTrayMenu->>CloudTrayMenu: Remove_from_cloud_uploading_transfers
CloudTrayMenu->>CloudTrayMenu: removeFromPendingQueue(file)
CloudTrayMenu->>CloudTrayMenu: scheduleFileSyncRefresh(1500_ms)
end
CloudTrayMenu-->>CloudSyncStatusTab: Re_render_with_updated_items_and_counts
end
loop Scheduled_refresh
CloudTrayMenu->>CloudTrayMenu: fetchFileSyncQueues()
CloudTrayMenu-->>CloudSyncStatusTab: Update_items_from_pending_and_completed
end
User->>CloudTrayMenu: Close_cloud_tray_menu_or_Major_Tom_stops
CloudTrayMenu->>CloudTrayMenu: teardownFileSyncTracking()
CloudTrayMenu->>ZenohSession: undeclare(uploading_subscriber)
CloudTrayMenu->>ZenohSession: close()
ZenohSession-->>CloudTrayMenu: Session_closed
Updated class diagram for Cloud tray menu, tabs, and file sync typesclassDiagram
class CloudTrayMenu {
<<VueComponent>>
+boolean menu_opened
+number active_tab
+boolean setting_token
+string token
+Session file_sync_session
+Promise~Session~ file_sync_session_promise
+Subscriber uploading_subscriber
+FileSyncEntry[] cloud_pending_uploads
+FileSyncEntry[] cloud_completed_uploads
+Record~string,UploadingTransfer~ cloud_uploading_transfers
+number file_sync_refresh_timeout
+number uploading_idle_timeout
+boolean is_major_tom_ready
+boolean is_tray_visible
+boolean is_cloud_setup_complete
+UploadingTransfer[] cloud_active_uploads
+FileSyncEntry[] cloud_pending_queue
+FileSyncEntry[] cloud_recent_completed
+number activeCount
+number pendingCount
+string statusSummary
+string pendingBadgeLabel
+CloudSyncDisplayItem[] cloud_sync_items
+string blueos_cloud_url
+string blueos_cloud_vehicles_url
+string missions_url
+mounted() Promise~void~
+beforeDestroy() void
+setUpTrayMenu() Promise~void~
+initializeFileSyncTracking() Promise~void~
+ensureFileSyncSession() Promise~boolean~
+fetchFileSyncQueues() Promise~void~
+queryFileSyncTopic(topic) Promise~FileSyncEntry[]|
+parseFileSyncQueue(payload) FileSyncEntry[]
+ensureUploadingSubscriber() Promise~void~
+handleUploadingSample(sample) void
+normalizeUploadingEvent(event) UploadingTransfer|
+teardownFileSyncTracking() Promise~void~
+normalizeFilePath(path) string
+pathsReferSameFile(pathA,pathB) boolean
+removeFromPendingQueue(canonicalPath) void
+scheduleFileSyncRefresh(delay) void
+clearScheduledFileSyncRefresh() void
+resetUploadingIdleWatchdog(timeoutSeconds) void
+clearUploadingIdleWatchdog() void
+getFileName(path) string
+formatFileSize(value) string
+formatUploadProgress(progress) string
}
class CloudSettingsTab {
<<VueComponent>>
+boolean isTokenSet
+boolean settingToken
+string token
+string blueosCloudUrl
+string blueosCloudVehiclesUrl
+$emit(toggle-token-input, boolean)
+$emit(update:token, string)
+$emit(submit-token)
}
class CloudSyncStatusTab {
<<VueComponent>>
+string summaryMessage
+CloudSyncDisplayItem[] items
+number activeCount
+number pendingCount
+string missionsUrl
+getFileName(path) string
+formatFileSize(value) string
+formatUploadProgress(progress) string
}
class FileSyncEntry {
+string path
+number size
}
class UploadingTransfer {
+string file
+string display_path
+number sent
+number total
+number timestamp
+number progress
+boolean completed
}
class CloudSyncDisplayItem {
+string id
+string path
+number size
+CloudSyncItemStatus status
+string display_path
+number sent
+number total
+number progress
}
class CloudSyncItemStatus {
<<type_alias>>
+uploading
+pending
+completed
}
class Session {
}
class Subscriber {
}
class Sample {
}
%% Relationships between components and types
CloudTrayMenu --> CloudSettingsTab : uses_in_Account_tab
CloudTrayMenu --> CloudSyncStatusTab : uses_in_Uploads_tab
CloudTrayMenu "*" --> FileSyncEntry : manages_queues
CloudTrayMenu "*" --> UploadingTransfer : tracks_active_uploads
CloudTrayMenu "*" --> CloudSyncDisplayItem : builds_display_items
CloudSyncStatusTab "*" --> CloudSyncDisplayItem : renders
CloudSyncDisplayItem --> CloudSyncItemStatus : has_status
CloudTrayMenu --> Session : opens_file_sync_session
CloudTrayMenu --> Subscriber : declares_uploading_subscriber
CloudTrayMenu --> Sample : processes_uploading_samples
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
aba561c to
b63a81b
Compare
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.
Hey there - I've reviewed your changes - here's some feedback:
- CloudTrayMenu.vue has grown quite large and now mixes UI concerns with fairly involved Zenoh file-sync session management; consider extracting the file-tracking logic (session setup, subscribers, queue parsing, timers) into a separate composable/service to keep the component more focused and easier to maintain.
- The file-sync timeouts and intervals (e.g. scheduleFileSyncRefresh delay, resetUploadingIdleWatchdog timeoutSeconds) are currently hardcoded inline; pulling these into named constants or config would make the behavior easier to understand and adjust over time.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- CloudTrayMenu.vue has grown quite large and now mixes UI concerns with fairly involved Zenoh file-sync session management; consider extracting the file-tracking logic (session setup, subscribers, queue parsing, timers) into a separate composable/service to keep the component more focused and easier to maintain.
- The file-sync timeouts and intervals (e.g. scheduleFileSyncRefresh delay, resetUploadingIdleWatchdog timeoutSeconds) are currently hardcoded inline; pulling these into named constants or config would make the behavior easier to understand and adjust over time.
## Individual Comments
### Comment 1
<location> `core/frontend/src/components/cloud/CloudTrayMenu.vue:432-445` </location>
<code_context>
+ this.file_sync_session_promise = null
+ }
+ },
+ async fetchFileSyncQueues(): Promise<void> {
+ try {
+ const [pending, completed] = await Promise.all([
+ this.queryFileSyncTopic(MAJOR_TOM_FILE_SYNC_PENDING_TOPIC),
+ this.queryFileSyncTopic(MAJOR_TOM_FILE_SYNC_COMPLETED_TOPIC),
+ ])
+
+ if (pending) {
+ this.cloud_pending_uploads = pending
+ }
+
+ if (completed) {
+ this.cloud_completed_uploads = completed
+ }
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Consider resetting pending/completed queues when queries fail to avoid stale UI state.
In `fetchFileSyncQueues`, if either `queryFileSyncTopic` call returns `null` (e.g., receiver void, query error, disconnect), the corresponding `cloud_*_uploads` array is never updated. A previously fetched value can then remain visible indefinitely when the backend is unavailable, implying uploads are still pending/completed. Consider clearing the arrays on failure or representing a distinct "unknown" state so the UI doesn’t display stale data when the queues can’t be queried.
```suggestion
async fetchFileSyncQueues(): Promise<void> {
try {
const [pending, completed] = await Promise.all([
this.queryFileSyncTopic(MAJOR_TOM_FILE_SYNC_PENDING_TOPIC),
this.queryFileSyncTopic(MAJOR_TOM_FILE_SYNC_COMPLETED_TOPIC),
])
// Reset queues when topics cannot be queried (null/undefined) to avoid stale UI
this.cloud_pending_uploads = pending ?? []
this.cloud_completed_uploads = completed ?? []
```
</issue_to_address>
### Comment 2
<location> `core/frontend/src/components/cloud/CloudTrayMenu.vue:528-536` </location>
<code_context>
+ return []
+ }
+ },
+ async ensureUploadingSubscriber(): Promise<void> {
+ if (this.uploading_subscriber || !this.file_sync_session) {
+ return
+ }
+
+ try {
+ this.uploading_subscriber = await this.file_sync_session.declare_subscriber(
+ MAJOR_TOM_FILE_SYNC_UPLOADING_TOPIC,
+ {
+ handler: async (sample: Sample) => {
+ this.handleUploadingSample(sample)
</code_context>
<issue_to_address>
**suggestion:** The subscriber handler is declared `async` but doesn't actually await anything, which adds unnecessary indirection.
The handler passed to `declare_subscriber` is currently `async (sample: Sample) => { this.handleUploadingSample(sample); return Promise.resolve() }`. Since `handleUploadingSample` is synchronous and you’re only returning `Promise.resolve()`, this should just be a synchronous function returning `void`, e.g. `handler: (sample: Sample) => { this.handleUploadingSample(sample) }`. This avoids an unnecessary promise wrapper and more accurately reflects the handler’s behavior.
```suggestion
MAJOR_TOM_FILE_SYNC_UPLOADING_TOPIC,
{
handler: (sample: Sample) => {
this.handleUploadingSample(sample)
},
history: true,
},
)
```
</issue_to_address>
### Comment 3
<location> `core/frontend/src/components/cloud/CloudTrayMenu.vue:597-606` </location>
<code_context>
+ async teardownFileSyncTracking(): Promise<void> {
</code_context>
<issue_to_address>
**suggestion (bug_risk):** There is a potential race between teardown and reinitialization of file sync tracking.
Since `teardownFileSyncTracking` is async and invoked from watchers/beforeDestroy without `await`, it can run concurrently with `initializeFileSyncTracking` when `is_major_tom_ready` or `menu_opened` change rapidly. This can lead to reusing a session while it’s being closed or clearing queues right after `fetchFileSyncQueues` refills them. Consider adding a state flag (e.g. `file_sync_teardown_in_progress`) or serializing both methods through a shared promise so each waits for the other to finish.
Suggested implementation:
```
async teardownFileSyncTracking(): Promise<void> {
// If a teardown is already in progress, wait for it and return to avoid
// concurrent teardown logic racing with initialization.
if (this.file_sync_teardown_in_progress) {
try {
await this.file_sync_teardown_in_progress
} catch {
// Errors are already handled in the original teardown logic.
}
return
}
const teardownPromise = (async () => {
this.clearScheduledFileSyncRefresh()
this.clearUploadingIdleWatchdog()
try {
if (this.uploading_subscriber) {
await this.uploading_subscriber.undeclare()
}
} catch (error) {
console.warn('[CloudTrayMenu] Failed to undeclare uploading subscriber:', error)
} finally {
// NOTE: any additional teardown logic below will be part of the
// serialized teardown operation.
```
To fully implement the race-avoidance strategy and align with the comment you left on the PR, the following additional changes are needed elsewhere in `CloudTrayMenu.vue`:
1. **Add state to track teardown in progress**
- In the component's state definition (e.g. `data()`, `data` option, or `setup()`-equivalent reactive object), add:
```ts
file_sync_teardown_in_progress: null as Promise<void> | null,
```
- Initialize it to `null`.
2. **Wire the state into `teardownFileSyncTracking`**
- Just before `const teardownPromise = (async () => { ...` in the replaced code, add:
```ts
this.file_sync_teardown_in_progress = teardownPromise
```
- After the `finally { ...` block inside the async IIFE, add:
```ts
this.file_sync_teardown_in_progress = null
```
- This ensures that `file_sync_teardown_in_progress` always points to the currently running teardown or `null`.
3. **Serialize `initializeFileSyncTracking` with teardown**
- In `initializeFileSyncTracking` (or the equivalent initialization method for file sync tracking in this component), add at the top of the method:
```ts
if (this.file_sync_teardown_in_progress) {
try {
await this.file_sync_teardown_in_progress
} catch {
// Ignore teardown errors here; they are handled in teardown.
}
}
```
- This guarantees that initialization never overlaps with an ongoing teardown even if `teardownFileSyncTracking` is invoked without `await`.
4. **Ensure watchers respect the serialization**
- For watchers reacting to `is_major_tom_ready`, `menu_opened`, or similar that call `initializeFileSyncTracking` or `teardownFileSyncTracking`:
- Prefer `async` watcher handlers that `await` the relevant method.
- If they must remain non-async, relying on the internal serialization via `file_sync_teardown_in_progress` is acceptable, as long as the state field and logic above are consistently applied.
These additional changes will ensure that file sync teardown and initialization are serialized through a shared promise and cannot run concurrently, eliminating the race condition mentioned in your review comment.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
patrickelectric
requested changes
Dec 19, 2025
* Add file uploading tracking to cloud tray menu
b63a81b to
2960c89
Compare
patrickelectric
approved these changes
Dec 19, 2025
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Not connected to Cloud
Connected to Cloud
No Files
Pending FIles + Last Uploaded
Summary by Sourcery
Add cloud file upload tracking and status display to the Cloud tray menu, including a dedicated uploads tab and mission link.
New Features:
Enhancements:
Summary by Sourcery
Add cloud file upload tracking and status visibility to the Cloud tray menu, including a dedicated uploads view and Zenoh-based file sync integration.
New Features:
Enhancements: