The GitHub Actions job "Required Checks" on texera.git/gh-readonly-queue/main/pr-8767-d69d740f2492cddf5826ac4279b328185c245455 has succeeded. Run started by GitHub user mengw15 (triggered by mengw15).
Head commit for run: e2bd0cdae31af7a1f05f150ce24da7214c4a429b / Ryan Zhang <[email protected]> feat(notebook-migration): support migrating local Python folders into workflows (#8767) ### What changes were proposed in this PR? The migration tool accepted a Jupyter notebook and a single Python file. It now accepts a folder of Python files selected from disk, and produces one workflow from all of them, with the same stored source and operator mapping that drive highlighting in the Jupyter panel. A folder is harder than a script for the same reason a script was harder than a notebook: the mapping keys on cells, and there are none. This PR does not introduce a second mechanism for that. The folder's Python files are concatenated into one document, each behind a banner line naming the file it came from, with line numbers running continuously across the whole thing. The model then reports line ranges exactly as it does for a single script, and #8007's segmenter derives the cells. The mapping stays keyed on cell uuids, so storage, the Jupyter panel and the highlight index never learn that folders exist. No backend, database, Helm, or dependency changes. The alternative was to number each file separately and key the mapping on file plus line range. That changes the stored mapping shape, which pulls in the backend, the database and the panel, to buy provenance the banner line already provides. Main pieces: * `folder-assembly.ts`, a new pure module: which files are in scope, the order they are joined in, the assembled document, the layout, the caps, and entry point resolution. It reuses the segmenter's own line splitting, so assembling, numbering the prompt and slicing the cells all agree on what a line is. * `segmentScript` gains one optional argument, a list of caller-required cut points. Each file's banner joins the boundary set, so no cell holds lines from two files. Every invariant the module already documented holds unchanged. * An indented layout of the folder, sent as prompt text ahead of the numbered document. It is deliberately not part of that document: numbering it would shift every line the model reports, and the segmenter would emit it as a cell of directory listing. It also names the folder's non-Python files without reading them, so the model knows a dataset or a requirements list exists. * Folder variants of the conversion prompt, the mapping prompt, and the worked example. `migration-prompts.ts` is 232 insertions and 10 deletions. All 232 insertions are the new folder constants. The 10 deletions are apostrophe fixes raised in review (`ID's` to `IDs`, `key's` to `keys`, `UDF's` to `UDFs` where it is a plural), applied uniformly to the notebook, script and folder prompts so the three do not drift apart on wording. No other notebook or script prompt text changed. The derived notebook holds the entry point alone, not every file. A folder's other files are function definitions, and a notebook of all of them reads as a wall of code rather than the story of what the project does. The model names the entry point in the conversion reply, so this costs no extra round trip, and the mapping it reports is expressed in that file's lines. Clicking an operator highlights the call that runs it. When the entry point cannot be resolved the notebook falls back to the whole folder, which is worse to read but never empty. Two caps refuse an oversized folder before any request is sent: 100 files, and 60,000 characters of code. The binding constraint is the reply rather than the prompt, because the conversion asks the model to return every line of code as JSON-escaped strings and no output budget is set. A cap the model cannot meet would defeat the point of refusing up front, since the user would wait out a full conversion and get a JSON parse error from a truncated reply. A byte check runs before anything is read, so one enormous generated file cannot be loaded into memory first. Dropping a folder onto the upload button is refused with a message. ng-zorro binds a drop handler on the button whatever its type, and in directory mode it hands over one file at a time with no path attached, so accepting a drop would stage a single arbitrary file and convert it as if it were the whole project. Three things ride along: * The three import modal diagrams are normalized to one size. They were the same layout exported at three resolutions with uneven whitespace, so the tabs rendered at slightly different scales. Trimming each to its content and scaling to a common canvas touches two images from earlier PRs. * `LICENSE-binary` credits `snippet_folder` for the new diagram, and adds `python_file_diagram.png` to the Material Symbols paths. That second entry fills an attribution gap from #8007, where the same icons were reused without extending the path list. * A final commit consolidates the folder caps and the `webkitRelativePath` parsing, which had spread across two files while the feature was built. #### Demo video https://drive.google.com/file/d/1KtI2FVWgOwoGCFZfnIWXLGurBcXQmmkz/view?usp=sharing ### Any related issues, documentation, discussions? Closes #8008 Parent-issue #4301 The reasoning for keeping the mapping and Jupyter panel on an input without cells was settled for scripts in https://github.com/apache/texera/discussions/8489 and applies to folders unchanged. The output budget argument behind the character cap applies just as much to a single `.py` and to a notebook, neither of which has a size cap today, and joining a project into one script is an easy way around the folder cap. That is worth its own issue rather than widening this one. ### How was this PR tested? Unit tests: * `folder-assembly.spec.ts`, new, 59 tests. File selection, ordering, assembly, the layout, the caps, entry point resolution and scoping. Two cases are worth naming. Directories sort by segment, so a sibling whose name sorts between a directory and its subdirectory (`src`, `src-old`, `src/utils`) cannot be rendered as the wrong parent. And an ambiguous entry point name resolves to null rather than to whichever file sorts first, because showing the wrong file is worse than falling back to the whole folder. * `script-segmentation.spec.ts`, 32 to 36. Forced cut points, including that out of range and non-integer boundaries are dropped rather than clamped. * `migration-llm.spec.ts`, 42 to 57. The layout is sent ahead of the code and never numbered, cuts land on every file banner, a range straddling a seam still maps to the cells on both sides, and the entry point scopes the notebook with a fallback when it cannot be resolved. * `notebook-migration.service.spec.ts`, 48 to 65. Reading a selection, the layout it produces, and each cap, including that the file count and byte checks run before any file is read. * `notebook-import-modal.component.spec.ts`, 27 to 39. The third tab, the directory picker, and the drop path modelled the way a real drop arrives: one file per call, each in its own one-element list, with no path. * `user-workflow.component.spec.ts`, 81 to 87. Dispatch on selection shape, and that a folder names its workflow after itself. Manually tested end to end against a live model with a small multi-file project (a package with an entry point, two modules and a helpers-only module, plus non-Python files and a `__pycache__`). ### Was this PR authored or co-authored using generative AI tooling? Generated-by: Claude Code (Claude Opus 5) Report URL: https://github.com/apache/texera/actions/runs/37386762567 With regards, GitHub Actions via GitBox
