feat(uti): Refactor styling
[csit.git] / resources / tools / dash / app / pal / trending / layout.py
index 6369a02..66af2f0 100644 (file)
 """Plotly Dash HTML layout override.
 """
 
 """Plotly Dash HTML layout override.
 """
 
-
 import pandas as pd
 
 from dash import dcc
 from dash import html
 from dash import callback_context, no_update
 import pandas as pd
 
 from dash import dcc
 from dash import html
 from dash import callback_context, no_update
-from dash import Input, Output, State, callback
+from dash import Input, Output, State
 from dash.exceptions import PreventUpdate
 from dash.exceptions import PreventUpdate
+import dash_bootstrap_components as dbc
 from yaml import load, FullLoader, YAMLError
 from datetime import datetime, timedelta
 
 from ..data.data import Data
 from yaml import load, FullLoader, YAMLError
 from datetime import datetime, timedelta
 
 from ..data.data import Data
-from .graphs import trending_tput
+from .graphs import graph_trending, graph_hdrh_latency, \
+    select_trending_data
 
 
 class Layout:
     """
     """
 
 
 
 class Layout:
     """
     """
 
+    STYLE_HIDEN = {"display": "none"}
+    STYLE_BLOCK = {"display": "block", "vertical-align": "top"}
+    STYLE_INLINE ={
+        "display": "inline-block",
+        "vertical-align": "top"
+    }
+    NO_GRAPH = {"data": [], "layout": {}, "frames": []}
+
     def __init__(self, app, html_layout_file, spec_file, graph_layout_file,
         data_spec_file):
         """
     def __init__(self, app, html_layout_file, spec_file, graph_layout_file,
         data_spec_file):
         """
@@ -128,6 +137,7 @@ class Layout:
                 id="div-main",
                 children=[
                     dcc.Store(id="selected-tests"),
                 id="div-main",
                 children=[
                     dcc.Store(id="selected-tests"),
+                    self._add_navbar(),
                     self._add_ctrl_div(),
                     self._add_plotting_div()
                 ]
                     self._add_ctrl_div(),
                     self._add_plotting_div()
                 ]
@@ -138,6 +148,22 @@ class Layout:
             children="An Error Occured."
         )
 
             children="An Error Occured."
         )
 
+    def _add_navbar(self):
+        """Add nav element with navigation panel. It is placed on the top.
+        """
+        return dbc.NavbarSimple(
+            children=[
+                dbc.NavItem(
+                    dbc.NavLink("Continuous Performance Trending", href="#")
+                )
+            ],
+            brand="Dashboard",
+            brand_href="/",
+            color="dark",
+            dark=True,
+            fluid=True,
+        )
+
     def _add_ctrl_div(self):
         """Add div with controls. It is placed on the left side.
         """
     def _add_ctrl_div(self):
         """Add div with controls. It is placed on the left side.
         """
@@ -154,8 +180,7 @@ class Layout:
             ],
             style={
                 "display": "inline-block",
             ],
             style={
                 "display": "inline-block",
-                "width": "18%",
-                "padding": "5px"
+                "width": "20%"
             }
         )
 
             }
         )
 
@@ -165,21 +190,107 @@ class Layout:
         return html.Div(
             id="div-plotting-area",
             children=[
         return html.Div(
             id="div-plotting-area",
             children=[
-                dcc.Loading(
-                    id="loading-graph",
-                    children=[
-                        dcc.Graph(
-                            id="graph"
-                        )
-                    ],
-                    type="circle"
-                )
+                html.Table(children=[
+                    html.Tr(
+                        id="div-tput",
+                        style=self.STYLE_HIDEN,
+                        children=[
+                            html.Td(children=[
+                                dcc.Loading(
+                                    dcc.Graph(
+                                        id="graph-tput"
+                                    ),
+                                )
+                            ], style={"width": "80%"}),
+                            html.Td(children=[
+                                dcc.Clipboard(
+                                    target_id="tput-metadata",
+                                    title="Copy",
+                                    style={"display": "inline-block"}
+                                ),
+                                html.Nobr(" "),
+                                html.Nobr(" "),
+                                dcc.Markdown(
+                                    children="**Throughput**",
+                                    style={"display": "inline-block"}
+                                ),
+                                html.Pre(
+                                    id="tput-metadata",
+                                    children="Click on data point in the graph"
+                                ),
+                                html.Div(
+                                    id="div-lat-metadata",
+                                    style=self.STYLE_HIDEN,
+                                    children=[
+                                        dcc.Clipboard(
+                                            target_id="lat-metadata",
+                                            title="Copy",
+                                            style={"display": "inline-block"}
+                                        ),
+                                        html.Nobr(" "),
+                                        html.Nobr(" "),
+                                        dcc.Markdown(
+                                            children="**Latency**",
+                                            style={"display": "inline-block"}
+                                        ),
+                                        html.Pre(
+                                            id="lat-metadata",
+                                            children= \
+                                            "Click on data point in the graph"
+                                        )
+                                    ]
+                                )
+                            ], style={"width": "20%"}),
+                        ]
+                    ),
+                    html.Tr(
+                        id="div-latency",
+                        style=self.STYLE_HIDEN,
+                        children=[
+                            html.Td(children=[
+                                dcc.Loading(
+                                    dcc.Graph(
+                                        id="graph-latency"
+                                    )
+                                )
+                            ], style={"width": "80%"}),
+                            html.Td(children=[
+                                dcc.Loading(
+                                    dcc.Graph(
+                                        id="graph-latency-hdrh",
+                                        style=self.STYLE_INLINE,
+                                        figure=self.NO_GRAPH
+                                    )
+                                )
+                            ], style={"width": "20%"}),
+                        ]
+                    ),
+                    html.Tr(
+                        id="div-download",
+                        style=self.STYLE_HIDEN,
+                        children=[
+                            html.Td(children=[
+                                dcc.Loading(
+                                    children=[
+                                        html.Button(
+                                            id="btn-download-data",
+                                            children=["Download Data"]
+                                        ),
+                                        dcc.Download(id="download-data")
+                                    ]
+                                )
+                            ], style={"width": "80%"}),
+                            html.Td(children=[
+                                html.Nobr(" ")
+                            ], style={"width": "20%"}),
+                        ]
+                    ),
+                ]),
             ],
             style={
                 "vertical-align": "top",
             ],
             style={
                 "vertical-align": "top",
-                "display": "none",
-                "width": "80%",
-                "padding": "5px"
+                "display": "inline-block",
+                "width": "80%"
             }
         )
 
             }
         )
 
@@ -458,13 +569,17 @@ class Layout:
             return _sync_checklists(opt, sel, all, "cl-ctrl-testtype")
 
         @app.callback(
             return _sync_checklists(opt, sel, all, "cl-ctrl-testtype")
 
         @app.callback(
-            Output("graph", "figure"),
+            Output("graph-tput", "figure"),
+            Output("graph-latency", "figure"),
+            Output("div-tput", "style"),
+            Output("div-latency", "style"),
+            Output("div-lat-metadata", "style"),
+            Output("div-download", "style"),
             Output("selected-tests", "data"),  # Store
             Output("cl-selected", "options"),  # User selection
             Output("dd-ctrl-phy", "value"),
             Output("dd-ctrl-area", "value"),
             Output("dd-ctrl-test", "value"),
             Output("selected-tests", "data"),  # Store
             Output("cl-selected", "options"),  # User selection
             Output("dd-ctrl-phy", "value"),
             Output("dd-ctrl-area", "value"),
             Output("dd-ctrl-test", "value"),
-            Output("div-plotting-area", "style"),
             State("selected-tests", "data"),  # Store
             State("cl-selected", "value"),
             State("dd-ctrl-phy", "value"),
             State("selected-tests", "data"),  # Store
             State("cl-selected", "value"),
             State("dd-ctrl-phy", "value"),
@@ -498,24 +613,46 @@ class Layout:
                 else:
                     return list()
 
                 else:
                     return list()
 
+            class RetunValue:
+                def __init__(self) -> None:
+                    self._output = {
+                        "graph-tput-figure": no_update,
+                        "graph-lat-figure": no_update,
+                        "div-tput-style": no_update,
+                        "div-latency-style": no_update,
+                        "div-lat-metadata-style": no_update,
+                        "div-download-style": no_update,
+                        "selected-tests-data": no_update,
+                        "cl-selected-options": no_update,
+                        "dd-ctrl-phy-value": no_update,
+                        "dd-ctrl-area-value": no_update,
+                        "dd-ctrl-test-value": no_update,
+                    }
+
+                def value(self):
+                    return tuple(self._output.values())
+
+                def set_values(self, kwargs: dict) -> None:
+                    for key, val in kwargs.items():
+                        if key in self._output:
+                            self._output[key] = val
+                        else:
+                            raise KeyError(f"The key {key} is not defined.")
+
+
             trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
 
             trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
 
-            d_start = datetime(
-                 int(d_start[0:4]), int(d_start[5:7]), int(d_start[8:10])
-            )
-            d_end = datetime(
-                 int(d_end[0:4]), int(d_end[5:7]), int(d_end[8:10])
-            )
+            d_start = datetime(int(d_start[0:4]), int(d_start[5:7]),
+                int(d_start[8:10]))
+            d_end = datetime(int(d_end[0:4]), int(d_end[5:7]), int(d_end[8:10]))
+
+            output = RetunValue()
 
             if trigger_id == "btn-ctrl-add":
                 # Add selected test to the list of tests in store:
                 if phy and area and test and cores and framesizes and testtypes:
 
             if trigger_id == "btn-ctrl-add":
                 # Add selected test to the list of tests in store:
                 if phy and area and test and cores and framesizes and testtypes:
-
-                    # TODO: Add validation
-
                     if store_sel is None:
                         store_sel = list()
                     if store_sel is None:
                         store_sel = list()
-
                     for core in cores:
                         for framesize in framesizes:
                             for ttype in testtypes:
                     for core in cores:
                         for framesize in framesizes:
                             for ttype in testtypes:
@@ -537,15 +674,32 @@ class Layout:
                                         "core": core.lower(),
                                         "testtype": ttype.lower()
                                     })
                                         "core": core.lower(),
                                         "testtype": ttype.lower()
                                     })
-                return (no_update, store_sel, _list_tests(), None,
-                    None, None, no_update)
+                output.set_values({
+                    "selected-tests-data": store_sel,
+                    "cl-selected-options": _list_tests(),
+                    "dd-ctrl-phy-value": None,
+                    "dd-ctrl-area-value": None,
+                    "dd-ctrl-test-value": None,
+                })
 
             elif trigger_id in ("btn-sel-display", "dpr-period"):
 
             elif trigger_id in ("btn-sel-display", "dpr-period"):
-                fig, style = trending_tput(
+                fig_tput, fig_lat = graph_trending(
                     self.data, store_sel, self.layout, d_start, d_end
                 )
                     self.data, store_sel, self.layout, d_start, d_end
                 )
-                return (fig, no_update, no_update,
-                    no_update, no_update, no_update, style)
+                output.set_values({
+                    "graph-tput-figure": \
+                        fig_tput if fig_tput else self.NO_GRAPH,
+                    "graph-lat-figure": \
+                        fig_lat if fig_lat else self.NO_GRAPH,
+                    "div-tput-style": \
+                        self.STYLE_BLOCK if fig_tput else self.STYLE_HIDEN,
+                    "div-latency-style": \
+                        self.STYLE_BLOCK if fig_lat else self.STYLE_HIDEN,
+                    "div-lat-metadata-style": \
+                        self.STYLE_BLOCK if fig_lat else self.STYLE_HIDEN,
+                    "div-download-style": \
+                        self.STYLE_BLOCK if fig_tput else self.STYLE_HIDEN,
+                })
 
             elif trigger_id == "btn-sel-remove":
                 if list_sel:
 
             elif trigger_id == "btn-sel-remove":
                 if list_sel:
@@ -555,17 +709,92 @@ class Layout:
                             new_store_sel.append(item)
                     store_sel = new_store_sel
                 if store_sel:
                             new_store_sel.append(item)
                     store_sel = new_store_sel
                 if store_sel:
-                    fig, style = trending_tput(
+                    fig_tput, fig_lat = graph_trending(
                         self.data, store_sel, self.layout, d_start, d_end
                     )
                         self.data, store_sel, self.layout, d_start, d_end
                     )
-                    return (fig, store_sel, _list_tests(),
-                    no_update, no_update, no_update, style)
+                    output.set_values({
+                        "graph-tput-figure": \
+                            fig_tput if fig_tput else self.NO_GRAPH,
+                        "graph-lat-figure": \
+                            fig_lat if fig_lat else self.NO_GRAPH,
+                        "div-tput-style": \
+                            self.STYLE_BLOCK if fig_tput else self.STYLE_HIDEN,
+                        "div-latency-style": \
+                            self.STYLE_BLOCK if fig_lat else self.STYLE_HIDEN,
+                        "div-lat-metadata-style": \
+                            self.STYLE_BLOCK if fig_lat else self.STYLE_HIDEN,
+                        "div-download-style": \
+                            self.STYLE_BLOCK if fig_tput else self.STYLE_HIDEN,
+                        "selected-tests-data": store_sel,
+                        "cl-selected-options": _list_tests()
+                    })
                 else:
                 else:
-                    style={
-                        "vertical-align": "top",
-                        "display": "none",
-                        "width": "80%",
-                        "padding": "5px"
-                    }
-                    return (no_update, store_sel, _list_tests(),
-                        no_update, no_update, no_update, style)
+                    output.set_values({
+                        "graph-tput-figure": self.NO_GRAPH,
+                        "graph-lat-figure": self.NO_GRAPH,
+                        "div-tput-style": self.STYLE_HIDEN,
+                        "div-latency-style": self.STYLE_HIDEN,
+                        "div-lat-metadata-style": self.STYLE_HIDEN,
+                        "div-download-style": self.STYLE_HIDEN,
+                        "selected-tests-data": store_sel,
+                        "cl-selected-options": _list_tests()
+                    })
+
+            return output.value()
+
+        @app.callback(
+            Output("tput-metadata", "children"),
+            Input("graph-tput", "clickData")
+        )
+        def _show_tput_metadata(hover_data):
+            """
+            """
+            if not hover_data:
+                raise PreventUpdate
+
+            return hover_data["points"][0]["text"].replace("<br>", "\n")
+
+        @app.callback(
+            Output("graph-latency-hdrh", "figure"),
+            Output("graph-latency-hdrh", "style"),
+            Output("lat-metadata", "children"),
+            Input("graph-latency", "clickData")
+        )
+        def _show_latency_hdhr(hover_data):
+            """
+            """
+            if not hover_data:
+                raise PreventUpdate
+
+            graph = no_update
+            hdrh_data = hover_data["points"][0].get("customdata", None)
+            if hdrh_data:
+                graph = graph_hdrh_latency(hdrh_data, self.layout)
+
+            return (
+                graph,
+                self.STYLE_INLINE,
+                hover_data["points"][0]["text"].replace("<br>", "\n")
+            )
+
+        @app.callback(
+            Output("download-data", "data"),
+            State("selected-tests", "data"),
+            Input("btn-download-data", "n_clicks"),
+            prevent_initial_call=True
+        )
+        def _download_data(store_sel, n_clicks):
+            """
+            """
+
+            if not n_clicks:
+                raise PreventUpdate
+
+            df = pd.DataFrame()
+            for itm in store_sel:
+                sel_data = select_trending_data(self.data, itm)
+                if sel_data is None:
+                    continue
+                df = pd.concat([df, sel_data], ignore_index=True)
+
+            return dcc.send_data_frame(df.to_csv, "trending_data.csv")