Repository navigation
fix(workflow-form): give the Form View a code editor host, so its step panel's code button opens the editor - #9023
yangzhang75 wants to merge 1 commit into
Conversation
3d3d280 to
facd072
Compare
Backport auto-label reportThis
|
Automated Reviewer SuggestionsBased on the
|
facd072 to
d1b230a
Compare
…p panel's code button opens the editor The code editor renders into an outlet the operator canvas published once (`<ng-template #codeEditor>` in WorkspaceComponent, handed to CodeEditorService as `vc`) and never let go of. The Form View has no outlet of its own, yet its step panel shows the operator's real property frame and, in edit mode, the ordinary "Edit code content" button. Pressing it opened the editor into whatever `vc` held: nothing, from a direct open of the Form View, or the destroyed canvas's outlet after the routed switch between the views (apache#8581), where Monaco rendered into a detached view and nothing appeared on screen. Verified on main: the button is there, the click does nothing, no error. Each view that shows operator properties now carries its own outlet, marked `texeraCodeEditorHost` on an `<ng-template>`: the directive attaches the outlet to CodeEditorService when the view's template is built and detaches it -- only its own -- when the view is destroyed, which also closes an editor still open in it. The views are sibling routes, so there is one host at a time. The button opens into whichever host is up and guards the case of none (every view that mounts a codearea hosts the editor now, so that is a guard rather than a state the button shows): a co-editor's open reaching a codearea with no host neither throws nor marks that operator's editor open, and reaches only the codearea on that operator. The editor is told its operator by its opener instead of reading the highlight in its constructor, and may be dragged within the Form View's page as well as the canvas. Deliberately not in this change: exposing a code property as a form input. The form's rules keep the code editor out of what an author may expose (NON_FORM_FIELD_TYPES) and fall back from it on an old workflow that carries one (CANVAS_ONLY_FORMLY_TYPES); editing code is not filling in a value, and a form reader should not be writing code. The step panel is the author's own editing surface, which is what this fixes. Closes apache#8760 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FVvP3ttj22f9LB4p9u2anY
d1b230a to
1968778
Compare
|
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
6 open issues
HostingViewComponentuses animportsarray but is not markedstandalone: true, and… · NewHostingViewComponentuses animportsarray but is not markedstandalone: true, and… · NewngOnInitassumescurrentOperatorIdis always set and thatgetOperator(...)returns a valid… · NewngOnInitassumescurrentOperatorIdis always set and thatgetOperator(...)returns a valid… · New This introduces a new::ng-deepusage, which is deprecated and can be brittle across Angular… · New Attaching the host in the constructor introduces a side effect during DI/construction rather than… · New
What changed in this PR
Fixes Form View’s inability to open the Monaco code editor by introducing a view-scoped “code editor host” outlet that is attached/detached with the active route.
Changes:
- Replace the single, long-lived
ViewContainerRefoutlet with an attachable/detachable host API inCodeEditorService. - Add
texeraCodeEditorHostvia a new directive and mount it in both Workspace (canvas) and Workflow Form views. - Update code editor opening/targeting behavior (operator passed via input) and adjust read-only/inspection UI behavior.
| File | Description |
|---|---|
| frontend/src/app/workspace/service/code-editor/code-editor.service.ts | Replaces global vc with attach/detach host outlet tracking. |
| frontend/src/app/workspace/service/code-editor/code-editor.service.spec.ts | Adds unit tests for host attach/detach semantics. |
| frontend/src/app/workspace/component/workspace.component.ts | Removes old @ViewChild outlet wiring; registers host directive instead. |
| frontend/src/app/workspace/component/workspace.component.spec.ts | Updates workspace tests to validate attach/detach interactions. |
| frontend/src/app/workspace/component/workspace.component.html | Switches canvas outlet to <ng-template texeraCodeEditorHost>. |
| frontend/src/app/workspace/component/workflow-form/workflow-form.spec-harness.ts | Extends test harness stub for form workflows. |
| frontend/src/app/workspace/component/workflow-form/workflow-form.rendered.spec.ts | Adds rendered test asserting host lifecycle in Form View. |
| frontend/src/app/workspace/component/workflow-form/workflow-form.component.ts | Imports the host directive for Form View. |
| frontend/src/app/workspace/component/workflow-form/workflow-form.component.scss | Raises editor z-index for Form View via deep selector override. |
| frontend/src/app/workspace/component/workflow-form/workflow-form.component.html | Adds Form View editor host outlet template. |
| frontend/src/app/workspace/component/property-editor/operator-property-edit-frame/operator-property-edit-frame.component.ts | Marks codearea fields as “inspect” when frame isn’t acting as editor. |
| frontend/src/app/workspace/component/property-editor/operator-property-edit-frame/operator-property-edit-frame.component.spec.ts | Tests inspect mark behavior for code fields. |
| frontend/src/app/workspace/component/codearea-custom-template/codearea-custom-template.component.ts | Opens editor into current host, passes operator id via input, refines coeditor logic. |
| frontend/src/app/workspace/component/codearea-custom-template/codearea-custom-template.component.spec.ts | Adds coverage for button label states, host guard behavior, and operator-targeted opening. |
| frontend/src/app/workspace/component/codearea-custom-template/codearea-custom-template.component.html | Updates label/readonly styling logic to respect inspection mode. |
| frontend/src/app/workspace/component/code-editor-dialog/code-editor.component.ts | Makes operator id an @Input, moves init logic to ngOnInit, and disables AI actions in read-only. |
| frontend/src/app/workspace/component/code-editor-dialog/code-editor.component.spec.ts | Updates construction to set currentOperatorId input; adds tests for new semantics and read-only AI actions. |
| frontend/src/app/workspace/component/code-editor-dialog/code-editor.component.html | Expands drag boundary to work in both Workspace and Workflow Form roots. |
| frontend/src/app/workspace/component/code-editor-dialog/code-editor.component.browser.spec.ts | Aligns browser tests with input-driven operator selection and shared-text staging. |
| frontend/src/app/workspace/component/code-editor-dialog/code-editor-host.directive.ts | Introduces directive that attaches/detaches the active view’s editor outlet. |
| frontend/src/app/workspace/component/code-editor-dialog/code-editor-host.directive.spec.ts | Adds directive lifecycle tests for attachment/detachment/teardown behavior. |
Review effort: Lite
| @Component({ | ||
| template: "<ng-template texeraCodeEditorHost></ng-template>", | ||
| imports: [CodeEditorHostDirective], | ||
| }) | ||
| class HostingViewComponent {} | ||
|
|
||
| /** Stands in for the editor: what the host holds, and what must be torn down with the view. */ | ||
| @Component({ template: "editor" }) | ||
| class HostedComponent implements OnDestroy { | ||
| static destroyed = 0; | ||
| ngOnDestroy(): void { | ||
| HostedComponent.destroyed++; | ||
| } | ||
| } |
|
|
||
| beforeEach(async () => { | ||
| HostedComponent.destroyed = 0; | ||
| await TestBed.configureTestingModule({ imports: [HostingViewComponent, HostedComponent] }).compileComponents(); |
| * The operator whose code this edits, given by the opener: the operator property panel's button | ||
| * reads the highlighted operator, the Form View's the operator its field is bound to. | ||
| */ | ||
| @Input() currentOperatorId!: string; |
| ngOnInit(): void { | ||
| // Everything here keys on currentOperatorId, an input set after construction. | ||
| const operatorType = this.workflowActionService.getTexeraGraph().getOperator(this.currentOperatorId).operatorType; |
| :host ::ng-deep #code-editor-container.box { | ||
| z-index: 21; | ||
| } |
| export class CodeEditorHostDirective implements OnDestroy { | ||
| constructor( | ||
| private readonly outlet: ViewContainerRef, | ||
| private readonly codeEditorService: CodeEditorService | ||
| ) { | ||
| this.codeEditorService.attachHost(this.outlet); | ||
| } |



What changes were proposed in this PR?
Closes #8760. Part of the Form View feature (#8011); from the #8439 refinements (item 1).
The code editor renders into an outlet the operator canvas published once (
<ng-template #codeEditor>inWorkspaceComponent, handed toCodeEditorServiceasvc) and never let go of. The Form View has no outlet of its own, yet its step panel shows the operator's real property frame and, in edit mode, the ordinary Edit code content button. Pressing it opened the editor into whatevervcheld: nothing, from a direct open of the Form View, or the destroyed canvas's outlet after the routed switch between the views (#8581), where Monaco rendered into a detached view and nothing appeared. Verified on main before changing anything: the button is there and enabled, the click does nothing, and no error is raised.Each view that shows operator properties now carries its own outlet, marked
texeraCodeEditorHoston an<ng-template>in its template:CodeEditorHostDirectiveattaches the outlet toCodeEditorServicewhen the view's template is built and detaches it -- only its own, so a host that has since replaced it stays -- when the view is destroyed, which also closes an editor still open in it (the view's teardown destroys what its outlet holds before the hook runs). The views are sibling routes and never overlap, so there is one host at a time.@ViewChild+vchand-over; the Form View gets the same one line. The service exposeshost,attachHostanddetachHostin place of the publicvc.currentOperatorIdis an input) instead of reading the highlight in its constructor, and may be dragged within the Form View's page as well as the canvas (cdkDragBoundary). The Form View lifts the editor above its sticky title row.inert, so nothing in it can be pressed, the code button included. That button used to read "View code content" there, as it does on the canvas for a workflow one cannot write, where it still opens a read-only editor; here it could not open anything, so it offered a view it could not deliver. The frame now marks the code field of such a mount (props.inspect), and the button keeps its usual face -- "Edit code content", greyed out with the rest of the panel -- which is also what the canvas shows for the same step. The canvas's read-only "View code content", which does work, is unchanged.Deliberately not in this change: exposing a code property as a form input. The form's rules keep the code editor out of what an author may expose (
NON_FORM_FIELD_TYPES) and fall back from it on an old workflow that carries one (CANVAS_ONLY_FORMLY_TYPES) -- editing code is not filling in a value, and a form reader should not be writing code. The step panel is the author's own editing surface, and that is what this fixes.Any related issues, documentation, discussions?
Closes #8760. Part of #8439 (item 1, raised by @mengw15 on #8436) and the Form View feature #8011. The routed switch that left the canvas's outlet destroyed under the Form View is #8581.
How was this PR tested?
Unit tests (vitest). The directive's spec: attaches its outlet on construction, closes what it hosts and lets go of the host on destroy, and does not let go of a host that has since replaced it. The service's spec:
host/attachHost/detachHost, including that detaching a stale host leaves the current one. The code button's spec: opens into the host and tells the editor its operator, which is the highlighted one; sets only inputs the editor declares; stays closed when a co-editor opens another operator's editor; and, without a host, opens nothing and marks nothing open on a click, and stays closed instead of throwing when a co-editor opens theirs. The editor's spec: takes its operator as an input and still highlights it on focus; a read-only control gets no context-menu actions. The button's label: "Edit code content" while editable; "View code content" while read-only and still pressable (the canvas); "Edit code content", greyed, on a read-only inspection that cannot press it; and the frame's spec pins that the inspection mark is set exactly when the frame is not acting as the editor. The Form View's rendered spec: hosts the editor while the page is up and lets go of the host when it is destroyed; the workspace's spec: the same through the directive instead of the old@ViewChild.By hand, on two dev builds of the same base commit, one with this change and one without, as the workflow's owner in the Form View's edit mode with the workflow strip open and the Python UDF step selected: on main, Edit code content is enabled and the click opens nothing. On this branch, the click opens the editor as a floating box titled with the step's name, showing its code, over the form page (a Monaco instance in the page, inside the Form View's own subtree), and the button reads as open while it is.
Each guard was deletion-checked and turned a named test red: the form's outlet, the detach on destroy, and the button's no-host guard (that one fails to compile without it). eslint, prettier and the production (AOT) build pass; every statement, function and branch this PR adds is covered; the full frontend suite passes.
Video
Before
Screen.Recording.2026-10-19.at.12.16.50.AM.mov
After
Screen.Recording.2026-10-19.at.12.17.10.AM.mov
Was this PR authored or co-authored using generative AI tooling?
Yes. Generated-by: Claude Code (Anthropic). Co-authored with Claude, reviewed line by line by the author before submission.
🤖 Generated with Claude Code
https://claude.ai/code/session_01FVvP3ttj22f9LB4p9u2anY