Copilot commented on code in PR #8516:
URL: https://github.com/apache/texera/pull/8516#discussion_r3994499960


##########
frontend/src/app/workspace/component/workflow-form/workflow-form.component.ts:
##########
@@ -316,7 +346,11 @@ export class WorkflowFormComponent implements OnInit, 
OnDestroy {
       .getViewResultOperatorsChangedStream()
       .pipe(untilDestroyed(this))
       .subscribe(() => {
+        // An eye toggled on the canvas changes both what shows (a 
newly-viewed step) and what the
+        // author can pick, so rebuild the picker here too -- otherwise a 
just-eyed step would not
+        // appear as an option (and an un-eyed one would linger) until the 
next full re-read.
         this.refreshShownResults();
+        this.rebuildResultChoices();

Review Comment:
   This updates result availability only when an eye changes, but the new 
filter also depends on operator existence, enabled state, and terminal 
topology. Those changes emit separate add/delete/link/disabled streams, so a 
co-editor can disable a currently shown step (or add a downstream link) and 
this page keeps the stale result card and picker pill until an unrelated 
result/config event, contradicting the rule that disabled/unavailable steps are 
neither offered nor shown. Subscribe to all graph events that can change 
availability and refresh both derived collections.



##########
frontend/src/app/workspace/component/workflow-form/workflow-form.component.ts:
##########
@@ -1301,40 +1545,47 @@ export class WorkflowFormComponent implements OnInit, 
OnDestroy {
    * workflow when the payload has no id, so saving whatever the graph holds 
would spawn
    * stray "Untitled workflow" rows when the page is left before its workflow 
loaded.
    */
-  private save(): void {
+  private save(afterwards?: () => void): void {
     // A read-only viewer can open and run the form (execution is gated on 
computing-unit access,
     // not workflow access) but must never persist: every such save is a 
guaranteed 403 that would
     // spam "Could not save" on each debounce. Their inputs are non-editable, 
so nothing is lost.
+    // `afterwards` runs once the save has completed, or at once when there is 
nothing to save;
+    // it does not run when the save fails, so a caller that navigates on it 
stays put instead.
     if (!this.canEdit) {
+      afterwards?.();
       return;
     }
     if (!this.userService.isLogin() || 
!this.workflowPersistService.isWorkflowPersistEnabled()) {
+      afterwards?.();
       return;
     }
     const workflow = this.workflowActionService.getWorkflow();
     if (workflow.wid === undefined || workflow.wid !== this.wid) {
+      afterwards?.();
       return;
     }
+    // Snapshot now, not when the request's turn comes: on the way out 
ngOnDestroy clears the graph
+    // right after asking for this save, and the queue may only reach it after 
that.
     const preserved: Workflow = {
       ...workflow,
       content: { ...workflow.content, operatorPositions: 
this.positionsToSave(workflow.content) },
     };
-    // On the way out the subscription must NOT be tied to this component: 
ngOnDestroy
-    // calls save(), and untilDestroyed would tear the subscription down as 
part of the
-    // very same destroy sequence, aborting the request that was the point of 
the call.
-    const persist = this.workflowPersistService.persistWorkflow(preserved);
-    // The `destroyed` branch deliberately omits untilDestroyed (see above); 
the persist call
-    // is a one-shot HTTP request that completes on its own, so it needs no 
teardown operator.
-    // eslint-disable-next-line rxjs-angular/prefer-takeuntil
-    (this.destroyed ? persist : persist.pipe(untilDestroyed(this))).subscribe({
-      // Feed the saved workflow back, exactly as the operator canvas does: 
this advances
-      // lastModifiedTime (and the normalised name), and the metadata 
subscription then repaints
-      // the title bar -- so "Saved at ..." moves past the moment the page 
opened.
-      next: updatedWorkflow => 
this.workflowActionService.setWorkflowMetadata(updatedWorkflow),
-      // A save that fails silently is the worst thing this page can do: the 
author walks
-      // away believing the form they just built is stored.
-      error: () => this.notificationService.error("Could not save. Your latest 
changes are not stored yet."),
-    });
+    this.persistQueue.next({ workflow: preserved, afterwards });
+  }
+
+  /** One persist of the given snapshot, reporting its outcome; the queue 
orders them. */
+  private persistNow(preserved: Workflow): Observable<Workflow> {
+    return this.workflowPersistService.persistWorkflow(preserved).pipe(
+      tap({
+        // Feed the saved workflow back, exactly as the operator canvas does: 
this advances
+        // lastModifiedTime (and the normalised name), and the metadata 
subscription then repaints
+        // the title bar -- so "Saved at ..." moves past the moment the page 
opened.
+        next: updatedWorkflow => 
this.workflowActionService.setWorkflowMetadata(updatedWorkflow),

Review Comment:
   Applying every queued response unconditionally can overwrite newer local 
metadata. For example, while save A is in flight, rename to B and enqueue save 
B; when A completes, this resets the shared metadata (and, after the 100 ms 
metadata subscription, the title) to A until B returns, and another autosave in 
that window can snapshot A as the newest value. The drain can also run this 
after teardown and repopulate the singleton after `clearWorkflow()`. Guard the 
response against the request snapshot/current workflow, and skip metadata 
feedback after destruction, while still merging server-owned save metadata such 
as the timestamp.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to