feat(uti): Generate structure of tests from data for trending
[csit.git] / resources / tools / dash / app / pal / trending / layout.py
index 11900c6..b5286a0 100644 (file)
 import pandas as pd
 import dash_bootstrap_components as dbc
 
 import pandas as pd
 import dash_bootstrap_components as dbc
 
+from flask import Flask
 from dash import dcc
 from dash import html
 from dash import dcc
 from dash import html
-from dash import callback_context, no_update
+from dash import callback_context, no_update, ALL
 from dash import Input, Output, State
 from dash.exceptions import PreventUpdate
 from yaml import load, FullLoader, YAMLError
 from datetime import datetime, timedelta
 from copy import deepcopy
 from dash import Input, Output, State
 from dash.exceptions import PreventUpdate
 from yaml import load, FullLoader, YAMLError
 from datetime import datetime, timedelta
 from copy import deepcopy
+from json import loads, JSONDecodeError
 
 from ..data.data import Data
 from .graphs import graph_trending, graph_hdrh_latency, \
 
 from ..data.data import Data
 from .graphs import graph_trending, graph_hdrh_latency, \
@@ -35,14 +37,14 @@ class Layout:
     """
     """
 
     """
     """
 
-    NO_GRAPH = {"data": [], "layout": {}, "frames": []}
+    STYLE_DISABLED = {"display": "none"}
+    STYLE_ENABLED = {"display": "inherit"}
 
     CL_ALL_DISABLED = [{
         "label": "All",
         "value": "all",
         "disabled": True
     }]
 
     CL_ALL_DISABLED = [{
         "label": "All",
         "value": "all",
         "disabled": True
     }]
-
     CL_ALL_ENABLED = [{
         "label": "All",
         "value": "all",
     CL_ALL_ENABLED = [{
         "label": "All",
         "value": "all",
@@ -51,8 +53,28 @@ class Layout:
 
     PLACEHOLDER = html.Nobr("")
 
 
     PLACEHOLDER = html.Nobr("")
 
-    def __init__(self, app, html_layout_file, spec_file, graph_layout_file,
-        data_spec_file):
+    DRIVERS = ("avf", "af-xdp", "rdma", "dpdk")
+
+    LABELS = {
+        "dpdk": "DPDK",
+        "container_memif": "LXC/DRC Container Memif",
+        "crypto": "IPSec IPv4 Routing",
+        "ip4": "IPv4 Routing",
+        "ip6": "IPv6 Routing",
+        "ip4_tunnels": "IPv4 Tunnels",
+        "l2": "L2 Ethernet Switching",
+        "srv6": "SRv6 Routing",
+        "vm_vhost": "VMs vhost-user",
+        "nfv_density-dcr_memif-chain_ipsec": "CNF Service Chains Routing IPSec",
+        "nfv_density-vm_vhost-chain_dot1qip4vxlan":"VNF Service Chains Tunnels",
+        "nfv_density-vm_vhost-chain": "VNF Service Chains Routing",
+        "nfv_density-dcr_memif-pipeline": "CNF Service Pipelines Routing",
+        "nfv_density-dcr_memif-chain": "CNF Service Chains Routing",
+    }
+
+    def __init__(self, app: Flask, html_layout_file: str, spec_file: str,
+        graph_layout_file: str, data_spec_file: str,
+        time_period: str=None) -> None:
         """
         """
 
         """
         """
 
@@ -62,23 +84,88 @@ class Layout:
         self._spec_file = spec_file
         self._graph_layout_file = graph_layout_file
         self._data_spec_file = data_spec_file
         self._spec_file = spec_file
         self._graph_layout_file = graph_layout_file
         self._data_spec_file = data_spec_file
+        self._time_period = time_period
 
         # Read the data:
         data_mrr = Data(
             data_spec_file=self._data_spec_file,
             debug=True
 
         # Read the data:
         data_mrr = Data(
             data_spec_file=self._data_spec_file,
             debug=True
-        ).read_trending_mrr(days=5)
+        ).read_trending_mrr(days=self._time_period)
 
         data_ndrpdr = Data(
             data_spec_file=self._data_spec_file,
             debug=True
 
         data_ndrpdr = Data(
             data_spec_file=self._data_spec_file,
             debug=True
-        ).read_trending_ndrpdr(days=14)
+        ).read_trending_ndrpdr(days=self._time_period)
 
         self._data = pd.concat([data_mrr, data_ndrpdr], ignore_index=True)
 
 
         self._data = pd.concat([data_mrr, data_ndrpdr], ignore_index=True)
 
+        data_time_period = \
+            (datetime.utcnow() - self._data["start_time"].min()).days
+        if self._time_period > data_time_period:
+            self._time_period = data_time_period
+
+
+        # Get structure of tests:
+        tbs = dict()
+        for _, row in self._data[["job", "test_id"]].drop_duplicates().\
+                iterrows():
+            lst_job = row["job"].split("-")
+            dut = lst_job[1]
+            ttype = lst_job[3]
+            tbed = "-".join(lst_job[-2:])
+            lst_test = row["test_id"].split(".")
+            if dut == "dpdk":
+                area = "dpdk"
+            else:
+                area = "-".join(lst_test[3:-2])
+            suite = lst_test[-2].replace("2n1l-", "").replace("1n1l-", "").\
+                replace("2n-", "")
+            test = lst_test[-1]
+            nic = suite.split("-")[0]
+            for drv in self.DRIVERS:
+                if drv in test:
+                    if drv == "af-xdp":
+                        driver = "af_xdp"
+                    else:
+                        driver = drv
+                    test = test.replace(f"{drv}-", "")
+                    break
+            else:
+                driver = "dpdk"
+            infra = "-".join((tbed, nic, driver))
+            lst_test = test.split("-")
+            framesize = lst_test[0]
+            core = lst_test[1] if lst_test[1] else "1C"
+            test = "-".join(lst_test[2: -1])
+
+            if tbs.get(dut, None) is None:
+                tbs[dut] = dict()
+            if tbs[dut].get(infra, None) is None:
+                tbs[dut][infra] = dict()
+            if tbs[dut][infra].get(area, None) is None:
+                tbs[dut][infra][area] = dict()
+            if tbs[dut][infra][area].get(test, None) is None:
+                tbs[dut][infra][area][test] = dict()
+                tbs[dut][infra][area][test]["core"] = list()
+                tbs[dut][infra][area][test]["frame-size"] = list()
+                tbs[dut][infra][area][test]["test-type"] = list()
+            if core.upper() not in tbs[dut][infra][area][test]["core"]:
+                tbs[dut][infra][area][test]["core"].append(core.upper())
+            if framesize.upper() not in \
+                    tbs[dut][infra][area][test]["frame-size"]:
+                tbs[dut][infra][area][test]["frame-size"].append(
+                    framesize.upper())
+            if ttype == "mrr":
+                if "MRR" not in tbs[dut][infra][area][test]["test-type"]:
+                    tbs[dut][infra][area][test]["test-type"].append("MRR")
+            elif ttype == "ndrpdr":
+                if "NDR" not in tbs[dut][infra][area][test]["test-type"]:
+                    tbs[dut][infra][area][test]["test-type"].extend(
+                        ("NDR", "PDR"))
+        self._spec_tbs = tbs
+
         # Read from files:
         self._html_layout = ""
         # Read from files:
         self._html_layout = ""
-        self._spec_tbs = None
         self._graph_layout = None
 
         try:
         self._graph_layout = None
 
         try:
@@ -89,20 +176,6 @@ class Layout:
                 f"Not possible to open the file {self._html_layout_file}\n{err}"
             )
 
                 f"Not possible to open the file {self._html_layout_file}\n{err}"
             )
 
-        try:
-            with open(self._spec_file, "r") as file_read:
-                self._spec_tbs = load(file_read, Loader=FullLoader)
-        except IOError as err:
-            raise RuntimeError(
-                f"Not possible to open the file {self._spec_file,}\n{err}"
-            )
-        except YAMLError as err:
-            raise RuntimeError(
-                f"An error occurred while parsing the specification file "
-                f"{self._spec_file,}\n"
-                f"{err}"
-            )
-
         try:
             with open(self._graph_layout_file, "r") as file_read:
                 self._graph_layout = load(file_read, Loader=FullLoader)
         try:
             with open(self._graph_layout_file, "r") as file_read:
                 self._graph_layout = load(file_read, Loader=FullLoader)
@@ -138,6 +211,13 @@ class Layout:
     def layout(self):
         return self._graph_layout
 
     def layout(self):
         return self._graph_layout
 
+    @property
+    def time_period(self):
+        return self._time_period
+
+    def label(self, key: str) -> str:
+        return self.LABELS.get(key, key)
+
     def add_content(self):
         """
         """
     def add_content(self):
         """
         """
@@ -154,6 +234,7 @@ class Layout:
                     ),
                     dcc.Loading(
                         dbc.Offcanvas(
                     ),
                     dcc.Loading(
                         dbc.Offcanvas(
+                            class_name="w-50",
                             id="offcanvas-metadata",
                             title="Throughput And Latency",
                             placement="end",
                             id="offcanvas-metadata",
                             title="Throughput And Latency",
                             placement="end",
@@ -231,25 +312,29 @@ class Layout:
         return dbc.Col(
             id="col-plotting-area",
             children=[
         return dbc.Col(
             id="col-plotting-area",
             children=[
-                dbc.Row(  # Throughput
-                    id="row-graph-tput",
-                    class_name="g-0 p-2",
+                dcc.Loading(
                     children=[
                     children=[
-                        self.PLACEHOLDER
-                    ]
-                ),
-                dbc.Row(  # Latency
-                    id="row-graph-lat",
-                    class_name="g-0 p-2",
-                    children=[
-                        self.PLACEHOLDER
-                    ]
-                ),
-                dbc.Row(  # Download
-                    id="row-btn-download",
-                    class_name="g-0 p-2",
-                    children=[
-                        self.PLACEHOLDER
+                        dbc.Row(  # Throughput
+                            id="row-graph-tput",
+                            class_name="g-0 p-2",
+                            children=[
+                                self.PLACEHOLDER
+                            ]
+                        ),
+                        dbc.Row(  # Latency
+                            id="row-graph-lat",
+                            class_name="g-0 p-2",
+                            children=[
+                                self.PLACEHOLDER
+                            ]
+                        ),
+                        dbc.Row(  # Download
+                            id="row-btn-download",
+                            class_name="g-0 p-2",
+                            children=[
+                                self.PLACEHOLDER
+                            ]
+                        )
                     ]
                 )
             ],
                     ]
                 )
             ],
@@ -264,48 +349,79 @@ class Layout:
             class_name="g-0 p-2",
             children=[
                 dbc.Row(
             class_name="g-0 p-2",
             children=[
                 dbc.Row(
-                    class_name="gy-1",
+                    class_name="g-0",
                     children=[
                     children=[
-                        dbc.Label(
-                            "Physical Test Bed Topology, NIC and Driver",
-                            class_name="p-0"
-                        ),
-                        dbc.Select(
-                            id="dd-ctrl-phy",
-                            placeholder="Select a Physical Test Bed Topology...",
-                            options=[
-                                {"label": k, "value": k} for k in self.spec_tbs.keys()
+                        dbc.InputGroup(
+                            [
+                                dbc.InputGroupText("DUT"),
+                                dbc.Select(
+                                    id="dd-ctrl-dut",
+                                    placeholder=(
+                                        "Select a Device under Test..."
+                                    ),
+                                    options=sorted(
+                                        [
+                                            {"label": k, "value": k} \
+                                                for k in self.spec_tbs.keys()
+                                        ],
+                                        key=lambda d: d["label"]
+                                    )
+                                )
                             ],
                             ],
+                            class_name="mb-3",
                             size="sm",
                         ),
                     ]
                 ),
                 dbc.Row(
                             size="sm",
                         ),
                     ]
                 ),
                 dbc.Row(
-                    class_name="gy-1",
+                    class_name="g-0",
                     children=[
                     children=[
-                        dbc.Label(
-                            "Area",
-                            class_name="p-0"
-                        ),
-                        dbc.Select(
-                            id="dd-ctrl-area",
-                            placeholder="Select an Area...",
-                            disabled=True,
+                        dbc.InputGroup(
+                            [
+                                dbc.InputGroupText("Infra"),
+                                dbc.Select(
+                                    id="dd-ctrl-phy",
+                                    placeholder=(
+                                        "Select a Physical Test Bed "
+                                        "Topology..."
+                                    )
+                                )
+                            ],
+                            class_name="mb-3",
                             size="sm",
                         ),
                     ]
                 ),
                 dbc.Row(
                             size="sm",
                         ),
                     ]
                 ),
                 dbc.Row(
-                    class_name="gy-1",
+                    class_name="g-0",
                     children=[
                     children=[
-                        dbc.Label(
-                            "Test",
-                            class_name="p-0"
+                        dbc.InputGroup(
+                            [
+                                dbc.InputGroupText("Area"),
+                                dbc.Select(
+                                    id="dd-ctrl-area",
+                                    placeholder="Select an Area...",
+                                    disabled=True,
+                                ),
+                            ],
+                            class_name="mb-3",
+                            size="sm",
                         ),
                         ),
-                        dbc.Select(
-                            id="dd-ctrl-test",
-                            placeholder="Select a Test...",
-                            disabled=True,
+                    ]
+                ),
+                dbc.Row(
+                    class_name="g-0",
+                    children=[
+                        dbc.InputGroup(
+                            [
+                                dbc.InputGroupText("Test"),
+                                dbc.Select(
+                                    id="dd-ctrl-test",
+                                    placeholder="Select a Test...",
+                                    disabled=True,
+                                ),
+                            ],
+                            class_name="mb-3",
                             size="sm",
                         ),
                     ]
                             size="sm",
                         ),
                     ]
@@ -401,14 +517,15 @@ class Layout:
                     ]
                 ),
                 dbc.Row(
                     ]
                 ),
                 dbc.Row(
-                    class_name="gy-1",
+                    class_name="gy-1 p-0",
                     children=[
                         dbc.ButtonGroup(
                             [
                                 dbc.Button(
                                     id="btn-ctrl-add",
                                     children="Add Selected",
                     children=[
                         dbc.ButtonGroup(
                             [
                                 dbc.Button(
                                     id="btn-ctrl-add",
                                     children="Add Selected",
-                                    color="secondary",
+                                    class_name="me-1",
+                                    color="info"
                                 )
                             ],
                             size="md",
                                 )
                             ],
                             size="md",
@@ -420,65 +537,62 @@ class Layout:
                     children=[
                         dcc.DatePickerRange(
                             id="dpr-period",
                     children=[
                         dcc.DatePickerRange(
                             id="dpr-period",
+                            className="d-flex justify-content-center",
                             min_date_allowed=\
                             min_date_allowed=\
-                                datetime.utcnow()-timedelta(days=180),
+                                datetime.utcnow() - timedelta(
+                                    days=self.time_period),
                             max_date_allowed=datetime.utcnow(),
                             initial_visible_month=datetime.utcnow(),
                             max_date_allowed=datetime.utcnow(),
                             initial_visible_month=datetime.utcnow(),
-                            start_date=datetime.utcnow() - timedelta(days=180),
+                            start_date=\
+                                datetime.utcnow() - timedelta(
+                                    days=self.time_period),
                             end_date=datetime.utcnow(),
                             display_format="D MMMM YY"
                         )
                     ]
                 ),
                 dbc.Row(
                             end_date=datetime.utcnow(),
                             display_format="D MMMM YY"
                         )
                     ]
                 ),
                 dbc.Row(
+                    id="row-card-sel-tests",
                     class_name="gy-1",
                     class_name="gy-1",
+                    style=self.STYLE_DISABLED,
                     children=[
                     children=[
-                        dbc.Card(
-                            class_name="p-0",
-                            children=[
-                                dbc.Label(
-                                    "Selected tests",
-                                    class_name="p-0"
-                                ),
-                                dbc.Checklist(
-                                    id="cl-selected",
-                                    options=[],
-                                    inline=False
-                                )
-                            ],
-                            color="light",
-                            outline=True
+                        dbc.Label(
+                            "Selected tests",
+                            class_name="p-0"
+                        ),
+                        dbc.Checklist(
+                            class_name="overflow-auto",
+                            id="cl-selected",
+                            options=[],
+                            inline=False,
+                            style={"max-height": "12em"},
                         )
                         )
-                    ]
+                    ],
                 ),
                 dbc.Row(
                 ),
                 dbc.Row(
+                    id="row-btns-sel-tests",
+                    style=self.STYLE_DISABLED,
                     children=[
                         dbc.ButtonGroup(
                     children=[
                         dbc.ButtonGroup(
-                            [
-                                dbc.Button(
-                                    id="btn-sel-remove-all",
-                                    children="Remove All",
-                                    class_name="w-100",
-                                    color="secondary",
-                                    disabled=False
-                                ),
+                            class_name="gy-2",
+                            children=[
                                 dbc.Button(
                                     id="btn-sel-remove",
                                     children="Remove Selected",
                                 dbc.Button(
                                     id="btn-sel-remove",
                                     children="Remove Selected",
-                                    class_name="w-100",
-                                    color="secondary",
+                                    class_name="w-100 me-1",
+                                    color="info",
                                     disabled=False
                                 ),
                                 dbc.Button(
                                     disabled=False
                                 ),
                                 dbc.Button(
-                                    id="btn-sel-display",
-                                    children="Display",
-                                    class_name="w-100",
-                                    color="secondary",
+                                    id="btn-sel-remove-all",
+                                    children="Remove All",
+                                    class_name="w-100 me-1",
+                                    color="info",
                                     disabled=False
                                     disabled=False
-                                )
+                                ),
                             ],
                             size="md",
                             ],
                             size="md",
-                        ),
+                        )
                     ]
                 ),
             ]
                     ]
                 ),
             ]
@@ -495,6 +609,9 @@ class Layout:
 
             # Defines also the order of keys
             self._defaults = {
 
             # Defines also the order of keys
             self._defaults = {
+                "dd-ctrl-dut-value": str(),
+                "dd-ctrl-phy-options": list(),
+                "dd-ctrl-phy-disabled": True,
                 "dd-ctrl-phy-value": str(),
                 "dd-ctrl-area-options": list(),
                 "dd-ctrl-area-disabled": True,
                 "dd-ctrl-phy-value": str(),
                 "dd-ctrl-area-options": list(),
                 "dd-ctrl-area-disabled": True,
@@ -560,9 +677,7 @@ class Layout:
         """Display selected tests with checkboxes
         """
         if selection:
         """Display selected tests with checkboxes
         """
         if selection:
-            return [
-                {"label": v["id"], "value": v["id"]} for v in selection
-            ]
+            return [{"label": v["id"], "value": v["id"]} for v in selection]
         else:
             return list()
 
         else:
             return list()
 
@@ -580,29 +695,27 @@ class Layout:
 
             if fig_tput:
                 row_fig_tput = [
 
             if fig_tput:
                 row_fig_tput = [
-                    dcc.Loading(
-                        dcc.Graph(
-                            id="graph-tput",
-                            figure=fig_tput
-                        )
-                    ),
+                    dcc.Graph(
+                        id={"type": "graph", "index": "tput"},
+                        figure=fig_tput
+                    )
                 ]
                 row_btn_dwnld = [
                     dcc.Loading(children=[
                         dbc.Button(
                             id="btn-download-data",
                 ]
                 row_btn_dwnld = [
                     dcc.Loading(children=[
                         dbc.Button(
                             id="btn-download-data",
-                            children=["Download Data"]
+                            children=["Download Data"],
+                            class_name="me-1",
+                            color="info"
                         ),
                         dcc.Download(id="download-data")
                     ]),
                 ]
             if fig_lat:
                 row_fig_lat = [
                         ),
                         dcc.Download(id="download-data")
                     ]),
                 ]
             if fig_lat:
                 row_fig_lat = [
-                    dcc.Loading(
-                        dcc.Graph(
-                            id="graph-latency",
-                            figure=fig_lat
-                        )
+                    dcc.Graph(
+                        id={"type": "graph", "index": "lat"},
+                        figure=fig_lat
                     )
                 ]
 
                     )
                 ]
 
@@ -614,6 +727,11 @@ class Layout:
             Output("row-graph-tput", "children"),
             Output("row-graph-lat", "children"),
             Output("row-btn-download", "children"),
             Output("row-graph-tput", "children"),
             Output("row-graph-lat", "children"),
             Output("row-btn-download", "children"),
+            Output("row-card-sel-tests", "style"),
+            Output("row-btns-sel-tests", "style"),
+            Output("dd-ctrl-dut", "value"),
+            Output("dd-ctrl-phy", "options"),
+            Output("dd-ctrl-phy", "disabled"),
             Output("dd-ctrl-phy", "value"),
             Output("dd-ctrl-area", "options"),
             Output("dd-ctrl-area", "disabled"),
             Output("dd-ctrl-phy", "value"),
             Output("dd-ctrl-area", "options"),
             Output("dd-ctrl-area", "disabled"),
@@ -638,6 +756,7 @@ class Layout:
             State("control-panel", "data"),  # Store
             State("selected-tests", "data"),  # Store
             State("cl-selected", "value"),  # User selection
             State("control-panel", "data"),  # Store
             State("selected-tests", "data"),  # Store
             State("cl-selected", "value"),  # User selection
+            Input("dd-ctrl-dut", "value"),
             Input("dd-ctrl-phy", "value"),
             Input("dd-ctrl-area", "value"),
             Input("dd-ctrl-test", "value"),
             Input("dd-ctrl-phy", "value"),
             Input("dd-ctrl-area", "value"),
             Input("dd-ctrl-test", "value"),
@@ -650,15 +769,14 @@ class Layout:
             Input("btn-ctrl-add", "n_clicks"),
             Input("dpr-period", "start_date"),
             Input("dpr-period", "end_date"),
             Input("btn-ctrl-add", "n_clicks"),
             Input("dpr-period", "start_date"),
             Input("dpr-period", "end_date"),
-            Input("btn-sel-display", "n_clicks"),
             Input("btn-sel-remove", "n_clicks"),
             Input("btn-sel-remove-all", "n_clicks"),
         )
         def _update_ctrl_panel(cp_data: dict, store_sel: list, list_sel: list,
             Input("btn-sel-remove", "n_clicks"),
             Input("btn-sel-remove-all", "n_clicks"),
         )
         def _update_ctrl_panel(cp_data: dict, store_sel: list, list_sel: list,
-            dd_phy: str, dd_area: str, dd_test: str, cl_core: list,
+            dd_dut: str, dd_phy: str, dd_area: str, dd_test: str, cl_core: list,
             cl_core_all: list, cl_framesize: list, cl_framesize_all: list,
             cl_testtype: list, cl_testtype_all: list, btn_add: int,
             cl_core_all: list, cl_framesize: list, cl_framesize_all: list,
             cl_testtype: list, cl_testtype_all: list, btn_add: int,
-            d_start: str, d_end: str, btn_display: int, btn_remove: int,
+            d_start: str, d_end: str, btn_remove: int,
             btn_remove_all: int) -> tuple:
             """
             """
             btn_remove_all: int) -> tuple:
             """
             """
@@ -670,21 +788,63 @@ class Layout:
             row_fig_tput = no_update
             row_fig_lat = no_update
             row_btn_dwnld = no_update
             row_fig_tput = no_update
             row_fig_lat = no_update
             row_btn_dwnld = no_update
+            row_card_sel_tests = no_update
+            row_btns_sel_tests = no_update
 
             ctrl_panel = self.ControlPanel(cp_data)
 
             trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
 
 
             ctrl_panel = self.ControlPanel(cp_data)
 
             trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
 
+            if trigger_id == "dd-ctrl-dut":
+                try:
+                    options = sorted(
+                        [
+                            {"label": v, "value": v}
+                                for v in self.spec_tbs[dd_dut].keys()
+                        ],
+                        key=lambda d: d["label"]
+                    )
+                    disabled = False
+                except KeyError:
+                    options = list()
+                    disabled = True
+                ctrl_panel.set({
+                    "dd-ctrl-dut-value": dd_dut,
+                    "dd-ctrl-phy-value": str(),
+                    "dd-ctrl-phy-options": options,
+                    "dd-ctrl-phy-disabled": disabled,
+                    "dd-ctrl-area-value": str(),
+                    "dd-ctrl-area-options": list(),
+                    "dd-ctrl-area-disabled": True,
+                    "dd-ctrl-test-options": list(),
+                    "dd-ctrl-test-disabled": True,
+                    "cl-ctrl-core-options": list(),
+                    "cl-ctrl-core-value": list(),
+                    "cl-ctrl-core-all-value": list(),
+                    "cl-ctrl-core-all-options": self.CL_ALL_DISABLED,
+                    "cl-ctrl-framesize-options": list(),
+                    "cl-ctrl-framesize-value": list(),
+                    "cl-ctrl-framesize-all-value": list(),
+                    "cl-ctrl-framesize-all-options": self.CL_ALL_DISABLED,
+                    "cl-ctrl-testtype-options": list(),
+                    "cl-ctrl-testtype-value": list(),
+                    "cl-ctrl-testtype-all-value": list(),
+                    "cl-ctrl-testtype-all-options": self.CL_ALL_DISABLED,
+                })
             if trigger_id == "dd-ctrl-phy":
                 try:
             if trigger_id == "dd-ctrl-phy":
                 try:
-                    options = [
-                        {"label": self.spec_tbs[dd_phy][v]["label"], "value": v}
-                            for v in [v for v in self.spec_tbs[dd_phy].keys()]
-                    ]
+                    dut = ctrl_panel.get("dd-ctrl-dut-value")
+                    options = sorted(
+                        [
+                            {"label": self.label(v), "value": v}
+                                for v in self.spec_tbs[dut][dd_phy].keys()
+                        ],
+                        key=lambda d: d["label"]
+                    )
                     disabled = False
                 except KeyError:
                     options = list()
                     disabled = False
                 except KeyError:
                     options = list()
-                    disabled = no_update
+                    disabled = True
                 ctrl_panel.set({
                     "dd-ctrl-phy-value": dd_phy,
                     "dd-ctrl-area-value": str(),
                 ctrl_panel.set({
                     "dd-ctrl-phy-value": dd_phy,
                     "dd-ctrl-area-value": str(),
@@ -704,15 +864,18 @@ class Layout:
                     "cl-ctrl-testtype-value": list(),
                     "cl-ctrl-testtype-all-value": list(),
                     "cl-ctrl-testtype-all-options": self.CL_ALL_DISABLED,
                     "cl-ctrl-testtype-value": list(),
                     "cl-ctrl-testtype-all-value": list(),
                     "cl-ctrl-testtype-all-options": self.CL_ALL_DISABLED,
-                    "btn-ctrl-add-disabled": True,
                 })
             elif trigger_id == "dd-ctrl-area":
                 try:
                 })
             elif trigger_id == "dd-ctrl-area":
                 try:
+                    dut = ctrl_panel.get("dd-ctrl-dut-value")
                     phy = ctrl_panel.get("dd-ctrl-phy-value")
                     phy = ctrl_panel.get("dd-ctrl-phy-value")
-                    options = [
-                        {"label": v, "value": v}
-                            for v in self.spec_tbs[phy][dd_area]["test"]
-                    ]
+                    options = sorted(
+                        [
+                            {"label": v, "value": v}
+                                for v in self.spec_tbs[dut][phy][dd_area].keys()
+                        ],
+                        key=lambda d: d["label"]
+                    )
                     disabled = False
                 except KeyError:
                     options = list()
                     disabled = False
                 except KeyError:
                     options = list()
@@ -734,26 +897,26 @@ class Layout:
                     "cl-ctrl-testtype-value": list(),
                     "cl-ctrl-testtype-all-value": list(),
                     "cl-ctrl-testtype-all-options": self.CL_ALL_DISABLED,
                     "cl-ctrl-testtype-value": list(),
                     "cl-ctrl-testtype-all-value": list(),
                     "cl-ctrl-testtype-all-options": self.CL_ALL_DISABLED,
-                    "btn-ctrl-add-disabled": True,
                 })
             elif trigger_id == "dd-ctrl-test":
                 core_opts = list()
                 framesize_opts = list()
                 testtype_opts = list()
                 })
             elif trigger_id == "dd-ctrl-test":
                 core_opts = list()
                 framesize_opts = list()
                 testtype_opts = list()
+                dut = ctrl_panel.get("dd-ctrl-dut-value")
                 phy = ctrl_panel.get("dd-ctrl-phy-value")
                 area = ctrl_panel.get("dd-ctrl-area-value")
                 phy = ctrl_panel.get("dd-ctrl-phy-value")
                 area = ctrl_panel.get("dd-ctrl-area-value")
-                if phy and area and dd_test:
+                cores = self.spec_tbs[dut][phy][area][dd_test]["core"]
+                fsizes = self.spec_tbs[dut][phy][area][dd_test]["frame-size"]
+                ttypes = self.spec_tbs[dut][phy][area][dd_test]["test-type"]
+                if dut and phy and area and dd_test:
                     core_opts = [
                     core_opts = [
-                        {"label": v, "value": v}
-                            for v in self.spec_tbs[phy][area]["core"]
+                        {"label": v, "value": v} for v in sorted(cores)
                     ]
                     framesize_opts = [
                     ]
                     framesize_opts = [
-                        {"label": v, "value": v}
-                            for v in self.spec_tbs[phy][area]["frame-size"]
+                        {"label": v, "value": v} for v in sorted(fsizes)
                     ]
                     testtype_opts = [
                     ]
                     testtype_opts = [
-                        {"label": v, "value": v}
-                            for v in self.spec_tbs[phy][area]["test-type"]
+                        {"label": v, "value": v}for v in sorted(ttypes)
                     ]
                     ctrl_panel.set({
                         "dd-ctrl-test-value": dd_test,
                     ]
                     ctrl_panel.set({
                         "dd-ctrl-test-value": dd_test,
@@ -769,7 +932,6 @@ class Layout:
                         "cl-ctrl-testtype-value": list(),
                         "cl-ctrl-testtype-all-value": list(),
                         "cl-ctrl-testtype-all-options": self.CL_ALL_ENABLED,
                         "cl-ctrl-testtype-value": list(),
                         "cl-ctrl-testtype-all-value": list(),
                         "cl-ctrl-testtype-all-options": self.CL_ALL_ENABLED,
-                        "btn-ctrl-add-disabled": False,
                     })
             elif trigger_id == "cl-ctrl-core":
                 val_sel, val_all = self._sync_checklists(
                     })
             elif trigger_id == "cl-ctrl-core":
                 val_sel, val_all = self._sync_checklists(
@@ -839,6 +1001,7 @@ class Layout:
                 })
             elif trigger_id == "btn-ctrl-add":
                 _ = btn_add
                 })
             elif trigger_id == "btn-ctrl-add":
                 _ = btn_add
+                dut = ctrl_panel.get("dd-ctrl-dut-value")
                 phy = ctrl_panel.get("dd-ctrl-phy-value")
                 area = ctrl_panel.get("dd-ctrl-area-value")
                 test = ctrl_panel.get("dd-ctrl-test-value")
                 phy = ctrl_panel.get("dd-ctrl-phy-value")
                 area = ctrl_panel.get("dd-ctrl-area-value")
                 test = ctrl_panel.get("dd-ctrl-test-value")
@@ -846,23 +1009,23 @@ class Layout:
                 framesizes = ctrl_panel.get("cl-ctrl-framesize-value")
                 testtypes = ctrl_panel.get("cl-ctrl-testtype-value")
                 # Add selected test to the list of tests in store:
                 framesizes = ctrl_panel.get("cl-ctrl-framesize-value")
                 testtypes = ctrl_panel.get("cl-ctrl-testtype-value")
                 # Add selected test to the list of tests in store:
-                if phy and area and test and cores and framesizes and testtypes:
+                if all((dut, phy, area, test, cores, framesizes, testtypes)):
                     if store_sel is None:
                         store_sel = list()
                     for core in cores:
                         for framesize in framesizes:
                             for ttype in testtypes:
                     if store_sel is None:
                         store_sel = list()
                     for core in cores:
                         for framesize in framesizes:
                             for ttype in testtypes:
-                                tid = (
-                                    f"{phy.replace('af_xdp', 'af-xdp')}-"
-                                    f"{area}-"
-                                    f"{framesize.lower()}-"
-                                    f"{core.lower()}-"
-                                    f"{test}-"
-                                    f"{ttype.lower()}"
-                                )
+                                if dut == "trex":
+                                    core = str()
+                                tid = "-".join((
+                                    dut, phy.replace('af_xdp', 'af-xdp'), area,
+                                    framesize.lower(), core.lower(), test,
+                                    ttype.lower()
+                                ))
                                 if tid not in [itm["id"] for itm in store_sel]:
                                     store_sel.append({
                                         "id": tid,
                                 if tid not in [itm["id"] for itm in store_sel]:
                                     store_sel.append({
                                         "id": tid,
+                                        "dut": dut,
                                         "phy": phy,
                                         "area": area,
                                         "test": test,
                                         "phy": phy,
                                         "area": area,
                                         "test": test,
@@ -870,12 +1033,21 @@ class Layout:
                                         "core": core.lower(),
                                         "testtype": ttype.lower()
                                     })
                                         "core": core.lower(),
                                         "testtype": ttype.lower()
                                     })
+                    store_sel = sorted(store_sel, key=lambda d: d["id"])
+                    row_card_sel_tests = self.STYLE_ENABLED
+                    row_btns_sel_tests = self.STYLE_ENABLED
                     ctrl_panel.set(ctrl_panel.defaults)
                     ctrl_panel.set({
                         "cl-selected-options": self._list_tests(store_sel)
                     })
                     ctrl_panel.set(ctrl_panel.defaults)
                     ctrl_panel.set({
                         "cl-selected-options": self._list_tests(store_sel)
                     })
-            elif trigger_id in ("btn-sel-display", "dpr-period"):
-                _ = btn_display
+                    row_fig_tput, row_fig_lat, row_btn_dwnld = \
+                        _generate_plotting_arrea(
+                            graph_trending(
+                                self.data, store_sel, self.layout, d_start,
+                                d_end
+                            )
+                        )
+            elif trigger_id == "dpr-period":
                 row_fig_tput, row_fig_lat, row_btn_dwnld = \
                     _generate_plotting_arrea(
                         graph_trending(
                 row_fig_tput, row_fig_lat, row_btn_dwnld = \
                     _generate_plotting_arrea(
                         graph_trending(
@@ -887,6 +1059,8 @@ class Layout:
                 row_fig_tput = self.PLACEHOLDER
                 row_fig_lat = self.PLACEHOLDER
                 row_btn_dwnld = self.PLACEHOLDER
                 row_fig_tput = self.PLACEHOLDER
                 row_fig_lat = self.PLACEHOLDER
                 row_btn_dwnld = self.PLACEHOLDER
+                row_card_sel_tests = self.STYLE_DISABLED
+                row_btns_sel_tests = self.STYLE_DISABLED
                 store_sel = list()
                 ctrl_panel.set({
                         "cl-selected-options": list()
                 store_sel = list()
                 ctrl_panel.set({
                         "cl-selected-options": list()
@@ -901,11 +1075,12 @@ class Layout:
                     store_sel = new_store_sel
                 if store_sel:
                     row_fig_tput, row_fig_lat, row_btn_dwnld = \
                     store_sel = new_store_sel
                 if store_sel:
                     row_fig_tput, row_fig_lat, row_btn_dwnld = \
-                    _generate_plotting_arrea(
-                        graph_trending(
-                            self.data, store_sel, self.layout, d_start, d_end
+                        _generate_plotting_arrea(
+                            graph_trending(
+                                self.data, store_sel, self.layout, d_start,
+                                d_end
+                            )
                         )
                         )
-                    )
                     ctrl_panel.set({
                         "cl-selected-options": self._list_tests(store_sel)
                     })
                     ctrl_panel.set({
                         "cl-selected-options": self._list_tests(store_sel)
                     })
@@ -913,14 +1088,27 @@ class Layout:
                     row_fig_tput = self.PLACEHOLDER
                     row_fig_lat = self.PLACEHOLDER
                     row_btn_dwnld = self.PLACEHOLDER
                     row_fig_tput = self.PLACEHOLDER
                     row_fig_lat = self.PLACEHOLDER
                     row_btn_dwnld = self.PLACEHOLDER
+                    row_card_sel_tests = self.STYLE_DISABLED
+                    row_btns_sel_tests = self.STYLE_DISABLED
                     store_sel = list()
                     ctrl_panel.set({
                             "cl-selected-options": list()
                     })
 
                     store_sel = list()
                     ctrl_panel.set({
                             "cl-selected-options": list()
                     })
 
+            if ctrl_panel.get("cl-ctrl-core-value") and \
+                    ctrl_panel.get("cl-ctrl-framesize-value") and \
+                    ctrl_panel.get("cl-ctrl-testtype-value"):
+                disabled = False
+            else:
+                disabled = True
+            ctrl_panel.set({
+                "btn-ctrl-add-disabled": disabled
+            })
+
             ret_val = [
                 ctrl_panel.panel, store_sel,
             ret_val = [
                 ctrl_panel.panel, store_sel,
-                row_fig_tput, row_fig_lat, row_btn_dwnld
+                row_fig_tput, row_fig_lat, row_btn_dwnld,
+                row_card_sel_tests, row_btns_sel_tests
             ]
             ret_val.extend(ctrl_panel.values())
             return ret_val
             ]
             ret_val.extend(ctrl_panel.values())
             return ret_val
@@ -929,30 +1117,38 @@ class Layout:
             Output("metadata-tput-lat", "children"),
             Output("metadata-hdrh-graph", "children"),
             Output("offcanvas-metadata", "is_open"),
             Output("metadata-tput-lat", "children"),
             Output("metadata-hdrh-graph", "children"),
             Output("offcanvas-metadata", "is_open"),
-            Input("graph-tput", "clickData"),
-            Input("graph-latency", "clickData")
+            Input({"type": "graph", "index": ALL}, "clickData"),
+            prevent_initial_call=True
         )
         )
-        def _show_metadata_from_graphs(
-            tput_data: dict, lat_data: dict) -> tuple:
+        def _show_metadata_from_graphs(graph_data: dict) -> tuple:
             """
             """
             """
             """
-            if not (tput_data or lat_data):
+            try:
+                trigger_id = loads(
+                    callback_context.triggered[0]["prop_id"].split(".")[0]
+                )["index"]
+                idx = 0 if trigger_id == "tput" else 1
+                graph_data = graph_data[idx]["points"][0]
+            except (JSONDecodeError, IndexError, KeyError, ValueError,
+                    TypeError):
                 raise PreventUpdate
 
             metadata = no_update
             graph = list()
 
                 raise PreventUpdate
 
             metadata = no_update
             graph = list()
 
-            trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
-            if trigger_id == "graph-tput":
+            children = [
+                dbc.ListGroupItem(
+                    [dbc.Badge(x.split(":")[0]), x.split(": ")[1]]
+                ) for x in graph_data.get("text", "").split("<br>")
+            ]
+            if trigger_id == "tput":
                 title = "Throughput"
                 title = "Throughput"
-                txt = tput_data["points"][0]["text"].replace("<br>", "\n")
-            elif trigger_id == "graph-latency":
+            elif trigger_id == "lat":
                 title = "Latency"
                 title = "Latency"
-                txt = lat_data["points"][0]["text"].replace("<br>", "\n")
-                hdrh_data = lat_data["points"][0].get("customdata", None)
+                hdrh_data = graph_data.get("customdata", None)
                 if hdrh_data:
                     graph = [dbc.Card(
                 if hdrh_data:
                     graph = [dbc.Card(
-                        class_name="g-0",
+                        class_name="gy-2 p-0",
                         children=[
                             dbc.CardHeader(hdrh_data.pop("name")),
                             dbc.CardBody(children=[
                         children=[
                             dbc.CardHeader(hdrh_data.pop("name")),
                             dbc.CardBody(children=[
@@ -967,7 +1163,7 @@ class Layout:
                     ]
             metadata = [
                 dbc.Card(
                     ]
             metadata = [
                 dbc.Card(
-                    class_name="g-0",
+                    class_name="gy-2 p-0",
                     children=[
                         dbc.CardHeader(children=[
                             dcc.Clipboard(
                     children=[
                         dbc.CardHeader(children=[
                             dcc.Clipboard(
@@ -979,7 +1175,8 @@ class Layout:
                         ]),
                         dbc.CardBody(
                             id="tput-lat-metadata",
                         ]),
                         dbc.CardBody(
                             id="tput-lat-metadata",
-                            children=[txt]
+                            class_name="p-0",
+                            children=[dbc.ListGroup(children, flush=True), ]
                         )
                     ]
                 )
                         )
                     ]
                 )