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.
19 import dash_bootstrap_components as dbc
21 from flask import Flask
24 from dash import callback_context, no_update, ALL
25 from dash import Input, Output, State
26 from dash.exceptions import PreventUpdate
27 from yaml import load, FullLoader, YAMLError
28 from datetime import datetime
29 from copy import deepcopy
30 from json import loads, JSONDecodeError
31 from ast import literal_eval
33 from ..utils.constants import Constants as C
34 from ..utils.utils import show_tooltip, label, sync_checklists, list_tests, \
35 gen_new_url, generate_options
36 from ..utils.url_processing import url_decode
37 from ..data.data import Data
38 from .graphs import graph_trending, graph_hdrh_latency, \
43 """The layout of the dash app and the callbacks.
46 def __init__(self, app: Flask, html_layout_file: str,
47 graph_layout_file: str, data_spec_file: str, tooltip_file: str,
48 time_period: str=None) -> None:
50 - save the input parameters,
51 - read and pre-process the data,
52 - prepare data for the control panel,
53 - read HTML layout file,
54 - read tooltips from the tooltip file.
56 :param app: Flask application running the dash application.
57 :param html_layout_file: Path and name of the file specifying the HTML
58 layout of the dash application.
59 :param graph_layout_file: Path and name of the file with layout of
61 :param data_spec_file: Path and name of the file specifying the data to
62 be read from parquets for this application.
63 :param tooltip_file: Path and name of the yaml file specifying the
65 :param time_period: It defines the time period for data read from the
66 parquets in days from now back to the past.
68 :type html_layout_file: str
69 :type graph_layout_file: str
70 :type data_spec_file: str
71 :type tooltip_file: str
72 :type time_period: int
77 self._html_layout_file = html_layout_file
78 self._graph_layout_file = graph_layout_file
79 self._data_spec_file = data_spec_file
80 self._tooltip_file = tooltip_file
81 self._time_period = time_period
85 data_spec_file=self._data_spec_file,
87 ).read_trending_mrr(days=self._time_period)
90 data_spec_file=self._data_spec_file,
92 ).read_trending_ndrpdr(days=self._time_period)
94 self._data = pd.concat([data_mrr, data_ndrpdr], ignore_index=True)
97 (datetime.utcnow() - self._data["start_time"].min()).days
98 if self._time_period > data_time_period:
99 self._time_period = data_time_period
102 # Get structure of tests:
104 for _, row in self._data[["job", "test_id"]].drop_duplicates().\
106 lst_job = row["job"].split("-")
109 tbed = "-".join(lst_job[-2:])
110 lst_test = row["test_id"].split(".")
114 area = "-".join(lst_test[3:-2])
115 suite = lst_test[-2].replace("2n1l-", "").replace("1n1l-", "").\
118 nic = suite.split("-")[0]
119 for drv in C.DRIVERS:
125 test = test.replace(f"{drv}-", "")
129 infra = "-".join((tbed, nic, driver))
130 lst_test = test.split("-")
131 framesize = lst_test[0]
132 core = lst_test[1] if lst_test[1] else "8C"
133 test = "-".join(lst_test[2: -1])
135 if tbs.get(dut, None) is None:
137 if tbs[dut].get(infra, None) is None:
138 tbs[dut][infra] = dict()
139 if tbs[dut][infra].get(area, None) is None:
140 tbs[dut][infra][area] = dict()
141 if tbs[dut][infra][area].get(test, None) is None:
142 tbs[dut][infra][area][test] = dict()
143 tbs[dut][infra][area][test]["core"] = list()
144 tbs[dut][infra][area][test]["frame-size"] = list()
145 tbs[dut][infra][area][test]["test-type"] = list()
146 if core.upper() not in tbs[dut][infra][area][test]["core"]:
147 tbs[dut][infra][area][test]["core"].append(core.upper())
148 if framesize.upper() not in \
149 tbs[dut][infra][area][test]["frame-size"]:
150 tbs[dut][infra][area][test]["frame-size"].append(
153 if "MRR" not in tbs[dut][infra][area][test]["test-type"]:
154 tbs[dut][infra][area][test]["test-type"].append("MRR")
155 elif ttype == "ndrpdr":
156 if "NDR" not in tbs[dut][infra][area][test]["test-type"]:
157 tbs[dut][infra][area][test]["test-type"].extend(
162 self._html_layout = ""
163 self._graph_layout = None
164 self._tooltips = dict()
167 with open(self._html_layout_file, "r") as file_read:
168 self._html_layout = file_read.read()
169 except IOError as err:
171 f"Not possible to open the file {self._html_layout_file}\n{err}"
175 with open(self._graph_layout_file, "r") as file_read:
176 self._graph_layout = load(file_read, Loader=FullLoader)
177 except IOError as err:
179 f"Not possible to open the file {self._graph_layout_file}\n"
182 except YAMLError as err:
184 f"An error occurred while parsing the specification file "
185 f"{self._graph_layout_file}\n{err}"
189 with open(self._tooltip_file, "r") as file_read:
190 self._tooltips = load(file_read, Loader=FullLoader)
191 except IOError as err:
193 f"Not possible to open the file {self._tooltip_file}\n{err}"
195 except YAMLError as err:
197 f"An error occurred while parsing the specification file "
198 f"{self._tooltip_file}\n{err}"
202 if self._app is not None and hasattr(self, 'callbacks'):
203 self.callbacks(self._app)
206 def html_layout(self):
207 return self._html_layout
211 return self._spec_tbs
219 return self._graph_layout
222 def time_period(self):
223 return self._time_period
225 def add_content(self):
226 """Top level method which generated the web page.
229 - Store for user input data,
231 - Main area with control panel and ploting area.
233 If no HTML layout is provided, an error message is displayed instead.
235 :returns: The HTML div with the whole page.
239 if self.html_layout and self.spec_tbs:
254 id="offcanvas-metadata",
255 title="Throughput And Latency",
259 dbc.Row(id="metadata-tput-lat"),
260 dbc.Row(id="metadata-hdrh-graph"),
268 dcc.Store(id="selected-tests"),
269 dcc.Store(id="control-panel"),
270 dcc.Location(id="url", refresh=False),
271 self._add_ctrl_col(),
272 self._add_plotting_col(),
290 def _add_navbar(self):
291 """Add nav element with navigation panel. It is placed on the top.
293 :returns: Navigation bar.
294 :rtype: dbc.NavbarSimple
296 return dbc.NavbarSimple(
297 id="navbarsimple-main",
301 "Continuous Performance Trending",
310 brand_external_link=True,
315 def _add_ctrl_col(self) -> dbc.Col:
316 """Add column with controls. It is placed on the left side.
318 :returns: Column with the control panel.
323 children=self._add_ctrl_panel(),
324 className="sticky-top"
328 def _add_plotting_col(self) -> dbc.Col:
329 """Add column with plots and tables. It is placed on the right side.
331 :returns: Column with tables.
335 id="col-plotting-area",
339 dbc.Row( # Throughput
341 class_name="g-0 p-2",
348 class_name="g-0 p-2",
354 id="row-btn-download",
355 class_name="g-0 p-2",
366 def _add_ctrl_panel(self) -> dbc.Row:
367 """Add control panel.
369 :returns: Control panel.
374 class_name="g-0 p-1",
379 children=show_tooltip(self._tooltips,
385 "Select a Device under Test..."
389 {"label": k, "value": k} \
390 for k in self.spec_tbs.keys()
392 key=lambda d: d["label"]
401 class_name="g-0 p-1",
406 children=show_tooltip(self._tooltips,
407 "help-infra", "Infra")
412 "Select a Physical Test Bed "
422 class_name="g-0 p-1",
427 children=show_tooltip(self._tooltips,
432 placeholder="Select an Area...",
441 class_name="g-0 p-1",
446 children=show_tooltip(self._tooltips,
451 placeholder="Select a Test...",
460 class_name="g-0 p-1",
463 children=show_tooltip(self._tooltips,
464 "help-framesize", "Frame Size"),
469 id="cl-ctrl-framesize-all",
470 options=C.CL_ALL_DISABLED,
480 id="cl-ctrl-framesize",
489 class_name="g-0 p-1",
492 children=show_tooltip(self._tooltips,
493 "help-cores", "Number of Cores"),
498 id="cl-ctrl-core-all",
499 options=C.CL_ALL_DISABLED,
518 class_name="g-0 p-1",
521 children=show_tooltip(self._tooltips,
522 "help-ttype", "Test Type"),
527 id="cl-ctrl-testtype-all",
528 options=C.CL_ALL_DISABLED,
538 id="cl-ctrl-testtype",
547 class_name="g-0 p-1",
550 children=show_tooltip(self._tooltips,
551 "help-normalize", "Normalize"),
556 id="cl-ctrl-normalize",
558 "value": "normalize",
560 "Normalize results to CPU "
573 class_name="g-0 p-1",
579 children="Add Selected",
587 id="row-card-sel-tests",
588 class_name="g-0 p-1",
589 style=C.STYLE_DISABLED,
591 dbc.Label("Selected tests"),
593 class_name="overflow-auto",
597 style={"max-height": "12em"},
602 id="row-btns-sel-tests",
603 class_name="g-0 p-1",
604 style=C.STYLE_DISABLED,
610 children="Remove Selected",
616 id="btn-sel-remove-all",
617 children="Remove All",
629 """A class representing the control panel.
632 def __init__(self, panel: dict) -> None:
633 """Initialisation of the control pannel by default values. If
634 particular values are provided (parameter "panel") they are set
637 :param panel: Custom values to be set to the control panel.
638 :param default: Default values to be set to the control panel.
643 # Defines also the order of keys
645 "dd-ctrl-dut-value": str(),
646 "dd-ctrl-phy-options": list(),
647 "dd-ctrl-phy-disabled": True,
648 "dd-ctrl-phy-value": str(),
649 "dd-ctrl-area-options": list(),
650 "dd-ctrl-area-disabled": True,
651 "dd-ctrl-area-value": str(),
652 "dd-ctrl-test-options": list(),
653 "dd-ctrl-test-disabled": True,
654 "dd-ctrl-test-value": str(),
655 "cl-ctrl-core-options": list(),
656 "cl-ctrl-core-value": list(),
657 "cl-ctrl-core-all-value": list(),
658 "cl-ctrl-core-all-options": C.CL_ALL_DISABLED,
659 "cl-ctrl-framesize-options": list(),
660 "cl-ctrl-framesize-value": list(),
661 "cl-ctrl-framesize-all-value": list(),
662 "cl-ctrl-framesize-all-options": C.CL_ALL_DISABLED,
663 "cl-ctrl-testtype-options": list(),
664 "cl-ctrl-testtype-value": list(),
665 "cl-ctrl-testtype-all-value": list(),
666 "cl-ctrl-testtype-all-options": C.CL_ALL_DISABLED,
667 "btn-ctrl-add-disabled": True,
668 "cl-normalize-value": list(),
669 "cl-selected-options": list()
672 self._panel = deepcopy(self._defaults)
674 for key in self._defaults:
675 self._panel[key] = panel[key]
678 def defaults(self) -> dict:
679 return self._defaults
682 def panel(self) -> dict:
685 def set(self, kwargs: dict) -> None:
686 """Set the values of the Control panel.
688 :param kwargs: key - value pairs to be set.
690 :raises KeyError: If the key in kwargs is not present in the Control
693 for key, val in kwargs.items():
694 if key in self._panel:
695 self._panel[key] = val
697 raise KeyError(f"The key {key} is not defined.")
699 def get(self, key: str) -> any:
700 """Returns the value of a key from the Control panel.
702 :param key: The key which value should be returned.
704 :returns: The value of the key.
706 :raises KeyError: If the key in kwargs is not present in the Control
709 return self._panel[key]
711 def values(self) -> tuple:
712 """Returns the values from the Control panel as a list.
714 :returns: The values from the Control panel.
717 return tuple(self._panel.values())
719 def callbacks(self, app):
720 """Callbacks for the whole application.
722 :param app: The application.
726 def _generate_plotting_area(figs: tuple, url: str) -> tuple:
727 """Generate the plotting area with all its content.
729 :param figs: Figures to be placed in the plotting area.
730 :param utl: The URL to be placed in the plotting area bellow the
732 :type figs: tuple of plotly.graph_objects.Figure
734 :returns: tuple of elements to be shown in the plotting area.
735 :rtype: tuple(dcc.Graph, dcc.Graph, list(dbc.Col, dbc.Col))
738 (fig_tput, fig_lat) = figs
740 row_fig_tput = C.PLACEHOLDER
741 row_fig_lat = C.PLACEHOLDER
742 row_btn_dwnld = C.PLACEHOLDER
747 id={"type": "graph", "index": "tput"},
755 dcc.Loading(children=[
757 id="btn-download-data",
758 children=show_tooltip(self._tooltips,
759 "help-download", "Download Data"),
763 dcc.Download(id="download-data")
775 children=show_tooltip(self._tooltips,
776 "help-url", "URL", "input-url")
793 id={"type": "graph", "index": "lat"},
798 return row_fig_tput, row_fig_lat, row_btn_dwnld
801 Output("control-panel", "data"), # Store
802 Output("selected-tests", "data"), # Store
803 Output("row-graph-tput", "children"),
804 Output("row-graph-lat", "children"),
805 Output("row-btn-download", "children"),
806 Output("row-card-sel-tests", "style"),
807 Output("row-btns-sel-tests", "style"),
808 Output("dd-ctrl-dut", "value"),
809 Output("dd-ctrl-phy", "options"),
810 Output("dd-ctrl-phy", "disabled"),
811 Output("dd-ctrl-phy", "value"),
812 Output("dd-ctrl-area", "options"),
813 Output("dd-ctrl-area", "disabled"),
814 Output("dd-ctrl-area", "value"),
815 Output("dd-ctrl-test", "options"),
816 Output("dd-ctrl-test", "disabled"),
817 Output("dd-ctrl-test", "value"),
818 Output("cl-ctrl-core", "options"),
819 Output("cl-ctrl-core", "value"),
820 Output("cl-ctrl-core-all", "value"),
821 Output("cl-ctrl-core-all", "options"),
822 Output("cl-ctrl-framesize", "options"),
823 Output("cl-ctrl-framesize", "value"),
824 Output("cl-ctrl-framesize-all", "value"),
825 Output("cl-ctrl-framesize-all", "options"),
826 Output("cl-ctrl-testtype", "options"),
827 Output("cl-ctrl-testtype", "value"),
828 Output("cl-ctrl-testtype-all", "value"),
829 Output("cl-ctrl-testtype-all", "options"),
830 Output("btn-ctrl-add", "disabled"),
831 Output("cl-ctrl-normalize", "value"),
832 Output("cl-selected", "options"), # User selection
833 State("control-panel", "data"), # Store
834 State("selected-tests", "data"), # Store
835 State("cl-selected", "value"), # User selection
836 Input("dd-ctrl-dut", "value"),
837 Input("dd-ctrl-phy", "value"),
838 Input("dd-ctrl-area", "value"),
839 Input("dd-ctrl-test", "value"),
840 Input("cl-ctrl-core", "value"),
841 Input("cl-ctrl-core-all", "value"),
842 Input("cl-ctrl-framesize", "value"),
843 Input("cl-ctrl-framesize-all", "value"),
844 Input("cl-ctrl-testtype", "value"),
845 Input("cl-ctrl-testtype-all", "value"),
846 Input("cl-ctrl-normalize", "value"),
847 Input("btn-ctrl-add", "n_clicks"),
848 Input("btn-sel-remove", "n_clicks"),
849 Input("btn-sel-remove-all", "n_clicks"),
852 def _update_ctrl_panel(cp_data: dict, store_sel: list, list_sel: list,
853 dd_dut: str, dd_phy: str, dd_area: str, dd_test: str, cl_core: list,
854 cl_core_all: list, cl_framesize: list, cl_framesize_all: list,
855 cl_testtype: list, cl_testtype_all: list, cl_normalize: list,
856 btn_add: int, btn_remove: int,
857 btn_remove_all: int, href: str) -> tuple:
858 """Update the application when the event is detected.
860 :param cp_data: Current status of the control panel stored in
862 :param store_sel: List of tests selected by user stored in the
864 :param list_sel: List of tests selected by the user shown in the
866 :param dd_dut: Input - DUTs.
867 :param dd_phy: Input - topo- arch-nic-driver.
868 :param dd_area: Input - Tested area.
869 :param dd_test: Input - Test.
870 :param cl_core: Input - Number of cores.
871 :param cl_core_all: Input - All numbers of cores.
872 :param cl_framesize: Input - Frame sizes.
873 :param cl_framesize_all: Input - All frame sizes.
874 :param cl_testtype: Input - Test type (NDR, PDR, MRR).
875 :param cl_testtype_all: Input - All test types.
876 :param cl_normalize: Input - Normalize the results.
877 :param btn_add: Input - Button "Add Selected" tests.
878 :param btn_remove: Input - Button "Remove selected" tests.
879 :param btn_remove_all: Input - Button "Remove All" tests.
880 :param href: Input - The URL provided by the browser.
882 :type store_sel: list
889 :type cl_core_all: list
890 :type cl_framesize: list
891 :type cl_framesize_all: list
892 :type cl_testtype: list
893 :type cl_testtype_all: list
894 :type cl_normalize: list
896 :type btn_remove: int
897 :type btn_remove_all: int
899 :returns: New values for web page elements.
903 ctrl_panel = self.ControlPanel(cp_data)
907 parsed_url = url_decode(href)
909 url_params = parsed_url["params"]
913 row_fig_tput = no_update
914 row_fig_lat = no_update
915 row_btn_dwnld = no_update
916 row_card_sel_tests = no_update
917 row_btns_sel_tests = no_update
919 trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
921 if trigger_id == "dd-ctrl-dut":
924 generate_options(sorted(self.spec_tbs[dd_dut].keys()))
930 "dd-ctrl-dut-value": dd_dut,
931 "dd-ctrl-phy-value": str(),
932 "dd-ctrl-phy-options": options,
933 "dd-ctrl-phy-disabled": disabled,
934 "dd-ctrl-area-value": str(),
935 "dd-ctrl-area-options": list(),
936 "dd-ctrl-area-disabled": True,
937 "dd-ctrl-test-value": str(),
938 "dd-ctrl-test-options": list(),
939 "dd-ctrl-test-disabled": True,
940 "cl-ctrl-core-options": list(),
941 "cl-ctrl-core-value": list(),
942 "cl-ctrl-core-all-value": list(),
943 "cl-ctrl-core-all-options": C.CL_ALL_DISABLED,
944 "cl-ctrl-framesize-options": list(),
945 "cl-ctrl-framesize-value": list(),
946 "cl-ctrl-framesize-all-value": list(),
947 "cl-ctrl-framesize-all-options": C.CL_ALL_DISABLED,
948 "cl-ctrl-testtype-options": list(),
949 "cl-ctrl-testtype-value": list(),
950 "cl-ctrl-testtype-all-value": list(),
951 "cl-ctrl-testtype-all-options": C.CL_ALL_DISABLED,
953 elif trigger_id == "dd-ctrl-phy":
955 dut = ctrl_panel.get("dd-ctrl-dut-value")
956 phy = self.spec_tbs[dut][dd_phy]
957 options = [{"label": label(v), "value": v} \
958 for v in sorted(phy.keys())]
964 "dd-ctrl-phy-value": dd_phy,
965 "dd-ctrl-area-value": str(),
966 "dd-ctrl-area-options": options,
967 "dd-ctrl-area-disabled": disabled,
968 "dd-ctrl-test-value": str(),
969 "dd-ctrl-test-options": list(),
970 "dd-ctrl-test-disabled": True,
971 "cl-ctrl-core-options": list(),
972 "cl-ctrl-core-value": list(),
973 "cl-ctrl-core-all-value": list(),
974 "cl-ctrl-core-all-options": C.CL_ALL_DISABLED,
975 "cl-ctrl-framesize-options": list(),
976 "cl-ctrl-framesize-value": list(),
977 "cl-ctrl-framesize-all-value": list(),
978 "cl-ctrl-framesize-all-options": C.CL_ALL_DISABLED,
979 "cl-ctrl-testtype-options": list(),
980 "cl-ctrl-testtype-value": list(),
981 "cl-ctrl-testtype-all-value": list(),
982 "cl-ctrl-testtype-all-options": C.CL_ALL_DISABLED,
984 elif trigger_id == "dd-ctrl-area":
986 dut = ctrl_panel.get("dd-ctrl-dut-value")
987 phy = ctrl_panel.get("dd-ctrl-phy-value")
988 area = self.spec_tbs[dut][phy][dd_area]
989 options = generate_options(sorted(area.keys()))
995 "dd-ctrl-area-value": dd_area,
996 "dd-ctrl-test-value": str(),
997 "dd-ctrl-test-options": options,
998 "dd-ctrl-test-disabled": disabled,
999 "cl-ctrl-core-options": list(),
1000 "cl-ctrl-core-value": list(),
1001 "cl-ctrl-core-all-value": list(),
1002 "cl-ctrl-core-all-options": C.CL_ALL_DISABLED,
1003 "cl-ctrl-framesize-options": list(),
1004 "cl-ctrl-framesize-value": list(),
1005 "cl-ctrl-framesize-all-value": list(),
1006 "cl-ctrl-framesize-all-options": C.CL_ALL_DISABLED,
1007 "cl-ctrl-testtype-options": list(),
1008 "cl-ctrl-testtype-value": list(),
1009 "cl-ctrl-testtype-all-value": list(),
1010 "cl-ctrl-testtype-all-options": C.CL_ALL_DISABLED,
1012 elif trigger_id == "dd-ctrl-test":
1013 dut = ctrl_panel.get("dd-ctrl-dut-value")
1014 phy = ctrl_panel.get("dd-ctrl-phy-value")
1015 area = ctrl_panel.get("dd-ctrl-area-value")
1016 if all((dut, phy, area, dd_test, )):
1017 test = self.spec_tbs[dut][phy][area][dd_test]
1019 "dd-ctrl-test-value": dd_test,
1020 "cl-ctrl-core-options": \
1021 generate_options(sorted(test["core"])),
1022 "cl-ctrl-core-value": list(),
1023 "cl-ctrl-core-all-value": list(),
1024 "cl-ctrl-core-all-options": C.CL_ALL_ENABLED,
1025 "cl-ctrl-framesize-options": \
1026 generate_options(sorted(test["frame-size"])),
1027 "cl-ctrl-framesize-value": list(),
1028 "cl-ctrl-framesize-all-value": list(),
1029 "cl-ctrl-framesize-all-options": C.CL_ALL_ENABLED,
1030 "cl-ctrl-testtype-options": \
1031 generate_options(sorted(test["test-type"])),
1032 "cl-ctrl-testtype-value": list(),
1033 "cl-ctrl-testtype-all-value": list(),
1034 "cl-ctrl-testtype-all-options": C.CL_ALL_ENABLED,
1036 elif trigger_id == "cl-ctrl-core":
1037 val_sel, val_all = sync_checklists(
1038 options=ctrl_panel.get("cl-ctrl-core-options"),
1044 "cl-ctrl-core-value": val_sel,
1045 "cl-ctrl-core-all-value": val_all,
1047 elif trigger_id == "cl-ctrl-core-all":
1048 val_sel, val_all = sync_checklists(
1049 options = ctrl_panel.get("cl-ctrl-core-options"),
1055 "cl-ctrl-core-value": val_sel,
1056 "cl-ctrl-core-all-value": val_all,
1058 elif trigger_id == "cl-ctrl-framesize":
1059 val_sel, val_all = sync_checklists(
1060 options = ctrl_panel.get("cl-ctrl-framesize-options"),
1066 "cl-ctrl-framesize-value": val_sel,
1067 "cl-ctrl-framesize-all-value": val_all,
1069 elif trigger_id == "cl-ctrl-framesize-all":
1070 val_sel, val_all = sync_checklists(
1071 options = ctrl_panel.get("cl-ctrl-framesize-options"),
1073 all=cl_framesize_all,
1077 "cl-ctrl-framesize-value": val_sel,
1078 "cl-ctrl-framesize-all-value": val_all,
1080 elif trigger_id == "cl-ctrl-testtype":
1081 val_sel, val_all = sync_checklists(
1082 options = ctrl_panel.get("cl-ctrl-testtype-options"),
1088 "cl-ctrl-testtype-value": val_sel,
1089 "cl-ctrl-testtype-all-value": val_all,
1091 elif trigger_id == "cl-ctrl-testtype-all":
1092 val_sel, val_all = sync_checklists(
1093 options = ctrl_panel.get("cl-ctrl-testtype-options"),
1095 all=cl_testtype_all,
1099 "cl-ctrl-testtype-value": val_sel,
1100 "cl-ctrl-testtype-all-value": val_all,
1102 elif trigger_id == "btn-ctrl-add":
1104 dut = ctrl_panel.get("dd-ctrl-dut-value")
1105 phy = ctrl_panel.get("dd-ctrl-phy-value")
1106 area = ctrl_panel.get("dd-ctrl-area-value")
1107 test = ctrl_panel.get("dd-ctrl-test-value")
1108 cores = ctrl_panel.get("cl-ctrl-core-value")
1109 framesizes = ctrl_panel.get("cl-ctrl-framesize-value")
1110 testtypes = ctrl_panel.get("cl-ctrl-testtype-value")
1111 # Add selected test to the list of tests in store:
1112 if all((dut, phy, area, test, cores, framesizes, testtypes)):
1113 if store_sel is None:
1116 for framesize in framesizes:
1117 for ttype in testtypes:
1121 dut, phy.replace('af_xdp', 'af-xdp'), area,
1122 framesize.lower(), core.lower(), test,
1125 if tid not in [itm["id"] for itm in store_sel]:
1132 "framesize": framesize.lower(),
1133 "core": core.lower(),
1134 "testtype": ttype.lower()
1136 store_sel = sorted(store_sel, key=lambda d: d["id"])
1137 row_card_sel_tests = C.STYLE_ENABLED
1138 row_btns_sel_tests = C.STYLE_ENABLED
1139 if C.CLEAR_ALL_INPUTS:
1140 ctrl_panel.set(ctrl_panel.defaults)
1141 elif trigger_id == "btn-sel-remove-all":
1143 row_fig_tput = C.PLACEHOLDER
1144 row_fig_lat = C.PLACEHOLDER
1145 row_btn_dwnld = C.PLACEHOLDER
1146 row_card_sel_tests = C.STYLE_DISABLED
1147 row_btns_sel_tests = C.STYLE_DISABLED
1149 ctrl_panel.set({"cl-selected-options": list()})
1150 elif trigger_id == "btn-sel-remove":
1153 new_store_sel = list()
1154 for item in store_sel:
1155 if item["id"] not in list_sel:
1156 new_store_sel.append(item)
1157 store_sel = new_store_sel
1158 elif trigger_id == "url":
1161 store_sel = literal_eval(url_params["store_sel"][0])
1162 norm = literal_eval(url_params["norm"][0])
1163 except (KeyError, IndexError):
1166 row_card_sel_tests = C.STYLE_ENABLED
1167 row_btns_sel_tests = C.STYLE_ENABLED
1168 last_test = store_sel[-1]
1169 test = self.spec_tbs[last_test["dut"]]\
1170 [last_test["phy"]][last_test["area"]]\
1173 "dd-ctrl-dut-value": last_test["dut"],
1174 "dd-ctrl-phy-value": last_test["phy"],
1175 "dd-ctrl-phy-options": generate_options(sorted(
1176 self.spec_tbs[last_test["dut"]].keys())),
1177 "dd-ctrl-phy-disabled": False,
1178 "dd-ctrl-area-value": last_test["area"],
1179 "dd-ctrl-area-options": [
1180 {"label": label(v), "value": v} \
1182 self.spec_tbs[last_test["dut"]]\
1183 [last_test["phy"]].keys())
1185 "dd-ctrl-area-disabled": False,
1186 "dd-ctrl-test-value": last_test["test"],
1187 "dd-ctrl-test-options": generate_options(sorted(
1188 self.spec_tbs[last_test["dut"]]\
1190 [last_test["area"]].keys())),
1191 "dd-ctrl-test-disabled": False,
1192 "cl-ctrl-core-options": generate_options(sorted(
1194 "cl-ctrl-core-value": [last_test["core"].upper(), ],
1195 "cl-ctrl-core-all-value": list(),
1196 "cl-ctrl-core-all-options": C.CL_ALL_ENABLED,
1197 "cl-ctrl-framesize-options": generate_options(
1198 sorted(test["frame-size"])),
1199 "cl-ctrl-framesize-value": \
1200 [last_test["framesize"].upper(), ],
1201 "cl-ctrl-framesize-all-value": list(),
1202 "cl-ctrl-framesize-all-options": C.CL_ALL_ENABLED,
1203 "cl-ctrl-testtype-options": generate_options(sorted(
1204 test["test-type"])),
1205 "cl-ctrl-testtype-value": \
1206 [last_test["testtype"].upper(), ],
1207 "cl-ctrl-testtype-all-value": list(),
1208 "cl-ctrl-testtype-all-options": C.CL_ALL_ENABLED
1211 if trigger_id in ("btn-ctrl-add", "url", "btn-sel-remove",
1212 "cl-ctrl-normalize"):
1214 row_fig_tput, row_fig_lat, row_btn_dwnld = \
1215 _generate_plotting_area(
1216 graph_trending(self.data, store_sel, self.layout,
1221 "store_sel": store_sel,
1227 "cl-selected-options": list_tests(store_sel)
1230 row_fig_tput = C.PLACEHOLDER
1231 row_fig_lat = C.PLACEHOLDER
1232 row_btn_dwnld = C.PLACEHOLDER
1233 row_card_sel_tests = C.STYLE_DISABLED
1234 row_btns_sel_tests = C.STYLE_DISABLED
1236 ctrl_panel.set({"cl-selected-options": list()})
1238 if ctrl_panel.get("cl-ctrl-core-value") and \
1239 ctrl_panel.get("cl-ctrl-framesize-value") and \
1240 ctrl_panel.get("cl-ctrl-testtype-value"):
1245 "btn-ctrl-add-disabled": disabled,
1246 "cl-normalize-value": norm
1250 ctrl_panel.panel, store_sel,
1251 row_fig_tput, row_fig_lat, row_btn_dwnld,
1252 row_card_sel_tests, row_btns_sel_tests
1254 ret_val.extend(ctrl_panel.values())
1258 Output("metadata-tput-lat", "children"),
1259 Output("metadata-hdrh-graph", "children"),
1260 Output("offcanvas-metadata", "is_open"),
1261 Input({"type": "graph", "index": ALL}, "clickData"),
1262 prevent_initial_call=True
1264 def _show_metadata_from_graphs(graph_data: dict) -> tuple:
1265 """Generates the data for the offcanvas displayed when a particular
1266 point in a graph is clicked on.
1268 :param graph_data: The data from the clicked point in the graph.
1269 :type graph_data: dict
1270 :returns: The data to be displayed on the offcanvas and the
1271 information to show the offcanvas.
1272 :rtype: tuple(list, list, bool)
1276 callback_context.triggered[0]["prop_id"].split(".")[0]
1278 idx = 0 if trigger_id == "tput" else 1
1279 graph_data = graph_data[idx]["points"][0]
1280 except (JSONDecodeError, IndexError, KeyError, ValueError,
1284 metadata = no_update
1289 [dbc.Badge(x.split(":")[0]), x.split(": ")[1]]
1290 ) for x in graph_data.get("text", "").split("<br>")
1292 if trigger_id == "tput":
1293 title = "Throughput"
1294 elif trigger_id == "lat":
1296 hdrh_data = graph_data.get("customdata", None)
1299 class_name="gy-2 p-0",
1301 dbc.CardHeader(hdrh_data.pop("name")),
1302 dbc.CardBody(children=[
1304 id="hdrh-latency-graph",
1305 figure=graph_hdrh_latency(
1306 hdrh_data, self.layout
1314 class_name="gy-2 p-0",
1316 dbc.CardHeader(children=[
1318 target_id="tput-lat-metadata",
1320 style={"display": "inline-block"}
1325 id="tput-lat-metadata",
1327 children=[dbc.ListGroup(children, flush=True), ]
1333 return metadata, graph, True
1336 Output("download-data", "data"),
1337 State("selected-tests", "data"),
1338 Input("btn-download-data", "n_clicks"),
1339 prevent_initial_call=True
1341 def _download_data(store_sel, n_clicks):
1342 """Download the data
1344 :param store_sel: List of tests selected by user stored in the
1346 :param n_clicks: Number of clicks on the button "Download".
1347 :type store_sel: list
1349 :returns: dict of data frame content (base64 encoded) and meta data
1350 used by the Download component.
1361 for itm in store_sel:
1362 sel_data = select_trending_data(self.data, itm)
1363 if sel_data is None:
1365 df = pd.concat([df, sel_data], ignore_index=True)
1367 return dcc.send_data_frame(df.to_csv, C.TREND_DOWNLOAD_FILE_NAME)