Skip to content

Commit

Permalink
Codify state icons in a separate module (#1186)
Browse files Browse the repository at this point in the history
This PR defines process state icons in a separate common module and utilizes them throughout the app.
  • Loading branch information
edan-bainglass authored Feb 26, 2025
1 parent 7cdec52 commit d1aae33
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 40 deletions.
52 changes: 30 additions & 22 deletions src/aiidalab_qe/app/result/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from aiida.engine import ProcessState
from aiidalab_qe.common.infobox import InAppGuide
from aiidalab_qe.common.process import STATE_ICONS
from aiidalab_qe.common.widgets import LoadingWidget
from aiidalab_qe.common.wizard import QeDependentWizardStep
from aiidalab_widgets_base import ProcessMonitor, WizardAppWidgetStep
Expand All @@ -13,19 +14,14 @@
from .components.viewer import WorkChainResultsViewer, WorkChainResultsViewerModel
from .model import ResultsStepModel

STATUS_TEMPLATE = "<h4>Workflow status: {}</h4"

STATUS_MAP = {
ProcessState.EXCEPTED: "Excepted",
ProcessState.KILLED: "Killed",
}


class ViewQeAppWorkChainStatusAndResultsStep(QeDependentWizardStep[ResultsStepModel]):
missing_information_warning = (
"No available results. Did you submit or load a calculation?"
)

STATUS_TEMPLATE = "<h4>Workflow status: {}</h4"

def __init__(self, model: ResultsStepModel, **kwargs):
self.log_widget = kwargs.pop("log_widget", None)
super().__init__(model=model, **kwargs)
Expand Down Expand Up @@ -235,30 +231,42 @@ def _update_status(self):
self._model.monitor_counter += 1

def _update_state(self):
process_node = self._model.fetch_process_node()
if not process_node:
if not (process_node := self._model.fetch_process_node()):
self.state = self.State.INIT
elif process_node.process_state in (
ProcessState.CREATED,
self._update_controls()
return

if process_state := process_node.process_state:
status = self._get_process_status(process_state.value)
else:
status = "Unknown"

if process_state is ProcessState.CREATED:
self.state = self.State.ACTIVE
elif process_state in (
ProcessState.RUNNING,
ProcessState.WAITING,
):
self.state = self.State.ACTIVE
self._model.process_info = STATUS_TEMPLATE.format("Running")
elif (
process_node.process_state
in (
ProcessState.EXCEPTED,
ProcessState.KILLED,
)
or process_node.is_failed
status = self._get_process_status("running") # overwrite status
elif process_state in (
ProcessState.EXCEPTED,
ProcessState.KILLED,
):
self.state = self.State.FAIL
status = STATUS_MAP.get(process_node.process_state, "Failed")
self._model.process_info = STATUS_TEMPLATE.format(status)
elif process_node.is_failed:
self.state = self.State.FAIL
elif process_node.is_finished_ok:
self.state = self.State.SUCCESS
self._model.process_info = STATUS_TEMPLATE.format("Completed successfully")

self._model.process_info = self.STATUS_TEMPLATE.format(status)

self._update_controls()

def _update_controls(self):
if self.state in (self.State.SUCCESS, self.State.FAIL):
self._update_kill_button_layout()
self._update_clean_scratch_button_layout()

def _get_process_status(self, state: str):
return f"{state.capitalize()} {STATE_ICONS[state]}"
8 changes: 1 addition & 7 deletions src/aiidalab_qe/app/utils/search_jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,9 @@
from table_widget import TableWidget

from aiida.orm import QueryBuilder, load_node
from aiidalab_qe.common.process import STATE_ICONS
from aiidalab_qe.common.widgets import LoadingWidget

STATE_ICONS = {
"running": "⏳",
"finished": "✅",
"excepted": "⚠️",
"killed": "❌",
}


def determine_state_icon(row):
"""Attach an icon to the displayed job state."""
Expand Down
6 changes: 4 additions & 2 deletions src/aiidalab_qe/common/mixins.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import typing as t

import traitlets as tl
Expand Down Expand Up @@ -107,9 +109,9 @@ def outputs(self):
process_node = self.fetch_process_node()
return process_node.outputs if process_node else []

def fetch_process_node(self):
def fetch_process_node(self) -> orm.ProcessNode | None:
try:
return orm.load_node(self.process_uuid) if self.process_uuid else None
return orm.load_node(self.process_uuid) if self.process_uuid else None # type: ignore
except NotExistent:
return None

Expand Down
5 changes: 5 additions & 0 deletions src/aiidalab_qe/common/process/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
from .process import QeAppWorkChainSelector, WorkChainSelector
from .state import STATE_ICONS
from .tree import SimplifiedProcessTree, SimplifiedProcessTreeModel

__all__ = [
"STATE_ICONS",
"QeAppWorkChainSelector",
"SimplifiedProcessTree",
"SimplifiedProcessTreeModel",
"WorkChainSelector",
]
11 changes: 11 additions & 0 deletions src/aiidalab_qe/common/process/state.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
STATE_ICONS = {
"created": "🚀",
"waiting": "⏳",
"queued": "⏳",
"paused": "⏸",
"running": "▶️",
"finished": "✅",
"failed": "❌",
"killed": "☠️",
"excepted": "⚠️",
}
12 changes: 3 additions & 9 deletions src/aiidalab_qe/common/process/tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
from aiidalab_qe.common.mvc import Model
from aiidalab_qe.common.widgets import LoadingWidget

from .state import STATE_ICONS


class SimplifiedProcessTreeModel(Model, HasProcess):
clicked = tl.Unicode(None, allow_none=True)
Expand Down Expand Up @@ -186,15 +188,7 @@ def _get_indentation(self):
return ipw.HTML(layout=ipw.Layout(width=f"{22 * self.level}px"))

def _get_emoji(self, state):
return {
"created": "🚀",
"waiting": "💤",
"running": "⏳",
"finished": "✅",
"failed": "❌",
"killed": "💀",
"excepted": "⚠️",
}.get(state, "❓")
return STATE_ICONS.get(state, "❓")

def _get_state(self):
if not hasattr(self.node, "process_state"):
Expand Down

0 comments on commit d1aae33

Please sign in to comment.