CDash: Improvements in layout
[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=C.BRAND,
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             html.Div(
316                 children=self._add_ctrl_panel(),
317                 className="sticky-top"
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 [
376             dbc.Row(
377                 class_name="g-0 p-1",
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                         size="sm",
398                     ),
399                 ]
400             ),
401             dbc.Row(
402                 class_name="g-0 p-1",
403                 children=[
404                     dbc.InputGroup(
405                         [
406                             dbc.InputGroupText(
407                                 children=show_tooltip(self._tooltips,
408                                     "help-dut", "DUT")
409                             ),
410                             dbc.Select(
411                                 id="dd-ctrl-dut",
412                                 placeholder=(
413                                     "Select a Device under Test..."
414                                 )
415                             )
416                         ],
417                         size="sm",
418                     ),
419                 ]
420             ),
421             dbc.Row(
422                 class_name="g-0 p-1",
423                 children=[
424                     dbc.InputGroup(
425                         [
426                             dbc.InputGroupText(
427                                 children=show_tooltip(self._tooltips,
428                                     "help-dut-ver", "DUT Version")
429                             ),
430                             dbc.Select(
431                                 id="dd-ctrl-dutver",
432                                 placeholder=(
433                                     "Select a Version of "
434                                     "Device under Test..."
435                                 )
436                             )
437                         ],
438                         size="sm",
439                     ),
440                 ]
441             ),
442             dbc.Row(
443                 class_name="g-0 p-1",
444                 children=[
445                     dbc.InputGroup(
446                         [
447                             dbc.InputGroupText(
448                                 children=show_tooltip(self._tooltips,
449                                     "help-infra", "Infra")
450                             ),
451                             dbc.Select(
452                                 id="dd-ctrl-phy",
453                                 placeholder=(
454                                     "Select a Physical Test Bed "
455                                     "Topology..."
456                                 )
457                             )
458                         ],
459                         size="sm",
460                     ),
461                 ]
462             ),
463             dbc.Row(
464                 class_name="g-0 p-1",
465                 children=[
466                     dbc.InputGroup(
467                         [
468                             dbc.InputGroupText(
469                                 children=show_tooltip(self._tooltips,
470                                     "help-area", "Area")
471                             ),
472                             dbc.Select(
473                                 id="dd-ctrl-area",
474                                 placeholder="Select an Area...",
475                                 disabled=True,
476                             ),
477                         ],
478                         size="sm",
479                     ),
480                 ]
481             ),
482             dbc.Row(
483                 class_name="g-0 p-1",
484                 children=[
485                     dbc.InputGroup(
486                         [
487                             dbc.InputGroupText(
488                                 children=show_tooltip(self._tooltips,
489                                     "help-test", "Test")
490                             ),
491                             dbc.Select(
492                                 id="dd-ctrl-test",
493                                 placeholder="Select a Test...",
494                                 disabled=True,
495                             ),
496                         ],
497                         size="sm",
498                     ),
499                 ]
500             ),
501             dbc.Row(
502                 class_name="g-0 p-1",
503                 children=[
504                     dbc.Label(
505                         children=show_tooltip(self._tooltips,
506                             "help-framesize", "Frame Size"),
507                     ),
508                     dbc.Col(
509                         children=[
510                             dbc.Checklist(
511                                 id="cl-ctrl-framesize-all",
512                                 options=C.CL_ALL_DISABLED,
513                                 inline=True,
514                                 switch=False
515                             ),
516                         ],
517                         width=3
518                     ),
519                     dbc.Col(
520                         children=[
521                             dbc.Checklist(
522                                 id="cl-ctrl-framesize",
523                                 inline=True,
524                                 switch=False
525                             )
526                         ]
527                     )
528                 ]
529             ),
530             dbc.Row(
531                 class_name="g-0 p-1",
532                 children=[
533                     dbc.Label(
534                         children=show_tooltip(self._tooltips,
535                             "help-cores", "Number of Cores"),
536                     ),
537                     dbc.Col(
538                         children=[
539                             dbc.Checklist(
540                                 id="cl-ctrl-core-all",
541                                 options=C.CL_ALL_DISABLED,
542                                 inline=False,
543                                 switch=False
544                             )
545                         ],
546                         width=3
547                     ),
548                     dbc.Col(
549                         children=[
550                             dbc.Checklist(
551                                 id="cl-ctrl-core",
552                                 inline=True,
553                                 switch=False
554                             )
555                         ]
556                     )
557                 ]
558             ),
559             dbc.Row(
560                 class_name="g-0 p-1",
561                 children=[
562                     dbc.Label(
563                         children=show_tooltip(self._tooltips,
564                             "help-ttype", "Test Type"),
565                     ),
566                     dbc.Col(
567                         children=[
568                             dbc.Checklist(
569                                 id="cl-ctrl-testtype-all",
570                                 options=C.CL_ALL_DISABLED,
571                                 inline=True,
572                                 switch=False
573                             ),
574                         ],
575                         width=3
576                     ),
577                     dbc.Col(
578                         children=[
579                             dbc.Checklist(
580                                 id="cl-ctrl-testtype",
581                                 inline=True,
582                                 switch=False
583                             )
584                         ]
585                     )
586                 ]
587             ),
588             dbc.Row(
589                 class_name="g-0 p-1",
590                 children=[
591                     dbc.Label(
592                         children=show_tooltip(self._tooltips,
593                             "help-normalize", "Normalize"),
594                     ),
595                     dbc.Col(
596                         children=[
597                             dbc.Checklist(
598                                 id="cl-ctrl-normalize",
599                                 options=[{
600                                     "value": "normalize",
601                                     "label": (
602                                         "Normalize results to CPU "
603                                         "frequency 2GHz"
604                                     )
605                                 }],
606                                 value=[],
607                                 inline=True,
608                                 switch=False
609                             ),
610                         ]
611                     )
612                 ]
613             ),
614             dbc.Row(
615                 class_name="g-0 p-1",
616                 children=[
617                     dbc.ButtonGroup(
618                         [
619                             dbc.Button(
620                                 id="btn-ctrl-add",
621                                 children="Add Selected",
622                                 color="info"
623                             )
624                         ]
625                     )
626                 ]
627             ),
628             dbc.Row(
629                 id="row-card-sel-tests",
630                 class_name="g-0 p-1",
631                 style=C.STYLE_DISABLED,
632                 children=[
633                     dbc.Label("Selected tests"),
634                     dbc.Checklist(
635                         class_name="overflow-auto",
636                         id="cl-selected",
637                         options=[],
638                         inline=False,
639                         style={"max-height": "20em"},
640                     )
641                 ],
642             ),
643             dbc.Row(
644                 id="row-btns-sel-tests",
645                 class_name="g-0 p-1",
646                 style=C.STYLE_DISABLED,
647                 children=[
648                     dbc.ButtonGroup(
649                         children=[
650                             dbc.Button(
651                                 id="btn-sel-remove",
652                                 children="Remove Selected",
653                                 class_name="w-100",
654                                 color="info",
655                                 disabled=False
656                             ),
657                             dbc.Button(
658                                 id="btn-sel-remove-all",
659                                 children="Remove All",
660                                 class_name="w-100",
661                                 color="info",
662                                 disabled=False
663                             ),
664                         ]
665                     )
666                 ]
667             ),
668         ]
669
670     class ControlPanel:
671         """A class representing the control panel.
672         """
673
674         def __init__(self, panel: dict) -> None:
675             """Initialisation of the control pannel by default values. If
676             particular values are provided (parameter "panel") they are set
677             afterwards.
678
679             :param panel: Custom values to be set to the control panel.
680             :param default: Default values to be set to the control panel.
681             :type panel: dict
682             :type defaults: dict
683             """
684
685             # Defines also the order of keys
686             self._defaults = {
687                 "dd-rls-value": str(),
688                 "dd-dut-options": list(),
689                 "dd-dut-disabled": True,
690                 "dd-dut-value": str(),
691                 "dd-dutver-options": list(),
692                 "dd-dutver-disabled": True,
693                 "dd-dutver-value": str(),
694                 "dd-phy-options": list(),
695                 "dd-phy-disabled": True,
696                 "dd-phy-value": str(),
697                 "dd-area-options": list(),
698                 "dd-area-disabled": True,
699                 "dd-area-value": str(),
700                 "dd-test-options": list(),
701                 "dd-test-disabled": True,
702                 "dd-test-value": str(),
703                 "cl-core-options": list(),
704                 "cl-core-value": list(),
705                 "cl-core-all-value": list(),
706                 "cl-core-all-options": C.CL_ALL_DISABLED,
707                 "cl-framesize-options": list(),
708                 "cl-framesize-value": list(),
709                 "cl-framesize-all-value": list(),
710                 "cl-framesize-all-options": C.CL_ALL_DISABLED,
711                 "cl-testtype-options": list(),
712                 "cl-testtype-value": list(),
713                 "cl-testtype-all-value": list(),
714                 "cl-testtype-all-options": C.CL_ALL_DISABLED,
715                 "btn-add-disabled": True,
716                 "cl-normalize-value": list(),
717                 "cl-selected-options": list()
718             }
719
720             self._panel = deepcopy(self._defaults)
721             if panel:
722                 for key in self._defaults:
723                     self._panel[key] = panel[key]
724
725         @property
726         def defaults(self) -> dict:
727             return self._defaults
728
729         @property
730         def panel(self) -> dict:
731             return self._panel
732
733         def set(self, kwargs: dict) -> None:
734             """Set the values of the Control panel.
735
736             :param kwargs: key - value pairs to be set.
737             :type kwargs: dict
738             :raises KeyError: If the key in kwargs is not present in the Control
739                 panel.
740             """
741             for key, val in kwargs.items():
742                 if key in self._panel:
743                     self._panel[key] = val
744                 else:
745                     raise KeyError(f"The key {key} is not defined.")
746
747         def get(self, key: str) -> any:
748             """Returns the value of a key from the Control panel.
749
750             :param key: The key which value should be returned.
751             :type key: str
752             :returns: The value of the key.
753             :rtype: any
754             :raises KeyError: If the key in kwargs is not present in the Control
755                 panel.
756             """
757             return self._panel[key]
758
759         def values(self) -> tuple:
760             """Returns the values from the Control panel as a list.
761
762             :returns: The values from the Control panel.
763             :rtype: list
764             """
765             return tuple(self._panel.values())
766
767     def callbacks(self, app):
768         """Callbacks for the whole application.
769
770         :param app: The application.
771         :type app: Flask
772         """
773
774         def _generate_plotting_area(figs: tuple, table: pd.DataFrame,
775                 url: str) -> tuple:
776             """Generate the plotting area with all its content.
777
778             :param figs: Figures to be placed in the plotting area.
779             :param table: A table to be placed in the plotting area bellow the
780                 figures.
781             :param utl: The URL to be placed in the plotting area bellow the
782                 tables.
783             :type figs: tuple of plotly.graph_objects.Figure
784             :type table: pandas.DataFrame
785             :type url: str
786             :returns: tuple of elements to be shown in the plotting area.
787             :rtype: tuple
788                 (dcc.Graph, dcc.Graph, dbc.Table, list(dbc.Col, dbc.Col))
789             """
790
791             (fig_tput, fig_lat) = figs
792
793             row_fig_tput = C.PLACEHOLDER
794             row_fig_lat = C.PLACEHOLDER
795             row_table = C.PLACEHOLDER
796             row_btn_dwnld = C.PLACEHOLDER
797
798             if fig_tput:
799                 row_fig_tput = [
800                     dcc.Graph(
801                         id={"type": "graph", "index": "tput"},
802                         figure=fig_tput
803                     )
804                 ]
805                 row_btn_dwnld = [
806                     dbc.Col(  # Download
807                         width=2,
808                         children=[
809                             dcc.Loading(children=[
810                                 dbc.Button(
811                                     id="btn-download-data",
812                                     children=show_tooltip(self._tooltips,
813                                         "help-download", "Download Data"),
814                                     class_name="me-1",
815                                     color="info"
816                                 ),
817                                 dcc.Download(id="download-data")
818                             ]),
819                         ]
820                     ),
821                     dbc.Col(  # Show URL
822                         width=10,
823                         children=[
824                             dbc.InputGroup(
825                                 class_name="me-1",
826                                 children=[
827                                     dbc.InputGroupText(
828                                         style=C.URL_STYLE,
829                                         children=show_tooltip(self._tooltips,
830                                             "help-url", "URL", "input-url")
831                                     ),
832                                     dbc.Input(
833                                         id="input-url",
834                                         readonly=True,
835                                         type="url",
836                                         style=C.URL_STYLE,
837                                         value=url
838                                     )
839                                 ]
840                             )
841                         ]
842                     )
843                 ]
844             if fig_lat:
845                 row_fig_lat = [
846                     dcc.Graph(
847                         id={"type": "graph", "index": "lat"},
848                         figure=fig_lat
849                     )
850                 ]
851             if not table.empty:
852                 row_table = [
853                     dbc.Table.from_dataframe(
854                         table,
855                         id={"type": "table", "index": "compare"},
856                         striped=True,
857                         bordered=True,
858                         hover=True
859                     )
860                 ]
861
862             return row_fig_tput, row_fig_lat, row_table, row_btn_dwnld
863
864         @app.callback(
865             Output("control-panel", "data"),  # Store
866             Output("selected-tests", "data"),  # Store
867             Output("row-graph-tput", "children"),
868             Output("row-graph-lat", "children"),
869             Output("row-table", "children"),
870             Output("row-btn-download", "children"),
871             Output("row-card-sel-tests", "style"),
872             Output("row-btns-sel-tests", "style"),
873             Output("dd-ctrl-rls", "value"),
874             Output("dd-ctrl-dut", "options"),
875             Output("dd-ctrl-dut", "disabled"),
876             Output("dd-ctrl-dut", "value"),
877             Output("dd-ctrl-dutver", "options"),
878             Output("dd-ctrl-dutver", "disabled"),
879             Output("dd-ctrl-dutver", "value"),
880             Output("dd-ctrl-phy", "options"),
881             Output("dd-ctrl-phy", "disabled"),
882             Output("dd-ctrl-phy", "value"),
883             Output("dd-ctrl-area", "options"),
884             Output("dd-ctrl-area", "disabled"),
885             Output("dd-ctrl-area", "value"),
886             Output("dd-ctrl-test", "options"),
887             Output("dd-ctrl-test", "disabled"),
888             Output("dd-ctrl-test", "value"),
889             Output("cl-ctrl-core", "options"),
890             Output("cl-ctrl-core", "value"),
891             Output("cl-ctrl-core-all", "value"),
892             Output("cl-ctrl-core-all", "options"),
893             Output("cl-ctrl-framesize", "options"),
894             Output("cl-ctrl-framesize", "value"),
895             Output("cl-ctrl-framesize-all", "value"),
896             Output("cl-ctrl-framesize-all", "options"),
897             Output("cl-ctrl-testtype", "options"),
898             Output("cl-ctrl-testtype", "value"),
899             Output("cl-ctrl-testtype-all", "value"),
900             Output("cl-ctrl-testtype-all", "options"),
901             Output("btn-ctrl-add", "disabled"),
902             Output("cl-ctrl-normalize", "value"),
903             Output("cl-selected", "options"),  # User selection
904             State("control-panel", "data"),  # Store
905             State("selected-tests", "data"),  # Store
906             State("cl-selected", "value"),  # User selection
907             Input("dd-ctrl-rls", "value"),
908             Input("dd-ctrl-dut", "value"),
909             Input("dd-ctrl-dutver", "value"),
910             Input("dd-ctrl-phy", "value"),
911             Input("dd-ctrl-area", "value"),
912             Input("dd-ctrl-test", "value"),
913             Input("cl-ctrl-core", "value"),
914             Input("cl-ctrl-core-all", "value"),
915             Input("cl-ctrl-framesize", "value"),
916             Input("cl-ctrl-framesize-all", "value"),
917             Input("cl-ctrl-testtype", "value"),
918             Input("cl-ctrl-testtype-all", "value"),
919             Input("cl-ctrl-normalize", "value"),
920             Input("btn-ctrl-add", "n_clicks"),
921             Input("btn-sel-remove", "n_clicks"),
922             Input("btn-sel-remove-all", "n_clicks"),
923             Input("url", "href")
924         )
925         def _update_ctrl_panel(cp_data: dict, store_sel: list, list_sel: list,
926             dd_rls: str, dd_dut: str, dd_dutver: str, dd_phy: str, dd_area: str,
927             dd_test: str, cl_core: list, cl_core_all: list, cl_framesize: list,
928             cl_framesize_all: list, cl_testtype: list, cl_testtype_all: list,
929             cl_normalize: list, btn_add: int, btn_remove: int,
930             btn_remove_all: int, href: str) -> tuple:
931             """Update the application when the event is detected.
932
933             :param cp_data: Current status of the control panel stored in
934                 browser.
935             :param store_sel: List of tests selected by user stored in the
936                 browser.
937             :param list_sel: List of tests selected by the user shown in the
938                 checklist.
939             :param dd_rls: Input - Releases.
940             :param dd_dut: Input - DUTs.
941             :param dd_dutver: Input - Version of DUT.
942             :param dd_phy: Input - topo- arch-nic-driver.
943             :param dd_area: Input - Tested area.
944             :param dd_test: Input - Test.
945             :param cl_core: Input - Number of cores.
946             :param cl_core_all: Input - All numbers of cores.
947             :param cl_framesize: Input - Frame sizes.
948             :param cl_framesize_all: Input - All frame sizes.
949             :param cl_testtype: Input - Test type (NDR, PDR, MRR).
950             :param cl_testtype_all: Input - All test types.
951             :param cl_normalize: Input - Normalize the results.
952             :param btn_add: Input - Button "Add Selected" tests.
953             :param btn_remove: Input - Button "Remove selected" tests.
954             :param btn_remove_all: Input - Button "Remove All" tests.
955             :param href: Input - The URL provided by the browser.
956             :type cp_data: dict
957             :type store_sel: list
958             :type list_sel: list
959             :type dd_rls: str
960             :type dd_dut: str
961             :type dd_dutver: str
962             :type dd_phy: str
963             :type dd_area: str
964             :type dd_test: str
965             :type cl_core: list
966             :type cl_core_all: list
967             :type cl_framesize: list
968             :type cl_framesize_all: list
969             :type cl_testtype: list
970             :type cl_testtype_all: list
971             :type cl_normalize: list
972             :type btn_add: int
973             :type btn_remove: int
974             :type btn_remove_all: int
975             :type href: str
976             :returns: New values for web page elements.
977             :rtype: tuple
978             """
979
980             ctrl_panel = self.ControlPanel(cp_data)
981             norm = cl_normalize
982
983             # Parse the url:
984             parsed_url = url_decode(href)
985             if parsed_url:
986                 url_params = parsed_url["params"]
987             else:
988                 url_params = None
989
990             row_fig_tput = no_update
991             row_fig_lat = no_update
992             row_table = no_update
993             row_btn_dwnld = no_update
994             row_card_sel_tests = no_update
995             row_btns_sel_tests = no_update
996
997             trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
998
999             if trigger_id == "dd-ctrl-rls":
1000                 try:
1001                     options = \
1002                         generate_options(sorted(self.spec_tbs[dd_rls].keys()))
1003                     disabled = False
1004                 except KeyError:
1005                     options = list()
1006                     disabled = True
1007                 ctrl_panel.set({
1008                     "dd-rls-value": dd_rls,
1009                     "dd-dut-value": str(),
1010                     "dd-dut-options": options,
1011                     "dd-dut-disabled": disabled,
1012                     "dd-dutver-value": str(),
1013                     "dd-dutver-options": list(),
1014                     "dd-dutver-disabled": True,
1015                     "dd-phy-value": str(),
1016                     "dd-phy-options": list(),
1017                     "dd-phy-disabled": True,
1018                     "dd-area-value": str(),
1019                     "dd-area-options": list(),
1020                     "dd-area-disabled": True,
1021                     "dd-test-value": str(),
1022                     "dd-test-options": list(),
1023                     "dd-test-disabled": True,
1024                     "cl-core-options": list(),
1025                     "cl-core-value": list(),
1026                     "cl-core-all-value": list(),
1027                     "cl-core-all-options": C.CL_ALL_DISABLED,
1028                     "cl-framesize-options": list(),
1029                     "cl-framesize-value": list(),
1030                     "cl-framesize-all-value": list(),
1031                     "cl-framesize-all-options": C.CL_ALL_DISABLED,
1032                     "cl-testtype-options": list(),
1033                     "cl-testtype-value": list(),
1034                     "cl-testtype-all-value": list(),
1035                     "cl-testtype-all-options": C.CL_ALL_DISABLED
1036                 })
1037             elif trigger_id == "dd-ctrl-dut":
1038                 try:
1039                     rls = ctrl_panel.get("dd-rls-value")
1040                     dut = self.spec_tbs[rls][dd_dut]
1041                     options = generate_options(sorted(dut.keys()))
1042                     disabled = False
1043                 except KeyError:
1044                     options = list()
1045                     disabled = True
1046                 ctrl_panel.set({
1047                     "dd-dut-value": dd_dut,
1048                     "dd-dutver-value": str(),
1049                     "dd-dutver-options": options,
1050                     "dd-dutver-disabled": disabled,
1051                     "dd-phy-value": str(),
1052                     "dd-phy-options": list(),
1053                     "dd-phy-disabled": True,
1054                     "dd-area-value": str(),
1055                     "dd-area-options": list(),
1056                     "dd-area-disabled": True,
1057                     "dd-test-value": str(),
1058                     "dd-test-options": list(),
1059                     "dd-test-disabled": True,
1060                     "cl-core-options": list(),
1061                     "cl-core-value": list(),
1062                     "cl-core-all-value": list(),
1063                     "cl-core-all-options": C.CL_ALL_DISABLED,
1064                     "cl-framesize-options": list(),
1065                     "cl-framesize-value": list(),
1066                     "cl-framesize-all-value": list(),
1067                     "cl-framesize-all-options": C.CL_ALL_DISABLED,
1068                     "cl-testtype-options": list(),
1069                     "cl-testtype-value": list(),
1070                     "cl-testtype-all-value": list(),
1071                     "cl-testtype-all-options": C.CL_ALL_DISABLED
1072                 })
1073             elif trigger_id == "dd-ctrl-dutver":
1074                 try:
1075                     rls = ctrl_panel.get("dd-rls-value")
1076                     dut = ctrl_panel.get("dd-dut-value")
1077                     dutver = self.spec_tbs[rls][dut][dd_dutver]
1078                     options = generate_options(sorted(dutver.keys()))
1079                     disabled = False
1080                 except KeyError:
1081                     options = list()
1082                     disabled = True
1083                 ctrl_panel.set({
1084                     "dd-dutver-value": dd_dutver,
1085                     "dd-phy-value": str(),
1086                     "dd-phy-options": options,
1087                     "dd-phy-disabled": disabled,
1088                     "dd-area-value": str(),
1089                     "dd-area-options": list(),
1090                     "dd-area-disabled": True,
1091                     "dd-test-value": str(),
1092                     "dd-test-options": list(),
1093                     "dd-test-disabled": True,
1094                     "cl-core-options": list(),
1095                     "cl-core-value": list(),
1096                     "cl-core-all-value": list(),
1097                     "cl-core-all-options": C.CL_ALL_DISABLED,
1098                     "cl-framesize-options": list(),
1099                     "cl-framesize-value": list(),
1100                     "cl-framesize-all-value": list(),
1101                     "cl-framesize-all-options": C.CL_ALL_DISABLED,
1102                     "cl-testtype-options": list(),
1103                     "cl-testtype-value": list(),
1104                     "cl-testtype-all-value": list(),
1105                     "cl-testtype-all-options": C.CL_ALL_DISABLED
1106                 })
1107             elif trigger_id == "dd-ctrl-phy":
1108                 try:
1109                     rls = ctrl_panel.get("dd-rls-value")
1110                     dut = ctrl_panel.get("dd-dut-value")
1111                     dutver = ctrl_panel.get("dd-dutver-value")
1112                     phy = self.spec_tbs[rls][dut][dutver][dd_phy]
1113                     options = [{"label": label(v), "value": v} \
1114                         for v in sorted(phy.keys())]
1115                     disabled = False
1116                 except KeyError:
1117                     options = list()
1118                     disabled = True
1119                 ctrl_panel.set({
1120                     "dd-phy-value": dd_phy,
1121                     "dd-area-value": str(),
1122                     "dd-area-options": options,
1123                     "dd-area-disabled": disabled,
1124                     "dd-test-value": str(),
1125                     "dd-test-options": list(),
1126                     "dd-test-disabled": True,
1127                     "cl-core-options": list(),
1128                     "cl-core-value": list(),
1129                     "cl-core-all-value": list(),
1130                     "cl-core-all-options": C.CL_ALL_DISABLED,
1131                     "cl-framesize-options": list(),
1132                     "cl-framesize-value": list(),
1133                     "cl-framesize-all-value": list(),
1134                     "cl-framesize-all-options": C.CL_ALL_DISABLED,
1135                     "cl-testtype-options": list(),
1136                     "cl-testtype-value": list(),
1137                     "cl-testtype-all-value": list(),
1138                     "cl-testtype-all-options": C.CL_ALL_DISABLED
1139                 })
1140             elif trigger_id == "dd-ctrl-area":
1141                 try:
1142                     rls = ctrl_panel.get("dd-rls-value")
1143                     dut = ctrl_panel.get("dd-dut-value")
1144                     dutver = ctrl_panel.get("dd-dutver-value")
1145                     phy = ctrl_panel.get("dd-phy-value")
1146                     area = self.spec_tbs[rls][dut][dutver][phy][dd_area]
1147                     options = generate_options(sorted(area.keys()))
1148                     disabled = False
1149                 except KeyError:
1150                     options = list()
1151                     disabled = True
1152                 ctrl_panel.set({
1153                     "dd-area-value": dd_area,
1154                     "dd-test-value": str(),
1155                     "dd-test-options": options,
1156                     "dd-test-disabled": disabled,
1157                     "cl-core-options": list(),
1158                     "cl-core-value": list(),
1159                     "cl-core-all-value": list(),
1160                     "cl-core-all-options": C.CL_ALL_DISABLED,
1161                     "cl-framesize-options": list(),
1162                     "cl-framesize-value": list(),
1163                     "cl-framesize-all-value": list(),
1164                     "cl-framesize-all-options": C.CL_ALL_DISABLED,
1165                     "cl-testtype-options": list(),
1166                     "cl-testtype-value": list(),
1167                     "cl-testtype-all-value": list(),
1168                     "cl-testtype-all-options": C.CL_ALL_DISABLED
1169                 })
1170             elif trigger_id == "dd-ctrl-test":
1171                 rls = ctrl_panel.get("dd-rls-value")
1172                 dut = ctrl_panel.get("dd-dut-value")
1173                 dutver = ctrl_panel.get("dd-dutver-value")
1174                 phy = ctrl_panel.get("dd-phy-value")
1175                 area = ctrl_panel.get("dd-area-value")
1176                 if all((rls, dut, dutver, phy, area, dd_test, )):
1177                     test = self.spec_tbs[rls][dut][dutver][phy][area][dd_test]
1178                     ctrl_panel.set({
1179                         "dd-test-value": dd_test,
1180                         "cl-core-options": \
1181                             generate_options(sorted(test["core"])),
1182                         "cl-core-value": list(),
1183                         "cl-core-all-value": list(),
1184                         "cl-core-all-options": C.CL_ALL_ENABLED,
1185                         "cl-framesize-options": \
1186                             generate_options(sorted(test["frame-size"])),
1187                         "cl-framesize-value": list(),
1188                         "cl-framesize-all-value": list(),
1189                         "cl-framesize-all-options": C.CL_ALL_ENABLED,
1190                         "cl-testtype-options": \
1191                             generate_options(sorted(test["test-type"])),
1192                         "cl-testtype-value": list(),
1193                         "cl-testtype-all-value": list(),
1194                         "cl-testtype-all-options": C.CL_ALL_ENABLED,
1195                     })
1196             elif trigger_id == "cl-ctrl-core":
1197                 val_sel, val_all = sync_checklists(
1198                     options=ctrl_panel.get("cl-core-options"),
1199                     sel=cl_core,
1200                     all=list(),
1201                     id=""
1202                 )
1203                 ctrl_panel.set({
1204                     "cl-core-value": val_sel,
1205                     "cl-core-all-value": val_all,
1206                 })
1207             elif trigger_id == "cl-ctrl-core-all":
1208                 val_sel, val_all = sync_checklists(
1209                     options = ctrl_panel.get("cl-core-options"),
1210                     sel=list(),
1211                     all=cl_core_all,
1212                     id="all"
1213                 )
1214                 ctrl_panel.set({
1215                     "cl-core-value": val_sel,
1216                     "cl-core-all-value": val_all,
1217                 })
1218             elif trigger_id == "cl-ctrl-framesize":
1219                 val_sel, val_all = sync_checklists(
1220                     options = ctrl_panel.get("cl-framesize-options"),
1221                     sel=cl_framesize,
1222                     all=list(),
1223                     id=""
1224                 )
1225                 ctrl_panel.set({
1226                     "cl-framesize-value": val_sel,
1227                     "cl-framesize-all-value": val_all,
1228                 })
1229             elif trigger_id == "cl-ctrl-framesize-all":
1230                 val_sel, val_all = sync_checklists(
1231                     options = ctrl_panel.get("cl-framesize-options"),
1232                     sel=list(),
1233                     all=cl_framesize_all,
1234                     id="all"
1235                 )
1236                 ctrl_panel.set({
1237                     "cl-framesize-value": val_sel,
1238                     "cl-framesize-all-value": val_all,
1239                 })
1240             elif trigger_id == "cl-ctrl-testtype":
1241                 val_sel, val_all = sync_checklists(
1242                     options = ctrl_panel.get("cl-testtype-options"),
1243                     sel=cl_testtype,
1244                     all=list(),
1245                     id=""
1246                 )
1247                 ctrl_panel.set({
1248                     "cl-testtype-value": val_sel,
1249                     "cl-testtype-all-value": val_all,
1250                 })
1251             elif trigger_id == "cl-ctrl-testtype-all":
1252                 val_sel, val_all = sync_checklists(
1253                     options = ctrl_panel.get("cl-testtype-options"),
1254                     sel=list(),
1255                     all=cl_testtype_all,
1256                     id="all"
1257                 )
1258                 ctrl_panel.set({
1259                     "cl-testtype-value": val_sel,
1260                     "cl-testtype-all-value": val_all,
1261                 })
1262             elif trigger_id == "btn-ctrl-add":
1263                 _ = btn_add
1264                 rls = ctrl_panel.get("dd-rls-value")
1265                 dut = ctrl_panel.get("dd-dut-value")
1266                 dutver = ctrl_panel.get("dd-dutver-value")
1267                 phy = ctrl_panel.get("dd-phy-value")
1268                 area = ctrl_panel.get("dd-area-value")
1269                 test = ctrl_panel.get("dd-test-value")
1270                 cores = ctrl_panel.get("cl-core-value")
1271                 framesizes = ctrl_panel.get("cl-framesize-value")
1272                 testtypes = ctrl_panel.get("cl-testtype-value")
1273                 # Add selected test to the list of tests in store:
1274                 if all((rls, dut, dutver, phy, area, test, cores, framesizes,
1275                         testtypes)):
1276                     if store_sel is None:
1277                         store_sel = list()
1278                     for core in cores:
1279                         for framesize in framesizes:
1280                             for ttype in testtypes:
1281                                 if dut == "trex":
1282                                     core = str()
1283                                 tid = "-".join((rls, dut, dutver,
1284                                     phy.replace('af_xdp', 'af-xdp'), area,
1285                                     framesize.lower(), core.lower(), test,
1286                                     ttype.lower()))
1287                                 if tid not in [itm["id"] for itm in store_sel]:
1288                                     store_sel.append({
1289                                         "id": tid,
1290                                         "rls": rls,
1291                                         "dut": dut,
1292                                         "dutver": dutver,
1293                                         "phy": phy,
1294                                         "area": area,
1295                                         "test": test,
1296                                         "framesize": framesize.lower(),
1297                                         "core": core.lower(),
1298                                         "testtype": ttype.lower()
1299                                     })
1300                     store_sel = sorted(store_sel, key=lambda d: d["id"])
1301                     row_card_sel_tests = C.STYLE_ENABLED
1302                     row_btns_sel_tests = C.STYLE_ENABLED
1303                     if C.CLEAR_ALL_INPUTS:
1304                         ctrl_panel.set(ctrl_panel.defaults)
1305                     ctrl_panel.set({
1306                         "cl-selected-options": list_tests(store_sel)
1307                     })
1308             elif trigger_id == "btn-sel-remove-all":
1309                 _ = btn_remove_all
1310                 row_fig_tput = C.PLACEHOLDER
1311                 row_fig_lat = C.PLACEHOLDER
1312                 row_table = C.PLACEHOLDER
1313                 row_btn_dwnld = C.PLACEHOLDER
1314                 row_card_sel_tests = C.STYLE_DISABLED
1315                 row_btns_sel_tests = C.STYLE_DISABLED
1316                 store_sel = list()
1317                 ctrl_panel.set({"cl-selected-options": list()})
1318             elif trigger_id == "btn-sel-remove":
1319                 _ = btn_remove
1320                 if list_sel:
1321                     new_store_sel = list()
1322                     for item in store_sel:
1323                         if item["id"] not in list_sel:
1324                             new_store_sel.append(item)
1325                     store_sel = new_store_sel
1326             elif trigger_id == "url":
1327                 if url_params:
1328                     try:
1329                         store_sel = literal_eval(url_params["store_sel"][0])
1330                         norm = literal_eval(url_params["norm"][0])
1331                     except (KeyError, IndexError):
1332                         pass
1333                     if store_sel:
1334                         row_card_sel_tests = C.STYLE_ENABLED
1335                         row_btns_sel_tests = C.STYLE_ENABLED
1336                         last_test = store_sel[-1]
1337                         test = self.spec_tbs[last_test["rls"]]\
1338                             [last_test["dut"]][last_test["dutver"]]\
1339                                 [last_test["phy"]][last_test["area"]]\
1340                                     [last_test["test"]]
1341                         ctrl_panel.set({
1342                             "dd-rls-value": last_test["rls"],
1343                             "dd-dut-value": last_test["dut"],
1344                             "dd-dut-options": generate_options(sorted(
1345                                 self.spec_tbs[last_test["rls"]].keys())),
1346                             "dd-dut-disabled": False,
1347                             "dd-dutver-value": last_test["dutver"],
1348                             "dd-dutver-options": generate_options(sorted(
1349                                 self.spec_tbs[last_test["rls"]]\
1350                                     [last_test["dut"]].keys())),
1351                             "dd-dutver-disabled": False,
1352                             "dd-phy-value": last_test["phy"],
1353                             "dd-phy-options": generate_options(sorted(
1354                                 self.spec_tbs[last_test["rls"]]\
1355                                     [last_test["dut"]]\
1356                                         [last_test["dutver"]].keys())),
1357                             "dd-phy-disabled": False,
1358                             "dd-area-value": last_test["area"],
1359                             "dd-area-options": [
1360                                 {"label": label(v), "value": v} for v in \
1361                                     sorted(self.spec_tbs[last_test["rls"]]\
1362                                         [last_test["dut"]][last_test["dutver"]]\
1363                                             [last_test["phy"]].keys())
1364                             ],
1365                             "dd-area-disabled": False,
1366                             "dd-test-value": last_test["test"],
1367                             "dd-test-options": generate_options(sorted(
1368                                 self.spec_tbs[last_test["rls"]]\
1369                                     [last_test["dut"]][last_test["dutver"]]\
1370                                         [last_test["phy"]]\
1371                                             [last_test["area"]].keys())),
1372                             "dd-test-disabled": False,
1373                             "cl-core-options": generate_options(sorted(
1374                                 test["core"])),
1375                             "cl-core-value": [last_test["core"].upper(), ],
1376                             "cl-core-all-value": list(),
1377                             "cl-core-all-options": C.CL_ALL_ENABLED,
1378                             "cl-framesize-options": generate_options(
1379                                 sorted(test["frame-size"])),
1380                             "cl-framesize-value": \
1381                                 [last_test["framesize"].upper(), ],
1382                             "cl-framesize-all-value": list(),
1383                             "cl-framesize-all-options": C.CL_ALL_ENABLED,
1384                             "cl-testtype-options": generate_options(sorted(
1385                                 test["test-type"])),
1386                             "cl-testtype-value": \
1387                                 [last_test["testtype"].upper(), ],
1388                             "cl-testtype-all-value": list(),
1389                             "cl-testtype-all-options": C.CL_ALL_ENABLED
1390                         })
1391
1392             if trigger_id in ("btn-ctrl-add", "url", "btn-sel-remove",
1393                     "cl-ctrl-normalize"):
1394                 if store_sel:
1395                     row_fig_tput, row_fig_lat, row_table, row_btn_dwnld = \
1396                         _generate_plotting_area(
1397                             graph_iterative(
1398                                 self.data, store_sel, self.layout, bool(norm)
1399                             ),
1400                             table_comparison(
1401                                 self.data, store_sel, bool(norm)
1402                             ),
1403                             gen_new_url(
1404                                 parsed_url,
1405                                 {"store_sel": store_sel, "norm": norm}
1406                             )
1407                         )
1408                     ctrl_panel.set({
1409                         "cl-selected-options": list_tests(store_sel)
1410                     })
1411                 else:
1412                     row_fig_tput = C.PLACEHOLDER
1413                     row_fig_lat = C.PLACEHOLDER
1414                     row_table = C.PLACEHOLDER
1415                     row_btn_dwnld = C.PLACEHOLDER
1416                     row_card_sel_tests = C.STYLE_DISABLED
1417                     row_btns_sel_tests = C.STYLE_DISABLED
1418                     store_sel = list()
1419                     ctrl_panel.set({"cl-selected-options": list()})
1420
1421             if ctrl_panel.get("cl-core-value") and \
1422                     ctrl_panel.get("cl-framesize-value") and \
1423                     ctrl_panel.get("cl-testtype-value"):
1424                 disabled = False
1425             else:
1426                 disabled = True
1427             ctrl_panel.set({
1428                 "btn-add-disabled": disabled,
1429                 "cl-normalize-value": norm
1430             })
1431
1432             ret_val = [
1433                 ctrl_panel.panel, store_sel,
1434                 row_fig_tput, row_fig_lat, row_table, row_btn_dwnld,
1435                 row_card_sel_tests, row_btns_sel_tests
1436             ]
1437             ret_val.extend(ctrl_panel.values())
1438             return ret_val
1439
1440         @app.callback(
1441             Output("download-data", "data"),
1442             State("selected-tests", "data"),
1443             Input("btn-download-data", "n_clicks"),
1444             prevent_initial_call=True
1445         )
1446         def _download_data(store_sel, n_clicks):
1447             """Download the data
1448
1449             :param store_sel: List of tests selected by user stored in the
1450                 browser.
1451             :param n_clicks: Number of clicks on the button "Download".
1452             :type store_sel: list
1453             :type n_clicks: int
1454             :returns: dict of data frame content (base64 encoded) and meta data
1455                 used by the Download component.
1456             :rtype: dict
1457             """
1458
1459             if not n_clicks:
1460                 raise PreventUpdate
1461
1462             if not store_sel:
1463                 raise PreventUpdate
1464
1465             df = pd.DataFrame()
1466             for itm in store_sel:
1467                 sel_data = select_iterative_data(self.data, itm)
1468                 if sel_data is None:
1469                     continue
1470                 df = pd.concat([df, sel_data], ignore_index=True)
1471
1472             return dcc.send_data_frame(df.to_csv, C.REPORT_DOWNLOAD_FILE_NAME)