11900c68fe2c3cf7518f5eb5b17c2f8e4591a87b
[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     NO_GRAPH = {"data": [], "layout": {}, "frames": []}
39
40     CL_ALL_DISABLED = [{
41         "label": "All",
42         "value": "all",
43         "disabled": True
44     }]
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                     class_name="gy-1",
435                     children=[
436                         dbc.Card(
437                             class_name="p-0",
438                             children=[
439                                 dbc.Label(
440                                     "Selected tests",
441                                     class_name="p-0"
442                                 ),
443                                 dbc.Checklist(
444                                     id="cl-selected",
445                                     options=[],
446                                     inline=False
447                                 )
448                             ],
449                             color="light",
450                             outline=True
451                         )
452                     ]
453                 ),
454                 dbc.Row(
455                     children=[
456                         dbc.ButtonGroup(
457                             [
458                                 dbc.Button(
459                                     id="btn-sel-remove-all",
460                                     children="Remove All",
461                                     class_name="w-100",
462                                     color="secondary",
463                                     disabled=False
464                                 ),
465                                 dbc.Button(
466                                     id="btn-sel-remove",
467                                     children="Remove Selected",
468                                     class_name="w-100",
469                                     color="secondary",
470                                     disabled=False
471                                 ),
472                                 dbc.Button(
473                                     id="btn-sel-display",
474                                     children="Display",
475                                     class_name="w-100",
476                                     color="secondary",
477                                     disabled=False
478                                 )
479                             ],
480                             size="md",
481                         ),
482                     ]
483                 ),
484             ]
485         )
486
487     class ControlPanel:
488         def __init__(self, panel: dict) -> None:
489
490             CL_ALL_DISABLED = [{
491                 "label": "All",
492                 "value": "all",
493                 "disabled": True
494             }]
495
496             # Defines also the order of keys
497             self._defaults = {
498                 "dd-ctrl-phy-value": str(),
499                 "dd-ctrl-area-options": list(),
500                 "dd-ctrl-area-disabled": True,
501                 "dd-ctrl-area-value": str(),
502                 "dd-ctrl-test-options": list(),
503                 "dd-ctrl-test-disabled": True,
504                 "dd-ctrl-test-value": str(),
505                 "cl-ctrl-core-options": list(),
506                 "cl-ctrl-core-value": list(),
507                 "cl-ctrl-core-all-value": list(),
508                 "cl-ctrl-core-all-options": CL_ALL_DISABLED,
509                 "cl-ctrl-framesize-options": list(),
510                 "cl-ctrl-framesize-value": list(),
511                 "cl-ctrl-framesize-all-value": list(),
512                 "cl-ctrl-framesize-all-options": CL_ALL_DISABLED,
513                 "cl-ctrl-testtype-options": list(),
514                 "cl-ctrl-testtype-value": list(),
515                 "cl-ctrl-testtype-all-value": list(),
516                 "cl-ctrl-testtype-all-options": CL_ALL_DISABLED,
517                 "btn-ctrl-add-disabled": True,
518                 "cl-selected-options": list(),
519             }
520
521             self._panel = deepcopy(self._defaults)
522             if panel:
523                 for key in self._defaults:
524                     self._panel[key] = panel[key]
525
526         @property
527         def defaults(self) -> dict:
528             return self._defaults
529
530         @property
531         def panel(self) -> dict:
532             return self._panel
533
534         def set(self, kwargs: dict) -> None:
535             for key, val in kwargs.items():
536                 if key in self._panel:
537                     self._panel[key] = val
538                 else:
539                     raise KeyError(f"The key {key} is not defined.")
540
541         def get(self, key: str) -> any:
542             return self._panel[key]
543
544         def values(self) -> tuple:
545             return tuple(self._panel.values())
546
547     @staticmethod
548     def _sync_checklists(opt: list, sel: list, all: list, id: str) -> tuple:
549         """
550         """
551         options = {v["value"] for v in opt}
552         if id =="all":
553             sel = list(options) if all else list()
554         else:
555             all = ["all", ] if set(sel) == options else list()
556         return sel, all
557
558     @staticmethod
559     def _list_tests(selection: dict) -> list:
560         """Display selected tests with checkboxes
561         """
562         if selection:
563             return [
564                 {"label": v["id"], "value": v["id"]} for v in selection
565             ]
566         else:
567             return list()
568
569     def callbacks(self, app):
570
571         def _generate_plotting_arrea(args: tuple) -> tuple:
572             """
573             """
574
575             (fig_tput, fig_lat) = args
576
577             row_fig_tput = self.PLACEHOLDER
578             row_fig_lat = self.PLACEHOLDER
579             row_btn_dwnld = self.PLACEHOLDER
580
581             if fig_tput:
582                 row_fig_tput = [
583                     dcc.Loading(
584                         dcc.Graph(
585                             id="graph-tput",
586                             figure=fig_tput
587                         )
588                     ),
589                 ]
590                 row_btn_dwnld = [
591                     dcc.Loading(children=[
592                         dbc.Button(
593                             id="btn-download-data",
594                             children=["Download Data"]
595                         ),
596                         dcc.Download(id="download-data")
597                     ]),
598                 ]
599             if fig_lat:
600                 row_fig_lat = [
601                     dcc.Loading(
602                         dcc.Graph(
603                             id="graph-latency",
604                             figure=fig_lat
605                         )
606                     )
607                 ]
608
609             return row_fig_tput, row_fig_lat, row_btn_dwnld
610
611         @app.callback(
612             Output("control-panel", "data"),  # Store
613             Output("selected-tests", "data"),  # Store
614             Output("row-graph-tput", "children"),
615             Output("row-graph-lat", "children"),
616             Output("row-btn-download", "children"),
617             Output("dd-ctrl-phy", "value"),
618             Output("dd-ctrl-area", "options"),
619             Output("dd-ctrl-area", "disabled"),
620             Output("dd-ctrl-area", "value"),
621             Output("dd-ctrl-test", "options"),
622             Output("dd-ctrl-test", "disabled"),
623             Output("dd-ctrl-test", "value"),
624             Output("cl-ctrl-core", "options"),
625             Output("cl-ctrl-core", "value"),
626             Output("cl-ctrl-core-all", "value"),
627             Output("cl-ctrl-core-all", "options"),
628             Output("cl-ctrl-framesize", "options"),
629             Output("cl-ctrl-framesize", "value"),
630             Output("cl-ctrl-framesize-all", "value"),
631             Output("cl-ctrl-framesize-all", "options"),
632             Output("cl-ctrl-testtype", "options"),
633             Output("cl-ctrl-testtype", "value"),
634             Output("cl-ctrl-testtype-all", "value"),
635             Output("cl-ctrl-testtype-all", "options"),
636             Output("btn-ctrl-add", "disabled"),
637             Output("cl-selected", "options"),  # User selection
638             State("control-panel", "data"),  # Store
639             State("selected-tests", "data"),  # Store
640             State("cl-selected", "value"),  # User selection
641             Input("dd-ctrl-phy", "value"),
642             Input("dd-ctrl-area", "value"),
643             Input("dd-ctrl-test", "value"),
644             Input("cl-ctrl-core", "value"),
645             Input("cl-ctrl-core-all", "value"),
646             Input("cl-ctrl-framesize", "value"),
647             Input("cl-ctrl-framesize-all", "value"),
648             Input("cl-ctrl-testtype", "value"),
649             Input("cl-ctrl-testtype-all", "value"),
650             Input("btn-ctrl-add", "n_clicks"),
651             Input("dpr-period", "start_date"),
652             Input("dpr-period", "end_date"),
653             Input("btn-sel-display", "n_clicks"),
654             Input("btn-sel-remove", "n_clicks"),
655             Input("btn-sel-remove-all", "n_clicks"),
656         )
657         def _update_ctrl_panel(cp_data: dict, store_sel: list, list_sel: list,
658             dd_phy: str, dd_area: str, dd_test: str, cl_core: list,
659             cl_core_all: list, cl_framesize: list, cl_framesize_all: list,
660             cl_testtype: list, cl_testtype_all: list, btn_add: int,
661             d_start: str, d_end: str, btn_display: int, btn_remove: int,
662             btn_remove_all: int) -> tuple:
663             """
664             """
665
666             d_start = datetime(int(d_start[0:4]), int(d_start[5:7]),
667                 int(d_start[8:10]))
668             d_end = datetime(int(d_end[0:4]), int(d_end[5:7]), int(d_end[8:10]))
669
670             row_fig_tput = no_update
671             row_fig_lat = no_update
672             row_btn_dwnld = no_update
673
674             ctrl_panel = self.ControlPanel(cp_data)
675
676             trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
677
678             if trigger_id == "dd-ctrl-phy":
679                 try:
680                     options = [
681                         {"label": self.spec_tbs[dd_phy][v]["label"], "value": v}
682                             for v in [v for v in self.spec_tbs[dd_phy].keys()]
683                     ]
684                     disabled = False
685                 except KeyError:
686                     options = list()
687                     disabled = no_update
688                 ctrl_panel.set({
689                     "dd-ctrl-phy-value": dd_phy,
690                     "dd-ctrl-area-value": str(),
691                     "dd-ctrl-area-options": options,
692                     "dd-ctrl-area-disabled": disabled,
693                     "dd-ctrl-test-options": list(),
694                     "dd-ctrl-test-disabled": True,
695                     "cl-ctrl-core-options": list(),
696                     "cl-ctrl-core-value": list(),
697                     "cl-ctrl-core-all-value": list(),
698                     "cl-ctrl-core-all-options": self.CL_ALL_DISABLED,
699                     "cl-ctrl-framesize-options": list(),
700                     "cl-ctrl-framesize-value": list(),
701                     "cl-ctrl-framesize-all-value": list(),
702                     "cl-ctrl-framesize-all-options": self.CL_ALL_DISABLED,
703                     "cl-ctrl-testtype-options": list(),
704                     "cl-ctrl-testtype-value": list(),
705                     "cl-ctrl-testtype-all-value": list(),
706                     "cl-ctrl-testtype-all-options": self.CL_ALL_DISABLED,
707                     "btn-ctrl-add-disabled": True,
708                 })
709             elif trigger_id == "dd-ctrl-area":
710                 try:
711                     phy = ctrl_panel.get("dd-ctrl-phy-value")
712                     options = [
713                         {"label": v, "value": v}
714                             for v in self.spec_tbs[phy][dd_area]["test"]
715                     ]
716                     disabled = False
717                 except KeyError:
718                     options = list()
719                     disabled = True
720                 ctrl_panel.set({
721                     "dd-ctrl-area-value": dd_area,
722                     "dd-ctrl-test-value": str(),
723                     "dd-ctrl-test-options": options,
724                     "dd-ctrl-test-disabled": disabled,
725                     "cl-ctrl-core-options": list(),
726                     "cl-ctrl-core-value": list(),
727                     "cl-ctrl-core-all-value": list(),
728                     "cl-ctrl-core-all-options": self.CL_ALL_DISABLED,
729                     "cl-ctrl-framesize-options": list(),
730                     "cl-ctrl-framesize-value": list(),
731                     "cl-ctrl-framesize-all-value": list(),
732                     "cl-ctrl-framesize-all-options": self.CL_ALL_DISABLED,
733                     "cl-ctrl-testtype-options": list(),
734                     "cl-ctrl-testtype-value": list(),
735                     "cl-ctrl-testtype-all-value": list(),
736                     "cl-ctrl-testtype-all-options": self.CL_ALL_DISABLED,
737                     "btn-ctrl-add-disabled": True,
738                 })
739             elif trigger_id == "dd-ctrl-test":
740                 core_opts = list()
741                 framesize_opts = list()
742                 testtype_opts = list()
743                 phy = ctrl_panel.get("dd-ctrl-phy-value")
744                 area = ctrl_panel.get("dd-ctrl-area-value")
745                 if phy and area and dd_test:
746                     core_opts = [
747                         {"label": v, "value": v}
748                             for v in self.spec_tbs[phy][area]["core"]
749                     ]
750                     framesize_opts = [
751                         {"label": v, "value": v}
752                             for v in self.spec_tbs[phy][area]["frame-size"]
753                     ]
754                     testtype_opts = [
755                         {"label": v, "value": v}
756                             for v in self.spec_tbs[phy][area]["test-type"]
757                     ]
758                     ctrl_panel.set({
759                         "dd-ctrl-test-value": dd_test,
760                         "cl-ctrl-core-options": core_opts,
761                         "cl-ctrl-core-value": list(),
762                         "cl-ctrl-core-all-value": list(),
763                         "cl-ctrl-core-all-options": self.CL_ALL_ENABLED,
764                         "cl-ctrl-framesize-options": framesize_opts,
765                         "cl-ctrl-framesize-value": list(),
766                         "cl-ctrl-framesize-all-value": list(),
767                         "cl-ctrl-framesize-all-options": self.CL_ALL_ENABLED,
768                         "cl-ctrl-testtype-options": testtype_opts,
769                         "cl-ctrl-testtype-value": list(),
770                         "cl-ctrl-testtype-all-value": list(),
771                         "cl-ctrl-testtype-all-options": self.CL_ALL_ENABLED,
772                         "btn-ctrl-add-disabled": False,
773                     })
774             elif trigger_id == "cl-ctrl-core":
775                 val_sel, val_all = self._sync_checklists(
776                     opt=ctrl_panel.get("cl-ctrl-core-options"),
777                     sel=cl_core,
778                     all=list(),
779                     id=""
780                 )
781                 ctrl_panel.set({
782                     "cl-ctrl-core-value": val_sel,
783                     "cl-ctrl-core-all-value": val_all,
784                 })
785             elif trigger_id == "cl-ctrl-core-all":
786                 val_sel, val_all = self._sync_checklists(
787                     opt = ctrl_panel.get("cl-ctrl-core-options"),
788                     sel=list(),
789                     all=cl_core_all,
790                     id="all"
791                 )
792                 ctrl_panel.set({
793                     "cl-ctrl-core-value": val_sel,
794                     "cl-ctrl-core-all-value": val_all,
795                 })
796             elif trigger_id == "cl-ctrl-framesize":
797                 val_sel, val_all = self._sync_checklists(
798                     opt = ctrl_panel.get("cl-ctrl-framesize-options"),
799                     sel=cl_framesize,
800                     all=list(),
801                     id=""
802                 )
803                 ctrl_panel.set({
804                     "cl-ctrl-framesize-value": val_sel,
805                     "cl-ctrl-framesize-all-value": val_all,
806                 })
807             elif trigger_id == "cl-ctrl-framesize-all":
808                 val_sel, val_all = self._sync_checklists(
809                     opt = ctrl_panel.get("cl-ctrl-framesize-options"),
810                     sel=list(),
811                     all=cl_framesize_all,
812                     id="all"
813                 )
814                 ctrl_panel.set({
815                     "cl-ctrl-framesize-value": val_sel,
816                     "cl-ctrl-framesize-all-value": val_all,
817                 })
818             elif trigger_id == "cl-ctrl-testtype":
819                 val_sel, val_all = self._sync_checklists(
820                     opt = ctrl_panel.get("cl-ctrl-testtype-options"),
821                     sel=cl_testtype,
822                     all=list(),
823                     id=""
824                 )
825                 ctrl_panel.set({
826                     "cl-ctrl-testtype-value": val_sel,
827                     "cl-ctrl-testtype-all-value": val_all,
828                 })
829             elif trigger_id == "cl-ctrl-testtype-all":
830                 val_sel, val_all = self._sync_checklists(
831                     opt = ctrl_panel.get("cl-ctrl-testtype-options"),
832                     sel=list(),
833                     all=cl_testtype_all,
834                     id="all"
835                 )
836                 ctrl_panel.set({
837                     "cl-ctrl-testtype-value": val_sel,
838                     "cl-ctrl-testtype-all-value": val_all,
839                 })
840             elif trigger_id == "btn-ctrl-add":
841                 _ = btn_add
842                 phy = ctrl_panel.get("dd-ctrl-phy-value")
843                 area = ctrl_panel.get("dd-ctrl-area-value")
844                 test = ctrl_panel.get("dd-ctrl-test-value")
845                 cores = ctrl_panel.get("cl-ctrl-core-value")
846                 framesizes = ctrl_panel.get("cl-ctrl-framesize-value")
847                 testtypes = ctrl_panel.get("cl-ctrl-testtype-value")
848                 # Add selected test to the list of tests in store:
849                 if phy and area and test and cores and framesizes and testtypes:
850                     if store_sel is None:
851                         store_sel = list()
852                     for core in cores:
853                         for framesize in framesizes:
854                             for ttype in testtypes:
855                                 tid = (
856                                     f"{phy.replace('af_xdp', 'af-xdp')}-"
857                                     f"{area}-"
858                                     f"{framesize.lower()}-"
859                                     f"{core.lower()}-"
860                                     f"{test}-"
861                                     f"{ttype.lower()}"
862                                 )
863                                 if tid not in [itm["id"] for itm in store_sel]:
864                                     store_sel.append({
865                                         "id": tid,
866                                         "phy": phy,
867                                         "area": area,
868                                         "test": test,
869                                         "framesize": framesize.lower(),
870                                         "core": core.lower(),
871                                         "testtype": ttype.lower()
872                                     })
873                     ctrl_panel.set(ctrl_panel.defaults)
874                     ctrl_panel.set({
875                         "cl-selected-options": self._list_tests(store_sel)
876                     })
877             elif trigger_id in ("btn-sel-display", "dpr-period"):
878                 _ = btn_display
879                 row_fig_tput, row_fig_lat, row_btn_dwnld = \
880                     _generate_plotting_arrea(
881                         graph_trending(
882                             self.data, store_sel, self.layout, d_start, d_end
883                         )
884                     )
885             elif trigger_id == "btn-sel-remove-all":
886                 _ = btn_remove_all
887                 row_fig_tput = self.PLACEHOLDER
888                 row_fig_lat = self.PLACEHOLDER
889                 row_btn_dwnld = self.PLACEHOLDER
890                 store_sel = list()
891                 ctrl_panel.set({
892                         "cl-selected-options": list()
893                 })
894             elif trigger_id == "btn-sel-remove":
895                 _ = btn_remove
896                 if list_sel:
897                     new_store_sel = list()
898                     for item in store_sel:
899                         if item["id"] not in list_sel:
900                             new_store_sel.append(item)
901                     store_sel = new_store_sel
902                 if store_sel:
903                     row_fig_tput, row_fig_lat, row_btn_dwnld = \
904                     _generate_plotting_arrea(
905                         graph_trending(
906                             self.data, store_sel, self.layout, d_start, d_end
907                         )
908                     )
909                     ctrl_panel.set({
910                         "cl-selected-options": self._list_tests(store_sel)
911                     })
912                 else:
913                     row_fig_tput = self.PLACEHOLDER
914                     row_fig_lat = self.PLACEHOLDER
915                     row_btn_dwnld = self.PLACEHOLDER
916                     store_sel = list()
917                     ctrl_panel.set({
918                             "cl-selected-options": list()
919                     })
920
921             ret_val = [
922                 ctrl_panel.panel, store_sel,
923                 row_fig_tput, row_fig_lat, row_btn_dwnld
924             ]
925             ret_val.extend(ctrl_panel.values())
926             return ret_val
927
928         @app.callback(
929             Output("metadata-tput-lat", "children"),
930             Output("metadata-hdrh-graph", "children"),
931             Output("offcanvas-metadata", "is_open"),
932             Input("graph-tput", "clickData"),
933             Input("graph-latency", "clickData")
934         )
935         def _show_metadata_from_graphs(
936             tput_data: dict, lat_data: dict) -> tuple:
937             """
938             """
939             if not (tput_data or lat_data):
940                 raise PreventUpdate
941
942             metadata = no_update
943             graph = list()
944
945             trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
946             if trigger_id == "graph-tput":
947                 title = "Throughput"
948                 txt = tput_data["points"][0]["text"].replace("<br>", "\n")
949             elif trigger_id == "graph-latency":
950                 title = "Latency"
951                 txt = lat_data["points"][0]["text"].replace("<br>", "\n")
952                 hdrh_data = lat_data["points"][0].get("customdata", None)
953                 if hdrh_data:
954                     graph = [dbc.Card(
955                         class_name="g-0",
956                         children=[
957                             dbc.CardHeader(hdrh_data.pop("name")),
958                             dbc.CardBody(children=[
959                                 dcc.Graph(
960                                     id="hdrh-latency-graph",
961                                     figure=graph_hdrh_latency(
962                                         hdrh_data, self.layout
963                                     )
964                                 )
965                             ])
966                         ])
967                     ]
968             metadata = [
969                 dbc.Card(
970                     class_name="g-0",
971                     children=[
972                         dbc.CardHeader(children=[
973                             dcc.Clipboard(
974                                 target_id="tput-lat-metadata",
975                                 title="Copy",
976                                 style={"display": "inline-block"}
977                             ),
978                             title
979                         ]),
980                         dbc.CardBody(
981                             id="tput-lat-metadata",
982                             children=[txt]
983                         )
984                     ]
985                 )
986             ]
987
988             return metadata, graph, True
989
990         @app.callback(
991             Output("download-data", "data"),
992             State("selected-tests", "data"),
993             Input("btn-download-data", "n_clicks"),
994             prevent_initial_call=True
995         )
996         def _download_data(store_sel, n_clicks):
997             """
998             """
999
1000             if not n_clicks:
1001                 raise PreventUpdate
1002
1003             if not store_sel:
1004                 raise PreventUpdate
1005
1006             df = pd.DataFrame()
1007             for itm in store_sel:
1008                 sel_data = select_trending_data(self.data, itm)
1009                 if sel_data is None:
1010                     continue
1011                 df = pd.concat([df, sel_data], ignore_index=True)
1012
1013             return dcc.send_data_frame(df.to_csv, "trending_data.csv")