From 735326498418c58697f8dc05457263ef9528d2c0 Mon Sep 17 00:00:00 2001 From: Edan Bainglass <45081142+edan-bainglass@users.noreply.github.com> Date: Thu, 9 Jan 2025 10:23:11 +0100 Subject: [PATCH] Fix resource setup widget bugs (#661) - Add additional guards for missing code setup input - Disable quick setup button if missing any requisites - Apply uniqueness to missing template variables warning - Make `ResourceSetupBaseWidget` "public" - Fix code full label uniqueness check Note that the fix in the query that looks for existing codes is a hack due to a potential bug in aiida-core. See https://github.com/aiidateam/aiida-core/issues/6687. --- .../computational_resources.py | 28 ++++++++++++++++--- aiidalab_widgets_base/databases.py | 10 +++++++ aiidalab_widgets_base/utils/__init__.py | 2 +- tests/test_computational_resources.py | 12 ++++---- 4 files changed, 41 insertions(+), 11 deletions(-) diff --git a/aiidalab_widgets_base/computational_resources.py b/aiidalab_widgets_base/computational_resources.py index 393a61a19..f1dd6e00e 100644 --- a/aiidalab_widgets_base/computational_resources.py +++ b/aiidalab_widgets_base/computational_resources.py @@ -119,7 +119,7 @@ def __init__( children.append(self._setup_new_code_output) # Computer/code setup - self.resource_setup = _ResourceSetupBaseWidget( + self.resource_setup = ResourceSetupBaseWidget( default_calc_job_plugin=self.default_calc_job_plugin, enable_quick_setup=enable_quick_setup, enable_detailed_setup=enable_detailed_setup, @@ -1193,6 +1193,13 @@ def on_setup_code(self, _=None): with self.setup_code_out: clear_output() + if not self.label.value: + self.message = wrap_message( + "Please provide a code label.", + MessageLevel.WARNING, + ) + return False + if not self.computer.value: self.message = wrap_message( "Please select an existing computer.", @@ -1229,7 +1236,7 @@ def on_setup_code(self, _=None): qb = orm.QueryBuilder() qb.append(orm.Computer, filters={"uuid": computer.uuid}, tag="computer") qb.append( - orm.AbstractCode, + orm.Code, with_computer="computer", filters={"label": kwargs["label"]}, ) @@ -1612,7 +1619,7 @@ def _on_template_variable_filled(self, _): self.fill() -class _ResourceSetupBaseWidget(ipw.VBox): +class ResourceSetupBaseWidget(ipw.VBox): """The widget that allows to setup a computer and code. This is the building block of the `ComputationalResourcesDatabaseWidget` which will be directly used by the user. @@ -1659,6 +1666,11 @@ def __init__( default_calc_job_plugin=default_calc_job_plugin, show_reset_button=False, ) + ipw.dlink( + (self.comp_resources_database, "configured"), + (self.quick_setup_button, "disabled"), + lambda configured: not configured, + ) # All templates self.template_computer_setup = TemplateVariablesWidget() @@ -1877,6 +1889,14 @@ def _on_quick_setup(self, _=None): ) return + # Raise error if the code is not selected. + if not self.comp_resources_database.code_selector.value: + self.message = wrap_message( + "Please select a code from the database.", + MessageLevel.ERROR, + ) + return + # Check if all the template variables are filled. # If not raise a warning and return (skip the setup). if ( @@ -1884,7 +1904,7 @@ def _on_quick_setup(self, _=None): + self.template_computer_configure.unfilled_variables + self.template_code.unfilled_variables ): - var_warn_message = ", ".join([f"{v}" for v in unfilled_variables]) + var_warn_message = ", ".join({f"{v}" for v in unfilled_variables}) self.message = wrap_message( f"Please fill the template variables: {var_warn_message}", MessageLevel.WARNING, diff --git a/aiidalab_widgets_base/databases.py b/aiidalab_widgets_base/databases.py index e3cf14bfd..48f1b9021 100644 --- a/aiidalab_widgets_base/databases.py +++ b/aiidalab_widgets_base/databases.py @@ -234,6 +234,8 @@ class ComputationalResourcesDatabaseWidget(ipw.VBox): computer_configure = tl.Dict() code_setup = tl.Dict() + configured = tl.Bool(False) + STYLE = {"description_width": "180px"} LAYOUT = {"width": "400px"} @@ -413,6 +415,8 @@ def _computer_changed(self, change=None): self.computer_setup = computer_setup self.computer_configure = computer_configure + self._set_configured() + def _code_changed(self, change=None): """Update code settings.""" if change["new"] is None: @@ -430,3 +434,9 @@ def _code_changed(self, change=None): .get("codes", {}) .get(selected_code, {}) ) + + self._set_configured() + + def _set_configured(self): + """Update state of the widget.""" + self.configured = all((self.computer_setup, self.code_setup)) diff --git a/aiidalab_widgets_base/utils/__init__.py b/aiidalab_widgets_base/utils/__init__.py index 8a6f53304..bea0e08f0 100644 --- a/aiidalab_widgets_base/utils/__init__.py +++ b/aiidalab_widgets_base/utils/__init__.py @@ -213,7 +213,7 @@ def wrap_message(message, level=MessageLevel.INFO): # The message is wrapped into a div with the class "alert" and the icon of the given level return f""" """ diff --git a/tests/test_computational_resources.py b/tests/test_computational_resources.py index 87a55fd6a..bc093da1e 100644 --- a/tests/test_computational_resources.py +++ b/tests/test_computational_resources.py @@ -7,7 +7,7 @@ from aiidalab_widgets_base import computational_resources from aiidalab_widgets_base.computational_resources import ( ComputationalResourcesWidget, - _ResourceSetupBaseWidget, + ResourceSetupBaseWidget, ) HTML_TAG_CLEANER = re.compile(r"<[^>]*>") @@ -496,11 +496,11 @@ def test_template_variables_widget_help_text_disappear_if_no_template_str(): def test_resource_setup_widget_default(): """Test the _ResourceSetupBaseWidget.""" with pytest.raises(ValueError): - w = _ResourceSetupBaseWidget( + w = ResourceSetupBaseWidget( enable_detailed_setup=False, enable_quick_setup=False ) - w = _ResourceSetupBaseWidget() + w = ResourceSetupBaseWidget() # Test message is update correctly. By click setup button without filling in any information. w._on_quick_setup() @@ -587,7 +587,7 @@ def test_resource_setup_widget_for_password_configure(monkeypatch, tmp_path): # monkeypatch home so the ssh key is generated in the temporary directory monkeypatch.setenv("HOME", str(tmp_path)) - w = _ResourceSetupBaseWidget() + w = ResourceSetupBaseWidget() # Test select a new resource setup will update the output interface (e.g. ssh_config, computer_setup, code_setup) # and the computer/code setup widget will be updated accordingly. @@ -654,7 +654,7 @@ def test_resource_setup_widget_for_password_configure(monkeypatch, tmp_path): @pytest.mark.usefixtures("aiida_profile_clean") def test_resource_setup_widget_computer_change_code_reset(): """Test the _ResourceSetupBaseWidget that when computer template changed, the code selector widget is reset.""" - w = _ResourceSetupBaseWidget() + w = ResourceSetupBaseWidget() # Test select a new resource setup will update the output interface (e.g. ssh_config, computer_setup, code_setup) # and the computer/code setup widget will be updated accordingly. @@ -673,7 +673,7 @@ def test_resource_setup_widget_computer_change_code_reset(): def test_resource_setup_widget_detailed_setup(): """Detail branch test of the resource setup widget""" - w = _ResourceSetupBaseWidget() + w = ResourceSetupBaseWidget() w.comp_resources_database.domain_selector.value = "daint.cscs.ch" w.comp_resources_database.computer_selector.value = "mc"