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.
22 from dash import callback_context, no_update
23 from dash import Input, Output, State, callback
24 from dash.exceptions import PreventUpdate
25 from yaml import load, FullLoader, YAMLError
26 from datetime import datetime, timedelta
28 from ..data.data import Data
29 from .graphs import trending_tput
36 def __init__(self, app, html_layout_file, spec_file, graph_layout_file,
43 self._html_layout_file = html_layout_file
44 self._spec_file = spec_file
45 self._graph_layout_file = graph_layout_file
46 self._data_spec_file = data_spec_file
50 data_spec_file=self._data_spec_file,
55 data_spec_file=self._data_spec_file,
57 ).read_trending_ndrpdr()
59 self._data = pd.concat([data_mrr, data_ndrpdr], ignore_index=True)
62 self._html_layout = ""
64 self._graph_layout = None
67 with open(self._html_layout_file, "r") as file_read:
68 self._html_layout = file_read.read()
69 except IOError as err:
71 f"Not possible to open the file {self._html_layout_file}\n{err}"
75 with open(self._spec_file, "r") as file_read:
76 self._spec_tbs = load(file_read, Loader=FullLoader)
77 except IOError as err:
79 f"Not possible to open the file {self._spec_file,}\n{err}"
81 except YAMLError as err:
83 f"An error occurred while parsing the specification file "
84 f"{self._spec_file,}\n"
89 with open(self._graph_layout_file, "r") as file_read:
90 self._graph_layout = load(file_read, Loader=FullLoader)
91 except IOError as err:
93 f"Not possible to open the file {self._graph_layout_file}\n"
96 except YAMLError as err:
98 f"An error occurred while parsing the specification file "
99 f"{self._graph_layout_file}\n"
104 if self._app is not None and hasattr(self, 'callbacks'):
105 self.callbacks(self._app)
108 def html_layout(self):
109 return self._html_layout
113 return self._spec_tbs
121 return self._graph_layout
123 def add_content(self):
126 if self.html_layout and self.spec_tbs:
130 dcc.Store(id="selected-tests"),
131 self._add_ctrl_div(),
132 self._add_plotting_div()
138 children="An Error Occured."
141 def _add_ctrl_div(self):
142 """Add div with controls. It is placed on the left side.
148 id="div-controls-tabs",
150 self._add_ctrl_select(),
151 self._add_ctrl_shown()
156 "display": "inline-block",
162 def _add_plotting_div(self):
163 """Add div with plots and tables. It is placed on the right side.
166 id="div-plotting-area",
179 "vertical-align": "top",
186 def _add_ctrl_shown(self):
192 html.H5("Selected tests"),
194 id="container-selected-tests",
199 labelStyle={"display": "block"}
203 children="Remove Selected",
207 id="btn-sel-display",
216 def _add_ctrl_select(self):
220 id="div-ctrl-select",
222 html.H5("Physical Test Bed Topology, NIC and Driver"),
225 placeholder="Select a Physical Test Bed Topology...",
229 {"label": k, "value": k} for k in self.spec_tbs.keys()
235 placeholder="Select an Area...",
243 placeholder="Select a Test...",
251 html.H5("Number of Cores"),
253 id="cl-ctrl-core-all",
254 options=[{"label": "All", "value": "all"}, ],
255 labelStyle={"display": "inline-block"}
259 labelStyle={"display": "inline-block"}
262 style={"display": "none"}
265 id="div-ctrl-framesize",
267 html.H5("Frame Size"),
269 id="cl-ctrl-framesize-all",
270 options=[{"label": "All", "value": "all"}, ],
271 labelStyle={"display": "inline-block"}
274 id="cl-ctrl-framesize",
275 labelStyle={"display": "inline-block"}
278 style={"display": "none"}
281 id="div-ctrl-testtype",
283 html.H5("Test Type"),
285 id="cl-ctrl-testtype-all",
286 options=[{"label": "All", "value": "all"}, ],
287 labelStyle={"display": "inline-block"}
290 id="cl-ctrl-testtype",
291 labelStyle={"display": "inline-block"}
294 style={"display": "none"}
304 min_date_allowed=datetime.utcnow() - timedelta(days=180),
305 max_date_allowed=datetime.utcnow(),
306 initial_visible_month=datetime.utcnow(),
307 start_date=datetime.utcnow() - timedelta(days=180),
308 end_date=datetime.utcnow(),
309 display_format="D MMMM YY"
314 def callbacks(self, app):
317 Output("dd-ctrl-area", "options"),
318 Output("dd-ctrl-area", "disabled"),
319 Input("dd-ctrl-phy", "value"),
321 def _update_dd_area(phy):
330 {"label": self.spec_tbs[phy][v]["label"], "value": v}
331 for v in [v for v in self.spec_tbs[phy].keys()]
338 return options, disable
341 Output("dd-ctrl-test", "options"),
342 Output("dd-ctrl-test", "disabled"),
343 State("dd-ctrl-phy", "value"),
344 Input("dd-ctrl-area", "value"),
346 def _update_dd_test(phy, area):
355 {"label": v, "value": v}
356 for v in self.spec_tbs[phy][area]["test"]
363 return options, disable
366 Output("div-ctrl-core", "style"),
367 Output("cl-ctrl-core", "options"),
368 Output("div-ctrl-framesize", "style"),
369 Output("cl-ctrl-framesize", "options"),
370 Output("div-ctrl-testtype", "style"),
371 Output("cl-ctrl-testtype", "options"),
372 Output("btn-ctrl-add", "disabled"),
373 State("dd-ctrl-phy", "value"),
374 State("dd-ctrl-area", "value"),
375 Input("dd-ctrl-test", "value"),
377 def _update_btn_add(phy, area, test):
384 core_style = {"display": "none"}
386 framesize_style = {"display": "none"}
388 testtype_style = {"display": "none"}
391 if phy and area and test:
392 core_style = {"display": "block"}
394 {"label": v, "value": v}
395 for v in self.spec_tbs[phy][area]["core"]
397 framesize_style = {"display": "block"}
399 {"label": v, "value": v}
400 for v in self.spec_tbs[phy][area]["frame-size"]
402 testtype_style = {"display": "block"}
404 {"label": v, "value": v}
405 for v in self.spec_tbs[phy][area]["test-type"]
410 core_style, core_opts,
411 framesize_style, framesize_opts,
412 testtype_style, testtype_opts,
416 def _sync_checklists(opt, sel, all, id):
419 options = {v["value"] for v in opt}
420 input_id = callback_context.triggered[0]["prop_id"].split(".")[0]
422 all = ["all"] if set(sel) == options else list()
424 sel = list(options) if all else list()
428 Output("cl-ctrl-core", "value"),
429 Output("cl-ctrl-core-all", "value"),
430 State("cl-ctrl-core", "options"),
431 Input("cl-ctrl-core", "value"),
432 Input("cl-ctrl-core-all", "value"),
433 prevent_initial_call=True
435 def _sync_cl_core(opt, sel, all):
436 return _sync_checklists(opt, sel, all, "cl-ctrl-core")
439 Output("cl-ctrl-framesize", "value"),
440 Output("cl-ctrl-framesize-all", "value"),
441 State("cl-ctrl-framesize", "options"),
442 Input("cl-ctrl-framesize", "value"),
443 Input("cl-ctrl-framesize-all", "value"),
444 prevent_initial_call=True
446 def _sync_cl_framesize(opt, sel, all):
447 return _sync_checklists(opt, sel, all, "cl-ctrl-framesize")
450 Output("cl-ctrl-testtype", "value"),
451 Output("cl-ctrl-testtype-all", "value"),
452 State("cl-ctrl-testtype", "options"),
453 Input("cl-ctrl-testtype", "value"),
454 Input("cl-ctrl-testtype-all", "value"),
455 prevent_initial_call=True
457 def _sync_cl_testtype(opt, sel, all):
458 return _sync_checklists(opt, sel, all, "cl-ctrl-testtype")
461 Output("graph", "figure"),
462 Output("selected-tests", "data"), # Store
463 Output("cl-selected", "options"), # User selection
464 Output("dd-ctrl-phy", "value"),
465 Output("dd-ctrl-area", "value"),
466 Output("dd-ctrl-test", "value"),
467 Output("div-plotting-area", "style"),
468 State("selected-tests", "data"), # Store
469 State("cl-selected", "value"),
470 State("dd-ctrl-phy", "value"),
471 State("dd-ctrl-area", "value"),
472 State("dd-ctrl-test", "value"),
473 State("cl-ctrl-core", "value"),
474 State("cl-ctrl-framesize", "value"),
475 State("cl-ctrl-testtype", "value"),
476 Input("btn-ctrl-add", "n_clicks"),
477 Input("btn-sel-display", "n_clicks"),
478 Input("btn-sel-remove", "n_clicks"),
479 Input("dpr-period", "start_date"),
480 Input("dpr-period", "end_date"),
481 prevent_initial_call=True
483 def _process_list(store_sel, list_sel, phy, area, test, cores,
484 framesizes, testtypes, btn_add, btn_display, btn_remove,
489 if not (btn_add or btn_display or btn_remove or d_start or d_end):
493 # Display selected tests with checkboxes:
496 {"label": v["id"], "value": v["id"]} for v in store_sel
501 trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
504 int(d_start[0:4]), int(d_start[5:7]), int(d_start[8:10])
507 int(d_end[0:4]), int(d_end[5:7]), int(d_end[8:10])
510 if trigger_id == "btn-ctrl-add":
511 # Add selected test to the list of tests in store:
512 if phy and area and test and cores and framesizes and testtypes:
514 # TODO: Add validation
516 if store_sel is None:
520 for framesize in framesizes:
521 for ttype in testtypes:
525 f"{framesize.lower()}-"
530 if tid not in [itm["id"] for itm in store_sel]:
536 "framesize": framesize.lower(),
537 "core": core.lower(),
538 "testtype": ttype.lower()
540 return (no_update, store_sel, _list_tests(), None,
541 None, None, no_update)
543 elif trigger_id in ("btn-sel-display", "dpr-period"):
544 fig, style = trending_tput(
545 self.data, store_sel, self.layout, d_start, d_end
547 return (fig, no_update, no_update,
548 no_update, no_update, no_update, style)
550 elif trigger_id == "btn-sel-remove":
552 new_store_sel = list()
553 for item in store_sel:
554 if item["id"] not in list_sel:
555 new_store_sel.append(item)
556 store_sel = new_store_sel
558 fig, style = trending_tput(
559 self.data, store_sel, self.layout, d_start, d_end
561 return (fig, store_sel, _list_tests(),
562 no_update, no_update, no_update, style)
565 "vertical-align": "top",
570 return (no_update, store_sel, _list_tests(),
571 no_update, no_update, no_update, style)