1 # Copyright (c) 2023 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 ast import literal_eval
30 from ..utils.constants import Constants as C
31 from ..utils.control_panel import ControlPanel
32 from ..utils.trigger import Trigger
33 from ..utils.utils import show_tooltip, label, sync_checklists, gen_new_url, \
34 generate_options, get_list_group_items, graph_hdrh_latency
35 from ..utils.url_processing import url_decode
36 from .graphs import graph_iterative, select_iterative_data
39 # Control panel partameters and their default values.
45 "dd-dutver-opt": list(),
46 "dd-dutver-dis": True,
47 "dd-dutver-val": str(),
51 "dd-area-opt": list(),
54 "dd-test-opt": list(),
57 "cl-core-opt": list(),
58 "cl-core-val": list(),
59 "cl-core-all-val": list(),
60 "cl-core-all-opt": C.CL_ALL_DISABLED,
61 "cl-frmsize-opt": list(),
62 "cl-frmsize-val": list(),
63 "cl-frmsize-all-val": list(),
64 "cl-frmsize-all-opt": C.CL_ALL_DISABLED,
65 "cl-tsttype-opt": list(),
66 "cl-tsttype-val": list(),
67 "cl-tsttype-all-val": list(),
68 "cl-tsttype-all-opt": C.CL_ALL_DISABLED,
70 "cl-normalize-val": list()
75 """The layout of the dash app and the callbacks.
81 data_iterative: pd.DataFrame,
82 html_layout_file: str,
83 graph_layout_file: str,
87 - save the input parameters,
88 - read and pre-process the data,
89 - prepare data for the control panel,
90 - read HTML layout file,
91 - read tooltips from the tooltip file.
93 :param app: Flask application running the dash application.
94 :param html_layout_file: Path and name of the file specifying the HTML
95 layout of the dash application.
96 :param graph_layout_file: Path and name of the file with layout of
98 :param tooltip_file: Path and name of the yaml file specifying the
101 :type html_layout_file: str
102 :type graph_layout_file: str
103 :type tooltip_file: str
108 self._html_layout_file = html_layout_file
109 self._graph_layout_file = graph_layout_file
110 self._tooltip_file = tooltip_file
111 self._data = data_iterative
113 # Get structure of tests:
116 "job", "test_id", "test_type", "dut_version", "tg_type", "release"
118 for _, row in self._data[cols].drop_duplicates().iterrows():
120 lst_job = row["job"].split("-")
122 d_ver = row["dut_version"]
123 tbed = "-".join(lst_job[-2:])
124 lst_test_id = row["test_id"].split(".")
128 area = ".".join(lst_test_id[3:-2])
129 suite = lst_test_id[-2].replace("2n1l-", "").replace("1n1l-", "").\
131 test = lst_test_id[-1]
132 nic = suite.split("-")[0]
133 for drv in C.DRIVERS:
135 driver = drv.replace("-", "_")
136 test = test.replace(f"{drv}-", "")
140 infra = "-".join((tbed, nic, driver))
141 lst_test = test.split("-")
142 framesize = lst_test[0]
143 core = lst_test[1] if lst_test[1] else "8C"
144 test = "-".join(lst_test[2: -1])
146 if tbs.get(rls, None) is None:
148 if tbs[rls].get(dut, None) is None:
149 tbs[rls][dut] = dict()
150 if tbs[rls][dut].get(d_ver, None) is None:
151 tbs[rls][dut][d_ver] = dict()
152 if tbs[rls][dut][d_ver].get(infra, None) is None:
153 tbs[rls][dut][d_ver][infra] = dict()
154 if tbs[rls][dut][d_ver][infra].get(area, None) is None:
155 tbs[rls][dut][d_ver][infra][area] = dict()
156 if tbs[rls][dut][d_ver][infra][area].get(test, None) is None:
157 tbs[rls][dut][d_ver][infra][area][test] = dict()
158 tbs[rls][dut][d_ver][infra][area][test]["core"] = list()
159 tbs[rls][dut][d_ver][infra][area][test]["frame-size"] = list()
160 tbs[rls][dut][d_ver][infra][area][test]["test-type"] = list()
161 if core.upper() not in \
162 tbs[rls][dut][d_ver][infra][area][test]["core"]:
163 tbs[rls][dut][d_ver][infra][area][test]["core"].append(
166 if framesize.upper() not in \
167 tbs[rls][dut][d_ver][infra][area][test]["frame-size"]:
168 tbs[rls][dut][d_ver][infra][area][test]["frame-size"].append(
171 if row["test_type"] == "mrr":
173 tbs[rls][dut][d_ver][infra][area][test]["test-type"]:
174 tbs[rls][dut][d_ver][infra][area][test]["test-type"].append(
177 elif row["test_type"] == "ndrpdr":
179 tbs[rls][dut][d_ver][infra][area][test]["test-type"]:
180 tbs[rls][dut][d_ver][infra][area][test]["test-type"].extend(
183 elif row["test_type"] == "hoststack" and \
184 row["tg_type"] in ("iperf", "vpp"):
186 tbs[rls][dut][d_ver][infra][area][test]["test-type"]:
187 tbs[rls][dut][d_ver][infra][area][test]["test-type"].append(
190 elif row["test_type"] == "hoststack" and row["tg_type"] == "ab":
192 tbs[rls][dut][d_ver][infra][area][test]["test-type"]:
193 tbs[rls][dut][d_ver][infra][area][test]["test-type"].extend(
199 self._html_layout = str()
200 self._graph_layout = None
201 self._tooltips = dict()
204 with open(self._html_layout_file, "r") as file_read:
205 self._html_layout = file_read.read()
206 except IOError as err:
208 f"Not possible to open the file {self._html_layout_file}\n{err}"
212 with open(self._graph_layout_file, "r") as file_read:
213 self._graph_layout = load(file_read, Loader=FullLoader)
214 except IOError as err:
216 f"Not possible to open the file {self._graph_layout_file}\n"
219 except YAMLError as err:
221 f"An error occurred while parsing the specification file "
222 f"{self._graph_layout_file}\n{err}"
226 with open(self._tooltip_file, "r") as file_read:
227 self._tooltips = load(file_read, Loader=FullLoader)
228 except IOError as err:
230 f"Not possible to open the file {self._tooltip_file}\n{err}"
232 except YAMLError as err:
234 f"An error occurred while parsing the specification file "
235 f"{self._tooltip_file}\n{err}"
239 if self._app is not None and hasattr(self, "callbacks"):
240 self.callbacks(self._app)
243 def html_layout(self):
244 return self._html_layout
246 def add_content(self):
247 """Top level method which generated the web page.
250 - Store for user input data,
252 - Main area with control panel and ploting area.
254 If no HTML layout is provided, an error message is displayed instead.
256 :returns: The HTML div with the whole page.
260 if self.html_layout and self._spec_tbs:
276 dcc.Store(id="store-selected-tests"),
277 dcc.Store(id="store-control-panel"),
278 dcc.Location(id="url", refresh=False),
279 self._add_ctrl_col(),
280 self._add_plotting_col()
286 id="offcanvas-metadata",
287 title="Throughput And Latency",
291 dbc.Row(id="metadata-tput-lat"),
292 dbc.Row(id="metadata-hdrh-graph")
295 delay_show=C.SPINNER_DELAY
299 id="offcanvas-documentation",
300 title="Documentation",
303 children=html.Iframe(
304 src=C.URL_DOC_REL_NOTES,
324 def _add_navbar(self):
325 """Add nav element with navigation panel. It is placed on the top.
327 :returns: Navigation bar.
328 :rtype: dbc.NavbarSimple
330 return dbc.NavbarSimple(
331 id="navbarsimple-main",
333 dbc.NavItem(dbc.NavLink(
339 dbc.NavItem(dbc.NavLink(
344 dbc.NavItem(dbc.NavLink(
349 dbc.NavItem(dbc.NavLink(
351 id="btn-documentation",
356 brand_external_link=True,
361 def _add_ctrl_col(self) -> dbc.Col:
362 """Add column with controls. It is placed on the left side.
364 :returns: Column with the control panel.
369 children=self._add_ctrl_panel(),
370 className="sticky-top"
374 def _add_plotting_col(self) -> dbc.Col:
375 """Add column with plots. It is placed on the right side.
377 :returns: Column with plots.
381 id="col-plotting-area",
387 class_name="g-0 p-0",
398 def _add_ctrl_panel(self) -> list:
399 """Add control panel.
401 :returns: Control panel.
406 class_name="g-0 p-1",
411 children=show_tooltip(
418 id={"type": "ctrl-dd", "index": "rls"},
419 placeholder="Select a Release...",
422 {"label": k, "value": k} \
423 for k in self._spec_tbs.keys()
425 key=lambda d: d["label"]
434 class_name="g-0 p-1",
439 children=show_tooltip(
446 id={"type": "ctrl-dd", "index": "dut"},
447 placeholder="Select a Device under Test..."
455 class_name="g-0 p-1",
460 children=show_tooltip(
467 id={"type": "ctrl-dd", "index": "dutver"},
469 "Select a Version of Device under Test..."
477 class_name="g-0 p-1",
482 children=show_tooltip(
489 id={"type": "ctrl-dd", "index": "phy"},
491 "Select a Physical Test Bed Topology..."
499 class_name="g-0 p-1",
504 children=show_tooltip(
511 id={"type": "ctrl-dd", "index": "area"},
512 placeholder="Select an Area..."
520 class_name="g-0 p-1",
525 children=show_tooltip(
532 id={"type": "ctrl-dd", "index": "test"},
533 placeholder="Select a Test..."
541 class_name="g-0 p-1",
546 children=show_tooltip(
557 "index": "frmsize-all"
559 options=C.CL_ALL_DISABLED,
578 style={"align-items": "center"},
584 class_name="g-0 p-1",
589 children=show_tooltip(
602 options=C.CL_ALL_DISABLED,
621 style={"align-items": "center"},
627 class_name="g-0 p-1",
632 children=show_tooltip(
643 "index": "tsttype-all"
645 options=C.CL_ALL_DISABLED,
664 style={"align-items": "center"},
670 class_name="g-0 p-1",
675 children=show_tooltip(
686 "value": "normalize",
688 "Normalize to CPU frequency "
699 style={"align-items": "center"},
705 class_name="g-0 p-1",
708 id={"type": "ctrl-btn", "index": "add-test"},
709 children="Add Selected",
715 id="row-card-sel-tests",
716 class_name="g-0 p-1",
717 style=C.STYLE_DISABLED,
720 class_name="overflow-auto p-0",
723 style={"max-height": "20em"},
729 id="row-btns-sel-tests",
730 class_name="g-0 p-1",
731 style=C.STYLE_DISABLED,
736 id={"type": "ctrl-btn", "index": "rm-test"},
737 children="Remove Selected",
743 id={"type": "ctrl-btn", "index": "rm-test-all"},
744 children="Remove All",
755 def _get_plotting_area(
761 """Generate the plotting area with all its content.
763 :param tests: List of tests to be displayed in the graphs.
764 :param normalize: If true, the values in graphs are normalized.
765 :param url: URL to be displayed in the modal window.
767 :type normalize: bool
769 :returns: List of rows with elements to be displayed in the plotting
776 figs = graph_iterative(self._data, tests, self._graph_layout, normalize)
784 id={"type": "graph", "index": "tput"},
787 class_name="g-0 p-1",
796 id={"type": "graph", "index": "lat"},
799 class_name="g-0 p-1",
807 class_name="g-0 p-0",
819 "text-transform": "none",
820 "padding": "0rem 1rem"
825 dbc.ModalHeader(dbc.ModalTitle("URL")),
834 id="plot-btn-download",
835 children="Download Data",
839 "text-transform": "none",
840 "padding": "0rem 1rem"
843 dcc.Download(id="download-iterative-data")
846 "d-grid gap-0 d-md-flex justify-content-md-end"
853 def callbacks(self, app):
854 """Callbacks for the whole application.
856 :param app: The application.
862 Output("store-control-panel", "data"),
863 Output("store-selected-tests", "data"),
864 Output("plotting-area", "children"),
865 Output("row-card-sel-tests", "style"),
866 Output("row-btns-sel-tests", "style"),
867 Output("lg-selected", "children"),
869 Output({"type": "ctrl-dd", "index": "rls"}, "value"),
870 Output({"type": "ctrl-dd", "index": "dut"}, "options"),
871 Output({"type": "ctrl-dd", "index": "dut"}, "disabled"),
872 Output({"type": "ctrl-dd", "index": "dut"}, "value"),
873 Output({"type": "ctrl-dd", "index": "dutver"}, "options"),
874 Output({"type": "ctrl-dd", "index": "dutver"}, "disabled"),
875 Output({"type": "ctrl-dd", "index": "dutver"}, "value"),
876 Output({"type": "ctrl-dd", "index": "phy"}, "options"),
877 Output({"type": "ctrl-dd", "index": "phy"}, "disabled"),
878 Output({"type": "ctrl-dd", "index": "phy"}, "value"),
879 Output({"type": "ctrl-dd", "index": "area"}, "options"),
880 Output({"type": "ctrl-dd", "index": "area"}, "disabled"),
881 Output({"type": "ctrl-dd", "index": "area"}, "value"),
882 Output({"type": "ctrl-dd", "index": "test"}, "options"),
883 Output({"type": "ctrl-dd", "index": "test"}, "disabled"),
884 Output({"type": "ctrl-dd", "index": "test"}, "value"),
885 Output({"type": "ctrl-cl", "index": "core"}, "options"),
886 Output({"type": "ctrl-cl", "index": "core"}, "value"),
887 Output({"type": "ctrl-cl", "index": "core-all"}, "value"),
888 Output({"type": "ctrl-cl", "index": "core-all"}, "options"),
889 Output({"type": "ctrl-cl", "index": "frmsize"}, "options"),
890 Output({"type": "ctrl-cl", "index": "frmsize"}, "value"),
891 Output({"type": "ctrl-cl", "index": "frmsize-all"}, "value"),
892 Output({"type": "ctrl-cl", "index": "frmsize-all"}, "options"),
893 Output({"type": "ctrl-cl", "index": "tsttype"}, "options"),
894 Output({"type": "ctrl-cl", "index": "tsttype"}, "value"),
895 Output({"type": "ctrl-cl", "index": "tsttype-all"}, "value"),
896 Output({"type": "ctrl-cl", "index": "tsttype-all"}, "options"),
897 Output({"type": "ctrl-btn", "index": "add-test"}, "disabled"),
898 Output("normalize", "value")
901 State("store-control-panel", "data"),
902 State("store-selected-tests", "data"),
903 State({"type": "sel-cl", "index": ALL}, "value")
906 Input("url", "href"),
907 Input("normalize", "value"),
909 Input({"type": "ctrl-dd", "index": ALL}, "value"),
910 Input({"type": "ctrl-cl", "index": ALL}, "value"),
911 Input({"type": "ctrl-btn", "index": ALL}, "n_clicks")
914 def _update_application(
922 """Update the application when the event is detected.
925 ctrl_panel = ControlPanel(CP_PARAMS, control_panel)
929 parsed_url = url_decode(href)
931 url_params = parsed_url["params"]
935 plotting_area = no_update
936 row_card_sel_tests = no_update
937 row_btns_sel_tests = no_update
938 lg_selected = no_update
940 trigger = Trigger(callback_context.triggered)
942 if trigger.type == "url" and url_params:
944 store_sel = literal_eval(url_params["store_sel"][0])
945 normalize = literal_eval(url_params["norm"][0])
946 except (KeyError, IndexError, AttributeError):
949 row_card_sel_tests = C.STYLE_ENABLED
950 row_btns_sel_tests = C.STYLE_ENABLED
951 last_test = store_sel[-1]
952 test = self._spec_tbs[last_test["rls"]][last_test["dut"]]\
953 [last_test["dutver"]][last_test["phy"]]\
954 [last_test["area"]][last_test["test"]]
956 "dd-rls-val": last_test["rls"],
957 "dd-dut-val": last_test["dut"],
958 "dd-dut-opt": generate_options(
959 self._spec_tbs[last_test["rls"]].keys()
962 "dd-dutver-val": last_test["dutver"],
963 "dd-dutver-opt": generate_options(
964 self._spec_tbs[last_test["rls"]]\
965 [last_test["dut"]].keys()
967 "dd-dutver-dis": False,
968 "dd-phy-val": last_test["phy"],
969 "dd-phy-opt": generate_options(
970 self._spec_tbs[last_test["rls"]][last_test["dut"]]\
971 [last_test["dutver"]].keys()
974 "dd-area-val": last_test["area"],
976 {"label": label(v), "value": v} for v in \
977 sorted(self._spec_tbs[last_test["rls"]]\
978 [last_test["dut"]][last_test["dutver"]]\
979 [last_test["phy"]].keys())
981 "dd-area-dis": False,
982 "dd-test-val": last_test["test"],
983 "dd-test-opt": generate_options(
984 self._spec_tbs[last_test["rls"]][last_test["dut"]]\
985 [last_test["dutver"]][last_test["phy"]]\
986 [last_test["area"]].keys()
988 "dd-test-dis": False,
989 "cl-core-opt": generate_options(test["core"]),
990 "cl-core-val": [last_test["core"].upper(), ],
991 "cl-core-all-val": list(),
992 "cl-core-all-opt": C.CL_ALL_ENABLED,
993 "cl-frmsize-opt": generate_options(test["frame-size"]),
994 "cl-frmsize-val": [last_test["framesize"].upper(), ],
995 "cl-frmsize-all-val": list(),
996 "cl-frmsize-all-opt": C.CL_ALL_ENABLED,
997 "cl-tsttype-opt": generate_options(test["test-type"]),
998 "cl-tsttype-val": [last_test["testtype"].upper(), ],
999 "cl-tsttype-all-val": list(),
1000 "cl-tsttype-all-opt": C.CL_ALL_ENABLED,
1001 "cl-normalize-val": normalize,
1002 "btn-add-dis": False
1005 elif trigger.type == "normalize":
1006 ctrl_panel.set({"cl-normalize-val": normalize})
1008 elif trigger.type == "ctrl-dd":
1009 if trigger.idx == "rls":
1011 options = generate_options(
1012 self._spec_tbs[trigger.value].keys()
1019 "dd-rls-val": trigger.value,
1020 "dd-dut-val": str(),
1021 "dd-dut-opt": options,
1022 "dd-dut-dis": disabled,
1023 "dd-dutver-val": str(),
1024 "dd-dutver-opt": list(),
1025 "dd-dutver-dis": True,
1026 "dd-phy-val": str(),
1027 "dd-phy-opt": list(),
1029 "dd-area-val": str(),
1030 "dd-area-opt": list(),
1031 "dd-area-dis": True,
1032 "dd-test-val": str(),
1033 "dd-test-opt": list(),
1034 "dd-test-dis": True,
1035 "cl-core-opt": list(),
1036 "cl-core-val": list(),
1037 "cl-core-all-val": list(),
1038 "cl-core-all-opt": C.CL_ALL_DISABLED,
1039 "cl-frmsize-opt": list(),
1040 "cl-frmsize-val": list(),
1041 "cl-frmsize-all-val": list(),
1042 "cl-frmsize-all-opt": C.CL_ALL_DISABLED,
1043 "cl-tsttype-opt": list(),
1044 "cl-tsttype-val": list(),
1045 "cl-tsttype-all-val": list(),
1046 "cl-tsttype-all-opt": C.CL_ALL_DISABLED,
1049 elif trigger.idx == "dut":
1051 rls = ctrl_panel.get("dd-rls-val")
1052 dut = self._spec_tbs[rls][trigger.value]
1053 options = generate_options(dut.keys())
1059 "dd-dut-val": trigger.value,
1060 "dd-dutver-val": str(),
1061 "dd-dutver-opt": options,
1062 "dd-dutver-dis": disabled,
1063 "dd-phy-val": str(),
1064 "dd-phy-opt": list(),
1066 "dd-area-val": str(),
1067 "dd-area-opt": list(),
1068 "dd-area-dis": True,
1069 "dd-test-val": str(),
1070 "dd-test-opt": list(),
1071 "dd-test-dis": True,
1072 "cl-core-opt": list(),
1073 "cl-core-val": list(),
1074 "cl-core-all-val": list(),
1075 "cl-core-all-opt": C.CL_ALL_DISABLED,
1076 "cl-frmsize-opt": list(),
1077 "cl-frmsize-val": list(),
1078 "cl-frmsize-all-val": list(),
1079 "cl-frmsize-all-opt": C.CL_ALL_DISABLED,
1080 "cl-tsttype-opt": list(),
1081 "cl-tsttype-val": list(),
1082 "cl-tsttype-all-val": list(),
1083 "cl-tsttype-all-opt": C.CL_ALL_DISABLED,
1086 elif trigger.idx == "dutver":
1088 rls = ctrl_panel.get("dd-rls-val")
1089 dut = ctrl_panel.get("dd-dut-val")
1090 dutver = self._spec_tbs[rls][dut][trigger.value]
1091 options = generate_options(dutver.keys())
1097 "dd-dutver-val": trigger.value,
1098 "dd-phy-val": str(),
1099 "dd-phy-opt": options,
1100 "dd-phy-dis": disabled,
1101 "dd-area-val": str(),
1102 "dd-area-opt": list(),
1103 "dd-area-dis": True,
1104 "dd-test-val": str(),
1105 "dd-test-opt": list(),
1106 "dd-test-dis": True,
1107 "cl-core-opt": list(),
1108 "cl-core-val": list(),
1109 "cl-core-all-val": list(),
1110 "cl-core-all-opt": C.CL_ALL_DISABLED,
1111 "cl-frmsize-opt": list(),
1112 "cl-frmsize-val": list(),
1113 "cl-frmsize-all-val": list(),
1114 "cl-frmsize-all-opt": C.CL_ALL_DISABLED,
1115 "cl-tsttype-opt": list(),
1116 "cl-tsttype-val": list(),
1117 "cl-tsttype-all-val": list(),
1118 "cl-tsttype-all-opt": C.CL_ALL_DISABLED,
1121 elif trigger.idx == "phy":
1123 rls = ctrl_panel.get("dd-rls-val")
1124 dut = ctrl_panel.get("dd-dut-val")
1125 dutver = ctrl_panel.get("dd-dutver-val")
1126 phy = self._spec_tbs[rls][dut][dutver][trigger.value]
1127 options = [{"label": label(v), "value": v} \
1128 for v in sorted(phy.keys())]
1134 "dd-phy-val": trigger.value,
1135 "dd-area-val": str(),
1136 "dd-area-opt": options,
1137 "dd-area-dis": disabled,
1138 "dd-test-val": str(),
1139 "dd-test-opt": list(),
1140 "dd-test-dis": True,
1141 "cl-core-opt": list(),
1142 "cl-core-val": list(),
1143 "cl-core-all-val": list(),
1144 "cl-core-all-opt": C.CL_ALL_DISABLED,
1145 "cl-frmsize-opt": list(),
1146 "cl-frmsize-val": list(),
1147 "cl-frmsize-all-val": list(),
1148 "cl-frmsize-all-opt": C.CL_ALL_DISABLED,
1149 "cl-tsttype-opt": list(),
1150 "cl-tsttype-val": list(),
1151 "cl-tsttype-all-val": list(),
1152 "cl-tsttype-all-opt": C.CL_ALL_DISABLED,
1155 elif trigger.idx == "area":
1157 rls = ctrl_panel.get("dd-rls-val")
1158 dut = ctrl_panel.get("dd-dut-val")
1159 dutver = ctrl_panel.get("dd-dutver-val")
1160 phy = ctrl_panel.get("dd-phy-val")
1162 self._spec_tbs[rls][dut][dutver][phy][trigger.value]
1163 options = generate_options(area.keys())
1169 "dd-area-val": trigger.value,
1170 "dd-test-val": str(),
1171 "dd-test-opt": options,
1172 "dd-test-dis": disabled,
1173 "cl-core-opt": list(),
1174 "cl-core-val": list(),
1175 "cl-core-all-val": list(),
1176 "cl-core-all-opt": C.CL_ALL_DISABLED,
1177 "cl-frmsize-opt": list(),
1178 "cl-frmsize-val": list(),
1179 "cl-frmsize-all-val": list(),
1180 "cl-frmsize-all-opt": C.CL_ALL_DISABLED,
1181 "cl-tsttype-opt": list(),
1182 "cl-tsttype-val": list(),
1183 "cl-tsttype-all-val": list(),
1184 "cl-tsttype-all-opt": C.CL_ALL_DISABLED,
1187 elif trigger.idx == "test":
1188 rls = ctrl_panel.get("dd-rls-val")
1189 dut = ctrl_panel.get("dd-dut-val")
1190 dutver = ctrl_panel.get("dd-dutver-val")
1191 phy = ctrl_panel.get("dd-phy-val")
1192 area = ctrl_panel.get("dd-area-val")
1193 if all((rls, dut, dutver, phy, area, trigger.value, )):
1194 test = self._spec_tbs[rls][dut][dutver][phy][area]\
1197 "dd-test-val": trigger.value,
1198 "cl-core-opt": generate_options(test["core"]),
1199 "cl-core-val": list(),
1200 "cl-core-all-val": list(),
1201 "cl-core-all-opt": C.CL_ALL_ENABLED,
1203 generate_options(test["frame-size"]),
1204 "cl-frmsize-val": list(),
1205 "cl-frmsize-all-val": list(),
1206 "cl-frmsize-all-opt": C.CL_ALL_ENABLED,
1208 generate_options(test["test-type"]),
1209 "cl-tsttype-val": list(),
1210 "cl-tsttype-all-val": list(),
1211 "cl-tsttype-all-opt": C.CL_ALL_ENABLED,
1214 elif trigger.type == "ctrl-cl":
1215 param = trigger.idx.split("-")[0]
1216 if "-all" in trigger.idx:
1217 c_sel, c_all, c_id = list(), trigger.value, "all"
1219 c_sel, c_all, c_id = trigger.value, list(), str()
1220 val_sel, val_all = sync_checklists(
1221 options=ctrl_panel.get(f"cl-{param}-opt"),
1227 f"cl-{param}-val": val_sel,
1228 f"cl-{param}-all-val": val_all,
1230 if all((ctrl_panel.get("cl-core-val"),
1231 ctrl_panel.get("cl-frmsize-val"),
1232 ctrl_panel.get("cl-tsttype-val"), )):
1233 ctrl_panel.set({"btn-add-dis": False})
1235 ctrl_panel.set({"btn-add-dis": True})
1236 elif trigger.type == "ctrl-btn":
1238 if trigger.idx == "add-test":
1239 rls = ctrl_panel.get("dd-rls-val")
1240 dut = ctrl_panel.get("dd-dut-val")
1241 dutver = ctrl_panel.get("dd-dutver-val")
1242 phy = ctrl_panel.get("dd-phy-val")
1243 area = ctrl_panel.get("dd-area-val")
1244 test = ctrl_panel.get("dd-test-val")
1245 # Add selected test to the list of tests in store:
1246 if store_sel is None:
1248 for core in ctrl_panel.get("cl-core-val"):
1249 for framesize in ctrl_panel.get("cl-frmsize-val"):
1250 for ttype in ctrl_panel.get("cl-tsttype-val"):
1257 phy.replace("af_xdp", "af-xdp"),
1264 if tid not in [i["id"] for i in store_sel]:
1273 "framesize": framesize.lower(),
1274 "core": core.lower(),
1275 "testtype": ttype.lower()
1277 store_sel = sorted(store_sel, key=lambda d: d["id"])
1278 if C.CLEAR_ALL_INPUTS:
1279 ctrl_panel.set(ctrl_panel.defaults)
1280 elif trigger.idx == "rm-test" and lst_sel:
1281 new_store_sel = list()
1282 for idx, item in enumerate(store_sel):
1283 if not lst_sel[idx]:
1284 new_store_sel.append(item)
1285 store_sel = new_store_sel
1286 elif trigger.idx == "rm-test-all":
1291 lg_selected = get_list_group_items(
1292 store_sel, "sel-cl", add_index=True
1294 plotting_area = self._get_plotting_area(
1299 {"store_sel": store_sel, "norm": normalize}
1302 row_card_sel_tests = C.STYLE_ENABLED
1303 row_btns_sel_tests = C.STYLE_ENABLED
1305 plotting_area = C.PLACEHOLDER
1306 row_card_sel_tests = C.STYLE_DISABLED
1307 row_btns_sel_tests = C.STYLE_DISABLED
1318 ret_val.extend(ctrl_panel.values)
1322 Output("plot-mod-url", "is_open"),
1323 [Input("plot-btn-url", "n_clicks")],
1324 [State("plot-mod-url", "is_open")],
1326 def toggle_plot_mod_url(n, is_open):
1327 """Toggle the modal window with url.
1334 Output("download-iterative-data", "data"),
1335 State("store-selected-tests", "data"),
1336 Input("plot-btn-download", "n_clicks"),
1337 prevent_initial_call=True
1339 def _download_iterative_data(store_sel, _):
1340 """Download the data
1342 :param store_sel: List of tests selected by user stored in the
1344 :type store_sel: list
1345 :returns: dict of data frame content (base64 encoded) and meta data
1346 used by the Download component.
1354 for itm in store_sel:
1355 sel_data = select_iterative_data(self._data, itm)
1356 if sel_data is None:
1358 df = pd.concat([df, sel_data], ignore_index=True)
1360 return dcc.send_data_frame(df.to_csv, C.REPORT_DOWNLOAD_FILE_NAME)
1363 Output("metadata-tput-lat", "children"),
1364 Output("metadata-hdrh-graph", "children"),
1365 Output("offcanvas-metadata", "is_open"),
1366 Input({"type": "graph", "index": ALL}, "clickData"),
1367 prevent_initial_call=True
1369 def _show_metadata_from_graphs(graph_data: dict) -> tuple:
1370 """Generates the data for the offcanvas displayed when a particular
1371 point in a graph is clicked on.
1373 :param graph_data: The data from the clicked point in the graph.
1374 :type graph_data: dict
1375 :returns: The data to be displayed on the offcanvas and the
1376 information to show the offcanvas.
1377 :rtype: tuple(list, list, bool)
1380 trigger = Trigger(callback_context.triggered)
1383 idx = 0 if trigger.idx == "tput" else 1
1384 graph_data = graph_data[idx]["points"]
1385 except (IndexError, KeyError, ValueError, TypeError):
1388 def _process_stats(data: list, param: str) -> list:
1389 """Process statistical data provided by plot.ly box graph.
1391 :param data: Statistical data provided by plot.ly box graph.
1392 :param param: Parameter saying if the data come from "tput" or
1396 :returns: Listo of tuples where the first value is the
1397 statistic's name and the secont one it's value.
1401 stats = ("max", "upper fence", "q3", "median", "q1",
1402 "lower fence", "min")
1403 elif len(data) == 9:
1404 stats = ("outlier", "max", "upper fence", "q3", "median",
1405 "q1", "lower fence", "min", "outlier")
1406 elif len(data) == 1:
1408 stats = ("Average Latency at 50% PDR", )
1410 stats = ("Throughput", )
1413 unit = " [us]" if param == "lat" else str()
1414 return [(f"{stat}{unit}", f"{value['y']:,.0f}")
1415 for stat, value in zip(stats, data)]
1418 if trigger.idx == "tput":
1419 title = "Throughput"
1420 elif trigger.idx == "lat":
1422 if len(graph_data) == 1:
1423 hdrh_data = graph_data[0].get("customdata", None)
1426 class_name="gy-2 p-0",
1428 dbc.CardHeader(hdrh_data.pop("name")),
1429 dbc.CardBody(children=[
1431 id="hdrh-latency-graph",
1432 figure=graph_hdrh_latency(
1433 hdrh_data, self._graph_layout
1443 class_name="gy-2 p-0",
1445 dbc.CardHeader(children=[
1447 target_id="tput-lat-metadata",
1449 style={"display": "inline-block"}
1454 id="tput-lat-metadata",
1456 children=[dbc.ListGroup(
1458 dbc.ListGroupItem([dbc.Badge(k), v])
1459 for k, v in _process_stats(
1460 graph_data, trigger.idx)
1469 return metadata, graph, True
1472 Output("offcanvas-documentation", "is_open"),
1473 Input("btn-documentation", "n_clicks"),
1474 State("offcanvas-documentation", "is_open")
1476 def toggle_offcanvas_documentation(n_clicks, is_open):