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


##########
frontend/src/app/dashboard/service/user/download/download.service.ts:
##########
@@ -315,8 +315,13 @@ export class DownloadService {
    */
   private retrieveWorkflowItem(id: number, name: string): 
Observable<DownloadableItem> {
     return this.workflowPersistService.retrieveWorkflow(id).pipe(
-      map(({ content }) => {
-        const workflowJson = JSON.stringify(content, null, 2);
+      map(({ content, defaultView }) => {
+        // Carry the landing view so a download-then-upload keeps a 
form-default workflow opening
+        // as a form. It goes in as one extra top-level key next to the 
workflow's own
+        // (operators/links/...); the importer destructures it back out (see 
uploadWorkflow), and an
+        // older importer that reads the whole object as content simply 
ignores the unknown key.
+        const exported = defaultView === undefined ? content : { ...content, 
defaultView };
+        const workflowJson = JSON.stringify(exported, null, 2);

Review Comment:
   This only updates dashboard downloads. The canvas's visible “export 
workflow” action still serializes `getWorkflowContent()` alone 
(`menu.component.ts:630-637`), so exporting there and uploading the file drops 
a FORM default. The promised download/upload round-trip should cover that entry 
point too, ideally through the same export-shaping helper.



##########
frontend/src/app/workspace/component/power-button/computing-unit-selection.component.ts:
##########
@@ -269,25 +269,66 @@ export class ComputingUnitSelectionComponent implements 
OnInit {
         if (wid !== this.workflowId) {
           this.workflowId = wid;
           if (isDefined(this.workflowId) && this.workflowId !== 
DEFAULT_WORKFLOW.wid) {
-            this.workflowExecutionsService
-              .retrieveLatestWorkflowExecution(this.workflowId)
-              .pipe(untilDestroyed(this))
-              .subscribe({
-                next: (latestWorkflowExecution: WorkflowExecutionsEntry) => {
-                  this.selectComputingUnit(this.workflowId, 
latestWorkflowExecution.cuId);
-                },
-                error: (err: unknown) => {
-                  const runningUnit = this.allComputingUnits.find(unit => 
unit.status === "Running");
-                  if (runningUnit) {
-                    this.selectComputingUnit(this.workflowId, 
runningUnit.computingUnit.cuid);
-                  }
-                },
-              });
+            this.selectInitialUnit(this.workflowId);
           }
         }
       });
   }
 
+  /**
+   * Pick the unit for a workflow that has just come into view. An explicit 
choice remembered for
+   * it is newer than its last run, so it wins -- but only once the unit list 
has arrived and still
+   * holds that unit. Deciding on an empty list would either chase a unit that 
has since been
+   * terminated (the status service waits for it to appear, forever, and the 
fallbacks below never
+   * run) or throw the choice away before the list has loaded. A remembered 
unit that is gone is
+   * forgotten, and the fallbacks take over: the last execution's unit, else 
any running unit.
+   */
+  private selectInitialUnit(wid: number): void {
+    const remembered = this.recallComputingUnit(wid);
+    if (!isDefined(remembered)) {
+      this.selectFromLastExecution(wid);
+      return;
+    }
+    this.computingUnitStatusService
+      .getAllComputingUnits()
+      .pipe(
+        filter(units => units.length > 0),
+        take(1),
+        untilDestroyed(this)
+      )
+      .subscribe(units => {
+        // The workflow can change while the list is still loading; that later 
change made its own
+        // decision, so this one is stale.
+        if (wid !== this.workflowId) {
+          return;
+        }
+        if (units.some(unit => unit.computingUnit.cuid === remembered)) {
+          this.selectComputingUnit(wid, remembered);
+        } else {
+          this.forgetComputingUnit(wid);
+          this.selectFromLastExecution(wid);
+        }
+      });
+  }
+
+  /** The unit the workflow last ran on, else any unit that is running. */
+  private selectFromLastExecution(wid: number): void {
+    this.workflowExecutionsService
+      .retrieveLatestWorkflowExecution(wid)
+      .pipe(untilDestroyed(this))
+      .subscribe({
+        next: (latestWorkflowExecution: WorkflowExecutionsEntry) => {
+          this.selectComputingUnit(wid, latestWorkflowExecution.cuId);
+        },
+        error: () => {
+          const runningUnit = this.allComputingUnits.find(unit => unit.status 
=== "Running");
+          if (runningUnit) {
+            this.selectComputingUnit(wid, runningUnit.computingUnit.cuid);
+          }
+        },

Review Comment:
   The stale-workflow guard ends before this asynchronous fallback. If the unit 
list rejects a remembered unit and workflow B opens while workflow A's 
latest-execution request is pending, A's response calls `selectComputingUnit(A, 
...)` and reconnects the shared status service to the workflow no longer 
displayed. Recheck `wid === this.workflowId` in both callbacks before selecting.



##########
frontend/src/app/workspace/component/menu/menu.component.ts:
##########
@@ -626,6 +637,70 @@ export class MenuComponent implements OnInit, OnDestroy {
     this.fileSaverService.saveAs(new Blob([workflowContentJson], { type: 
"text/plain;charset=utf-8" }), fileName);
   }
 
+  /**
+   * Open the Form View -- a full page load, not a route: the two views share 
root-level
+   * singletons (graph, Yjs shared model), and routing left the old 
collaboration client
+   * alive (you appeared as your own coeditor). A fresh document is the clean 
handover.
+   */
+  public onClickOpenFormView(): void {
+    const wid = this.workflowActionService.getWorkflowMetadata().wid;
+    if (wid === undefined || this.handingOverToFormView) {
+      return;
+    }
+    // Save first, and hand over only once the save has completed. The 
full-page load that
+    // follows unloads this document, and a request still in flight at that 
moment is aborted, so
+    // navigating right after firing the save could lose the very edit the 
switch is meant to carry
+    // across; the workspace's beforeunload save runs into the same unload and 
is no safety net. A
+    // save that fails keeps the user here with the error shown, rather than 
leaving with changes
+    // that were never stored. The form's own switch (openRegularCanvas) does 
the same.
+    //
+    // Two more things the hand-over must not lose. An autosave already in 
flight when the switch
+    // is clicked: WorkflowPersistService sends saves one at a time and in 
order, so ours lands after
+    // it and completes after it. And an edit made while our save is out (the 
page stays editable
+    // until the load): workflowChanged marks it, and the drain below saves 
once more before handing
+    // over rather than letting the full-page load abort that edit's own 
debounced autosave.
+    this.handingOverToFormView = true;
+    this.isSaving = true;
+    this.saveThenOpenFormView(wid);

Review Comment:
   Read-only workflows still render this view switch, but this path always 
attempts `persistWorkflow`; the backend rejects that write, so readers see an 
error and can never reach Form View. Since `writeAccess` already reflects 
`metadata.readonly`, skip the save and navigate directly when it is false, 
matching the Form View's reader-side switch.
   
   This issue also appears on line 668 of the same file.



-- 
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