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 ..utils.constants import Constants as C
34 from ..utils.url_processing import url_decode, url_encode
35 from ..data.data import Data
36 from .graphs import graph_trending, graph_hdrh_latency, \
44 def __init__(self, app: Flask, html_layout_file: str,
45 graph_layout_file: str, data_spec_file: str, tooltip_file: str,
46 time_period: str=None) -> None:
52 self._html_layout_file = html_layout_file
53 self._graph_layout_file = graph_layout_file
54 self._data_spec_file = data_spec_file
55 self._tooltip_file = tooltip_file
56 self._time_period = time_period
60 data_spec_file=self._data_spec_file,
62 ).read_trending_mrr(days=self._time_period)
65 data_spec_file=self._data_spec_file,
67 ).read_trending_ndrpdr(days=self._time_period)
69 self._data = pd.concat([data_mrr, data_ndrpdr], ignore_index=True)
72 (datetime.utcnow() - self._data["start_time"].min()).days
73 if self._time_period > data_time_period:
74 self._time_period = data_time_period
77 # Get structure of tests:
79 for _, row in self._data[["job", "test_id"]].drop_duplicates().\
81 lst_job = row["job"].split("-")
84 tbed = "-".join(lst_job[-2:])
85 lst_test = row["test_id"].split(".")
89 area = "-".join(lst_test[3:-2])
90 suite = lst_test[-2].replace("2n1l-", "").replace("1n1l-", "").\
93 nic = suite.split("-")[0]
100 test = test.replace(f"{drv}-", "")
104 infra = "-".join((tbed, nic, driver))
105 lst_test = test.split("-")
106 framesize = lst_test[0]
107 core = lst_test[1] if lst_test[1] else "8C"
108 test = "-".join(lst_test[2: -1])
110 if tbs.get(dut, None) is None:
112 if tbs[dut].get(infra, None) is None:
113 tbs[dut][infra] = dict()
114 if tbs[dut][infra].get(area, None) is None:
115 tbs[dut][infra][area] = dict()
116 if tbs[dut][infra][area].get(test, None) is None:
117 tbs[dut][infra][area][test] = dict()
118 tbs[dut][infra][area][test]["core"] = list()
119 tbs[dut][infra][area][test]["frame-size"] = list()
120 tbs[dut][infra][area][test]["test-type"] = list()
121 if core.upper() not in tbs[dut][infra][area][test]["core"]:
122 tbs[dut][infra][area][test]["core"].append(core.upper())
123 if framesize.upper() not in \
124 tbs[dut][infra][area][test]["frame-size"]:
125 tbs[dut][infra][area][test]["frame-size"].append(
128 if "MRR" not in tbs[dut][infra][area][test]["test-type"]:
129 tbs[dut][infra][area][test]["test-type"].append("MRR")
130 elif ttype == "ndrpdr":
131 if "NDR" not in tbs[dut][infra][area][test]["test-type"]:
132 tbs[dut][infra][area][test]["test-type"].extend(
137 self._html_layout = ""
138 self._graph_layout = None
139 self._tooltips = dict()
142 with open(self._html_layout_file, "r") as file_read:
143 self._html_layout = file_read.read()
144 except IOError as err:
146 f"Not possible to open the file {self._html_layout_file}\n{err}"
150 with open(self._graph_layout_file, "r") as file_read:
151 self._graph_layout = load(file_read, Loader=FullLoader)
152 except IOError as err:
154 f"Not possible to open the file {self._graph_layout_file}\n"
157 except YAMLError as err:
159 f"An error occurred while parsing the specification file "
160 f"{self._graph_layout_file}\n{err}"
164 with open(self._tooltip_file, "r") as file_read:
165 self._tooltips = load(file_read, Loader=FullLoader)
166 except IOError as err:
168 f"Not possible to open the file {self._tooltip_file}\n{err}"
170 except YAMLError as err:
172 f"An error occurred while parsing the specification file "
173 f"{self._tooltip_file}\n{err}"
177 if self._app is not None and hasattr(self, 'callbacks'):
178 self.callbacks(self._app)
181 def html_layout(self):
182 return self._html_layout
186 return self._spec_tbs
194 return self._graph_layout
197 def time_period(self):
198 return self._time_period
200 def label(self, key: str) -> str:
201 return C.LABELS.get(key, key)
203 def _show_tooltip(self, id: str, title: str,
204 clipboard_id: str=None) -> list:
208 dcc.Clipboard(target_id=clipboard_id, title="Copy URL") \
209 if clipboard_id else str(),
217 class_name="border ms-1",
220 children=self._tooltips.get(id, str()),
226 def add_content(self):
229 if self.html_layout and self.spec_tbs:
243 id="offcanvas-metadata",
244 title="Throughput And Latency",
248 dbc.Row(id="metadata-tput-lat"),
249 dbc.Row(id="metadata-hdrh-graph"),
257 dcc.Store(id="selected-tests"),
258 dcc.Store(id="control-panel"),
259 dcc.Location(id="url", refresh=False),
260 self._add_ctrl_col(),
261 self._add_plotting_col(),
279 def _add_navbar(self):
280 """Add nav element with navigation panel. It is placed on the top.
282 return dbc.NavbarSimple(
283 id="navbarsimple-main",
287 "Continuous Performance Trending",
296 brand_external_link=True,
301 def _add_ctrl_col(self) -> dbc.Col:
302 """Add column with controls. It is placed on the left side.
307 self._add_ctrl_panel(),
311 def _add_plotting_col(self) -> dbc.Col:
312 """Add column with plots and tables. It is placed on the right side.
315 id="col-plotting-area",
319 dbc.Row( # Throughput
321 class_name="g-0 p-2",
328 class_name="g-0 p-2",
334 id="row-btn-download",
335 class_name="g-0 p-2",
346 def _add_ctrl_panel(self) -> dbc.Row:
351 class_name="g-0 p-2",
359 children=self._show_tooltip(
365 "Select a Device under Test..."
369 {"label": k, "value": k} \
370 for k in self.spec_tbs.keys()
372 key=lambda d: d["label"]
387 children=self._show_tooltip(
388 "help-infra", "Infra")
393 "Select a Physical Test Bed "
409 children=self._show_tooltip(
414 placeholder="Select an Area...",
429 children=self._show_tooltip(
434 placeholder="Select a Test...",
444 id="row-ctrl-framesize",
448 children=self._show_tooltip(
449 "help-framesize", "Frame Size"),
455 id="cl-ctrl-framesize-all",
456 options=C.CL_ALL_DISABLED,
466 id="cl-ctrl-framesize",
479 children=self._show_tooltip(
480 "help-cores", "Number of Cores"),
486 id="cl-ctrl-core-all",
487 options=C.CL_ALL_DISABLED,
506 id="row-ctrl-testtype",
510 children=self._show_tooltip(
511 "help-ttype", "Test Type"),
517 id="cl-ctrl-testtype-all",
518 options=C.CL_ALL_DISABLED,
528 id="cl-ctrl-testtype",
537 id="row-ctrl-normalize",
541 children=self._show_tooltip(
542 "help-normalize", "Normalize"),
548 id="cl-ctrl-normalize",
550 "value": "normalize",
552 "Normalize results to CPU"
565 class_name="gy-1 p-0",
571 children="Add Selected",
585 children=self._show_tooltip(
586 "help-time-period", "Time Period"),
590 className="d-flex justify-content-center",
592 datetime.utcnow() - timedelta(
593 days=self.time_period),
594 max_date_allowed=datetime.utcnow(),
595 initial_visible_month=datetime.utcnow(),
597 datetime.utcnow() - timedelta(
598 days=self.time_period),
599 end_date=datetime.utcnow(),
600 display_format="D MMM YY"
605 id="row-card-sel-tests",
607 style=C.STYLE_DISABLED,
614 class_name="overflow-auto",
618 style={"max-height": "12em"},
623 id="row-btns-sel-tests",
624 style=C.STYLE_DISABLED,
631 children="Remove Selected",
632 class_name="w-100 me-1",
637 id="btn-sel-remove-all",
638 children="Remove All",
639 class_name="w-100 me-1",
652 def __init__(self, panel: dict) -> None:
654 # Defines also the order of keys
656 "dd-ctrl-dut-value": str(),
657 "dd-ctrl-phy-options": list(),
658 "dd-ctrl-phy-disabled": True,
659 "dd-ctrl-phy-value": str(),
660 "dd-ctrl-area-options": list(),
661 "dd-ctrl-area-disabled": True,
662 "dd-ctrl-area-value": str(),
663 "dd-ctrl-test-options": list(),
664 "dd-ctrl-test-disabled": True,
665 "dd-ctrl-test-value": str(),
666 "cl-ctrl-core-options": list(),
667 "cl-ctrl-core-value": list(),
668 "cl-ctrl-core-all-value": list(),
669 "cl-ctrl-core-all-options": C.CL_ALL_DISABLED,
670 "cl-ctrl-framesize-options": list(),
671 "cl-ctrl-framesize-value": list(),
672 "cl-ctrl-framesize-all-value": list(),
673 "cl-ctrl-framesize-all-options": C.CL_ALL_DISABLED,
674 "cl-ctrl-testtype-options": list(),
675 "cl-ctrl-testtype-value": list(),
676 "cl-ctrl-testtype-all-value": list(),
677 "cl-ctrl-testtype-all-options": C.CL_ALL_DISABLED,
678 "btn-ctrl-add-disabled": True,
679 "cl-normalize-value": list(),
680 "cl-selected-options": list(),
683 self._panel = deepcopy(self._defaults)
685 for key in self._defaults:
686 self._panel[key] = panel[key]
689 def defaults(self) -> dict:
690 return self._defaults
693 def panel(self) -> dict:
696 def set(self, kwargs: dict) -> None:
697 for key, val in kwargs.items():
698 if key in self._panel:
699 self._panel[key] = val
701 raise KeyError(f"The key {key} is not defined.")
703 def get(self, key: str) -> any:
704 return self._panel[key]
706 def values(self) -> tuple:
707 return tuple(self._panel.values())
710 def _sync_checklists(opt: list, sel: list, all: list, id: str) -> tuple:
713 options = {v["value"] for v in opt}
715 sel = list(options) if all else list()
717 all = ["all", ] if set(sel) == options else list()
721 def _list_tests(selection: dict) -> list:
722 """Display selected tests with checkboxes
725 return [{"label": v["id"], "value": v["id"]} for v in selection]
730 def _get_date(s_date: str) -> datetime:
731 return datetime(int(s_date[0:4]), int(s_date[5:7]), int(s_date[8:10]))
733 def callbacks(self, app):
735 def _generate_plotting_area(figs: tuple, url: str) -> tuple:
739 (fig_tput, fig_lat) = figs
741 row_fig_tput = C.PLACEHOLDER
742 row_fig_lat = C.PLACEHOLDER
743 row_btn_dwnld = C.PLACEHOLDER
748 id={"type": "graph", "index": "tput"},
756 dcc.Loading(children=[
758 id="btn-download-data",
759 children=self._show_tooltip(
760 "help-download", "Download Data"),
764 dcc.Download(id="download-data")
776 children=self._show_tooltip(
777 "help-url", "URL", "input-url")
794 id={"type": "graph", "index": "lat"},
799 return row_fig_tput, row_fig_lat, row_btn_dwnld
802 Output("control-panel", "data"), # Store
803 Output("selected-tests", "data"), # Store
804 Output("row-graph-tput", "children"),
805 Output("row-graph-lat", "children"),
806 Output("row-btn-download", "children"),
807 Output("row-card-sel-tests", "style"),
808 Output("row-btns-sel-tests", "style"),
809 Output("dd-ctrl-dut", "value"),
810 Output("dd-ctrl-phy", "options"),
811 Output("dd-ctrl-phy", "disabled"),
812 Output("dd-ctrl-phy", "value"),
813 Output("dd-ctrl-area", "options"),
814 Output("dd-ctrl-area", "disabled"),
815 Output("dd-ctrl-area", "value"),
816 Output("dd-ctrl-test", "options"),
817 Output("dd-ctrl-test", "disabled"),
818 Output("dd-ctrl-test", "value"),
819 Output("cl-ctrl-core", "options"),
820 Output("cl-ctrl-core", "value"),
821 Output("cl-ctrl-core-all", "value"),
822 Output("cl-ctrl-core-all", "options"),
823 Output("cl-ctrl-framesize", "options"),
824 Output("cl-ctrl-framesize", "value"),
825 Output("cl-ctrl-framesize-all", "value"),
826 Output("cl-ctrl-framesize-all", "options"),
827 Output("cl-ctrl-testtype", "options"),
828 Output("cl-ctrl-testtype", "value"),
829 Output("cl-ctrl-testtype-all", "value"),
830 Output("cl-ctrl-testtype-all", "options"),
831 Output("btn-ctrl-add", "disabled"),
832 Output("cl-ctrl-normalize", "value"),
833 Output("cl-selected", "options"), # User selection
834 State("control-panel", "data"), # Store
835 State("selected-tests", "data"), # Store
836 State("cl-selected", "value"), # User selection
837 Input("dd-ctrl-dut", "value"),
838 Input("dd-ctrl-phy", "value"),
839 Input("dd-ctrl-area", "value"),
840 Input("dd-ctrl-test", "value"),
841 Input("cl-ctrl-core", "value"),
842 Input("cl-ctrl-core-all", "value"),
843 Input("cl-ctrl-framesize", "value"),
844 Input("cl-ctrl-framesize-all", "value"),
845 Input("cl-ctrl-testtype", "value"),
846 Input("cl-ctrl-testtype-all", "value"),
847 Input("cl-ctrl-normalize", "value"),
848 Input("btn-ctrl-add", "n_clicks"),
849 Input("dpr-period", "start_date"),
850 Input("dpr-period", "end_date"),
851 Input("btn-sel-remove", "n_clicks"),
852 Input("btn-sel-remove-all", "n_clicks"),
855 def _update_ctrl_panel(cp_data: dict, store_sel: list, list_sel: list,
856 dd_dut: str, dd_phy: str, dd_area: str, dd_test: str, cl_core: list,
857 cl_core_all: list, cl_framesize: list, cl_framesize_all: list,
858 cl_testtype: list, cl_testtype_all: list, cl_normalize: list,
859 btn_add: int, d_start: str, d_end: str, btn_remove: int,
860 btn_remove_all: int, href: str) -> tuple:
864 def _gen_new_url(parsed_url: dict, store_sel: list,
865 start: datetime, end: datetime) -> str:
868 new_url = url_encode({
869 "scheme": parsed_url["scheme"],
870 "netloc": parsed_url["netloc"],
871 "path": parsed_url["path"],
873 "store_sel": store_sel,
883 ctrl_panel = self.ControlPanel(cp_data)
885 d_start = self._get_date(d_start)
886 d_end = self._get_date(d_end)
889 parsed_url = url_decode(href)
891 row_fig_tput = no_update
892 row_fig_lat = no_update
893 row_btn_dwnld = no_update
894 row_card_sel_tests = no_update
895 row_btns_sel_tests = no_update
897 trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
899 if trigger_id == "dd-ctrl-dut":
901 dut = self.spec_tbs[dd_dut]
903 [{"label": v, "value": v}for v in dut.keys()],
904 key=lambda d: d["label"]
911 "dd-ctrl-dut-value": dd_dut,
912 "dd-ctrl-phy-value": str(),
913 "dd-ctrl-phy-options": options,
914 "dd-ctrl-phy-disabled": disabled,
915 "dd-ctrl-area-value": str(),
916 "dd-ctrl-area-options": list(),
917 "dd-ctrl-area-disabled": True,
918 "dd-ctrl-test-value": str(),
919 "dd-ctrl-test-options": list(),
920 "dd-ctrl-test-disabled": True,
921 "cl-ctrl-core-options": list(),
922 "cl-ctrl-core-value": list(),
923 "cl-ctrl-core-all-value": list(),
924 "cl-ctrl-core-all-options": C.CL_ALL_DISABLED,
925 "cl-ctrl-framesize-options": list(),
926 "cl-ctrl-framesize-value": list(),
927 "cl-ctrl-framesize-all-value": list(),
928 "cl-ctrl-framesize-all-options": C.CL_ALL_DISABLED,
929 "cl-ctrl-testtype-options": list(),
930 "cl-ctrl-testtype-value": list(),
931 "cl-ctrl-testtype-all-value": list(),
932 "cl-ctrl-testtype-all-options": C.CL_ALL_DISABLED,
934 elif trigger_id == "dd-ctrl-phy":
936 dut = ctrl_panel.get("dd-ctrl-dut-value")
937 phy = self.spec_tbs[dut][dd_phy]
939 [{"label": self.label(v), "value": v}
940 for v in phy.keys()],
941 key=lambda d: d["label"]
948 "dd-ctrl-phy-value": dd_phy,
949 "dd-ctrl-area-value": str(),
950 "dd-ctrl-area-options": options,
951 "dd-ctrl-area-disabled": disabled,
952 "dd-ctrl-test-value": str(),
953 "dd-ctrl-test-options": list(),
954 "dd-ctrl-test-disabled": True,
955 "cl-ctrl-core-options": list(),
956 "cl-ctrl-core-value": list(),
957 "cl-ctrl-core-all-value": list(),
958 "cl-ctrl-core-all-options": C.CL_ALL_DISABLED,
959 "cl-ctrl-framesize-options": list(),
960 "cl-ctrl-framesize-value": list(),
961 "cl-ctrl-framesize-all-value": list(),
962 "cl-ctrl-framesize-all-options": C.CL_ALL_DISABLED,
963 "cl-ctrl-testtype-options": list(),
964 "cl-ctrl-testtype-value": list(),
965 "cl-ctrl-testtype-all-value": list(),
966 "cl-ctrl-testtype-all-options": C.CL_ALL_DISABLED,
968 elif trigger_id == "dd-ctrl-area":
970 dut = ctrl_panel.get("dd-ctrl-dut-value")
971 phy = ctrl_panel.get("dd-ctrl-phy-value")
972 area = self.spec_tbs[dut][phy][dd_area]
974 [{"label": v, "value": v} for v in area.keys()],
975 key=lambda d: d["label"]
982 "dd-ctrl-area-value": dd_area,
983 "dd-ctrl-test-value": str(),
984 "dd-ctrl-test-options": options,
985 "dd-ctrl-test-disabled": disabled,
986 "cl-ctrl-core-options": list(),
987 "cl-ctrl-core-value": list(),
988 "cl-ctrl-core-all-value": list(),
989 "cl-ctrl-core-all-options": C.CL_ALL_DISABLED,
990 "cl-ctrl-framesize-options": list(),
991 "cl-ctrl-framesize-value": list(),
992 "cl-ctrl-framesize-all-value": list(),
993 "cl-ctrl-framesize-all-options": C.CL_ALL_DISABLED,
994 "cl-ctrl-testtype-options": list(),
995 "cl-ctrl-testtype-value": list(),
996 "cl-ctrl-testtype-all-value": list(),
997 "cl-ctrl-testtype-all-options": C.CL_ALL_DISABLED,
999 elif trigger_id == "dd-ctrl-test":
1001 framesize_opts = list()
1002 testtype_opts = list()
1003 dut = ctrl_panel.get("dd-ctrl-dut-value")
1004 phy = ctrl_panel.get("dd-ctrl-phy-value")
1005 area = ctrl_panel.get("dd-ctrl-area-value")
1006 test = self.spec_tbs[dut][phy][area][dd_test]
1007 cores = test["core"]
1008 fsizes = test["frame-size"]
1009 ttypes = test["test-type"]
1010 if dut and phy and area and dd_test:
1011 core_opts = [{"label": v, "value": v}
1012 for v in sorted(cores)]
1013 framesize_opts = [{"label": v, "value": v}
1014 for v in sorted(fsizes)]
1015 testtype_opts = [{"label": v, "value": v}
1016 for v in sorted(ttypes)]
1018 "dd-ctrl-test-value": dd_test,
1019 "cl-ctrl-core-options": core_opts,
1020 "cl-ctrl-core-value": list(),
1021 "cl-ctrl-core-all-value": list(),
1022 "cl-ctrl-core-all-options": C.CL_ALL_ENABLED,
1023 "cl-ctrl-framesize-options": framesize_opts,
1024 "cl-ctrl-framesize-value": list(),
1025 "cl-ctrl-framesize-all-value": list(),
1026 "cl-ctrl-framesize-all-options": C.CL_ALL_ENABLED,
1027 "cl-ctrl-testtype-options": testtype_opts,
1028 "cl-ctrl-testtype-value": list(),
1029 "cl-ctrl-testtype-all-value": list(),
1030 "cl-ctrl-testtype-all-options": C.CL_ALL_ENABLED,
1032 elif trigger_id == "cl-ctrl-core":
1033 val_sel, val_all = self._sync_checklists(
1034 opt=ctrl_panel.get("cl-ctrl-core-options"),
1040 "cl-ctrl-core-value": val_sel,
1041 "cl-ctrl-core-all-value": val_all,
1043 elif trigger_id == "cl-ctrl-core-all":
1044 val_sel, val_all = self._sync_checklists(
1045 opt = ctrl_panel.get("cl-ctrl-core-options"),
1051 "cl-ctrl-core-value": val_sel,
1052 "cl-ctrl-core-all-value": val_all,
1054 elif trigger_id == "cl-ctrl-framesize":
1055 val_sel, val_all = self._sync_checklists(
1056 opt = ctrl_panel.get("cl-ctrl-framesize-options"),
1062 "cl-ctrl-framesize-value": val_sel,
1063 "cl-ctrl-framesize-all-value": val_all,
1065 elif trigger_id == "cl-ctrl-framesize-all":
1066 val_sel, val_all = self._sync_checklists(
1067 opt = ctrl_panel.get("cl-ctrl-framesize-options"),
1069 all=cl_framesize_all,
1073 "cl-ctrl-framesize-value": val_sel,
1074 "cl-ctrl-framesize-all-value": val_all,
1076 elif trigger_id == "cl-ctrl-testtype":
1077 val_sel, val_all = self._sync_checklists(
1078 opt = ctrl_panel.get("cl-ctrl-testtype-options"),
1084 "cl-ctrl-testtype-value": val_sel,
1085 "cl-ctrl-testtype-all-value": val_all,
1087 elif trigger_id == "cl-ctrl-testtype-all":
1088 val_sel, val_all = self._sync_checklists(
1089 opt = ctrl_panel.get("cl-ctrl-testtype-options"),
1091 all=cl_testtype_all,
1095 "cl-ctrl-testtype-value": val_sel,
1096 "cl-ctrl-testtype-all-value": val_all,
1098 elif trigger_id == "btn-ctrl-add":
1100 dut = ctrl_panel.get("dd-ctrl-dut-value")
1101 phy = ctrl_panel.get("dd-ctrl-phy-value")
1102 area = ctrl_panel.get("dd-ctrl-area-value")
1103 test = ctrl_panel.get("dd-ctrl-test-value")
1104 cores = ctrl_panel.get("cl-ctrl-core-value")
1105 framesizes = ctrl_panel.get("cl-ctrl-framesize-value")
1106 testtypes = ctrl_panel.get("cl-ctrl-testtype-value")
1107 # Add selected test to the list of tests in store:
1108 if all((dut, phy, area, test, cores, framesizes, testtypes)):
1109 if store_sel is None:
1112 for framesize in framesizes:
1113 for ttype in testtypes:
1117 dut, phy.replace('af_xdp', 'af-xdp'), area,
1118 framesize.lower(), core.lower(), test,
1121 if tid not in [itm["id"] for itm in store_sel]:
1128 "framesize": framesize.lower(),
1129 "core": core.lower(),
1130 "testtype": ttype.lower()
1132 store_sel = sorted(store_sel, key=lambda d: d["id"])
1133 row_card_sel_tests = C.STYLE_ENABLED
1134 row_btns_sel_tests = C.STYLE_ENABLED
1135 if C.CLEAR_ALL_INPUTS:
1136 ctrl_panel.set(ctrl_panel.defaults)
1137 elif trigger_id == "btn-sel-remove-all":
1139 row_fig_tput = C.PLACEHOLDER
1140 row_fig_lat = C.PLACEHOLDER
1141 row_btn_dwnld = C.PLACEHOLDER
1142 row_card_sel_tests = C.STYLE_DISABLED
1143 row_btns_sel_tests = C.STYLE_DISABLED
1145 ctrl_panel.set({"cl-selected-options": list()})
1146 elif trigger_id == "btn-sel-remove":
1149 new_store_sel = list()
1150 for item in store_sel:
1151 if item["id"] not in list_sel:
1152 new_store_sel.append(item)
1153 store_sel = new_store_sel
1154 elif trigger_id == "url":
1155 # TODO: Add verification
1156 url_params = parsed_url["params"]
1158 store_sel = literal_eval(
1159 url_params.get("store_sel", list())[0])
1160 d_start = self._get_date(url_params.get("start", list())[0])
1161 d_end = self._get_date(url_params.get("end", list())[0])
1163 row_card_sel_tests = C.STYLE_ENABLED
1164 row_btns_sel_tests = C.STYLE_ENABLED
1166 if trigger_id in ("btn-ctrl-add", "url", "dpr-period",
1167 "btn-sel-remove", "cl-ctrl-normalize"):
1169 row_fig_tput, row_fig_lat, row_btn_dwnld = \
1170 _generate_plotting_area(
1171 graph_trending(self.data, store_sel, self.layout,
1172 d_start, d_end, bool(cl_normalize)),
1173 _gen_new_url(parsed_url, store_sel, d_start, d_end)
1176 "cl-selected-options": self._list_tests(store_sel)
1179 row_fig_tput = C.PLACEHOLDER
1180 row_fig_lat = C.PLACEHOLDER
1181 row_btn_dwnld = C.PLACEHOLDER
1182 row_card_sel_tests = C.STYLE_DISABLED
1183 row_btns_sel_tests = C.STYLE_DISABLED
1185 ctrl_panel.set({"cl-selected-options": list()})
1187 if ctrl_panel.get("cl-ctrl-core-value") and \
1188 ctrl_panel.get("cl-ctrl-framesize-value") and \
1189 ctrl_panel.get("cl-ctrl-testtype-value"):
1194 "btn-ctrl-add-disabled": disabled,
1195 "cl-normalize-value": cl_normalize
1199 ctrl_panel.panel, store_sel,
1200 row_fig_tput, row_fig_lat, row_btn_dwnld,
1201 row_card_sel_tests, row_btns_sel_tests
1203 ret_val.extend(ctrl_panel.values())
1207 Output("metadata-tput-lat", "children"),
1208 Output("metadata-hdrh-graph", "children"),
1209 Output("offcanvas-metadata", "is_open"),
1210 Input({"type": "graph", "index": ALL}, "clickData"),
1211 prevent_initial_call=True
1213 def _show_metadata_from_graphs(graph_data: dict) -> tuple:
1218 callback_context.triggered[0]["prop_id"].split(".")[0]
1220 idx = 0 if trigger_id == "tput" else 1
1221 graph_data = graph_data[idx]["points"][0]
1222 except (JSONDecodeError, IndexError, KeyError, ValueError,
1226 metadata = no_update
1231 [dbc.Badge(x.split(":")[0]), x.split(": ")[1]]
1232 ) for x in graph_data.get("text", "").split("<br>")
1234 if trigger_id == "tput":
1235 title = "Throughput"
1236 elif trigger_id == "lat":
1238 hdrh_data = graph_data.get("customdata", None)
1241 class_name="gy-2 p-0",
1243 dbc.CardHeader(hdrh_data.pop("name")),
1244 dbc.CardBody(children=[
1246 id="hdrh-latency-graph",
1247 figure=graph_hdrh_latency(
1248 hdrh_data, self.layout
1256 class_name="gy-2 p-0",
1258 dbc.CardHeader(children=[
1260 target_id="tput-lat-metadata",
1262 style={"display": "inline-block"}
1267 id="tput-lat-metadata",
1269 children=[dbc.ListGroup(children, flush=True), ]
1275 return metadata, graph, True
1278 Output("download-data", "data"),
1279 State("selected-tests", "data"),
1280 Input("btn-download-data", "n_clicks"),
1281 prevent_initial_call=True
1283 def _download_data(store_sel, n_clicks):
1294 for itm in store_sel:
1295 sel_data = select_trending_data(self.data, itm)
1296 if sel_data is None:
1298 df = pd.concat([df, sel_data], ignore_index=True)
1300 return dcc.send_data_frame(df.to_csv, "trending_data.csv")