mirror of
https://github.com/bec-project/bec_widgets.git
synced 2025-07-14 11:41:49 +02:00
feat(widgets): added simple bec queue widget
This commit is contained in:
@ -13,6 +13,7 @@ class Widgets(str, enum.Enum):
|
|||||||
Enum for the available widgets.
|
Enum for the available widgets.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
BECQueue = "BECQueue"
|
||||||
BECStatusBox = "BECStatusBox"
|
BECStatusBox = "BECStatusBox"
|
||||||
BECDock = "BECDock"
|
BECDock = "BECDock"
|
||||||
BECDockArea = "BECDockArea"
|
BECDockArea = "BECDockArea"
|
||||||
@ -1446,6 +1447,24 @@ class BECPlotBase(RPCBase):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class BECQueue(RPCBase):
|
||||||
|
@property
|
||||||
|
@rpc_call
|
||||||
|
def config_dict(self) -> "dict":
|
||||||
|
"""
|
||||||
|
Get the configuration of the widget.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: The configuration of the widget.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@rpc_call
|
||||||
|
def get_all_rpc(self) -> "dict":
|
||||||
|
"""
|
||||||
|
Get all registered RPC objects.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class BECStatusBox(RPCBase):
|
class BECStatusBox(RPCBase):
|
||||||
@property
|
@property
|
||||||
@rpc_call
|
@rpc_call
|
||||||
|
0
bec_widgets/widgets/bec_queue/__init__.py
Normal file
0
bec_widgets/widgets/bec_queue/__init__.py
Normal file
111
bec_widgets/widgets/bec_queue/bec_queue.py
Normal file
111
bec_widgets/widgets/bec_queue/bec_queue.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
from bec_lib.endpoints import MessageEndpoints
|
||||||
|
from qtpy.QtCore import Qt, Slot
|
||||||
|
from qtpy.QtWidgets import QHeaderView, QTableWidget, QTableWidgetItem, QWidget
|
||||||
|
|
||||||
|
from bec_widgets.utils.bec_connector import BECConnector, ConnectionConfig
|
||||||
|
|
||||||
|
|
||||||
|
class BECQueue(BECConnector, QTableWidget):
|
||||||
|
"""
|
||||||
|
Widget to display the BEC queue.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
parent: QWidget | None = None,
|
||||||
|
client=None,
|
||||||
|
config: ConnectionConfig = None,
|
||||||
|
gui_id: str = None,
|
||||||
|
):
|
||||||
|
super().__init__(client, config, gui_id)
|
||||||
|
QTableWidget.__init__(self, parent=parent)
|
||||||
|
self.setColumnCount(3)
|
||||||
|
self.setHorizontalHeaderLabels(["Scan Number", "Type", "Status"])
|
||||||
|
header = self.horizontalHeader()
|
||||||
|
header.setSectionResizeMode(QHeaderView.Stretch)
|
||||||
|
self.bec_dispatcher.connect_slot(self.update_queue, MessageEndpoints.scan_queue_status())
|
||||||
|
self.reset_content()
|
||||||
|
|
||||||
|
@Slot(dict, dict)
|
||||||
|
def update_queue(self, content, _metadata):
|
||||||
|
"""
|
||||||
|
Update the queue table with the latest queue information.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
content (dict): The queue content.
|
||||||
|
_metadata (dict): The metadata.
|
||||||
|
"""
|
||||||
|
# only show the primary queue for now
|
||||||
|
queue_info = content.get("queue", {}).get("primary", {}).get("info", [])
|
||||||
|
self.setRowCount(len(queue_info))
|
||||||
|
self.clearContents()
|
||||||
|
|
||||||
|
if not queue_info:
|
||||||
|
self.reset_content()
|
||||||
|
return
|
||||||
|
|
||||||
|
for index, item in enumerate(queue_info):
|
||||||
|
blocks = item.get("request_blocks", [])
|
||||||
|
scan_types = []
|
||||||
|
scan_numbers = []
|
||||||
|
status = item.get("status", "")
|
||||||
|
for request_block in blocks:
|
||||||
|
scan_type = request_block.get("content", {}).get("scan_type", "")
|
||||||
|
if scan_type:
|
||||||
|
scan_types.append(scan_type)
|
||||||
|
scan_number = request_block.get("scan_number", "")
|
||||||
|
if scan_number:
|
||||||
|
scan_numbers.append(str(scan_number))
|
||||||
|
if scan_types:
|
||||||
|
scan_types = ", ".join(scan_types)
|
||||||
|
if scan_numbers:
|
||||||
|
scan_numbers = ", ".join(scan_numbers)
|
||||||
|
self.set_row(index, scan_numbers, scan_types, status)
|
||||||
|
|
||||||
|
def format_item(self, content: str) -> QTableWidgetItem:
|
||||||
|
"""
|
||||||
|
Format the content of the table item.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
content (str): The content to be formatted.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
QTableWidgetItem: The formatted item.
|
||||||
|
"""
|
||||||
|
item = QTableWidgetItem(content)
|
||||||
|
item.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
|
||||||
|
return item
|
||||||
|
|
||||||
|
def set_row(self, index: int, scan_number: str, scan_type: str, status: str):
|
||||||
|
"""
|
||||||
|
Set the row of the table.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
index (int): The index of the row.
|
||||||
|
scan_number (str): The scan number.
|
||||||
|
scan_type (str): The scan type.
|
||||||
|
status (str): The status.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.setItem(index, 0, self.format_item(scan_number))
|
||||||
|
self.setItem(index, 1, self.format_item(scan_type))
|
||||||
|
self.setItem(index, 2, self.format_item(status))
|
||||||
|
|
||||||
|
def reset_content(self):
|
||||||
|
"""
|
||||||
|
Reset the content of the table.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.setRowCount(1)
|
||||||
|
self.set_row(0, "", "", "")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": # pragma: no cover
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from qtpy.QtWidgets import QApplication
|
||||||
|
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
widget = BECQueue()
|
||||||
|
widget.show()
|
||||||
|
sys.exit(app.exec_())
|
1
bec_widgets/widgets/bec_queue/bec_queue.pyproject
Normal file
1
bec_widgets/widgets/bec_queue/bec_queue.pyproject
Normal file
@ -0,0 +1 @@
|
|||||||
|
{'files': ['bec_queue.py']}
|
54
bec_widgets/widgets/bec_queue/bec_queue_plugin.py
Normal file
54
bec_widgets/widgets/bec_queue/bec_queue_plugin.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
# Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||||
|
|
||||||
|
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
||||||
|
from qtpy.QtGui import QIcon
|
||||||
|
|
||||||
|
from bec_widgets.widgets.bec_queue.bec_queue import BECQueue
|
||||||
|
|
||||||
|
DOM_XML = """
|
||||||
|
<ui language='c++'>
|
||||||
|
<widget class='BECQueue' name='bec_queue'>
|
||||||
|
</widget>
|
||||||
|
</ui>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class BECQueuePlugin(QDesignerCustomWidgetInterface): # pragma: no cover
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self._form_editor = None
|
||||||
|
|
||||||
|
def createWidget(self, parent):
|
||||||
|
t = BECQueue(parent)
|
||||||
|
return t
|
||||||
|
|
||||||
|
def domXml(self):
|
||||||
|
return DOM_XML
|
||||||
|
|
||||||
|
def group(self):
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def icon(self):
|
||||||
|
return QIcon()
|
||||||
|
|
||||||
|
def includeFile(self):
|
||||||
|
return "bec_queue"
|
||||||
|
|
||||||
|
def initialize(self, form_editor):
|
||||||
|
self._form_editor = form_editor
|
||||||
|
|
||||||
|
def isContainer(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def isInitialized(self):
|
||||||
|
return self._form_editor is not None
|
||||||
|
|
||||||
|
def name(self):
|
||||||
|
return "BECQueue"
|
||||||
|
|
||||||
|
def toolTip(self):
|
||||||
|
return "Widget to display the BEC queue."
|
||||||
|
|
||||||
|
def whatsThis(self):
|
||||||
|
return self.toolTip()
|
15
bec_widgets/widgets/bec_queue/register_bec_queue.py
Normal file
15
bec_widgets/widgets/bec_queue/register_bec_queue.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
def main(): # pragma: no cover
|
||||||
|
from qtpy import PYSIDE6
|
||||||
|
|
||||||
|
if not PYSIDE6:
|
||||||
|
print("PYSIDE6 is not available in the environment. Cannot patch designer.")
|
||||||
|
return
|
||||||
|
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
||||||
|
|
||||||
|
from bec_widgets.widgets.bec_queue.bec_queue_plugin import BECQueuePlugin
|
||||||
|
|
||||||
|
QPyDesignerCustomWidgetCollection.addCustomWidget(BECQueuePlugin())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": # pragma: no cover
|
||||||
|
main()
|
111
tests/unit_tests/test_bec_queue.py
Normal file
111
tests/unit_tests/test_bec_queue.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
import pytest
|
||||||
|
from bec_lib import messages
|
||||||
|
|
||||||
|
from bec_widgets.widgets.bec_queue.bec_queue import BECQueue
|
||||||
|
|
||||||
|
from .client_mocks import mocked_client
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def bec_queue_msg_full():
|
||||||
|
content = {
|
||||||
|
"primary": {
|
||||||
|
"info": [
|
||||||
|
{
|
||||||
|
"active_request_block": None,
|
||||||
|
"is_scan": [True],
|
||||||
|
"queue_id": "600163fc-5e56-4901-af25-14e9ee76817c",
|
||||||
|
"request_blocks": [
|
||||||
|
{
|
||||||
|
"RID": "89a76021-28c0-4297-828e-74ae40b941e5",
|
||||||
|
"content": {
|
||||||
|
"parameter": {
|
||||||
|
"args": {"samx": [-0.1, 0.1]},
|
||||||
|
"kwargs": {
|
||||||
|
"exp_time": 0.5,
|
||||||
|
"relative": True,
|
||||||
|
"steps": 20,
|
||||||
|
"system_config": {
|
||||||
|
"file_directory": None,
|
||||||
|
"file_suffix": None,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"queue": "primary",
|
||||||
|
"scan_type": "line_scan",
|
||||||
|
},
|
||||||
|
"is_scan": True,
|
||||||
|
"metadata": {
|
||||||
|
"RID": "89a76021-28c0-4297-828e-74ae40b941e5",
|
||||||
|
"file_directory": None,
|
||||||
|
"file_suffix": None,
|
||||||
|
"user_metadata": {"sample_name": "testA"},
|
||||||
|
},
|
||||||
|
"msg": messages.ScanQueueMessage(
|
||||||
|
metadata={
|
||||||
|
"file_suffix": None,
|
||||||
|
"file_directory": None,
|
||||||
|
"user_metadata": {"sample_name": "testA"},
|
||||||
|
"RID": "89a76021-28c0-4297-828e-74ae40b941e5",
|
||||||
|
},
|
||||||
|
scan_type="line_scan",
|
||||||
|
parameter={
|
||||||
|
"args": {"samx": [-0.1, 0.1]},
|
||||||
|
"kwargs": {
|
||||||
|
"steps": 20,
|
||||||
|
"exp_time": 0.5,
|
||||||
|
"relative": True,
|
||||||
|
"system_config": {
|
||||||
|
"file_suffix": None,
|
||||||
|
"file_directory": None,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
queue="primary",
|
||||||
|
),
|
||||||
|
"readout_priority": {
|
||||||
|
"async": [],
|
||||||
|
"baseline": [],
|
||||||
|
"monitored": ["samx"],
|
||||||
|
"on_request": [],
|
||||||
|
},
|
||||||
|
"report_instructions": [{"scan_progress": 20}],
|
||||||
|
"scan_id": "2d704cc3-c172-404c-866d-608ce09fce40",
|
||||||
|
"scan_motors": ["samx"],
|
||||||
|
"scan_number": 1289,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"scan_id": ["2d704cc3-c172-404c-866d-608ce09fce40"],
|
||||||
|
"scan_number": [1289],
|
||||||
|
"status": "COMPLETED",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"status": "RUNNING",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
msg = messages.ScanQueueStatusMessage(metadata={}, queue=content)
|
||||||
|
return msg
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def bec_queue(qtbot, mocked_client):
|
||||||
|
widget = BECQueue(client=mocked_client)
|
||||||
|
qtbot.addWidget(widget)
|
||||||
|
qtbot.waitExposed(widget)
|
||||||
|
yield widget
|
||||||
|
|
||||||
|
|
||||||
|
def test_bec_queue(bec_queue, bec_queue_msg_full):
|
||||||
|
bec_queue.update_queue(bec_queue_msg_full.content, {})
|
||||||
|
assert bec_queue.rowCount() == 1
|
||||||
|
assert bec_queue.item(0, 0).text() == "1289"
|
||||||
|
assert bec_queue.item(0, 1).text() == "line_scan"
|
||||||
|
assert bec_queue.item(0, 2).text() == "COMPLETED"
|
||||||
|
|
||||||
|
|
||||||
|
def test_bec_queue_empty(bec_queue):
|
||||||
|
bec_queue.update_queue({}, {})
|
||||||
|
assert bec_queue.rowCount() == 1
|
||||||
|
assert bec_queue.item(0, 0).text() == ""
|
||||||
|
assert bec_queue.item(0, 1).text() == ""
|
||||||
|
assert bec_queue.item(0, 2).text() == ""
|
Reference in New Issue
Block a user