''' Handles display of control charts ''' from PyQt6.QtWebEngineWidgets import QWebEngineView from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QComboBox, QHBoxLayout, QDateEdit, QLabel, QSizePolicy ) from PyQt6.QtCore import QSignalBlocker from backend.db import ControlType, Control from PyQt6.QtCore import QDate, QSize import logging from tools import Report, Result from backend.excel.reports import convert_data_list_to_df from frontend.visualizations.control_charts import create_charts, construct_html logger = logging.getLogger(f"submissions.{__name__}") class ControlsViewer(QWidget): def __init__(self, parent: QWidget) -> None: super().__init__(parent) self.app = self.parent().parent() # logger.debug(f"\n\n{self.app}\n\n") self.report = Report() self.datepicker = ControlsDatePicker() self.webengineview = QWebEngineView() # set tab2 layout self.layout = QVBoxLayout(self) self.control_typer = QComboBox() # fetch types of controls con_types = [item.name for item in ControlType.query()] self.control_typer.addItems(con_types) # create custom widget to get types of analysis self.mode_typer = QComboBox() mode_types = Control.get_modes() self.mode_typer.addItems(mode_types) # create custom widget to get subtypes of analysis self.sub_typer = QComboBox() self.sub_typer.setEnabled(False) # add widgets to tab2 layout self.layout.addWidget(self.datepicker) self.layout.addWidget(self.control_typer) self.layout.addWidget(self.mode_typer) self.layout.addWidget(self.sub_typer) self.layout.addWidget(self.webengineview) self.setLayout(self.layout) self.controls_getter() self.control_typer.currentIndexChanged.connect(self.controls_getter) self.mode_typer.currentIndexChanged.connect(self.controls_getter) self.datepicker.start_date.dateChanged.connect(self.controls_getter) self.datepicker.end_date.dateChanged.connect(self.controls_getter) def controls_getter(self): """ Lookup controls from database and send to chartmaker """ self.controls_getter_function() def controls_getter_function(self): """ Get controls based on start/end dates """ report = Report() # NOTE: subtype defaults to disabled try: self.sub_typer.disconnect() except TypeError: pass # NOTE: correct start date being more recent than end date and rerun if self.datepicker.start_date.date() > self.datepicker.end_date.date(): logger.warning("Start date after end date is not allowed!") threemonthsago = self.datepicker.end_date.date().addDays(-60) # NOTE: block signal that will rerun controls getter and set start date # Without triggering this function again with QSignalBlocker(self.datepicker.start_date) as blocker: self.datepicker.start_date.setDate(threemonthsago) self.controls_getter() self.report.add_result(report) return # NOTE: convert to python useable date objects self.start_date = self.datepicker.start_date.date().toPyDate() self.end_date = self.datepicker.end_date.date().toPyDate() self.con_type = self.control_typer.currentText() self.mode = self.mode_typer.currentText() self.sub_typer.clear() # NOTE: lookup subtypes sub_types = ControlType.query(name=self.con_type).get_subtypes(mode=self.mode) if sub_types != []: # NOTE: block signal that will rerun controls getter and update sub_typer with QSignalBlocker(self.sub_typer) as blocker: self.sub_typer.addItems(sub_types) self.sub_typer.setEnabled(True) self.sub_typer.currentTextChanged.connect(self.chart_maker) else: self.sub_typer.clear() self.sub_typer.setEnabled(False) self.chart_maker() self.report.add_result(report) def chart_maker(self): """ Creates plotly charts for webview """ self.chart_maker_function() def chart_maker_function(self): """ Create html chart for controls reporting Args: obj (QMainWindow): original app window Returns: Tuple[QMainWindow, dict]: Collection of new main app window and result dict """ report = Report() # logger.debug(f"Control getter context: \n\tControl type: {self.con_type}\n\tMode: {self.mode}\n\tStart Date: {self.start_date}\n\tEnd Date: {self.end_date}") # NOTE: set the subtype for kraken if self.sub_typer.currentText() == "": self.subtype = None else: self.subtype = self.sub_typer.currentText() # logger.debug(f"Subtype: {self.subtype}") # NOTE: query all controls using the type/start and end dates from the gui controls = Control.query(control_type=self.con_type, start_date=self.start_date, end_date=self.end_date) # NOTE: if no data found from query set fig to none for reporting in webview if controls is None: fig = None else: # NOTE: change each control to list of dictionaries data = [control.convert_by_mode(mode=self.mode) for control in controls] # NOTE: flatten data to one dimensional list data = [item for sublist in data for item in sublist] # logger.debug(f"Control objects going into df conversion: {type(data)}") if not data: self.report.add_result(Result(status="Critical", msg="No data found for controls in given date range.")) return # NOTE send to dataframe creator df = convert_data_list_to_df(input=data, subtype=self.subtype) if self.subtype is None: title = self.mode else: title = f"{self.mode} - {self.subtype}" # NOTE: send dataframe to chart maker fig = create_charts(ctx=self.app.ctx, df=df, ytitle=title) # logger.debug(f"Updating figure...") # NOTE: construct html for webview html = construct_html(figure=fig) # logger.debug(f"The length of html code is: {len(html)}") self.webengineview.setHtml(html) self.webengineview.update() # logger.debug("Figure updated... I hope.") self.report.add_result(report) class ControlsDatePicker(QWidget): """ custom widget to pick start and end dates for controls graphs """ def __init__(self) -> None: super().__init__() self.start_date = QDateEdit(calendarPopup=True) # NOTE: start date is two months prior to end date by default twomonthsago = QDate.currentDate().addDays(-60) self.start_date.setDate(twomonthsago) self.end_date = QDateEdit(calendarPopup=True) self.end_date.setDate(QDate.currentDate()) self.layout = QHBoxLayout() self.layout.addWidget(QLabel("Start Date")) self.layout.addWidget(self.start_date) self.layout.addWidget(QLabel("End Date")) self.layout.addWidget(self.end_date) self.setLayout(self.layout) self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) def sizeHint(self) -> QSize: return QSize(80,20)