3131)
3232from typing_extensions import override
3333
34- from _ert .events import EnsembleEvaluationWarning
34+ from _ert .events import (
35+ EnsembleEvaluationWarning ,
36+ WorkflowBatchFinishedEvent ,
37+ WorkflowBatchStartedEvent ,
38+ WorkflowCancelledEvent ,
39+ WorkflowFinishedEvent ,
40+ WorkflowStartedEvent ,
41+ )
42+ from _ert .hook_runtime import HookRuntime
3543from ert .config import ErrorInfo , WarningInfo
3644from ert .ensemble_evaluator import (
3745 EndEvent ,
8088from .queue_emitter import QueueEmitter
8189from .view import (
8290 DiskSpaceWidget ,
91+ IterationWidget ,
8392 ProgressWidget ,
8493 RealizationWidget ,
8594 RunpathProgressWidget ,
8695 UpdateWidget ,
96+ WorkflowWidget ,
8797)
8898from .view .disk_space_widget import MountType
8999
@@ -389,10 +399,15 @@ def is_experiment_done(self) -> bool:
389399
390400 def _current_tab_changed (self , index : int ) -> None :
391401 widget = self ._tab_widget .widget (index )
392- if isinstance (widget , RealizationWidget ):
393- widget .refresh_current_selection ()
402+ if isinstance (widget , IterationWidget ):
403+ current_widget = widget .current_widget ()
404+ if isinstance (current_widget , RealizationWidget ):
405+ current_widget .refresh_current_selection ()
406+
407+ self .fm_step_frame .setHidden (isinstance (current_widget , UpdateWidget ))
408+ return
394409
395- self .fm_step_frame .setHidden (isinstance ( widget , UpdateWidget ) )
410+ self .fm_step_frame .setHidden (False )
396411
397412 @Slot (QModelIndex , int , int )
398413 def on_snapshot_new_iteration (
@@ -402,26 +417,21 @@ def on_snapshot_new_iteration(
402417 index = self ._snapshot_model .index (start , 0 , parent )
403418 iteration = int (cast (IterNode , index .internalPointer ()).id_ )
404419 self ._latest_iteration = iteration
405- iter_row = start
406420 self ._iteration_progress_label .setText (
407421 f"Progress for iteration { iteration } "
408422 if not self .is_everest
409423 else f"Progress for batch { iteration } "
410424 )
411425
412- widget = RealizationWidget (iter_row )
426+ iteration_widget = self ._get_or_create_iteration_tab (
427+ iteration , is_update = False
428+ )
429+ widget = iteration_widget .select_or_create_realization_tab ()
413430 widget .setSnapshotModel (self ._snapshot_model )
414431 widget .itemClicked .connect (self ._select_real )
415432 widget .setProperty ("identifier" , f"tab-iter-{ iteration } " )
416433 self ._select_real (widget ._real_list_model .index (0 , 0 ))
417- tab_index = self ._tab_widget .addTab (
418- widget ,
419- f"Realizations for iteration { iteration } "
420- if not self .is_everest
421- else f"Batch { iteration } ..." ,
422- )
423- if self ._tab_widget .currentIndex () == self ._tab_widget .count () - 2 :
424- self ._tab_widget .setCurrentIndex (tab_index )
434+ self ._tab_widget .setCurrentWidget (iteration_widget )
425435
426436 if self .is_everest :
427437 self ._batch_result_types .append (set ())
@@ -579,6 +589,24 @@ def _on_event(self, event: object) -> None:
579589 self .post_experiment_warnings .append (msg )
580590 case EnsembleEvaluationWarning (warning_message = msg ):
581591 self ._show_warning (msg )
592+ case WorkflowBatchStartedEvent (
593+ hook = hook , iteration = iteration , workflow_names = workflow_names
594+ ):
595+ self ._select_or_create_workflow_tab (hook , iteration , workflow_names )
596+ case WorkflowStartedEvent (hook = hook , iteration = iteration ):
597+ self ._select_or_create_workflow_tab (hook , iteration ).handle_event (event )
598+ case WorkflowFinishedEvent (
599+ hook = hook ,
600+ iteration = iteration ,
601+ ):
602+ self ._select_or_create_workflow_tab (hook , iteration ).handle_event (event )
603+ case WorkflowCancelledEvent (
604+ hook = hook ,
605+ iteration = iteration ,
606+ ):
607+ self ._select_or_create_workflow_tab (hook , iteration ).handle_event (event )
608+ case WorkflowBatchFinishedEvent (hook = hook , iteration = iteration ):
609+ self ._select_or_create_workflow_tab (hook , iteration ).handle_event (event )
582610
583611 case FullSnapshotEvent (
584612 status_count = status_count , realization_count = realization_count
@@ -604,22 +632,23 @@ def _on_event(self, event: object) -> None:
604632 )
605633 self .progress_update_event .emit (status_count , realization_count )
606634 case RunModelUpdateBeginEvent (iteration = iteration ):
607- widget = UpdateWidget (iteration )
608- tab_index = self ._tab_widget .addTab (widget , f"Update { iteration } " )
609- if self ._tab_widget .currentIndex () == self ._tab_widget .count () - 2 :
610- self ._tab_widget .setCurrentIndex (tab_index )
635+ iteration_widget = self ._get_or_create_iteration_tab (
636+ iteration , is_update = True
637+ )
638+ widget = iteration_widget .select_or_create_update_tab ()
639+ self ._tab_widget .setCurrentWidget (iteration_widget )
611640 widget .begin (event )
612641 case RunModelUpdateEndEvent ():
613642 self ._progress_widget .stop_waiting_progress_bar ()
614- self ._get_update_widget (event .iteration ).end (event )
643+ self ._get_or_create_update_tab (event .iteration ).end (event )
615644 event .write_as_csv (self .output_path )
616645 case RunModelStatusEvent () | RunModelTimeEvent ():
617- self ._get_update_widget (event .iteration ).update_status (event )
646+ self ._get_or_create_update_tab (event .iteration ).update_status (event )
618647 case RunModelDataEvent ():
619- self ._get_update_widget (event .iteration ).add_table (event )
648+ self ._get_or_create_update_tab (event .iteration ).add_table (event )
620649 event .write_as_csv (self .output_path )
621650 case RunModelErrorEvent ():
622- self ._get_update_widget (event .iteration ).error (event )
651+ self ._get_or_create_update_tab (event .iteration ).error (event )
623652 event .write_as_csv (self .output_path )
624653 case EverestBatchResultEvent ():
625654 batch_types = self ._batch_result_types [event .batch ]
@@ -652,12 +681,85 @@ def _on_event(self, event: object) -> None:
652681 ):
653682 runpath_widget .advance ()
654683
655- def _get_update_widget (self , iteration : int ) -> UpdateWidget :
656- for i in range (self ._tab_widget .count ()):
657- widget = self ._tab_widget .widget (i )
658- if isinstance (widget , UpdateWidget ) and widget .iteration == iteration :
684+ def _get_or_create_update_tab (self , iteration : int ) -> UpdateWidget :
685+ return self ._get_or_create_iteration_tab (
686+ iteration , is_update = True
687+ ).select_or_create_update_tab ()
688+
689+ def _get_or_create_iteration_tab (
690+ self , iteration : int , is_update : bool
691+ ) -> IterationWidget :
692+ for index in range (self ._tab_widget .count ()):
693+ widget = self ._tab_widget .widget (index )
694+ if (
695+ isinstance (widget , IterationWidget )
696+ and widget .iteration == iteration
697+ and widget .is_update_page == is_update
698+ ):
659699 return widget
660- raise ValueError ("Could not find UpdateWidget" )
700+
701+ widget = IterationWidget (iteration , self )
702+ widget .is_update_page = is_update
703+ widget .currentTabChanged .connect (
704+ lambda iteration_widget = widget : self ._on_iteration_tab_changed (
705+ iteration_widget
706+ )
707+ )
708+
709+ self ._tab_widget .addTab (
710+ widget ,
711+ f"update-{ iteration } " if is_update else f"iteration-{ iteration } " ,
712+ )
713+ return widget
714+
715+ def _on_iteration_tab_changed (self , iteration_widget : IterationWidget ) -> None :
716+ if self ._tab_widget .currentWidget () is iteration_widget :
717+ self ._current_tab_changed (self ._tab_widget .currentIndex ())
718+
719+ @staticmethod
720+ def _workflow_belongs_to_update (hook : HookRuntime ) -> bool :
721+ return hook in {
722+ HookRuntime .PRE_FIRST_UPDATE ,
723+ HookRuntime .PRE_UPDATE ,
724+ HookRuntime .POST_UPDATE ,
725+ }
726+
727+ def _select_or_create_workflow_tab (
728+ self ,
729+ hook : HookRuntime | None ,
730+ iteration : int | None ,
731+ workflow_names : list [str ] | None = None ,
732+ ) -> WorkflowWidget :
733+ assert hook is not None
734+ if iteration is not None :
735+ iteration_widget = (
736+ self ._get_or_create_iteration_tab (iteration , is_update = True )
737+ if self ._workflow_belongs_to_update (hook )
738+ else self ._get_or_create_iteration_tab (iteration , is_update = False )
739+ )
740+ widget = iteration_widget .select_or_create_workflow_tab (
741+ hook , workflow_names
742+ )
743+ self ._tab_widget .setCurrentWidget (iteration_widget )
744+ return widget
745+
746+ for index in range (self ._tab_widget .count ()):
747+ existing_widget = self ._tab_widget .widget (index )
748+ if (
749+ isinstance (existing_widget , WorkflowWidget )
750+ and existing_widget .hook == hook
751+ ):
752+ return existing_widget
753+
754+ if workflow_names is None :
755+ raise RuntimeError (
756+ "Workflow tab must be created from WorkflowBatchStartedEvent"
757+ )
758+
759+ widget = WorkflowWidget (hook , workflow_names , parent = self )
760+ tab_index = self ._tab_widget .addTab (widget , hook .workflow_tab_title ())
761+ self ._tab_widget .setCurrentIndex (tab_index )
762+ return widget
661763
662764 def update_total_progress (
663765 self , progress_value : float , iteration_label : str , iteration : int | None = None
0 commit comments