Skip to content

Commit

Permalink
Detects the unsaved changes in the already confirmed steps. (aiidalab…
Browse files Browse the repository at this point in the history
…#537)

This PR detects the unsaved changes in the already confirmed steps. If there are any unsaved changes, it will block the submit step and show the blocker messages there. This is achieved by observing the selected_index of the _wizard_app_widget, and comparing the related values.

---------

Co-authored-by: Jusong Yu <jusong.yeu@gmail.com>
Co-authored-by: Miki Bonacci <mikibonacci@hotmail.it>
  • Loading branch information
3 people authored Nov 8, 2023
1 parent 251e423 commit 0da5836
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 12 deletions.
7 changes: 7 additions & 0 deletions src/aiidalab_qe/app/configuration/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,13 @@ def confirm(self, _=None):
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
Expand Down
34 changes: 30 additions & 4 deletions src/aiidalab_qe/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ def __init__(self, qe_auto_setup=True):
self.structure_step.observe(self._observe_structure_selection, "structure")
self.configure_step = ConfigureQeAppWorkChainStep(auto_advance=True)
self.submit_step = SubmitQeAppWorkChainStep(
auto_advance=True, qe_auto_setup=qe_auto_setup
auto_advance=True,
qe_auto_setup=qe_auto_setup,
)
self.results_step = ViewQeAppWorkChainStatusAndResultsStep()

Expand All @@ -49,7 +50,6 @@ def __init__(self, qe_auto_setup=True):
(self.configure_step, "configuration_parameters"),
(self.submit_step, "input_parameters"),
)

ipw.dlink(
(self.submit_step, "process"),
(self.results_step, "process"),
Expand All @@ -65,6 +65,7 @@ def __init__(self, qe_auto_setup=True):
("Status & Results", self.results_step),
]
)
self._wizard_app_widget.observe(self._observe_selected_index, "selected_index")

# Add process selection header
self.work_chain_selector = QeAppWorkChainSelector(
Expand All @@ -85,14 +86,39 @@ def __init__(self, qe_auto_setup=True):
]
)

# Reset all subsequent steps in case that a new structure is selected
@property
def steps(self):
return self._wizard_app_widget.steps

# Reset the confirmed_structure in case that a new structure is selected
def _observe_structure_selection(self, change):
with self.structure_step.hold_sync():
if (
self.structure_step.confirmed_structure is not None
and self.structure_step.confirmed_structure != change["new"]
):
self._wizard_app_widget.reset()
self.structure_step.confirmed_structure = None

def _observe_selected_index(self, change):
"""Check unsaved change in the step when leaving the step."""
# no accordion tab is selected
if not change["new"]:
return
new_idx = change["new"]
# only when entering the submit step, check and udpate the blocker messages
# steps[new_idx][0] is the title of the step
if self.steps[new_idx][1] is not self.submit_step:
return
blockers = []
# Loop over all steps before the submit step
for title, step in self.steps[:new_idx]:
# check if the step is saved
if not step.is_saved():
step.state = WizardAppWidgetStep.State.CONFIGURED
blockers.append(
f"Unsaved changes in the <b>{title}</b> step. Please save the changes before submitting."
)
self.submit_step.external_submission_blockers = blockers

def _observe_process_selection(self, change):
from aiida.orm.utils.serialize import deserialize_unsafe
Expand Down
5 changes: 5 additions & 0 deletions src/aiidalab_qe/app/structure/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,11 @@ def confirm(self, _=None):
self.confirmed_structure = self.structure
self.message_area.value = ""

def is_saved(self):
"""Check if the current structure is saved.
That all changes are confirmed."""
return self.confirmed_structure == self.structure

def can_reset(self):
return self.confirmed_structure is not None

Expand Down
18 changes: 11 additions & 7 deletions src/aiidalab_qe/app/submission/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ class SubmitQeAppWorkChainStep(ipw.VBox, WizardAppWidgetStep):
process = tl.Instance(orm.WorkChainNode, allow_none=True)
previous_step_state = tl.UseEnum(WizardAppWidgetStep.State)
input_parameters = tl.Dict()
_submission_blockers = tl.List(tl.Unicode())
internal_submission_blockers = tl.List(tl.Unicode())
external_submission_blockers = tl.List(tl.Unicode())

def __init__(self, qe_auto_setup=True, **kwargs):
self.message_area = ipw.Output()
Expand Down Expand Up @@ -138,10 +139,12 @@ def __init__(self, qe_auto_setup=True, **kwargs):
]
)

@tl.observe("_submission_blockers")
def _observe_submission_blockers(self, change):
if change["new"]:
fmt_list = "\n".join((f"<li>{item}</li>" for item in sorted(change["new"])))
@tl.observe("internal_submission_blockers", "external_submission_blockers")
def _observe_submission_blockers(self, _change):
"""Observe the submission blockers and update the message area."""
blockers = self.internal_submission_blockers + self.external_submission_blockers
if any(blockers):
fmt_list = "\n".join((f"<li>{item}</li>" for item in sorted(blockers)))
self._submission_blocker_messages.value = f"""
<div class="alert alert-info">
<strong>The submission is blocked, due to the following reason(s):</strong>
Expand All @@ -150,6 +153,7 @@ def _observe_submission_blockers(self, change):
self._submission_blocker_messages.value = ""

def _identify_submission_blockers(self):
"""Validate the resource inputs and identify blockers for the submission."""
# Do not submit while any of the background setup processes are running.
if self.qe_setup_status.busy or self.sssp_installation_status.busy:
yield "Background setup processes must finish."
Expand Down Expand Up @@ -184,11 +188,11 @@ def _update_state(self, _=None):

blockers = list(self._identify_submission_blockers())
if any(blockers):
self._submission_blockers = blockers
self.internal_submission_blockers = blockers
self.state = self.State.READY
return

self._submission_blockers = []
self.internal_submission_blockers = []
self.state = self.state.CONFIGURED

def _toggle_install_widgets(self, change):
Expand Down
31 changes: 31 additions & 0 deletions tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,34 @@ def test_reload_and_reset(submit_app_generator, generate_qeapp_workchain):
== 0
)
assert app.submit_step.resources_config.num_cpus.value == 1


def test_select_new_structure(app_to_submit, generate_structure_data):
"""Test if the new structure is selected, the confirmed structure is reset"""
app = app_to_submit
assert app.structure_step.confirmed_structure is not None
# select a new structure will reset the confirmed structure
app.structure_step.structure = generate_structure_data()
assert app.structure_step.confirmed_structure is None


def test_unsaved_changes(app_to_submit):
"""Test if the unsaved changes are handled correctly"""
from aiidalab_widgets_base import WizardAppWidgetStep

app = app_to_submit
# go to the configue step, and make some changes
app._wizard_app_widget.selected_index = 1
app.configure_step.workchain_settings.relax_type.value = "positions"
# go to the submit step
app._wizard_app_widget.selected_index = 2
# the state of the configue step should be updated.
assert app.configure_step.state == WizardAppWidgetStep.State.CONFIGURED
# check if a new blocker is added
assert len(app.submit_step.external_submission_blockers) == 1
# confirm the changes
app._wizard_app_widget.selected_index = 1
app.configure_step.confirm()
app._wizard_app_widget.selected_index = 2
# the blocker should be removed
assert len(app.submit_step.external_submission_blockers) == 0
2 changes: 1 addition & 1 deletion tests/test_codes.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def test_set_selected_codes(submit_app_generator):


def test_update_codes_display():
"""Test update_codes_visibility method.
"""Test update_codes_display method.
If the workchain property is not selected, the related code should be hidden.
"""
from aiidalab_qe.app.submission import SubmitQeAppWorkChainStep
Expand Down

0 comments on commit 0da5836

Please sign in to comment.