feat: add support for enabling/disabling experimental features in the app

This commit is contained in:
2026-06-29 09:40:58 +02:00
committed by wakonig_k
parent f608659e9d
commit 9282522358
4 changed files with 98 additions and 2 deletions
@@ -4,6 +4,8 @@ Utilities for managing BECDockArea profiles stored in INI files.
Policy:
- All created/modified profiles are stored under the BEC settings root:
<base_path>/profiles/{baseline,runtime}
- App-wide dock-area metadata and feature flags are stored in
<base_path>/profiles/_meta.ini.
- Bundled read-only baselines are discovered in BW core profiles and plugin
bec_widgets/profiles but never written to.
- Lookup order when reading: runtime → settings baseline → app or plugin bundled baseline.
@@ -463,6 +465,7 @@ SETTINGS_KEYS = {
"manifest": "manifest/widgets",
"created_at": "profile/created_at",
"is_quick_select": "profile/quick_select",
"experimental_features": "app/experimental_features",
"screenshot": "profile/screenshot",
"screenshot_at": "profile/screenshot_at",
"last_profile": "app/last_profile",
@@ -661,6 +664,28 @@ def set_last_profile(
s.remove(key)
def is_experimental_features_enabled() -> bool:
"""
Check whether experimental features are enabled globally.
Returns:
bool: ``True`` when the global experimental flag is enabled.
"""
s = _app_settings()
return s.value(SETTINGS_KEYS["experimental_features"], False, type=bool)
def set_experimental_features_enabled(enabled: bool) -> None:
"""
Persist the global experimental-features flag.
Args:
enabled (bool): ``True`` to enable experimental features, ``False`` to disable.
"""
s = _app_settings()
s.setValue(SETTINGS_KEYS["experimental_features"], bool(enabled))
def now_iso_utc() -> str:
"""
Return the current UTC timestamp formatted in ISO 8601.
@@ -12,6 +12,7 @@ from qtpy.QtWidgets import (
QHBoxLayout,
QLabel,
QMainWindow,
QMessageBox,
QStyle,
QVBoxLayout,
QWidget,
@@ -22,6 +23,10 @@ from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.colors import apply_theme
from bec_widgets.utils.error_popups import SafeSlot
from bec_widgets.utils.ui_loader import UILoader
from bec_widgets.widgets.containers.dock_area.profile_utils import (
is_experimental_features_enabled,
set_experimental_features_enabled,
)
from bec_widgets.widgets.containers.main_window.addons.hover_widget import HoverWidget
from bec_widgets.widgets.containers.main_window.addons.notification_center.notification_banner import (
BECNotificationBroker,
@@ -343,6 +348,12 @@ class BECMainWindow(BECWidget, QMainWindow):
widget_tree_action.triggered.connect(self._show_widget_hierarchy_dialog)
theme_menu.addAction(widget_tree_action)
experimental_features_action = QAction("Enable Experimental Features", self, checkable=True)
experimental_features_action.setChecked(is_experimental_features_enabled())
experimental_features_action.toggled.connect(self._toggle_experimental_features)
theme_menu.addAction(experimental_features_action)
self._experimental_features_action = experimental_features_action
# Set the default theme
if hasattr(self.app, "theme") and self.app.theme:
theme_name = self.app.theme.theme.lower()
@@ -417,6 +428,19 @@ class BECMainWindow(BECWidget, QMainWindow):
"""
apply_theme(theme) # emits theme_updated and applies palette globally
@SafeSlot(bool)
def _toggle_experimental_features(self, enabled: bool):
set_experimental_features_enabled(enabled)
reply = QMessageBox.question(
self,
"Restart Required",
"Experimental features require a restart to take effect. Restart the application now?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No,
)
if reply == QMessageBox.StandardButton.Yes:
self.app.quit()
def _show_widget_hierarchy_dialog(self):
if self._widget_hierarchy_dialog is None:
dialog = WidgetHierarchyDialog(root_widget=None, parent=self)
+16
View File
@@ -22,6 +22,7 @@ from bec_widgets.widgets.containers.dock_area.profile_utils import (
SETTINGS_KEYS,
baseline_profile_path,
get_profile_info,
is_experimental_features_enabled,
is_profile_read_only,
is_quick_select,
list_profiles,
@@ -32,6 +33,7 @@ from bec_widgets.widgets.containers.dock_area.profile_utils import (
read_manifest,
restore_runtime_from_baseline,
runtime_profile_path,
set_experimental_features_enabled,
set_quick_select,
write_manifest,
)
@@ -1443,6 +1445,20 @@ class TestWorkSpaceManager:
assert is_quick_select(name) is (not initial)
assert target.refresh_calls >= 1
def test_global_experimental_features_flag_persists(self):
assert is_experimental_features_enabled() is False
set_experimental_features_enabled(True)
assert is_experimental_features_enabled() is True
meta_path = os.path.join(os.environ["BECWIDGETS_PROFILE_DIR"], "_meta.ini")
assert (
QSettings(meta_path, QSettings.IniFormat).value(
SETTINGS_KEYS["experimental_features"], type=bool
)
is True
)
def test_save_current_as_profile_with_target(self, qtbot, workspace_manager_target):
name = "profile_save"
self._create_profiles([name])
+33 -2
View File
@@ -1,12 +1,14 @@
import os
import webbrowser
from types import SimpleNamespace
from unittest.mock import MagicMock, patch
import pytest
from qtpy.QtCore import QEvent, QPoint, QPointF
from qtpy.QtCore import QEvent, QPoint, QPointF, QSettings
from qtpy.QtGui import QEnterEvent
from qtpy.QtWidgets import QApplication, QFrame, QLabel
from qtpy.QtWidgets import QApplication, QFrame, QLabel, QMessageBox
from bec_widgets.widgets.containers.dock_area.profile_utils import SETTINGS_KEYS
from bec_widgets.widgets.containers.main_window.addons.hover_widget import (
HoverWidget,
WidgetTooltip,
@@ -112,6 +114,35 @@ def test_hidden_scan_progress_parent_blocks_children_namespace(bec_main_window):
assert nested_progress.parent_id == hidden_progress.gui_id
def test_experimental_features_menu_action_persists_flag(
monkeypatch, tmp_path, qtbot, mocked_client
):
monkeypatch.setenv("BECWIDGETS_PROFILE_DIR", str(tmp_path))
widget = BECMainWindow(client=mocked_client)
qtbot.addWidget(widget)
qtbot.waitExposed(widget)
action = widget._experimental_features_action
assert action.isCheckable()
assert action.isChecked() is False
quit_calls = []
monkeypatch.setattr(widget.app, "quit", lambda: quit_calls.append(True))
monkeypatch.setattr(QMessageBox, "question", lambda *args, **kwargs: QMessageBox.Yes)
action.setChecked(True)
action.setChecked(False)
meta_path = os.path.join(str(tmp_path), "_meta.ini")
assert (
QSettings(meta_path, QSettings.Format.IniFormat).value(
SETTINGS_KEYS["experimental_features"], type=bool
)
is False
)
assert quit_calls == [True, True]
#################################################################
# Tests for BECMainWindow Addons
#################################################################