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 .graphs import graph_trending, graph_hdrh_latency, \
36 from ..data.url_processing import url_decode, url_encode
43 STYLE_DISABLED = {"display": "none"}
44 STYLE_ENABLED = {"display": "inherit"}
57 PLACEHOLDER = html.Nobr("")
59 DRIVERS = ("avf", "af-xdp", "rdma", "dpdk")
63 "container_memif": "LXC/DRC Container Memif",
64 "crypto": "IPSec IPv4 Routing",
65 "ip4": "IPv4 Routing",
66 "ip6": "IPv6 Routing",
67 "ip4_tunnels": "IPv4 Tunnels",
68 "l2": "L2 Ethernet Switching",
69 "srv6": "SRv6 Routing",
70 "vm_vhost": "VMs vhost-user",
71 "nfv_density-dcr_memif-chain_ipsec": "CNF Service Chains Routing IPSec",
72 "nfv_density-vm_vhost-chain_dot1qip4vxlan":"VNF Service Chains Tunnels",
73 "nfv_density-vm_vhost-chain": "VNF Service Chains Routing",
74 "nfv_density-dcr_memif-pipeline": "CNF Service Pipelines Routing",
75 "nfv_density-dcr_memif-chain": "CNF Service Chains Routing",
78 def __init__(self, app: Flask, html_layout_file: str, spec_file: str,
79 graph_layout_file: str, data_spec_file: str, tooltip_file: str,
80 time_period: str=None) -> None:
86 self._html_layout_file = html_layout_file
87 self._spec_file = spec_file
88 self._graph_layout_file = graph_layout_file
89 self._data_spec_file = data_spec_file
90 self._tooltip_file = tooltip_file
91 self._time_period = time_period
95 data_spec_file=self._data_spec_file,
97 ).read_trending_mrr(days=self._time_period)
100 data_spec_file=self._data_spec_file,
102 ).read_trending_ndrpdr(days=self._time_period)
104 self._data = pd.concat([data_mrr, data_ndrpdr], ignore_index=True)
107 (datetime.utcnow() - self._data["start_time"].min()).days
108 if self._time_period > data_time_period:
109 self._time_period = data_time_period
112 # Get structure of tests:
114 for _, row in self._data[["job", "test_id"]].drop_duplicates().\
116 lst_job = row["job"].split("-")
119 tbed = "-".join(lst_job[-2:])
120 lst_test = row["test_id"].split(".")
124 area = "-".join(lst_test[3:-2])
125 suite = lst_test[-2].replace("2n1l-", "").replace("1n1l-", "").\
128 nic = suite.split("-")[0]
129 for drv in self.DRIVERS:
135 test = test.replace(f"{drv}-", "")
139 infra = "-".join((tbed, nic, driver))
140 lst_test = test.split("-")
141 framesize = lst_test[0]
142 core = lst_test[1] if lst_test[1] else "8C"
143 test = "-".join(lst_test[2: -1])
145 if tbs.get(dut, None) is None:
147 if tbs[dut].get(infra, None) is None:
148 tbs[dut][infra] = dict()
149 if tbs[dut][infra].get(area, None) is None:
150 tbs[dut][infra][area] = dict()
151 if tbs[dut][infra][area].get(test, None) is None:
152 tbs[dut][infra][area][test] = dict()
153 tbs[dut][infra][area][test]["core"] = list()
154 tbs[dut][infra][area][test]["frame-size"] = list()
155 tbs[dut][infra][area][test]["test-type"] = list()
156 if core.upper() not in tbs[dut][infra][area][test]["core"]:
157 tbs[dut][infra][area][test]["core"].append(core.upper())
158 if framesize.upper() not in \
159 tbs[dut][infra][area][test]["frame-size"]:
160 tbs[dut][infra][area][test]["frame-size"].append(
163 if "MRR" not in tbs[dut][infra][area][test]["test-type"]:
164 tbs[dut][infra][area][test]["test-type"].append("MRR")
165 elif ttype == "ndrpdr":
166 if "NDR" not in tbs[dut][infra][area][test]["test-type"]:
167 tbs[dut][infra][area][test]["test-type"].extend(
172 self._html_layout = ""
173 self._graph_layout = None
174 self._tooltips = dict()
177 with open(self._html_layout_file, "r") as file_read:
178 self._html_layout = file_read.read()
179 except IOError as err:
181 f"Not possible to open the file {self._html_layout_file}\n{err}"
185 with open(self._graph_layout_file, "r") as file_read:
186 self._graph_layout = load(file_read, Loader=FullLoader)
187 except IOError as err:
189 f"Not possible to open the file {self._graph_layout_file}\n"
192 except YAMLError as err:
194 f"An error occurred while parsing the specification file "
195 f"{self._graph_layout_file}\n{err}"
199 with open(self._tooltip_file, "r") as file_read:
200 self._tooltips = load(file_read, Loader=FullLoader)
201 except IOError as err:
203 f"Not possible to open the file {self._tooltip_file}\n{err}"
205 except YAMLError as err:
207 f"An error occurred while parsing the specification file "
208 f"{self._tooltip_file}\n{err}"
212 if self._app is not None and hasattr(self, 'callbacks'):
213 self.callbacks(self._app)
216 def html_layout(self):
217 return self._html_layout
221 return self._spec_tbs
229 return self._graph_layout
232 def time_period(self):
233 return self._time_period
235 def label(self, key: str) -> str:
236 return self.LABELS.get(key, key)
238 def _show_tooltip(self, id: str, title: str) -> list:
249 class_name="border ms-1",
252 children=self._tooltips.get(id, str()),
258 def add_content(self):
261 if self.html_layout and self.spec_tbs:
275 id="offcanvas-metadata",
276 title="Throughput And Latency",
280 dbc.Row(id="metadata-tput-lat"),
281 dbc.Row(id="metadata-hdrh-graph"),
289 dcc.Store(id="selected-tests"),
290 dcc.Store(id="control-panel"),
291 dcc.Location(id="url", refresh=False),
292 self._add_ctrl_col(),
293 self._add_plotting_col(),
311 def _add_navbar(self):
312 """Add nav element with navigation panel. It is placed on the top.
314 return dbc.NavbarSimple(
315 id="navbarsimple-main",
319 "Continuous Performance Trending",
328 brand_external_link=True,
333 def _add_ctrl_col(self) -> dbc.Col:
334 """Add column with controls. It is placed on the left side.
339 self._add_ctrl_panel(),
343 def _add_plotting_col(self) -> dbc.Col:
344 """Add column with plots and tables. It is placed on the right side.
347 id="col-plotting-area",
351 dbc.Row( # Throughput
353 class_name="g-0 p-2",
360 class_name="g-0 p-2",
366 id="row-btn-download",
367 class_name="g-0 p-2",
378 def _add_ctrl_panel(self) -> dbc.Row:
383 class_name="g-0 p-2",
391 children=self._show_tooltip(
397 "Select a Device under Test..."
401 {"label": k, "value": k} \
402 for k in self.spec_tbs.keys()
404 key=lambda d: d["label"]
419 children=self._show_tooltip(
420 "help-infra", "Infra")
425 "Select a Physical Test Bed "
441 children=self._show_tooltip(
446 placeholder="Select an Area...",
461 children=self._show_tooltip(
466 placeholder="Select a Test...",
476 id="row-ctrl-framesize",
480 children=self._show_tooltip(
481 "help-framesize", "Frame Size"),
487 id="cl-ctrl-framesize-all",
488 options=self.CL_ALL_DISABLED,
498 id="cl-ctrl-framesize",
511 children=self._show_tooltip(
512 "help-cores", "Number of Cores"),
518 id="cl-ctrl-core-all",
519 options=self.CL_ALL_DISABLED,
538 id="row-ctrl-testtype",
542 children=self._show_tooltip(
543 "help-ttype", "Test Type"),
549 id="cl-ctrl-testtype-all",
550 options=self.CL_ALL_DISABLED,
560 id="cl-ctrl-testtype",
569 class_name="gy-1 p-0",
575 children="Add Selected",
589 children=self._show_tooltip(
590 "help-time-period", "Time Period"),
594 className="d-flex justify-content-center",
596 datetime.utcnow() - timedelta(
597 days=self.time_period),
598 max_date_allowed=datetime.utcnow(),
599 initial_visible_month=datetime.utcnow(),
601 datetime.utcnow() - timedelta(
602 days=self.time_period),
603 end_date=datetime.utcnow(),
604 display_format="D MMM YY"
609 id="row-card-sel-tests",
611 style=self.STYLE_DISABLED,
618 class_name="overflow-auto",
622 style={"max-height": "12em"},
627 id="row-btns-sel-tests",
628 style=self.STYLE_DISABLED,
635 children="Remove Selected",
636 class_name="w-100 me-1",
641 id="btn-sel-remove-all",
642 children="Remove All",
643 class_name="w-100 me-1",
656 def __init__(self, panel: dict) -> None:
664 # Defines also the order of keys
666 "dd-ctrl-dut-value": str(),
667 "dd-ctrl-phy-options": list(),
668 "dd-ctrl-phy-disabled": True,
669 "dd-ctrl-phy-value": str(),
670 "dd-ctrl-area-options": list(),
671 "dd-ctrl-area-disabled": True,
672 "dd-ctrl-area-value": str(),
673 "dd-ctrl-test-options": list(),
674 "dd-ctrl-test-disabled": True,
675 "dd-ctrl-test-value": str(),
676 "cl-ctrl-core-options": list(),
677 "cl-ctrl-core-value": list(),
678 "cl-ctrl-core-all-value": list(),
679 "cl-ctrl-core-all-options": CL_ALL_DISABLED,
680 "cl-ctrl-framesize-options": list(),
681 "cl-ctrl-framesize-value": list(),
682 "cl-ctrl-framesize-all-value": list(),
683 "cl-ctrl-framesize-all-options": CL_ALL_DISABLED,
684 "cl-ctrl-testtype-options": list(),
685 "cl-ctrl-testtype-value": list(),
686 "cl-ctrl-testtype-all-value": list(),
687 "cl-ctrl-testtype-all-options": CL_ALL_DISABLED,
688 "btn-ctrl-add-disabled": True,
689 "cl-selected-options": list(),
692 self._panel = deepcopy(self._defaults)
694 for key in self._defaults:
695 self._panel[key] = panel[key]
698 def defaults(self) -> dict:
699 return self._defaults
702 def panel(self) -> dict:
705 def set(self, kwargs: dict) -> None:
706 for key, val in kwargs.items():
707 if key in self._panel:
708 self._panel[key] = val
710 raise KeyError(f"The key {key} is not defined.")
712 def get(self, key: str) -> any:
713 return self._panel[key]
715 def values(self) -> tuple:
716 return tuple(self._panel.values())
719 def _sync_checklists(opt: list, sel: list, all: list, id: str) -> tuple:
722 options = {v["value"] for v in opt}
724 sel = list(options) if all else list()
726 all = ["all", ] if set(sel) == options else list()
730 def _list_tests(selection: dict) -> list:
731 """Display selected tests with checkboxes
734 return [{"label": v["id"], "value": v["id"]} for v in selection]
739 def _get_date(s_date: str) -> datetime:
740 return datetime(int(s_date[0:4]), int(s_date[5:7]), int(s_date[8:10]))
742 def callbacks(self, app):
744 def _generate_plotting_area(figs: tuple, url: str) -> tuple:
748 (fig_tput, fig_lat) = figs
750 row_fig_tput = self.PLACEHOLDER
751 row_fig_lat = self.PLACEHOLDER
752 row_btn_dwnld = self.PLACEHOLDER
757 id={"type": "graph", "index": "tput"},
765 dcc.Loading(children=[
767 id="btn-download-data",
768 children=self._show_tooltip(
769 "help-download", "Download"),
773 dcc.Download(id="download-data")
783 class_name="gy-2 p-0",
786 target_id="card-url",
788 style={"display": "inline-block"}
799 id={"type": "graph", "index": "lat"},
804 return row_fig_tput, row_fig_lat, row_btn_dwnld
807 Output("control-panel", "data"), # Store
808 Output("selected-tests", "data"), # Store
809 Output("row-graph-tput", "children"),
810 Output("row-graph-lat", "children"),
811 Output("row-btn-download", "children"),
812 Output("row-card-sel-tests", "style"),
813 Output("row-btns-sel-tests", "style"),
814 Output("dd-ctrl-dut", "value"),
815 Output("dd-ctrl-phy", "options"),
816 Output("dd-ctrl-phy", "disabled"),
817 Output("dd-ctrl-phy", "value"),
818 Output("dd-ctrl-area", "options"),
819 Output("dd-ctrl-area", "disabled"),
820 Output("dd-ctrl-area", "value"),
821 Output("dd-ctrl-test", "options"),
822 Output("dd-ctrl-test", "disabled"),
823 Output("dd-ctrl-test", "value"),
824 Output("cl-ctrl-core", "options"),
825 Output("cl-ctrl-core", "value"),
826 Output("cl-ctrl-core-all", "value"),
827 Output("cl-ctrl-core-all", "options"),
828 Output("cl-ctrl-framesize", "options"),
829 Output("cl-ctrl-framesize", "value"),
830 Output("cl-ctrl-framesize-all", "value"),
831 Output("cl-ctrl-framesize-all", "options"),
832 Output("cl-ctrl-testtype", "options"),
833 Output("cl-ctrl-testtype", "value"),
834 Output("cl-ctrl-testtype-all", "value"),
835 Output("cl-ctrl-testtype-all", "options"),
836 Output("btn-ctrl-add", "disabled"),
837 Output("cl-selected", "options"), # User selection
838 State("control-panel", "data"), # Store
839 State("selected-tests", "data"), # Store
840 State("cl-selected", "value"), # User selection
841 Input("dd-ctrl-dut", "value"),
842 Input("dd-ctrl-phy", "value"),
843 Input("dd-ctrl-area", "value"),
844 Input("dd-ctrl-test", "value"),
845 Input("cl-ctrl-core", "value"),
846 Input("cl-ctrl-core-all", "value"),
847 Input("cl-ctrl-framesize", "value"),
848 Input("cl-ctrl-framesize-all", "value"),
849 Input("cl-ctrl-testtype", "value"),
850 Input("cl-ctrl-testtype-all", "value"),
851 Input("btn-ctrl-add", "n_clicks"),
852 Input("dpr-period", "start_date"),
853 Input("dpr-period", "end_date"),
854 Input("btn-sel-remove", "n_clicks"),
855 Input("btn-sel-remove-all", "n_clicks"),
858 def _update_ctrl_panel(cp_data: dict, store_sel: list, list_sel: list,
859 dd_dut: str, dd_phy: str, dd_area: str, dd_test: str, cl_core: list,
860 cl_core_all: list, cl_framesize: list, cl_framesize_all: list,
861 cl_testtype: list, cl_testtype_all: list, btn_add: int,
862 d_start: str, d_end: str, btn_remove: int,
863 btn_remove_all: int, href: str) -> tuple:
867 def _gen_new_url(parsed_url: dict, store_sel: list,
868 start: datetime, end: datetime) -> str:
871 new_url = url_encode({
872 "scheme": parsed_url["scheme"],
873 "netloc": parsed_url["netloc"],
874 "path": parsed_url["path"],
876 "store_sel": store_sel,
886 ctrl_panel = self.ControlPanel(cp_data)
888 d_start = self._get_date(d_start)
889 d_end = self._get_date(d_end)
892 parsed_url = url_decode(href)
894 row_fig_tput = no_update
895 row_fig_lat = no_update
896 row_btn_dwnld = no_update
897 row_card_sel_tests = no_update
898 row_btns_sel_tests = no_update
900 trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
902 if trigger_id == "dd-ctrl-dut":
906 {"label": v, "value": v}
907 for v in self.spec_tbs[dd_dut].keys()
909 key=lambda d: d["label"]
916 "dd-ctrl-dut-value": dd_dut,
917 "dd-ctrl-phy-value": str(),
918 "dd-ctrl-phy-options": options,
919 "dd-ctrl-phy-disabled": disabled,
920 "dd-ctrl-area-value": str(),
921 "dd-ctrl-area-options": list(),
922 "dd-ctrl-area-disabled": True,
923 "dd-ctrl-test-options": list(),
924 "dd-ctrl-test-disabled": True,
925 "cl-ctrl-core-options": list(),
926 "cl-ctrl-core-value": list(),
927 "cl-ctrl-core-all-value": list(),
928 "cl-ctrl-core-all-options": self.CL_ALL_DISABLED,
929 "cl-ctrl-framesize-options": list(),
930 "cl-ctrl-framesize-value": list(),
931 "cl-ctrl-framesize-all-value": list(),
932 "cl-ctrl-framesize-all-options": self.CL_ALL_DISABLED,
933 "cl-ctrl-testtype-options": list(),
934 "cl-ctrl-testtype-value": list(),
935 "cl-ctrl-testtype-all-value": list(),
936 "cl-ctrl-testtype-all-options": self.CL_ALL_DISABLED,
938 elif trigger_id == "dd-ctrl-phy":
940 dut = ctrl_panel.get("dd-ctrl-dut-value")
943 {"label": self.label(v), "value": v}
944 for v in self.spec_tbs[dut][dd_phy].keys()
946 key=lambda d: d["label"]
953 "dd-ctrl-phy-value": dd_phy,
954 "dd-ctrl-area-value": str(),
955 "dd-ctrl-area-options": options,
956 "dd-ctrl-area-disabled": disabled,
957 "dd-ctrl-test-options": list(),
958 "dd-ctrl-test-disabled": True,
959 "cl-ctrl-core-options": list(),
960 "cl-ctrl-core-value": list(),
961 "cl-ctrl-core-all-value": list(),
962 "cl-ctrl-core-all-options": self.CL_ALL_DISABLED,
963 "cl-ctrl-framesize-options": list(),
964 "cl-ctrl-framesize-value": list(),
965 "cl-ctrl-framesize-all-value": list(),
966 "cl-ctrl-framesize-all-options": self.CL_ALL_DISABLED,
967 "cl-ctrl-testtype-options": list(),
968 "cl-ctrl-testtype-value": list(),
969 "cl-ctrl-testtype-all-value": list(),
970 "cl-ctrl-testtype-all-options": self.CL_ALL_DISABLED,
972 elif trigger_id == "dd-ctrl-area":
974 dut = ctrl_panel.get("dd-ctrl-dut-value")
975 phy = ctrl_panel.get("dd-ctrl-phy-value")
978 {"label": v, "value": v}
979 for v in self.spec_tbs[dut][phy][dd_area].keys()
981 key=lambda d: d["label"]
988 "dd-ctrl-area-value": dd_area,
989 "dd-ctrl-test-value": str(),
990 "dd-ctrl-test-options": options,
991 "dd-ctrl-test-disabled": disabled,
992 "cl-ctrl-core-options": list(),
993 "cl-ctrl-core-value": list(),
994 "cl-ctrl-core-all-value": list(),
995 "cl-ctrl-core-all-options": self.CL_ALL_DISABLED,
996 "cl-ctrl-framesize-options": list(),
997 "cl-ctrl-framesize-value": list(),
998 "cl-ctrl-framesize-all-value": list(),
999 "cl-ctrl-framesize-all-options": self.CL_ALL_DISABLED,
1000 "cl-ctrl-testtype-options": list(),
1001 "cl-ctrl-testtype-value": list(),
1002 "cl-ctrl-testtype-all-value": list(),
1003 "cl-ctrl-testtype-all-options": self.CL_ALL_DISABLED,
1005 elif trigger_id == "dd-ctrl-test":
1007 framesize_opts = list()
1008 testtype_opts = list()
1009 dut = ctrl_panel.get("dd-ctrl-dut-value")
1010 phy = ctrl_panel.get("dd-ctrl-phy-value")
1011 area = ctrl_panel.get("dd-ctrl-area-value")
1012 cores = self.spec_tbs[dut][phy][area][dd_test]["core"]
1013 fsizes = self.spec_tbs[dut][phy][area][dd_test]["frame-size"]
1014 ttypes = self.spec_tbs[dut][phy][area][dd_test]["test-type"]
1015 if dut and phy and area and dd_test:
1017 {"label": v, "value": v} for v in sorted(cores)
1020 {"label": v, "value": v} for v in sorted(fsizes)
1023 {"label": v, "value": v}for v in sorted(ttypes)
1026 "dd-ctrl-test-value": dd_test,
1027 "cl-ctrl-core-options": core_opts,
1028 "cl-ctrl-core-value": list(),
1029 "cl-ctrl-core-all-value": list(),
1030 "cl-ctrl-core-all-options": self.CL_ALL_ENABLED,
1031 "cl-ctrl-framesize-options": framesize_opts,
1032 "cl-ctrl-framesize-value": list(),
1033 "cl-ctrl-framesize-all-value": list(),
1034 "cl-ctrl-framesize-all-options": self.CL_ALL_ENABLED,
1035 "cl-ctrl-testtype-options": testtype_opts,
1036 "cl-ctrl-testtype-value": list(),
1037 "cl-ctrl-testtype-all-value": list(),
1038 "cl-ctrl-testtype-all-options": self.CL_ALL_ENABLED,
1040 elif trigger_id == "cl-ctrl-core":
1041 val_sel, val_all = self._sync_checklists(
1042 opt=ctrl_panel.get("cl-ctrl-core-options"),
1048 "cl-ctrl-core-value": val_sel,
1049 "cl-ctrl-core-all-value": val_all,
1051 elif trigger_id == "cl-ctrl-core-all":
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-framesize":
1063 val_sel, val_all = self._sync_checklists(
1064 opt = ctrl_panel.get("cl-ctrl-framesize-options"),
1070 "cl-ctrl-framesize-value": val_sel,
1071 "cl-ctrl-framesize-all-value": val_all,
1073 elif trigger_id == "cl-ctrl-framesize-all":
1074 val_sel, val_all = self._sync_checklists(
1075 opt = ctrl_panel.get("cl-ctrl-framesize-options"),
1077 all=cl_framesize_all,
1081 "cl-ctrl-framesize-value": val_sel,
1082 "cl-ctrl-framesize-all-value": val_all,
1084 elif trigger_id == "cl-ctrl-testtype":
1085 val_sel, val_all = self._sync_checklists(
1086 opt = ctrl_panel.get("cl-ctrl-testtype-options"),
1092 "cl-ctrl-testtype-value": val_sel,
1093 "cl-ctrl-testtype-all-value": val_all,
1095 elif trigger_id == "cl-ctrl-testtype-all":
1096 val_sel, val_all = self._sync_checklists(
1097 opt = ctrl_panel.get("cl-ctrl-testtype-options"),
1099 all=cl_testtype_all,
1103 "cl-ctrl-testtype-value": val_sel,
1104 "cl-ctrl-testtype-all-value": val_all,
1106 elif trigger_id == "btn-ctrl-add":
1108 dut = ctrl_panel.get("dd-ctrl-dut-value")
1109 phy = ctrl_panel.get("dd-ctrl-phy-value")
1110 area = ctrl_panel.get("dd-ctrl-area-value")
1111 test = ctrl_panel.get("dd-ctrl-test-value")
1112 cores = ctrl_panel.get("cl-ctrl-core-value")
1113 framesizes = ctrl_panel.get("cl-ctrl-framesize-value")
1114 testtypes = ctrl_panel.get("cl-ctrl-testtype-value")
1115 # Add selected test to the list of tests in store:
1116 if all((dut, phy, area, test, cores, framesizes, testtypes)):
1117 if store_sel is None:
1120 for framesize in framesizes:
1121 for ttype in testtypes:
1125 dut, phy.replace('af_xdp', 'af-xdp'), area,
1126 framesize.lower(), core.lower(), test,
1129 if tid not in [itm["id"] for itm in store_sel]:
1136 "framesize": framesize.lower(),
1137 "core": core.lower(),
1138 "testtype": ttype.lower()
1140 store_sel = sorted(store_sel, key=lambda d: d["id"])
1141 row_card_sel_tests = self.STYLE_ENABLED
1142 row_btns_sel_tests = self.STYLE_ENABLED
1143 ctrl_panel.set(ctrl_panel.defaults)
1145 "cl-selected-options": self._list_tests(store_sel)
1147 row_fig_tput, row_fig_lat, row_btn_dwnld = \
1148 _generate_plotting_area(
1150 self.data, store_sel, self.layout, d_start,
1153 _gen_new_url(parsed_url, store_sel, d_start, d_end)
1155 elif trigger_id == "dpr-period":
1156 row_fig_tput, row_fig_lat, row_btn_dwnld = \
1157 _generate_plotting_area(
1159 self.data, store_sel, self.layout, d_start, d_end
1161 _gen_new_url(parsed_url, store_sel, d_start, d_end)
1163 elif trigger_id == "btn-sel-remove-all":
1165 row_fig_tput = self.PLACEHOLDER
1166 row_fig_lat = self.PLACEHOLDER
1167 row_btn_dwnld = self.PLACEHOLDER
1168 row_card_sel_tests = self.STYLE_DISABLED
1169 row_btns_sel_tests = self.STYLE_DISABLED
1172 "cl-selected-options": list()
1174 elif trigger_id == "btn-sel-remove":
1177 new_store_sel = list()
1178 for item in store_sel:
1179 if item["id"] not in list_sel:
1180 new_store_sel.append(item)
1181 store_sel = new_store_sel
1183 row_fig_tput, row_fig_lat, row_btn_dwnld = \
1184 _generate_plotting_area(
1186 self.data, store_sel, self.layout, d_start,
1189 _gen_new_url(parsed_url, store_sel, d_start, d_end)
1192 "cl-selected-options": self._list_tests(store_sel)
1195 row_fig_tput = self.PLACEHOLDER
1196 row_fig_lat = self.PLACEHOLDER
1197 row_btn_dwnld = self.PLACEHOLDER
1198 row_card_sel_tests = self.STYLE_DISABLED
1199 row_btns_sel_tests = self.STYLE_DISABLED
1202 "cl-selected-options": list()
1204 elif trigger_id == "url":
1205 # TODO: Add verification
1206 url_params = parsed_url["params"]
1208 store_sel = literal_eval(
1209 url_params.get("store_sel", list())[0])
1210 d_start = self._get_date(url_params.get("start", list())[0])
1211 d_end = self._get_date(url_params.get("end", list())[0])
1213 row_fig_tput, row_fig_lat, row_btn_dwnld = \
1214 _generate_plotting_area(
1216 self.data, store_sel, self.layout, d_start,
1220 parsed_url, store_sel, d_start, d_end
1223 row_card_sel_tests = self.STYLE_ENABLED
1224 row_btns_sel_tests = self.STYLE_ENABLED
1226 "cl-selected-options": self._list_tests(store_sel)
1229 row_fig_tput = self.PLACEHOLDER
1230 row_fig_lat = self.PLACEHOLDER
1231 row_btn_dwnld = self.PLACEHOLDER
1232 row_card_sel_tests = self.STYLE_DISABLED
1233 row_btns_sel_tests = self.STYLE_DISABLED
1236 "cl-selected-options": list()
1239 if ctrl_panel.get("cl-ctrl-core-value") and \
1240 ctrl_panel.get("cl-ctrl-framesize-value") and \
1241 ctrl_panel.get("cl-ctrl-testtype-value"):
1246 "btn-ctrl-add-disabled": disabled
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:
1269 callback_context.triggered[0]["prop_id"].split(".")[0]
1271 idx = 0 if trigger_id == "tput" else 1
1272 graph_data = graph_data[idx]["points"][0]
1273 except (JSONDecodeError, IndexError, KeyError, ValueError,
1277 metadata = no_update
1282 [dbc.Badge(x.split(":")[0]), x.split(": ")[1]]
1283 ) for x in graph_data.get("text", "").split("<br>")
1285 if trigger_id == "tput":
1286 title = "Throughput"
1287 elif trigger_id == "lat":
1289 hdrh_data = graph_data.get("customdata", None)
1292 class_name="gy-2 p-0",
1294 dbc.CardHeader(hdrh_data.pop("name")),
1295 dbc.CardBody(children=[
1297 id="hdrh-latency-graph",
1298 figure=graph_hdrh_latency(
1299 hdrh_data, self.layout
1307 class_name="gy-2 p-0",
1309 dbc.CardHeader(children=[
1311 target_id="tput-lat-metadata",
1313 style={"display": "inline-block"}
1318 id="tput-lat-metadata",
1320 children=[dbc.ListGroup(children, flush=True), ]
1326 return metadata, graph, True
1329 Output("download-data", "data"),
1330 State("selected-tests", "data"),
1331 Input("btn-download-data", "n_clicks"),
1332 prevent_initial_call=True
1334 def _download_data(store_sel, n_clicks):
1345 for itm in store_sel:
1346 sel_data = select_trending_data(self.data, itm)
1347 if sel_data is None:
1349 df = pd.concat([df, sel_data], ignore_index=True)
1351 return dcc.send_data_frame(df.to_csv, "trending_data.csv")