PR #21935: Convert workflow extraction interface to Vue
Author: Ahmed Hamid Awan (@ahmedhamidawan) — implementation plan by @jmchilton
Repo: galaxyproject/galaxy
State: MERGED 2026-03-27 (merge ff944254b43c477e52455f71b099b65d5610c1d9)
Created: 2026-02-25
Labels: area/UI-UX, kind/feature, highlight, release-testing-26.1
Fixes Issue 17506 - Convert Workflow Extraction Interface to Vue — the parent issue (“the last non-data-display Mako in Galaxy”). Direct follow-up is PR 22706 - Workflow Extraction by IDs (OPEN/unmerged), which “builds on this cycle’s #21935”: #21935 is the foundational HID-based Vue conversion; #22706 is the later ID/ICJ-native endpoint. #22706’s POST /api/workflows/extract is not part of this PR.
Summary
Deletes the Mako-based legacy history→workflow extraction UI (templates/build_from_current_history.mako, −178) and the legacy WorkflowController.build_from_current_history web-controller handler (controllers/workflow.py, −58, plus its now-dead imports). Replaces them with two new FastAPI endpoints on FastAPIHistories — GET /api/histories/{history_id}/extraction_summary (returns a Pydantic-typed WorkflowExtractionSummary of jobs/inputs) and POST /api/histories/{history_id}/extract_workflow (consumes the HID-keyed WorkflowExtractionPayload, returns WorkflowExtractionResult) — feeding a new SPA route /histories/{history_id}/extract_workflow rendered by WorkflowExtractionForm.vue plus WorkflowExtractionCard.vue / WorkflowExtractionMessages.vue. Introduces a reusable Common/RenameModal.vue that replaces the deleted Workflow/List/WorkflowRename.vue, rewrites History/Content/GenericItem.vue from Options API to <script setup> with useIntersectionObserver lazy provider rendering, and removes the legacy navigation plugin. Also a one-line, body-unmentioned bug fix in workflow/extract.py: dataset_collection_names was hard-coded None, silently dropping caller-supplied collection input names. The payload being HID-keyed and the endpoint history-scoped makes this design inherently single-history — the limitation PR 22706 - Workflow Extraction by IDs and Workflow Extraction Multiple Histories later address.
Changes
Line numbers verified at origin/dev SHA 78fc4a33 (the merge is an ancestor; nothing from this PR is superseded in dev — see below).
Backend
lib/galaxy/schema/workflows.py(+142): five new Pydantic models —WorkflowExtractionOutput(line 307),WorkflowExtractionJob(line 340;step_type: Literal["tool","input_dataset","input_collection"],checked,tool_version_warning),WorkflowExtractionSummary(line 383;history_id,warnings,jobs),WorkflowExtractionPayload(line 401;job_ids: list[DecodedDatabaseIdField],dataset_hids: list[int],dataset_collection_hids: list[int], names — no model validator),WorkflowExtractionResult(line 434; new workflowid).lib/galaxy/webapps/galaxy/api/histories.py(+54): top-of-modulefrom galaxy.workflow.extract import extract_workflow;extraction_summary@router.get("/api/histories/{history_id}/extraction_summary")(def line 862) →service.create_workflow_extraction_summary;extract_workflow_from_history@router.post("/api/histories/{history_id}/extract_workflow")(def line 879) mapspayload.dataset_hids → extract_workflow(dataset_ids=...), returnsWorkflowExtractionResult.lib/galaxy/webapps/galaxy/services/histories.py(+103):HistoriesService.create_workflow_extraction_summary(history_id, trans)(def line 802) wrapssummarize(trans, history); classifies fake jobs (is_fake) as input steps (id=None), real jobs viatoolbox.get_tool, demotesnot tool.is_workflow_compatibleto input (line 852), setstool_version_warningon version mismatch,checked = any(not data.deleted ...).lib/galaxy/workflow/extract.py(+1/−1): insideextract_workflow(), the delegating call’sdataset_collection_names=None→dataset_collection_names=dataset_collection_names(dev line 74). A real bug fix — collection input names were being dropped; the PR body does not mention it.lib/galaxy/webapps/galaxy/buildapp.py(+1):add_client_route("/histories/{history_id}/extract_workflow")(dev line 276) so SPA deep links resolve.lib/galaxy/webapps/galaxy/controllers/workflow.py(−58): removes@web.expose build_from_current_history+ deadOptional/GalaxyWebTransaction/extract_workflow/summarizeimports.lib/galaxy/selenium/navigates_galaxy.py(−1): dropsswitch_to_main_panel()innavigate_to_workflow_extraction()(Vue page renders in the SPA router, not the legacy main-panel iframe).templates/build_from_current_history.mako(−178): the entire legacy server-rendered form, deleted.
New Vue extraction UI
client/src/components/History/WorkflowExtractionForm.vue(+329, new) — top-levelHistory/, not theWorkflowExtraction/subdir.<script setup lang="ts">, prophistoryId; computedsselectedJobIds(dev line 86),selectedInputs,submissionDisabled;submitWorkflow()(dev line 210) builds the HID payload (dataset_hidsat line 224) →submitWorkflowExtraction(historyId, payload). Selenium hooksdata-description="workflow-extraction-form",data-step-type,data-job-id.client/src/components/History/WorkflowExtraction/WorkflowExtractionCard.vue(+126, new):GCardwith badges fromSTEP_TYPE_META, “View Job” / “Different Tool Version” badges, renders oneGenericHistoryItemper output, emitsrename/select.client/src/components/History/WorkflowExtraction/WorkflowExtractionMessages.vue(+60, new): static guidance + warnings as a dismissibleBAlertthat demotes to aBPopover.client/src/components/History/WorkflowExtraction/types.ts(+13, new):WorkflowExtractionInput(job +newName);isWorkflowExtractionInput=step_type.startsWith("input").client/src/components/History/HistoryOptions.vue(+1/−3) andclient/src/entry/analysis/router.js(+6): rewire the “Extract Workflow” option to the new SPA routehistories/:historyId/extract_workflow.
Shared components
client/src/components/Common/RenameModal.vue(+85, new): generic rename modal — propsname,itemType,renameAction; wrapsGModal+GFormInput, success/error toasts. Replacesclient/src/components/Workflow/List/WorkflowRename.vue(−63, deleted);WorkflowCardList.vue(+4/−2) re-imports the shared one withrename-actioncallingupdateWorkflow.client/src/components/Common/GCard.vue(+14):dimWhenUnselectedprop (saturate/opacity dim).Common/GTable.vue(+8/−1):selectCheckboxTitleprop.BaseComponents/GModal.vue(+3):onMountedcallsshowModal()ifshowis already true.
Client API / selenium / plugins
client/src/api/histories.ts(+42): re-exports the four schema types;extractWorkflowFromHistory(dev line 309,GET .../extraction_summary) andsubmitWorkflowExtraction(dev line 323,POST .../extract_workflow).client/src/api/datasets.ts(+3/−2):fetchDatasetDetailsgains an optionalAbortSignalto cancel per-output fetches when cards unmount.client/src/api/schema/schema.ts(+279, autogenerated);client/src/utils/navigation/navigation.yml(+9/−17) +schema.ts(+12): swap Mako-era selectors for the Vue set (tool_card = [data-step-type="tool"],card_checkbox_by_job_id, etc.).client/src/components/plugins/index.js(−5) +client/src/components/plugins/legacyNavigation.js(−42, deleted): remove the legacy navigation mixin.client/src/components/History/Content/GenericItem.vue(+118/−129): Options→<script setup lang="ts">rewrite addinguseIntersectionObserverlazy provider rendering. Load-bearing — the extraction card list mounts aGenericHistoryItemper output, so deferred provider resolution is what keeps a many-job summary viable.JobParameters.test.ts(+13/−18) is collateral of this refactor (stub nowgenerichistoryitem-stubwithitem-id/item-src).
Changes since merge
git log ff944254..origin/dev per file: nothing from this PR is superseded in dev.
WorkflowExtractionForm.vue,WorkflowExtractionCard.vue,WorkflowExtractionMessages.vue,WorkflowExtraction/types.ts,Common/RenameModal.vue— zero post-merge commits, byte-identical to merge. (Confirms PR 22706 - Workflow Extraction by IDs, which rewrites all of these, is OPEN/unmerged and absent from dev — devnavigation.ymllines 1066–1076 are exactly the #21935 selector set, nomapped_*/card_by_icj_id.)extract.py— three follow-ups (b50938e3cfLDDA-leaf NoneType guard for #22359 inWorkflowSummary;345efca3e5mypy assert;cd4f4686a8release merge), none touchingextract_workflow()’s signature or thedataset_collection_namesfix.schema/workflows.py— unrelated invocation-param validation commits; the fiveWorkflowExtraction*models unchanged (lines 307/340/383/401/434).services/histories.py— 11 follow-ups (history-graph endpoint,.model_dump(), manager shims);create_workflow_extraction_summaryitself unchanged (moved to line 802 only by preceding insertions).api/histories.py— 4 follow-ups, neither extraction endpoint touched (dev lines 862/879).test_workflow_extraction.py—a7d122afd9gxformat2 0.26.0 fixture syntax bump, not the new summary test class.api/histories.ts—cacef72a70modernized an unrelated fn.
The HID-keyed WorkflowExtractionPayload and both endpoints still exist unchanged at dev. Per #22706’s body the legacy HID path is intentionally kept as the default for the history endpoint even after #22706 lands, so this PR’s contribution remains the production path.
File path migration
No migrations — all 31 non-deleted touched paths exist at their PR-era paths at dev SHA. Notably WorkflowExtractionForm.vue stays at top-level History/ (not the WorkflowExtraction/ subdir, which holds only Card/Messages/types). The 4 deletions (build_from_current_history.mako, Workflow/List/WorkflowRename.vue, plugins/legacyNavigation.js, the controllers/workflow.py method) are expected-absent, not migrations.
Cross-checks
- Mako deletion (−178), legacy controller handler deletion (−58) — confirmed absent at dev.
- Two distinct routes:
GET .../extraction_summaryandPOST .../extract_workflow— confirmed (api/histories.py:862/879). The PR body is correct; an earlier framing of this as a singleextract_workflowGET/POST route was inaccurate. WorkflowExtractionPayloadHID-keyed (dataset_hids/dataset_collection_hids: list[int]) — confirmed (schema lines 412/417).extract.py+1/−1 is a substantive bug fix (dataset_collection_names=None→ caller value), not cosmetic — body omits it (noted gap).RenameModal.vuereplacingWorkflowRename.vue,legacyNavigation.jsremoval,buildapp.pyroute (dev line 276) — all confirmed. No body↔diff contradictions.
Unresolved questions
- HID/single-history limitation is by design here (
dataset_hids: list[int],/api/histories/{id}/...) — no cross-history copied-dataset support. Deprecate the HID path once PR 22706 - Workflow Extraction by IDs lands, or keep as default indefinitely (its body says keep)? - No API test of
POST .../extract_workflowin the +90 (only the GET summary is API-tested); POST correctness rides on the Selenium rewrite + pre-existingextract_workflow/summarizecoverage — thin for mapped/implicit-collection steps. - Behavior change: the Vue form blocks empty-step submission (create disabled) whereas the old Mako form allowed an inputs-only workflow — confirm no downstream reliance on the old behavior.
GenericItem.vuerewrite is load-bearing (lazy providers), not cosmetic — changed test-facing stub shape.
Tests
lib/galaxy_test/api/test_workflow_extraction.py(+90): newTestWorkflowExtractionSummaryApi—test_extraction_summary_empty_history,_input_datasets_from_upload,_input_collection,_tool_step(@skip_without_tool("cat1")),_structure. Summary endpoint only.client/src/components/History/WorkflowExtractionForm.test.ts(+267): loading, empty-history, with-jobs, submission-validation, input-renaming, submission suites (callssubmitWorkflowExtraction).client/src/components/Common/RenameModal.test.ts(+90) andWorkflow/List/WorkflowCardList.test.ts(+71): rename-flow incl. a shared-modal name-bleed regression guard.client/src/components/JobParameters/JobParameters.test.ts(+13/−18): adjusted to theGenericItem.vuerefactor (collateral, not a feature test).lib/galaxy_test/selenium/test_workflow_extraction.py(+52/−54): rewrites the suite from the Mako page to the Vue form; assertsno_workflow_messagefor empty history and the create buttonaria-disabledwhen no steps selected.
Notes
- This is the HID-era foundation of the workflow-extraction modernization tracked by Issue 17506 - Convert Workflow Extraction Interface to Vue and the Plan - Workflow Extraction Vue Conversion / Plan - Workflow Extraction Vue Conversion - API specs. It delivers the Vue UI + typed FastAPI surface; PR 22706 - Workflow Extraction by IDs then swaps the HID-keyed payload for encoded-id/ICJ-native selection without changing the UI’s overall shape.
- The summary endpoint wraps the existing
summarize/WorkflowSummaryancestry traversal documented in Component - Workflow Extraction Models; the design problem this PR’s HID payload leaves open is detailed in Workflow Extraction Multiple Histories and Workflow Extraction Issues. - Extraction endpoints live on the histories controller (
FastAPIHistories), not the workflows controller — see Component - Workflow API for the sibling workflow surface; PR 22706 - Workflow Extraction by IDs adds the parallelPOST /api/workflows/extract.