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