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