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