1
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2026-03-05 00:12:49 +01:00

refactor(ophyd-validation): Allow option to keep device visible after successful validation

This commit is contained in:
2026-01-08 09:21:11 +01:00
committed by Jan Wyzula
parent cbdeae15a1
commit 04a30ea04c
3 changed files with 101 additions and 47 deletions

View File

@@ -52,6 +52,10 @@ class DeviceManagerOphydValidationDialog(QtWidgets.QDialog):
# Load and apply configuration
config = config or {}
device_name = config.get("name", None)
if device_name:
self.device_manager_ophyd_test.add_device_to_keep_visible_after_validation(device_name)
self.device_manager_ophyd_test.change_device_configs([config], True, True)
# Dialog Buttons: equal size, stacked horizontally
@@ -332,15 +336,17 @@ class DeviceFormDialog(QtWidgets.QDialog):
):
"""Handle completion of validation."""
try:
if DeviceModel.model_validate(device_config) == DeviceModel.model_validate(
self._validation_result[0]
if (
DeviceModel.model_validate(device_config)
== DeviceModel.model_validate(self._validation_result[0])
and connection_status == ConnectionStatus.UNKNOWN.value
):
config_status = self._validation_result[1]
# Config unchanged, we can reuse previous connection status. Only do this if the new
# connection status is UNKNOWN as the current validation should not test the connection.
connection_status = self._validation_result[2]
validation_msg = self._validation_result[3]
except Exception:
logger.debug(
f"Device config validation changed for config: {device_config} compared to previous validation. Using status from recent validation ."
f"Device config validation changed for config: {device_config} compared to previous validation. Using status from recent validation."
)
self._validation_result = (device_config, config_status, connection_status, validation_msg)
if self._wait_dialog is not None:
@@ -351,7 +357,6 @@ class DeviceFormDialog(QtWidgets.QDialog):
def _add_config(self):
"""
Adding a config will always run a validation check of the config without a connection test.
We will check if tests have already run, and reuse the information in case they also tested the connection to the device.
"""
@@ -371,33 +376,34 @@ class DeviceFormDialog(QtWidgets.QDialog):
# We will show a wait dialog while this is happening, and compare the results with the last known validation results.
# If the config is unchanged, we will use the connection status results from the last validation.
self._wait_dialog = self._create_validation_dialog()
ophyd_validation: OphydValidation | None = None
try:
ophyd_validation = self._create_and_run_ophyd_validation(config)
ophyd_validation = self._create_and_run_ophyd_validation(config)
# NOTE If dialog was already closed, this means that a validation callback was already received
# which closed the dialog. In this case, we skip exec to avoid deadlock. With the singleShot above,
# this should not happen, but we keep the check for safety.
if self._wait_dialog is not None:
self._wait_dialog.exec() # This will block until the validation is complete
# NOTE If dialog was already close, this means that a validation callback was already received
# which closed the dialog. In this case, we skip exec to avoid deadlock. With the singleShot above,
# this should not happen, but we keep the check for safety.
if self._wait_dialog is not None:
self._wait_dialog.exec() # This will block until the validation is complete
config, config_status, connection_status, validation_msg = self._validation_result
config, config_status, connection_status, validation_msg = self._validation_result
if config_status == ConfigStatus.INVALID.value:
msg_box = self._create_warning_message_box(
"Invalid Device Configuration",
f"Device configuration is invalid. Last known validation message:\n\nErrors:\n{self._validation_result[3]}",
)
msg_box.exec()
return
if config_status == ConfigStatus.INVALID.value:
msg_box = self._create_warning_message_box(
"Invalid Device Configuration",
f"Device configuration is invalid. Last known validation message:\n\nErrors:\n{self._validation_result[3]}",
self.accepted_data.emit(
config, config_status, connection_status, validation_msg, self._old_device_name
)
msg_box.exec()
ophyd_validation.close()
ophyd_validation.deleteLater()
return
self.accepted_data.emit(
config, config_status, connection_status, validation_msg, self._old_device_name
)
self.accept()
ophyd_validation.close()
ophyd_validation.deleteLater()
self.accept()
finally:
if ophyd_validation is not None:
ophyd_validation.close()
ophyd_validation.deleteLater()
def _create_warning_message_box(self, title: str, text: str) -> QtWidgets.QMessageBox:
msg_box = QtWidgets.QMessageBox(self)

View File

@@ -312,6 +312,7 @@ class OphydValidation(BECWidget, QtWidgets.QWidget):
def __init__(self, parent=None, client=None, hide_legend: bool = False):
super().__init__(parent=parent, client=client, theme_update=True)
self._running_ophyd_tests = False
self._keep_visible_after_validation: list[str] = []
if not READY_TO_TEST:
self.setDisabled(True)
self.thread_pool_manager = None
@@ -339,6 +340,25 @@ class OphydValidation(BECWidget, QtWidgets.QWidget):
self._main_layout.addWidget(legend_widget)
self._thread_pool_poll_loop()
def add_device_to_keep_visible_after_validation(self, device_name: str) -> None:
"""Add a device name to the list of devices to keep visible after validation.
Args:
device_name (str): Name of the device to keep visible.
"""
if device_name not in self._keep_visible_after_validation:
self._keep_visible_after_validation.append(device_name)
def remove_device_to_keep_visible_after_validation(self, device_name: str) -> None:
"""Remove a device name from the list of devices to keep visible after validation.
Args:
device_name (str): Name of the device to remove.
"""
if device_name in self._keep_visible_after_validation:
self._keep_visible_after_validation.remove(device_name)
self._remove_device(device_name)
def apply_theme(self, theme: str):
"""Apply the current theme to the widget."""
self._colors = get_accent_colors()
@@ -507,6 +527,8 @@ class OphydValidation(BECWidget, QtWidgets.QWidget):
connect (bool, optional): Whether to attempt connection during validation. Defaults to False.
force_connect (bool, optional): Whether to force connection during validation. Defaults to False.
timeout (float, optional): Timeout for connection attempt. Defaults to 5.0.
skip_validation (bool, optional): Whether to skip validation for the added devices. Defaults to False.
keep_device_item_in_list (bool, optional): Whether to keep the device item in the list after validation in success case.
"""
if not READY_TO_TEST:
logger.error("Cannot change device configs: dependencies not available.")
@@ -535,6 +557,20 @@ class OphydValidation(BECWidget, QtWidgets.QWidget):
"Device already in session.",
)
)
if device_name in self._keep_visible_after_validation:
self._add_device_config(
cfg,
connect=connect,
force_connect=force_connect,
timeout=timeout,
skip_validation=True,
)
self._on_device_test_completed(
cfg,
ConfigStatus.VALID.value,
ConnectionStatus.CONNECTED.value,
"Device already in session.",
)
self._remove_device_config(cfg)
continue
if not self._device_already_exists(cfg.get("name")): # New device case
@@ -596,7 +632,12 @@ class OphydValidation(BECWidget, QtWidgets.QWidget):
return device_name in self.list_widget
def _add_device_config(
self, device_config: dict[str, Any], connect: bool, force_connect: bool, timeout: float
self,
device_config: dict[str, Any],
connect: bool,
force_connect: bool,
timeout: float,
skip_validation: bool = False,
) -> None:
device_name = device_config.get("name")
# Check if device is in redis session with same config, if yes don't even bother testing..
@@ -611,22 +652,28 @@ class OphydValidation(BECWidget, QtWidgets.QWidget):
)
widget.request_rerun_validation.connect(self._on_request_rerun_validation)
self.list_widget.add_widget_item(device_name, widget)
self.__delayed_submit_test(widget, connect, force_connect, timeout)
if skip_validation is False:
self.__delayed_submit_test(widget, connect, force_connect, timeout)
def _remove_device_config(self, device_config: dict[str, Any]) -> None:
device_name = device_config.get("name")
if not device_name:
logger.error(f"Device config missing 'name': {device_config}. Cannot remove device.")
return
def _remove_device(self, device_name: str) -> None:
if not self._device_already_exists(device_name):
logger.debug(
f"Device with name {device_name} not found in OphydValidation, can't remove it."
)
return
if device_name in self._keep_visible_after_validation:
logger.debug(
f"Device with name {device_name} is set to be kept visible after validation, not removing it."
)
return
if self.thread_pool_manager:
self.thread_pool_manager.clear_device_in_queue(device_name)
self.list_widget.remove_widget_item(device_name)
def _remove_device_config(self, device_config: dict[str, Any]) -> None:
device_name = device_config.get("name")
self._remove_device(device_name)
@SafeSlot(str, dict, bool, bool, float)
def _on_request_rerun_validation(
self,
@@ -712,16 +759,6 @@ class OphydValidation(BECWidget, QtWidgets.QWidget):
if not self._device_already_exists(device_name):
logger.debug(f"Received test result for unknown device {device_name}. Ignoring.")
return
if config_status == ConfigStatus.VALID.value and connection_status in [
ConnectionStatus.CONNECTED.value,
ConnectionStatus.CAN_CONNECT.value,
]:
# Validated successfully, remove item from running list
self.list_widget.remove_widget_item(device_name)
self.validation_completed.emit(
device_config, config_status, connection_status, error_message
)
return
widget = self.list_widget.get_widget(device_name)
if widget:
widget.on_validation_finished(
@@ -729,6 +766,12 @@ class OphydValidation(BECWidget, QtWidgets.QWidget):
config_status=config_status,
connection_status=connection_status,
)
if config_status == ConfigStatus.VALID.value and connection_status in [
ConnectionStatus.CONNECTED.value,
ConnectionStatus.CAN_CONNECT.value,
]:
# Validated successfully, remove item from running list
self._remove_device(device_name)
self.validation_completed.emit(
device_config, config_status, connection_status, error_message
)

View File

@@ -652,7 +652,7 @@ class TestDeviceManagerView:
mock_add.assert_called_once()
def test_run_validate_connection_action_connected(
self, device_manager_display_widget: DeviceManagerDisplayWidget, device_configs: dict
self, device_manager_display_widget: DeviceManagerDisplayWidget, device_configs: dict, qtbot
):
"""Test run validate connection action is connected."""
dm_view = device_manager_display_widget
@@ -661,7 +661,12 @@ class TestDeviceManagerView:
dm_view.ophyd_test_view, "change_device_configs"
) as mock_change_configs:
# First, add device configs to the table
dm_view.device_table_view.add_device_configs(device_configs)
with qtbot.waitSignal(dm_view.device_table_view.device_configs_changed) as sig_blocker:
dm_view.device_table_view.add_device_configs(device_configs)
cfgs, added, skip_validation = sig_blocker.args
assert cfgs == device_configs
assert added is True
assert skip_validation is False
mock_change_configs.assert_called_once_with(
device_configs=device_configs, added=True, skip_validation=False
) # Configs were added