Copilot commented on code in PR #6213:
URL: https://github.com/apache/texera/pull/6213#discussion_r3627484573
##########
frontend/src/app/workspace/component/workflow-editor/workflow-editor.component.ts:
##########
@@ -389,6 +414,111 @@ export class WorkflowEditorComponent implements OnInit,
AfterViewInit, OnDestroy
});
}
+ /**
+ * Drives the performance heat-map overlay (Layers > Performance). The
overlay colors only the
+ * operator body fill, so it coexists with the execution-status border.
Colors are derived from
+ * the WorkflowStatusService performance metrics via the pure
heatmap-scoring helpers, so both the
+ * main canvas and the mini-map (shared model) update.
+ */
+ private handleHeatmapOverlay(): void {
+ // Repaint whenever the active view or the metrics change (only while a
view is active).
+ combineLatest([this.wrapper.getHeatmapViewStream(),
this.workflowStatusService.getPerformanceMetricsStream()])
+ .pipe(untilDestroyed(this))
+ .subscribe(([view]) => {
+ if (view !== null) {
+ this.repaintHeatmapColors(view);
+ }
+ });
+
+ // Restore default fills when the overlay is turned off.
+ this.wrapper
+ .getHeatmapViewStream()
+ .pipe(untilDestroyed(this))
+ .subscribe(view => {
+ if (view === null) {
+ this.restoreAllOperatorFills();
+ this.heatmapTooltip = null;
+ }
+ });
+
+ // Paint newly (re)added operators when the overlay is active (e.g. after
reload).
+ this.workflowActionService
+ .getTexeraGraph()
+ .getOperatorAddStream()
+ .pipe(untilDestroyed(this))
+ .subscribe(() => {
+ const view = this.wrapper.getHeatmapView();
+ if (view !== null) {
+ this.repaintHeatmapColors(view);
+ }
+ });
+ }
+
+ private heatmapScores(view: HeatmapView): Record<string, number> {
+ const metrics = this.workflowStatusService.getCurrentPerformanceMetrics();
+ const rawById: Record<string, number> = {};
+ for (const operatorId of Object.keys(metrics)) {
+ rawById[operatorId] = rawMetricForView(metrics[operatorId], view);
+ }
+ return normalizeScores(rawById);
+ }
+
+ private repaintHeatmapColors(view: HeatmapView): void {
+ const scores = this.heatmapScores(view);
+ this.workflowActionService
+ .getTexeraGraph()
+ .getAllOperators()
+ .forEach(op => this.jointUIService.applyHeatmapColor(this.paper,
op.operatorID, scores[op.operatorID]));
+ }
+
+ private restoreAllOperatorFills(): void {
+ this.workflowActionService
+ .getTexeraGraph()
+ .getAllOperators()
+ .forEach(op => this.jointUIService.restoreOperatorFill(this.paper, op));
+ }
+
+ /**
+ * Shows a small tooltip with the hovered operator's metric value and heat
score for the active
+ * view. Only active while the heat-map overlay is on.
+ */
+ private handleHeatmapHover(): void {
+ fromJointPaperEvent(this.paper, "element:mouseenter")
+ .pipe(untilDestroyed(this))
+ .subscribe(([elementView, evt]) => {
+ const view = this.wrapper.getHeatmapView();
+ if (view === null) {
+ return;
+ }
+ const operatorId = elementView.model.id.toString();
+ if
(!this.workflowActionService.getTexeraGraph().hasOperator(operatorId)) {
+ return;
+ }
+ const metrics =
this.workflowStatusService.getCurrentPerformanceMetrics()[operatorId];
+ const rawValue = metrics ? rawMetricForView(metrics, view) : 0;
+ const score = this.heatmapScores(view)[operatorId];
+ const rect = this.editor.getBoundingClientRect();
+ const mouseEvent = evt as unknown as MouseEvent;
+ this.heatmapTooltip = {
+ x: mouseEvent.clientX - rect.left + 12,
+ y: mouseEvent.clientY - rect.top + 12,
+ title: heatmapViewTitle(view),
+ metricLabel: formatMetricForView(rawValue, view),
+ heatLabel: score === undefined ? "—" : `${Math.round(score * 100)}%`,
+ };
Review Comment:
When an operator has no performance metrics yet, the tooltip currently
formats the metric as `0` while the heat score is shown as "—". This makes
“missing data” indistinguishable from a real zero value for the active view.
Consider treating missing metrics as missing for the metric label as well
(e.g., render "—").
##########
frontend/src/app/workspace/service/heatmap/heatmap-scoring.ts:
##########
@@ -0,0 +1,154 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { OperatorPerformanceMetrics } from
"../workflow-status/performance-metrics";
+
+/**
+ * The three heat-map views. Each answers a different "where should I look?"
+ * question; see {@link rawMetricForView} for the per-operator cost each uses.
+ * String-valued so the selection serializes readably (e.g. to localStorage).
+ */
+export enum HeatmapView {
+ Runtime = "runtime",
+ Throughput = "throughput",
+ IoImbalance = "io-imbalance",
+}
+
+/**
+ * Per-operator raw cost for a view, BEFORE normalization. Higher = hotter
(more
+ * worth a look).
+ *
+ * - Runtime: data + control processing time — slower operators are hotter.
+ * - Throughput: seconds per output tuple — slow producers (low throughput)
are
+ * hotter; no output -> 0 (cold).
+ * - IoImbalance: |out - in| / (out + in) — operators that drop OR amplify
rows are
+ * hotter; a balanced operator or missing input -> 0 (cold).
Normalized
+ * to [0, 1] so an extreme amplifier can't dominate the scale.
+ */
+export function rawMetricForView(metrics: OperatorPerformanceMetrics, view:
HeatmapView): number {
+ switch (view) {
+ case HeatmapView.Runtime:
+ return metrics.dataProcessingTimeNs + metrics.controlProcessingTimeNs;
+ case HeatmapView.Throughput: {
+ const timeSec = (metrics.dataProcessingTimeNs +
metrics.controlProcessingTimeNs) / 1e9;
+ return metrics.outputRows > 0 ? timeSec / metrics.outputRows : 0;
+ }
+ case HeatmapView.IoImbalance:
+ return metrics.inputRows > 0
+ ? Math.abs(metrics.outputRows - metrics.inputRows) /
(metrics.outputRows + metrics.inputRows)
+ : 0;
+ default:
+ return 0;
+ }
+}
+
+/** Human-readable title for a view, shown in the legend and hover tooltip. */
+export function heatmapViewTitle(view: HeatmapView): string {
+ switch (view) {
+ case HeatmapView.Runtime:
+ return "Runtime";
+ case HeatmapView.Throughput:
+ return "Throughput";
+ case HeatmapView.IoImbalance:
+ return "I/O imbalance";
+ }
+}
Review Comment:
`heatmapViewTitle` doesn’t return a value for unexpected inputs (no
`default` branch / fallback return), so it can return `undefined` at runtime if
a non-enum value is ever passed in (e.g., from persisted state). Providing an
explicit default makes the function total and avoids silent `undefined` in the
UI.
##########
frontend/src/app/workspace/component/menu/menu.component.html:
##########
@@ -197,6 +197,39 @@
>Status</label
>
</li>
+ <li nz-menu-item>
+ <label
+ nz-checkbox
+ [(ngModel)]="showHeatmap"
+ (ngModelChange)="toggleHeatmap()"
+ >Performance</label
+ >
+ </li>
+ <li
+ nz-menu-item
+ *ngIf="showHeatmap"
+ class="heatmap-view-options"
+ [nzDisabled]="true">
Review Comment:
The Performance view selector is rendered inside an `nz-menu-item` that is
marked disabled (`[nzDisabled]="true"`). In Ng Zorro menus, a disabled item
typically blocks pointer/click interaction for its contents, which can prevent
the radio buttons from being selectable and also exposes incorrect disabled
semantics to assistive tech.
Prefer leaving the item enabled and stopping event propagation so selecting
a view doesn’t close the dropdown.
--
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]