X-Git-Url: https://gerrit.fd.io/r/gitweb?a=blobdiff_plain;f=csit.infra.dash%2Fapp%2Fcdash%2Fnews%2Flayout.py;h=da36b1430cd2017a96516100a491f81f32032d2a;hb=c31372861134f29ae6eec8d98874e030e57ab5f1;hp=dfe6eba67ad56be85bb331b4fa34fbef9d4780dd;hpb=af8e703eb180e46ca65ff0c165a21f2261896548;p=csit.git diff --git a/csit.infra.dash/app/cdash/news/layout.py b/csit.infra.dash/app/cdash/news/layout.py index dfe6eba67a..da36b1430c 100644 --- a/csit.infra.dash/app/cdash/news/layout.py +++ b/csit.infra.dash/app/cdash/news/layout.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 Cisco and/or its affiliates. +# Copyright (c) 2023 Cisco and/or its affiliates. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at: @@ -14,7 +14,6 @@ """Plotly Dash HTML layout override. """ -import logging import pandas as pd import dash_bootstrap_components as dbc @@ -22,14 +21,11 @@ from flask import Flask from dash import dcc from dash import html from dash import callback_context -from dash import Input, Output -from yaml import load, FullLoader, YAMLError +from dash import Input, Output, State -from ..data.data import Data from ..utils.constants import Constants as C -from ..utils.utils import classify_anomalies, show_tooltip, gen_new_url +from ..utils.utils import classify_anomalies, gen_new_url from ..utils.url_processing import url_decode -from ..data.data import Data from .tables import table_summary @@ -37,8 +33,13 @@ class Layout: """The layout of the dash app and the callbacks. """ - def __init__(self, app: Flask, html_layout_file: str, data_spec_file: str, - tooltip_file: str) -> None: + def __init__( + self, + app: Flask, + data_stats: pd.DataFrame, + data_trending: pd.DataFrame, + html_layout_file: str + ) -> None: """Initialization: - save the input parameters, - read and pre-process the data, @@ -47,34 +48,22 @@ class Layout: - read tooltips from the tooltip file. :param app: Flask application running the dash application. + :param data_stats: Pandas dataframe with staistical data. + :param data_trending: Pandas dataframe with trending data. :param html_layout_file: Path and name of the file specifying the HTML layout of the dash application. - :param data_spec_file: Path and name of the file specifying the data to - be read from parquets for this application. - :param tooltip_file: Path and name of the yaml file specifying the - tooltips. :type app: Flask + :type data_stats: pandas.DataFrame + :type data_trending: pandas.DataFrame :type html_layout_file: str - :type data_spec_file: str - :type tooltip_file: str """ # Inputs self._app = app self._html_layout_file = html_layout_file - self._data_spec_file = data_spec_file - self._tooltip_file = tooltip_file - - # Read the data: - data_stats, data_mrr, data_ndrpdr = Data( - data_spec_file=self._data_spec_file, - debug=True - ).read_stats(days=C.NEWS_TIME_PERIOD) - - df_tst_info = pd.concat([data_mrr, data_ndrpdr], ignore_index=True) # Prepare information for the control panel: - self._jobs = sorted(list(df_tst_info["job"].unique())) + self._jobs = sorted(list(data_trending["job"].unique())) d_job_info = { "job": list(), "dut": list(), @@ -115,7 +104,7 @@ class Layout: } for job in self._jobs: # Create lists of failed tests: - df_job = df_tst_info.loc[(df_tst_info["job"] == job)] + df_job = data_trending.loc[(data_trending["job"] == job)] last_build = str(max(pd.to_numeric(df_job["build"].unique()))) df_build = df_job.loc[(df_job["build"] == last_build)] tst_info["job"].append(job) @@ -226,7 +215,6 @@ class Layout: # Read from files: self._html_layout = str() - self._tooltips = dict() try: with open(self._html_layout_file, "r") as file_read: @@ -236,23 +224,8 @@ class Layout: f"Not possible to open the file {self._html_layout_file}\n{err}" ) - try: - with open(self._tooltip_file, "r") as file_read: - self._tooltips = load(file_read, Loader=FullLoader) - except IOError as err: - logging.warning( - f"Not possible to open the file {self._tooltip_file}\n{err}" - ) - except YAMLError as err: - logging.warning( - f"An error occurred while parsing the specification file " - f"{self._tooltip_file}\n{err}" - ) - self._default_period = C.NEWS_SHORT self._default_active = (False, True, False) - self._default_table = \ - table_summary(self._data, self._jobs, self._default_period) # Callbacks: if self._app is not None and hasattr(self, 'callbacks'): @@ -286,7 +259,7 @@ class Layout: id="row-navbar", class_name="g-0", children=[ - self._add_navbar(), + self._add_navbar() ] ), dbc.Row( @@ -294,7 +267,7 @@ class Layout: class_name="g-0", children=[ self._add_ctrl_col(), - self._add_plotting_col(), + self._add_plotting_col() ] ) ] @@ -305,10 +278,10 @@ class Layout: children=[ dbc.Alert( [ - "An Error Occured", + "An Error Occured" ], - color="danger", - ), + color="danger" + ) ] ) @@ -335,7 +308,7 @@ class Layout: brand_href="/", brand_external_link=True, class_name="p-2", - fluid=True, + fluid=True ) def _add_ctrl_col(self) -> dbc.Col: @@ -357,62 +330,31 @@ class Layout: :returns: Column with tables. :rtype: dbc.Col """ - return dbc.Col( id="col-plotting-area", children=[ - dcc.Loading( + dbc.Spinner( children=[ - dbc.Row( # Failed tests - id="row-table", - class_name="g-0 p-2", - children=self._default_table - ), dbc.Row( - class_name="g-0 p-2", - align="center", - justify="start", + id="plotting-area", + class_name="g-0 p-0", children=[ - dbc.InputGroup( - class_name="me-1", - children=[ - dbc.InputGroupText( - style=C.URL_STYLE, - children=show_tooltip( - self._tooltips, - "help-url", "URL", - "input-url" - ) - ), - dbc.Input( - id="input-url", - readonly=True, - type="url", - style=C.URL_STYLE, - value="" - ) - ] - ) + C.PLACEHOLDER ] ) ] ) ], - width=9, + width=9 ) - def _add_ctrl_panel(self) -> dbc.Row: + def _add_ctrl_panel(self) -> list: """Add control panel. :returns: Control panel. - :rtype: dbc.Row + :rtype: list """ return [ - dbc.Label( - class_name="g-0 p-1", - children=show_tooltip(self._tooltips, - "help-summary-period", "Window") - ), dbc.Row( class_name="g-0 p-1", children=[ @@ -447,6 +389,59 @@ class Layout: ) ] + def _get_plotting_area( + self, + period: int, + url: str + ) -> list: + """Generate the plotting area with all its content. + + :param period: The time period for summary tables. + :param url: URL to be displayed in the modal window. + :type period: int + :type url: str + :returns: The content of the plotting area. + :rtype: list + """ + return [ + dbc.Row( + id="row-table", + class_name="g-0 p-1", + children=table_summary(self._data, self._jobs, period) + ), + dbc.Row( + [ + dbc.Col([html.Div( + [ + dbc.Button( + id="plot-btn-url", + children="Show URL", + class_name="me-1", + color="info", + style={ + "text-transform": "none", + "padding": "0rem 1rem" + } + ), + dbc.Modal( + [ + dbc.ModalHeader(dbc.ModalTitle("URL")), + dbc.ModalBody(url) + ], + id="plot-mod-url", + size="xl", + is_open=False, + scrollable=True + ) + ], + className=\ + "d-grid gap-0 d-md-flex justify-content-md-end" + )]) + ], + class_name="g-0 p-0" + ) + ] + def callbacks(self, app): """Callbacks for the whole application. @@ -455,26 +450,22 @@ class Layout: """ @app.callback( - Output("row-table", "children"), - Output("input-url", "value"), + Output("plotting-area", "children"), Output("period-last", "active"), Output("period-short", "active"), Output("period-long", "active"), + Input("url", "href"), Input("period-last", "n_clicks"), Input("period-short", "n_clicks"), - Input("period-long", "n_clicks"), - Input("url", "href") + Input("period-long", "n_clicks") ) - def _update_application(btn_last: int, btn_short: int, btn_long: int, - href: str) -> tuple: + def _update_application(href: str, *_) -> tuple: """Update the application when the event is detected. :returns: New values for web page elements. :rtype: tuple """ - _, _, _ = btn_last, btn_short, btn_long - periods = { "period-last": C.NEWS_LAST, "period-short": C.NEWS_SHORT, @@ -497,12 +488,23 @@ class Layout: if trigger_id == "url" and url_params: trigger_id = url_params.get("period", list())[0] - period = periods.get(trigger_id, self._default_period) - active = actives.get(trigger_id, self._default_active) - ret_val = [ - table_summary(self._data, self._jobs, period), - gen_new_url(parsed_url, {"period": trigger_id}) + self._get_plotting_area( + periods.get(trigger_id, self._default_period), + gen_new_url(parsed_url, {"period": trigger_id}) + ) ] - ret_val.extend(active) + ret_val.extend(actives.get(trigger_id, self._default_active)) return ret_val + + @app.callback( + Output("plot-mod-url", "is_open"), + [Input("plot-btn-url", "n_clicks")], + [State("plot-mod-url", "is_open")], + ) + def toggle_plot_mod_url(n, is_open): + """Toggle the modal window with url. + """ + if n: + return not is_open + return is_open