Skip to content

Have ert show hooked workflows in GUI#13397

Open
jonathan-eq wants to merge 4 commits into
equinor:mainfrom
jonathan-eq:23042026_show_workflow_status_in_gui
Open

Have ert show hooked workflows in GUI#13397
jonathan-eq wants to merge 4 commits into
equinor:mainfrom
jonathan-eq:23042026_show_workflow_status_in_gui

Conversation

@jonathan-eq
Copy link
Copy Markdown
Contributor

@jonathan-eq jonathan-eq commented Apr 23, 2026

Issue
Resolves #13320

Approach
This commit changes the GUI to:

  • Show iterations and updates in tabs to accommodate showing pre/post
    run/update workflows
  • Show pre-experiment and post-experiment workflows on main tab-bar
  • Pressing terminate experiment during hooked workflows cancels the
    workflow, and marks it as cancelled by user. Following workflows in
    the same hook will be marked as cancelled, as to not mislead the user.

(Screenshot of new behavior in GUI if applicable)

  • PR title captures the intent of the changes, and is fitting for release notes.
  • Added appropriate release note label
  • Commit history is consistent and clean, in line with the contribution guidelines.
  • Make sure unit tests pass locally after every commit (git rebase -i main --exec 'just rapid-tests')

When applicable

  • When there are user facing changes: Updated documentation
  • New behavior or changes to existing untested code: Ensured that unit tests are added (See Ground Rules).
  • Large PR: Prepare changes in small commits for more convenient review
  • Bug fix: Add regression test for the bug
  • Bug fix: Add backport label to latest release (format: 'backport release-branch-name')

@jonathan-eq jonathan-eq force-pushed the 23042026_show_workflow_status_in_gui branch 2 times, most recently from 0095d01 to 4e61524 Compare April 23, 2026 14:03
@jonathan-eq
Copy link
Copy Markdown
Contributor Author

jonathan-eq commented Apr 29, 2026

Changing iteration tabs does not always update the fm_overview table...
EDIT: It might be handled, it was atleast VERY rarely, and the test seems to be passing.

@jonathan-eq jonathan-eq force-pushed the 23042026_show_workflow_status_in_gui branch from 4e61524 to fd82164 Compare April 29, 2026 10:39
Comment thread src/_ert/events.py

from pydantic import BaseModel, ConfigDict, Field, TypeAdapter

from _ert.hook_runtime import HookRuntime
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had to move hook_runtime to _ert to avoid circular import

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 29, 2026

Codecov Report

❌ Patch coverage is 92.39631% with 33 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.55%. Comparing base (4e80552) to head (f1c555d).
⚠️ Report is 3 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/ert/gui/experiments/run_dialog.py 89.58% 15 Missing ⚠️
src/ert/workflow_runner.py 77.77% 14 Missing ⚠️
src/ert/run_models/run_model.py 88.00% 3 Missing ⚠️
src/ert/gui/experiments/view/workflow.py 99.23% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #13397      +/-   ##
==========================================
+ Coverage   89.54%   89.55%   +0.01%     
==========================================
  Files         463      465       +2     
  Lines       32747    33129     +382     
==========================================
+ Hits        29322    29668     +346     
- Misses       3425     3461      +36     
Flag Coverage Δ
cli-tests 35.58% <25.57%> (-0.16%) ⬇️
fuzz 43.77% <25.80%> (-0.07%) ⬇️
gui-tests 60.12% <85.02%> (+0.28%) ⬆️
performance-and-unit-tests 78.18% <86.40%> (+0.11%) ⬆️
test 45.30% <35.71%> (-0.13%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/_ert/events.py 100.00% <100.00%> (ø)
src/_ert/hook_runtime.py 100.00% <ø> (ø)
src/ert/config/ert_config.py 95.76% <ø> (ø)
src/ert/config/external_ert_script.py 100.00% <100.00%> (ø)
src/ert/config/parsing/__init__.py 100.00% <100.00%> (ø)
src/ert/config/parsing/config_schema.py 100.00% <100.00%> (ø)
src/ert/config/workflow.py 98.52% <100.00%> (+0.19%) ⬆️
src/ert/config/workflow_fixtures.py 100.00% <100.00%> (ø)
src/ert/gui/experiments/view/__init__.py 100.00% <100.00%> (ø)
src/ert/gui/experiments/view/tab_group_widget.py 100.00% <100.00%> (ø)
... and 6 more

... and 3 files with indirect coverage changes

@jonathan-eq jonathan-eq force-pushed the 23042026_show_workflow_status_in_gui branch from 9181b64 to 4d0e6b6 Compare April 30, 2026 09:30
Comment thread src/_ert/events.py


class _WorkflowEvent(BaseModel):
hook: HookRuntime | None = None
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

workflow_runner tests required this to be optional

Comment thread src/_ert/hook_runtime.py Outdated
from .workflow import WorkflowWidget


class IterationWidget(QWidget):
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the widget that allows us to group the tabs. Maybe TabGroupingWidget is better? It can contain UpdateWidget, RealizationWidget, and WorkflowWIdget.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TabGroupWidget

Comment on lines +24 to +30
_STATUS_TO_BACKGROUND = {
WorkflowStatus.PENDING: QColor(*state.COLOR_PENDING),
WorkflowStatus.RUNNING: QColor(*state.COLOR_RUNNING),
WorkflowStatus.FINISHED: QColor(*state.COLOR_FINISHED),
WorkflowStatus.FAILED: QColor(*state.COLOR_FAILED),
WorkflowStatus.CANCELLED: QColor(*state.COLOR_CANCELLED),
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very similar to the statuses and look we have in evaluator. The tables can probably at some point be combined (if we want to add the duration field for workflows too).

Comment on lines +33 to +34
"Workflow job": 0,
"Status": 1,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe all should be uppercase?

@jonathan-eq jonathan-eq force-pushed the 23042026_show_workflow_status_in_gui branch 3 times, most recently from 2e60d28 to d466b09 Compare May 5, 2026 06:33
@jonathan-eq jonathan-eq requested a review from Copilot May 5, 2026 06:49
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces workflow lifecycle events (batch + per-workflow started/finished/cancelled) and wires them through the run model into the GUI so workflow execution can be visualized in dedicated tabs (including per-iteration/update grouping). It also centralizes HookRuntime under _ert and updates affected tests accordingly.

Changes:

  • Add new workflow event types (Workflow*Event, WorkflowStatus) and emit them from RunModel.run_workflows() and WorkflowRunner.run_blocking().
  • Introduce GUI widgets/tabs (WorkflowWidget, IterationWidget) and update RunDialog to place workflow hooks before/after iteration/update tabs.
  • Expand unit/UI test coverage to assert event emission and GUI tab ordering/behavior for workflows.

Reviewed changes

Copilot reviewed 22 out of 24 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/ert/unit_tests/workflow_runner/test_workflow_runner.py Adds tests asserting per-workflow events include hook/iteration/workflow_name and capture stdout; adds cancellation event assertions.
tests/ert/unit_tests/run_models/test_base_run_model.py Adds test for workflow batch events across multiple workflows, including non-stopping failures.
tests/ert/unit_tests/gui/experiments/test_run_dialog.py Updates GUI unit tests for new IterationWidget structure and adds test for workflow tabs placement around iteration tab.
tests/ert/ui_tests/gui/test_workflow_widget.py New UI test suite validating WorkflowWidget status/label/output rendering and row coloring.
tests/ert/ui_tests/gui/test_update_runs.py Extends UI tests to validate workflow hook tab placement across experiment/iteration/update lifecycle.
tests/ert/ui_tests/gui/test_restart_ensemble_experiment.py Adjusts UI tests to navigate through IterationWidget to reach RealizationWidget.
tests/ert/ui_tests/gui/test_docs_screenshots.py Updates screenshot test navigation for IterationWidget nesting.
tests/ert/ui_tests/gui/conftest.py Updates common GUI test helper to account for IterationWidget tab nesting.
src/ert/workflow_runner.py Emits workflow started/finished/cancelled events and aggregates stdout/stderr across jobs.
src/ert/run_models/update_run_model.py Reorders RunModelUpdateBeginEvent/status emission relative to pre-update workflows.
src/ert/run_models/run_model.py Emits workflow batch started/finished events and passes hook/iteration/name into WorkflowRunner.
src/ert/run_models/event.py Extends run-model event union to include WorkflowEvent.
src/ert/gui/experiments/view/workflow.py New WorkflowWidget displaying workflow status and stdout/stderr in a table with status coloring.
src/ert/gui/experiments/view/iteration.py New IterationWidget to group Run/Update/Workflow sub-tabs per iteration/update.
src/ert/gui/experiments/view/init.py Exports IterationWidget and WorkflowWidget.
src/ert/gui/experiments/run_dialog.py Reworks tab management to use IterationWidget containers and to create/update workflow tabs on workflow events.
src/ert/config/workflow_fixtures.py Switches HookRuntime import to _ert.hook_runtime.
src/ert/config/parsing/hook_runtime.py Removes local HookRuntime definition (moved to _ert).
src/ert/config/parsing/config_schema.py Updates schema typing to use _ert.hook_runtime.HookRuntime.
src/ert/config/parsing/init.py Re-exports _ert.hook_runtime.HookRuntime from parsing package.
src/_ert/hook_runtime.py New home for HookRuntime plus workflow_tab_title() helper for GUI labels.
src/_ert/events.py Adds WorkflowStatus and workflow event models to the shared events module.

Comment thread src/ert/workflow_runner.py Outdated
Comment on lines +185 to +190
logger.info(f"Workflow job {jobrunner.name} starting")
jobrunner.run(args, fixtures=self.fixtures)
try:
jobrunner.run(args, fixtures=self.fixtures)
except Exception as err:
stdout = ""
stderr = str(err)
Comment thread src/ert/workflow_runner.py Outdated
Comment on lines +186 to +188
try:
jobrunner.run(args, fixtures=self.fixtures)
except Exception as err:
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be step 2!

Comment on lines +220 to +222
item.setText("-" if text == "" else text) # noqa: PLC1901
item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
item.setToolTip(text)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't passing None be fine?

@jonathan-eq
Copy link
Copy Markdown
Contributor Author

Now it is possible to cancel hooked workflows while they are running. It is very similar to the fm step statuses during evaluation.

Screen.Recording.2026-05-05.at.14.59.47.mov

@jonathan-eq jonathan-eq force-pushed the 23042026_show_workflow_status_in_gui branch from de417eb to 5f7baed Compare May 5, 2026 13:23
@jonathan-eq jonathan-eq changed the title init Have ert show hooked workflows in GUI May 5, 2026
@jonathan-eq jonathan-eq requested a review from Copilot May 5, 2026 13:24
@jonathan-eq jonathan-eq marked this pull request as ready for review May 5, 2026 13:24
@jonathan-eq jonathan-eq force-pushed the 23042026_show_workflow_status_in_gui branch from 5f7baed to b3a7616 Compare May 5, 2026 13:27
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 25 changed files in this pull request and generated 6 comments.

Comment thread src/ert/run_models/run_model.py
Comment on lines +225 to +227
item.setText("-" if text == "" else text) # noqa: PLC1901
item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
item.setToolTip(text)
Comment on lines +201 to +203
@pytest.mark.usefixtures("use_tmpdir")
def test_run_workflows_emits_events_for_following_workflow_after_non_stopping_failure():
WorkflowCommon.createExternalDumpJob()
Comment on lines +280 to +283
def test_run_workflows_cancels_active_workflow_and_stops_following_workflows(
use_tmpdir,
):
WorkflowCommon.createExternalDumpJob()
@pytest.mark.filterwarnings(
"ignore:Use of legacy_ertscript_workflow is deprecated.*:DeprecationWarning"
)
def test_run_dialog_places_all_workflow_hooks_in_expected_tabs(qtbot, tmp_path):
Comment on lines +478 to +480
def test_esmda_with_all_workflows_produces_expected_final_gui(
qtbot, tmp_path, source_root, run_experiment
):
@jonathan-eq
Copy link
Copy Markdown
Contributor Author

Found a new bug: #13487

@jonathan-eq jonathan-eq force-pushed the 23042026_show_workflow_status_in_gui branch 3 times, most recently from 3cac030 to 12957a2 Compare May 6, 2026 12:55
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 25 changed files in this pull request and generated 3 comments.

Comment thread src/ert/gui/experiments/view/workflow.py Outdated
Comment thread src/ert/gui/experiments/view/workflow.py
Comment thread src/ert/config/workflow.py Outdated
@jonathan-eq jonathan-eq force-pushed the 23042026_show_workflow_status_in_gui branch from f89b19b to f45a77b Compare May 8, 2026 09:49
@jonathan-eq jonathan-eq requested a review from Copilot May 8, 2026 10:20
@jonathan-eq jonathan-eq self-assigned this May 8, 2026
@jonathan-eq jonathan-eq added the release-notes:new-feature Automatically categorise as new feature in release notes label May 8, 2026
@jonathan-eq jonathan-eq force-pushed the 23042026_show_workflow_status_in_gui branch from f45a77b to 1db9136 Compare May 8, 2026 10:22
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 27 changed files in this pull request and generated 3 comments.

Comment on lines +204 to +206
@pytest.mark.filterwarnings("ignore:.*Deprecated keywords, SCRIPT and INTERNAL")
@pytest.mark.usefixtures("use_tmpdir")
def test_run_workflows_emits_events_for_following_workflow_after_non_stopping_failure():
Comment on lines +330 to +337
@pytest.mark.mpl_image_compare(tolerance=10.0)
@pytest.mark.skip_mac_ci # test is slow
@pytest.mark.filterwarnings(
"ignore:Use of legacy_ertscript_workflow is deprecated.*:DeprecationWarning"
)
def test_esmda_with_all_workflows_produces_expected_final_gui(
qtbot: QtBot, tmp_path, source_root, run_experiment
):
self,
workflow_name: str,
status: WorkflowStatus,
) -> int | None:
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 27 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

src/ert/workflow_runner.py:135

  • WorkflowRunner creates a new ThreadPoolExecutor per instance (self._workflow_executor = ...) but it is never shut down. This can leave idle worker threads alive for the lifetime of the process (especially in the GUI where workflows can be run repeatedly). Consider shutting down the executor (e.g., in __exit__/a dedicated close() method, or by lazily creating it only when run() is used and shutting it down after completion).
        self.__workflow_result: bool | None = None
        self._workflow_executor = futures.ThreadPoolExecutor(max_workers=1)
        self._workflow_job: Future[None] | None = None

@jonathan-eq jonathan-eq force-pushed the 23042026_show_workflow_status_in_gui branch from 1618f9d to 0a686ec Compare May 8, 2026 13:48
@jonathan-eq
Copy link
Copy Markdown
Contributor Author

jonathan-eq commented May 12, 2026

Findings: Rerun doesnt clear workflow widget table before appending more rows
Rerunning after cancelling might cause issues(?) - clear underlying table (table.clear()?)
Remove generic widgets from TabGroupWidget.
Widget should not autoselect on new tab, if specific tab is already selected

@jonathan-eq jonathan-eq force-pushed the 23042026_show_workflow_status_in_gui branch 4 times, most recently from 051e529 to 63d618a Compare May 21, 2026 10:29
@jonathan-eq jonathan-eq requested a review from Copilot May 21, 2026 11:16
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 28 changed files in this pull request and generated 3 comments.

Comment on lines +403 to +411
if isinstance(widget, TabGroupWidget):
current_widget = widget.current_widget()
if isinstance(current_widget, RealizationWidget):
current_widget.refresh_current_selection()

self.fm_step_frame.setHidden(isinstance(current_widget, UpdateWidget))
return

self.fm_step_frame.setHidden(isinstance(widget, UpdateWidget))
self.fm_step_frame.setHidden(False)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe in the future, this is closer to the behavior we already had.

finally:
self._workflow_runner = None

if self._end_event.is_set():
from ert.gui.experiments.view.tab_group_widget import TabGroupWidget


def test_tab_group_widget_current_widget_tracks_tab_widget_selection(qtbot):
@jonathan-eq jonathan-eq force-pushed the 23042026_show_workflow_status_in_gui branch from b3ff033 to 91a94b6 Compare May 21, 2026 11:42
This commit changes the GUI to:
* Show iterations and updates in tabs to accommodate showing pre/post
  run/update workflows
* Show pre-experiment and post-experiment workflows on main tab-bar
* Pressing terminate experiment during hooked workflows cancels the
  workflow, and marks it as cancelled by user. Following workflows in
  the same hook will be marked as cancelled, as to not mislead the user.
@jonathan-eq jonathan-eq force-pushed the 23042026_show_workflow_status_in_gui branch from 91a94b6 to 4c7c553 Compare May 21, 2026 13:33
@jonathan-eq jonathan-eq force-pushed the 23042026_show_workflow_status_in_gui branch from 4c7c553 to f1c555d Compare May 21, 2026 13:38
@jonathan-eq
Copy link
Copy Markdown
Contributor Author

This one is almost ready, it just needs some cleanup and an additional test that makes sure tabs dont auto select on new events if the user has manually selected a tab.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-notes:new-feature Automatically categorise as new feature in release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Store output from workflows

4 participants