fix: Show mrr trials in trending graphs
[csit.git] / csit.infra.dash / app / cdash / report / layout.py
index 1e79b68..2d325b0 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2023 Cisco and/or its affiliates.
+# Copyright (c) 2024 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,6 +14,7 @@
 """Plotly Dash HTML layout override.
 """
 
+
 import logging
 import pandas as pd
 import dash_bootstrap_components as dbc
@@ -31,7 +32,8 @@ 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, graph_hdrh_latency
+    generate_options, get_list_group_items, navbar_report, \
+    show_iterative_graph_data
 from ..utils.url_processing import url_decode
 from .graphs import graph_iterative, select_iterative_data
 
@@ -149,50 +151,34 @@ class Layout:
                 tbs[rls][dut] = dict()
             if tbs[rls][dut].get(d_ver, None) is None:
                 tbs[rls][dut][d_ver] = dict()
-            if tbs[rls][dut][d_ver].get(infra, None) is None:
-                tbs[rls][dut][d_ver][infra] = dict()
-            if tbs[rls][dut][d_ver][infra].get(area, None) is None:
-                tbs[rls][dut][d_ver][infra][area] = dict()
-            if tbs[rls][dut][d_ver][infra][area].get(test, None) is None:
-                tbs[rls][dut][d_ver][infra][area][test] = dict()
-                tbs[rls][dut][d_ver][infra][area][test]["core"] = list()
-                tbs[rls][dut][d_ver][infra][area][test]["frame-size"] = list()
-                tbs[rls][dut][d_ver][infra][area][test]["test-type"] = list()
-            if core.upper() not in \
-                    tbs[rls][dut][d_ver][infra][area][test]["core"]:
-                tbs[rls][dut][d_ver][infra][area][test]["core"].append(
-                    core.upper()
-                )
-            if framesize.upper() not in \
-                        tbs[rls][dut][d_ver][infra][area][test]["frame-size"]:
-                tbs[rls][dut][d_ver][infra][area][test]["frame-size"].append(
-                    framesize.upper()
-                )
-            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 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", )
-                    )
+            if tbs[rls][dut][d_ver].get(area, None) is None:
+                tbs[rls][dut][d_ver][area] = dict()
+            if tbs[rls][dut][d_ver][area].get(test, None) is None:
+                tbs[rls][dut][d_ver][area][test] = dict()
+            if tbs[rls][dut][d_ver][area][test].get(infra, None) is None:
+                tbs[rls][dut][d_ver][area][test][infra] = {
+                    "core": list(),
+                    "frame-size": list(),
+                    "test-type": list()
+                }
+            tst_params = tbs[rls][dut][d_ver][area][test][infra]
+            if core.upper() not in tst_params["core"]:
+                tst_params["core"].append(core.upper())
+            if framesize.upper() not in tst_params["frame-size"]:
+                tst_params["frame-size"].append(framesize.upper())
+            if row["test_type"] == "ndrpdr":
+                if "NDR" not in tst_params["test-type"]:
+                    tst_params["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"
-                    )
+                if "BPS" not in tst_params["test-type"]:
+                    tst_params["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")
-                    )
+                if "CPS" not in tst_params["test-type"]:
+                    tst_params["test-type"].extend(("CPS", "RPS"))
+            else:  # MRR, SOAK
+                if row["test_type"].upper() not in tst_params["test-type"]:
+                    tst_params["test-type"].append(row["test_type"].upper())
         self._spec_tbs = tbs
 
         # Read from files:
@@ -265,9 +251,7 @@ class Layout:
                     dbc.Row(
                         id="row-navbar",
                         class_name="g-0",
-                        children=[
-                            self._add_navbar()
-                        ]
+                        children=[navbar_report((True, False, False, False)), ]
                     ),
                     dbc.Row(
                         id="row-main",
@@ -293,6 +277,18 @@ class Layout:
                             ]
                         ),
                         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%"
+                        )
                     )
                 ]
             )
@@ -309,31 +305,6 @@ class Layout:
                 ]
             )
 
-    def _add_navbar(self):
-        """Add nav element with navigation panel. It is placed on the top.
-
-        :returns: Navigation bar.
-        :rtype: dbc.NavbarSimple
-        """
-        return dbc.NavbarSimple(
-            id="navbarsimple-main",
-            children=[
-                dbc.NavItem(
-                    dbc.NavLink(
-                        C.REPORT_TITLE,
-                        disabled=True,
-                        external_link=True,
-                        href="#"
-                    )
-                )
-            ],
-            brand=C.BRAND,
-            brand_href="/",
-            brand_external_link=True,
-            class_name="p-2",
-            fluid=True
-        )
-
     def _add_ctrl_col(self) -> dbc.Col:
         """Add column with controls. It is placed on the left side.
 
@@ -377,19 +348,17 @@ class Layout:
         :returns: Control panel.
         :rtype: list
         """
-        return [
+        test_selection = [
             dbc.Row(
                 class_name="g-0 p-1",
                 children=[
                     dbc.InputGroup(
                         [
-                            dbc.InputGroupText(
-                                children=show_tooltip(
-                                    self._tooltips,
-                                    "help-release",
-                                    "CSIT Release"
-                                )
-                            ),
+                            dbc.InputGroupText(show_tooltip(
+                                self._tooltips,
+                                "help-release",
+                                "CSIT Release"
+                            )),
                             dbc.Select(
                                 id={"type": "ctrl-dd", "index": "rls"},
                                 placeholder="Select a Release...",
@@ -411,13 +380,11 @@ class Layout:
                 children=[
                     dbc.InputGroup(
                         [
-                            dbc.InputGroupText(
-                                children=show_tooltip(
-                                    self._tooltips,
-                                    "help-dut",
-                                    "DUT"
-                                )
-                            ),
+                            dbc.InputGroupText(show_tooltip(
+                                self._tooltips,
+                                "help-dut",
+                                "DUT"
+                            )),
                             dbc.Select(
                                 id={"type": "ctrl-dd", "index": "dut"},
                                 placeholder="Select a Device under Test..."
@@ -432,13 +399,11 @@ class Layout:
                 children=[
                     dbc.InputGroup(
                         [
-                            dbc.InputGroupText(
-                                children=show_tooltip(
-                                    self._tooltips,
-                                    "help-dut-ver",
-                                    "DUT Version"
-                                )
-                            ),
+                            dbc.InputGroupText(show_tooltip(
+                                self._tooltips,
+                                "help-dut-ver",
+                                "DUT Version"
+                            )),
                             dbc.Select(
                                 id={"type": "ctrl-dd", "index": "dutver"},
                                 placeholder=\
@@ -454,17 +419,14 @@ class Layout:
                 children=[
                     dbc.InputGroup(
                         [
-                            dbc.InputGroupText(
-                                children=show_tooltip(
-                                    self._tooltips,
-                                    "help-infra",
-                                    "Infra"
-                                )
-                            ),
+                            dbc.InputGroupText(show_tooltip(
+                                self._tooltips,
+                                "help-area",
+                                "Area"
+                            )),
                             dbc.Select(
-                                id={"type": "ctrl-dd", "index": "phy"},
-                                placeholder=\
-                                    "Select a Physical Test Bed Topology..."
+                                id={"type": "ctrl-dd", "index": "area"},
+                                placeholder="Select an Area..."
                             )
                         ],
                         size="sm"
@@ -476,16 +438,14 @@ class Layout:
                 children=[
                     dbc.InputGroup(
                         [
-                            dbc.InputGroupText(
-                                children=show_tooltip(
-                                    self._tooltips,
-                                    "help-area",
-                                    "Area"
-                                )
-                            ),
+                            dbc.InputGroupText(show_tooltip(
+                                self._tooltips,
+                                "help-test",
+                                "Test"
+                            )),
                             dbc.Select(
-                                id={"type": "ctrl-dd", "index": "area"},
-                                placeholder="Select an Area..."
+                                id={"type": "ctrl-dd", "index": "test"},
+                                placeholder="Select a Test..."
                             )
                         ],
                         size="sm"
@@ -497,16 +457,15 @@ class Layout:
                 children=[
                     dbc.InputGroup(
                         [
-                            dbc.InputGroupText(
-                                children=show_tooltip(
-                                    self._tooltips,
-                                    "help-test",
-                                    "Test"
-                                )
-                            ),
+                            dbc.InputGroupText(show_tooltip(
+                                self._tooltips,
+                                "help-infra",
+                                "Infra"
+                            )),
                             dbc.Select(
-                                id={"type": "ctrl-dd", "index": "test"},
-                                placeholder="Select a Test..."
+                                id={"type": "ctrl-dd", "index": "phy"},
+                                placeholder=\
+                                    "Select a Physical Test Bed Topology..."
                             )
                         ],
                         size="sm"
@@ -645,52 +604,36 @@ class Layout:
             dbc.Row(
                 class_name="g-0 p-1",
                 children=[
-                    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"
+                    dbc.Button(
+                        id={"type": "ctrl-btn", "index": "add-test"},
+                        children="Add Selected",
+                        color="info",
+                        class_name="p-1"
                     )
                 ]
-            ),
+            )
+        ]
+        processing = [
             dbc.Row(
                 class_name="g-0 p-1",
                 children=[
-                    dbc.Button(
-                        id={"type": "ctrl-btn", "index": "add-test"},
-                        children="Add Selected",
-                        color="info"
+                    dbc.Checklist(
+                        id="normalize",
+                        options=[{
+                            "value": "normalize",
+                            "label": "Normalize to 2GHz CPU frequency"
+                        }],
+                        value=[],
+                        inline=True,
+                        class_name="ms-2"
                     )
                 ]
-            ),
+            )
+        ]
+        test_list = [
             dbc.Row(
                 id="row-card-sel-tests",
                 class_name="g-0 p-1",
-                style=C.STYLE_DISABLED,
                 children=[
                     dbc.ListGroup(
                         class_name="overflow-auto p-0",
@@ -701,33 +644,100 @@ class Layout:
                     )
                 ]
             ),
-            dbc.Row(
+            dbc.Stack(
                 id="row-btns-sel-tests",
                 class_name="g-0 p-1",
-                style=C.STYLE_DISABLED,
+                gap=2,
                 children=[
-                    dbc.ButtonGroup(
-                        children=[
-                            dbc.Button(
-                                id={"type": "ctrl-btn", "index": "rm-test"},
-                                children="Remove Selected",
-                                class_name="w-100",
-                                color="info",
-                                disabled=False
-                            ),
-                            dbc.Button(
-                                id={"type": "ctrl-btn", "index": "rm-test-all"},
-                                children="Remove All",
-                                class_name="w-100",
-                                color="info",
-                                disabled=False
-                            )
-                        ]
-                    )
+                    dbc.ButtonGroup(children=[
+                        dbc.Button(
+                            id={"type": "ctrl-btn", "index": "rm-test"},
+                            children="Remove Selected",
+                            class_name="w-100 p-1",
+                            color="info",
+                            disabled=False
+                        ),
+                        dbc.Button(
+                            id={"type": "ctrl-btn", "index": "rm-test-all"},
+                            children="Remove All",
+                            class_name="w-100 p-1",
+                            color="info",
+                            disabled=False
+                        )
+                    ]),
+                    dbc.ButtonGroup(children=[
+                        dbc.Button(
+                            id="plot-btn-url",
+                            children="Show URL",
+                            class_name="w-100 p-1",
+                            color="info",
+                            disabled=False
+                        ),
+                        dbc.Button(
+                            id="plot-btn-download",
+                            children="Download Data",
+                            class_name="w-100 p-1",
+                            color="info",
+                            disabled=False
+                        )
+                    ])
                 ]
             )
         ]
 
+        return [
+            dbc.Row(
+                dbc.Card(
+                    [
+                        dbc.CardHeader(
+                            html.H5("Test Selection")
+                        ),
+                        dbc.CardBody(
+                            children=test_selection,
+                            class_name="g-0 p-0"
+                        )
+                    ],
+                    color="secondary",
+                    outline=True
+                ),
+                class_name="g-0 p-1"
+            ),
+            dbc.Row(
+                dbc.Card(
+                    [
+                        dbc.CardHeader(
+                            html.H5("Data Manipulations")
+                        ),
+                        dbc.CardBody(
+                            children=processing,
+                            class_name="g-0 p-0"
+                        )
+                    ],
+                    color="secondary",
+                    outline=True
+                ),
+                class_name="g-0 p-1"
+            ),
+            dbc.Row(
+                dbc.Card(
+                    [
+                        dbc.CardHeader(
+                            html.H5("Selected Tests")
+                        ),
+                        dbc.CardBody(
+                            children=test_list,
+                            class_name="g-0 p-0"
+                        )
+                    ],
+                    color="secondary",
+                    outline=True
+                ),
+                id = "row-selected-tests",
+                class_name="g-0 p-1",
+                style=C.STYLE_DISABLED,
+            )
+        ]
+
     def _get_plotting_area(
             self,
             tests: list,
@@ -749,81 +759,67 @@ class Layout:
         if not tests:
             return C.PLACEHOLDER
 
-        figs = graph_iterative(self._data, tests, self._graph_layout, normalize)
+        graphs = \
+            graph_iterative(self._data, tests, self._graph_layout, normalize)
 
-        if not figs[0]:
+        if not graphs[0]:
             return C.PLACEHOLDER
-
-        row_items = [
-            dbc.Col(
+        
+        tab_items = [
+            dbc.Tab(
                 children=dcc.Graph(
                     id={"type": "graph", "index": "tput"},
-                    figure=figs[0]
+                    figure=graphs[0]
                 ),
-                class_name="g-0 p-1",
-                width=6
+                label="Throughput",
+                tab_id="tab-tput"
             )
         ]
 
-        if figs[1]:
-            row_items.append(
-                dbc.Col(
+        if graphs[1]:
+            tab_items.append(
+                dbc.Tab(
+                    children=dcc.Graph(
+                        id={"type": "graph", "index": "bandwidth"},
+                        figure=graphs[1]
+                    ),
+                    label="Bandwidth",
+                    tab_id="tab-bandwidth"
+                )
+            )
+
+        if graphs[2]:
+            tab_items.append(
+                dbc.Tab(
                     children=dcc.Graph(
                         id={"type": "graph", "index": "lat"},
-                        figure=figs[1]
+                        figure=graphs[2]
                     ),
-                    class_name="g-0 p-1",
-                    width=6
+                    label="Latency",
+                    tab_id="tab-lat"
                 )
             )
 
         return [
             dbc.Row(
-                children=row_items,
-                class_name="g-0 p-0",
+                dbc.Tabs(
+                    children=tab_items,
+                    id="tabs",
+                    active_tab="tab-tput",
+                ),
+                class_name="g-0 p-0"
             ),
-            dbc.Row(
+            dbc.Modal(
                 [
-                    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
-                            ),
-                            dbc.Button(
-                                id="plot-btn-download",
-                                children="Download Data",
-                                class_name="me-1",
-                                color="info",
-                                style={
-                                    "text-transform": "none",
-                                    "padding": "0rem 1rem"
-                                }
-                            ),
-                            dcc.Download(id="download-iterative-data")
-                        ],
-                        className=\
-                            "d-grid gap-0 d-md-flex justify-content-md-end"
-                    )])
+                    dbc.ModalHeader(dbc.ModalTitle("URL")),
+                    dbc.ModalBody(url)
                 ],
-                class_name="g-0 p-0"
-            )
+                id="plot-mod-url",
+                size="xl",
+                is_open=False,
+                scrollable=True
+            ),
+            dcc.Download(id="download-iterative-data")
         ]
 
     def callbacks(self, app):
@@ -838,8 +834,7 @@ class Layout:
                 Output("store-control-panel", "data"),
                 Output("store-selected-tests", "data"),
                 Output("plotting-area", "children"),
-                Output("row-card-sel-tests", "style"),
-                Output("row-btns-sel-tests", "style"),
+                Output("row-selected-tests", "style"),
                 Output("lg-selected", "children"),
 
                 Output({"type": "ctrl-dd", "index": "rls"}, "value"),
@@ -909,8 +904,7 @@ class Layout:
                 url_params = None
 
             plotting_area = no_update
-            row_card_sel_tests = no_update
-            row_btns_sel_tests = no_update
+            row_sel_tests = no_update
             lg_selected = no_update
 
             trigger = Trigger(callback_context.triggered)
@@ -919,15 +913,14 @@ 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
-                    row_btns_sel_tests = C.STYLE_ENABLED
+                    row_sel_tests = C.STYLE_ENABLED
                     last_test = store_sel[-1]
                     test = self._spec_tbs[last_test["rls"]][last_test["dut"]]\
-                        [last_test["dutver"]][last_test["phy"]]\
-                            [last_test["area"]][last_test["test"]]
+                        [last_test["dutver"]][last_test["area"]]\
+                            [last_test["test"]][last_test["phy"]]
                     ctrl_panel.set({
                         "dd-rls-val": last_test["rls"],
                         "dd-dut-val": last_test["dut"],
@@ -941,27 +934,27 @@ class Layout:
                                 [last_test["dut"]].keys()
                         ),
                         "dd-dutver-dis": False,
-                        "dd-phy-val": last_test["phy"],
-                        "dd-phy-opt": generate_options(
-                            self._spec_tbs[last_test["rls"]][last_test["dut"]]\
-                                [last_test["dutver"]].keys()
-                        ),
-                        "dd-phy-dis": False,
                         "dd-area-val": last_test["area"],
                         "dd-area-opt": [
                             {"label": label(v), "value": v} for v in \
                                 sorted(self._spec_tbs[last_test["rls"]]\
-                                    [last_test["dut"]][last_test["dutver"]]\
-                                        [last_test["phy"]].keys())
+                                    [last_test["dut"]]\
+                                        [last_test["dutver"]].keys())
                         ],
                         "dd-area-dis": False,
                         "dd-test-val": last_test["test"],
                         "dd-test-opt": generate_options(
                             self._spec_tbs[last_test["rls"]][last_test["dut"]]\
-                                [last_test["dutver"]][last_test["phy"]]\
-                                    [last_test["area"]].keys()
+                                [last_test["dutver"]][last_test["area"]].keys()
                         ),
                         "dd-test-dis": False,
+                        "dd-phy-val": last_test["phy"],
+                        "dd-phy-opt": generate_options(
+                            self._spec_tbs[last_test["rls"]][last_test["dut"]]\
+                                [last_test["dutver"]][last_test["area"]]\
+                                    [last_test["test"]].keys()
+                        ),
+                        "dd-phy-dis": False,
                         "cl-core-opt": generate_options(test["core"]),
                         "cl-core-val": [last_test["core"].upper(), ],
                         "cl-core-all-val": list(),
@@ -1064,22 +1057,23 @@ class Layout:
                         rls = ctrl_panel.get("dd-rls-val")
                         dut = ctrl_panel.get("dd-dut-val")
                         dutver = self._spec_tbs[rls][dut][trigger.value]
-                        options = generate_options(dutver.keys())
+                        options = [{"label": label(v), "value": v} \
+                            for v in sorted(dutver.keys())]
                         disabled = False
                     except KeyError:
                         options = list()
                         disabled = True
                     ctrl_panel.set({
                         "dd-dutver-val": trigger.value,
-                        "dd-phy-val": str(),
-                        "dd-phy-opt": options,
-                        "dd-phy-dis": disabled,
                         "dd-area-val": str(),
-                        "dd-area-opt": list(),
-                        "dd-area-dis": True,
+                        "dd-area-opt": options,
+                        "dd-area-dis": disabled,
                         "dd-test-val": str(),
                         "dd-test-opt": list(),
                         "dd-test-dis": True,
+                        "dd-phy-val": str(),
+                        "dd-phy-opt": list(),
+                        "dd-phy-dis": True,
                         "cl-core-opt": list(),
                         "cl-core-val": list(),
                         "cl-core-all-val": list(),
@@ -1094,26 +1088,25 @@ class Layout:
                         "cl-tsttype-all-opt": C.CL_ALL_DISABLED,
                         "btn-add-dis": True
                     })
-                elif trigger.idx == "phy":
+                elif trigger.idx == "area":
                     try:
                         rls = ctrl_panel.get("dd-rls-val")
                         dut = ctrl_panel.get("dd-dut-val")
                         dutver = ctrl_panel.get("dd-dutver-val")
-                        phy = self._spec_tbs[rls][dut][dutver][trigger.value]
-                        options = [{"label": label(v), "value": v} \
-                            for v in sorted(phy.keys())]
+                        area = self._spec_tbs[rls][dut][dutver][trigger.value]
+                        options = generate_options(area.keys())
                         disabled = False
                     except KeyError:
                         options = list()
                         disabled = True
                     ctrl_panel.set({
-                        "dd-phy-val": trigger.value,
-                        "dd-area-val": str(),
-                        "dd-area-opt": options,
-                        "dd-area-dis": disabled,
+                        "dd-area-val": trigger.value,
                         "dd-test-val": str(),
-                        "dd-test-opt": list(),
-                        "dd-test-dis": True,
+                        "dd-test-opt": options,
+                        "dd-test-dis": disabled,
+                        "dd-phy-val": str(),
+                        "dd-phy-opt": list(),
+                        "dd-phy-dis": True,
                         "cl-core-opt": list(),
                         "cl-core-val": list(),
                         "cl-core-all-val": list(),
@@ -1128,24 +1121,24 @@ class Layout:
                         "cl-tsttype-all-opt": C.CL_ALL_DISABLED,
                         "btn-add-dis": True
                     })
-                elif trigger.idx == "area":
+                elif trigger.idx == "test":
                     try:
                         rls = ctrl_panel.get("dd-rls-val")
                         dut = ctrl_panel.get("dd-dut-val")
                         dutver = ctrl_panel.get("dd-dutver-val")
-                        phy = ctrl_panel.get("dd-phy-val")
-                        area = \
-                            self._spec_tbs[rls][dut][dutver][phy][trigger.value]
-                        options = generate_options(area.keys())
+                        area = ctrl_panel.get("dd-area-val")
+                        test = self._spec_tbs[rls][dut][dutver][area]\
+                            [trigger.value]
+                        options = generate_options(test.keys())
                         disabled = False
                     except KeyError:
                         options = list()
                         disabled = True
                     ctrl_panel.set({
-                        "dd-area-val": trigger.value,
-                        "dd-test-val": str(),
-                        "dd-test-opt": options,
-                        "dd-test-dis": disabled,
+                        "dd-test-val": trigger.value,
+                        "dd-phy-val": str(),
+                        "dd-phy-opt": options,
+                        "dd-phy-dis": disabled,
                         "cl-core-opt": list(),
                         "cl-core-val": list(),
                         "cl-core-all-val": list(),
@@ -1160,28 +1153,28 @@ class Layout:
                         "cl-tsttype-all-opt": C.CL_ALL_DISABLED,
                         "btn-add-dis": True
                     })
-                elif trigger.idx == "test":
+                elif trigger.idx == "phy":
                     rls = ctrl_panel.get("dd-rls-val")
                     dut = ctrl_panel.get("dd-dut-val")
                     dutver = ctrl_panel.get("dd-dutver-val")
-                    phy = ctrl_panel.get("dd-phy-val")
                     area = ctrl_panel.get("dd-area-val")
-                    if all((rls, dut, dutver, phy, area, trigger.value, )):
-                        test = self._spec_tbs[rls][dut][dutver][phy][area]\
+                    test = ctrl_panel.get("dd-test-val")
+                    if all((rls, dut, dutver, area, test, trigger.value, )):
+                        phy = self._spec_tbs[rls][dut][dutver][area][test]\
                             [trigger.value]
                         ctrl_panel.set({
-                            "dd-test-val": trigger.value,
-                            "cl-core-opt": generate_options(test["core"]),
+                            "dd-phy-val": trigger.value,
+                            "cl-core-opt": generate_options(phy["core"]),
                             "cl-core-val": list(),
                             "cl-core-all-val": list(),
                             "cl-core-all-opt": C.CL_ALL_ENABLED,
                             "cl-frmsize-opt": \
-                                generate_options(test["frame-size"]),
+                                generate_options(phy["frame-size"]),
                             "cl-frmsize-val": list(),
                             "cl-frmsize-all-val": list(),
                             "cl-frmsize-all-opt": C.CL_ALL_ENABLED,
                             "cl-tsttype-opt": \
-                                generate_options(test["test-type"]),
+                                generate_options(phy["test-type"]),
                             "cl-tsttype-val": list(),
                             "cl-tsttype-all-val": list(),
                             "cl-tsttype-all-opt": C.CL_ALL_ENABLED,
@@ -1275,20 +1268,17 @@ class Layout:
                             {"store_sel": store_sel, "norm": normalize}
                         )
                     )
-                    row_card_sel_tests = C.STYLE_ENABLED
-                    row_btns_sel_tests = C.STYLE_ENABLED
+                    row_sel_tests = C.STYLE_ENABLED
                 else:
                     plotting_area = C.PLACEHOLDER
-                    row_card_sel_tests = C.STYLE_DISABLED
-                    row_btns_sel_tests = C.STYLE_DISABLED
+                    row_sel_tests = C.STYLE_DISABLED
                     store_sel = list()
 
             ret_val = [
                 ctrl_panel.panel,
                 store_sel,
                 plotting_area,
-                row_card_sel_tests,
-                row_btns_sel_tests,
+                row_sel_tests,
                 lg_selected
             ]
             ret_val.extend(ctrl_panel.values)
@@ -1296,15 +1286,16 @@ class Layout:
 
         @app.callback(
             Output("plot-mod-url", "is_open"),
-            [Input("plot-btn-url", "n_clicks")],
-            [State("plot-mod-url", "is_open")],
+            Output("plot-btn-url", "n_clicks"),
+            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
+                return not is_open, 0
+            return is_open, 0
 
         @app.callback(
             Output("download-iterative-data", "data"),
@@ -1354,92 +1345,18 @@ class Layout:
             """
 
             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):
+            if not trigger.value:
                 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 show_iterative_graph_data(
+                    trigger, graph_data, self._graph_layout)
 
-            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