Skip to content

Fix blockers system - #1513

Merged
edan-bainglass merged 3 commits into
aiidalab:mainfrom
edan-bainglass:fix-blockers
Jun 2, 2026
Merged

Fix blockers system#1513
edan-bainglass merged 3 commits into
aiidalab:mainfrom
edan-bainglass:fix-blockers

Conversation

@edan-bainglass

@edan-bainglass edan-bainglass commented May 22, 2026

Copy link
Copy Markdown
Member

This PR fixes various issues with the blockers system. It also extends the mechanism to all models, for furture support.
In passing, the PR fixes a few inconsistencies with state handling and component updating.

For context, a blocking system was introduced a while back to the app to allow blocking wizard steps for any given reason. For example, the submission step is blocked if QE or its codes are still being installed. Since, the system was expanded in the configuration step to block for unreasonable configuration. This is implemented in a way that allows each panel model to define its own blocker(s). However, the system was incomplete, evident by various inconsistencies and bugs discovered along the way. This PR aims to resolve these issues, as well as introduce BLOCKED as a new actionable wizard step state, so that we can better track blocking events.

@codecov

codecov Bot commented May 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.75000% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.26%. Comparing base (dc0ae7d) to head (d8b8b03).

Files with missing lines Patch % Lines
src/aiidalab_qe/common/mixins.py 71.42% 4 Missing ⚠️
src/aiidalab_qe/app/configuration/model.py 75.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1513      +/-   ##
==========================================
- Coverage   75.35%   75.26%   -0.10%     
==========================================
  Files         115      115              
  Lines        7378     7381       +3     
==========================================
- Hits         5560     5555       -5     
- Misses       1818     1826       +8     
Flag Coverage Δ
python-3.12 75.26% <93.75%> (-0.10%) ⬇️
python-3.9 75.29% <93.75%> (-0.10%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

include = True # build-in panel

def update(self, specific=""):
def _update(self, specific=""):

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking change that has already been addressed in the only plugin that used it (aiidaplugins/aiida-qe-xspec#37)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this method is being used in the plugins, why make it private?

self._toggle_eigenvalues_widget()

def _update(self):
def _update_ui(self):

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above

Comment on lines +129 to +130
if self.is_blocked:
self.state = State.BLOCKED

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking a step is now an official state we can react to. We use this to also change the color of a blocked step to red (same as failed) to better alert the user of a blocking issue.

else:
info.value = ""
model.update()
panel.refresh()

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of just triggering a model update, we trigger a full panel refresh, which unlinks any widgets before updating the model, so that in case a model update triggers a UI update, we don't end up with orphan links.

Comment on lines +89 to +92
tl.dlink(
(model, "blockers"),
(self, "blockers"),
)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HasBlockers is now placed on the root Model, so any Model can be blocked. Hence, there is no longer a need to check this on a given model.


def update_blockers(self):
blockers = list(self._check_blockers())
blockers = list(self._check_blockers() or [])

@edan-bainglass edan-bainglass May 30, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_check_blockers yields str blockers, or None if no blockers are yielded. The latter scenario cannot be used in list, so we guard here with or []. list([]) is just [] 👍

Comment on lines +189 to +194
def reset_blockers(self):
self.blockers = []
if isinstance(self, HasModels):
for _, model in self.get_models():
if isinstance(model, HasBlockers):
model.reset_blockers()

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general good to have a reset for the blockers register, but more practically, this is used when refreshing panels.

Comment on lines +23 to +27
class Model(
tl.HasTraits,
HasBlockers,
metaclass=MetaHasTraitsLast,
):

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extending the blocking mecahnism to all models!

Comment on lines -35 to -36
`updated` : `bool`
Whether the model has been updated.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused, but also overcomplicates things at minimal-to-no gain.

Comment on lines +68 to +75
if self.locked or specific == "widgets":
return
self._update(specific)
self.update_blockers()
self.update_state()

def update_state(self):
"""Update the model state."""

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generalizing the concept of refreshing a model at the root Model level.

Comment on lines +90 to +92
def _update(self, specific=""):
"""Internal method to update the model."""
pass

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Within the generalization, this allows each specific Model subclass to define what it means by "updating", in addition to the general part.

super().__init__(*args, **kwargs)


CSM = t.TypeVar("CSM", bound=ConfigurationSettingsModel)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missed this in a previous PR deprecating ConfigurationSettingsModel

Comment on lines +47 to +49
def update_blockers(self):
if self.include:
super().update_blockers()

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For PanelModel instances, we want to update the blocking system ONLY if the panel is included.

Comment thread pyproject.toml

[tool.ruff.lint]
ignore = ["E501", "E402", "TRY003", "RUF012", "N806"]
ignore = ["ARG002", "E501", "E402", "TRY003", "RUF012", "N806"]

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ARG002 to avoid ruff complaining about the often unused specific argument of update

@edan-bainglass

Copy link
Copy Markdown
Member Author

@AndresOrtegaGuerrero just finished a self-review. Hope it helps you review this medium PR 🙂 Happy to answer any questions 🙏

@danielhollas danielhollas left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just had a quick look, seems reasonable, but I am not really familiar with the current QeApp codebase.

@yakutovicha yakutovicha left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed the PR in a call, the changes make sense to me.

@edan-bainglass
edan-bainglass merged commit c5645aa into aiidalab:main Jun 2, 2026
9 checks passed
edan-bainglass added a commit that referenced this pull request Jun 6, 2026
To prevent users from submitting calculations with unconfigured codes,
we filter them out. However, this SHOULD NOT happen when loading a 
process, since the user cannot submit in that state. But it was, so
step 3 was emitting blockers due to unselected codes. Prior to #1513,
the blockers didn't do their job of blocking the step, but now they do.
So if someone loads the app from a process that was run by someone
else (e.g., a downloaded example), step 3 is blocked and step 4 never
loads. This PR adds a `filter_codes_for_user` flag injected through the
resources refresh mechanism that is generally `True` but set to `False`
if we're loading a process.

In addition, the PR relaxes default code handling, falling back on the
first available code option if the default code does not exist.
Finally, the PR removes the blocking of step 3 w.r.t the local QE
installation status, since users should still be allowed to proceed
with remote submission.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants