This is an automated email from the ASF dual-hosted git repository.
linxinyuan 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 9322ae62db feat(gui): Display the number of workers for each operator
during execution (#3911)
9322ae62db is described below
commit 9322ae62db5c6e5407c97654419c959ed694be3f
Author: yunyad <[email protected]>
AuthorDate: Wed Oct 15 16:42:26 2025 -0700
feat(gui): Display the number of workers for each operator during execution
(#3911)
### Description
This PR adds a feature to display the number of workers (degree of
parallelism) assigned to each operator during workflow execution.
A small label appears on top of each operator node while the workflow is
running, indicating the current number of workers for that operator.
To improve user experience and avoid clutter, a checkbox is provided to
show or hide the worker count indicators as needed.
### Motivation
Previously, only UDF operators exposed their worker count in the
property panel, while native operators (e.g., Java) did not surface this
information. This enhancement improves runtime observability and helps
users better understand system behavior and resource allocation during
execution.
### Demo

Fix #3841
---------
Co-authored-by: Xinyuan Lin <[email protected]>
---
.../src/app/workspace/component/menu/menu.component.html | 14 ++++++++++++--
.../src/app/workspace/component/menu/menu.component.ts | 13 ++++++++++---
.../workflow-editor/workflow-editor.component.scss | 4 ++++
.../component/workflow-editor/workflow-editor.component.ts | 1 +
.../src/app/workspace/service/joint-ui/joint-ui.service.ts | 10 ++++++++++
.../src/app/workspace/types/execute-workflow.interface.ts | 1 +
6 files changed, 38 insertions(+), 5 deletions(-)
diff --git a/frontend/src/app/workspace/component/menu/menu.component.html
b/frontend/src/app/workspace/component/menu/menu.component.html
index e88eeaca25..dde9a8aff9 100644
--- a/frontend/src/app/workspace/component/menu/menu.component.html
+++ b/frontend/src/app/workspace/component/menu/menu.component.html
@@ -159,12 +159,22 @@
<li nz-menu-item>
<label
nz-checkbox
- [(ngModel)]="grid"
- (ngModelChange)="onGridChange()"
+ [(ngModel)]="showGrid"
+ (ngModelChange)="toggleGrid()"
>Grid</label
>
</li>
</ul>
+ <ul nz-menu>
+ <li nz-menu-item>
+ <label
+ nz-checkbox
+ [(ngModel)]="showNumWorkers"
+ (ngModelChange)="toggleNumWorkers()"
+ >#Workers</label
+ >
+ </li>
+ </ul>
</nz-dropdown-menu>
<button
(click)="onClickClosePanels()"
diff --git a/frontend/src/app/workspace/component/menu/menu.component.ts
b/frontend/src/app/workspace/component/menu/menu.component.ts
index 994608fc36..ae804b0e1d 100644
--- a/frontend/src/app/workspace/component/menu/menu.component.ts
+++ b/frontend/src/app/workspace/component/menu/menu.component.ts
@@ -88,7 +88,8 @@ export class MenuComponent implements OnInit, OnDestroy {
public isWorkflowModifiable: boolean = false;
public workflowId?: number;
public isExportDeactivate: boolean = false;
- public grid: boolean = false;
+ public showGrid: boolean = false;
+ public showNumWorkers: boolean = false;
protected readonly DASHBOARD_USER_WORKFLOW = DASHBOARD_USER_WORKFLOW;
@Input() public writeAccess: boolean = false;
@@ -253,6 +254,12 @@ export class MenuComponent implements OnInit, OnDestroy {
document.body.removeChild(tempSpan);
}
+ toggleNumWorkers() {
+ this.workflowActionService
+ .getJointGraphWrapper()
+ .mainPaper.el.classList.toggle("hide-worker-count",
!this.showNumWorkers);
+ }
+
public async onClickOpenShareAccess(): Promise<void> {
this.modalService.create({
nzContent: ShareAccessComponent,
@@ -458,8 +465,8 @@ export class MenuComponent implements OnInit, OnDestroy {
});
}
- public onGridChange(): void {
-
this.workflowActionService.getJointGraphWrapper().mainPaper.setGridSize(this.grid
? 2 : 1);
+ public toggleGrid(): void {
+
this.workflowActionService.getJointGraphWrapper().mainPaper.setGridSize(this.showGrid
? 2 : 1);
}
/**
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 872e9fc287..a51b289dab 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
@@ -37,3 +37,7 @@
::ng-deep #workflow-editor .link-tools .tool-remove circle {
fill-opacity: 0;
}
+
+::ng-deep .hide-worker-count .operator-worker-count {
+ 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 a334b48603..b18f4baa86 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
@@ -244,6 +244,7 @@ export class WorkflowEditorComponent implements OnInit,
AfterViewInit, OnDestroy
width: this.editor.offsetWidth,
height: this.editor.offsetHeight,
});
+ this.editor.classList.add("hide-worker-count");
}
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 84a88ea29e..ba3f611cbe 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
@@ -111,6 +111,7 @@ export const operatorIconClass = "texera-operator-icon";
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";
export const linkPathStrokeColor = "#919191";
@@ -128,6 +129,7 @@ class TexeraCustomJointElement extends
joint.shapes.devs.Model {
<text class="${operatorFriendlyNameClass}"></text>
<text class="${operatorNameClass}"></text>
<text class="${operatorPortMetricsClass}"></text>
+ <text class="${operatorWorkerCountClass}"></text>
<text class="${operatorStateClass}"></text>
<text class="${operatorReuseCacheTextClass}"></text>
<text class="${operatorCoeditorEditingClass}"></text>
@@ -308,6 +310,9 @@ export class JointUIService {
const inputMetrics = statistics.inputPortMetrics;
const outputMetrics = statistics.outputPortMetrics;
+ const workerCount = statistics.numWorkers ?? 1;
+ element.attr(`.${operatorWorkerCountClass}/text`, "#workers: " +
String(workerCount));
+
inPorts.forEach(portDef => {
const portId = portDef.id;
if (portId != null) {
@@ -404,6 +409,7 @@ export class JointUIService {
[`.${operatorStateClass}`]: { fill: fillColor },
"rect.body": { stroke: fillColor },
[`.${operatorPortMetricsClass}`]: { fill: fillColor },
+ [`.${operatorWorkerCountClass}`]: { fill: fillColor },
});
const element = jointPaper.getModelById(operatorID) as
joint.shapes.devs.Model;
const allPorts = element.getPorts();
@@ -791,6 +797,10 @@ export class JointUIService {
"y-alignment": "middle",
"x-alignment": "middle",
},
+ [`.${operatorWorkerCountClass}`]: {
+ "ref-x": -5,
+ "ref-y": -35,
+ },
".delete-button": {
x: 60,
y: -20,
diff --git a/frontend/src/app/workspace/types/execute-workflow.interface.ts
b/frontend/src/app/workspace/types/execute-workflow.interface.ts
index bc1ab9f7d9..23ade23199 100644
--- a/frontend/src/app/workspace/types/execute-workflow.interface.ts
+++ b/frontend/src/app/workspace/types/execute-workflow.interface.ts
@@ -85,6 +85,7 @@ export interface OperatorStatistics
inputPortMetrics: Record<string, number>;
aggregatedOutputRowCount: number;
outputPortMetrics: Record<string, number>;
+ numWorkers?: number;
}> {}
export interface OperatorStatsUpdate