Yicong-Huang commented on code in PR #8629:
URL: https://github.com/apache/texera/pull/8629#discussion_r4365090761
##########
frontend/src/app/workspace/component/workflow-editor/workflow-editor.component.spec.ts:
##########
@@ -2417,4 +2418,286 @@ describe("WorkflowEditorComponent editor wiring", () =>
{
expect(detectChanges).toHaveBeenCalledTimes(1);
});
});
+
+ describe("heat-map overlay", () => {
+ const HOT = scoreToColor(1);
+ const COLD = scoreToColor(0);
+ const ENABLED_FILL = "#FFFFFF";
+ const DISABLED_FILL = "#E0E0E0";
+
+ const wrapper = () => workflowActionService.getJointGraphWrapper();
+ const fillOf = (operatorID: string) =>
component.paper.getModelById(operatorID).attr("rect.body/fill");
+
+ /**
+ * Drives the real statistics -> performance-metrics chain rather than
pushing metrics
+ * directly, so the scoring the overlay reads is the one the service
derives.
+ */
+ function publishStatistics(byOperator: Record<string, { dataNs?: number;
inRows?: number; outRows?: number }>) {
+ const payload: Record<string, unknown> = {};
+ for (const [operatorID, m] of Object.entries(byOperator)) {
+ payload[operatorID] = {
+ ...emptyStatistics,
+ aggregatedDataProcessingTime: m.dataNs ?? 0,
+ aggregatedInputRowCount: m.inRows ?? 0,
+ aggregatedOutputRowCount: m.outRows ?? 0,
+ };
+ }
+ (workflowStatusService as any).statisticsSubject.next(payload);
+ }
+
+ /** scan is the hot operator, result the cold one, under the Runtime view.
*/
+ function addTwoOperatorsWithSpread() {
+ workflowActionService.addOperator(mockScanPredicate, mockPoint);
+ workflowActionService.addOperator(mockResultPredicate, mockPoint);
+ publishStatistics({
+ [mockScanPredicate.operatorID]: { dataNs: 1_000_000_000, inRows: 100,
outRows: 10 },
+ [mockResultPredicate.operatorID]: { dataNs: 0, inRows: 10, outRows: 10
},
+ });
+ }
+
+ function hoverOver(operatorID: string, clientX = 0, clientY = 0): void {
+ const view = component.paper.findViewByModel(operatorID);
+ (component.paper as any).trigger("element:mouseenter", view, { clientX,
clientY });
+ }
+
+ describe("painting", () => {
+ it("paints the heat spread as soon as a view is selected", () => {
+ addTwoOperatorsWithSpread();
+
+ wrapper().setHeatmapView(HeatmapView.Runtime);
+
+ expect(fillOf(mockScanPredicate.operatorID)).toEqual(HOT);
+ expect(fillOf(mockResultPredicate.operatorID)).toEqual(COLD);
+ });
+
+ it("repaints when new metrics arrive while a view is active", () => {
+ addTwoOperatorsWithSpread();
+ wrapper().setHeatmapView(HeatmapView.Runtime);
+
+ // The spread inverts: result becomes the slow one.
+ publishStatistics({
+ [mockScanPredicate.operatorID]: { dataNs: 0 },
+ [mockResultPredicate.operatorID]: { dataNs: 1_000_000_000 },
+ });
+
+ expect(fillOf(mockScanPredicate.operatorID)).toEqual(COLD);
+ expect(fillOf(mockResultPredicate.operatorID)).toEqual(HOT);
+ });
+
+ it("leaves fills alone while no view is active, even as metrics stream
in", () => {
+ addTwoOperatorsWithSpread();
+
+ expect(fillOf(mockScanPredicate.operatorID)).toEqual(ENABLED_FILL);
+ expect(fillOf(mockResultPredicate.operatorID)).toEqual(ENABLED_FILL);
+ });
+
+ it("paints an operator with no metrics as no-data rather than coldest",
() => {
+ // Absent from the payload entirely: it must not anchor the scale
minimum.
+ workflowActionService.addOperator(mockScanPredicate, mockPoint);
+ workflowActionService.addOperator(mockResultPredicate, mockPoint);
+ publishStatistics({ [mockScanPredicate.operatorID]: { dataNs:
1_000_000_000 } });
+
+ wrapper().setHeatmapView(HeatmapView.Runtime);
+
+
expect(fillOf(mockResultPredicate.operatorID)).toEqual(HEATMAP_NO_DATA_COLOR);
+ });
+
+ it("paints an operator the active view cannot measure as no-data", () =>
{
+ // I/O imbalance is undefined without input rows (a source), which is
a different
+ // thing from measuring zero.
+ addTwoOperatorsWithSpread();
+ publishStatistics({
+ [mockScanPredicate.operatorID]: { inRows: 0, outRows: 500 },
+ [mockResultPredicate.operatorID]: { inRows: 100, outRows: 1 },
+ });
+
+ wrapper().setHeatmapView(HeatmapView.IoImbalance);
+
+
expect(fillOf(mockScanPredicate.operatorID)).toEqual(HEATMAP_NO_DATA_COLOR);
+
expect(fillOf(mockResultPredicate.operatorID)).not.toEqual(HEATMAP_NO_DATA_COLOR);
Review Comment:
**Advisory:**
The untouched default `#FFFFFF` fill also satisfies
`not.toEqual(HEATMAP_NO_DATA_COLOR)`, so this half of the spec would not catch
the measurable operator going unpainted. It is the only measurable operator, so
it scores 1 and its fill is deterministic. Pinning it with `toEqual(HOT)`
closes that gap. The negated checks after `toEqual(HOT)` at lines 2583 and 2594
are implied by the positive assertion and can go.
```suggestion
expect(fillOf(mockResultPredicate.operatorID)).toEqual(HOT);
```
--
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]