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
First-pass at a pattern for handling state updates from SSE #139
Conversation
Functionally this works for me both with multiple tabs and with a shared tale. Using an incognito session logged in as a second user with access to the tale, the metadata page is updated. I do think we'll want to have special handling if the second user is in edit mode (e.g., popup saying that the resource changed, do you want to reload or keep your changes or similar) but for now I think this is going to be a rare case. |
Basic Tale updates should be in place on the Tale Catalog view (any subview) and the Run View (any subview). Each of these updates currently just calls the previously-crafted
Minor feedback regarding the Instance-based events: I think we are currently just sending the NOTE: The Instance Launching/Running events ( |
@bodom0015 Do you use the |
@craig-willis Yes, I think that |
ngOnInit(): void { | ||
this.instanceLaunchingSubscription = this.syncService.instanceLaunchingSubject.subscribe((instanceId: string) => { | ||
// TODO: How do we know that this instance is for the Tale that this button represents? | ||
}); | ||
this.instanceRunningSubscription = this.syncService.instanceRunningSubject.subscribe((instanceId: string) => { | ||
// TODO: How do we know that this instance is for the Tale that this button represents? | ||
}); | ||
} | ||
|
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.
@craig-willis this is the part where the taleId
will help - here, we are inside of the Tale-specific "Run" button. While this is a component appears as a simple button, it actually contains/collects all of the logic for starting/stopping a Tale, Copy on Launch, polling for the instance status on a timer, and updating the UI state with the resulting change.
When a new instance is launched, we don't yet have a reference to it and won't know which Tale is associated with the Instance. So we can fetch it from Girder, but upon fetching it, we might learn that the taleId
for the instance is not the same as the taleId
for the tale-run-button
button that we are currently within, and that we did not need to fetch this instance at all.
It is also notable that without including the taleId
, this behavior will trigger once per-Tale per-event, meaning that the call to fetch the instance will fire once per Tale that you have in your Catalog view (or just once on the Run view). If we can easily include the taleId
along with the instanceId
in those events, we will know ahead of time whether or not the Instance is associated with the same Tale that our button is.
Makes sense. Currently I send the
|
@craig-willis that looks perfect 👍 With a structure like this, the Instance cases above would change to something like: this.instanceLaunchingSubscription = this.syncService.instanceLaunchingSubject.subscribe((taleId: string, instanceId: string) => {
if (this.tale._id == taleId) {
// This update applies to an instance for this Tale... fetch the instance
this.instanceService.instanceGetInstance(instanceId).subscribe((instance: Instance) => {
// Update button state in the UI
this.instance = instance;
this.ref.detectChanges();
});
}
}); This should save us from needing to poll, and from needing to perform as many blind queries. |
@bodom0015 I've updated with that change. There's an unrelated failing test, but I think things should work for you. |
Here are the results of my testing prior to the My basic test case:
Based on this I'm seeing the following:
|
Added some additional handling for instances to I still need to do a bit more testing to figure out what's going on with that form error |
This is looking great. I did not see the "Form submission canceled" error with this round of testing, but will keep an eye out if I can repeat it. Steps 4 (share/unshare), 6 (metadata update view mode) and 9 (delete) in my above scenario now appear to be working. However, I am seeing the following issues:
The sync modal now appears for me but when I click "Yes" the metadata is not updated. Also, if I wait 30 seconds the popup reappears, which I think may be a bigger issue (see below).
With user2 on the run view when user1 unshares the tale, I now see the "Tale was unshared" modal along with a toast with a 403 error for the run/taleid. The redirect works on click, so it's really just the error. Along with the pop-up reappearing on edit, I see a constant stream of events in the dev console for both users when nothing is happening. Generally |
Per last dev call, I've reduced the notification expiration to 5s for these events in whole-tale/girder_wholetale#457. This should solve one of the problems I described above. |
As discussed with @craig-willis offline, changing expiration of messages to 5s doesn't exactly work as expected due to mongodb's internals. Specifically:
However, extending notification stream timeout to ~90s (which should give mongo plenty of time to remove notifications) along with whole-tale/girder_wholetale#457 worked for me. I used the following patch: diff --git a/src/app/layout/notification-stream/notification-stream.service.ts b/src/app/layout/notification-stream/notification-stream.service.ts
index c62f870..f9463af 100644
--- a/src/app/layout/notification-stream/notification-stream.service.ts
+++ b/src/app/layout/notification-stream/notification-stream.service.ts
@@ -12,8 +12,8 @@ import { bypassSanitizationTrustResourceUrl } from '@angular/core/src/sanitizati
})
class NotificationStreamService implements OnDestroy {
static readonly Path = '/notification/stream';
- static readonly TimeoutMs = 30;
- static readonly IntervalDelayMs = 30000;
+ static readonly TimeoutMs = 85;
+ static readonly IntervalDelayMs = 85000;
private _since: number = 0;
interval: any;
@@ -96,7 +96,7 @@ class NotificationStreamService implements OnDestroy {
this.disconnect();
// Connect to SSE using the given parameters
- this.source = new EventSource(this.url, { headers: { 'Girder-Token': this.token } });
+ this.source = new EventSource(this.url, { headers: { 'Girder-Token': this.token }, heartbeatTimeout: 90000});
this.source.onerror = this.onError.bind(this);
this.source.onopen = this.onOpen.bind(this); The only issue that remained afterwards was simultaneous editing of Tale's metadata. Personally I consider that scenario a fringe of an extreme edge case and definitely not a blocker for this PR. |
…gx-dashboard into event_notifications
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.
This is all working for me now
Problem
When multiple browser tabs are open, any edits to the Tale in one browser cause the other tabs to go out of sync.
Approach
Integrate with whole-tale/girder_wholetale#445 to provide updates to the UI state via Server-Side Eventing. As resources change in the backend, we will attempt to refresh the frontend data to keep the state in sync.
I am currently only handling
wt_tale_updated
event in therun-tale
component and subviews to test out the pattern, and will start expanding to cover the other state update events that the backend is sending.NOTE: There was a
TransferHttpCacheModule
that was caching GET requests. I have blindly disabled it for now, so we may see a (hopefully minor) performance hit. If the platform becomes unstable as a result, I can look into trying to disable it only for particular resources. Also noting that if/when any HTTP polling is cleaned up, this should ultimately result in an increase in performance.How to Test
Prerequisites: at least one Tale created by you
Setup
Tale Created
Tale Removed
Tale Updated
Tale Updated While Editing
Tale Shared / Unshared
TBD: Implemented, but needs testing
Instance Launching / Running
TBD: Not implemented -
taleId
might be helpful to return here in addition toinstanceId
Tale Import Started / Completed / Failed
TBD: Not implemented - out of scope