Skip to content

fix(frontend): preserve operator state border on workflow page return#5146

Open
PG1204 wants to merge 7 commits into
apache:mainfrom
PG1204:fix/prevent-operator-status-color-reset
Open

fix(frontend): preserve operator state border on workflow page return#5146
PG1204 wants to merge 7 commits into
apache:mainfrom
PG1204:fix/prevent-operator-status-color-reset

Conversation

@PG1204
Copy link
Copy Markdown
Contributor

@PG1204 PG1204 commented May 22, 2026

What changes were proposed in this PR?

Fixes a visual regression where an operator's border, state text, port row counts, and worker count reset to default after navigating away from a workflow page and returning, even though the execution state is still cached in WorkflowStatusService.

Root cause: WorkspaceComponent clears the workflow on destroy and calls reloadWorkflow() on re-init, recreating every operator from the workflow JSON with default JointJS attributes. The cached execution status was never reapplied, and the validation pass that runs on operator-add called changeOperatorColor(..., true) for valid operators, overwriting rect.body/stroke and forcing the border back to gray.

Fix (two changes, both in workflow-editor.component.ts):

Subscribe to getOperatorAddStream() inside handleOperatorStatisticsUpdate. When an operator is added (drag-drop, reloadWorkflow, undo/redo, collaborative add via Yjs - all routed through a single emission point), look up the cached OperatorStatistics. If present, call changeOperatorStatistics(...) to restore the state color, port labels, worker count, and state text. New operators with no cached entry early-return and get default coloring.

Make handleOperatorValidation status-aware. Invalid operators still get a red border (priority preserved). For valid operators, the handler now checks the cached status - if one exists, it repaints via changeOperatorState(...) instead of overwriting with default gray. Valid operators with no cached status continue to get the default gray border.

Before fix:

473837274-d703b42e-8504-4735-bc9a-bcac9602c201.mov

After fix:

Screen.Recording.2026-05-22.at.4.18.14.PM.mov

Any related issues, documentation, discussions?

Fixes #3614.

How was this PR tested?

Unit tests: Three tests added to workflow-editor.component.spec.ts under a new describe("operator border restoration after navigation") block:

Valid operator + cached Completed -> changeOperatorState(..., Completed) is called.
Valid operator + empty cache -> default changeOperatorColor(..., true) is called (existing behavior preserved).
Invalid operator + cached Completed -> changeOperatorColor(..., false) is called, red wins (existing behavior preserved).
All three pass under Vitest (ng test).

Manual UI testing: Reproduced the issue's recording locally:

Open a workflow (e.g., CSV File Scan → Radar Chart) and run it; both operators turn green with port row counts.
Navigate to a different page, then back.
Before fix: operators reset to default gray borders, row counts blank. After fix: green borders, row counts, worker counts, and "Completed" state label all persist.
Edge cases manually verified: fresh workflow (default coloring), new operator dragged in after a completed run (default for new, cached for existing), re-running (resetStatus repaints Uninitialized, live updates flow normally), invalid operator with cached Completed (red border, validation priority).

Note: workflow-editor.component.spec.ts was previously excluded from the default jsdom test target in angular.json. This PR removes that exclusion (so the new tests run in CI and contribute to Codecov coverage) and comments out six pre-existing mouse-event tests that fail under jsdom (they continue to pass in the ng run gui:test-browser target, marked with TODO(#3614) in place). A follow-up PR will revive those commented-out tests.

Was this PR authored or co-authored using generative AI tooling?

Co-authored-by: Claude Code (Anthropic Claude Opus 4.7)

@github-actions github-actions Bot added fix frontend Changes related to the frontend GUI labels May 22, 2026
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 22, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 49.71%. Comparing base (953e2c4) to head (95a8941).

Additional details and impacted files
@@             Coverage Diff              @@
##               main    #5146      +/-   ##
============================================
+ Coverage     49.16%   49.71%   +0.55%     
+ Complexity     2384     2347      -37     
============================================
  Files          1051     1049       -2     
  Lines         40350    40219     -131     
  Branches       4279     4278       -1     
============================================
+ Hits          19837    19994     +157     
+ Misses        19353    19069     -284     
+ Partials       1160     1156       -4     
Flag Coverage Δ *Carryforward flag
access-control-service 39.41% <ø> (-2.49%) ⬇️ Carriedforward from 69bee15
agent-service 33.76% <ø> (ø) Carriedforward from 69bee15
amber 51.39% <ø> (-0.27%) ⬇️ Carriedforward from 69bee15
computing-unit-managing-service 0.00% <ø> (ø) Carriedforward from 69bee15
config-service 0.00% <ø> (ø) Carriedforward from 69bee15
file-service 37.56% <ø> (-0.86%) ⬇️ Carriedforward from 69bee15
frontend 43.08% <100.00%> (+2.00%) ⬆️
python 90.51% <ø> (-0.29%) ⬇️ Carriedforward from 69bee15
workflow-compiling-service 56.81% <ø> (ø) Carriedforward from 69bee15

*This pull request uses carry forward flags. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@PG1204
Copy link
Copy Markdown
Contributor Author

PG1204 commented May 22, 2026

Codecov flagged 0% patch because my tests are in workflow-editor.component.spec.ts, which is excluded from the unit-test runner in angular.json. I'm planning to move them into a new sibling file workflow-editor-status-restoration.spec.ts so the default glob picks them up and was verified locally that this brings patch coverage close to 100%. Happy to take a different approach if the team prefers, otherwise can push the new planned spec.ts file.

@Yicong-Huang
Copy link
Copy Markdown
Contributor

For UI/user facing changes, please include before and after screenshots in the PR description, under What changes were proposed in this PR? part. so that reviewer can easily understand the change. Thanks.

@Yicong-Huang
Copy link
Copy Markdown
Contributor

Also please add tests to cover your changes.

@PG1204
Copy link
Copy Markdown
Contributor Author

PG1204 commented May 22, 2026

@Yicong-Huang I have added a comment above regarding the test file & code coverage.

@Yicong-Huang
Copy link
Copy Markdown
Contributor

@Yicong-Huang I have added a comment above regarding the test file & code coverage.

just saw it, sorry. sg, you can place the tests else where for now, or simply add the tests in the same file and comment out the previous tests.

@PG1204
Copy link
Copy Markdown
Contributor Author

PG1204 commented May 22, 2026

@Yicong-Huang I have added a comment above regarding the test file & code coverage.

just saw it, sorry. sg, you can place the tests else where for now, or simply add the tests in the same file and comment out the previous tests.

No worries. Thanks for the suggestions.

@PG1204
Copy link
Copy Markdown
Contributor Author

PG1204 commented May 23, 2026

@Yicong-Huang I have added a comment above regarding the test file & code coverage.

just saw it, sorry. sg, you can place the tests else where for now, or simply add the tests in the same file and comment out the previous tests.

Quick clarification on Option 2, since workflow-editor.component.spec.ts is currently in angular.json's exclude list, commenting out the previous tests by itself wouldn't make the file run in CI (so Codecov would still report 0% on the new tests). Were you implicitly suggesting I also remove the exclusion as part of this, or would you prefer that change be left out of this PR?

@Yicong-Huang
Copy link
Copy Markdown
Contributor

you can

  • move out workflow-editor.component.spec.ts from exclusion list,
  • add your new tests there while keeping other tests still commented.

Alternatively you can use another PR to enable this workflow-editor.component.spec.ts and uncomment and fix the previous test cases first.

@PG1204
Copy link
Copy Markdown
Contributor Author

PG1204 commented May 24, 2026

you can

  • move out workflow-editor.component.spec.ts from exclusion list,
  • add your new tests there while keeping other tests still commented.

Alternatively you can use another PR to enable this workflow-editor.component.spec.ts and uncomment and fix the previous test cases first.

Should I wait for this PR to be reviewed and merged first before raising the follow-up PR to uncomment (and fix) the test cases in workflow-editor.component.spec.ts?

@PG1204
Copy link
Copy Markdown
Contributor Author

PG1204 commented May 26, 2026

you can

  • move out workflow-editor.component.spec.ts from exclusion list,
  • add your new tests there while keeping other tests still commented.

Alternatively you can use another PR to enable this workflow-editor.component.spec.ts and uncomment and fix the previous test cases first.

Should I wait for this PR to be reviewed and merged first before raising the follow-up PR to uncomment (and fix) the test cases in workflow-editor.component.spec.ts?

/request-review @Yicong-Huang

I shall raise the follow-up PR as soon as this is merged. Thanks.

@chenlica chenlica requested a review from Xiao-zhen-Liu May 27, 2026 23:37
@chenlica
Copy link
Copy Markdown
Contributor

I added @Xiao-zhen-Liu to be a reviewer, assuming @Yicong-Huang may not be very available.

My preference is that if the test cases are not related to this functionality of this PR, it should be fixed in a separate PR.

@Yicong-Huang
Copy link
Copy Markdown
Contributor

as we are low on coverage, I would encourage to add test cases to related files when fit, even it is not fully related to the changed line. Otherwise we won't know if the change in this PR would break some related parts (because we don't have tests for them).

After we have reached to a good ratio of coverage, then we can make it strict that only introduce or change tests related to the changed lines.

Copy link
Copy Markdown
Contributor

@Xiao-zhen-Liu Xiao-zhen-Liu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for tackling #3614 — the writeup of what was going wrong is excellent, and the actual fix (the two changes in workflow-editor.component.ts) is small, focused, and fixes a real problem. Two things worth addressing, both about how the border color is decided and how the new tests check it.

How the border color gets decided

  • The underlying problem is that two separate parts of the code both set the operator's border color, and they were overwriting each other. This fix makes them agree in the normal case (a valid operator with a saved "completed" status), which is correct. But they are still two separate parts setting the same color, with the same look-up-and-repaint logic duplicated in both, and one tricky case remains: an operator that is both invalid AND has a saved "completed" status. Red (invalid) is supposed to win, but whether it actually does depends on which of the two code paths runs last — and nothing guarantees that order. It works today, but it's fragile. Having a single place decide the final color (check validity first, then saved status, then fall back to the default) would make this reliable and remove the duplicated logic that now lives in both places.

The new tests

  • The new tests check "was a particular paint function called?" rather than "what color did the operator actually end up?". Because the same paint function is also called from the other new code path, one of the tests would pass even if the fix were removed, and another doesn't confirm the final color actually wins. Checking the operator's final border color directly would make the tests genuinely verify the behavior. See the inline notes on the two tests.

// the workflow page, where WorkspaceComponent calls reloadWorkflow and
// operators are recreated from the workflow JSON — restore their visual
// state from the cached status so completed runs don't appear to reset.
this.workflowActionService
Copy link
Copy Markdown
Contributor

@Xiao-zhen-Liu Xiao-zhen-Liu May 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new block looks up the saved status and repaints the operator's border color. The validation code further down (the other change in this PR) now does the same look-up-and-repaint. So the same logic lives in two places, which is easy to break later: someone updates one copy and forgets the other.

Stepping back: the original bug is that two separate parts of the code both set the border color and end up overwriting each other. This fix makes them agree in the normal case, but they're still two separate things setting the same color. It would be cleaner to have one function decide the final border color (taking into account both whether the operator is valid and what its run status is) and call that from a single place.

}
const statistics = this.workflowStatusService.getCurrentStatus()[value.operatorID];
if (statistics) {
this.jointUIService.changeOperatorState(this.paper, value.operatorID, statistics.operatorState);
Copy link
Copy Markdown
Contributor

@Xiao-zhen-Liu Xiao-zhen-Liu May 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edge case worth thinking about: an operator that is both invalid AND has a saved "completed" status. Red (invalid) is supposed to win over green here. But whether it actually does depends on which of the two color-setting code paths happens to run last, and nothing in the code guarantees that order: they are triggered through two different chains of events. It happens to work today, but if that order ever changed, an invalid operator could wrongly show up green instead of red.

Having one place decide the final color (validation first, then saved status, then default) would remove this dependence on timing.

workflowStatusService = TestBed.inject(WorkflowStatusService);
});

it("repaints execution-state stroke for valid operators with a cached status", () => {
Copy link
Copy Markdown
Contributor

@Xiao-zhen-Liu Xiao-zhen-Liu May 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is meant to verify the validation fix, but it only checks "was the green-painting function called?". That same function also gets called by the other new block (the operator-add code above), so this test would still pass even if the validation fix were removed entirely. In other words, it does not actually test the change it is paired with.

Checking the operator's actual final border color would test the real behavior.

expect(changeOperatorColorSpy).toHaveBeenCalledWith(component.paper, mockScanPredicate.operatorID, true);
});

it("paints the invalid stroke (red) for invalid operators regardless of cached status", () => {
Copy link
Copy Markdown
Contributor

@Xiao-zhen-Liu Xiao-zhen-Liu May 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This checks "was the red-painting function called?", but calling it is not enough on its own: red also has to be the color that actually ends up on the operator (see my note on the validation handler about the ordering). The test does not confirm the operator ends up red, so it would not catch the timing problem described there.

Checking the final border color would make this test reliable.

Copy link
Copy Markdown
Contributor

@Xiao-zhen-Liu Xiao-zhen-Liu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes — I think two of the points raised are worth addressing before merge rather than leaving as follow-ups:

  1. Decide the operator's border color in one place. Right now two separate parts of the code set it, with the same look-up-and-repaint logic duplicated in both. Consolidating it into a single decision (check validity first, then saved status, then the default) removes the duplication and also fixes the edge case where an operator that is both invalid and has a saved "completed" status can show the wrong color depending on which code path happens to run last.

  2. Make the two new tests check the operator's final border color rather than just whether a paint function was called. As written, one of them would pass even if the fix were removed, so the regression tests don't yet guard against the regression.

Details are in the inline comments. The behavior fix itself is correct and close — these are about making it robust and properly tested.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix frontend Changes related to the frontend GUI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Operator Status Color Resets After Page Navigation

5 participants