Feat(uti): Last failed tests
[csit.git] / resources / tools / dash / app / pal / report / layout.py
1 # Copyright (c) 2022 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 """Plotly Dash HTML layout override.
15 """
16
17 import logging
18 import pandas as pd
19 import dash_bootstrap_components as dbc
20
21 from flask import Flask
22 from dash import dcc
23 from dash import html
24 from dash import callback_context, no_update, ALL
25 from dash import Input, Output, State
26 from yaml import load, FullLoader, YAMLError
27 from copy import deepcopy
28 from ast import literal_eval
29
30 from ..data.data import Data
31 from ..data.url_processing import url_decode, url_encode
32 from .graphs import graph_iterative, table_comparison, get_short_version
33
34
35 class Layout:
36     """
37     """
38
39     # If True, clear all inputs in control panel when button "ADD SELECTED" is
40     # pressed.
41     CLEAR_ALL_INPUTS = False
42
43     STYLE_DISABLED = {"display": "none"}
44     STYLE_ENABLED = {"display": "inherit"}
45
46     CL_ALL_DISABLED = [{
47         "label": "All",
48         "value": "all",
49         "disabled": True
50     }]
51     CL_ALL_ENABLED = [{
52         "label": "All",
53         "value": "all",
54         "disabled": False
55     }]
56
57     PLACEHOLDER = html.Nobr("")
58
59     DRIVERS = ("avf", "af-xdp", "rdma", "dpdk")
60
61     LABELS = {
62         "dpdk": "DPDK",
63         "container_memif": "LXC/DRC Container Memif",
64         "crypto": "IPSec IPv4 Routing",
65         "ip4": "IPv4 Routing",
66         "ip6": "IPv6 Routing",
67         "ip4_tunnels": "IPv4 Tunnels",
68         "l2": "L2 Ethernet Switching",
69         "srv6": "SRv6 Routing",
70         "vm_vhost": "VMs vhost-user",
71         "nfv_density-dcr_memif-chain_ipsec": "CNF Service Chains Routing IPSec",
72         "nfv_density-vm_vhost-chain_dot1qip4vxlan":"VNF Service Chains Tunnels",
73         "nfv_density-vm_vhost-chain": "VNF Service Chains Routing",
74         "nfv_density-dcr_memif-pipeline": "CNF Service Pipelines Routing",
75         "nfv_density-dcr_memif-chain": "CNF Service Chains Routing",
76     }
77
78     URL_STYLE = {
79         "background-color": "#d2ebf5",
80         "border-color": "#bce1f1",
81         "color": "#135d7c"
82     }
83
84     def __init__(self, app: Flask, releases: list, html_layout_file: str,
85         graph_layout_file: str, data_spec_file: str, tooltip_file: str) -> None:
86         """
87         """
88
89         # Inputs
90         self._app = app
91         self.releases = releases
92         self._html_layout_file = html_layout_file
93         self._graph_layout_file = graph_layout_file
94         self._data_spec_file = data_spec_file
95         self._tooltip_file = tooltip_file
96
97         # Read the data:
98         self._data = pd.DataFrame()
99         for rls in releases:
100             data_mrr = Data(self._data_spec_file, True).\
101                 read_iterative_mrr(release=rls.replace("csit", "rls"))
102             data_mrr["release"] = rls
103             data_ndrpdr = Data(self._data_spec_file, True).\
104                 read_iterative_ndrpdr(release=rls.replace("csit", "rls"))
105             data_ndrpdr["release"] = rls
106             self._data = pd.concat(
107                 [self._data, data_mrr, data_ndrpdr], ignore_index=True)
108
109         # Get structure of tests:
110         tbs = dict()
111         cols = ["job", "test_id", "test_type", "dut_version", "release"]
112         for _, row in self._data[cols].drop_duplicates().iterrows():
113             rls = row["release"]
114             ttype = row["test_type"]
115             lst_job = row["job"].split("-")
116             dut = lst_job[1]
117             d_ver = get_short_version(row["dut_version"], dut)
118             tbed = "-".join(lst_job[-2:])
119             lst_test_id = row["test_id"].split(".")
120             if dut == "dpdk":
121                 area = "dpdk"
122             else:
123                 area = "-".join(lst_test_id[3:-2])
124             suite = lst_test_id[-2].replace("2n1l-", "").replace("1n1l-", "").\
125                 replace("2n-", "")
126             test = lst_test_id[-1]
127             nic = suite.split("-")[0]
128             for drv in self.DRIVERS:
129                 if drv in test:
130                     driver = drv.replace("-", "_")
131                     test = test.replace(f"{drv}-", "")
132                     break
133             else:
134                 driver = "dpdk"
135             infra = "-".join((tbed, nic, driver))
136             lst_test = test.split("-")
137             framesize = lst_test[0]
138             core = lst_test[1] if lst_test[1] else "8C"
139             test = "-".join(lst_test[2: -1])
140
141             if tbs.get(rls, None) is None:
142                 tbs[rls] = dict()
143             if tbs[rls].get(dut, None) is None:
144                 tbs[rls][dut] = dict()
145             if tbs[rls][dut].get(d_ver, None) is None:
146                 tbs[rls][dut][d_ver] = dict()
147             if tbs[rls][dut][d_ver].get(infra, None) is None:
148                 tbs[rls][dut][d_ver][infra] = dict()
149             if tbs[rls][dut][d_ver][infra].get(area, None) is None:
150                 tbs[rls][dut][d_ver][infra][area] = dict()
151             if tbs[rls][dut][d_ver][infra][area].get(test, None) is None:
152                 tbs[rls][dut][d_ver][infra][area][test] = dict()
153                 tbs[rls][dut][d_ver][infra][area][test]["core"] = list()
154                 tbs[rls][dut][d_ver][infra][area][test]["frame-size"] = list()
155                 tbs[rls][dut][d_ver][infra][area][test]["test-type"] = list()
156             if core.upper() not in \
157                     tbs[rls][dut][d_ver][infra][area][test]["core"]:
158                 tbs[rls][dut][d_ver][infra][area][test]["core"].append(
159                     core.upper())
160             if framesize.upper() not in \
161                         tbs[rls][dut][d_ver][infra][area][test]["frame-size"]:
162                 tbs[rls][dut][d_ver][infra][area][test]["frame-size"].append(
163                     framesize.upper())
164             if ttype == "mrr":
165                 if "MRR" not in \
166                         tbs[rls][dut][d_ver][infra][area][test]["test-type"]:
167                     tbs[rls][dut][d_ver][infra][area][test]["test-type"].append(
168                         "MRR")
169             elif ttype == "ndrpdr":
170                 if "NDR" not in \
171                         tbs[rls][dut][d_ver][infra][area][test]["test-type"]:
172                     tbs[rls][dut][d_ver][infra][area][test]["test-type"].extend(
173                         ("NDR", "PDR", ))
174         self._spec_tbs = tbs
175
176         # Read from files:
177         self._html_layout = ""
178         self._graph_layout = None
179         self._tooltips = dict()
180
181         try:
182             with open(self._html_layout_file, "r") as file_read:
183                 self._html_layout = file_read.read()
184         except IOError as err:
185             raise RuntimeError(
186                 f"Not possible to open the file {self._html_layout_file}\n{err}"
187             )
188
189         try:
190             with open(self._graph_layout_file, "r") as file_read:
191                 self._graph_layout = load(file_read, Loader=FullLoader)
192         except IOError as err:
193             raise RuntimeError(
194                 f"Not possible to open the file {self._graph_layout_file}\n"
195                 f"{err}"
196             )
197         except YAMLError as err:
198             raise RuntimeError(
199                 f"An error occurred while parsing the specification file "
200                 f"{self._graph_layout_file}\n{err}"
201             )
202
203         try:
204             with open(self._tooltip_file, "r") as file_read:
205                 self._tooltips = load(file_read, Loader=FullLoader)
206         except IOError as err:
207             logging.warning(
208                 f"Not possible to open the file {self._tooltip_file}\n{err}"
209             )
210         except YAMLError as err:
211             logging.warning(
212                 f"An error occurred while parsing the specification file "
213                 f"{self._tooltip_file}\n{err}"
214             )
215
216         # Callbacks:
217         if self._app is not None and hasattr(self, 'callbacks'):
218             self.callbacks(self._app)
219
220     @property
221     def html_layout(self):
222         return self._html_layout
223
224     @property
225     def spec_tbs(self):
226         return self._spec_tbs
227
228     @property
229     def data(self):
230         return self._data
231
232     @property
233     def layout(self):
234         return self._graph_layout
235
236     def label(self, key: str) -> str:
237         return self.LABELS.get(key, key)
238
239     def _show_tooltip(self, id: str, title: str,
240             clipboard_id: str=None) -> list:
241         """
242         """
243         return [
244             dcc.Clipboard(target_id=clipboard_id, title="Copy URL") \
245                 if clipboard_id else str(),
246             f"{title} ",
247             dbc.Badge(
248                 id=id,
249                 children="?",
250                 pill=True,
251                 color="white",
252                 text_color="info",
253                 class_name="border ms-1",
254             ),
255             dbc.Tooltip(
256                 children=self._tooltips.get(id, str()),
257                 target=id,
258                 placement="auto"
259             )
260         ]
261
262     def add_content(self):
263         """
264         """
265         if self.html_layout and self.spec_tbs:
266             return html.Div(
267                 id="div-main",
268                 children=[
269                     dbc.Row(
270                         id="row-navbar",
271                         class_name="g-0",
272                         children=[
273                             self._add_navbar(),
274                         ]
275                     ),
276                     dcc.Loading(
277                         dbc.Offcanvas(
278                             class_name="w-50",
279                             id="offcanvas-metadata",
280                             title="Throughput And Latency",
281                             placement="end",
282                             is_open=False,
283                             children=[
284                                 dbc.Row(id="metadata-tput-lat"),
285                                 dbc.Row(id="metadata-hdrh-graph"),
286                             ]
287                         )
288                     ),
289                     dbc.Row(
290                         id="row-main",
291                         class_name="g-0",
292                         children=[
293                             dcc.Store(id="selected-tests"),
294                             dcc.Store(id="control-panel"),
295                             dcc.Location(id="url", refresh=False),
296                             self._add_ctrl_col(),
297                             self._add_plotting_col(),
298                         ]
299                     )
300                 ]
301             )
302         else:
303             return html.Div(
304                 id="div-main-error",
305                 children=[
306                     dbc.Alert(
307                         [
308                             "An Error Occured",
309                         ],
310                         color="danger",
311                     ),
312                 ]
313             )
314
315     def _add_navbar(self):
316         """Add nav element with navigation panel. It is placed on the top.
317         """
318         return dbc.NavbarSimple(
319             id="navbarsimple-main",
320             children=[
321                 dbc.NavItem(
322                     dbc.NavLink(
323                         "Iterative Test Runs",
324                         disabled=True,
325                         external_link=True,
326                         href="#"
327                     )
328                 )
329             ],
330             brand="Dashboard",
331             brand_href="/",
332             brand_external_link=True,
333             class_name="p-2",
334             fluid=True,
335         )
336
337     def _add_ctrl_col(self) -> dbc.Col:
338         """Add column with controls. It is placed on the left side.
339         """
340         return dbc.Col(
341             id="col-controls",
342             children=[
343                 self._add_ctrl_panel(),
344             ],
345         )
346
347     def _add_plotting_col(self) -> dbc.Col:
348         """Add column with plots and tables. It is placed on the right side.
349         """
350         return dbc.Col(
351             id="col-plotting-area",
352             children=[
353                 dcc.Loading(
354                     children=[
355                         dbc.Row(  # Graphs
356                             class_name="g-0 p-2",
357                             children=[
358                                 dbc.Col(
359                                     dbc.Row(  # Throughput
360                                         id="row-graph-tput",
361                                         class_name="g-0 p-2",
362                                         children=[
363                                             self.PLACEHOLDER
364                                         ]
365                                     ),
366                                     width=6
367                                 ),
368                                 dbc.Col(
369                                     dbc.Row(  # Latency
370                                         id="row-graph-lat",
371                                         class_name="g-0 p-2",
372                                         children=[
373                                             self.PLACEHOLDER
374                                         ]
375                                     ),
376                                     width=6
377                                 )
378                             ]
379                         ),
380                         dbc.Row(  # Tables
381                             id="row-table",
382                             class_name="g-0 p-2",
383                             children=[
384                                 self.PLACEHOLDER
385                             ]
386                         ),
387                         dbc.Row(  # Download
388                             id="row-btn-download",
389                             class_name="g-0 p-2",
390                             children=[
391                                 self.PLACEHOLDER
392                             ]
393                         )
394                     ]
395                 )
396             ],
397             width=9
398         )
399
400     def _add_ctrl_panel(self) -> dbc.Row:
401         """
402         """
403         return dbc.Row(
404             id="row-ctrl-panel",
405             class_name="g-0 p-2",
406             children=[
407                 dbc.Row(
408                     class_name="g-0",
409                     children=[
410                         dbc.InputGroup(
411                             [
412                                 dbc.InputGroupText(
413                                     children=self._show_tooltip(
414                                         "help-release", "CSIT Release")
415                                 ),
416                                 dbc.Select(
417                                     id="dd-ctrl-rls",
418                                     placeholder=("Select a Release..."),
419                                     options=sorted(
420                                         [
421                                             {"label": k, "value": k} \
422                                                 for k in self.spec_tbs.keys()
423                                         ],
424                                         key=lambda d: d["label"]
425                                     )
426                                 )
427                             ],
428                             class_name="mb-3",
429                             size="sm",
430                         ),
431                     ]
432                 ),
433                 dbc.Row(
434                     class_name="g-0",
435                     children=[
436                         dbc.InputGroup(
437                             [
438                                 dbc.InputGroupText(
439                                     children=self._show_tooltip(
440                                         "help-dut", "DUT")
441                                 ),
442                                 dbc.Select(
443                                     id="dd-ctrl-dut",
444                                     placeholder=(
445                                         "Select a Device under Test..."
446                                     )
447                                 )
448                             ],
449                             class_name="mb-3",
450                             size="sm",
451                         ),
452                     ]
453                 ),
454                 dbc.Row(
455                     class_name="g-0",
456                     children=[
457                         dbc.InputGroup(
458                             [
459                                 dbc.InputGroupText(
460                                     children=self._show_tooltip(
461                                         "help-dut-ver", "DUT Version")
462                                 ),
463                                 dbc.Select(
464                                     id="dd-ctrl-dutver",
465                                     placeholder=(
466                                         "Select a Version of "
467                                         "Device under Test..."
468                                     )
469                                 )
470                             ],
471                             class_name="mb-3",
472                             size="sm",
473                         ),
474                     ]
475                 ),
476                 dbc.Row(
477                     class_name="g-0",
478                     children=[
479                         dbc.InputGroup(
480                             [
481                                 dbc.InputGroupText(
482                                     children=self._show_tooltip(
483                                         "help-infra", "Infra")
484                                 ),
485                                 dbc.Select(
486                                     id="dd-ctrl-phy",
487                                     placeholder=(
488                                         "Select a Physical Test Bed "
489                                         "Topology..."
490                                     )
491                                 )
492                             ],
493                             class_name="mb-3",
494                             size="sm",
495                         ),
496                     ]
497                 ),
498                 dbc.Row(
499                     class_name="g-0",
500                     children=[
501                         dbc.InputGroup(
502                             [
503                                 dbc.InputGroupText(
504                                     children=self._show_tooltip(
505                                         "help-area", "Area")
506                                 ),
507                                 dbc.Select(
508                                     id="dd-ctrl-area",
509                                     placeholder="Select an Area...",
510                                     disabled=True,
511                                 ),
512                             ],
513                             class_name="mb-3",
514                             size="sm",
515                         ),
516                     ]
517                 ),
518                 dbc.Row(
519                     class_name="g-0",
520                     children=[
521                         dbc.InputGroup(
522                             [
523                                 dbc.InputGroupText(
524                                     children=self._show_tooltip(
525                                         "help-test", "Test")
526                                 ),
527                                 dbc.Select(
528                                     id="dd-ctrl-test",
529                                     placeholder="Select a Test...",
530                                     disabled=True,
531                                 ),
532                             ],
533                             class_name="mb-3",
534                             size="sm",
535                         ),
536                     ]
537                 ),
538                 dbc.Row(
539                     id="row-ctrl-framesize",
540                     class_name="gy-1",
541                     children=[
542                         dbc.Label(
543                             children=self._show_tooltip(
544                                 "help-framesize", "Frame Size"),
545                             class_name="p-0"
546                         ),
547                         dbc.Col(
548                             children=[
549                                 dbc.Checklist(
550                                     id="cl-ctrl-framesize-all",
551                                     options=self.CL_ALL_DISABLED,
552                                     inline=True,
553                                     switch=False
554                                 ),
555                             ],
556                             width=3
557                         ),
558                         dbc.Col(
559                             children=[
560                                 dbc.Checklist(
561                                     id="cl-ctrl-framesize",
562                                     inline=True,
563                                     switch=False
564                                 )
565                             ]
566                         )
567                     ]
568                 ),
569                 dbc.Row(
570                     id="row-ctrl-core",
571                     class_name="gy-1",
572                     children=[
573                         dbc.Label(
574                             children=self._show_tooltip(
575                                 "help-cores", "Number of Cores"),
576                             class_name="p-0"
577                         ),
578                         dbc.Col(
579                             children=[
580                                 dbc.Checklist(
581                                     id="cl-ctrl-core-all",
582                                     options=self.CL_ALL_DISABLED,
583                                     inline=False,
584                                     switch=False
585                                 )
586                             ],
587                             width=3
588                         ),
589                         dbc.Col(
590                             children=[
591                                 dbc.Checklist(
592                                     id="cl-ctrl-core",
593                                     inline=True,
594                                     switch=False
595                                 )
596                             ]
597                         )
598                     ]
599                 ),
600                 dbc.Row(
601                     id="row-ctrl-testtype",
602                     class_name="gy-1",
603                     children=[
604                         dbc.Label(
605                             children=self._show_tooltip(
606                                 "help-ttype", "Test Type"),
607                             class_name="p-0"
608                         ),
609                         dbc.Col(
610                             children=[
611                                 dbc.Checklist(
612                                     id="cl-ctrl-testtype-all",
613                                     options=self.CL_ALL_DISABLED,
614                                     inline=True,
615                                     switch=False
616                                 ),
617                             ],
618                             width=3
619                         ),
620                         dbc.Col(
621                             children=[
622                                 dbc.Checklist(
623                                     id="cl-ctrl-testtype",
624                                     inline=True,
625                                     switch=False
626                                 )
627                             ]
628                         )
629                     ]
630                 ),
631                 dbc.Row(
632                     class_name="gy-1 p-0",
633                     children=[
634                         dbc.ButtonGroup(
635                             [
636                                 dbc.Button(
637                                     id="btn-ctrl-add",
638                                     children="Add Selected",
639                                     class_name="me-1",
640                                     color="info"
641                                 )
642                             ],
643                             size="md",
644                         )
645                     ]
646                 ),
647                 dbc.Row(
648                     id="row-card-sel-tests",
649                     class_name="gy-1",
650                     style=self.STYLE_DISABLED,
651                     children=[
652                         dbc.Label(
653                             "Selected tests",
654                             class_name="p-0"
655                         ),
656                         dbc.Checklist(
657                             class_name="overflow-auto",
658                             id="cl-selected",
659                             options=[],
660                             inline=False,
661                             style={"max-height": "20em"},
662                         )
663                     ],
664                 ),
665                 dbc.Row(
666                     id="row-btns-sel-tests",
667                     style=self.STYLE_DISABLED,
668                     children=[
669                         dbc.ButtonGroup(
670                             class_name="gy-2",
671                             children=[
672                                 dbc.Button(
673                                     id="btn-sel-remove",
674                                     children="Remove Selected",
675                                     class_name="w-100 me-1",
676                                     color="info",
677                                     disabled=False
678                                 ),
679                                 dbc.Button(
680                                     id="btn-sel-remove-all",
681                                     children="Remove All",
682                                     class_name="w-100 me-1",
683                                     color="info",
684                                     disabled=False
685                                 ),
686                             ],
687                             size="md",
688                         )
689                     ]
690                 ),
691             ]
692         )
693
694     class ControlPanel:
695         def __init__(self, panel: dict) -> None:
696
697             CL_ALL_DISABLED = [{
698                 "label": "All",
699                 "value": "all",
700                 "disabled": True
701             }]
702
703             # Defines also the order of keys
704             self._defaults = {
705                 "dd-rls-value": str(),
706                 "dd-dut-options": list(),
707                 "dd-dut-disabled": True,
708                 "dd-dut-value": str(),
709                 "dd-dutver-options": list(),
710                 "dd-dutver-disabled": True,
711                 "dd-dutver-value": str(),
712                 "dd-phy-options": list(),
713                 "dd-phy-disabled": True,
714                 "dd-phy-value": str(),
715                 "dd-area-options": list(),
716                 "dd-area-disabled": True,
717                 "dd-area-value": str(),
718                 "dd-test-options": list(),
719                 "dd-test-disabled": True,
720                 "dd-test-value": str(),
721                 "cl-core-options": list(),
722                 "cl-core-value": list(),
723                 "cl-core-all-value": list(),
724                 "cl-core-all-options": CL_ALL_DISABLED,
725                 "cl-framesize-options": list(),
726                 "cl-framesize-value": list(),
727                 "cl-framesize-all-value": list(),
728                 "cl-framesize-all-options": CL_ALL_DISABLED,
729                 "cl-testtype-options": list(),
730                 "cl-testtype-value": list(),
731                 "cl-testtype-all-value": list(),
732                 "cl-testtype-all-options": CL_ALL_DISABLED,
733                 "btn-add-disabled": True,
734                 "cl-selected-options": list()
735             }
736
737             self._panel = deepcopy(self._defaults)
738             if panel:
739                 for key in self._defaults:
740                     self._panel[key] = panel[key]
741
742         @property
743         def defaults(self) -> dict:
744             return self._defaults
745
746         @property
747         def panel(self) -> dict:
748             return self._panel
749
750         def set(self, kwargs: dict) -> None:
751             for key, val in kwargs.items():
752                 if key in self._panel:
753                     self._panel[key] = val
754                 else:
755                     raise KeyError(f"The key {key} is not defined.")
756
757         def get(self, key: str) -> any:
758             return self._panel[key]
759
760         def values(self) -> tuple:
761             return tuple(self._panel.values())
762
763     @staticmethod
764     def _sync_checklists(opt: list, sel: list, all: list, id: str) -> tuple:
765         """
766         """
767         options = {v["value"] for v in opt}
768         if id =="all":
769             sel = list(options) if all else list()
770         else:
771             all = ["all", ] if set(sel) == options else list()
772         return sel, all
773
774     @staticmethod
775     def _list_tests(selection: dict) -> list:
776         """Display selected tests with checkboxes
777         """
778         if selection:
779             return [{"label": v["id"], "value": v["id"]} for v in selection]
780         else:
781             return list()
782
783     def callbacks(self, app):
784
785         def _generate_plotting_area(figs: tuple, table: pd.DataFrame,
786                 url: str) -> tuple:
787             """
788             """
789
790             (fig_tput, fig_lat) = figs
791
792             row_fig_tput = self.PLACEHOLDER
793             row_fig_lat = self.PLACEHOLDER
794             row_table = self.PLACEHOLDER
795             row_btn_dwnld = self.PLACEHOLDER
796
797             if fig_tput:
798                 row_fig_tput = [
799                     dcc.Graph(
800                         id={"type": "graph", "index": "tput"},
801                         figure=fig_tput
802                     )
803                 ]
804                 row_btn_dwnld = [
805                     dbc.Col(  # Download
806                         width=2,
807                         children=[
808                             dcc.Loading(children=[
809                                 dbc.Button(
810                                     id="btn-download-data",
811                                     children=self._show_tooltip(
812                                         "help-download", "Download Data"),
813                                     class_name="me-1",
814                                     color="info"
815                                 ),
816                                 dcc.Download(id="download-data")
817                             ]),
818                         ]
819                     ),
820                     dbc.Col(  # Show URL
821                         width=10,
822                         children=[
823                             dbc.InputGroup(
824                                 class_name="me-1",
825                                 children=[
826                                     dbc.InputGroupText(
827                                         style=self.URL_STYLE,
828                                         children=self._show_tooltip(
829                                             "help-url", "URL", "input-url")
830                                     ),
831                                     dbc.Input(
832                                         id="input-url",
833                                         readonly=True,
834                                         type="url",
835                                         style=self.URL_STYLE,
836                                         value=url
837                                     )
838                                 ]
839                             )
840                         ]
841                     )
842                 ]
843             if fig_lat:
844                 row_fig_lat = [
845                     dcc.Graph(
846                         id={"type": "graph", "index": "lat"},
847                         figure=fig_lat
848                     )
849                 ]
850             if not table.empty:
851                 row_table = [
852                     dbc.Table.from_dataframe(
853                         table,
854                         id={"type": "table", "index": "compare"},
855                         striped=True,
856                         bordered=True,
857                         hover=True
858                     )
859                 ]
860
861             return row_fig_tput, row_fig_lat, row_table, row_btn_dwnld
862
863         @app.callback(
864             Output("control-panel", "data"),  # Store
865             Output("selected-tests", "data"),  # Store
866             Output("row-graph-tput", "children"),
867             Output("row-graph-lat", "children"),
868             Output("row-table", "children"),
869             Output("row-btn-download", "children"),
870             Output("row-card-sel-tests", "style"),
871             Output("row-btns-sel-tests", "style"),
872             Output("dd-ctrl-rls", "value"),
873             Output("dd-ctrl-dut", "options"),
874             Output("dd-ctrl-dut", "disabled"),
875             Output("dd-ctrl-dut", "value"),
876             Output("dd-ctrl-dutver", "options"),
877             Output("dd-ctrl-dutver", "disabled"),
878             Output("dd-ctrl-dutver", "value"),
879             Output("dd-ctrl-phy", "options"),
880             Output("dd-ctrl-phy", "disabled"),
881             Output("dd-ctrl-phy", "value"),
882             Output("dd-ctrl-area", "options"),
883             Output("dd-ctrl-area", "disabled"),
884             Output("dd-ctrl-area", "value"),
885             Output("dd-ctrl-test", "options"),
886             Output("dd-ctrl-test", "disabled"),
887             Output("dd-ctrl-test", "value"),
888             Output("cl-ctrl-core", "options"),
889             Output("cl-ctrl-core", "value"),
890             Output("cl-ctrl-core-all", "value"),
891             Output("cl-ctrl-core-all", "options"),
892             Output("cl-ctrl-framesize", "options"),
893             Output("cl-ctrl-framesize", "value"),
894             Output("cl-ctrl-framesize-all", "value"),
895             Output("cl-ctrl-framesize-all", "options"),
896             Output("cl-ctrl-testtype", "options"),
897             Output("cl-ctrl-testtype", "value"),
898             Output("cl-ctrl-testtype-all", "value"),
899             Output("cl-ctrl-testtype-all", "options"),
900             Output("btn-ctrl-add", "disabled"),
901             Output("cl-selected", "options"),  # User selection
902             State("control-panel", "data"),  # Store
903             State("selected-tests", "data"),  # Store
904             State("cl-selected", "value"),  # User selection
905             Input("dd-ctrl-rls", "value"),
906             Input("dd-ctrl-dut", "value"),
907             Input("dd-ctrl-dutver", "value"),
908             Input("dd-ctrl-phy", "value"),
909             Input("dd-ctrl-area", "value"),
910             Input("dd-ctrl-test", "value"),
911             Input("cl-ctrl-core", "value"),
912             Input("cl-ctrl-core-all", "value"),
913             Input("cl-ctrl-framesize", "value"),
914             Input("cl-ctrl-framesize-all", "value"),
915             Input("cl-ctrl-testtype", "value"),
916             Input("cl-ctrl-testtype-all", "value"),
917             Input("btn-ctrl-add", "n_clicks"),
918             Input("btn-sel-remove", "n_clicks"),
919             Input("btn-sel-remove-all", "n_clicks"),
920             Input("url", "href")
921         )
922         def _update_ctrl_panel(cp_data: dict, store_sel: list, list_sel: list,
923             dd_rls: str, dd_dut: str, dd_dutver: str, dd_phy: str, dd_area: str,
924             dd_test: str, cl_core: list, cl_core_all: list, cl_framesize: list,
925             cl_framesize_all: list, cl_testtype: list, cl_testtype_all: list,
926             btn_add: int, btn_remove: int, btn_remove_all: int,
927             href: str) -> tuple:
928             """
929             """
930
931             def _gen_new_url(parsed_url: dict, store_sel: list) -> str:
932
933                 if parsed_url:
934                     new_url = url_encode({
935                         "scheme": parsed_url["scheme"],
936                         "netloc": parsed_url["netloc"],
937                         "path": parsed_url["path"],
938                         "params": {
939                             "store_sel": store_sel,
940                         }
941                     })
942                 else:
943                     new_url = str()
944                 return new_url
945
946
947             ctrl_panel = self.ControlPanel(cp_data)
948
949             # Parse the url:
950             parsed_url = url_decode(href)
951
952             row_fig_tput = no_update
953             row_fig_lat = no_update
954             row_table = no_update
955             row_btn_dwnld = no_update
956             row_card_sel_tests = no_update
957             row_btns_sel_tests = no_update
958
959             trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
960
961             if trigger_id == "dd-ctrl-rls":
962                 try:
963                     rls = self.spec_tbs[dd_rls]
964                     options = sorted(
965                         [{"label": v, "value": v} for v in rls.keys()],
966                         key=lambda d: d["label"]
967                     )
968                     disabled = False
969                 except KeyError:
970                     options = list()
971                     disabled = True
972                 ctrl_panel.set({
973                     "dd-rls-value": dd_rls,
974                     "dd-dut-value": str(),
975                     "dd-dut-options": options,
976                     "dd-dut-disabled": disabled,
977                     "dd-dutver-value": str(),
978                     "dd-dutver-options": list(),
979                     "dd-dutver-disabled": True,
980                     "dd-phy-value": str(),
981                     "dd-phy-options": list(),
982                     "dd-phy-disabled": True,
983                     "dd-area-value": str(),
984                     "dd-area-options": list(),
985                     "dd-area-disabled": True,
986                     "dd-test-value": str(),
987                     "dd-test-options": list(),
988                     "dd-test-disabled": True,
989                     "cl-core-options": list(),
990                     "cl-core-value": list(),
991                     "cl-core-all-value": list(),
992                     "cl-core-all-options": self.CL_ALL_DISABLED,
993                     "cl-framesize-options": list(),
994                     "cl-framesize-value": list(),
995                     "cl-framesize-all-value": list(),
996                     "cl-framesize-all-options": self.CL_ALL_DISABLED,
997                     "cl-testtype-options": list(),
998                     "cl-testtype-value": list(),
999                     "cl-testtype-all-value": list(),
1000                     "cl-testtype-all-options": self.CL_ALL_DISABLED
1001                 })
1002             elif trigger_id == "dd-ctrl-dut":
1003                 try:
1004                     rls = ctrl_panel.get("dd-rls-value")
1005                     dut = self.spec_tbs[rls][dd_dut]
1006                     options = sorted(
1007                         [{"label": v, "value": v} for v in dut.keys()],
1008                         key=lambda d: d["label"]
1009                     )
1010                     disabled = False
1011                 except KeyError:
1012                     options = list()
1013                     disabled = True
1014                 ctrl_panel.set({
1015                     "dd-dut-value": dd_dut,
1016                     "dd-dutver-value": str(),
1017                     "dd-dutver-options": options,
1018                     "dd-dutver-disabled": disabled,
1019                     "dd-phy-value": str(),
1020                     "dd-phy-options": list(),
1021                     "dd-phy-disabled": True,
1022                     "dd-area-value": str(),
1023                     "dd-area-options": list(),
1024                     "dd-area-disabled": True,
1025                     "dd-test-value": str(),
1026                     "dd-test-options": list(),
1027                     "dd-test-disabled": True,
1028                     "cl-core-options": list(),
1029                     "cl-core-value": list(),
1030                     "cl-core-all-value": list(),
1031                     "cl-core-all-options": self.CL_ALL_DISABLED,
1032                     "cl-framesize-options": list(),
1033                     "cl-framesize-value": list(),
1034                     "cl-framesize-all-value": list(),
1035                     "cl-framesize-all-options": self.CL_ALL_DISABLED,
1036                     "cl-testtype-options": list(),
1037                     "cl-testtype-value": list(),
1038                     "cl-testtype-all-value": list(),
1039                     "cl-testtype-all-options": self.CL_ALL_DISABLED
1040                 })
1041             elif trigger_id == "dd-ctrl-dutver":
1042                 try:
1043                     rls = ctrl_panel.get("dd-rls-value")
1044                     dut = ctrl_panel.get("dd-dut-value")
1045                     dutver = self.spec_tbs[rls][dut][dd_dutver]
1046                     options = sorted(
1047                         [{"label": v, "value": v} for v in dutver.keys()],
1048                         key=lambda d: d["label"]
1049                     )
1050                     disabled = False
1051                 except KeyError:
1052                     options = list()
1053                     disabled = True
1054                 ctrl_panel.set({
1055                     "dd-dutver-value": dd_dutver,
1056                     "dd-phy-value": str(),
1057                     "dd-phy-options": options,
1058                     "dd-phy-disabled": disabled,
1059                     "dd-area-value": str(),
1060                     "dd-area-options": list(),
1061                     "dd-area-disabled": True,
1062                     "dd-test-value": str(),
1063                     "dd-test-options": list(),
1064                     "dd-test-disabled": True,
1065                     "cl-core-options": list(),
1066                     "cl-core-value": list(),
1067                     "cl-core-all-value": list(),
1068                     "cl-core-all-options": self.CL_ALL_DISABLED,
1069                     "cl-framesize-options": list(),
1070                     "cl-framesize-value": list(),
1071                     "cl-framesize-all-value": list(),
1072                     "cl-framesize-all-options": self.CL_ALL_DISABLED,
1073                     "cl-testtype-options": list(),
1074                     "cl-testtype-value": list(),
1075                     "cl-testtype-all-value": list(),
1076                     "cl-testtype-all-options": self.CL_ALL_DISABLED
1077                 })
1078             elif trigger_id == "dd-ctrl-phy":
1079                 try:
1080                     rls = ctrl_panel.get("dd-rls-value")
1081                     dut = ctrl_panel.get("dd-dut-value")
1082                     dutver = ctrl_panel.get("dd-dutver-value")
1083                     phy = self.spec_tbs[rls][dut][dutver][dd_phy]
1084                     options = sorted(
1085                         [{"label": self.label(v), "value": v}
1086                             for v in phy.keys()],
1087                         key=lambda d: d["label"]
1088                     )
1089                     disabled = False
1090                 except KeyError:
1091                     options = list()
1092                     disabled = True
1093                 ctrl_panel.set({
1094                     "dd-phy-value": dd_phy,
1095                     "dd-area-value": str(),
1096                     "dd-area-options": options,
1097                     "dd-area-disabled": disabled,
1098                     "dd-test-value": str(),
1099                     "dd-test-options": list(),
1100                     "dd-test-disabled": True,
1101                     "cl-core-options": list(),
1102                     "cl-core-value": list(),
1103                     "cl-core-all-value": list(),
1104                     "cl-core-all-options": self.CL_ALL_DISABLED,
1105                     "cl-framesize-options": list(),
1106                     "cl-framesize-value": list(),
1107                     "cl-framesize-all-value": list(),
1108                     "cl-framesize-all-options": self.CL_ALL_DISABLED,
1109                     "cl-testtype-options": list(),
1110                     "cl-testtype-value": list(),
1111                     "cl-testtype-all-value": list(),
1112                     "cl-testtype-all-options": self.CL_ALL_DISABLED
1113                 })
1114             elif trigger_id == "dd-ctrl-area":
1115                 try:
1116                     rls = ctrl_panel.get("dd-rls-value")
1117                     dut = ctrl_panel.get("dd-dut-value")
1118                     dutver = ctrl_panel.get("dd-dutver-value")
1119                     phy = ctrl_panel.get("dd-phy-value")
1120                     area = self.spec_tbs[rls][dut][dutver][phy][dd_area]
1121                     options = sorted(
1122                         [{"label": v, "value": v} for v in area.keys()],
1123                         key=lambda d: d["label"]
1124                     )
1125                     disabled = False
1126                 except KeyError:
1127                     options = list()
1128                     disabled = True
1129                 ctrl_panel.set({
1130                     "dd-area-value": dd_area,
1131                     "dd-test-value": str(),
1132                     "dd-test-options": options,
1133                     "dd-test-disabled": disabled,
1134                     "cl-core-options": list(),
1135                     "cl-core-value": list(),
1136                     "cl-core-all-value": list(),
1137                     "cl-core-all-options": self.CL_ALL_DISABLED,
1138                     "cl-framesize-options": list(),
1139                     "cl-framesize-value": list(),
1140                     "cl-framesize-all-value": list(),
1141                     "cl-framesize-all-options": self.CL_ALL_DISABLED,
1142                     "cl-testtype-options": list(),
1143                     "cl-testtype-value": list(),
1144                     "cl-testtype-all-value": list(),
1145                     "cl-testtype-all-options": self.CL_ALL_DISABLED
1146                 })
1147             elif trigger_id == "dd-ctrl-test":
1148                 rls = ctrl_panel.get("dd-rls-value")
1149                 dut = ctrl_panel.get("dd-dut-value")
1150                 dutver = ctrl_panel.get("dd-dutver-value")
1151                 phy = ctrl_panel.get("dd-phy-value")
1152                 area = ctrl_panel.get("dd-area-value")
1153                 test = self.spec_tbs[rls][dut][dutver][phy][area][dd_test]
1154                 if dut and phy and area and dd_test:
1155                     ctrl_panel.set({
1156                         "dd-test-value": dd_test,
1157                         "cl-core-options": [{"label": v, "value": v}
1158                             for v in sorted(test["core"])],
1159                         "cl-core-value": list(),
1160                         "cl-core-all-value": list(),
1161                         "cl-core-all-options": self.CL_ALL_ENABLED,
1162                         "cl-framesize-options": [{"label": v, "value": v}
1163                             for v in sorted(test["frame-size"])],
1164                         "cl-framesize-value": list(),
1165                         "cl-framesize-all-value": list(),
1166                         "cl-framesize-all-options": self.CL_ALL_ENABLED,
1167                         "cl-testtype-options": [{"label": v, "value": v}
1168                             for v in sorted(test["test-type"])],
1169                         "cl-testtype-value": list(),
1170                         "cl-testtype-all-value": list(),
1171                         "cl-testtype-all-options": self.CL_ALL_ENABLED,
1172                     })
1173             elif trigger_id == "cl-ctrl-core":
1174                 val_sel, val_all = self._sync_checklists(
1175                     opt=ctrl_panel.get("cl-core-options"),
1176                     sel=cl_core,
1177                     all=list(),
1178                     id=""
1179                 )
1180                 ctrl_panel.set({
1181                     "cl-core-value": val_sel,
1182                     "cl-core-all-value": val_all,
1183                 })
1184             elif trigger_id == "cl-ctrl-core-all":
1185                 val_sel, val_all = self._sync_checklists(
1186                     opt = ctrl_panel.get("cl-core-options"),
1187                     sel=list(),
1188                     all=cl_core_all,
1189                     id="all"
1190                 )
1191                 ctrl_panel.set({
1192                     "cl-core-value": val_sel,
1193                     "cl-core-all-value": val_all,
1194                 })
1195             elif trigger_id == "cl-ctrl-framesize":
1196                 val_sel, val_all = self._sync_checklists(
1197                     opt = ctrl_panel.get("cl-framesize-options"),
1198                     sel=cl_framesize,
1199                     all=list(),
1200                     id=""
1201                 )
1202                 ctrl_panel.set({
1203                     "cl-framesize-value": val_sel,
1204                     "cl-framesize-all-value": val_all,
1205                 })
1206             elif trigger_id == "cl-ctrl-framesize-all":
1207                 val_sel, val_all = self._sync_checklists(
1208                     opt = ctrl_panel.get("cl-framesize-options"),
1209                     sel=list(),
1210                     all=cl_framesize_all,
1211                     id="all"
1212                 )
1213                 ctrl_panel.set({
1214                     "cl-framesize-value": val_sel,
1215                     "cl-framesize-all-value": val_all,
1216                 })
1217             elif trigger_id == "cl-ctrl-testtype":
1218                 val_sel, val_all = self._sync_checklists(
1219                     opt = ctrl_panel.get("cl-testtype-options"),
1220                     sel=cl_testtype,
1221                     all=list(),
1222                     id=""
1223                 )
1224                 ctrl_panel.set({
1225                     "cl-testtype-value": val_sel,
1226                     "cl-testtype-all-value": val_all,
1227                 })
1228             elif trigger_id == "cl-ctrl-testtype-all":
1229                 val_sel, val_all = self._sync_checklists(
1230                     opt = ctrl_panel.get("cl-testtype-options"),
1231                     sel=list(),
1232                     all=cl_testtype_all,
1233                     id="all"
1234                 )
1235                 ctrl_panel.set({
1236                     "cl-testtype-value": val_sel,
1237                     "cl-testtype-all-value": val_all,
1238                 })
1239             elif trigger_id == "btn-ctrl-add":
1240                 _ = btn_add
1241                 rls = ctrl_panel.get("dd-rls-value")
1242                 dut = ctrl_panel.get("dd-dut-value")
1243                 dutver = ctrl_panel.get("dd-dutver-value")
1244                 phy = ctrl_panel.get("dd-phy-value")
1245                 area = ctrl_panel.get("dd-area-value")
1246                 test = ctrl_panel.get("dd-test-value")
1247                 cores = ctrl_panel.get("cl-core-value")
1248                 framesizes = ctrl_panel.get("cl-framesize-value")
1249                 testtypes = ctrl_panel.get("cl-testtype-value")
1250                 # Add selected test to the list of tests in store:
1251                 if all((rls, dut, dutver, phy, area, test, cores, framesizes,
1252                         testtypes)):
1253                     if store_sel is None:
1254                         store_sel = list()
1255                     for core in cores:
1256                         for framesize in framesizes:
1257                             for ttype in testtypes:
1258                                 if dut == "trex":
1259                                     core = str()
1260                                 tid = "-".join((rls, dut, dutver,
1261                                     phy.replace('af_xdp', 'af-xdp'), area,
1262                                     framesize.lower(), core.lower(), test,
1263                                     ttype.lower()))
1264                                 if tid not in [itm["id"] for itm in store_sel]:
1265                                     store_sel.append({
1266                                         "id": tid,
1267                                         "rls": rls,
1268                                         "dut": dut,
1269                                         "dutver": dutver,
1270                                         "phy": phy,
1271                                         "area": area,
1272                                         "test": test,
1273                                         "framesize": framesize.lower(),
1274                                         "core": core.lower(),
1275                                         "testtype": ttype.lower()
1276                                     })
1277                     store_sel = sorted(store_sel, key=lambda d: d["id"])
1278                     row_card_sel_tests = self.STYLE_ENABLED
1279                     row_btns_sel_tests = self.STYLE_ENABLED
1280                     if self.CLEAR_ALL_INPUTS:
1281                         ctrl_panel.set(ctrl_panel.defaults)
1282                     ctrl_panel.set({
1283                         "cl-selected-options": self._list_tests(store_sel)
1284                     })
1285                     row_fig_tput, row_fig_lat, row_table, row_btn_dwnld = \
1286                         _generate_plotting_area(
1287                             graph_iterative(self.data, store_sel, self.layout),
1288                             table_comparison(self.data, store_sel),
1289                             _gen_new_url(parsed_url, store_sel)
1290                         )
1291             elif trigger_id == "btn-sel-remove-all":
1292                 _ = btn_remove_all
1293                 row_fig_tput = self.PLACEHOLDER
1294                 row_fig_lat = self.PLACEHOLDER
1295                 row_table = self.PLACEHOLDER
1296                 row_btn_dwnld = self.PLACEHOLDER
1297                 row_card_sel_tests = self.STYLE_DISABLED
1298                 row_btns_sel_tests = self.STYLE_DISABLED
1299                 store_sel = list()
1300                 ctrl_panel.set({"cl-selected-options": list()})
1301             elif trigger_id == "btn-sel-remove":
1302                 _ = btn_remove
1303                 if list_sel:
1304                     new_store_sel = list()
1305                     for item in store_sel:
1306                         if item["id"] not in list_sel:
1307                             new_store_sel.append(item)
1308                     store_sel = new_store_sel
1309                 if store_sel:
1310                     row_fig_tput, row_fig_lat, row_table, row_btn_dwnld = \
1311                         _generate_plotting_area(
1312                             graph_iterative(self.data, store_sel, self.layout),
1313                             table_comparison(self.data, store_sel),
1314                             _gen_new_url(parsed_url, store_sel)
1315                         )
1316                     ctrl_panel.set({
1317                         "cl-selected-options": self._list_tests(store_sel)
1318                     })
1319                 else:
1320                     row_fig_tput = self.PLACEHOLDER
1321                     row_fig_lat = self.PLACEHOLDER
1322                     row_table = self.PLACEHOLDER
1323                     row_btn_dwnld = self.PLACEHOLDER
1324                     row_card_sel_tests = self.STYLE_DISABLED
1325                     row_btns_sel_tests = self.STYLE_DISABLED
1326                     store_sel = list()
1327                     ctrl_panel.set({"cl-selected-options": list()})
1328             elif trigger_id == "url":
1329                 # TODO: Add verification
1330                 url_params = parsed_url["params"]
1331                 if url_params:
1332                     store_sel = literal_eval(
1333                         url_params.get("store_sel", list())[0])
1334                     if store_sel:
1335                         row_fig_tput, row_fig_lat, row_table, row_btn_dwnld = \
1336                             _generate_plotting_area(
1337                                 graph_iterative(self.data, store_sel,
1338                                     self.layout),
1339                                 table_comparison(self.data, store_sel),
1340                                 _gen_new_url(parsed_url, store_sel)
1341                             )
1342                         row_card_sel_tests = self.STYLE_ENABLED
1343                         row_btns_sel_tests = self.STYLE_ENABLED
1344                         ctrl_panel.set({
1345                             "cl-selected-options": self._list_tests(store_sel)
1346                         })
1347                     else:
1348                         row_fig_tput = self.PLACEHOLDER
1349                         row_fig_lat = self.PLACEHOLDER
1350                         row_table = self.PLACEHOLDER
1351                         row_btn_dwnld = self.PLACEHOLDER
1352                         row_card_sel_tests = self.STYLE_DISABLED
1353                         row_btns_sel_tests = self.STYLE_DISABLED
1354                         store_sel = list()
1355                         ctrl_panel.set({"cl-selected-options": list()})
1356
1357             if ctrl_panel.get("cl-core-value") and \
1358                     ctrl_panel.get("cl-framesize-value") and \
1359                     ctrl_panel.get("cl-testtype-value"):
1360                 disabled = False
1361             else:
1362                 disabled = True
1363             ctrl_panel.set({"btn-add-disabled": disabled})
1364
1365             ret_val = [
1366                 ctrl_panel.panel, store_sel,
1367                 row_fig_tput, row_fig_lat, row_table, row_btn_dwnld,
1368                 row_card_sel_tests, row_btns_sel_tests
1369             ]
1370             ret_val.extend(ctrl_panel.values())
1371             return ret_val
1372
1373         # @app.callback(
1374         #     Output("download-data", "data"),
1375         #     State("selected-tests", "data"),
1376         #     Input("btn-download-data", "n_clicks"),
1377         #     prevent_initial_call=True
1378         # )
1379         # def _download_data(store_sel, n_clicks):
1380         #     """
1381         #     """
1382
1383         #     if not n_clicks:
1384         #         raise PreventUpdate
1385
1386         #     if not store_sel:
1387         #         raise PreventUpdate
1388
1389         #     df = pd.DataFrame()
1390         #     for itm in store_sel:
1391         #         sel_data = select_trending_data(self.data, itm)
1392         #         if sel_data is None:
1393         #             continue
1394         #         df = pd.concat([df, sel_data], ignore_index=True)
1395
1396         #     return dcc.send_data_frame(df.to_csv, "trending_data.csv")