This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-5285-8a4cb29e1045f18cbfaf5f143473e0f05b25d4ea in repository https://gitbox.apache.org/repos/asf/texera.git
commit 4f73e952555aa7ae4d146c7a08ef0381e873d18b Author: Kunwoo (Chris) <[email protected]> AuthorDate: Fri Jun 5 16:09:34 2026 -0700 fix: clear red change-line brackets when restoring a workflow version (#5285) ### What changes were proposed in this PR? When restoring a previous workflow version that displays a **red change line** (the bracket highlight drawn around the neighbors of deleted operators), the red line stayed visible after the version was restored. Versions whose diff only contained green/orange operator highlights cleared correctly. Why it's a bug: `highlightOpVersionDiff()` applies three kinds of highlight when previewing a version: - orange boundary **fill** on modified operators - green boundary **fill** on added operators - **red bracket strokes** (`path.left-boundary/stroke` / `path.right-boundary/stroke`) on the neighbors of *deleted* operators `unhighlightOpVersionDiff()` only reset the boundary fills — it never reset the bracket strokes. `closeParticularVersionDisplay()` masked the issue because it calls `reloadWorkflow()`, which rebuilds the JointJS paper elements and incidentally wipes the leftover brackets. `revertToVersion()` does **not** reload the paper, so the orphaned red brackets persisted after restore. This PR makes `unhighlightOpVersionDiff()` symmetric with `highlightOpVersionDiff()`: it now also resets the deleted-operators' neighbor brackets back to the default transparent `rgba(0,0,0,0)`, so the red change line is cleared on restore. ### Any related issues, documentation, discussions? Fixes #3043 Fixes #3828 ### How was this PR tested? - Manually verified in the UI that the red change line clears after restoring a version. https://github.com/user-attachments/assets/6417c52c-4f6e-47eb-82d4-0ac95f55ac5f ### Was this PR authored or co-authored using generative AI tooling? Generated-by: Claude Code (Claude Opus 4.7) --- .../workflow-version.service.spec.ts | 35 ++++++++++++++++++++++ .../workflow-version/workflow-version.service.ts | 14 +++++++++ 2 files changed, 49 insertions(+) diff --git a/frontend/src/app/dashboard/service/user/workflow-version/workflow-version.service.spec.ts b/frontend/src/app/dashboard/service/user/workflow-version/workflow-version.service.spec.ts index 85c9016500..659af0feb2 100644 --- a/frontend/src/app/dashboard/service/user/workflow-version/workflow-version.service.spec.ts +++ b/frontend/src/app/dashboard/service/user/workflow-version/workflow-version.service.spec.ts @@ -310,6 +310,41 @@ describe("WorkflowVersionService", () => { }); }); + // ─── unhighlightOpVersionDiff ───────────────────────────────────────────── + + describe("unhighlightOpVersionDiff", () => { + it("resets the boundary fill of added and modified ops to transparent", () => { + service.unhighlightOpVersionDiff({ modified: ["m"], added: ["a"], deleted: [] }); + + expect(paperGetModelById).toHaveBeenCalledWith("m"); + expect(paperGetModelById).toHaveBeenCalledWith("a"); + expect(modelAttr).toHaveBeenCalledWith("rect.boundary/fill", "rgba(0,0,0,0)"); + }); + + it("resets the red brackets drawn around the neighbors of deleted ops", () => { + const tempWorkflow = buildWorkflow({ + content: buildContent({ + operators: [buildOperator({ operatorID: "alive-left" }), buildOperator({ operatorID: "alive-right" })], + links: [buildLink("dead", "alive-right"), buildLink("alive-left", "dead")], + }), + }); + actionSpy.getTempWorkflow.mockReturnValue(tempWorkflow); + + service.unhighlightOpVersionDiff({ modified: [], added: [], deleted: ["dead"] }); + + expect(modelAttr).toHaveBeenCalledWith("path.left-boundary/stroke", "rgba(0,0,0,0)"); + expect(modelAttr).toHaveBeenCalledWith("path.right-boundary/stroke", "rgba(0,0,0,0)"); + }); + + it("skips bracket clearing when the temp workflow is missing", () => { + actionSpy.getTempWorkflow.mockReturnValue(undefined); + + service.unhighlightOpVersionDiff({ modified: [], added: [], deleted: ["dead"] }); + + expect(getMainJointPaper).not.toHaveBeenCalled(); + }); + }); + // ─── getWorkflowsDifference / getOperatorsDifference ────────────────────── describe("getWorkflowsDifference", () => { diff --git a/frontend/src/app/dashboard/service/user/workflow-version/workflow-version.service.ts b/frontend/src/app/dashboard/service/user/workflow-version/workflow-version.service.ts index 0c31b4edf1..1b527e35cb 100644 --- a/frontend/src/app/dashboard/service/user/workflow-version/workflow-version.service.ts +++ b/frontend/src/app/dashboard/service/user/workflow-version/workflow-version.service.ts @@ -271,6 +271,20 @@ export class WorkflowVersionService { for (const id of differentOpIDsList.added.concat(differentOpIDsList.modified)) { this.highlightOpBoundary(id, "0,0,0,0"); } + + if (differentOpIDsList.deleted.length > 0) { + const tempWorkflow = this.workflowActionService.getTempWorkflow(); + if (tempWorkflow != undefined) { + for (const link of tempWorkflow.content.links) { + if (differentOpIDsList.deleted.includes(link.source.operatorID) && link.target.operatorID != undefined) { + this.highlightOpBracket(link.target.operatorID, "0,0,0,0", "left-"); + } + if (differentOpIDsList.deleted.includes(link.target.operatorID) && link.source.operatorID != undefined) { + this.highlightOpBracket(link.source.operatorID, "0,0,0,0", "right-"); + } + } + } + } this.operatorPropertyDiff = {}; }
