This is an automated email from the ASF dual-hosted git repository.
yunyd pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/texera.git
The following commit(s) were added to refs/heads/main by this push:
new 852ebee5a4 feat(gui): Display the status for each operator during
execution (#4195)
852ebee5a4 is described below
commit 852ebee5a49998e6c09b819f34fb3a167c205682
Author: yunyad <[email protected]>
AuthorDate: Fri Feb 13 19:33:20 2026 -0800
feat(gui): Display the status for each operator during execution (#4195)
<!--
Thanks for sending a pull request (PR)! Here are some tips for you:
1. If this is your first time, please read our contributor guidelines:
[Contributing to
Texera](https://github.com/apache/texera/blob/main/CONTRIBUTING.md)
2. Ensure you have added or run the appropriate tests for your PR
3. If the PR is work in progress, mark it a draft on GitHub.
4. Please write your PR title to summarize what this PR proposes, we
are following Conventional Commits style for PR titles as well.
5. Be sure to keep the PR description updated to reflect all changes.
-->
### What changes were proposed in this PR?
<!--
Please clarify what changes you are proposing. The purpose of this
section
is to outline the changes. Here are some tips for you:
1. If you propose a new API, clarify the use case for a new API.
2. If you fix a bug, you can clarify why it is a bug.
3. If it is a refactoring, clarify what has been changed.
3. It would be helpful to include a before-and-after comparison using
screenshots or GIFs.
4. Please consider writing useful notes for better and faster reviews.
-->
This PR addresses issue #1753 by adding a toggleable per-operator status
overlay in the workflow UI. When enabled, each operator displays its
execution state (Running / Completed / Waiting / Pausing) along with the
associated worker info, following the same display pattern as the
existing worker indicator. The overlay is color-coded by operator state
and is positioned as close to the operator as possible to keep the UI
clean and readable during execution.
See the screenshot for a quick demo.
### Any related issues, documentation, discussions?
Fixes #1753
### How was this PR tested?
<!--
If tests were added, say they were added here. Or simply mention that if
the PR
is tested with existing test cases. Make sure to include/update test
cases that
check the changes thoroughly including negative and positive cases if
possible.
If it was tested in a way different from regular unit tests, please
clarify how
you tested step by step, ideally copy and paste-able, so that other
reviewers can
test and check, and descendants can verify in the future. If tests were
not added,
please describe why they were not added and/or why it was difficult to
add.
-->
**Manual UI Testing (Local):**
1. Start the application locally.
2. Open the workflow UI and load a workflow containing multiple
operators.
3. Run the workflow execution.
4. Enable the **"Show operator status"** toggle.
5. Verify that:
- Each operator displays its status (**Running / Completed / Waiting /
Pausing**).
- The status indicator is **color-coded** according to the operator
state.
- The status updates **in real time** as execution progresses.
- The indicator is positioned **close to the operator** and does not
obstruct the UI.
**Tests:**
No new automated tests were added.
This change primarily affects **UI rendering and real-time visual
behavior**, which is difficult to validate via existing unit tests. The
functionality was verified through manual UI testing.
### Was this PR authored or co-authored using generative AI tooling?
No.

---
.../app/workspace/component/menu/menu.component.html | 8 ++++++++
.../app/workspace/component/menu/menu.component.ts | 18 ++++++++++++++++++
.../workflow-editor/workflow-editor.component.scss | 4 ++++
.../workflow-editor/workflow-editor.component.ts | 1 +
.../workspace/service/joint-ui/joint-ui.service.ts | 19 +++++++++++++++++++
5 files changed, 50 insertions(+)
diff --git a/frontend/src/app/workspace/component/menu/menu.component.html
b/frontend/src/app/workspace/component/menu/menu.component.html
index c9828e9d77..7433ccab67 100644
--- a/frontend/src/app/workspace/component/menu/menu.component.html
+++ b/frontend/src/app/workspace/component/menu/menu.component.html
@@ -180,6 +180,14 @@
>Workers</label
>
</li>
+ <li nz-menu-item>
+ <label
+ nz-checkbox
+ [(ngModel)]="showStatus"
+ (ngModelChange)="toggleStatus()"
+ >Status</label
+ >
+ </li>
</ul>
</nz-dropdown-menu>
<button
diff --git a/frontend/src/app/workspace/component/menu/menu.component.ts
b/frontend/src/app/workspace/component/menu/menu.component.ts
index 69ccfd48c6..8ee4cf622d 100644
--- a/frontend/src/app/workspace/component/menu/menu.component.ts
+++ b/frontend/src/app/workspace/component/menu/menu.component.ts
@@ -92,6 +92,7 @@ export class MenuComponent implements OnInit, OnDestroy {
public showRegion: boolean = false;
public showGrid: boolean = false;
public showNumWorkers: boolean = false;
+ public showStatus: boolean = false;
protected readonly DASHBOARD_USER_WORKFLOW = DASHBOARD_USER_WORKFLOW;
@Input() public writeAccess: boolean = false;
@@ -261,6 +262,23 @@ export class MenuComponent implements OnInit, OnDestroy {
this.workflowActionService
.getJointGraphWrapper()
.mainPaper.el.classList.toggle("hide-worker-count",
!this.showNumWorkers);
+ this.applyOperatorStatusPosition();
+ }
+
+ toggleStatus() {
+ this.workflowActionService
+ .getJointGraphWrapper()
+ .mainPaper.el.classList.toggle("hide-operator-status", !this.showStatus);
+ this.applyOperatorStatusPosition();
+ }
+
+ private applyOperatorStatusPosition(): void {
+ const refY = this.showNumWorkers ? -55 : -35;
+ const paperModel =
this.workflowActionService.getJointGraphWrapper().mainPaper.model as any;
+ paperModel.getElements().forEach((el: any) => {
+ el.attr(".operator-status/ref-x", -10);
+ el.attr(".operator-status/ref-y", refY);
+ });
}
public async onClickOpenShareAccess(): Promise<void> {
diff --git
a/frontend/src/app/workspace/component/workflow-editor/workflow-editor.component.scss
b/frontend/src/app/workspace/component/workflow-editor/workflow-editor.component.scss
index fad009b223..33a74e37be 100644
---
a/frontend/src/app/workspace/component/workflow-editor/workflow-editor.component.scss
+++
b/frontend/src/app/workspace/component/workflow-editor/workflow-editor.component.scss
@@ -28,3 +28,7 @@
::ng-deep .hide-worker-count .operator-worker-count {
display: none;
}
+
+::ng-deep .hide-operator-status .operator-status {
+ display: none;
+}
diff --git
a/frontend/src/app/workspace/component/workflow-editor/workflow-editor.component.ts
b/frontend/src/app/workspace/component/workflow-editor/workflow-editor.component.ts
index b23f92caf3..b53fa4e663 100644
---
a/frontend/src/app/workspace/component/workflow-editor/workflow-editor.component.ts
+++
b/frontend/src/app/workspace/component/workflow-editor/workflow-editor.component.ts
@@ -248,6 +248,7 @@ export class WorkflowEditorComponent implements OnInit,
AfterViewInit, OnDestroy
height: this.editor.offsetHeight,
});
this.editor.classList.add("hide-worker-count");
+ this.editor.classList.add("hide-operator-status");
}
private handleDisableJointPaperInteractiveness(): void {
diff --git a/frontend/src/app/workspace/service/joint-ui/joint-ui.service.ts
b/frontend/src/app/workspace/service/joint-ui/joint-ui.service.ts
index 77458947cd..40a4129071 100644
--- a/frontend/src/app/workspace/service/joint-ui/joint-ui.service.ts
+++ b/frontend/src/app/workspace/service/joint-ui/joint-ui.service.ts
@@ -123,6 +123,7 @@ export const operatorNameClass = "texera-operator-name";
export const operatorFriendlyNameClass = "texera-operator-friendly-name";
export const operatorPortMetricsClass = "texera-operator-port-metrics";
const operatorWorkerCountClass = "operator-worker-count";
+const operatorStatusTextClass = "operator-status";
export const linkPathStrokeColor = "#919191";
@@ -141,6 +142,7 @@ class TexeraCustomJointElement extends
joint.shapes.devs.Model {
<text class="${operatorNameClass}"></text>
<text class="${operatorPortMetricsClass}"></text>
<text class="${operatorWorkerCountClass}"></text>
+ <text class="${operatorStatusTextClass}"></text>
<text class="${operatorStateClass}"></text>
<text class="${operatorReuseCacheTextClass}"></text>
<text class="${operatorCoeditorEditingClass}"></text>
@@ -324,6 +326,11 @@ export class JointUIService {
const workerCount = statistics.numWorkers ?? 1;
element.attr(`.${operatorWorkerCountClass}/text`, "#workers: " +
String(workerCount));
+ element.attr(
+ `.${operatorStatusTextClass}/text`,
+ "status: " +
JointUIService.getStatusDisplayText(statistics.operatorState)
+ );
+
inPorts.forEach(portDef => {
const portId = portDef.id;
if (portId != null) {
@@ -421,6 +428,7 @@ export class JointUIService {
"rect.body": { stroke: fillColor },
[`.${operatorPortMetricsClass}`]: { fill: fillColor },
[`.${operatorWorkerCountClass}`]: { fill: fillColor },
+ [`.${operatorStatusTextClass}`]: { fill: fillColor },
});
const element = jointPaper.getModelById(operatorID) as
joint.shapes.devs.Model;
const allPorts = element.getPorts();
@@ -812,6 +820,10 @@ export class JointUIService {
"ref-x": -5,
"ref-y": -35,
},
+ [`.${operatorStatusTextClass}`]: {
+ "ref-x": -10,
+ "ref-y": -35,
+ },
".delete-button": {
x: 60,
y: -20,
@@ -977,6 +989,13 @@ export class JointUIService {
return userCursor;
}
+ private static getStatusDisplayText(state: OperatorState): string {
+ if (state === OperatorState.Uninitialized) {
+ return "Waiting";
+ }
+ return String(state);
+ }
+
public static getJointUserPointerName(coeditor: Coeditor) {
return "pointer_" + coeditor.clientId;
}