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(area, None) is None:
153 tbs[rls][dut][d_ver][area] = dict()
154 if tbs[rls][dut][d_ver][area].get(test, None) is None:
155 tbs[rls][dut][d_ver][area][test] = dict()
156 if tbs[rls][dut][d_ver][area][test].get(infra, None) is None:
157 tbs[rls][dut][d_ver][area][test][infra] = {
159 "frame-size": list(),
162 tst_params = tbs[rls][dut][d_ver][area][test][infra]
163 if core.upper() not in tst_params["core"]:
164 tst_params["core"].append(core.upper())
165 if framesize.upper() not in tst_params["frame-size"]:
166 tst_params["frame-size"].append(framesize.upper())
167 if row["test_type"] == "mrr":
168 if "MRR" not in tst_params["test-type"]:
169 tst_params["test-type"].append("MRR")
170 elif row["test_type"] == "ndrpdr":
171 if "NDR" not in tst_params["test-type"]:
172 tst_params["test-type"].extend(("NDR", "PDR", ))
173 elif row["test_type"] == "hoststack" and \
174 row["tg_type"] in ("iperf", "vpp"):
175 if "BPS" not in tst_params["test-type"]:
176 tst_params["test-type"].append("BPS")
177 elif row["test_type"] == "hoststack" and row["tg_type"] == "ab":
178 if "CPS" not in tst_params["test-type"]:
179 tst_params["test-type"].extend(("CPS", "RPS"))
183 self._html_layout = str()
184 self._graph_layout = None
185 self._tooltips = dict()
188 with open(self._html_layout_file, "r") as file_read:
189 self._html_layout = file_read.read()
190 except IOError as err:
192 f"Not possible to open the file {self._html_layout_file}\n{err}"
196 with open(self._graph_layout_file, "r") as file_read:
197 self._graph_layout = load(file_read, Loader=FullLoader)
198 except IOError as err:
200 f"Not possible to open the file {self._graph_layout_file}\n"
203 except YAMLError as err:
205 f"An error occurred while parsing the specification file "
206 f"{self._graph_layout_file}\n{err}"
210 with open(self._tooltip_file, "r") as file_read:
211 self._tooltips = load(file_read, Loader=FullLoader)
212 except IOError as err:
214 f"Not possible to open the file {self._tooltip_file}\n{err}"
216 except YAMLError as err:
218 f"An error occurred while parsing the specification file "
219 f"{self._tooltip_file}\n{err}"
223 if self._app is not None and hasattr(self, "callbacks"):
224 self.callbacks(self._app)
227 def html_layout(self):
228 return self._html_layout
230 def add_content(self):
231 """Top level method which generated the web page.
234 - Store for user input data,
236 - Main area with control panel and ploting area.
238 If no HTML layout is provided, an error message is displayed instead.
240 :returns: The HTML div with the whole page.
244 if self.html_layout and self._spec_tbs:
260 dcc.Store(id="store-selected-tests"),
261 dcc.Store(id="store-control-panel"),
262 dcc.Location(id="url", refresh=False),
263 self._add_ctrl_col(),
264 self._add_plotting_col()
270 id="offcanvas-metadata",
271 title="Throughput And Latency",
275 dbc.Row(id="metadata-tput-lat"),
276 dbc.Row(id="metadata-hdrh-graph")
279 delay_show=C.SPINNER_DELAY
283 id="offcanvas-documentation",
284 title="Documentation",
287 children=html.Iframe(
288 src=C.URL_DOC_REL_NOTES,
308 def _add_navbar(self):
309 """Add nav element with navigation panel. It is placed on the top.
311 :returns: Navigation bar.
312 :rtype: dbc.NavbarSimple
314 return dbc.NavbarSimple(
315 id="navbarsimple-main",
317 dbc.NavItem(dbc.NavLink(
323 dbc.NavItem(dbc.NavLink(
328 dbc.NavItem(dbc.NavLink(
333 dbc.NavItem(dbc.NavLink(
335 id="btn-documentation",
340 brand_external_link=True,
345 def _add_ctrl_col(self) -> dbc.Col:
346 """Add column with controls. It is placed on the left side.
348 :returns: Column with the control panel.
353 children=self._add_ctrl_panel(),
354 className="sticky-top"
358 def _add_plotting_col(self) -> dbc.Col:
359 """Add column with plots. It is placed on the right side.
361 :returns: Column with plots.
365 id="col-plotting-area",
371 class_name="g-0 p-0",
382 def _add_ctrl_panel(self) -> list:
383 """Add control panel.
385 :returns: Control panel.
390 class_name="g-0 p-1",
395 children=show_tooltip(
402 id={"type": "ctrl-dd", "index": "rls"},
403 placeholder="Select a Release...",
406 {"label": k, "value": k} \
407 for k in self._spec_tbs.keys()
409 key=lambda d: d["label"]
418 class_name="g-0 p-1",
423 children=show_tooltip(
430 id={"type": "ctrl-dd", "index": "dut"},
431 placeholder="Select a Device under Test..."
439 class_name="g-0 p-1",
444 children=show_tooltip(
451 id={"type": "ctrl-dd", "index": "dutver"},
453 "Select a Version of Device under Test..."
461 class_name="g-0 p-1",
466 children=show_tooltip(
473 id={"type": "ctrl-dd", "index": "area"},
474 placeholder="Select an Area..."
482 class_name="g-0 p-1",
487 children=show_tooltip(
494 id={"type": "ctrl-dd", "index": "test"},
495 placeholder="Select a Test..."
503 class_name="g-0 p-1",
508 children=show_tooltip(
515 id={"type": "ctrl-dd", "index": "phy"},
517 "Select a Physical Test Bed Topology..."
525 class_name="g-0 p-1",
530 children=show_tooltip(
541 "index": "frmsize-all"
543 options=C.CL_ALL_DISABLED,
562 style={"align-items": "center"},
568 class_name="g-0 p-1",
573 children=show_tooltip(
586 options=C.CL_ALL_DISABLED,
605 style={"align-items": "center"},
611 class_name="g-0 p-1",
616 children=show_tooltip(
627 "index": "tsttype-all"
629 options=C.CL_ALL_DISABLED,
648 style={"align-items": "center"},
654 class_name="g-0 p-1",
659 children=show_tooltip(
670 "value": "normalize",
672 "Normalize to CPU frequency "
683 style={"align-items": "center"},
689 class_name="g-0 p-1",
692 id={"type": "ctrl-btn", "index": "add-test"},
693 children="Add Selected",
699 id="row-card-sel-tests",
700 class_name="g-0 p-1",
701 style=C.STYLE_DISABLED,
704 class_name="overflow-auto p-0",
707 style={"max-height": "20em"},
713 id="row-btns-sel-tests",
714 class_name="g-0 p-1",
715 style=C.STYLE_DISABLED,
720 id={"type": "ctrl-btn", "index": "rm-test"},
721 children="Remove Selected",
727 id={"type": "ctrl-btn", "index": "rm-test-all"},
728 children="Remove All",
739 def _get_plotting_area(
745 """Generate the plotting area with all its content.
747 :param tests: List of tests to be displayed in the graphs.
748 :param normalize: If true, the values in graphs are normalized.
749 :param url: URL to be displayed in the modal window.
751 :type normalize: bool
753 :returns: List of rows with elements to be displayed in the plotting
760 figs = graph_iterative(self._data, tests, self._graph_layout, normalize)
768 id={"type": "graph", "index": "tput"},
771 class_name="g-0 p-1",
780 id={"type": "graph", "index": "lat"},
783 class_name="g-0 p-1",
791 class_name="g-0 p-0",
803 "text-transform": "none",
804 "padding": "0rem 1rem"
809 dbc.ModalHeader(dbc.ModalTitle("URL")),
818 id="plot-btn-download",
819 children="Download Data",
823 "text-transform": "none",
824 "padding": "0rem 1rem"
827 dcc.Download(id="download-iterative-data")
830 "d-grid gap-0 d-md-flex justify-content-md-end"
837 def callbacks(self, app):
838 """Callbacks for the whole application.
840 :param app: The application.
846 Output("store-control-panel", "data"),
847 Output("store-selected-tests", "data"),
848 Output("plotting-area", "children"),
849 Output("row-card-sel-tests", "style"),
850 Output("row-btns-sel-tests", "style"),
851 Output("lg-selected", "children"),
853 Output({"type": "ctrl-dd", "index": "rls"}, "value"),
854 Output({"type": "ctrl-dd", "index": "dut"}, "options"),
855 Output({"type": "ctrl-dd", "index": "dut"}, "disabled"),
856 Output({"type": "ctrl-dd", "index": "dut"}, "value"),
857 Output({"type": "ctrl-dd", "index": "dutver"}, "options"),
858 Output({"type": "ctrl-dd", "index": "dutver"}, "disabled"),
859 Output({"type": "ctrl-dd", "index": "dutver"}, "value"),
860 Output({"type": "ctrl-dd", "index": "phy"}, "options"),
861 Output({"type": "ctrl-dd", "index": "phy"}, "disabled"),
862 Output({"type": "ctrl-dd", "index": "phy"}, "value"),
863 Output({"type": "ctrl-dd", "index": "area"}, "options"),
864 Output({"type": "ctrl-dd", "index": "area"}, "disabled"),
865 Output({"type": "ctrl-dd", "index": "area"}, "value"),
866 Output({"type": "ctrl-dd", "index": "test"}, "options"),
867 Output({"type": "ctrl-dd", "index": "test"}, "disabled"),
868 Output({"type": "ctrl-dd", "index": "test"}, "value"),
869 Output({"type": "ctrl-cl", "index": "core"}, "options"),
870 Output({"type": "ctrl-cl", "index": "core"}, "value"),
871 Output({"type": "ctrl-cl", "index": "core-all"}, "value"),
872 Output({"type": "ctrl-cl", "index": "core-all"}, "options"),
873 Output({"type": "ctrl-cl", "index": "frmsize"}, "options"),
874 Output({"type": "ctrl-cl", "index": "frmsize"}, "value"),
875 Output({"type": "ctrl-cl", "index": "frmsize-all"}, "value"),
876 Output({"type": "ctrl-cl", "index": "frmsize-all"}, "options"),
877 Output({"type": "ctrl-cl", "index": "tsttype"}, "options"),
878 Output({"type": "ctrl-cl", "index": "tsttype"}, "value"),
879 Output({"type": "ctrl-cl", "index": "tsttype-all"}, "value"),
880 Output({"type": "ctrl-cl", "index": "tsttype-all"}, "options"),
881 Output({"type": "ctrl-btn", "index": "add-test"}, "disabled"),
882 Output("normalize", "value")
885 State("store-control-panel", "data"),
886 State("store-selected-tests", "data"),
887 State({"type": "sel-cl", "index": ALL}, "value")
890 Input("url", "href"),
891 Input("normalize", "value"),
893 Input({"type": "ctrl-dd", "index": ALL}, "value"),
894 Input({"type": "ctrl-cl", "index": ALL}, "value"),
895 Input({"type": "ctrl-btn", "index": ALL}, "n_clicks")
898 def _update_application(
906 """Update the application when the event is detected.
909 ctrl_panel = ControlPanel(CP_PARAMS, control_panel)
913 parsed_url = url_decode(href)
915 url_params = parsed_url["params"]
919 plotting_area = no_update
920 row_card_sel_tests = no_update
921 row_btns_sel_tests = no_update
922 lg_selected = no_update
924 trigger = Trigger(callback_context.triggered)
926 if trigger.type == "url" and url_params:
928 store_sel = literal_eval(url_params["store_sel"][0])
929 normalize = literal_eval(url_params["norm"][0])
930 except (KeyError, IndexError, AttributeError):
933 row_card_sel_tests = C.STYLE_ENABLED
934 row_btns_sel_tests = C.STYLE_ENABLED
935 last_test = store_sel[-1]
936 test = self._spec_tbs[last_test["rls"]][last_test["dut"]]\
937 [last_test["dutver"]][last_test["area"]]\
938 [last_test["test"]][last_test["phy"]]
940 "dd-rls-val": last_test["rls"],
941 "dd-dut-val": last_test["dut"],
942 "dd-dut-opt": generate_options(
943 self._spec_tbs[last_test["rls"]].keys()
946 "dd-dutver-val": last_test["dutver"],
947 "dd-dutver-opt": generate_options(
948 self._spec_tbs[last_test["rls"]]\
949 [last_test["dut"]].keys()
951 "dd-dutver-dis": False,
952 "dd-area-val": last_test["area"],
954 {"label": label(v), "value": v} for v in \
955 sorted(self._spec_tbs[last_test["rls"]]\
957 [last_test["dutver"]].keys())
959 "dd-area-dis": False,
960 "dd-test-val": last_test["test"],
961 "dd-test-opt": generate_options(
962 self._spec_tbs[last_test["rls"]][last_test["dut"]]\
963 [last_test["dutver"]][last_test["area"]].keys()
965 "dd-test-dis": False,
966 "dd-phy-val": last_test["phy"],
967 "dd-phy-opt": generate_options(
968 self._spec_tbs[last_test["rls"]][last_test["dut"]]\
969 [last_test["dutver"]][last_test["area"]]\
970 [last_test["test"]].keys()
973 "cl-core-opt": generate_options(test["core"]),
974 "cl-core-val": [last_test["core"].upper(), ],
975 "cl-core-all-val": list(),
976 "cl-core-all-opt": C.CL_ALL_ENABLED,
977 "cl-frmsize-opt": generate_options(test["frame-size"]),
978 "cl-frmsize-val": [last_test["framesize"].upper(), ],
979 "cl-frmsize-all-val": list(),
980 "cl-frmsize-all-opt": C.CL_ALL_ENABLED,
981 "cl-tsttype-opt": generate_options(test["test-type"]),
982 "cl-tsttype-val": [last_test["testtype"].upper(), ],
983 "cl-tsttype-all-val": list(),
984 "cl-tsttype-all-opt": C.CL_ALL_ENABLED,
985 "cl-normalize-val": normalize,
989 elif trigger.type == "normalize":
990 ctrl_panel.set({"cl-normalize-val": normalize})
992 elif trigger.type == "ctrl-dd":
993 if trigger.idx == "rls":
995 options = generate_options(
996 self._spec_tbs[trigger.value].keys()
1003 "dd-rls-val": trigger.value,
1004 "dd-dut-val": str(),
1005 "dd-dut-opt": options,
1006 "dd-dut-dis": disabled,
1007 "dd-dutver-val": str(),
1008 "dd-dutver-opt": list(),
1009 "dd-dutver-dis": True,
1010 "dd-phy-val": str(),
1011 "dd-phy-opt": list(),
1013 "dd-area-val": str(),
1014 "dd-area-opt": list(),
1015 "dd-area-dis": True,
1016 "dd-test-val": str(),
1017 "dd-test-opt": list(),
1018 "dd-test-dis": True,
1019 "cl-core-opt": list(),
1020 "cl-core-val": list(),
1021 "cl-core-all-val": list(),
1022 "cl-core-all-opt": C.CL_ALL_DISABLED,
1023 "cl-frmsize-opt": list(),
1024 "cl-frmsize-val": list(),
1025 "cl-frmsize-all-val": list(),
1026 "cl-frmsize-all-opt": C.CL_ALL_DISABLED,
1027 "cl-tsttype-opt": list(),
1028 "cl-tsttype-val": list(),
1029 "cl-tsttype-all-val": list(),
1030 "cl-tsttype-all-opt": C.CL_ALL_DISABLED,
1033 elif trigger.idx == "dut":
1035 rls = ctrl_panel.get("dd-rls-val")
1036 dut = self._spec_tbs[rls][trigger.value]
1037 options = generate_options(dut.keys())
1043 "dd-dut-val": trigger.value,
1044 "dd-dutver-val": str(),
1045 "dd-dutver-opt": options,
1046 "dd-dutver-dis": disabled,
1047 "dd-phy-val": str(),
1048 "dd-phy-opt": list(),
1050 "dd-area-val": str(),
1051 "dd-area-opt": list(),
1052 "dd-area-dis": True,
1053 "dd-test-val": str(),
1054 "dd-test-opt": list(),
1055 "dd-test-dis": True,
1056 "cl-core-opt": list(),
1057 "cl-core-val": list(),
1058 "cl-core-all-val": list(),
1059 "cl-core-all-opt": C.CL_ALL_DISABLED,
1060 "cl-frmsize-opt": list(),
1061 "cl-frmsize-val": list(),
1062 "cl-frmsize-all-val": list(),
1063 "cl-frmsize-all-opt": C.CL_ALL_DISABLED,
1064 "cl-tsttype-opt": list(),
1065 "cl-tsttype-val": list(),
1066 "cl-tsttype-all-val": list(),
1067 "cl-tsttype-all-opt": C.CL_ALL_DISABLED,
1070 elif trigger.idx == "dutver":
1072 rls = ctrl_panel.get("dd-rls-val")
1073 dut = ctrl_panel.get("dd-dut-val")
1074 dutver = self._spec_tbs[rls][dut][trigger.value]
1075 options = [{"label": label(v), "value": v} \
1076 for v in sorted(dutver.keys())]
1082 "dd-dutver-val": trigger.value,
1083 "dd-area-val": str(),
1084 "dd-area-opt": options,
1085 "dd-area-dis": disabled,
1086 "dd-test-val": str(),
1087 "dd-test-opt": list(),
1088 "dd-test-dis": True,
1089 "dd-phy-val": str(),
1090 "dd-phy-opt": list(),
1092 "cl-core-opt": list(),
1093 "cl-core-val": list(),
1094 "cl-core-all-val": list(),
1095 "cl-core-all-opt": C.CL_ALL_DISABLED,
1096 "cl-frmsize-opt": list(),
1097 "cl-frmsize-val": list(),
1098 "cl-frmsize-all-val": list(),
1099 "cl-frmsize-all-opt": C.CL_ALL_DISABLED,
1100 "cl-tsttype-opt": list(),
1101 "cl-tsttype-val": list(),
1102 "cl-tsttype-all-val": list(),
1103 "cl-tsttype-all-opt": C.CL_ALL_DISABLED,
1106 elif trigger.idx == "area":
1108 rls = ctrl_panel.get("dd-rls-val")
1109 dut = ctrl_panel.get("dd-dut-val")
1110 dutver = ctrl_panel.get("dd-dutver-val")
1111 area = self._spec_tbs[rls][dut][dutver][trigger.value]
1112 options = generate_options(area.keys())
1118 "dd-area-val": trigger.value,
1119 "dd-test-val": str(),
1120 "dd-test-opt": options,
1121 "dd-test-dis": disabled,
1122 "dd-phy-val": str(),
1123 "dd-phy-opt": list(),
1125 "cl-core-opt": list(),
1126 "cl-core-val": list(),
1127 "cl-core-all-val": list(),
1128 "cl-core-all-opt": C.CL_ALL_DISABLED,
1129 "cl-frmsize-opt": list(),
1130 "cl-frmsize-val": list(),
1131 "cl-frmsize-all-val": list(),
1132 "cl-frmsize-all-opt": C.CL_ALL_DISABLED,
1133 "cl-tsttype-opt": list(),
1134 "cl-tsttype-val": list(),
1135 "cl-tsttype-all-val": list(),
1136 "cl-tsttype-all-opt": C.CL_ALL_DISABLED,
1139 elif trigger.idx == "test":
1141 rls = ctrl_panel.get("dd-rls-val")
1142 dut = ctrl_panel.get("dd-dut-val")
1143 dutver = ctrl_panel.get("dd-dutver-val")
1144 area = ctrl_panel.get("dd-area-val")
1145 test = self._spec_tbs[rls][dut][dutver][area]\
1147 options = generate_options(test.keys())
1153 "dd-test-val": trigger.value,
1154 "dd-phy-val": str(),
1155 "dd-phy-opt": options,
1156 "dd-phy-dis": disabled,
1157 "cl-core-opt": list(),
1158 "cl-core-val": list(),
1159 "cl-core-all-val": list(),
1160 "cl-core-all-opt": C.CL_ALL_DISABLED,
1161 "cl-frmsize-opt": list(),
1162 "cl-frmsize-val": list(),
1163 "cl-frmsize-all-val": list(),
1164 "cl-frmsize-all-opt": C.CL_ALL_DISABLED,
1165 "cl-tsttype-opt": list(),
1166 "cl-tsttype-val": list(),
1167 "cl-tsttype-all-val": list(),
1168 "cl-tsttype-all-opt": C.CL_ALL_DISABLED,
1171 elif trigger.idx == "phy":
1172 rls = ctrl_panel.get("dd-rls-val")
1173 dut = ctrl_panel.get("dd-dut-val")
1174 dutver = ctrl_panel.get("dd-dutver-val")
1175 area = ctrl_panel.get("dd-area-val")
1176 test = ctrl_panel.get("dd-test-val")
1177 if all((rls, dut, dutver, area, test, trigger.value, )):
1178 phy = self._spec_tbs[rls][dut][dutver][area][test]\
1181 "dd-phy-val": trigger.value,
1182 "cl-core-opt": generate_options(phy["core"]),
1183 "cl-core-val": list(),
1184 "cl-core-all-val": list(),
1185 "cl-core-all-opt": C.CL_ALL_ENABLED,
1187 generate_options(phy["frame-size"]),
1188 "cl-frmsize-val": list(),
1189 "cl-frmsize-all-val": list(),
1190 "cl-frmsize-all-opt": C.CL_ALL_ENABLED,
1192 generate_options(phy["test-type"]),
1193 "cl-tsttype-val": list(),
1194 "cl-tsttype-all-val": list(),
1195 "cl-tsttype-all-opt": C.CL_ALL_ENABLED,
1198 elif trigger.type == "ctrl-cl":
1199 param = trigger.idx.split("-")[0]
1200 if "-all" in trigger.idx:
1201 c_sel, c_all, c_id = list(), trigger.value, "all"
1203 c_sel, c_all, c_id = trigger.value, list(), str()
1204 val_sel, val_all = sync_checklists(
1205 options=ctrl_panel.get(f"cl-{param}-opt"),
1211 f"cl-{param}-val": val_sel,
1212 f"cl-{param}-all-val": val_all,
1214 if all((ctrl_panel.get("cl-core-val"),
1215 ctrl_panel.get("cl-frmsize-val"),
1216 ctrl_panel.get("cl-tsttype-val"), )):
1217 ctrl_panel.set({"btn-add-dis": False})
1219 ctrl_panel.set({"btn-add-dis": True})
1220 elif trigger.type == "ctrl-btn":
1222 if trigger.idx == "add-test":
1223 rls = ctrl_panel.get("dd-rls-val")
1224 dut = ctrl_panel.get("dd-dut-val")
1225 dutver = ctrl_panel.get("dd-dutver-val")
1226 phy = ctrl_panel.get("dd-phy-val")
1227 area = ctrl_panel.get("dd-area-val")
1228 test = ctrl_panel.get("dd-test-val")
1229 # Add selected test to the list of tests in store:
1230 if store_sel is None:
1232 for core in ctrl_panel.get("cl-core-val"):
1233 for framesize in ctrl_panel.get("cl-frmsize-val"):
1234 for ttype in ctrl_panel.get("cl-tsttype-val"):
1241 phy.replace("af_xdp", "af-xdp"),
1248 if tid not in [i["id"] for i in store_sel]:
1257 "framesize": framesize.lower(),
1258 "core": core.lower(),
1259 "testtype": ttype.lower()
1261 store_sel = sorted(store_sel, key=lambda d: d["id"])
1262 if C.CLEAR_ALL_INPUTS:
1263 ctrl_panel.set(ctrl_panel.defaults)
1264 elif trigger.idx == "rm-test" and lst_sel:
1265 new_store_sel = list()
1266 for idx, item in enumerate(store_sel):
1267 if not lst_sel[idx]:
1268 new_store_sel.append(item)
1269 store_sel = new_store_sel
1270 elif trigger.idx == "rm-test-all":
1275 lg_selected = get_list_group_items(
1276 store_sel, "sel-cl", add_index=True
1278 plotting_area = self._get_plotting_area(
1283 {"store_sel": store_sel, "norm": normalize}
1286 row_card_sel_tests = C.STYLE_ENABLED
1287 row_btns_sel_tests = C.STYLE_ENABLED
1289 plotting_area = C.PLACEHOLDER
1290 row_card_sel_tests = C.STYLE_DISABLED
1291 row_btns_sel_tests = C.STYLE_DISABLED
1302 ret_val.extend(ctrl_panel.values)
1306 Output("plot-mod-url", "is_open"),
1307 [Input("plot-btn-url", "n_clicks")],
1308 [State("plot-mod-url", "is_open")],
1310 def toggle_plot_mod_url(n, is_open):
1311 """Toggle the modal window with url.
1318 Output("download-iterative-data", "data"),
1319 State("store-selected-tests", "data"),
1320 Input("plot-btn-download", "n_clicks"),
1321 prevent_initial_call=True
1323 def _download_iterative_data(store_sel, _):
1324 """Download the data
1326 :param store_sel: List of tests selected by user stored in the
1328 :type store_sel: list
1329 :returns: dict of data frame content (base64 encoded) and meta data
1330 used by the Download component.
1338 for itm in store_sel:
1339 sel_data = select_iterative_data(self._data, itm)
1340 if sel_data is None:
1342 df = pd.concat([df, sel_data], ignore_index=True)
1344 return dcc.send_data_frame(df.to_csv, C.REPORT_DOWNLOAD_FILE_NAME)
1347 Output("metadata-tput-lat", "children"),
1348 Output("metadata-hdrh-graph", "children"),
1349 Output("offcanvas-metadata", "is_open"),
1350 Input({"type": "graph", "index": ALL}, "clickData"),
1351 prevent_initial_call=True
1353 def _show_metadata_from_graphs(graph_data: dict) -> tuple:
1354 """Generates the data for the offcanvas displayed when a particular
1355 point in a graph is clicked on.
1357 :param graph_data: The data from the clicked point in the graph.
1358 :type graph_data: dict
1359 :returns: The data to be displayed on the offcanvas and the
1360 information to show the offcanvas.
1361 :rtype: tuple(list, list, bool)
1364 trigger = Trigger(callback_context.triggered)
1367 idx = 0 if trigger.idx == "tput" else 1
1368 graph_data = graph_data[idx]["points"]
1369 except (IndexError, KeyError, ValueError, TypeError):
1372 def _process_stats(data: list, param: str) -> list:
1373 """Process statistical data provided by plot.ly box graph.
1375 :param data: Statistical data provided by plot.ly box graph.
1376 :param param: Parameter saying if the data come from "tput" or
1380 :returns: Listo of tuples where the first value is the
1381 statistic's name and the secont one it's value.
1385 stats = ("max", "upper fence", "q3", "median", "q1",
1386 "lower fence", "min")
1387 elif len(data) == 9:
1388 stats = ("outlier", "max", "upper fence", "q3", "median",
1389 "q1", "lower fence", "min", "outlier")
1390 elif len(data) == 1:
1392 stats = ("Average Latency at 50% PDR", )
1394 stats = ("Throughput", )
1397 unit = " [us]" if param == "lat" else str()
1398 return [(f"{stat}{unit}", f"{value['y']:,.0f}")
1399 for stat, value in zip(stats, data)]
1402 if trigger.idx == "tput":
1403 title = "Throughput"
1404 elif trigger.idx == "lat":
1406 if len(graph_data) == 1:
1407 hdrh_data = graph_data[0].get("customdata", None)
1409 name = hdrh_data.pop("name")
1411 class_name="gy-2 p-0",
1413 dbc.CardHeader(html.A(
1415 href=f"{C.URL_JENKINS}{name}",
1418 dbc.CardBody(dcc.Graph(
1419 id="hdrh-latency-graph",
1420 figure=graph_hdrh_latency(
1421 hdrh_data, self._graph_layout
1428 list_group_items = list()
1429 for k, v in _process_stats(graph_data, trigger.idx):
1430 list_group_items.append(dbc.ListGroupItem([dbc.Badge(k), v]))
1431 if trigger.idx == "tput" and len(list_group_items) == 1:
1432 job = graph_data[0].get("customdata", "")
1433 list_group_items.append(dbc.ListGroupItem([
1434 dbc.Badge("csit-ref"),
1435 html.A(job, href=f"{C.URL_JENKINS}{job}", target="_blank")
1439 class_name="gy-2 p-0",
1441 dbc.CardHeader(children=[
1443 target_id="tput-lat-metadata",
1445 style={"display": "inline-block"}
1450 dbc.ListGroup(list_group_items, flush=True),
1451 id="tput-lat-metadata",
1458 return metadata, graph, True
1461 Output("offcanvas-documentation", "is_open"),
1462 Input("btn-documentation", "n_clicks"),
1463 State("offcanvas-documentation", "is_open")
1465 def toggle_offcanvas_documentation(n_clicks, is_open):