C-Dash: Add documentation and other items to the navbar
[csit.git] / csit.infra.dash / app / cdash / report / layout.py
index d4720d9..06b5d46 100644 (file)
@@ -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:
@@ -31,10 +31,9 @@ from ..utils.constants import Constants as C
 from ..utils.control_panel import ControlPanel
 from ..utils.trigger import Trigger
 from ..utils.utils import show_tooltip, label, sync_checklists, gen_new_url, \
-    generate_options, get_list_group_items
+    generate_options, get_list_group_items, graph_hdrh_latency
 from ..utils.url_processing import url_decode
-from ..data.data import Data
-from .graphs import graph_iterative, get_short_version, select_iterative_data
+from .graphs import graph_iterative, select_iterative_data
 
 
 # Control panel partameters and their default values.
@@ -76,8 +75,14 @@ class Layout:
     """The layout of the dash app and the callbacks.
     """
 
-    def __init__(self, app: Flask, releases: list, html_layout_file: str,
-        graph_layout_file: str, data_spec_file: str, tooltip_file: str) -> None:
+    def __init__(
+            self,
+            app: Flask,
+            data_iterative: pd.DataFrame,
+            html_layout_file: str,
+            graph_layout_file: str,
+            tooltip_file: str
+        ) -> None:
         """Initialization:
         - save the input parameters,
         - read and pre-process the data,
@@ -86,60 +91,41 @@ class Layout:
         - read tooltips from the tooltip file.
 
         :param app: Flask application running the dash application.
-        :param releases: Lis of releases to be displayed.
         :param html_layout_file: Path and name of the file specifying the HTML
             layout of the dash application.
         :param graph_layout_file: Path and name of the file with layout of
             plot.ly graphs.
-        :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 releases: list
         :type html_layout_file: str
         :type graph_layout_file: str
-        :type data_spec_file: str
         :type tooltip_file: str
         """
 
         # Inputs
         self._app = app
-        self.releases = releases
         self._html_layout_file = html_layout_file
         self._graph_layout_file = graph_layout_file
-        self._data_spec_file = data_spec_file
         self._tooltip_file = tooltip_file
-
-        # Read the data:
-        self._data = pd.DataFrame()
-        for rls in releases:
-            data_mrr = Data(self._data_spec_file, True).\
-                read_iterative_mrr(release=rls.replace("csit", "rls"))
-            data_mrr["release"] = rls
-            data_ndrpdr = Data(self._data_spec_file, True).\
-                read_iterative_ndrpdr(release=rls.replace("csit", "rls"))
-            data_ndrpdr["release"] = rls
-            self._data = pd.concat(
-                [self._data, data_mrr, data_ndrpdr],
-                ignore_index=True
-            )
+        self._data = data_iterative
 
         # Get structure of tests:
         tbs = dict()
-        cols = ["job", "test_id", "test_type", "dut_version", "release"]
+        cols = [
+            "job", "test_id", "test_type", "dut_version", "tg_type", "release"
+        ]
         for _, row in self._data[cols].drop_duplicates().iterrows():
             rls = row["release"]
-            ttype = row["test_type"]
             lst_job = row["job"].split("-")
             dut = lst_job[1]
-            d_ver = get_short_version(row["dut_version"], dut)
+            d_ver = row["dut_version"]
             tbed = "-".join(lst_job[-2:])
             lst_test_id = row["test_id"].split(".")
             if dut == "dpdk":
                 area = "dpdk"
             else:
-                area = "-".join(lst_test_id[3:-2])
+                area = ".".join(lst_test_id[3:-2])
             suite = lst_test_id[-2].replace("2n1l-", "").replace("1n1l-", "").\
                 replace("2n-", "")
             test = lst_test_id[-1]
@@ -182,18 +168,31 @@ class Layout:
                 tbs[rls][dut][d_ver][infra][area][test]["frame-size"].append(
                     framesize.upper()
                 )
-            if ttype == "mrr":
+            if row["test_type"] == "mrr":
                 if "MRR" not in \
                         tbs[rls][dut][d_ver][infra][area][test]["test-type"]:
                     tbs[rls][dut][d_ver][infra][area][test]["test-type"].append(
                         "MRR"
                     )
-            elif ttype == "ndrpdr":
+            elif row["test_type"] == "ndrpdr":
                 if "NDR" not in \
                         tbs[rls][dut][d_ver][infra][area][test]["test-type"]:
                     tbs[rls][dut][d_ver][infra][area][test]["test-type"].extend(
                         ("NDR", "PDR", )
                     )
+            elif row["test_type"] == "hoststack" and \
+                    row["tg_type"] in ("iperf", "vpp"):
+                if "BPS" not in \
+                        tbs[rls][dut][d_ver][infra][area][test]["test-type"]:
+                    tbs[rls][dut][d_ver][infra][area][test]["test-type"].append(
+                        "BPS"
+                    )
+            elif row["test_type"] == "hoststack" and row["tg_type"] == "ab":
+                if "CPS" not in \
+                        tbs[rls][dut][d_ver][infra][area][test]["test-type"]:
+                    tbs[rls][dut][d_ver][infra][area][test]["test-type"].extend(
+                        ("CPS", "RPS")
+                    )
         self._spec_tbs = tbs
 
         # Read from files:
@@ -280,6 +279,32 @@ class Layout:
                             self._add_ctrl_col(),
                             self._add_plotting_col()
                         ]
+                    ),
+                    dbc.Spinner(
+                        dbc.Offcanvas(
+                            class_name="w-50",
+                            id="offcanvas-metadata",
+                            title="Throughput And Latency",
+                            placement="end",
+                            is_open=False,
+                            children=[
+                                dbc.Row(id="metadata-tput-lat"),
+                                dbc.Row(id="metadata-hdrh-graph")
+                            ]
+                        ),
+                        delay_show=C.SPINNER_DELAY
+                    ),
+                    dbc.Offcanvas(
+                        class_name="w-75",
+                        id="offcanvas-documentation",
+                        title="Documentation",
+                        placement="end",
+                        is_open=False,
+                        children=html.Iframe(
+                            src=C.URL_DOC_REL_NOTES,
+                            width="100%",
+                            height="100%"
+                        )
                     )
                 ]
             )
@@ -305,14 +330,26 @@ class Layout:
         return dbc.NavbarSimple(
             id="navbarsimple-main",
             children=[
-                dbc.NavItem(
-                    dbc.NavLink(
-                        C.REPORT_TITLE,
-                        disabled=True,
-                        external_link=True,
-                        href="#"
-                    )
-                )
+                dbc.NavItem(dbc.NavLink(
+                    C.REPORT_TITLE,
+                    active=True,
+                    external_link=True,
+                    href="/report"
+                )),
+                dbc.NavItem(dbc.NavLink(
+                    "Comparisons",
+                    external_link=True,
+                    href="/comparisons"
+                )),
+                dbc.NavItem(dbc.NavLink(
+                    "Coverage Data",
+                    external_link=True,
+                    href="/coverage"
+                )),
+                dbc.NavItem(dbc.NavLink(
+                    "Documentation",
+                    id="btn-documentation",
+                ))
             ],
             brand=C.BRAND,
             brand_href="/",
@@ -343,7 +380,7 @@ class Layout:
         return dbc.Col(
             id="col-plotting-area",
             children=[
-                dcc.Loading(
+                dbc.Spinner(
                     children=[
                         dbc.Row(
                             id="plotting-area",
@@ -503,132 +540,164 @@ class Layout:
             dbc.Row(
                 class_name="g-0 p-1",
                 children=[
-                    dbc.Label(
-                        children=show_tooltip(
-                            self._tooltips,
-                            "help-framesize",
-                            "Frame Size"
-                        )
-                    ),
-                    dbc.Col(
-                        children=[
-                            dbc.Checklist(
-                                id={"type": "ctrl-cl", "index": "frmsize-all"},
-                                options=C.CL_ALL_DISABLED,
-                                inline=True,
-                                switch=False,
-                                input_class_name="border-info bg-info"
+                    dbc.InputGroup(
+                        [
+                            dbc.InputGroupText(
+                                children=show_tooltip(
+                                    self._tooltips,
+                                    "help-framesize",
+                                    "Frame Size"
+                                )
+                            ),
+                            dbc.Col(
+                                children=[
+                                    dbc.Checklist(
+                                        id={
+                                            "type": "ctrl-cl",
+                                            "index": "frmsize-all"
+                                        },
+                                        options=C.CL_ALL_DISABLED,
+                                        inline=True,
+                                        class_name="ms-2"
+                                    )
+                                ],
+                                width=2
+                            ),
+                            dbc.Col(
+                                children=[
+                                    dbc.Checklist(
+                                        id={
+                                            "type": "ctrl-cl",
+                                            "index": "frmsize"
+                                        },
+                                        inline=True
+                                    )
+                                ]
                             )
                         ],
-                        width=3
-                    ),
-                    dbc.Col(
-                        children=[
-                            dbc.Checklist(
-                                id={"type": "ctrl-cl", "index": "frmsize"},
-                                inline=True,
-                                switch=False,
-                                input_class_name="border-info bg-info"
-                            )
-                        ]
+                        style={"align-items": "center"},
+                        size="sm"
                     )
                 ]
             ),
             dbc.Row(
                 class_name="g-0 p-1",
                 children=[
-                    dbc.Label(
-                        children=show_tooltip(
-                            self._tooltips,
-                            "help-cores",
-                            "Number of Cores"
-                        )
-                    ),
-                    dbc.Col(
-                        children=[
-                            dbc.Checklist(
-                                id={"type": "ctrl-cl", "index": "core-all"},
-                                options=C.CL_ALL_DISABLED,
-                                inline=False,
-                                switch=False,
-                                input_class_name="border-info bg-info"
+                    dbc.InputGroup(
+                        [
+                            dbc.InputGroupText(
+                                children=show_tooltip(
+                                    self._tooltips,
+                                    "help-cores",
+                                    "Number of Cores"
+                                )
+                            ),
+                            dbc.Col(
+                                children=[
+                                    dbc.Checklist(
+                                        id={
+                                            "type": "ctrl-cl",
+                                            "index": "core-all"
+                                        },
+                                        options=C.CL_ALL_DISABLED,
+                                        inline=True,
+                                        class_name="ms-2"
+                                    )
+                                ],
+                                width=2
+                            ),
+                            dbc.Col(
+                                children=[
+                                    dbc.Checklist(
+                                        id={
+                                            "type": "ctrl-cl",
+                                            "index": "core"
+                                        },
+                                        inline=True
+                                    )
+                                ]
                             )
                         ],
-                        width=3
-                    ),
-                    dbc.Col(
-                        children=[
-                            dbc.Checklist(
-                                id={"type": "ctrl-cl", "index": "core"},
-                                inline=True,
-                                switch=False,
-                                input_class_name="border-info bg-info"
-                            )
-                        ]
+                        style={"align-items": "center"},
+                        size="sm"
                     )
                 ]
             ),
             dbc.Row(
                 class_name="g-0 p-1",
                 children=[
-                    dbc.Label(
-                        children=show_tooltip(
-                            self._tooltips,
-                            "help-ttype",
-                            "Test Type"
-                        )
-                    ),
-                    dbc.Col(
-                        children=[
-                            dbc.Checklist(
-                                id={"type": "ctrl-cl", "index": "tsttype-all"},
-                                options=C.CL_ALL_DISABLED,
-                                inline=True,
-                                switch=False,
-                                input_class_name="border-info bg-info"
+                    dbc.InputGroup(
+                        [
+                            dbc.InputGroupText(
+                                children=show_tooltip(
+                                    self._tooltips,
+                                    "help-ttype",
+                                    "Test Type"
+                                )
+                            ),
+                            dbc.Col(
+                                children=[
+                                    dbc.Checklist(
+                                        id={
+                                            "type": "ctrl-cl",
+                                            "index": "tsttype-all"
+                                        },
+                                        options=C.CL_ALL_DISABLED,
+                                        inline=True,
+                                        class_name="ms-2"
+                                    )
+                                ],
+                                width=2
+                            ),
+                            dbc.Col(
+                                children=[
+                                    dbc.Checklist(
+                                        id={
+                                            "type": "ctrl-cl",
+                                            "index": "tsttype"
+                                        },
+                                        inline=True
+                                    )
+                                ]
                             )
                         ],
-                        width=3
-                    ),
-                    dbc.Col(
-                        children=[
-                            dbc.Checklist(
-                                id={"type": "ctrl-cl", "index": "tsttype"},
-                                inline=True,
-                                switch=False,
-                                input_class_name="border-info bg-info"
-                            )
-                        ]
+                        style={"align-items": "center"},
+                        size="sm"
                     )
                 ]
             ),
             dbc.Row(
                 class_name="g-0 p-1",
                 children=[
-                    dbc.Label(
-                        children=show_tooltip(
-                            self._tooltips,
-                            "help-normalize",
-                            "Normalize"
-                        )
-                    ),
-                    dbc.Col(
-                        children=[
-                            dbc.Checklist(
-                                id="normalize",
-                                options=[{
-                                    "value": "normalize",
-                                    "label": (
-                                        "Normalize results to CPU "
-                                        "frequency 2GHz"
-                                    )
-                                }],
-                                value=[],
-                                inline=True,
-                                switch=False,
-                                input_class_name="border-info bg-info"
+                    dbc.InputGroup(
+                        [
+                            dbc.InputGroupText(
+                                children=show_tooltip(
+                                    self._tooltips,
+                                    "help-normalize",
+                                    "Normalization"
+                                )
                             ),
-                        ]
+                            dbc.Col(
+                                children=[
+                                    dbc.Checklist(
+                                        id="normalize",
+                                        options=[{
+                                            "value": "normalize",
+                                            "label": (
+                                                "Normalize to CPU frequency "
+                                                "2GHz"
+                                            )
+                                        }],
+                                        value=[],
+                                        inline=True,
+                                        class_name="ms-2"
+                                    )
+                                ]
+                            )
+                        ],
+                        style={"align-items": "center"},
+                        size="sm"
                     )
                 ]
             ),
@@ -651,7 +720,7 @@ class Layout:
                         class_name="overflow-auto p-0",
                         id="lg-selected",
                         children=[],
-                        style={"max-height": "14em"},
+                        style={"max-height": "20em"},
                         flush=True
                     )
                 ]
@@ -743,7 +812,7 @@ class Layout:
                         [
                             dbc.Button(
                                 id="plot-btn-url",
-                                children="URL",
+                                children="Show URL",
                                 class_name="me-1",
                                 color="info",
                                 style={
@@ -874,7 +943,7 @@ class Layout:
                 try:
                     store_sel = literal_eval(url_params["store_sel"][0])
                     normalize = literal_eval(url_params["norm"][0])
-                except (KeyError, IndexError):
+                except (KeyError, IndexError, AttributeError):
                     pass
                 if store_sel:
                     row_card_sel_tests = C.STYLE_ENABLED
@@ -1158,7 +1227,7 @@ class Layout:
                     f"cl-{param}-val": val_sel,
                     f"cl-{param}-all-val": val_all,
                 })
-                if all((ctrl_panel.get("cl-core-val"), 
+                if all((ctrl_panel.get("cl-core-val"),
                         ctrl_panel.get("cl-frmsize-val"),
                         ctrl_panel.get("cl-tsttype-val"), )):
                     ctrl_panel.set({"btn-add-dis": False})
@@ -1219,7 +1288,9 @@ class Layout:
 
             if on_draw:
                 if store_sel:
-                    lg_selected = get_list_group_items(store_sel)
+                    lg_selected = get_list_group_items(
+                        store_sel, "sel-cl", add_index=True
+                    )
                     plotting_area = self._get_plotting_area(
                         store_sel,
                         bool(normalize),
@@ -1265,7 +1336,7 @@ class Layout:
             Input("plot-btn-download", "n_clicks"),
             prevent_initial_call=True
         )
-        def _download_trending_data(store_sel, _):
+        def _download_iterative_data(store_sel, _):
             """Download the data
 
             :param store_sel: List of tests selected by user stored in the
@@ -1287,3 +1358,122 @@ class Layout:
                 df = pd.concat([df, sel_data], ignore_index=True)
 
             return dcc.send_data_frame(df.to_csv, C.REPORT_DOWNLOAD_FILE_NAME)
+
+        @app.callback(
+            Output("metadata-tput-lat", "children"),
+            Output("metadata-hdrh-graph", "children"),
+            Output("offcanvas-metadata", "is_open"),
+            Input({"type": "graph", "index": ALL}, "clickData"),
+            prevent_initial_call=True
+        )
+        def _show_metadata_from_graphs(graph_data: dict) -> tuple:
+            """Generates the data for the offcanvas displayed when a particular
+            point in a graph is clicked on.
+
+            :param graph_data: The data from the clicked point in the graph.
+            :type graph_data: dict
+            :returns: The data to be displayed on the offcanvas and the
+                information to show the offcanvas.
+            :rtype: tuple(list, list, bool)
+            """
+
+            trigger = Trigger(callback_context.triggered)
+
+            try:
+                idx = 0 if trigger.idx == "tput" else 1
+                graph_data = graph_data[idx]["points"]
+            except (IndexError, KeyError, ValueError, TypeError):
+                raise PreventUpdate
+
+            def _process_stats(data: list, param: str) -> list:
+                """Process statistical data provided by plot.ly box graph.
+
+                :param data: Statistical data provided by plot.ly box graph.
+                :param param: Parameter saying if the data come from "tput" or
+                    "lat" graph.
+                :type data: list
+                :type param: str
+                :returns: Listo of tuples where the first value is the
+                    statistic's name and the secont one it's value.
+                :rtype: list
+                """
+                if len(data) == 7:
+                    stats = ("max", "upper fence", "q3", "median", "q1",
+                            "lower fence", "min")
+                elif len(data) == 9:
+                    stats = ("outlier", "max", "upper fence", "q3", "median",
+                            "q1", "lower fence", "min", "outlier")
+                elif len(data) == 1:
+                    if param == "lat":
+                        stats = ("Average Latency at 50% PDR", )
+                    else:
+                        stats = ("Throughput", )
+                else:
+                    return list()
+                unit = " [us]" if param == "lat" else str()
+                return [(f"{stat}{unit}", f"{value['y']:,.0f}")
+                        for stat, value in zip(stats, data)]
+
+            graph = list()
+            if trigger.idx == "tput":
+                title = "Throughput"
+            elif trigger.idx == "lat":
+                title = "Latency"
+                if len(graph_data) == 1:
+                    hdrh_data = graph_data[0].get("customdata", None)
+                    if hdrh_data:
+                        graph = [dbc.Card(
+                            class_name="gy-2 p-0",
+                            children=[
+                                dbc.CardHeader(hdrh_data.pop("name")),
+                                dbc.CardBody(children=[
+                                    dcc.Graph(
+                                        id="hdrh-latency-graph",
+                                        figure=graph_hdrh_latency(
+                                            hdrh_data, self._graph_layout
+                                        )
+                                    )
+                                ])
+                            ])
+                        ]
+            else:
+                raise PreventUpdate
+            metadata = [
+                dbc.Card(
+                    class_name="gy-2 p-0",
+                    children=[
+                        dbc.CardHeader(children=[
+                            dcc.Clipboard(
+                                target_id="tput-lat-metadata",
+                                title="Copy",
+                                style={"display": "inline-block"}
+                            ),
+                            title
+                        ]),
+                        dbc.CardBody(
+                            id="tput-lat-metadata",
+                            class_name="p-0",
+                            children=[dbc.ListGroup(
+                                [
+                                    dbc.ListGroupItem([dbc.Badge(k), v])
+                                        for k, v in _process_stats(
+                                            graph_data, trigger.idx)
+                                ],
+                                flush=True)
+                            ]
+                        )
+                    ]
+                )
+            ]
+
+            return metadata, graph, True
+
+        @app.callback(
+            Output("offcanvas-documentation", "is_open"),
+            Input("btn-documentation", "n_clicks"),
+            State("offcanvas-documentation", "is_open")
+        )
+        def toggle_offcanvas_documentation(n_clicks, is_open):
+            if n_clicks:
+                return not is_open
+            return is_open