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