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, timedelta
29 from copy import deepcopy
30 from json import loads, JSONDecodeError
31 from ast import literal_eval
33 from ..data.data import Data
34 from ..data.url_processing import url_decode, url_encode
35 from .graphs import graph_trending, graph_hdrh_latency, \
43 # If True, clear all inputs in control panel when button "ADD SELECTED" is
45 CLEAR_ALL_INPUTS = False
47 STYLE_DISABLED = {"display": "none"}
48 STYLE_ENABLED = {"display": "inherit"}
61 PLACEHOLDER = html.Nobr("")
63 DRIVERS = ("avf", "af-xdp", "rdma", "dpdk")
67 "container_memif": "LXC/DRC Container Memif",
68 "crypto": "IPSec IPv4 Routing",
69 "ip4": "IPv4 Routing",
70 "ip6": "IPv6 Routing",
71 "ip4_tunnels": "IPv4 Tunnels",
72 "l2": "L2 Ethernet Switching",
73 "srv6": "SRv6 Routing",
74 "vm_vhost": "VMs vhost-user",
75 "nfv_density-dcr_memif-chain_ipsec": "CNF Service Chains Routing IPSec",
76 "nfv_density-vm_vhost-chain_dot1qip4vxlan":"VNF Service Chains Tunnels",
77 "nfv_density-vm_vhost-chain": "VNF Service Chains Routing",
78 "nfv_density-dcr_memif-pipeline": "CNF Service Pipelines Routing",
79 "nfv_density-dcr_memif-chain": "CNF Service Chains Routing",
83 "background-color": "#d2ebf5",
84 "border-color": "#bce1f1",
88 def __init__(self, app: Flask, html_layout_file: str,
89 graph_layout_file: str, data_spec_file: str, tooltip_file: str,
90 time_period: str=None) -> None:
96 self._html_layout_file = html_layout_file
97 self._graph_layout_file = graph_layout_file
98 self._data_spec_file = data_spec_file
99 self._tooltip_file = tooltip_file
100 self._time_period = time_period
104 data_spec_file=self._data_spec_file,
106 ).read_trending_mrr(days=self._time_period)
109 data_spec_file=self._data_spec_file,
111 ).read_trending_ndrpdr(days=self._time_period)
113 self._data = pd.concat([data_mrr, data_ndrpdr], ignore_index=True)
116 (datetime.utcnow() - self._data["start_time"].min()).days
117 if self._time_period > data_time_period:
118 self._time_period = data_time_period
121 # Get structure of tests:
123 for _, row in self._data[["job", "test_id"]].drop_duplicates().\
125 lst_job = row["job"].split("-")
128 tbed = "-".join(lst_job[-2:])
129 lst_test = row["test_id"].split(".")
133 area = "-".join(lst_test[3:-2])
134 suite = lst_test[-2].replace("2n1l-", "").replace("1n1l-", "").\
137 nic = suite.split("-")[0]
138 for drv in self.DRIVERS:
144 test = test.replace(f"{drv}-", "")
148 infra = "-".join((tbed, nic, driver))
149 lst_test = test.split("-")
150 framesize = lst_test[0]
151 core = lst_test[1] if lst_test[1] else "8C"
152 test = "-".join(lst_test[2: -1])
154 if tbs.get(dut, None) is None:
156 if tbs[dut].get(infra, None) is None:
157 tbs[dut][infra] = dict()
158 if tbs[dut][infra].get(area, None) is None:
159 tbs[dut][infra][area] = dict()
160 if tbs[dut][infra][area].get(test, None) is None:
161 tbs[dut][infra][area][test] = dict()
162 tbs[dut][infra][area][test]["core"] = list()
163 tbs[dut][infra][area][test]["frame-size"] = list()
164 tbs[dut][infra][area][test]["test-type"] = list()
165 if core.upper() not in tbs[dut][infra][area][test]["core"]:
166 tbs[dut][infra][area][test]["core"].append(core.upper())
167 if framesize.upper() not in \
168 tbs[dut][infra][area][test]["frame-size"]:
169 tbs[dut][infra][area][test]["frame-size"].append(
172 if "MRR" not in tbs[dut][infra][area][test]["test-type"]:
173 tbs[dut][infra][area][test]["test-type"].append("MRR")
174 elif ttype == "ndrpdr":
175 if "NDR" not in tbs[dut][infra][area][test]["test-type"]:
176 tbs[dut][infra][area][test]["test-type"].extend(
181 self._html_layout = ""
182 self._graph_layout = None
183 self._tooltips = dict()
186 with open(self._html_layout_file, "r") as file_read:
187 self._html_layout = file_read.read()
188 except IOError as err:
190 f"Not possible to open the file {self._html_layout_file}\n{err}"
194 with open(self._graph_layout_file, "r") as file_read:
195 self._graph_layout = load(file_read, Loader=FullLoader)
196 except IOError as err:
198 f"Not possible to open the file {self._graph_layout_file}\n"
201 except YAMLError as err:
203 f"An error occurred while parsing the specification file "
204 f"{self._graph_layout_file}\n{err}"
208 with open(self._tooltip_file, "r") as file_read:
209 self._tooltips = load(file_read, Loader=FullLoader)
210 except IOError as err:
212 f"Not possible to open the file {self._tooltip_file}\n{err}"
214 except YAMLError as err:
216 f"An error occurred while parsing the specification file "
217 f"{self._tooltip_file}\n{err}"
221 if self._app is not None and hasattr(self, 'callbacks'):
222 self.callbacks(self._app)
225 def html_layout(self):
226 return self._html_layout
230 return self._spec_tbs
238 return self._graph_layout
241 def time_period(self):
242 return self._time_period
244 def label(self, key: str) -> str:
245 return self.LABELS.get(key, key)
247 def _show_tooltip(self, id: str, title: str,
248 clipboard_id: str=None) -> list:
252 dcc.Clipboard(target_id=clipboard_id, title="Copy URL") \
253 if clipboard_id else str(),
261 class_name="border ms-1",
264 children=self._tooltips.get(id, str()),
270 def add_content(self):
273 if self.html_layout and self.spec_tbs:
287 id="offcanvas-metadata",
288 title="Throughput And Latency",
292 dbc.Row(id="metadata-tput-lat"),
293 dbc.Row(id="metadata-hdrh-graph"),
301 dcc.Store(id="selected-tests"),
302 dcc.Store(id="control-panel"),
303 dcc.Location(id="url", refresh=False),
304 self._add_ctrl_col(),
305 self._add_plotting_col(),
323 def _add_navbar(self):
324 """Add nav element with navigation panel. It is placed on the top.
326 return dbc.NavbarSimple(
327 id="navbarsimple-main",
331 "Continuous Performance Trending",
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.
351 self._add_ctrl_panel(),
355 def _add_plotting_col(self) -> dbc.Col:
356 """Add column with plots and tables. It is placed on the right side.
359 id="col-plotting-area",
363 dbc.Row( # Throughput
365 class_name="g-0 p-2",
372 class_name="g-0 p-2",
378 id="row-btn-download",
379 class_name="g-0 p-2",
390 def _add_ctrl_panel(self) -> dbc.Row:
395 class_name="g-0 p-2",
403 children=self._show_tooltip(
409 "Select a Device under Test..."
413 {"label": k, "value": k} \
414 for k in self.spec_tbs.keys()
416 key=lambda d: d["label"]
431 children=self._show_tooltip(
432 "help-infra", "Infra")
437 "Select a Physical Test Bed "
453 children=self._show_tooltip(
458 placeholder="Select an Area...",
473 children=self._show_tooltip(
478 placeholder="Select a Test...",
488 id="row-ctrl-framesize",
492 children=self._show_tooltip(
493 "help-framesize", "Frame Size"),
499 id="cl-ctrl-framesize-all",
500 options=self.CL_ALL_DISABLED,
510 id="cl-ctrl-framesize",
523 children=self._show_tooltip(
524 "help-cores", "Number of Cores"),
530 id="cl-ctrl-core-all",
531 options=self.CL_ALL_DISABLED,
550 id="row-ctrl-testtype",
554 children=self._show_tooltip(
555 "help-ttype", "Test Type"),
561 id="cl-ctrl-testtype-all",
562 options=self.CL_ALL_DISABLED,
572 id="cl-ctrl-testtype",
581 class_name="gy-1 p-0",
587 children="Add Selected",
601 children=self._show_tooltip(
602 "help-time-period", "Time Period"),
606 className="d-flex justify-content-center",
608 datetime.utcnow() - timedelta(
609 days=self.time_period),
610 max_date_allowed=datetime.utcnow(),
611 initial_visible_month=datetime.utcnow(),
613 datetime.utcnow() - timedelta(
614 days=self.time_period),
615 end_date=datetime.utcnow(),
616 display_format="D MMM YY"
621 id="row-card-sel-tests",
623 style=self.STYLE_DISABLED,
630 class_name="overflow-auto",
634 style={"max-height": "12em"},
639 id="row-btns-sel-tests",
640 style=self.STYLE_DISABLED,
647 children="Remove Selected",
648 class_name="w-100 me-1",
653 id="btn-sel-remove-all",
654 children="Remove All",
655 class_name="w-100 me-1",
668 def __init__(self, panel: dict) -> None:
676 # Defines also the order of keys
678 "dd-ctrl-dut-value": str(),
679 "dd-ctrl-phy-options": list(),
680 "dd-ctrl-phy-disabled": True,
681 "dd-ctrl-phy-value": str(),
682 "dd-ctrl-area-options": list(),
683 "dd-ctrl-area-disabled": True,
684 "dd-ctrl-area-value": str(),
685 "dd-ctrl-test-options": list(),
686 "dd-ctrl-test-disabled": True,
687 "dd-ctrl-test-value": str(),
688 "cl-ctrl-core-options": list(),
689 "cl-ctrl-core-value": list(),
690 "cl-ctrl-core-all-value": list(),
691 "cl-ctrl-core-all-options": CL_ALL_DISABLED,
692 "cl-ctrl-framesize-options": list(),
693 "cl-ctrl-framesize-value": list(),
694 "cl-ctrl-framesize-all-value": list(),
695 "cl-ctrl-framesize-all-options": CL_ALL_DISABLED,
696 "cl-ctrl-testtype-options": list(),
697 "cl-ctrl-testtype-value": list(),
698 "cl-ctrl-testtype-all-value": list(),
699 "cl-ctrl-testtype-all-options": CL_ALL_DISABLED,
700 "btn-ctrl-add-disabled": True,
701 "cl-selected-options": list(),
704 self._panel = deepcopy(self._defaults)
706 for key in self._defaults:
707 self._panel[key] = panel[key]
710 def defaults(self) -> dict:
711 return self._defaults
714 def panel(self) -> dict:
717 def set(self, kwargs: dict) -> None:
718 for key, val in kwargs.items():
719 if key in self._panel:
720 self._panel[key] = val
722 raise KeyError(f"The key {key} is not defined.")
724 def get(self, key: str) -> any:
725 return self._panel[key]
727 def values(self) -> tuple:
728 return tuple(self._panel.values())
731 def _sync_checklists(opt: list, sel: list, all: list, id: str) -> tuple:
734 options = {v["value"] for v in opt}
736 sel = list(options) if all else list()
738 all = ["all", ] if set(sel) == options else list()
742 def _list_tests(selection: dict) -> list:
743 """Display selected tests with checkboxes
746 return [{"label": v["id"], "value": v["id"]} for v in selection]
751 def _get_date(s_date: str) -> datetime:
752 return datetime(int(s_date[0:4]), int(s_date[5:7]), int(s_date[8:10]))
754 def callbacks(self, app):
756 def _generate_plotting_area(figs: tuple, url: str) -> tuple:
760 (fig_tput, fig_lat) = figs
762 row_fig_tput = self.PLACEHOLDER
763 row_fig_lat = self.PLACEHOLDER
764 row_btn_dwnld = self.PLACEHOLDER
769 id={"type": "graph", "index": "tput"},
777 dcc.Loading(children=[
779 id="btn-download-data",
780 children=self._show_tooltip(
781 "help-download", "Download Data"),
785 dcc.Download(id="download-data")
796 style=self.URL_STYLE,
797 children=self._show_tooltip(
798 "help-url", "URL", "input-url")
804 style=self.URL_STYLE,
815 id={"type": "graph", "index": "lat"},
820 return row_fig_tput, row_fig_lat, row_btn_dwnld
823 Output("control-panel", "data"), # Store
824 Output("selected-tests", "data"), # Store
825 Output("row-graph-tput", "children"),
826 Output("row-graph-lat", "children"),
827 Output("row-btn-download", "children"),
828 Output("row-card-sel-tests", "style"),
829 Output("row-btns-sel-tests", "style"),
830 Output("dd-ctrl-dut", "value"),
831 Output("dd-ctrl-phy", "options"),
832 Output("dd-ctrl-phy", "disabled"),
833 Output("dd-ctrl-phy", "value"),
834 Output("dd-ctrl-area", "options"),
835 Output("dd-ctrl-area", "disabled"),
836 Output("dd-ctrl-area", "value"),
837 Output("dd-ctrl-test", "options"),
838 Output("dd-ctrl-test", "disabled"),
839 Output("dd-ctrl-test", "value"),
840 Output("cl-ctrl-core", "options"),
841 Output("cl-ctrl-core", "value"),
842 Output("cl-ctrl-core-all", "value"),
843 Output("cl-ctrl-core-all", "options"),
844 Output("cl-ctrl-framesize", "options"),
845 Output("cl-ctrl-framesize", "value"),
846 Output("cl-ctrl-framesize-all", "value"),
847 Output("cl-ctrl-framesize-all", "options"),
848 Output("cl-ctrl-testtype", "options"),
849 Output("cl-ctrl-testtype", "value"),
850 Output("cl-ctrl-testtype-all", "value"),
851 Output("cl-ctrl-testtype-all", "options"),
852 Output("btn-ctrl-add", "disabled"),
853 Output("cl-selected", "options"), # User selection
854 State("control-panel", "data"), # Store
855 State("selected-tests", "data"), # Store
856 State("cl-selected", "value"), # User selection
857 Input("dd-ctrl-dut", "value"),
858 Input("dd-ctrl-phy", "value"),
859 Input("dd-ctrl-area", "value"),
860 Input("dd-ctrl-test", "value"),
861 Input("cl-ctrl-core", "value"),
862 Input("cl-ctrl-core-all", "value"),
863 Input("cl-ctrl-framesize", "value"),
864 Input("cl-ctrl-framesize-all", "value"),
865 Input("cl-ctrl-testtype", "value"),
866 Input("cl-ctrl-testtype-all", "value"),
867 Input("btn-ctrl-add", "n_clicks"),
868 Input("dpr-period", "start_date"),
869 Input("dpr-period", "end_date"),
870 Input("btn-sel-remove", "n_clicks"),
871 Input("btn-sel-remove-all", "n_clicks"),
874 def _update_ctrl_panel(cp_data: dict, store_sel: list, list_sel: list,
875 dd_dut: str, dd_phy: str, dd_area: str, dd_test: str, cl_core: list,
876 cl_core_all: list, cl_framesize: list, cl_framesize_all: list,
877 cl_testtype: list, cl_testtype_all: list, btn_add: int,
878 d_start: str, d_end: str, btn_remove: int,
879 btn_remove_all: int, href: str) -> tuple:
883 def _gen_new_url(parsed_url: dict, store_sel: list,
884 start: datetime, end: datetime) -> str:
887 new_url = url_encode({
888 "scheme": parsed_url["scheme"],
889 "netloc": parsed_url["netloc"],
890 "path": parsed_url["path"],
892 "store_sel": store_sel,
902 ctrl_panel = self.ControlPanel(cp_data)
904 d_start = self._get_date(d_start)
905 d_end = self._get_date(d_end)
908 parsed_url = url_decode(href)
910 row_fig_tput = no_update
911 row_fig_lat = no_update
912 row_btn_dwnld = no_update
913 row_card_sel_tests = no_update
914 row_btns_sel_tests = no_update
916 trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
918 if trigger_id == "dd-ctrl-dut":
920 dut = self.spec_tbs[dd_dut]
922 [{"label": v, "value": v}for v in dut.keys()],
923 key=lambda d: d["label"]
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": self.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": self.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": self.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]
958 [{"label": self.label(v), "value": v}
959 for v in phy.keys()],
960 key=lambda d: d["label"]
967 "dd-ctrl-phy-value": dd_phy,
968 "dd-ctrl-area-value": str(),
969 "dd-ctrl-area-options": options,
970 "dd-ctrl-area-disabled": disabled,
971 "dd-ctrl-test-value": str(),
972 "dd-ctrl-test-options": list(),
973 "dd-ctrl-test-disabled": True,
974 "cl-ctrl-core-options": list(),
975 "cl-ctrl-core-value": list(),
976 "cl-ctrl-core-all-value": list(),
977 "cl-ctrl-core-all-options": self.CL_ALL_DISABLED,
978 "cl-ctrl-framesize-options": list(),
979 "cl-ctrl-framesize-value": list(),
980 "cl-ctrl-framesize-all-value": list(),
981 "cl-ctrl-framesize-all-options": self.CL_ALL_DISABLED,
982 "cl-ctrl-testtype-options": list(),
983 "cl-ctrl-testtype-value": list(),
984 "cl-ctrl-testtype-all-value": list(),
985 "cl-ctrl-testtype-all-options": self.CL_ALL_DISABLED,
987 elif trigger_id == "dd-ctrl-area":
989 dut = ctrl_panel.get("dd-ctrl-dut-value")
990 phy = ctrl_panel.get("dd-ctrl-phy-value")
991 area = self.spec_tbs[dut][phy][dd_area]
993 [{"label": v, "value": v} for v in area.keys()],
994 key=lambda d: d["label"]
1001 "dd-ctrl-area-value": dd_area,
1002 "dd-ctrl-test-value": str(),
1003 "dd-ctrl-test-options": options,
1004 "dd-ctrl-test-disabled": disabled,
1005 "cl-ctrl-core-options": list(),
1006 "cl-ctrl-core-value": list(),
1007 "cl-ctrl-core-all-value": list(),
1008 "cl-ctrl-core-all-options": self.CL_ALL_DISABLED,
1009 "cl-ctrl-framesize-options": list(),
1010 "cl-ctrl-framesize-value": list(),
1011 "cl-ctrl-framesize-all-value": list(),
1012 "cl-ctrl-framesize-all-options": self.CL_ALL_DISABLED,
1013 "cl-ctrl-testtype-options": list(),
1014 "cl-ctrl-testtype-value": list(),
1015 "cl-ctrl-testtype-all-value": list(),
1016 "cl-ctrl-testtype-all-options": self.CL_ALL_DISABLED,
1018 elif trigger_id == "dd-ctrl-test":
1020 framesize_opts = list()
1021 testtype_opts = list()
1022 dut = ctrl_panel.get("dd-ctrl-dut-value")
1023 phy = ctrl_panel.get("dd-ctrl-phy-value")
1024 area = ctrl_panel.get("dd-ctrl-area-value")
1025 test = self.spec_tbs[dut][phy][area][dd_test]
1026 cores = test["core"]
1027 fsizes = test["frame-size"]
1028 ttypes = test["test-type"]
1029 if dut and phy and area and dd_test:
1030 core_opts = [{"label": v, "value": v}
1031 for v in sorted(cores)]
1032 framesize_opts = [{"label": v, "value": v}
1033 for v in sorted(fsizes)]
1034 testtype_opts = [{"label": v, "value": v}
1035 for v in sorted(ttypes)]
1037 "dd-ctrl-test-value": dd_test,
1038 "cl-ctrl-core-options": core_opts,
1039 "cl-ctrl-core-value": list(),
1040 "cl-ctrl-core-all-value": list(),
1041 "cl-ctrl-core-all-options": self.CL_ALL_ENABLED,
1042 "cl-ctrl-framesize-options": framesize_opts,
1043 "cl-ctrl-framesize-value": list(),
1044 "cl-ctrl-framesize-all-value": list(),
1045 "cl-ctrl-framesize-all-options": self.CL_ALL_ENABLED,
1046 "cl-ctrl-testtype-options": testtype_opts,
1047 "cl-ctrl-testtype-value": list(),
1048 "cl-ctrl-testtype-all-value": list(),
1049 "cl-ctrl-testtype-all-options": self.CL_ALL_ENABLED,
1051 elif trigger_id == "cl-ctrl-core":
1052 val_sel, val_all = self._sync_checklists(
1053 opt=ctrl_panel.get("cl-ctrl-core-options"),
1059 "cl-ctrl-core-value": val_sel,
1060 "cl-ctrl-core-all-value": val_all,
1062 elif trigger_id == "cl-ctrl-core-all":
1063 val_sel, val_all = self._sync_checklists(
1064 opt = ctrl_panel.get("cl-ctrl-core-options"),
1070 "cl-ctrl-core-value": val_sel,
1071 "cl-ctrl-core-all-value": val_all,
1073 elif trigger_id == "cl-ctrl-framesize":
1074 val_sel, val_all = self._sync_checklists(
1075 opt = ctrl_panel.get("cl-ctrl-framesize-options"),
1081 "cl-ctrl-framesize-value": val_sel,
1082 "cl-ctrl-framesize-all-value": val_all,
1084 elif trigger_id == "cl-ctrl-framesize-all":
1085 val_sel, val_all = self._sync_checklists(
1086 opt = ctrl_panel.get("cl-ctrl-framesize-options"),
1088 all=cl_framesize_all,
1092 "cl-ctrl-framesize-value": val_sel,
1093 "cl-ctrl-framesize-all-value": val_all,
1095 elif trigger_id == "cl-ctrl-testtype":
1096 val_sel, val_all = self._sync_checklists(
1097 opt = ctrl_panel.get("cl-ctrl-testtype-options"),
1103 "cl-ctrl-testtype-value": val_sel,
1104 "cl-ctrl-testtype-all-value": val_all,
1106 elif trigger_id == "cl-ctrl-testtype-all":
1107 val_sel, val_all = self._sync_checklists(
1108 opt = ctrl_panel.get("cl-ctrl-testtype-options"),
1110 all=cl_testtype_all,
1114 "cl-ctrl-testtype-value": val_sel,
1115 "cl-ctrl-testtype-all-value": val_all,
1117 elif trigger_id == "btn-ctrl-add":
1119 dut = ctrl_panel.get("dd-ctrl-dut-value")
1120 phy = ctrl_panel.get("dd-ctrl-phy-value")
1121 area = ctrl_panel.get("dd-ctrl-area-value")
1122 test = ctrl_panel.get("dd-ctrl-test-value")
1123 cores = ctrl_panel.get("cl-ctrl-core-value")
1124 framesizes = ctrl_panel.get("cl-ctrl-framesize-value")
1125 testtypes = ctrl_panel.get("cl-ctrl-testtype-value")
1126 # Add selected test to the list of tests in store:
1127 if all((dut, phy, area, test, cores, framesizes, testtypes)):
1128 if store_sel is None:
1131 for framesize in framesizes:
1132 for ttype in testtypes:
1136 dut, phy.replace('af_xdp', 'af-xdp'), area,
1137 framesize.lower(), core.lower(), test,
1140 if tid not in [itm["id"] for itm in store_sel]:
1147 "framesize": framesize.lower(),
1148 "core": core.lower(),
1149 "testtype": ttype.lower()
1151 store_sel = sorted(store_sel, key=lambda d: d["id"])
1152 row_card_sel_tests = self.STYLE_ENABLED
1153 row_btns_sel_tests = self.STYLE_ENABLED
1154 if self.CLEAR_ALL_INPUTS:
1155 ctrl_panel.set(ctrl_panel.defaults)
1157 "cl-selected-options": self._list_tests(store_sel)
1159 row_fig_tput, row_fig_lat, row_btn_dwnld = \
1160 _generate_plotting_area(
1161 graph_trending(self.data, store_sel, self.layout,
1163 _gen_new_url(parsed_url, store_sel, d_start, d_end)
1165 elif trigger_id == "dpr-period":
1166 row_fig_tput, row_fig_lat, row_btn_dwnld = \
1167 _generate_plotting_area(
1168 graph_trending(self.data, store_sel, self.layout,
1170 _gen_new_url(parsed_url, store_sel, d_start, d_end)
1172 elif trigger_id == "btn-sel-remove-all":
1174 row_fig_tput = self.PLACEHOLDER
1175 row_fig_lat = self.PLACEHOLDER
1176 row_btn_dwnld = self.PLACEHOLDER
1177 row_card_sel_tests = self.STYLE_DISABLED
1178 row_btns_sel_tests = self.STYLE_DISABLED
1180 ctrl_panel.set({"cl-selected-options": list()})
1181 elif trigger_id == "btn-sel-remove":
1184 new_store_sel = list()
1185 for item in store_sel:
1186 if item["id"] not in list_sel:
1187 new_store_sel.append(item)
1188 store_sel = new_store_sel
1190 row_fig_tput, row_fig_lat, row_btn_dwnld = \
1191 _generate_plotting_area(
1192 graph_trending(self.data, store_sel, self.layout,
1194 _gen_new_url(parsed_url, store_sel, d_start, d_end)
1197 "cl-selected-options": self._list_tests(store_sel)
1200 row_fig_tput = self.PLACEHOLDER
1201 row_fig_lat = self.PLACEHOLDER
1202 row_btn_dwnld = self.PLACEHOLDER
1203 row_card_sel_tests = self.STYLE_DISABLED
1204 row_btns_sel_tests = self.STYLE_DISABLED
1206 ctrl_panel.set({"cl-selected-options": list()})
1207 elif trigger_id == "url":
1208 # TODO: Add verification
1209 url_params = parsed_url["params"]
1211 store_sel = literal_eval(
1212 url_params.get("store_sel", list())[0])
1213 d_start = self._get_date(url_params.get("start", list())[0])
1214 d_end = self._get_date(url_params.get("end", list())[0])
1216 row_fig_tput, row_fig_lat, row_btn_dwnld = \
1217 _generate_plotting_area(
1219 self.data, store_sel, self.layout, d_start,
1223 parsed_url, store_sel, d_start, d_end
1226 row_card_sel_tests = self.STYLE_ENABLED
1227 row_btns_sel_tests = self.STYLE_ENABLED
1229 "cl-selected-options": self._list_tests(store_sel)
1232 row_fig_tput = self.PLACEHOLDER
1233 row_fig_lat = self.PLACEHOLDER
1234 row_btn_dwnld = self.PLACEHOLDER
1235 row_card_sel_tests = self.STYLE_DISABLED
1236 row_btns_sel_tests = self.STYLE_DISABLED
1239 "cl-selected-options": list()
1242 if ctrl_panel.get("cl-ctrl-core-value") and \
1243 ctrl_panel.get("cl-ctrl-framesize-value") and \
1244 ctrl_panel.get("cl-ctrl-testtype-value"):
1248 ctrl_panel.set({"btn-ctrl-add-disabled": disabled})
1251 ctrl_panel.panel, store_sel,
1252 row_fig_tput, row_fig_lat, row_btn_dwnld,
1253 row_card_sel_tests, row_btns_sel_tests
1255 ret_val.extend(ctrl_panel.values())
1259 Output("metadata-tput-lat", "children"),
1260 Output("metadata-hdrh-graph", "children"),
1261 Output("offcanvas-metadata", "is_open"),
1262 Input({"type": "graph", "index": ALL}, "clickData"),
1263 prevent_initial_call=True
1265 def _show_metadata_from_graphs(graph_data: dict) -> tuple:
1270 callback_context.triggered[0]["prop_id"].split(".")[0]
1272 idx = 0 if trigger_id == "tput" else 1
1273 graph_data = graph_data[idx]["points"][0]
1274 except (JSONDecodeError, IndexError, KeyError, ValueError,
1278 metadata = no_update
1283 [dbc.Badge(x.split(":")[0]), x.split(": ")[1]]
1284 ) for x in graph_data.get("text", "").split("<br>")
1286 if trigger_id == "tput":
1287 title = "Throughput"
1288 elif trigger_id == "lat":
1290 hdrh_data = graph_data.get("customdata", None)
1293 class_name="gy-2 p-0",
1295 dbc.CardHeader(hdrh_data.pop("name")),
1296 dbc.CardBody(children=[
1298 id="hdrh-latency-graph",
1299 figure=graph_hdrh_latency(
1300 hdrh_data, self.layout
1308 class_name="gy-2 p-0",
1310 dbc.CardHeader(children=[
1312 target_id="tput-lat-metadata",
1314 style={"display": "inline-block"}
1319 id="tput-lat-metadata",
1321 children=[dbc.ListGroup(children, flush=True), ]
1327 return metadata, graph, True
1330 Output("download-data", "data"),
1331 State("selected-tests", "data"),
1332 Input("btn-download-data", "n_clicks"),
1333 prevent_initial_call=True
1335 def _download_data(store_sel, n_clicks):
1346 for itm in store_sel:
1347 sel_data = select_trending_data(self.data, itm)
1348 if sel_data is None:
1350 df = pd.concat([df, sel_data], ignore_index=True)
1352 return dcc.send_data_frame(df.to_csv, "trending_data.csv")