From 87def4961c9799198fce30e2bc292ca2d22757c3 Mon Sep 17 00:00:00 2001 From: chrin Date: Mon, 24 Oct 2022 08:12:47 +0200 Subject: [PATCH] sendelogsls.py uses bdbase.sendelogframe.py --- base.py | 40 +++++-- enumkind.py | 1 + guiframe.py | 133 +++++++++++++++++++-- sendelogframe.py | 305 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 460 insertions(+), 19 deletions(-) create mode 100644 sendelogframe.py diff --git a/base.py b/base.py index b3cfc12..0e31196 100644 --- a/base.py +++ b/base.py @@ -5,6 +5,7 @@ from collections import OrderedDict from datetime import datetime import getpass import h5py +import logging import inspect import platform import os @@ -29,7 +30,7 @@ from pyqtacc.bdbase.helpbrowser import HelpBrowser from pyqtacc.bdbase.readjson import ReadJSON from pyqtacc.bdbase.savehdf import QSaveHDF from pyqtacc.bdbase.hdf5filemenu import HDF5GroupBox -from pyqtacc.bdbase.sendelog import QSendToELOG + from pyqtacc.bdbase.screenshot import QScreenshot from pyqtacc.bdbase.guiframe import GUIFrame @@ -331,7 +332,8 @@ class BaseWindow(QMainWindow): self.facility = facility self.user_mode = user_mode self.appname, self.appext = self.pymodule.split(".") - + + print("=============================================") print("Starting {0} at: {1}".format(self.appname, datetime.now())) print("User: {0} Host: {1}".format(os.getlogin(), os.uname()[1])) @@ -340,6 +342,8 @@ class BaseWindow(QMainWindow): self.settings = ReadJSON(self.appname) + #Read out current_logbook + self.cafe = PyCafe.CyCafe() self.cyca = PyCafe.CyCa() self.cafe_exception = PyCafe.CafeException @@ -390,6 +394,12 @@ class BaseWindow(QMainWindow): self.stdlog_dest = (self.settings.data["stdlog"]["destination"] + self.appname + "-" + os.getlogin() + ".log") + self.logging = logging + #self.logging.basicConfig(filename=self.stdlog_dest, level=logging.DEBUG) + self.logging.basicConfig(level=logging.DEBUG) + self.logger = self.logging.getLogger(__name__) + self.logger.info("Logging activated") + self.date_str = None self.autopost_elog = True @@ -492,8 +502,10 @@ class BaseWindow(QMainWindow): if self.facility == Facility.SwissFEL: from pyqtacc.sf.guiheader import GUIHeader + from pyqtacc.bdbase.sendelog import QSendToELOG elif self.facility == Facility.SLS: from pyqtacc.sls.guiheader import GUIHeader + from pyqtacc.sls.sendelog import QSendToELOG self.gui_header = GUIHeader(self, user_mode=self.user_mode, extended=extended) @@ -553,7 +565,7 @@ class BaseWindow(QMainWindow): if self.settings.data["Parameters"][key]["flag"]: self.input_parameters[key] = val - self.all_input_parameters[key] = val + self.all_input_parameters[key] = val #print("val=",val, key) except KeyError as e: @@ -599,8 +611,8 @@ class BaseWindow(QMainWindow): if self.settings.data["Expert"][key]["flag"]: self.expert_parameters[key] = val - self.all_expert_parameters[key] = val - #print("val=",val, key) + self.all_expert_parameters[key] = val + #print("val=",val, key) except KeyError as e: print("Key Error in base.py initialization:", e) @@ -841,7 +853,7 @@ class BaseWindow(QMainWindow): """ #Close all dock widgets #self.removeDockWidget(self.hdf_dock_widget) - + self.logger.info("Closing Application") self.save_application_settings() QApplication.processEvents() self.cafe.monitorStopAll() @@ -1716,18 +1728,26 @@ class BaseWindow(QMainWindow): @Slot(dict) def receive_analysis_results(self, all_dict): - print("receive analysis results===>") self.all_data = all_dict self.gui_frame.canvas_update(all_dict['Figure data']) - print("here") + + if self.gui_frame.results_output_wgt_dict: + #all_dict['Processed data']['Results']={} + #all_dict['Processed data']['Results']['mean'] = 123.23 + #all_dict['Processed data']['Results']['SD'] = 0.23 + try: + results_data = all_dict['Processed data']['Results'] + self.gui_frame.send_to_results_output_wgt(results_data) + except: + pass + self.gui_frame.central_tab_widget.setCurrentIndex(1) - self.gui_frame.results_tab_wgt.setCurrentIndex(0) + self.gui_frame.results_tab_wgt.setCurrentIndex(0) @Slot() def receive_abort_analysis(self): """Instantiate action on abort """ - print("Aborting", flush=True) if self.analysis_procedure.abort: return self.gui_frame.in_abort_procedure() diff --git a/enumkind.py b/enumkind.py index 3311e95..cb99888 100644 --- a/enumkind.py +++ b/enumkind.py @@ -18,6 +18,7 @@ class Facility(IntEnum): PSI = 0 SwissFEL = 1 SLS = 2 + HIPA = 3 class MsgSeverity(IntEnum): """ For use with message logger diff --git a/guiframe.py b/guiframe.py index b91c83b..921063c 100644 --- a/guiframe.py +++ b/guiframe.py @@ -17,7 +17,7 @@ from qtpy.QtWidgets import (QCheckBox, QComboBox, QDoubleSpinBox, QFrame, QStackedWidget, QTabBar, QTabWidget, QToolButton, QVBoxLayout, QWidget) from caqtwidgets.pvwidgets import (CAQLabel, CAQTableWidget, QMessageWidget, - QHLine) + QHLine, QVLine) from pyqtacc.bdbase.enumkind import Facility, MsgSeverity, UserMode @@ -61,6 +61,8 @@ class GUIFrame(QWidget): self.all_expert_parameters = parent.all_expert_parameters self.all_expert_labels = parent.all_expert_labels + self.results_output_wgt_dict = {} + try: self.widget_height = self.settings.data["StyleGuide"][ "widgetHeight"] @@ -75,6 +77,7 @@ class GUIFrame(QWidget): self.expert_parameters_group = None self.operator_parameters_group = None + self.output_parameters_wgt = None self.optics_group = None self.energy_stacked_widget = None @@ -268,6 +271,76 @@ class GUIFrame(QWidget): self.optics_group.cycle_quads = None return self.optics_group + def output_parameters_group(self, title="Results Output"): + """ Generic group box for user supplied input + """ + self.output_parameters_wgt = QWidget() + qtop = QVBoxLayout() + self.output_parameters_group_box = QGroupBox(title) + self.output_parameters_group_box.setObjectName("OUTER") + vbox = QVBoxLayout() + vbox.setContentsMargins(9, 19, 9, 9) + vbox.setAlignment(Qt.AlignTop | Qt.AlignHCenter) + keys = self.settings.data["Results"].keys() + max_str_len = 4 + for key in keys: + if len(key) > max_str_len: + max_str_len = len(key) + + for key in keys: + ikey = self.settings.data["Results"][key] + + if ikey['flag']: + _widget = ikey["data"]["widget"].upper() + _text = ikey["data"]["text"] + _value = str(ikey["data"]["value"]) + _width = int(ikey["data"]["width"]) + if _widget == "QLINEEDIT": + ql = QLabel(str(_text)) + _label_width = (ql.fontMetrics().averageCharWidth() * + max_str_len+2) + ql.setMinimumWidth(_label_width) + ql.setFixedHeight(self.widget_height) + ql.setFont(self.font_pts10) + + wgt = QLineEdit() + wgt.setFixedWidth(_width) + wgt.setObjectName("ReadRight") + wgt.setFixedHeight(self.widget_height) + wgt.setFont(self.font_pts10) + wgt.setAlignment(Qt.AlignRight) + self.results_output_wgt_dict[key] = wgt + if "NONE" not in _value.upper(): + self.results_output_wgt_dict[key].setText(_value) + + hbox = QHBoxLayout() + hbox.setContentsMargins(0, 0, 0, 0) + hbox.addWidget(ql) + hbox.addWidget(wgt) + hbox.setAlignment(Qt.AlignLeft) + vbox.addLayout(hbox) + + self.output_parameters_group_box.setContentsMargins(9, 19, 9, 9) + self.output_parameters_group_box.setMinimumWidth(140) + self.output_parameters_group_box.setMaximumWidth(300) + #self.output_parameters_group_box.setMaximumHeight(400) + self.output_parameters_group_box.setFont(self.font_gui) + self.output_parameters_group_box.setAlignment(Qt.AlignTop | + Qt.AlignHCenter) + + qf = QFrame() + qf.setFixedHeight(35) + self.output_parameters_group_box.setLayout(vbox) + qtop.addWidget(qf) + qtop.addWidget(self.output_parameters_group_box) + self.output_parameters_wgt.setLayout(qtop) + return self.output_parameters_wgt + + def send_to_results_output_wgt(self, results_data): + for key, value in results_data.items(): + self.results_output_wgt_dict[key].setText(str(value)) + + def input_parameters_group(self, title="Input Parameters"): """ Generic group box for user supplied input """ @@ -309,11 +382,12 @@ class GUIFrame(QWidget): #print(self.input_parameters) checkbox = QCheckBox(text) + checkbox.setToolTip(tip) checkbox.setFont(self.font_gui) checkbox.stateChanged.connect(on_checkbox_change) #checkbox.setFixedHeight(30) - + #checkbox.setLayoutDirection(Qt.RightToLeft) checked_value = default_value if key in self.parent.input_parameters.keys(): if "data" in self.settings.data[top_key][key].keys(): @@ -564,6 +638,13 @@ class GUIFrame(QWidget): for table in self.table_wgt: table.init_value_button.setEnabled(False) table.restore_value_button.setEnabled(False) + + if self.output_parameters_wgt is not None: + for wgt in self.results_output_wgt_dict.values(): + wgt.setText("") + + #self.output_parameters_wg.setEnabled(False) + def in_abort_procedure(self): self.abort_wgt.setEnabled(False) @@ -585,6 +666,8 @@ class GUIFrame(QWidget): for table in self.table_wgt: table.init_value_button.setEnabled(True) table.restore_value_button.setEnabled(True) + #if self.output_parameters_wgt is not None: + # self.output_parameters_wgt.setEnabled(True) def in_hdf_measurement_procedure(self): self.parent.h5_groupbox.analyze_h5_widget.setEnabled(False) @@ -748,7 +831,7 @@ class GUIFrame(QWidget): except KeyError as error: print(("KeyError in guiframe.py; Unknown key {0} " + "in def checkbox_wgt: {1}").format(key, error)) - text = key + text = key checkbox = QCheckBox(text) checkbox.setToolTip(tooltip) @@ -756,6 +839,7 @@ class GUIFrame(QWidget): if object_name: checkbox.setObjectName(object_name) + checkbox.setLayoutDirection(Qt.LeftToRight) #is default checkbox.stateChanged.connect(on_change) checked_value = Qt.Unchecked @@ -1319,6 +1403,8 @@ class GUIFrame(QWidget): self.line_sender_dict[item].setText(str(value_list)) line = self.radio_buttons(key=key, options=value) + line.setContentsMargins(0, 0, 0, 0) + rblist = line.findChildren(QRadioButton) for rb in rblist: @@ -1326,6 +1412,7 @@ class GUIFrame(QWidget): wgt_grid.addWidget(line, irow, 0, 1, 4, Qt.AlignLeft) + rblist[0].setChecked(True) rblist[0].toggled.emit(True) @@ -1340,10 +1427,26 @@ class GUIFrame(QWidget): line.setChecked(Qt.Checked if value else Qt.Unchecked) line.stateChanged.connect(check_cb) line.setMaximumWidth(320) - line.setMinimumWidth(220) + line.setMinimumWidth(100) line.setFixedHeight(28) + + + if key in self.settings.data["Expert"].keys(): + top_key = "Expert" + elif key in self.settings.data["Parameters"].keys(): + top_key = "Parameters" - wgt_grid.addWidget(line, irow, 0, 1, 4, Qt.AlignLeft | Qt.AlignVCenter) + col_wdt = 4 + try: + orientation = self.settings.data[top_key][key]["data"]["orientation"] + orientation = Qt.RightToLeft if "RightToLeft" in orientation \ + else Qt.LeftToRight + line.setLayoutDirection(orientation) + col_wdt=3 + except KeyError: + pass + + wgt_grid.addWidget(line, irow, 0, 1, col_wdt, Qt.AlignLeft | Qt.AlignVCenter) return line @@ -1488,7 +1591,10 @@ class GUIFrame(QWidget): wgt_grid.addWidget(qFrame, irow, 0, 1, 4) - def input_wgt(self, buddy, label, key, value, irow=0, wgt_grid=None): + def input_wgt(self, buddy, label, key, value, irow=0, wgt_grid=None,): + + if "QVLINE" in buddy: + print("QVLine", label, key, value) if wgt_grid is None: wgt_grid = self.input_wgt_grid @@ -1581,12 +1687,21 @@ class GUIFrame(QWidget): except KeyError: pass + def draw_widget(buddy): + flag = self.settings.data["Parameters"][key]["flag"] + if buddy == "NONE": + return False + elif buddy not in ["QHLINE", "QFRAME"] and not flag: + return False + return True + for (key, val), label in zip(self.all_input_parameters.items(), self.all_input_labels.values()): buddy = self.settings.data[ "Parameters"][key]["data"]["widget"].upper() j = self.input_wgt_grid.rowCount() - if buddy != "NONE": + #if buddy != "NONE": + if draw_widget(buddy): self.input_wgt(buddy=buddy, label=label, key=key, value=val, irow=j, wgt_grid=self.input_wgt_grid) @@ -1697,7 +1812,7 @@ class GUIFrame(QWidget): widget = QWidget() layout = QGridLayout() layout.setContentsMargins(5, 0, 5, 0) - widget.setFixedWidth(220) + widget.setMaximumWidth(220) #self.setLayout(layout) target_list = options @@ -1715,7 +1830,7 @@ class GUIFrame(QWidget): elif key in self.settings.data["Parameters"].keys(): top_key = "Parameters" - _width = 1 + _width = 1 _full_width = _width * len(options) + 1 layout.addWidget(QHLine(), 0, 0, 1, _full_width) diff --git a/sendelogframe.py b/sendelogframe.py new file mode 100644 index 0000000..55fa4d8 --- /dev/null +++ b/sendelogframe.py @@ -0,0 +1,305 @@ +import getpass +import inspect +import os +import time + +from qtpy.QtCore import Qt +from qtpy.QtWidgets import (QComboBox, QDialog, QFileDialog, QHBoxLayout, + QLabel, QLineEdit, QPushButton, QTextEdit, + QVBoxLayout) + +import elog # https://github.com/paulscherrerinstitute/py_elog +from pyqtacc.bdbase.enumkind import MsgSeverity + +_version = "1.0.0" +_pymodule = os.path.basename(__file__) +_appname, _appext = _pymodule.split(".") + +def _line(): + """Macro to return the current line number. + + The current line number within the file is used when + reporting messages to the message logging window. + + Returns: + int: Current line number. + """ + return inspect.currentframe().f_back.f_lineno + +class QSendToELOGFrame(QDialog): + """ Graphical interface to elog + """ + def __init__(self, parent, logbook=None, title=None, message=None, + attachFile=None, destination_folder=None): + super().__init__() + + self.files_text = '' + self.fflag = False + self.parent = parent + + if destination_folder is None: + self.destination = self.parent.elog_dest + " / " + self.parent.appname + else: + self.destination = destination_folder + self.window_title = self.parent.appname + ": " + _appname + self.pymodule = _pymodule + + self.setWindowTitle(self.window_title) + + elog_books = list(self.parent.settings.data["ElogBooks"]) + self.elog_items = QComboBox() + self.elog_items.addItems(elog_books) + self.elog_items.currentIndexChanged.connect(self.on_elog_change) + idx = 0 + + if logbook is not None: + try: + idx = elog_books.index(logbook) + except Exception: + mess = "Index not found for logbook {0}".format(logbook) + self.parent.show_log_message(MsgSeverity.WARN, self.pymodule, + _line(), mess) + + + else: + logbook = self.parent.settings.data["Elog"]["book"] + try: + idx = elog_books.index(logbook) + except Exception: + mess = "Index not found for logbook {0}".format(logbook) + self.parent.show_log_message(MsgSeverity.WARN, self.pymodule, + _line(), mess) + + self.logbook = logbook + print("logbook===>", self.logbook) + + self.elog_items.setCurrentIndex(idx) + self.elog_items.currentIndexChanged.emit(idx) + self.elog_items.setObjectName("Elog") + + self.title_items = QHBoxLayout() + self.title_items.addWidget(QLabel('Title: ')) + self.titleline = QLineEdit() + self.titleline.setObjectName('Elog') + #self.titleline.setStatusTip('elog') + if title is None: + title = self.parent.appname + self.titleline.setText(str(title)) + self.titleline.setFixedWidth(300) + self.titleline.setAlignment(Qt.AlignCenter) + self.title_items.addWidget(self.titleline) + + self.applicationbox = QHBoxLayout() + self.applicationbox.addWidget(QLabel('Application:')) + self.applicationlabel = QLabel(self.parent.pymodule) + self.applicationlabel.setObjectName("Elog") + # self.applicationbox.addStretch() + self.applicationbox.addWidget(self.applicationlabel) + + logbook = QHBoxLayout() + logbook.addWidget(QLabel('Logbook: ')) + logbook.addWidget(self.elog_items) + + + authorbox = QHBoxLayout() + authorbox.addWidget(QLabel('Author: ')) + self.author = QLineEdit() + self.author.setObjectName('Elog') + self.author.setFixedWidth(195) + self.author.setStatusTip('elog') + self.author.setText(getpass.getuser()) + authorbox.addWidget(self.author) + + self.files = [] + self.attributes = {} + + self.layout = QVBoxLayout(self) + self.layout.addLayout(logbook) + self.layout.addLayout(self.applicationbox) + + self.layout.addLayout(authorbox) + self.layout.addLayout(self.title_items) + + report = QLabel('Report: ') + self.layout.addWidget(report) + self.message = QTextEdit(message) + self.layout.addWidget(self.message) + + filebox = QHBoxLayout() + qlfile = QLabel('Attach:') + qlfile.setAlignment(Qt.AlignTop) + filebox.addWidget(qlfile) + + self.filesE = QTextEdit() + self.filesE.setAlignment(Qt.AlignTop) + self.filesE.setFixedHeight(80) + self.filesE.setReadOnly(True) + + self.attachFile = attachFile + + if self.attachFile is not None: + _attachFile = [] + if isinstance(self.attachFile, str): + _attachFile.append(self.attachFile) + elif isinstance(self.attachFile, list): + _attachFile = self.attachFile + for i, file in enumerate(_attachFile): + _attach_base = os.path.basename(file) + if i > 0: + self.files_text += "\n" + self.files_text += str(_attach_base) + self.filesE.setText(self.files_text) + self.fflag = True + + filebox.addWidget(self.filesE) + + openCloseVBox = QVBoxLayout() + self.openBtn = QPushButton('Add') + self.openBtn.setAutoDefault(False) + self.openBtn.clicked.connect(self.openFiles) + openCloseVBox.addWidget(self.openBtn) + + closeBtn = QPushButton('Clear') + closeBtn.setAutoDefault(False) + closeBtn.clicked.connect(self.clearFiles) + openCloseVBox.addWidget(closeBtn) + + filebox.addLayout(openCloseVBox) + self.layout.addLayout(filebox) + + btnLayout = QHBoxLayout() + self.okBtn = QPushButton('Send') + self.okBtn.setAutoDefault(False) + self.okBtn.clicked.connect(self.ok) + + self.cancelBtn = QPushButton('Cancel') + self.cancelBtn.setAutoDefault(False) + self.cancelBtn.clicked.connect(self.close) + btnLayout.addWidget(self.okBtn) + btnLayout.addWidget(self.cancelBtn) + self.messagelbl = QLabel('') + self.messagelbl.setStyleSheet("QLabel { color : red; }") + self.layout.addWidget(self.messagelbl) + self.layout.addLayout(btnLayout) + print("logbook5", logbook) + self.setMinimumWidth(440) + #self.exec() + + def on_elog_change(self, elog_change_val): + if "test" in self.elog_items.currentText(): + _bgcolor = "QComboBox {background: plum; color : black;}" + else: + _bgcolor = "QComboBox {background: lightblue; color : black;}" + self.elog_items.setStyleSheet(_bgcolor) + + + def ok(self): + message = self.message.document().toPlainText() + if not message: + self.messagelbl.setText('Please enter a brief Report') + return + title = self.titleline.text() + if not title: + self.messagelbl.setText('Titel attribute is required') + return + + author = self.author.text() + application = self.applicationlabel.text() + self.attributes['Autor'] = author + self.attributes['Author'] = author + self.attributes['Application'] = application + self.attributes['Titel'] = title + self.attributes['Title'] = title + self.attributes['When'] = str(time.time()) + self.attributes['Wann'] = str(time.time()) + + + + if self.attachFile is not None: + _attachFile = [] + if isinstance(self.attachFile, str): + _attachFile.append(self.attachFile) + elif isinstance(self.attachFile, list): + _attachFile = self.attachFile + for i in range(0, len(_attachFile)): + if "/tmp" in _attachFile[i]: + self.files.append(str(_attachFile[i])) + elif "/afs/psi.ch" in _attachFile[i]: + self.files.append(str(_attachFile[i])) + elif "/sls/bd/data/" in _attachFile[i]: + self.files.append(str(_attachFile[i])) + else: + self.files.append(self.destination + str(_attachFile[i])) + + + + el = self.elog_items.currentText() + print("el==============>", el) + url = self.parent.settings.data["ElogBooks"][el]["url"] + + self.logbook = elog.open(url, user='robot', password='robot') + + print("sendelogframe.py========>: ", url, flush=True) + print(message, flush=True) + try: + if self.files: + self.logbook.post(message, attributes=self.attributes, + attachments=self.files) + else: + self.logbook.post(message, attributes=self.attributes) + + #self.trigger_elog_entry.emit(True, url, "OK") + self.receive_elog_notification(True, url, "OK") + except Exception as ex: + #self.trigger_elog_entry.emit(False, url, str(ex)) + print("Exception in sendelog.py", str(ex)) + self.receive_elog_notification(False, url, str(ex)) + + #for file in self.files: + # if os.path.exists(file): + # os.remove(file) + + self.close() + + + def clearFiles(self): + self.attachFile = [] + self.filesE.clear() + self.files = [] + self.files_text = '' + self.fflag = False + + def openFiles(self): + # Notethat openFiles also gets called when qDialog cacnel is clicked! + qFileDialog = QFileDialog() + extensions = ("Images (*.bmp *.eps *.gif *.jpeg *.jpg *.pdf *.png " + + "*.ps *.tiff *.xpm);;Text files (*.txt *.text);;" + + "JSON/XML files (*.json *.xml)") + + flocal = qFileDialog.getOpenFileNames( + self, 'Open File', self.destination, extensions)[0] + + if not flocal: + return + self.files.append(flocal[0]) + if self.fflag: + self.files_text += '\n' + self.files_text += str(flocal[0].split('/')[-1]) + self.fflag = True + self.filesE.setText(self.files_text) + + + def receive_elog_notification(self, is_accepted, logbook_url, elog_message): + '''Receive notification from ELOG, and report to log window''' + + yes_no = "made" if is_accepted else "failed" + mess = "Entry into ELOG: {0} {1}".format(logbook_url, yes_no) + + if is_accepted: + self.parent.show_log_message(MsgSeverity.INFO, self.pymodule, + _line(), mess) + else: + mess += ".\n" + elog_message + self.parent.show_log_message(MsgSeverity.WARN, self.pymodule, + _line(), mess) + self.parent.statusbar.showMessage(mess)