Skip to content

Commit

Permalink
Create QeApp class as the entry point of the app
Browse files Browse the repository at this point in the history
  • Loading branch information
superstar54 committed Jan 17, 2025
1 parent 682f99f commit 0d6b500
Show file tree
Hide file tree
Showing 3 changed files with 262 additions and 258 deletions.
61 changes: 9 additions & 52 deletions qe.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,6 @@
"load_profile();"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from aiidalab_widgets_base.utils.loaders import load_css\n",
"\n",
"load_css(css_path=\"src/aiidalab_qe/app/static/styles\")"
]
},
{
"cell_type": "code",
"execution_count": null,
Expand All @@ -50,43 +39,6 @@
" pass"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from aiidalab_qe.app.wrapper import AppWrapperContoller, AppWrapperModel, AppWrapperView\n",
"\n",
"model = AppWrapperModel()\n",
"view = AppWrapperView()\n",
"controller = AppWrapperContoller(model, view)\n",
"\n",
"display(view)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from aiidalab_qe.app.main import App\n",
"from aiidalab_widgets_base.bug_report import (\n",
" install_create_github_issue_exception_handler,\n",
")\n",
"\n",
"install_create_github_issue_exception_handler(\n",
" view.output,\n",
" url=\"https://github.com/aiidalab/aiidalab-qe/issues/new\",\n",
" labels=(\"bug\", \"automated-report\"),\n",
")\n",
"\n",
"app = App(qe_auto_setup=True)\n",
"\n",
"view.main.children = [app]"
]
},
{
"cell_type": "code",
"execution_count": null,
Expand All @@ -95,10 +47,15 @@
"source": [
"import urllib.parse as urlparse\n",
"\n",
"from aiidalab_qe.app.main import QeApp\n",
"\n",
"url = urlparse.urlsplit(jupyter_notebook_url) # noqa F821\n",
"query = urlparse.parse_qs(url.query)\n",
"if \"pk\" in query:\n",
" app.process = query[\"pk\"][0]"
"pk = query[\"pk\"][0] if \"pk\" in query else None\n",
"\n",
"app = QeApp(\n",
" process=pk, bug_report_url=\"https://github.com/aiidalab/aiidalab-qe/issues/new\"\n",
")"
]
},
{
Expand All @@ -107,13 +64,13 @@
"metadata": {},
"outputs": [],
"source": [
"controller.enable_controls()"
"app.load()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "base",
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
Expand Down
254 changes: 48 additions & 206 deletions src/aiidalab_qe/app/main.py
Original file line number Diff line number Diff line change
@@ -1,210 +1,52 @@
"""The main widget that shows the application in the Jupyter notebook.
"""The main app that shows the application in the Jupyter notebook.
Authors: AiiDAlab team
"""

import ipywidgets as ipw
import traitlets as tl
from IPython.display import Javascript, display

from aiida.orm import load_node
from aiida.orm.utils.serialize import deserialize_unsafe
from aiidalab_qe.app.configuration import ConfigureQeAppWorkChainStep
from aiidalab_qe.app.configuration.model import ConfigurationStepModel
from aiidalab_qe.app.result import ViewQeAppWorkChainStatusAndResultsStep
from aiidalab_qe.app.result.model import ResultsStepModel
from aiidalab_qe.app.structure import StructureSelectionStep
from aiidalab_qe.app.structure.model import StructureStepModel
from aiidalab_qe.app.submission import SubmitQeAppWorkChainStep
from aiidalab_qe.app.submission.model import SubmissionStepModel
from aiidalab_qe.common.infobox import InAppGuide
from aiidalab_qe.common.widgets import LoadingWidget, QeWizardStep
from aiidalab_widgets_base import WizardAppWidget


class App(ipw.VBox):
"""The main widget that combines all the application steps together."""

# The PK or UUID of the work chain node.
process = tl.Union([tl.Unicode(), tl.Int()], allow_none=True)

def __init__(self, qe_auto_setup=True):
# Initialize the models
self.structure_model = StructureStepModel()
self.configure_model = ConfigurationStepModel()
self.submit_model = SubmissionStepModel()
self.results_model = ResultsStepModel()

# Create the application steps
self.structure_step = StructureSelectionStep(
model=self.structure_model,
auto_advance=True,
)
self.configure_step = ConfigureQeAppWorkChainStep(
model=self.configure_model,
auto_advance=True,
)
self.submit_step = SubmitQeAppWorkChainStep(
model=self.submit_model,
auto_advance=True,
qe_auto_setup=qe_auto_setup,
)
self.results_step = ViewQeAppWorkChainStatusAndResultsStep(
model=self.results_model,
)

# Wizard step observations
ipw.dlink(
(self.structure_step, "state"),
(self.configure_step, "previous_step_state"),
)
self.structure_model.observe(
self._on_structure_confirmation_change,
"confirmed",
)
ipw.dlink(
(self.configure_step, "state"),
(self.submit_step, "previous_step_state"),
)
self.configure_model.observe(
self._on_configuration_confirmation_change,
"confirmed",
)
ipw.dlink(
(self.submit_step, "state"),
(self.results_step, "previous_step_state"),
)
self.submit_model.observe(
self._on_submission,
"confirmed",
)

# Add the application steps to the application
self._wizard_app_widget = WizardAppWidget(
steps=[
("Select structure", self.structure_step),
("Configure workflow", self.configure_step),
("Choose computational resources", self.submit_step),
("Status & results", self.results_step),
]
)
self._wizard_app_widget.observe(
self._on_step_change,
"selected_index",
)

# Hide the header
self._wizard_app_widget.children[0].layout.display = "none" # type: ignore

self._process_loading_message = LoadingWidget(
message="Loading process",
layout=ipw.Layout(display="none"),
)

super().__init__(
children=[
InAppGuide(identifier="guide-header"),
self._process_loading_message,
self._wizard_app_widget,
InAppGuide(identifier="post-guide"),
]
)

self._wizard_app_widget.selected_index = None

self.structure_step.state = QeWizardStep.State.READY

self._update_blockers()

@property
def steps(self):
return self._wizard_app_widget.steps

@tl.observe("process")
def _on_process_change(self, change):
self._update_from_process(change["new"])

def _on_new_workchain_button_click(self, _):
display(Javascript("window.open('./qe.ipynb', '_blank')"))

def _on_step_change(self, change):
if (step_index := change["new"]) is not None:
self._render_step(step_index)

def _on_structure_confirmation_change(self, _):
self._update_configuration_step()
self._update_blockers()

def _on_configuration_confirmation_change(self, _):
self._update_submission_step()
self._update_blockers()

def _on_submission(self, _):
self._update_results_step()
self._lock_app()

def _render_step(self, step_index):
step: QeWizardStep = self.steps[step_index][1]
step.render()

def _update_configuration_step(self):
if self.structure_model.confirmed:
self.configure_model.input_structure = self.structure_model.input_structure
else:
self.configure_model.input_structure = None

def _update_submission_step(self):
if self.configure_model.confirmed:
self.submit_model.input_structure = self.structure_model.input_structure
self.submit_model.input_parameters = self.configure_model.get_model_state()
else:
self.submit_model.input_structure = None
self.submit_model.input_parameters = {}

def _update_results_step(self):
ipw.dlink(
(self.submit_model, "process_node"),
(self.results_model, "process_uuid"),
lambda node: node.uuid if node is not None else None,
)

def _lock_app(self):
for model in (
self.structure_model,
self.configure_model,
self.submit_model,
):
model.unobserve_all("confirmed")

def _update_blockers(self):
self.submit_model.external_submission_blockers = [
f"Unsaved changes in the <b>{title}</b> step. Please confirm the changes before submitting."
for title, step in self.steps[:2]
if not step.is_saved()
]

def _update_from_process(self, pk):
if pk is None:
self._wizard_app_widget.reset()
self._wizard_app_widget.selected_index = 0
else:
self._show_process_loading_message()
process_node = load_node(pk)
self.structure_model.input_structure = process_node.inputs.structure
self.structure_model.confirm()
parameters = process_node.base.extras.get("ui_parameters", {})
if parameters and isinstance(parameters, str):
parameters = deserialize_unsafe(parameters)
self.configure_model.set_model_state(parameters)
self.configure_model.confirm()
self.submit_model.process_node = process_node
self.submit_model.set_model_state(parameters)
self.submit_model.confirm()
self._wizard_app_widget.selected_index = 3
self._hide_process_loading_message()

def _show_process_loading_message(self):
self._process_loading_message.layout.display = "flex"

def _hide_process_loading_message(self):
self._process_loading_message.layout.display = "none"
from pathlib import Path

from IPython.display import display

from aiidalab_qe.app.static import styles
from aiidalab_qe.app.wizard_app import WizardApp
from aiidalab_qe.app.wrapper import AppWrapperContoller, AppWrapperModel, AppWrapperView
from aiidalab_widgets_base.bug_report import (
install_create_github_issue_exception_handler,
)
from aiidalab_widgets_base.utils.loaders import load_css


class QeApp:
def __init__(self, process=None, bug_report_url=None):
"""Initialize the AiiDAlab QE application with the necessary setup."""
self._load_styles()

# Initialize MVC components
self.model = AppWrapperModel()
self.view = AppWrapperView()
display(self.view)

# Set up bug report handling (if a URL is provided)
if bug_report_url:
install_create_github_issue_exception_handler(
self.view.output,
url=bug_report_url,
labels=("bug", "automated-report"),
)

# setup UI controls
self.controller = AppWrapperContoller(self.model, self.view)
self.controller.enable_controls()
self.process = process

def _load_styles(self):
"""Load CSS styles from the static directory."""
load_css(css_path=Path(styles.__file__).parent)

def load(self):
"""Initialize the WizardApp and integrate the app into the main view."""
self.app = WizardApp(qe_auto_setup=True)
self.view.main.children = [self.app]
# load a previous calculation if it is provided
if self.process:
self.app.process = self.process
Loading

0 comments on commit 0d6b500

Please sign in to comment.