aglinxinyuan opened a new pull request, #8640:
URL: https://github.com/apache/texera/pull/8640

   ### What changes were proposed in this PR?
   
   Inside a control block (`Loop Start ... Loop End`), any primitive property 
of an operator (string, integer, number, boolean) can now hold a loop-variable 
reference such as `$K`. The frontend sends the literal string `"$K"` unchanged. 
The backend marks it as a placeholder when it compiles the plan and binds it 
when the iteration's state message reaches the worker (#8639). Operators 
outside every block behave as before.
   
   ```
   Loop Start (K = 2) -> Limit(limit: "$K") -> Loop End
                           ^ panel offers $K; canvas valid; sent as "$K"
   rename K to i in the Loop Start  ->  Limit flagged "$K is not a variable of 
an enclosing block"
   delete a link upstream           ->  Limit outside the block -> "must be 
integer"
   ```
   
   | Piece | What it does |
   |---|---|
   | Block membership (`getEnclosingLoopStarts`, on `WorkflowGraph`) | Finds 
the Loop Starts whose block encloses an operator, outermost first. It walks 
downstream to an unmatched Loop End and upstream to an unmatched Loop Start, 
counting open blocks, so blocks in a row do not leak into each other and nested 
blocks give several starts. The open count is capped, so the walk ends even on 
a cycle through a control operator. |
   | Loop variables (`extractLoopVariables`, `loopVariablesInScope`) | Reads 
the variables from each enclosing Loop Start's `initialization` code, following 
Python's module-level binding: assignments at any depth outside a `def`/`class` 
body, for-loop targets, and one-line compound bodies such as `try: k = 1`. |
   | Property panel | Inside a block, each plain primitive field becomes a text 
box that offers `$name` for every variable in scope. Enums, arrays and custom 
widgets keep their own controls. A `$name` that no enclosing block declares is 
flagged. |
   | Canvas validation | Inside a block, the schema `type` error is waived 
exactly where a reference sits in a primitive-typed property. Every reference 
must name a declared variable. Adding or removing any link, or editing any Loop 
Start, re-validates every operator that holds a reference, so the Run button 
follows edits two hops away. |
   | Open panel | Rebuilt on those same edits when the operator's scope 
changed. |
   
   Panel behavior for a field inside a block:
   
   | Field type | Typed text | Stored value |
   |---|---|---|
   | integer | `7` | `7` (number) |
   | integer / number / boolean | `$K` | `"$K"` |
   | number | `0.5`, typed one key at a time | `0.5`; partial text such as `7.` 
stays text until complete |
   | boolean | `true` / `FALSE` | `true` / `false`; other text is flagged |
   | string | `hello` / `$K` | `"hello"` / `"$K"` |
   | any | `$foo` (undeclared) | `"$foo"`, flagged; operator marked invalid |
   
   **Merge order.** This PR depends on #8639 and must merge after it. Until 
then, a `"$K"` in a non-string property fails deserialization when the workflow 
is compiled.
   
   **Scope limits and follow-ups**
   - Operators whose code is generated before the loop runs (Python UDFs and 
`PythonOperatorDescriptor` subclasses such as the sklearn operators) accept 
`$K` in the panel. The backend reports a compile error for them, since nothing 
can bind their values yet.
   - An operator with an input fed from outside the block (for example a join 
whose other side comes from outside) is inside the block for the panel, so the 
panel offers `$K`. The backend reports a compile error for it, because that 
input's tuples could arrive before the loop state.
   - The Form View builds its fields with its own formly mapping and does not 
offer `$K` yet.
   - Inside a block, a boolean property renders as a text box (`true` / `false` 
/ `$name`) rather than a checkbox.
   - Inside a block, a whole-string `$name` in a string property is always a 
reference. There is no escape for a literal `$name` there yet.
   
   **Screenshots.** Before/after screenshots of the property panel inside a 
block are still to be attached: the workspace requires signing in, which was 
not possible from the authoring environment.
   
   ### Any related issues, documentation, discussions?
   
   Closes #8636
   
   ### How was this PR tested?
   
   New and updated Vitest specs, run from `frontend/`:
   
   | Spec | Covers |
   |---|---|
   | `loop-block.util.spec.ts` | Straight, branching, nested, sequential and 
incomplete blocks; a cycle in the body; three cycles through a control 
operator, which exhausted the heap before the cap. |
   | `loop-variable.util.spec.ts` | Assignment forms; if/else, try/except, 
while and for bodies; for targets; one-line compound bodies; def/class bodies 
and with/import/except-as names excluded; Python keywords never taken for 
names. |
   | `loop-variable-field.util.spec.ts` | Complete vs partial numerals (`7.`, 
`-0.`, `1e-` kept as text); the parser sets the control without redrawing the 
box; the string `""` default; the boolean validator rejects `maybe`. |
   | `loop-variable-input.component.spec.ts` (new) | Rendered through a real 
formly form: typing `1.05`, `-0.5` and `7.5` one key at a time stores the right 
number and keeps the box as typed; `$K` and integers; formly attributes reach 
the input. |
   | `validation-workflow.service.spec.ts` | The waiver applies only to 
primitive types. Canvas state follows graph edits: a Loop Start variable 
renamed and restored, an upstream link deleted, a block closed by a link 
between two other operators, an operator inserted into the body. |
   | `operator-property-edit-frame.component.spec.ts` | Primitive fields 
converted inside a block; file picker, enum and array keep their controls; the 
form rebuilds when a link or the Loop Start's variables change its scope, and 
only then. |
   
   Each new behavior spec was checked red against the pre-fix code before the 
final run.
   
   ```
   yarn ng test --watch=false --include=<the 6 loop specs>                 # 
185 passed
   yarn ng test --watch=false                                              # 
233 files, 6300 passed, 1 skipped (existing it.skip)
   yarn exec tsc --noEmit -p tsconfig.json   (and src/tsconfig.spec.json)  # 
clean
   yarn exec ngc -p src/tsconfig.app.json --noEmit                         # 
AOT + strictTemplates clean
   yarn format:fix && yarn eslint <changed files>                          # 
clean
   ```
   
   `yarn ng build` did not complete locally: the worktree's `node_modules` is a 
symlink, so the custom webpack rules for monaco assets never match. The 
template compile above is clean, and CI's frontend build covers the bundle.
   
   Manual steps for the reviewer (Chrome, desktop): link `Loop Start -> Limit 
-> Loop End` and set the Loop Start initialization to `K = 2`. Open `Limit`: 
its integer field is a text box, typing `$` offers `$K`, and choosing it keeps 
the operator valid. Typing `$foo` shows "$foo is not a variable of an enclosing 
block". Delete a link so `Limit` leaves the block: the field is a number box 
again and `$K` is a type error.
   
   ### Was this PR authored or co-authored using generative AI tooling?
   
   Generated-by: Claude Code (Claude Fable 5.1, Claude Opus 5.5)
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)
   


-- 
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]

Reply via email to