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