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