diff --git a/src/aiidalab_qe/app/result/__init__.py b/src/aiidalab_qe/app/result/__init__.py index 9451a28ac..72742b0d8 100644 --- a/src/aiidalab_qe/app/result/__init__.py +++ b/src/aiidalab_qe/app/result/__init__.py @@ -52,14 +52,30 @@ def __init__(self, **kwargs): tooltip="Kill the below workchain.", button_style="danger", icon="window-close", - layout=ipw.Layout(width="120px", height="40px"), + layout=ipw.Layout(width="120px", height="40px", display="none"), ) self.kill_button.on_click(self._on_click_kill_button) + + self.clean_scratch_button = ipw.Button( + description="Clean remote data", + tooltip="Clean the remote folders of the workchain.", + button_style="danger", + icon="folder", + layout=ipw.Layout(width="150px", height="40px", display="none"), + ) + self.clean_scratch_button.on_click(self._on_click_clean_scratch_button) + self.process_info = ipw.HTML() super().__init__( [ - ipw.HBox(children=[self.kill_button, self.process_info]), + ipw.HBox( + children=[ + self.kill_button, + self.process_info, + self.clean_scratch_button, + ] + ), self.process_status, ], **kwargs, @@ -93,12 +109,14 @@ def _update_state(self): or process.is_failed ): self.state = self.State.FAIL - self.kill_button.layout.display = "none" self.process_info.value = PROCESS_EXCEPTED elif process.is_finished_ok: self.state = self.State.SUCCESS - self.kill_button.layout.display = "none" self.process_info.value = PROCESS_COMPLETED + # trigger the update of kill and clean button. + if self.state in [self.State.SUCCESS, self.State.FAIL]: + self._update_kill_button_layout() + self._update_clean_scratch_button_layout() def _update_kill_button_layout(self): """Update the layout of the kill button.""" @@ -107,8 +125,8 @@ def _update_kill_button_layout(self): self.kill_button.layout.display = "none" else: process = orm.load_node(self.process) - # If the process is finished or excepted, hide the button. - if process.is_finished or process.is_excepted: + # If the process is terminated, hide the button. + if process.is_terminated: self.kill_button.layout.display = "none" else: self.kill_button.layout.display = "block" @@ -120,6 +138,32 @@ def _update_kill_button_layout(self): else: self.kill_button.disabled = True + def _update_clean_scratch_button_layout(self): + """Update the layout of the kill button.""" + # The button is hidden by default, but if we load a new process, we hide again. + if not self.process: + self.clean_scratch_button.layout.display = "none" + else: + process = orm.load_node(self.process) + # If the process is terminated, show the button. + if process.is_terminated: + self.clean_scratch_button.layout.display = "block" + else: + self.clean_scratch_button.layout.display = "none" + + # If the scratch is already empty, we should deactivate the button. + # not sure about the performance if descendants are several. + cleaned_bool = [] + for called_descendant in process.called_descendants: + if isinstance(called_descendant, orm.CalcJobNode): + try: + cleaned_bool.append( + called_descendant.outputs.remote_folder.is_cleaned + ) + except (OSError, KeyError): + pass + self.clean_scratch_button.disabled = all(cleaned_bool) + def _on_click_kill_button(self, _=None): """callback for the kill button. First kill the process, then update the kill button layout. @@ -130,6 +174,22 @@ def _on_click_kill_button(self, _=None): # update the kill button layout self._update_kill_button_layout() + def _on_click_clean_scratch_button(self, _=None): + """callback for the clean scratch button. + First clean the remote folders, then update the clean button layout. + """ + process = orm.load_node(self.process) + + for called_descendant in process.called_descendants: + if isinstance(called_descendant, orm.CalcJobNode): + try: + called_descendant.outputs.remote_folder._clean() + except (OSError, KeyError): + pass + + # update the kill button layout + self._update_clean_scratch_button_layout() + @tl.observe("process") def _observe_process(self, _): """Callback for when the process is changed.""" @@ -137,3 +197,4 @@ def _observe_process(self, _): # as the self.state is updated in the _update_state method. self._update_state() self._update_kill_button_layout() + self._update_clean_scratch_button_layout() diff --git a/tests/test_result.py b/tests/test_result.py index c6e087d17..cbeef97ed 100644 --- a/tests/test_result.py +++ b/tests/test_result.py @@ -1,3 +1,7 @@ +import pytest + + +@pytest.mark.usefixtures("sssp") def test_result_step(app_to_submit, generate_qeapp_workchain): """Test the result step is properly updated when the process is running.""" @@ -7,6 +11,21 @@ def test_result_step(app_to_submit, generate_qeapp_workchain): assert step.state == step.State.ACTIVE +@pytest.mark.usefixtures("sssp") +def test_kill_and_clean_buttons(app_to_submit, generate_qeapp_workchain): + """Test the kill and clean_scratch button are properly displayed when the process + is in different states.""" + + step = app_to_submit.results_step + step.process = generate_qeapp_workchain().node.uuid + step._update_state() + step._update_kill_button_layout() + step._update_clean_scratch_button_layout() + assert step.kill_button.layout.display == "block" + assert step.clean_scratch_button.layout.display == "none" + + +@pytest.mark.usefixtures("sssp") def test_workchainview(generate_qeapp_workchain): """Test the result tabs are properly updated""" from aiidalab_qe.app.result.workchain_viewer import WorkChainViewer @@ -18,6 +37,7 @@ def test_workchainview(generate_qeapp_workchain): assert wcv.result_tabs._titles["1"] == "Final Geometry" +@pytest.mark.usefixtures("sssp") def test_summary_report(data_regression, generate_qeapp_workchain): """Test the summary report can be properly generated.""" from aiidalab_qe.app.result.summary_viewer import SummaryView @@ -29,6 +49,7 @@ def test_summary_report(data_regression, generate_qeapp_workchain): data_regression.check(report) +@pytest.mark.usefixtures("sssp") def test_summary_report_advanced_settings(data_regression, generate_qeapp_workchain): """Test advanced settings are properly reported""" from aiidalab_qe.app.result.summary_viewer import SummaryView @@ -41,6 +62,7 @@ def test_summary_report_advanced_settings(data_regression, generate_qeapp_workch assert report["initial_magnetic_moments"]["Si"] == 0.1 +@pytest.mark.usefixtures("sssp") def test_summary_view(generate_qeapp_workchain): """Test the report html can be properly generated.""" from bs4 import BeautifulSoup