From 84bed162a21b466f7c59a66e95f116b52a9852ea Mon Sep 17 00:00:00 2001 From: Carl Simon Adorf Date: Thu, 23 Sep 2021 09:05:33 +0200 Subject: [PATCH] Support not registered apps and improve handling of registry server cannot be reached (#71) * Fix warning message for unknown update status. * Handle unregistered apps and registry cannot be reached. --- home/app_manager.py | 30 +++++++++++++++++++++--------- home/app_store.py | 10 ++++++++-- home/start_page.py | 2 +- home/themes.py | 2 ++ home/widgets.py | 36 ++++++++++++++++++------------------ 5 files changed, 50 insertions(+), 30 deletions(-) diff --git a/home/app_manager.py b/home/app_manager.py index a6a5ccc..c0ffbbb 100644 --- a/home/app_manager.py +++ b/home/app_manager.py @@ -5,6 +5,7 @@ import ipywidgets as ipw import traitlets +from aiidalab.app import AppRemoteUpdateStatus as AppStatus from aiidalab.app import AppVersion from jinja2 import Template from packaging.version import parse @@ -240,6 +241,10 @@ def _refresh_widget_state(self, _=None): installed = self.app.is_installed() installed_version = self.app.installed_version compatible = len(self.app.available_versions) > 0 + registered = self.app.remote_update_status is not AppStatus.NOT_REGISTERED + cannot_reach_registry = ( + self.app.remote_update_status is AppStatus.CANNOT_REACH_REGISTRY + ) busy = self.app.busy detached = self.app.detached available_versions = self.app.available_versions @@ -248,7 +253,9 @@ def _refresh_widget_state(self, _=None): blocked_install = ( detached or not compatible ) and not self.blocked_ignore.value - blocked_uninstall = detached and not self.blocked_ignore.value + blocked_uninstall = ( + detached or not registered or cannot_reach_registry + ) and not self.blocked_ignore.value # Check app compatibility and show banner if not compatible. self.compatibility_warning.layout.visibility = ( @@ -261,11 +268,9 @@ def _refresh_widget_state(self, _=None): # These messages and icons are only shown if needed. warn_or_ban_icon = "warning" if override else "ban" if override: - tooltip_danger = ( - "Operation will lead to potential loss of local modifications!" - ) + tooltip_danger = "Operation will lead to potential loss of local data!" else: - tooltip_danger = "Operation blocked due to local modifications." + tooltip_danger = "Operation blocked due to potential data loss." tooltip_incompatible = "The app is not supported for this environment." # Determine whether we can install, updated, and uninstall. @@ -279,7 +284,10 @@ def _refresh_widget_state(self, _=None): ) or not installed can_uninstall = installed try: - can_update = self.app.updates_available and installed + can_update = ( + self.app.remote_update_status is AppStatus.UPDATE_AVAILABLE + and installed + ) except RuntimeError: can_update = None @@ -362,10 +370,14 @@ def _refresh_widget_state(self, _=None): ) # Indicate whether there are local modifications and present option for user override. - if detached: + if cannot_reach_registry: + self.issue_indicator.value = f' Unable to reach the registry server.' + elif not registered: + self.issue_indicator.value = f' The app is not registered.' + elif detached: self.issue_indicator.value = ( - f' The app is modified or the installed version ' - "is not on the specified release line." + f' The app has local modifications or was checked out ' + "to an unknown version." ) elif not compatible: self.issue_indicator.value = f' The app is not supported for this environment.' diff --git a/home/app_store.py b/home/app_store.py index 17ab85b..58aaf61 100644 --- a/home/app_store.py +++ b/home/app_store.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- """AiiDAlab app store.""" +import logging import ipywidgets as ipw from aiidalab.app import AiidaLabApp @@ -9,13 +10,18 @@ from home.app_manager import AppManagerWidget +logger = logging.getLogger(__name__) + class AiidaLabAppStore(ipw.HBox): """Class to manage AiiDAlab app store.""" def __init__(self): - # TODO: Improve fallback implementation! - self.index = load_app_registry_index() or dict(apps=[], categories=[]) + try: + self.index = load_app_registry_index() + except RuntimeError as error: + logger.warning(error) + self.index = dict(apps=[], categories=[]) self.output = ipw.Output() # Apps per page. diff --git a/home/start_page.py b/home/start_page.py index 1b02fdc..b58ce97 100644 --- a/home/start_page.py +++ b/home/start_page.py @@ -153,7 +153,7 @@ def __init__(self, app, allow_move=False, allow_manage=True): if allow_manage: app_status_info = AppStatusInfoWidget() - for trait in ("detached", "compatible", "updates_available"): + for trait in ("detached", "compatible", "remote_update_status"): ipw.dlink((app, trait), (app_status_info, trait)) app_status_info.layout.margin = "0px 0px 0px 800px" header_items.append(app_status_info) diff --git a/home/themes.py b/home/themes.py index 6690555..d97dd86 100644 --- a/home/themes.py +++ b/home/themes.py @@ -7,11 +7,13 @@ class IconSetDefault: CHAIN_BROKEN = '' LOADING = '' ARROW_CIRCLE_UP = '' + FOLDER = '' # App states (general) APP_DETACHED = CHAIN_BROKEN APP_INCOMPATIBLE = TIMES_CIRCLE APP_VERSION_INCOMPATIBLE = WARNING + APP_NOT_REGISTERED = FOLDER # App states (updates) APP_NO_UPDATE_AVAILABLE = CHECK diff --git a/home/widgets.py b/home/widgets.py index 5b0fa32..e08d133 100644 --- a/home/widgets.py +++ b/home/widgets.py @@ -5,6 +5,8 @@ import ipywidgets as ipw import traitlets +from aiidalab.app import AppRemoteUpdateStatus as AppStatus +from aiidalab.config import AIIDALAB_REGISTRY from .themes import ThemeDefault as Theme @@ -74,26 +76,23 @@ class AppStatusInfoWidget(ipw.HTML): detached = traitlets.Bool(allow_none=True) compatible = traitlets.Bool(allow_none=True) - updates_available = traitlets.Bool(allow_none=True) + remote_update_status = traitlets.UseEnum(AppStatus) MESSAGE_INIT = f"
{Theme.ICONS.LOADING} Loading...
" - TOOLTIP_DETACHED = ( + TOOLTIP_APP_DETACHED = ( "The app is in a detached state - likely due to local modifications - " "which means the ability to manage the app via the AiiDAlab interface is reduced." ) - MESSAGE_DETACHED = ( - f'
' - f"{Theme.ICONS.APP_DETACHED} Modified
" - ) - TOOLTIP_APP_INCOMPATIBLE = ( "None of the available app versions are compatible with this AiiDAlab environment. " "You can continue using this app, but be advised that you might encounter " "compatibility issues." ) + TOOLTIP_APP_NOT_REGISTERED = "This app is not registered." + MESSAGE_APP_INCOMPATIBLE = ( f'
' f"{Theme.ICONS.APP_INCOMPATIBLE} App incompatible
" @@ -106,14 +105,17 @@ class AppStatusInfoWidget(ipw.HTML): ) MESSAGES_UPDATES = { - None: '
" - f'{Theme.ICONS.APP_UPDATE_AVAILABLE_UNKNOWN} ' - "Unable to determine availability of updates.
", - True: '
' + AppStatus.CANNOT_REACH_REGISTRY: f'
' + f'{Theme.ICONS.APP_UPDATE_AVAILABLE_UNKNOWN} ' + "Cannot reach server.
", + AppStatus.UPDATE_AVAILABLE: '
' f'{Theme.ICONS.APP_UPDATE_AVAILABLE} Update available
', - False: '
' + AppStatus.UP_TO_DATE: '
' f'{Theme.ICONS.APP_NO_UPDATE_AVAILABLE} Latest version
', + AppStatus.DETACHED: f'
' + f"{Theme.ICONS.APP_DETACHED} Modified
", + AppStatus.NOT_REGISTERED: f'
' + f"{Theme.ICONS.APP_NOT_REGISTERED} Not registered
", } def __init__(self, value=None, **kwargs): @@ -121,16 +123,14 @@ def __init__(self, value=None, **kwargs): value = self.MESSAGE_INIT super().__init__(value=value, **kwargs) self.observe( - self._refresh, names=["detached", "compatible", "updates_available"] + self._refresh, names=["detached", "compatible", "remote_update_status"] ) def _refresh(self, _=None): - if self.detached is True: - self.value = self.MESSAGE_DETACHED - elif self.compatible is False: + if self.compatible is False: self.value = self.MESSAGE_APP_INCOMPATIBLE else: - self.value = self.MESSAGES_UPDATES[self.updates_available] + self.value = self.MESSAGES_UPDATES[self.remote_update_status] class Spinner(ipw.HTML):