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:
6 # http://www.apache.org/licenses/LICENSE-2.0
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.
14 """Plotly Dash HTML layout override.
23 from dash import callback_context, no_update
24 from dash import Input, Output, State, callback
25 from dash.exceptions import PreventUpdate
26 from yaml import load, FullLoader, YAMLError
27 from datetime import datetime, timedelta
29 from ..data.data import Data
30 from .graphs import trending_tput
37 def __init__(self, app, html_layout_file, spec_file, graph_layout_file,
44 self._html_layout_file = html_layout_file
45 self._spec_file = spec_file
46 self._graph_layout_file = graph_layout_file
47 self._data_spec_file = data_spec_file
51 data_spec_file=self._data_spec_file,
56 data_spec_file=self._data_spec_file,
58 ).read_trending_ndrpdr()
60 self._data = pd.concat([data_mrr, data_ndrpdr], ignore_index=True)
63 self._html_layout = ""
65 self._graph_layout = None
68 with open(self._html_layout_file, "r") as file_read:
69 self._html_layout = file_read.read()
70 except IOError as err:
72 f"Not possible to open the file {self._html_layout_file}\n{err}"
76 with open(self._spec_file, "r") as file_read:
77 self._spec_tbs = load(file_read, Loader=FullLoader)
78 except IOError as err:
80 f"Not possible to open the file {self._spec_file,}\n{err}"
82 except YAMLError as err:
84 f"An error occurred while parsing the specification file "
85 f"{self._spec_file,}\n"
90 with open(self._graph_layout_file, "r") as file_read:
91 self._graph_layout = load(file_read, Loader=FullLoader)
92 except IOError as err:
94 f"Not possible to open the file {self._graph_layout_file}\n"
97 except YAMLError as err:
99 f"An error occurred while parsing the specification file "
100 f"{self._graph_layout_file}\n"
105 if self._app is not None and hasattr(self, 'callbacks'):
106 self.callbacks(self._app)
109 def html_layout(self):
110 return self._html_layout
114 return self._spec_tbs
122 return self._graph_layout
124 def add_content(self):
127 if self.html_layout and self.spec_tbs:
131 dcc.Store(id="selected-tests"),
132 self._add_ctrl_div(),
133 self._add_plotting_div()
139 children="An Error Occured."
142 def _add_ctrl_div(self):
143 """Add div with controls. It is placed on the left side.
149 id="div-controls-tabs",
151 self._add_ctrl_select(),
152 self._add_ctrl_shown()
157 "display": "inline-block",
163 def _add_plotting_div(self):
164 """Add div with plots and tables. It is placed on the right side.
167 id="div-plotting-area",
183 Click on data points in the graph.
192 "vertical-align": "top",
199 def _add_ctrl_shown(self):
205 html.H5("Selected tests"),
207 id="container-selected-tests",
212 labelStyle={"display": "block"}
216 children="Remove Selected",
220 id="btn-sel-display",
229 def _add_ctrl_select(self):
233 id="div-ctrl-select",
235 html.H5("Physical Test Bed Topology, NIC and Driver"),
238 placeholder="Select a Physical Test Bed Topology...",
242 {"label": k, "value": k} for k in self.spec_tbs.keys()
248 placeholder="Select an Area...",
256 placeholder="Select a Test...",
264 html.H5("Number of Cores"),
266 id="cl-ctrl-core-all",
267 options=[{"label": "All", "value": "all"}, ],
268 labelStyle={"display": "inline-block"}
272 labelStyle={"display": "inline-block"}
275 style={"display": "none"}
278 id="div-ctrl-framesize",
280 html.H5("Frame Size"),
282 id="cl-ctrl-framesize-all",
283 options=[{"label": "All", "value": "all"}, ],
284 labelStyle={"display": "inline-block"}
287 id="cl-ctrl-framesize",
288 labelStyle={"display": "inline-block"}
291 style={"display": "none"}
294 id="div-ctrl-testtype",
296 html.H5("Test Type"),
298 id="cl-ctrl-testtype-all",
299 options=[{"label": "All", "value": "all"}, ],
300 labelStyle={"display": "inline-block"}
303 id="cl-ctrl-testtype",
304 labelStyle={"display": "inline-block"}
307 style={"display": "none"}
317 min_date_allowed=datetime.utcnow() - timedelta(days=180),
318 max_date_allowed=datetime.utcnow(),
319 initial_visible_month=datetime.utcnow(),
320 start_date=datetime.utcnow() - timedelta(days=180),
321 end_date=datetime.utcnow(),
322 display_format="D MMMM YY"
327 def callbacks(self, app):
330 Output("dd-ctrl-area", "options"),
331 Output("dd-ctrl-area", "disabled"),
332 Input("dd-ctrl-phy", "value"),
334 def _update_dd_area(phy):
343 {"label": self.spec_tbs[phy][v]["label"], "value": v}
344 for v in [v for v in self.spec_tbs[phy].keys()]
351 return options, disable
354 Output("dd-ctrl-test", "options"),
355 Output("dd-ctrl-test", "disabled"),
356 State("dd-ctrl-phy", "value"),
357 Input("dd-ctrl-area", "value"),
359 def _update_dd_test(phy, area):
368 {"label": v, "value": v}
369 for v in self.spec_tbs[phy][area]["test"]
376 return options, disable
379 Output("div-ctrl-core", "style"),
380 Output("cl-ctrl-core", "options"),
381 Output("div-ctrl-framesize", "style"),
382 Output("cl-ctrl-framesize", "options"),
383 Output("div-ctrl-testtype", "style"),
384 Output("cl-ctrl-testtype", "options"),
385 Output("btn-ctrl-add", "disabled"),
386 State("dd-ctrl-phy", "value"),
387 State("dd-ctrl-area", "value"),
388 Input("dd-ctrl-test", "value"),
390 def _update_btn_add(phy, area, test):
397 core_style = {"display": "none"}
399 framesize_style = {"display": "none"}
401 testtype_style = {"display": "none"}
404 if phy and area and test:
405 core_style = {"display": "block"}
407 {"label": v, "value": v}
408 for v in self.spec_tbs[phy][area]["core"]
410 framesize_style = {"display": "block"}
412 {"label": v, "value": v}
413 for v in self.spec_tbs[phy][area]["frame-size"]
415 testtype_style = {"display": "block"}
417 {"label": v, "value": v}
418 for v in self.spec_tbs[phy][area]["test-type"]
423 core_style, core_opts,
424 framesize_style, framesize_opts,
425 testtype_style, testtype_opts,
429 def _sync_checklists(opt, sel, all, id):
432 options = {v["value"] for v in opt}
433 input_id = callback_context.triggered[0]["prop_id"].split(".")[0]
435 all = ["all"] if set(sel) == options else list()
437 sel = list(options) if all else list()
441 Output("cl-ctrl-core", "value"),
442 Output("cl-ctrl-core-all", "value"),
443 State("cl-ctrl-core", "options"),
444 Input("cl-ctrl-core", "value"),
445 Input("cl-ctrl-core-all", "value"),
446 prevent_initial_call=True
448 def _sync_cl_core(opt, sel, all):
449 return _sync_checklists(opt, sel, all, "cl-ctrl-core")
452 Output("cl-ctrl-framesize", "value"),
453 Output("cl-ctrl-framesize-all", "value"),
454 State("cl-ctrl-framesize", "options"),
455 Input("cl-ctrl-framesize", "value"),
456 Input("cl-ctrl-framesize-all", "value"),
457 prevent_initial_call=True
459 def _sync_cl_framesize(opt, sel, all):
460 return _sync_checklists(opt, sel, all, "cl-ctrl-framesize")
463 Output("cl-ctrl-testtype", "value"),
464 Output("cl-ctrl-testtype-all", "value"),
465 State("cl-ctrl-testtype", "options"),
466 Input("cl-ctrl-testtype", "value"),
467 Input("cl-ctrl-testtype-all", "value"),
468 prevent_initial_call=True
470 def _sync_cl_testtype(opt, sel, all):
471 return _sync_checklists(opt, sel, all, "cl-ctrl-testtype")
474 Output("graph", "figure"),
475 Output("selected-tests", "data"), # Store
476 Output("cl-selected", "options"), # User selection
477 Output("dd-ctrl-phy", "value"),
478 Output("dd-ctrl-area", "value"),
479 Output("dd-ctrl-test", "value"),
480 Output("div-plotting-area", "style"),
481 State("selected-tests", "data"), # Store
482 State("cl-selected", "value"),
483 State("dd-ctrl-phy", "value"),
484 State("dd-ctrl-area", "value"),
485 State("dd-ctrl-test", "value"),
486 State("cl-ctrl-core", "value"),
487 State("cl-ctrl-framesize", "value"),
488 State("cl-ctrl-testtype", "value"),
489 Input("btn-ctrl-add", "n_clicks"),
490 Input("btn-sel-display", "n_clicks"),
491 Input("btn-sel-remove", "n_clicks"),
492 Input("dpr-period", "start_date"),
493 Input("dpr-period", "end_date"),
494 prevent_initial_call=True
496 def _process_list(store_sel, list_sel, phy, area, test, cores,
497 framesizes, testtypes, btn_add, btn_display, btn_remove,
502 if not (btn_add or btn_display or btn_remove or d_start or d_end):
506 # Display selected tests with checkboxes:
509 {"label": v["id"], "value": v["id"]} for v in store_sel
514 trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
517 int(d_start[0:4]), int(d_start[5:7]), int(d_start[8:10])
520 int(d_end[0:4]), int(d_end[5:7]), int(d_end[8:10])
523 if trigger_id == "btn-ctrl-add":
524 # Add selected test to the list of tests in store:
525 if phy and area and test and cores and framesizes and testtypes:
527 # TODO: Add validation
529 if store_sel is None:
533 for framesize in framesizes:
534 for ttype in testtypes:
536 f"{phy.replace('af_xdp', 'af-xdp')}-"
538 f"{framesize.lower()}-"
543 if tid not in [itm["id"] for itm in store_sel]:
549 "framesize": framesize.lower(),
550 "core": core.lower(),
551 "testtype": ttype.lower()
553 return (no_update, store_sel, _list_tests(), None,
554 None, None, no_update)
556 elif trigger_id in ("btn-sel-display", "dpr-period"):
557 fig, style = trending_tput(
558 self.data, store_sel, self.layout, d_start, d_end
560 return (fig, no_update, no_update,
561 no_update, no_update, no_update, style)
563 elif trigger_id == "btn-sel-remove":
565 new_store_sel = list()
566 for item in store_sel:
567 if item["id"] not in list_sel:
568 new_store_sel.append(item)
569 store_sel = new_store_sel
571 fig, style = trending_tput(
572 self.data, store_sel, self.layout, d_start, d_end
574 return (fig, store_sel, _list_tests(),
575 no_update, no_update, no_update, style)
578 "vertical-align": "top",
583 return (no_update, store_sel, _list_tests(),
584 no_update, no_update, no_update, style)
587 Output("hover-metadata", "children"),
588 Input("graph", "clickData")
590 def _show_metadata(hover_data):
593 return json.dumps(hover_data, indent=2)