Compare commits

...

8 Commits

14 changed files with 112 additions and 37 deletions
+4 -1
View File
@@ -1,4 +1,7 @@
#!/usr/bin/env python3 ##########################
### AI-generated file. ###
##########################
"""Aggregate and merge benchmark JSON files. """Aggregate and merge benchmark JSON files.
The workflow runs the same benchmark suite on multiple independent runners. The workflow runs the same benchmark suite on multiple independent runners.
+4 -1
View File
@@ -1,4 +1,7 @@
#!/usr/bin/env python3 ##########################
### AI-generated file. ###
##########################
"""Compare benchmark JSON files and write a GitHub Actions summary. """Compare benchmark JSON files and write a GitHub Actions summary.
The script supports JSON emitted by hyperfine, JSON emitted by pytest-benchmark, The script supports JSON emitted by hyperfine, JSON emitted by pytest-benchmark,
+5
View File
@@ -1,4 +1,9 @@
#!/usr/bin/env bash #!/usr/bin/env bash
##########################
### AI-generated file. ###
##########################
set -euo pipefail set -euo pipefail
mkdir -p benchmark-results mkdir -p benchmark-results
+4 -1
View File
@@ -1,4 +1,7 @@
#!/usr/bin/env python3 ##########################
### AI-generated file. ###
##########################
"""Run a command with BEC e2e services available.""" """Run a command with BEC e2e services available."""
from __future__ import annotations from __future__ import annotations
+9 -6
View File
@@ -1,6 +1,6 @@
name: BW Benchmarks name: BW Benchmarks
on: [workflow_call] on: [ workflow_call ]
permissions: permissions:
contents: read contents: read
@@ -10,7 +10,7 @@ env:
BENCHMARK_BASELINE_JSON: gh-pages-benchmark-data/benchmarks/latest.json BENCHMARK_BASELINE_JSON: gh-pages-benchmark-data/benchmarks/latest.json
BENCHMARK_SUMMARY: benchmark-results/summary.md BENCHMARK_SUMMARY: benchmark-results/summary.md
BENCHMARK_COMMAND: "bash .github/scripts/run_benchmarks.sh" BENCHMARK_COMMAND: "bash .github/scripts/run_benchmarks.sh"
BENCHMARK_THRESHOLD_PERCENT: 10 BENCHMARK_THRESHOLD_PERCENT: 20
BENCHMARK_HIGHER_IS_BETTER: false BENCHMARK_HIGHER_IS_BETTER: false
jobs: jobs:
@@ -25,7 +25,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
attempt: [1, 2, 3] attempt: [ 1, 2, 3 ]
env: env:
BENCHMARK_JSON: benchmark-results/current-${{ matrix.attempt }}.json BENCHMARK_JSON: benchmark-results/current-${{ matrix.attempt }}.json
@@ -84,7 +84,7 @@ jobs:
path: ${{ env.BENCHMARK_JSON }} path: ${{ env.BENCHMARK_JSON }}
benchmark: benchmark:
needs: [benchmark_attempt] needs: [ benchmark_attempt ]
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: read contents: read
@@ -191,7 +191,7 @@ jobs:
run: exit 1 run: exit 1
publish: publish:
needs: [benchmark] needs: [ benchmark ]
if: github.event_name == 'push' && github.ref == 'refs/heads/main' if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
@@ -208,7 +208,10 @@ jobs:
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: bw-benchmark-json name: bw-benchmark-json
path: . path: benchmark-results
- name: Verify aggregate benchmark artifact
run: test -s "$BENCHMARK_JSON"
- name: Prepare gh-pages for publishing - name: Prepare gh-pages for publishing
run: | run: |
+47
View File
@@ -1,6 +1,53 @@
# CHANGELOG # CHANGELOG
## v3.7.2 (2026-04-29)
### Bug Fixes
- **dock-area**: Avoid switching profile when saving new profile
([`73b44cf`](https://github.com/bec-project/bec_widgets/commit/73b44cffb219347cacb609f3b93068eda6701b42))
- **workspace-actions**: Use try/finally and restore previous blocked state in refresh_profiles
([`30ef255`](https://github.com/bec-project/bec_widgets/commit/30ef25533af9df5a9bd9e69808dc49fdf22f4318))
Agent-Logs-Url:
https://github.com/bec-project/bec_widgets/sessions/004cb4bc-5015-485e-a803-1e63876b7024
Co-authored-by: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com>
### Build System
- Add pytest-benchmark dependency
([`551d38d`](https://github.com/bec-project/bec_widgets/commit/551d38d90111361e64bfcf10a1409be71dd298bc))
### Chores
- Update header comments in script files to indicate AI generation
([`3f1aa80`](https://github.com/bec-project/bec_widgets/commit/3f1aa80756368454c3631cb6cf9d29db28af177a))
### Continuous Integration
- Add benchmark workflow
([`999b7a2`](https://github.com/bec-project/bec_widgets/commit/999b7a2321f2f222c04b056a2db4280f66de9c48))
- Fix benchmark upload
([`19b5c8f`](https://github.com/bec-project/bec_widgets/commit/19b5c8f724dbdb7421957c290f9213bf072392df))
- Increase threshold to 20 percent
([`409c9e5`](https://github.com/bec-project/bec_widgets/commit/409c9e5bfacdfc003f1bc8c9944f01798bd3818e))
### Testing
- Fix assertions after updating ophyd devices templates
([`a614d66`](https://github.com/bec-project/bec_widgets/commit/a614d662d6da716a49afb4ed3a3903f108210386))
Co-authored-by: Copilot <copilot@github.com>
- Remove references to "scan_motors" in tests
([`5056ef8`](https://github.com/bec-project/bec_widgets/commit/5056ef8946d03a20e802709d3fe81c84c195fe41))
## v3.7.1 (2026-04-21) ## v3.7.1 (2026-04-21)
### Bug Fixes ### Bug Fixes
@@ -235,11 +235,8 @@ class BECDockArea(DockAreaWidget):
def _load_initial_profile(self, name: str) -> None: def _load_initial_profile(self, name: str) -> None:
"""Load the initial profile.""" """Load the initial profile."""
self.load_profile(name) self.load_profile(name)
combo = self.toolbar.components.get_action("workspace_combo").widget
combo.blockSignals(True)
if not self._empty_profile_active: if not self._empty_profile_active:
combo.setCurrentText(name) self._set_workspace_combo_text_silent(name)
combo.blockSignals(False)
def _start_empty_workspace(self) -> None: def _start_empty_workspace(self) -> None:
""" """
@@ -669,6 +666,14 @@ class BECDockArea(DockAreaWidget):
combo = self.toolbar.components.get_action("workspace_combo").widget combo = self.toolbar.components.get_action("workspace_combo").widget
combo.refresh_profiles(active_profile=name) combo.refresh_profiles(active_profile=name)
def _set_workspace_combo_text_silent(self, text: str) -> None:
combo = self.toolbar.components.get_action("workspace_combo").widget
was_blocked = combo.blockSignals(True)
try:
combo.setCurrentText(text)
finally:
combo.blockSignals(was_blocked)
def _enter_empty_profile_state(self) -> None: def _enter_empty_profile_state(self) -> None:
""" """
Switch to the transient empty workspace state. Switch to the transient empty workspace state.
@@ -796,7 +801,6 @@ class BECDockArea(DockAreaWidget):
self._pending_autosave_skip = (current_profile, name) self._pending_autosave_skip = (current_profile, name)
else: else:
self._pending_autosave_skip = None self._pending_autosave_skip = None
workspace_combo.setCurrentText(name)
self._finalize_profile_change(name, namespace) self._finalize_profile_change(name, namespace)
@SafeSlot() @SafeSlot()
@@ -24,19 +24,9 @@ class ProfileComboBox(QComboBox):
def set_quick_profile_provider(self, provider: Callable[[], list[str]]) -> None: def set_quick_profile_provider(self, provider: Callable[[], list[str]]) -> None:
self._quick_provider = provider self._quick_provider = provider
def refresh_profiles( def _refresh_profiles(
self, active_profile: str | None = None, show_empty_profile: bool = False self, current_text: str, active_profile: str | None = None, show_empty_profile: bool = False
) -> None: ) -> None:
"""
Refresh the profile list and ensure the active profile is visible.
Args:
active_profile(str | None): The currently active profile name.
show_empty_profile(bool): If True, show an explicit empty unsaved workspace entry.
"""
current_text = active_profile or self.currentText()
self.blockSignals(True)
self.clear() self.clear()
quick_profiles = self._quick_provider() quick_profiles = self._quick_provider()
@@ -103,7 +93,6 @@ class ProfileComboBox(QComboBox):
if index >= 0: if index >= 0:
self.setCurrentIndex(index) self.setCurrentIndex(index)
self.blockSignals(False)
if active_profile and self.currentText() != active_profile: if active_profile and self.currentText() != active_profile:
idx = self.findText(active_profile) idx = self.findText(active_profile)
if idx >= 0: if idx >= 0:
@@ -115,6 +104,24 @@ class ProfileComboBox(QComboBox):
else: else:
self.setToolTip("") self.setToolTip("")
def refresh_profiles(
self, active_profile: str | None = None, show_empty_profile: bool = False
) -> None:
"""
Refresh the profile list and ensure the active profile is visible.
Args:
active_profile(str | None): The currently active profile name.
show_empty_profile(bool): If True, show an explicit empty unsaved workspace entry.
"""
current_text = active_profile or self.currentText()
was_blocked = self.blockSignals(True)
try:
self._refresh_profiles(current_text, active_profile, show_empty_profile)
finally:
self.blockSignals(was_blocked)
def workspace_bundle(components: ToolbarComponents, enable_tools: bool = True) -> ToolbarBundle: def workspace_bundle(components: ToolbarComponents, enable_tools: bool = True) -> ToolbarBundle:
""" """
@@ -122,6 +129,7 @@ def workspace_bundle(components: ToolbarComponents, enable_tools: bool = True) -
Args: Args:
components (ToolbarComponents): The components to be added to the bundle. components (ToolbarComponents): The components to be added to the bundle.
enable_tools(bool): If True, show the workspace management tools; otherwise, only show the profile combo.
Returns: Returns:
ToolbarBundle: The workspace toolbar bundle. ToolbarBundle: The workspace toolbar bundle.
+1 -1
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "bec_widgets" name = "bec_widgets"
version = "3.7.1" version = "3.7.2"
description = "BEC Widgets" description = "BEC Widgets"
requires-python = ">=3.11" requires-python = ">=3.11"
classifiers = [ classifiers = [
-1
View File
@@ -59,5 +59,4 @@ def test_run_line_scan_with_parameters_e2e(scan_control, bec_client_lib, qtbot):
last_scan = queue.scan_storage.storage[-1] last_scan = queue.scan_storage.storage[-1]
assert last_scan.status_message.info["scan_name"] == scan_name assert last_scan.status_message.info["scan_name"] == scan_name
assert last_scan.status_message.info["exp_time"] == kwargs["exp_time"] assert last_scan.status_message.info["exp_time"] == kwargs["exp_time"]
assert last_scan.status_message.info["scan_motors"] == [args["device"]]
assert last_scan.status_message.info["num_points"] == kwargs["steps"] assert last_scan.status_message.info["num_points"] == kwargs["steps"]
-1
View File
@@ -84,7 +84,6 @@ def test_scan_metadata_for_custom_scan(
last_scan = queue.scan_storage.storage[-1] last_scan = queue.scan_storage.storage[-1]
assert last_scan.status_message.info["scan_name"] == scan_name assert last_scan.status_message.info["scan_name"] == scan_name
assert last_scan.status_message.info["exp_time"] == kwargs["exp_time"] assert last_scan.status_message.info["exp_time"] == kwargs["exp_time"]
assert last_scan.status_message.info["scan_motors"] == [args["device"]]
assert last_scan.status_message.info["num_points"] == kwargs["steps"] assert last_scan.status_message.info["num_points"] == kwargs["steps"]
if valid: if valid:
-1
View File
@@ -71,7 +71,6 @@ def bec_queue_msg_full():
}, },
"report_instructions": [{"scan_progress": 20}], "report_instructions": [{"scan_progress": 20}],
"scan_id": "2d704cc3-c172-404c-866d-608ce09fce40", "scan_id": "2d704cc3-c172-404c-866d-608ce09fce40",
"scan_motors": ["samx"],
"scan_number": 1289, "scan_number": 1289,
} }
], ],
+2 -5
View File
@@ -146,10 +146,7 @@ class TestDeviceManagerViewDialogs:
group_combo: QtWidgets.QComboBox = dialog._control_widgets["group_combo"] group_combo: QtWidgets.QComboBox = dialog._control_widgets["group_combo"]
assert group_combo.count() == len(OPHYD_DEVICE_TEMPLATES) assert group_combo.count() == len(OPHYD_DEVICE_TEMPLATES)
# Test select a group from available templates
variant_combo = dialog._control_widgets["variant_combo"] variant_combo = dialog._control_widgets["variant_combo"]
assert variant_combo.isEnabled() is False
with qtbot.waitSignal(group_combo.currentTextChanged): with qtbot.waitSignal(group_combo.currentTextChanged):
epics_signal_index = group_combo.findText("EpicsSignal") epics_signal_index = group_combo.findText("EpicsSignal")
group_combo.setCurrentIndex(epics_signal_index) # Select "EpicsSignal" group group_combo.setCurrentIndex(epics_signal_index) # Select "EpicsSignal" group
@@ -235,7 +232,7 @@ class TestDeviceManagerViewDialogs:
sample_config = { sample_config = {
"name": "TestDevice", "name": "TestDevice",
"enabled": True, "enabled": True,
"deviceClass": "ophyd.EpicsSignal", "deviceClass": "ophyd_devices.EpicsSignal",
"readoutPriority": "baseline", "readoutPriority": "baseline",
"deviceConfig": {"read_pv": "X25DA-ES1-MOT:GET"}, "deviceConfig": {"read_pv": "X25DA-ES1-MOT:GET"},
} }
@@ -248,7 +245,7 @@ class TestDeviceManagerViewDialogs:
assert variant_combo.currentText() == "EpicsSignal" assert variant_combo.currentText() == "EpicsSignal"
config = dialog._device_config_template.get_config_fields() config = dialog._device_config_template.get_config_fields()
assert config["name"] == "TestDevice" assert config["name"] == "TestDevice"
assert config["deviceClass"] == "ophyd.EpicsSignal" assert config["deviceClass"] == "ophyd_devices.EpicsSignal"
assert config["deviceConfig"]["read_pv"] == "X25DA-ES1-MOT:GET" assert config["deviceConfig"]["read_pv"] == "X25DA-ES1-MOT:GET"
# Test now to add the device config with different validation results # Test now to add the device config with different validation results
+6 -1
View File
@@ -1863,9 +1863,14 @@ class TestWorkspaceProfileOperations:
with patch( with patch(
"bec_widgets.widgets.containers.dock_area.dock_area.SaveProfileDialog", StubDialog "bec_widgets.widgets.containers.dock_area.dock_area.SaveProfileDialog", StubDialog
): ):
advanced_dock_area.save_profile(show_dialog=True) widgets_before_save = list(advanced_dock_area.widget_list())
with patch.object(advanced_dock_area, "load_profile") as mock_load_profile:
advanced_dock_area.save_profile(show_dialog=True)
qtbot.wait(100)
mock_load_profile.assert_not_called()
qtbot.wait(500) qtbot.wait(500)
assert list(advanced_dock_area.widget_list()) == widgets_before_save
source_manifest = read_manifest(helper.open_user(source_profile)) source_manifest = read_manifest(helper.open_user(source_profile))
new_manifest = read_manifest(helper.open_user(new_profile)) new_manifest = read_manifest(helper.open_user(new_profile))