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


##########
frontend/src/app/workspace/component/workflow-form/workflow-form.component.ts:
##########
@@ -164,6 +200,70 @@ export class WorkflowFormComponent implements OnInit, 
OnDestroy {
     this.wid = wid;
     this.load(wid);
 
+    // The run clock, reusing the operator canvas's source outright rather 
than timing anything
+    // here: the engine is the only thing that knows when the run really 
began, so a stopwatch
+    // started at the click would drift and would be wrong after a reload.
+    this.workflowWebsocketService
+      .subscribeToEvent("ExecutionDurationUpdateEvent")
+      .pipe(
+        tap(event => (this.executionDuration = event.duration)),
+        switchMap(event => (event.isRunning ? timer(1000, 1000) : EMPTY)),
+        untilDestroyed(this)
+      )
+      .subscribe(() => {
+        this.executionDuration += 1000;
+        this.cdr.markForCheck();
+      });
+
+    // The run button's state is read from getters, so a change in 
unit/connection/validity has to
+    // repaint the view. markForCheck, not detectChanges: a synchronous pass 
can be thrown out of by
+    // an unrelated component's NG0100, killing the subscription.
+    this.computingUnitStatusService
+      .getSelectedComputingUnit()
+      .pipe(untilDestroyed(this))
+      .subscribe(() => this.cdr.markForCheck());

Review Comment:
   The selected unit is discarded here, so the form never applies the 
computing-unit permission gate. A shared unit with `READ` or `NONE` privilege 
still reaches the enabled `Run` state and sends an execution request, whereas 
the canvas explicitly disables execution unless 
`selectedComputingUnit.accessPrivilege === Privilege.WRITE` 
(`menu.component.html:430`); this also contradicts this component's statement 
at lines 896-898 that execution is gated on computing-unit access. Retain the 
selected unit and include its write privilege in the run-button disabled state.



##########
frontend/src/app/workspace/component/workflow-form/workflow-form.component.ts:
##########
@@ -511,6 +616,154 @@ export class WorkflowFormComponent implements OnInit, 
OnDestroy {
     return rendered.resolved.binding.id;
   }
 
+  // 
---------------------------------------------------------------------------
+  // Instruction: the author's one piece of guidance, shown as rendered 
markdown
+  // 
---------------------------------------------------------------------------
+
+  public get hasInstruction(): boolean {
+    return this.instructionBody.trim().length > 0;
+  }
+
+  private async renderInstruction(): Promise<void> {
+    // Capture the body this render is for: parsing can resolve on a later 
microtask, and a fresh
+    // readConfig() may start another render meanwhile. If the configured body 
changed while we were
+    // parsing, this result is stale -- drop it so the newer render's output 
stands.
+    const body = this.instructionBody;
+    const html = body.trim() ? await 
Promise.resolve(this.markdownService.parse(body)) : "";
+    if (body !== this.instructionBody) {
+      return;
+    }
+    this.instructionPreviewHtml = html;
+    this.cdr.detectChanges();
+  }
+
+  public toggleInstruction(): void {
+    this.instructionOpen = !this.instructionOpen;
+  }
+
+  // 
---------------------------------------------------------------------------
+  // Running the same workflow the canvas runs, through the same execute/kill 
service. The canvas
+  // wraps its run with completion-email options 
(executeWorkflowWithEmailNotification); this page
+  // runs plainly (executeWorkflow), so a form-started run does not send that 
email.
+  // 
---------------------------------------------------------------------------
+
+  public get isRunning(): boolean {
+    return (
+      this.executionState !== ExecutionState.Uninitialized &&
+      this.executionState !== ExecutionState.Completed &&
+      this.executionState !== ExecutionState.Failed &&
+      this.executionState !== ExecutionState.Killed &&
+      this.executionState !== ExecutionState.Terminated
+    );
+  }
+
+  /**
+   * A unit is picked but its socket is still coming up -- the same window the 
operator canvas shows
+   * "Connecting" and disables its run button. Read from the exact condition 
the canvas uses
+   * (menu.component's getRunButtonBehavior), so the two stay in step.
+   */
+  public get isConnecting(): boolean {
+    return (
+      this.computingUnitStatus !== ComputingUnitState.NoComputingUnit && 
!this.workflowWebsocketService.isConnected
+    );
+  }
+
+  /** No unit chosen yet: the button shows a disabled "Connect" hint and the 
unit is picked in the
+   *  embedded selector -- unlike the canvas, where the Connect button is 
itself the click target. */
+  public get hasNoComputingUnit(): boolean {
+    return this.computingUnitStatus === ComputingUnitState.NoComputingUnit;
+  }
+
+  /**
+   * The Run button's label, icon and disabled state. It shares the operator 
canvas's disable
+   * conditions -- an invalid or empty workflow, a unit still connecting, or 
no unit chosen each
+   * disable it and say why -- but deliberately simplifies the execution 
states a reader needs down
+   * to Run and Stop, with no pause/resume: while a run is in flight the 
button stops (kills) it,
+   * otherwise it runs. (The canvas offers Pause/Resume/Submitting and a 
clickable Connect; a form
+   * reader does not, and picks the unit in the embedded selector instead.)
+   */
+  public get runButtonState(): { label: string; icon: string; disabled: 
boolean } {
+    if (this.isRunning) {
+      return { label: "Stop", icon: "stop", disabled: false };
+    }
+    if (!this.isWorkflowValid) {
+      return { label: "Invalid", icon: "warning", disabled: true };
+    }
+    if (this.isWorkflowEmpty) {
+      return { label: "Empty", icon: "info-circle", disabled: true };
+    }
+    if (this.isConnecting) {
+      return { label: "Connecting", icon: "loading", disabled: true };
+    }
+    if (this.hasNoComputingUnit) {
+      return { label: "Connect", icon: "plus-circle", disabled: true };
+    }
+    return { label: "Run", icon: "caret-right", disabled: false };
+  }

Review Comment:
   `isRunning` is checked before `isConnecting`, so if the workflow socket 
drops during an active run this returns an enabled Stop button. Clicking it 
calls `killWorkflow()`, whose request is sent through the disconnected socket 
and is not delivered; the canvas disables its kill/run controls in this state. 
Check the connection state first so the button remains disabled as `Connecting` 
until the socket reconnects.



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