Skip to content

Commit

Permalink
Implement lazy loading
Browse files Browse the repository at this point in the history
  • Loading branch information
edan-bainglass committed Oct 24, 2024
1 parent b0cfdbd commit 21aa97b
Show file tree
Hide file tree
Showing 59 changed files with 5,465 additions and 4,126 deletions.
31 changes: 15 additions & 16 deletions qe.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,6 @@
"metadata": {},
"outputs": [],
"source": [
"import urllib.parse as urlparse\n",
"\n",
"from aiidalab_qe.app.main import App\n",
"from aiidalab_widgets_base.bug_report import (\n",
" install_create_github_issue_exception_handler,\n",
Expand All @@ -86,17 +84,23 @@
" 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,
"metadata": {},
"outputs": [],
"source": [
"import urllib.parse as urlparse\n",
"\n",
"url = urlparse.urlsplit(jupyter_notebook_url) # noqa F821\n",
"query = urlparse.parse_qs(url.query)\n",
"\n",
"app = App(qe_auto_setup=True)\n",
"# if a pk is provided in the query string, set it as the process of the app\n",
"if \"pk\" in query:\n",
" pk = query[\"pk\"][0]\n",
" app.process = pk\n",
"\n",
"view.main.children = [app]\n",
"view.app = app"
" app.process = query[\"pk\"][0]"
]
},
{
Expand All @@ -111,7 +115,7 @@
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"display_name": "base",
"language": "python",
"name": "python3"
},
Expand All @@ -126,11 +130,6 @@
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.13"
},
"vscode": {
"interpreter": {
"hash": "d4d1e4263499bec80672ea0156c357c1ee493ec2b1c70f0acce89fc37c4a6abe"
}
}
},
"nbformat": 4,
Expand Down
254 changes: 126 additions & 128 deletions src/aiidalab_qe/app/configuration/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,91 +8,82 @@
import ipywidgets as ipw
import traitlets as tl

from aiida import orm
from aiidalab_qe.app.utils import get_entry_items
from aiidalab_qe.app.parameters import DEFAULT_PARAMETERS
from aiidalab_qe.common.panel import SettingsPanel
from aiidalab_widgets_base import WizardAppWidgetStep

from .advanced import AdvancedSettings
from .model import ConfigurationModel
from .workflow import WorkChainSettings

DEFAULT: dict = DEFAULT_PARAMETERS # type: ignore


class ConfigureQeAppWorkChainStep(ipw.VBox, WizardAppWidgetStep):
confirmed = tl.Bool()
previous_step_state = tl.UseEnum(WizardAppWidgetStep.State)
input_structure = tl.Instance(orm.StructureData, allow_none=True)

# output dictionary
configuration_parameters = tl.Dict()

def __init__(self, **kwargs):
self.workchain_settings = WorkChainSettings()
self.advanced_settings = AdvancedSettings()
def __init__(self, model: ConfigurationModel, **kwargs):
from aiidalab_qe.common.widgets import LoadingWidget

ipw.dlink(
(self.workchain_settings.workchain_protocol, "value"),
(self.advanced_settings, "protocol"),
)
ipw.dlink(
(self.workchain_settings.spin_type, "value"),
(self.advanced_settings, "spin_type"),
super().__init__(
children=[LoadingWidget("Loading workflow configuration panel")],
**kwargs,
)
ipw.dlink(
(self.workchain_settings.electronic_type, "value"),
(self.advanced_settings, "electronic_type"),

self._model = model
self._model.observe(
self._on_confirmation_change,
"confirmed",
)
ipw.dlink(
(self, "input_structure"),
(self.advanced_settings, "input_structure"),
self._model.observe(
self._on_input_structure_change,
"input_structure",
)
#
ipw.dlink(
(self, "input_structure"),
(self.workchain_settings, "input_structure"),
self._model.workchain.observe(
self._on_protocol_change,
"protocol",
)
#

self.missing_structure_message = """
<div class="alert alert-info">
<b>Please set the input structure first.</b>
</div>
"""
self.structure_set_message = ipw.HTML(self.missing_structure_message)

self.workchain_settings = WorkChainSettings(config_model=model)
self.advanced_settings = AdvancedSettings(config_model=model)

self.built_in_settings = [
self.workchain_settings,
self.advanced_settings,
]
self.tab = ipw.Tab(
children=self.built_in_settings,
layout=ipw.Layout(min_height="250px"),
)

self.tab.set_title(0, "Basic settings")
self.tab.set_title(1, "Advanced settings")

# store the property identifier and setting panel for all plugins
# only show the setting panel when the corresponding property is selected
# first add the built-in settings
self.settings = {
self.settings: dict[str, SettingsPanel] = {
"workchain": self.workchain_settings,
"advanced": self.advanced_settings,
}

# list of trailets to link
# if new trailets are added to the settings, they need to be added here
trailets_list = ["input_structure", "protocol", "electronic_type", "spin_type"]

# then add plugin specific settings
entries = get_entry_items("aiidalab_qe.properties", "setting")
for identifier, entry_point in entries.items():
self.settings[identifier] = entry_point(parent=self)
self.settings[identifier].identifier = identifier
# link basic protocol to all plugin specific protocols
if identifier in self.workchain_settings.properties:
self.workchain_settings.properties[identifier].run.observe(
self._update_panel, "value"
)
# link the trailets if they exist in the plugin specific settings
for trailet in trailets_list:
if hasattr(self.settings[identifier], trailet):
ipw.dlink(
(self.advanced_settings, trailet),
(self.settings[identifier], trailet),
)

self._submission_blocker_messages = ipw.HTML()
self.workchain_settings.fetch_setting_entries(
register_setting_callback=self._register_setting,
update_tabs_callback=self._update_tabs,
)

self.rendered = False

def render(self):
if self.rendered:
return

self.tab = ipw.Tab(
layout=ipw.Layout(min_height="250px"),
selected_index=None,
)
self.tab.observe(
self._on_tab_change,
"selected_index",
)
self._update_tabs()

self.confirm_button = ipw.Button(
description="Confirm",
Expand All @@ -102,84 +93,91 @@ def __init__(self, **kwargs):
disabled=True,
layout=ipw.Layout(width="auto"),
)

ipw.dlink(
(self, "state"),
(self.confirm_button, "disabled"),
lambda state: state != self.State.CONFIGURED,
)
self.confirm_button.on_click(self.confirm)

super().__init__(
children=[
self.tab,
self._submission_blocker_messages,
self.confirm_button,
],
**kwargs,
)
self.children = [
self.structure_set_message,
self.tab,
self.confirm_button,
]

self.rendered = True

def is_saved(self):
return self._model.confirmed

def confirm(self, _=None):
self._model.confirmed = True

def reset(self):
self._model.reset()
if self.rendered:
self.tab.selected_index = 0
for _, settings in self.settings.items():
settings.reset()

@tl.observe("previous_step_state")
def _observe_previous_step_state(self, _change):
def _on_previous_step_state_change(self, _):
self._update_state()

def get_configuration_parameters(self):
"""Get the parameters of the configuration step."""
def _on_tab_change(self, change):
if (tab_index := change["new"]) is None:
return
tab: SettingsPanel = self.tab.children[tab_index] # type: ignore
tab.render()
tab.update()

return {s.identifier: s.get_panel_value() for s in self.tab.children}
def _on_input_structure_change(self, _):
# TODO model updates should be done more generally (plugin-approach)
self._model.workchain.update()
self._update_missing_structure_warning()
self.reset()

def set_configuration_parameters(self, parameters):
"""Set the inputs in the GUI based on a set of parameters."""
def _on_protocol_change(self, _):
self._model.advanced.update()

with self.hold_trait_notifications():
for identifier, settings in self.settings.items():
if parameters.get(identifier):
settings.set_panel_value(parameters[identifier])
def _on_confirmation_change(self, _):
self._update_state()

def _register_setting(self, identifier, setting):
self.settings[identifier] = setting(
parent=self,
identifier=identifier,
config_model=self._model,
)

def _update_missing_structure_warning(self):
self.structure_set_message.value = (
self.missing_structure_message
if self._model.input_structure is None
else ""
)

def _update_tabs(self):
children = []
titles = []
for identifier, model in self._model.get_models():
if model.include:
setting = self.settings[identifier]
titles.append(setting.title)
children.append(setting)
if hasattr(self, "tab"):
self.tab.children = children
for i, title in enumerate(titles):
self.tab.set_title(i, title)
self.tab.selected_index = 0

def _update_state(self, _=None):
if self.previous_step_state == self.State.SUCCESS:
self.confirm_button.disabled = False
self._submission_blocker_messages.value = ""
if self._model.confirmed:
self.state = self.State.SUCCESS
elif self.previous_step_state is self.State.SUCCESS:
self.state = self.State.CONFIGURED
# update plugin specific settings
for _, settings in self.settings.items():
settings._update_state()
elif self.previous_step_state == self.State.FAIL:
elif self.previous_step_state is self.State.FAIL:
self.state = self.State.FAIL
else:
self.confirm_button.disabled = True
self.state = self.State.INIT
self.reset()

def confirm(self, _=None):
self.configuration_parameters = self.get_configuration_parameters()
self.confirm_button.disabled = False
self.state = self.State.SUCCESS

def is_saved(self):
"""Check if the current step is saved.
That all changes are confirmed.
"""
new_parameters = self.get_configuration_parameters()
return new_parameters == self.configuration_parameters

@tl.default("state")
def _default_state(self):
return self.State.INIT

def reset(self):
"""Reset the widgets in all settings to their initial states."""
with self.hold_trait_notifications():
self.input_structure = None
for _, settings in self.settings.items():
settings.reset()

def _update_panel(self, _=None):
"""Dynamic add/remove the panel based on the selected properties."""
# only keep basic and advanced settings
self.tab.children = self.built_in_settings
# add plugin specific settings
for identifier in self.workchain_settings.properties:
if (
identifier in self.settings
and self.workchain_settings.properties[identifier].run.value
):
self.tab.children += (self.settings[identifier],)
self.tab.set_title(
len(self.tab.children) - 1, self.settings[identifier].title
)
Loading

0 comments on commit 21aa97b

Please sign in to comment.