690a9b72e0a2d50747c2947bb5296a4112e9cc0c
[csit.git] / resources / tools / dash / app / pal / trending / 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 pandas as pd
18 import dash_bootstrap_components as dbc
19
20 from dash import dcc
21 from dash import html
22 from dash import callback_context, no_update
23 from dash import Input, Output, State
24 from dash.exceptions import PreventUpdate
25 from yaml import load, FullLoader, YAMLError
26 from datetime import datetime, timedelta
27 from copy import deepcopy
28
29 from ..data.data import Data
30 from .graphs import graph_trending, graph_hdrh_latency, \
31     select_trending_data
32
33
34 class Layout:
35     """
36     """
37
38     STYLE_DISABLED = {"display": "none"}
39     STYLE_ENABLED = {"display": "inherit"}
40
41     CL_ALL_DISABLED = [{
42         "label": "All",
43         "value": "all",
44         "disabled": True
45     }]
46     CL_ALL_ENABLED = [{
47         "label": "All",
48         "value": "all",
49         "disabled": False
50     }]
51
52     PLACEHOLDER = html.Nobr("")
53
54     def __init__(self, app, html_layout_file, spec_file, graph_layout_file,
55         data_spec_file):
56         """
57         """
58
59         # Inputs
60         self._app = app
61         self._html_layout_file = html_layout_file
62         self._spec_file = spec_file
63         self._graph_layout_file = graph_layout_file
64         self._data_spec_file = data_spec_file
65
66         # Read the data:
67         data_mrr = Data(
68             data_spec_file=self._data_spec_file,
69             debug=True
70         ).read_trending_mrr(days=5)
71
72         data_ndrpdr = Data(
73             data_spec_file=self._data_spec_file,
74             debug=True
75         ).read_trending_ndrpdr(days=14)
76
77         self._data = pd.concat([data_mrr, data_ndrpdr], ignore_index=True)
78
79         # Read from files:
80         self._html_layout = ""
81         self._spec_tbs = None
82         self._graph_layout = None
83
84         try:
85             with open(self._html_layout_file, "r") as file_read:
86                 self._html_layout = file_read.read()
87         except IOError as err:
88             raise RuntimeError(
89                 f"Not possible to open the file {self._html_layout_file}\n{err}"
90             )
91
92         try:
93             with open(self._spec_file, "r") as file_read:
94                 self._spec_tbs = load(file_read, Loader=FullLoader)
95         except IOError as err:
96             raise RuntimeError(
97                 f"Not possible to open the file {self._spec_file,}\n{err}"
98             )
99         except YAMLError as err:
100             raise RuntimeError(
101                 f"An error occurred while parsing the specification file "
102                 f"{self._spec_file,}\n"
103                 f"{err}"
104             )
105
106         try:
107             with open(self._graph_layout_file, "r") as file_read:
108                 self._graph_layout = load(file_read, Loader=FullLoader)
109         except IOError as err:
110             raise RuntimeError(
111                 f"Not possible to open the file {self._graph_layout_file}\n"
112                 f"{err}"
113             )
114         except YAMLError as err:
115             raise RuntimeError(
116                 f"An error occurred while parsing the specification file "
117                 f"{self._graph_layout_file}\n"
118                 f"{err}"
119             )
120
121         # Callbacks:
122         if self._app is not None and hasattr(self, 'callbacks'):
123             self.callbacks(self._app)
124
125     @property
126     def html_layout(self):
127         return self._html_layout
128
129     @property
130     def spec_tbs(self):
131         return self._spec_tbs
132
133     @property
134     def data(self):
135         return self._data
136
137     @property
138     def layout(self):
139         return self._graph_layout
140
141     def add_content(self):
142         """
143         """
144         if self.html_layout and self.spec_tbs:
145             return html.Div(
146                 id="div-main",
147                 children=[
148                     dbc.Row(
149                         id="row-navbar",
150                         class_name="g-0",
151                         children=[
152                             self._add_navbar(),
153                         ]
154                     ),
155                     dcc.Loading(
156                         dbc.Offcanvas(
157                             class_name="w-50",
158                             id="offcanvas-metadata",
159                             title="Throughput And Latency",
160                             placement="end",
161                             is_open=False,
162                             children=[
163                                 dbc.Row(id="metadata-tput-lat"),
164                                 dbc.Row(id="metadata-hdrh-graph"),
165                             ]
166                         )
167                     ),
168                     dbc.Row(
169                         id="row-main",
170                         class_name="g-0",
171                         children=[
172                             dcc.Store(
173                                 id="selected-tests"
174                             ),
175                             dcc.Store(
176                                 id="control-panel"
177                             ),
178                             self._add_ctrl_col(),
179                             self._add_plotting_col(),
180                         ]
181                     )
182                 ]
183             )
184         else:
185             return html.Div(
186                 id="div-main-error",
187                 children=[
188                     dbc.Alert(
189                         [
190                             "An Error Occured",
191                         ],
192                         color="danger",
193                     ),
194                 ]
195             )
196
197     def _add_navbar(self):
198         """Add nav element with navigation panel. It is placed on the top.
199         """
200         return dbc.NavbarSimple(
201             id="navbarsimple-main",
202             children=[
203                 dbc.NavItem(
204                     dbc.NavLink(
205                         "Continuous Performance Trending",
206                         disabled=True,
207                         external_link=True,
208                         href="#"
209                     )
210                 )
211             ],
212             brand="Dashboard",
213             brand_href="/",
214             brand_external_link=True,
215             class_name="p-2",
216             fluid=True,
217         )
218
219     def _add_ctrl_col(self) -> dbc.Col:
220         """Add column with controls. It is placed on the left side.
221         """
222         return dbc.Col(
223             id="col-controls",
224             children=[
225                 self._add_ctrl_panel(),
226             ],
227         )
228
229     def _add_plotting_col(self) -> dbc.Col:
230         """Add column with plots and tables. It is placed on the right side.
231         """
232         return dbc.Col(
233             id="col-plotting-area",
234             children=[
235                 dbc.Row(  # Throughput
236                     id="row-graph-tput",
237                     class_name="g-0 p-2",
238                     children=[
239                         self.PLACEHOLDER
240                     ]
241                 ),
242                 dbc.Row(  # Latency
243                     id="row-graph-lat",
244                     class_name="g-0 p-2",
245                     children=[
246                         self.PLACEHOLDER
247                     ]
248                 ),
249                 dbc.Row(  # Download
250                     id="row-btn-download",
251                     class_name="g-0 p-2",
252                     children=[
253                         self.PLACEHOLDER
254                     ]
255                 )
256             ],
257             width=9,
258         )
259
260     def _add_ctrl_panel(self) -> dbc.Row:
261         """
262         """
263         return dbc.Row(
264             id="row-ctrl-panel",
265             class_name="g-0 p-2",
266             children=[
267                 dbc.Row(
268                     class_name="gy-1",
269                     children=[
270                         dbc.Label(
271                             "Physical Test Bed Topology, NIC and Driver",
272                             class_name="p-0"
273                         ),
274                         dbc.Select(
275                             id="dd-ctrl-phy",
276                             placeholder="Select a Physical Test Bed Topology...",
277                             options=[
278                                 {"label": k, "value": k} for k in self.spec_tbs.keys()
279                             ],
280                             size="sm",
281                         ),
282                     ]
283                 ),
284                 dbc.Row(
285                     class_name="gy-1",
286                     children=[
287                         dbc.Label(
288                             "Area",
289                             class_name="p-0"
290                         ),
291                         dbc.Select(
292                             id="dd-ctrl-area",
293                             placeholder="Select an Area...",
294                             disabled=True,
295                             size="sm",
296                         ),
297                     ]
298                 ),
299                 dbc.Row(
300                     class_name="gy-1",
301                     children=[
302                         dbc.Label(
303                             "Test",
304                             class_name="p-0"
305                         ),
306                         dbc.Select(
307                             id="dd-ctrl-test",
308                             placeholder="Select a Test...",
309                             disabled=True,
310                             size="sm",
311                         ),
312                     ]
313                 ),
314                 dbc.Row(
315                     id="row-ctrl-core",
316                     class_name="gy-1",
317                     children=[
318                         dbc.Label(
319                             "Number of Cores",
320                             class_name="p-0"
321                         ),
322                         dbc.Col(
323                             children=[
324                                 dbc.Checklist(
325                                     id="cl-ctrl-core-all",
326                                     options=self.CL_ALL_DISABLED,
327                                     inline=False,
328                                     switch=False
329                                 )
330                             ],
331                             width=3
332                         ),
333                         dbc.Col(
334                             children=[
335                                 dbc.Checklist(
336                                     id="cl-ctrl-core",
337                                     inline=True,
338                                     switch=False
339                                 )
340                             ]
341                         )
342                     ]
343                 ),
344                 dbc.Row(
345                     id="row-ctrl-framesize",
346                     class_name="gy-1",
347                     children=[
348                         dbc.Label(
349                             "Frame Size",
350                             class_name="p-0"
351                         ),
352                         dbc.Col(
353                             children=[
354                                 dbc.Checklist(
355                                     id="cl-ctrl-framesize-all",
356                                     options=self.CL_ALL_DISABLED,
357                                     inline=True,
358                                     switch=False
359                                 ),
360                             ],
361                             width=3
362                         ),
363                         dbc.Col(
364                             children=[
365                                 dbc.Checklist(
366                                     id="cl-ctrl-framesize",
367                                     inline=True,
368                                     switch=False
369                                 )
370                             ]
371                         )
372                     ]
373                 ),
374                 dbc.Row(
375                     id="row-ctrl-testtype",
376                     class_name="gy-1",
377                     children=[
378                         dbc.Label(
379                             "Test Type",
380                             class_name="p-0"
381                         ),
382                         dbc.Col(
383                             children=[
384                                 dbc.Checklist(
385                                     id="cl-ctrl-testtype-all",
386                                     options=self.CL_ALL_DISABLED,
387                                     inline=True,
388                                     switch=False
389                                 ),
390                             ],
391                             width=3
392                         ),
393                         dbc.Col(
394                             children=[
395                                 dbc.Checklist(
396                                     id="cl-ctrl-testtype",
397                                     inline=True,
398                                     switch=False
399                                 )
400                             ]
401                         )
402                     ]
403                 ),
404                 dbc.Row(
405                     class_name="gy-1",
406                     children=[
407                         dbc.ButtonGroup(
408                             [
409                                 dbc.Button(
410                                     id="btn-ctrl-add",
411                                     children="Add Selected",
412                                     color="secondary",
413                                 )
414                             ],
415                             size="md",
416                         )
417                     ]
418                 ),
419                 dbc.Row(
420                     class_name="gy-1",
421                     children=[
422                         dcc.DatePickerRange(
423                             id="dpr-period",
424                             className="d-flex justify-content-center",
425                             min_date_allowed=\
426                                 datetime.utcnow()-timedelta(days=180),
427                             max_date_allowed=datetime.utcnow(),
428                             initial_visible_month=datetime.utcnow(),
429                             start_date=datetime.utcnow() - timedelta(days=180),
430                             end_date=datetime.utcnow(),
431                             display_format="D MMMM YY"
432                         )
433                     ]
434                 ),
435                 dbc.Row(
436                     id="row-card-sel-tests",
437                     class_name="gy-1",
438                     style=self.STYLE_DISABLED,
439                     children=[
440                         dbc.Label(
441                             "Selected tests",
442                             class_name="p-0"
443                         ),
444                         dbc.Checklist(
445                             class_name="overflow-auto",
446                             id="cl-selected",
447                             options=[],
448                             inline=False,
449                             style={"max-height": "12em"},
450                         )
451                     ],
452                 ),
453                 dbc.Row(
454                     id="row-btns-sel-tests",
455                     style=self.STYLE_DISABLED,
456                     children=[
457                         dbc.ButtonGroup(
458                             children=[
459                                 dbc.Button(
460                                     id="btn-sel-remove-all",
461                                     children="Remove All",
462                                     class_name="w-100",
463                                     color="secondary",
464                                     disabled=False
465                                 ),
466                                 dbc.Button(
467                                     id="btn-sel-remove",
468                                     children="Remove Selected",
469                                     class_name="w-100",
470                                     color="secondary",
471                                     disabled=False
472                                 ),
473                                 dbc.Button(
474                                     id="btn-sel-display",
475                                     children="Display",
476                                     class_name="w-100",
477                                     color="secondary",
478                                     disabled=False
479                                 )
480                             ],
481                             size="md",
482                         )
483                     ]
484                 ),
485             ]
486         )
487
488     class ControlPanel:
489         def __init__(self, panel: dict) -> None:
490
491             CL_ALL_DISABLED = [{
492                 "label": "All",
493                 "value": "all",
494                 "disabled": True
495             }]
496
497             # Defines also the order of keys
498             self._defaults = {
499                 "dd-ctrl-phy-value": str(),
500                 "dd-ctrl-area-options": list(),
501                 "dd-ctrl-area-disabled": True,
502                 "dd-ctrl-area-value": str(),
503                 "dd-ctrl-test-options": list(),
504                 "dd-ctrl-test-disabled": True,
505                 "dd-ctrl-test-value": str(),
506                 "cl-ctrl-core-options": list(),
507                 "cl-ctrl-core-value": list(),
508                 "cl-ctrl-core-all-value": list(),
509                 "cl-ctrl-core-all-options": CL_ALL_DISABLED,
510                 "cl-ctrl-framesize-options": list(),
511                 "cl-ctrl-framesize-value": list(),
512                 "cl-ctrl-framesize-all-value": list(),
513                 "cl-ctrl-framesize-all-options": CL_ALL_DISABLED,
514                 "cl-ctrl-testtype-options": list(),
515                 "cl-ctrl-testtype-value": list(),
516                 "cl-ctrl-testtype-all-value": list(),
517                 "cl-ctrl-testtype-all-options": CL_ALL_DISABLED,
518                 "btn-ctrl-add-disabled": True,
519                 "cl-selected-options": list(),
520             }
521
522             self._panel = deepcopy(self._defaults)
523             if panel:
524                 for key in self._defaults:
525                     self._panel[key] = panel[key]
526
527         @property
528         def defaults(self) -> dict:
529             return self._defaults
530
531         @property
532         def panel(self) -> dict:
533             return self._panel
534
535         def set(self, kwargs: dict) -> None:
536             for key, val in kwargs.items():
537                 if key in self._panel:
538                     self._panel[key] = val
539                 else:
540                     raise KeyError(f"The key {key} is not defined.")
541
542         def get(self, key: str) -> any:
543             return self._panel[key]
544
545         def values(self) -> tuple:
546             return tuple(self._panel.values())
547
548     @staticmethod
549     def _sync_checklists(opt: list, sel: list, all: list, id: str) -> tuple:
550         """
551         """
552         options = {v["value"] for v in opt}
553         if id =="all":
554             sel = list(options) if all else list()
555         else:
556             all = ["all", ] if set(sel) == options else list()
557         return sel, all
558
559     @staticmethod
560     def _list_tests(selection: dict) -> list:
561         """Display selected tests with checkboxes
562         """
563         if selection:
564             return [
565                 {"label": v["id"], "value": v["id"]} for v in selection
566             ]
567         else:
568             return list()
569
570     def callbacks(self, app):
571
572         def _generate_plotting_arrea(args: tuple) -> tuple:
573             """
574             """
575
576             (fig_tput, fig_lat) = args
577
578             row_fig_tput = self.PLACEHOLDER
579             row_fig_lat = self.PLACEHOLDER
580             row_btn_dwnld = self.PLACEHOLDER
581
582             if fig_tput:
583                 row_fig_tput = [
584                     dcc.Loading(
585                         dcc.Graph(
586                             id="graph-tput",
587                             figure=fig_tput
588                         )
589                     ),
590                 ]
591                 row_btn_dwnld = [
592                     dcc.Loading(children=[
593                         dbc.Button(
594                             id="btn-download-data",
595                             children=["Download Data"]
596                         ),
597                         dcc.Download(id="download-data")
598                     ]),
599                 ]
600             if fig_lat:
601                 row_fig_lat = [
602                     dcc.Loading(
603                         dcc.Graph(
604                             id="graph-latency",
605                             figure=fig_lat
606                         )
607                     )
608                 ]
609
610             return row_fig_tput, row_fig_lat, row_btn_dwnld
611
612         @app.callback(
613             Output("control-panel", "data"),  # Store
614             Output("selected-tests", "data"),  # Store
615             Output("row-graph-tput", "children"),
616             Output("row-graph-lat", "children"),
617             Output("row-btn-download", "children"),
618             Output("row-card-sel-tests", "style"),
619             Output("row-btns-sel-tests", "style"),
620             Output("dd-ctrl-phy", "value"),
621             Output("dd-ctrl-area", "options"),
622             Output("dd-ctrl-area", "disabled"),
623             Output("dd-ctrl-area", "value"),
624             Output("dd-ctrl-test", "options"),
625             Output("dd-ctrl-test", "disabled"),
626             Output("dd-ctrl-test", "value"),
627             Output("cl-ctrl-core", "options"),
628             Output("cl-ctrl-core", "value"),
629             Output("cl-ctrl-core-all", "value"),
630             Output("cl-ctrl-core-all", "options"),
631             Output("cl-ctrl-framesize", "options"),
632             Output("cl-ctrl-framesize", "value"),
633             Output("cl-ctrl-framesize-all", "value"),
634             Output("cl-ctrl-framesize-all", "options"),
635             Output("cl-ctrl-testtype", "options"),
636             Output("cl-ctrl-testtype", "value"),
637             Output("cl-ctrl-testtype-all", "value"),
638             Output("cl-ctrl-testtype-all", "options"),
639             Output("btn-ctrl-add", "disabled"),
640             Output("cl-selected", "options"),  # User selection
641             State("control-panel", "data"),  # Store
642             State("selected-tests", "data"),  # Store
643             State("cl-selected", "value"),  # User selection
644             Input("dd-ctrl-phy", "value"),
645             Input("dd-ctrl-area", "value"),
646             Input("dd-ctrl-test", "value"),
647             Input("cl-ctrl-core", "value"),
648             Input("cl-ctrl-core-all", "value"),
649             Input("cl-ctrl-framesize", "value"),
650             Input("cl-ctrl-framesize-all", "value"),
651             Input("cl-ctrl-testtype", "value"),
652             Input("cl-ctrl-testtype-all", "value"),
653             Input("btn-ctrl-add", "n_clicks"),
654             Input("dpr-period", "start_date"),
655             Input("dpr-period", "end_date"),
656             Input("btn-sel-display", "n_clicks"),
657             Input("btn-sel-remove", "n_clicks"),
658             Input("btn-sel-remove-all", "n_clicks"),
659         )
660         def _update_ctrl_panel(cp_data: dict, store_sel: list, list_sel: list,
661             dd_phy: str, dd_area: str, dd_test: str, cl_core: list,
662             cl_core_all: list, cl_framesize: list, cl_framesize_all: list,
663             cl_testtype: list, cl_testtype_all: list, btn_add: int,
664             d_start: str, d_end: str, btn_display: int, btn_remove: int,
665             btn_remove_all: int) -> tuple:
666             """
667             """
668
669             d_start = datetime(int(d_start[0:4]), int(d_start[5:7]),
670                 int(d_start[8:10]))
671             d_end = datetime(int(d_end[0:4]), int(d_end[5:7]), int(d_end[8:10]))
672
673             row_fig_tput = no_update
674             row_fig_lat = no_update
675             row_btn_dwnld = no_update
676             row_card_sel_tests = no_update
677             row_btns_sel_tests = no_update
678
679             ctrl_panel = self.ControlPanel(cp_data)
680
681             trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
682
683             if trigger_id == "dd-ctrl-phy":
684                 try:
685                     options = [
686                         {"label": self.spec_tbs[dd_phy][v]["label"], "value": v}
687                             for v in [v for v in self.spec_tbs[dd_phy].keys()]
688                     ]
689                     disabled = False
690                 except KeyError:
691                     options = list()
692                     disabled = no_update
693                 ctrl_panel.set({
694                     "dd-ctrl-phy-value": dd_phy,
695                     "dd-ctrl-area-value": str(),
696                     "dd-ctrl-area-options": options,
697                     "dd-ctrl-area-disabled": disabled,
698                     "dd-ctrl-test-options": list(),
699                     "dd-ctrl-test-disabled": True,
700                     "cl-ctrl-core-options": list(),
701                     "cl-ctrl-core-value": list(),
702                     "cl-ctrl-core-all-value": list(),
703                     "cl-ctrl-core-all-options": self.CL_ALL_DISABLED,
704                     "cl-ctrl-framesize-options": list(),
705                     "cl-ctrl-framesize-value": list(),
706                     "cl-ctrl-framesize-all-value": list(),
707                     "cl-ctrl-framesize-all-options": self.CL_ALL_DISABLED,
708                     "cl-ctrl-testtype-options": list(),
709                     "cl-ctrl-testtype-value": list(),
710                     "cl-ctrl-testtype-all-value": list(),
711                     "cl-ctrl-testtype-all-options": self.CL_ALL_DISABLED,
712                     "btn-ctrl-add-disabled": True,
713                 })
714             elif trigger_id == "dd-ctrl-area":
715                 try:
716                     phy = ctrl_panel.get("dd-ctrl-phy-value")
717                     options = [
718                         {"label": v, "value": v}
719                             for v in self.spec_tbs[phy][dd_area]["test"]
720                     ]
721                     disabled = False
722                 except KeyError:
723                     options = list()
724                     disabled = True
725                 ctrl_panel.set({
726                     "dd-ctrl-area-value": dd_area,
727                     "dd-ctrl-test-value": str(),
728                     "dd-ctrl-test-options": options,
729                     "dd-ctrl-test-disabled": disabled,
730                     "cl-ctrl-core-options": list(),
731                     "cl-ctrl-core-value": list(),
732                     "cl-ctrl-core-all-value": list(),
733                     "cl-ctrl-core-all-options": self.CL_ALL_DISABLED,
734                     "cl-ctrl-framesize-options": list(),
735                     "cl-ctrl-framesize-value": list(),
736                     "cl-ctrl-framesize-all-value": list(),
737                     "cl-ctrl-framesize-all-options": self.CL_ALL_DISABLED,
738                     "cl-ctrl-testtype-options": list(),
739                     "cl-ctrl-testtype-value": list(),
740                     "cl-ctrl-testtype-all-value": list(),
741                     "cl-ctrl-testtype-all-options": self.CL_ALL_DISABLED,
742                     "btn-ctrl-add-disabled": True,
743                 })
744             elif trigger_id == "dd-ctrl-test":
745                 core_opts = list()
746                 framesize_opts = list()
747                 testtype_opts = list()
748                 phy = ctrl_panel.get("dd-ctrl-phy-value")
749                 area = ctrl_panel.get("dd-ctrl-area-value")
750                 if phy and area and dd_test:
751                     core_opts = [
752                         {"label": v, "value": v}
753                             for v in self.spec_tbs[phy][area]["core"]
754                     ]
755                     framesize_opts = [
756                         {"label": v, "value": v}
757                             for v in self.spec_tbs[phy][area]["frame-size"]
758                     ]
759                     testtype_opts = [
760                         {"label": v, "value": v}
761                             for v in self.spec_tbs[phy][area]["test-type"]
762                     ]
763                     ctrl_panel.set({
764                         "dd-ctrl-test-value": dd_test,
765                         "cl-ctrl-core-options": core_opts,
766                         "cl-ctrl-core-value": list(),
767                         "cl-ctrl-core-all-value": list(),
768                         "cl-ctrl-core-all-options": self.CL_ALL_ENABLED,
769                         "cl-ctrl-framesize-options": framesize_opts,
770                         "cl-ctrl-framesize-value": list(),
771                         "cl-ctrl-framesize-all-value": list(),
772                         "cl-ctrl-framesize-all-options": self.CL_ALL_ENABLED,
773                         "cl-ctrl-testtype-options": testtype_opts,
774                         "cl-ctrl-testtype-value": list(),
775                         "cl-ctrl-testtype-all-value": list(),
776                         "cl-ctrl-testtype-all-options": self.CL_ALL_ENABLED,
777                         "btn-ctrl-add-disabled": False,
778                     })
779             elif trigger_id == "cl-ctrl-core":
780                 val_sel, val_all = self._sync_checklists(
781                     opt=ctrl_panel.get("cl-ctrl-core-options"),
782                     sel=cl_core,
783                     all=list(),
784                     id=""
785                 )
786                 ctrl_panel.set({
787                     "cl-ctrl-core-value": val_sel,
788                     "cl-ctrl-core-all-value": val_all,
789                 })
790             elif trigger_id == "cl-ctrl-core-all":
791                 val_sel, val_all = self._sync_checklists(
792                     opt = ctrl_panel.get("cl-ctrl-core-options"),
793                     sel=list(),
794                     all=cl_core_all,
795                     id="all"
796                 )
797                 ctrl_panel.set({
798                     "cl-ctrl-core-value": val_sel,
799                     "cl-ctrl-core-all-value": val_all,
800                 })
801             elif trigger_id == "cl-ctrl-framesize":
802                 val_sel, val_all = self._sync_checklists(
803                     opt = ctrl_panel.get("cl-ctrl-framesize-options"),
804                     sel=cl_framesize,
805                     all=list(),
806                     id=""
807                 )
808                 ctrl_panel.set({
809                     "cl-ctrl-framesize-value": val_sel,
810                     "cl-ctrl-framesize-all-value": val_all,
811                 })
812             elif trigger_id == "cl-ctrl-framesize-all":
813                 val_sel, val_all = self._sync_checklists(
814                     opt = ctrl_panel.get("cl-ctrl-framesize-options"),
815                     sel=list(),
816                     all=cl_framesize_all,
817                     id="all"
818                 )
819                 ctrl_panel.set({
820                     "cl-ctrl-framesize-value": val_sel,
821                     "cl-ctrl-framesize-all-value": val_all,
822                 })
823             elif trigger_id == "cl-ctrl-testtype":
824                 val_sel, val_all = self._sync_checklists(
825                     opt = ctrl_panel.get("cl-ctrl-testtype-options"),
826                     sel=cl_testtype,
827                     all=list(),
828                     id=""
829                 )
830                 ctrl_panel.set({
831                     "cl-ctrl-testtype-value": val_sel,
832                     "cl-ctrl-testtype-all-value": val_all,
833                 })
834             elif trigger_id == "cl-ctrl-testtype-all":
835                 val_sel, val_all = self._sync_checklists(
836                     opt = ctrl_panel.get("cl-ctrl-testtype-options"),
837                     sel=list(),
838                     all=cl_testtype_all,
839                     id="all"
840                 )
841                 ctrl_panel.set({
842                     "cl-ctrl-testtype-value": val_sel,
843                     "cl-ctrl-testtype-all-value": val_all,
844                 })
845             elif trigger_id == "btn-ctrl-add":
846                 _ = btn_add
847                 phy = ctrl_panel.get("dd-ctrl-phy-value")
848                 area = ctrl_panel.get("dd-ctrl-area-value")
849                 test = ctrl_panel.get("dd-ctrl-test-value")
850                 cores = ctrl_panel.get("cl-ctrl-core-value")
851                 framesizes = ctrl_panel.get("cl-ctrl-framesize-value")
852                 testtypes = ctrl_panel.get("cl-ctrl-testtype-value")
853                 # Add selected test to the list of tests in store:
854                 if phy and area and test and cores and framesizes and testtypes:
855                     if store_sel is None:
856                         store_sel = list()
857                     for core in cores:
858                         for framesize in framesizes:
859                             for ttype in testtypes:
860                                 tid = (
861                                     f"{phy.replace('af_xdp', 'af-xdp')}-"
862                                     f"{area}-"
863                                     f"{framesize.lower()}-"
864                                     f"{core.lower()}-"
865                                     f"{test}-"
866                                     f"{ttype.lower()}"
867                                 )
868                                 if tid not in [itm["id"] for itm in store_sel]:
869                                     store_sel.append({
870                                         "id": tid,
871                                         "phy": phy,
872                                         "area": area,
873                                         "test": test,
874                                         "framesize": framesize.lower(),
875                                         "core": core.lower(),
876                                         "testtype": ttype.lower()
877                                     })
878                     row_card_sel_tests = self.STYLE_ENABLED
879                     row_btns_sel_tests = self.STYLE_ENABLED
880                     ctrl_panel.set(ctrl_panel.defaults)
881                     ctrl_panel.set({
882                         "cl-selected-options": self._list_tests(store_sel)
883                     })
884             elif trigger_id in ("btn-sel-display", "dpr-period"):
885                 _ = btn_display
886                 row_fig_tput, row_fig_lat, row_btn_dwnld = \
887                     _generate_plotting_arrea(
888                         graph_trending(
889                             self.data, store_sel, self.layout, d_start, d_end
890                         )
891                     )
892             elif trigger_id == "btn-sel-remove-all":
893                 _ = btn_remove_all
894                 row_fig_tput = self.PLACEHOLDER
895                 row_fig_lat = self.PLACEHOLDER
896                 row_btn_dwnld = self.PLACEHOLDER
897                 row_card_sel_tests = self.STYLE_DISABLED
898                 row_btns_sel_tests = self.STYLE_DISABLED
899                 store_sel = list()
900                 ctrl_panel.set({
901                         "cl-selected-options": list()
902                 })
903             elif trigger_id == "btn-sel-remove":
904                 _ = btn_remove
905                 if list_sel:
906                     new_store_sel = list()
907                     for item in store_sel:
908                         if item["id"] not in list_sel:
909                             new_store_sel.append(item)
910                     store_sel = new_store_sel
911                 if store_sel:
912                     row_fig_tput, row_fig_lat, row_btn_dwnld = \
913                     _generate_plotting_arrea(
914                         graph_trending(
915                             self.data, store_sel, self.layout, d_start, d_end
916                         )
917                     )
918                     ctrl_panel.set({
919                         "cl-selected-options": self._list_tests(store_sel)
920                     })
921                 else:
922                     row_fig_tput = self.PLACEHOLDER
923                     row_fig_lat = self.PLACEHOLDER
924                     row_btn_dwnld = self.PLACEHOLDER
925                     row_card_sel_tests = self.STYLE_DISABLED
926                     row_btns_sel_tests = self.STYLE_DISABLED
927                     store_sel = list()
928                     ctrl_panel.set({
929                             "cl-selected-options": list()
930                     })
931
932             ret_val = [
933                 ctrl_panel.panel, store_sel,
934                 row_fig_tput, row_fig_lat, row_btn_dwnld,
935                 row_card_sel_tests, row_btns_sel_tests
936             ]
937             ret_val.extend(ctrl_panel.values())
938             return ret_val
939
940         @app.callback(
941             Output("metadata-tput-lat", "children"),
942             Output("metadata-hdrh-graph", "children"),
943             Output("offcanvas-metadata", "is_open"),
944             Input("graph-tput", "clickData"),
945             Input("graph-latency", "clickData")
946         )
947         def _show_metadata_from_graphs(
948             tput_data: dict, lat_data: dict) -> tuple:
949             """
950             """
951             if not (tput_data or lat_data):
952                 raise PreventUpdate
953
954             metadata = no_update
955             graph = list()
956
957             trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
958             if trigger_id == "graph-tput":
959                 title = "Throughput"
960                 array = tput_data["points"][0]["text"].split("<br>")
961                 children = [
962                     dbc.ListGroupItem(
963                         [dbc.Badge(x.split(":")[0]), x.split(": ")[1]]
964                     ) for x in array
965                 ]
966             elif trigger_id == "graph-latency":
967                 title = "Latency"
968                 array = lat_data["points"][0]["text"].split("<br>")
969                 children = [
970                     dbc.ListGroupItem(
971                         [dbc.Badge(x.split(":")[0]), x.split(": ")[1]]
972                     ) for x in array
973                 ]
974                 hdrh_data = lat_data["points"][0].get("customdata", None)
975                 if hdrh_data:
976                     graph = [dbc.Card(
977                         class_name="gy-2 p-0",
978                         children=[
979                             dbc.CardHeader(hdrh_data.pop("name")),
980                             dbc.CardBody(children=[
981                                 dcc.Graph(
982                                     id="hdrh-latency-graph",
983                                     figure=graph_hdrh_latency(
984                                         hdrh_data, self.layout
985                                     )
986                                 )
987                             ])
988                         ])
989                     ]
990             metadata = [
991                 dbc.Card(
992                     class_name="gy-2 p-0",
993                     children=[
994                         dbc.CardHeader(children=[
995                             dcc.Clipboard(
996                                 target_id="tput-lat-metadata",
997                                 title="Copy",
998                                 style={"display": "inline-block"}
999                             ),
1000                             title
1001                         ]),
1002                         dbc.CardBody(
1003                             id="tput-lat-metadata",
1004                             class_name="p-0",
1005                             children=[
1006                                 dbc.ListGroup(children, flush=True)
1007                             ]
1008                         )
1009                     ]
1010                 )
1011             ]
1012
1013             return metadata, graph, True
1014
1015         @app.callback(
1016             Output("download-data", "data"),
1017             State("selected-tests", "data"),
1018             Input("btn-download-data", "n_clicks"),
1019             prevent_initial_call=True
1020         )
1021         def _download_data(store_sel, n_clicks):
1022             """
1023             """
1024
1025             if not n_clicks:
1026                 raise PreventUpdate
1027
1028             if not store_sel:
1029                 raise PreventUpdate
1030
1031             df = pd.DataFrame()
1032             for itm in store_sel:
1033                 sel_data = select_trending_data(self.data, itm)
1034                 if sel_data is None:
1035                     continue
1036                 df = pd.concat([df, sel_data], ignore_index=True)
1037
1038             return dcc.send_data_frame(df.to_csv, "trending_data.csv")