This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a commit to branch 
gh-readonly-queue/main/pr-8592-2ab8ee0f2859339458cd8f44659db989910b201d
in repository https://gitbox.apache.org/repos/asf/texera.git

commit 5264df2861840bf8b1662ff2e1814b8aa80215db
Author: Meng Wang <[email protected]>
AuthorDate: Fri Sep 18 19:39:26 2026 +0000

    fix(frontend): say the warehouse is missing on the form view's run button 
(#8592)
    
    ### What changes were proposed in this PR?
    
    The form view's run button enumerates every state a run cannot start
    from — invalid workflow, empty workflow, connecting, no computing unit,
    a read-only unit — so that, in its own words, "the reader is never sent
    to press a button that does nothing". The warehouse requirement added in
    #8551 was missing from that list: with the feature enabled and nothing
    picked, the button read `Run` and looked ready, and the click was
    refused deeper down in `ExecuteWorkflowService`, with a toast.
    
    It now names what is missing, ordered after the computing unit as the
    canvas orders them, and stays disabled — the warehouse is picked in the
    embedded selector, the same reasoning the no-unit case already gives.
    The condition it reads is the one `ExecuteWorkflowService` refuses on —
    the boot-time flag and the picked warehouse, both root-scoped — so the
    button predicts that refusal exactly.
    
    Flag off (the default): `warehouseRequiredButMissing` is never true, so
    the button behaves exactly as before.
    
    ### Any related issues, documentation, discussions?
    
    Closes #8591. Follows #8551. Part of #6870.
    
    ### How was this PR tested?
    
    - Two Vitest cases added: the button names the missing warehouse and
    stays disabled, and returns to `Run` once one is picked. The workspace
    suite passes in full: 3324 tests.
    - Failure path verified rather than assumed: the new case was removed on
    purpose and the suite confirmed to fail for the expected reason before
    being restored.
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (claude-opus-5, claude-fable-5)
---
 .../workflow-form/workflow-form.component.spec.ts  | 31 +++++++++++++++++++++-
 .../workflow-form/workflow-form.component.ts       | 15 ++++++++++-
 .../workflow-form/workflow-form.spec-harness.ts    | 10 ++++++-
 3 files changed, 53 insertions(+), 3 deletions(-)

diff --git 
a/frontend/src/app/workspace/component/workflow-form/workflow-form.component.spec.ts
 
b/frontend/src/app/workspace/component/workflow-form/workflow-form.component.spec.ts
index 50edda7f4b..0c3e5e1f8a 100644
--- 
a/frontend/src/app/workspace/component/workflow-form/workflow-form.component.spec.ts
+++ 
b/frontend/src/app/workspace/component/workflow-form/workflow-form.component.spec.ts
@@ -71,7 +71,8 @@ describe("WorkflowFormComponent", () => {
       h.datePipe as any,
       h.panelResizeService as any,
       h.validationWorkflowService as any,
-      h.config as any
+      h.config as any,
+      h.warehouseService as any
     );
     return component;
   };
@@ -1506,6 +1507,34 @@ describe("WorkflowFormComponent", () => {
       expect(component.runButtonState).toEqual({ label: "Computing Unit", 
icon: "plus-circle", disabled: true });
     });
 
+    it("names the missing warehouse instead of offering a run that would be 
refused (#8591)", () => {
+      build(formViewWorkflow).ngOnInit();
+      makeReady();
+      h.config.env.warehouseEnabled = true;
+      h.warehouseService.selectWarehouse(undefined);
+
+      expect(component.runButtonState).toEqual({ label: "Warehouse", icon: 
"plus-circle", disabled: true });
+    });
+
+    it("says 'No access' before the warehouse, since picking one would not 
unblock a reader", () => {
+      build(formViewWorkflow).ngOnInit();
+      makeReady();
+      (component as any).selectedUnit = { accessPrivilege: "READ" };
+      h.config.env.warehouseEnabled = true;
+      h.warehouseService.selectWarehouse(undefined);
+
+      expect(component.runButtonState).toEqual({ label: "No access", icon: 
"lock", disabled: true });
+    });
+
+    it("runs once a warehouse is picked", () => {
+      build(formViewWorkflow).ngOnInit();
+      makeReady();
+      h.config.env.warehouseEnabled = true;
+      h.warehouseService.selectWarehouse(7);
+
+      expect(component.runButtonState).toEqual({ label: "Run", icon: 
"caret-right", disabled: false });
+    });
+
     it("offers Run once a unit is up and the graph is valid", () => {
       build(formViewWorkflow).ngOnInit();
       makeReady();
diff --git 
a/frontend/src/app/workspace/component/workflow-form/workflow-form.component.ts 
b/frontend/src/app/workspace/component/workflow-form/workflow-form.component.ts
index 2b9433c482..0c3c182b12 100644
--- 
a/frontend/src/app/workspace/component/workflow-form/workflow-form.component.ts
+++ 
b/frontend/src/app/workspace/component/workflow-form/workflow-form.component.ts
@@ -53,6 +53,7 @@ import { FormBindingService, ResolvedField } from 
"../../service/form-binding/fo
 import { WorkflowActionService } from 
"../../service/workflow-graph/model/workflow-action.service";
 import { ValidationWorkflowService } from 
"../../service/validation/validation-workflow.service";
 import { GuiConfigService } from "../../../common/service/gui-config.service";
+import { WarehouseService } from 
"../../../common/service/warehouse/warehouse.service";
 import { WorkflowConsoleService } from 
"../../service/workflow-console/workflow-console.service";
 import { WorkflowResultService } from 
"../../service/workflow-result/workflow-result.service";
 import { PanelResizeService } from 
"../../service/workflow-result/panel-resize/panel-resize.service";
@@ -299,7 +300,8 @@ export class WorkflowFormComponent implements OnInit, 
OnDestroy {
     // Same source the operator canvas reads its "Invalid" / "Empty" states 
from, so Run is
     // disabled here exactly when it is disabled there.
     private validationWorkflowService: ValidationWorkflowService,
-    private config: GuiConfigService
+    private config: GuiConfigService,
+    private warehouseService: WarehouseService
   ) {}
 
   ngOnInit(): void {
@@ -1526,6 +1528,12 @@ export class WorkflowFormComponent implements OnInit, 
OnDestroy {
     );
   }
 
+  /** The exact condition ExecuteWorkflowService refuses a run on, so the 
button can say it first
+   *  instead of starting nothing and explaining in a toast. */
+  public get hasNoWarehouse(): boolean {
+    return this.config.env.warehouseEnabled && 
this.warehouseService.getSelectedWarehouseIdValue() === undefined;
+  }
+
   /** No unit chosen yet: the button names what is missing and stays disabled, 
because the unit is
    *  picked in the embedded selector -- unlike the canvas, where that button 
is itself the click
    *  target for creating one. */
@@ -1573,6 +1581,11 @@ export class WorkflowFormComponent implements OnInit, 
OnDestroy {
     if (!this.hasUnitWriteAccess) {
       return { label: "No access", icon: "lock", disabled: true };
     }
+    // Last of the blocked states: picking a warehouse is the only one of them 
the reader can act
+    // on here, so naming it earlier would send a reader without write access 
to fix the wrong thing.
+    if (this.hasNoWarehouse) {
+      return { label: "Warehouse", icon: "plus-circle", disabled: true };
+    }
     return { label: "Run", icon: "caret-right", disabled: false };
   }
 
diff --git 
a/frontend/src/app/workspace/component/workflow-form/workflow-form.spec-harness.ts
 
b/frontend/src/app/workspace/component/workflow-form/workflow-form.spec-harness.ts
index dc5cb7609c..9ca8be1001 100644
--- 
a/frontend/src/app/workspace/component/workflow-form/workflow-form.spec-harness.ts
+++ 
b/frontend/src/app/workspace/component/workflow-form/workflow-form.spec-harness.ts
@@ -308,7 +308,14 @@ export function setupHarness() {
   // default so a rebuild is never suppressed, and overridden by the tests 
that probe typing.
   const host = { nativeElement: { querySelector: () => null, contains: () => 
false } };
   const datePipe = { transform: () => "01/01/2026 00:00:00" };
-  const config = { env: { formViewEnabled: true } };
+  const config = { env: { formViewEnabled: true, warehouseEnabled: false } };
+  // The run button asks the same pair ExecuteWorkflowService refuses on: the 
flag above and the
+  // pick below.
+  let selectedWarehouseId: number | undefined = undefined;
+  const warehouseService = {
+    getSelectedWarehouseIdValue: () => selectedWarehouseId,
+    selectWarehouse: (whid: number | undefined) => (selectedWarehouseId = 
whid),
+  };
 
   // Point the persist mock at `workflow`; each spec supplies the remaining 
constructor
   // arguments in its own order via the named mocks above.
@@ -343,6 +350,7 @@ export function setupHarness() {
     host,
     datePipe,
     config,
+    warehouseService,
     workflowChangedStream,
     formBindingChanged,
     workflowMetaDataChangedStream,

Reply via email to