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.
18 import dash_bootstrap_components as dbc
20 from flask import Flask
23 from dash import callback_context, no_update, ALL
24 from dash import Input, Output, State
25 from dash.exceptions import PreventUpdate
26 from yaml import load, FullLoader, YAMLError
27 from datetime import datetime, timedelta
28 from copy import deepcopy
29 from json import loads, JSONDecodeError
31 from ..data.data import Data
32 from .graphs import graph_trending, graph_hdrh_latency, \
40 STYLE_DISABLED = {"display": "none"}
41 STYLE_ENABLED = {"display": "inherit"}
54 PLACEHOLDER = html.Nobr("")
56 DRIVERS = ("avf", "af-xdp", "rdma", "dpdk")
60 "container_memif": "LXC/DRC Container Memif",
61 "crypto": "IPSec IPv4 Routing",
62 "ip4": "IPv4 Routing",
63 "ip6": "IPv6 Routing",
64 "ip4_tunnels": "IPv4 Tunnels",
65 "l2": "L2 Ethernet Switching",
66 "srv6": "SRv6 Routing",
67 "vm_vhost": "VMs vhost-user",
68 "nfv_density-dcr_memif-chain_ipsec": "CNF Service Chains Routing IPSec",
69 "nfv_density-vm_vhost-chain_dot1qip4vxlan":"VNF Service Chains Tunnels",
70 "nfv_density-vm_vhost-chain": "VNF Service Chains Routing",
71 "nfv_density-dcr_memif-pipeline": "CNF Service Pipelines Routing",
72 "nfv_density-dcr_memif-chain": "CNF Service Chains Routing",
75 def __init__(self, app: Flask, html_layout_file: str, spec_file: str,
76 graph_layout_file: str, data_spec_file: str,
77 time_period: str=None) -> None:
83 self._html_layout_file = html_layout_file
84 self._spec_file = spec_file
85 self._graph_layout_file = graph_layout_file
86 self._data_spec_file = data_spec_file
87 self._time_period = time_period
91 data_spec_file=self._data_spec_file,
93 ).read_trending_mrr(days=self._time_period)
96 data_spec_file=self._data_spec_file,
98 ).read_trending_ndrpdr(days=self._time_period)
100 self._data = pd.concat([data_mrr, data_ndrpdr], ignore_index=True)
103 (datetime.utcnow() - self._data["start_time"].min()).days
104 if self._time_period > data_time_period:
105 self._time_period = data_time_period
108 # Get structure of tests:
110 for _, row in self._data[["job", "test_id"]].drop_duplicates().\
112 lst_job = row["job"].split("-")
115 tbed = "-".join(lst_job[-2:])
116 lst_test = row["test_id"].split(".")
120 area = "-".join(lst_test[3:-2])
121 suite = lst_test[-2].replace("2n1l-", "").replace("1n1l-", "").\
124 nic = suite.split("-")[0]
125 for drv in self.DRIVERS:
131 test = test.replace(f"{drv}-", "")
135 infra = "-".join((tbed, nic, driver))
136 lst_test = test.split("-")
137 framesize = lst_test[0]
138 core = lst_test[1] if lst_test[1] else "1C"
139 test = "-".join(lst_test[2: -1])
141 if tbs.get(dut, None) is None:
143 if tbs[dut].get(infra, None) is None:
144 tbs[dut][infra] = dict()
145 if tbs[dut][infra].get(area, None) is None:
146 tbs[dut][infra][area] = dict()
147 if tbs[dut][infra][area].get(test, None) is None:
148 tbs[dut][infra][area][test] = dict()
149 tbs[dut][infra][area][test]["core"] = list()
150 tbs[dut][infra][area][test]["frame-size"] = list()
151 tbs[dut][infra][area][test]["test-type"] = list()
152 if core.upper() not in tbs[dut][infra][area][test]["core"]:
153 tbs[dut][infra][area][test]["core"].append(core.upper())
154 if framesize.upper() not in \
155 tbs[dut][infra][area][test]["frame-size"]:
156 tbs[dut][infra][area][test]["frame-size"].append(
159 if "MRR" not in tbs[dut][infra][area][test]["test-type"]:
160 tbs[dut][infra][area][test]["test-type"].append("MRR")
161 elif ttype == "ndrpdr":
162 if "NDR" not in tbs[dut][infra][area][test]["test-type"]:
163 tbs[dut][infra][area][test]["test-type"].extend(
168 self._html_layout = ""
169 self._graph_layout = None
172 with open(self._html_layout_file, "r") as file_read:
173 self._html_layout = file_read.read()
174 except IOError as err:
176 f"Not possible to open the file {self._html_layout_file}\n{err}"
180 with open(self._graph_layout_file, "r") as file_read:
181 self._graph_layout = load(file_read, Loader=FullLoader)
182 except IOError as err:
184 f"Not possible to open the file {self._graph_layout_file}\n"
187 except YAMLError as err:
189 f"An error occurred while parsing the specification file "
190 f"{self._graph_layout_file}\n"
195 if self._app is not None and hasattr(self, 'callbacks'):
196 self.callbacks(self._app)
199 def html_layout(self):
200 return self._html_layout
204 return self._spec_tbs
212 return self._graph_layout
215 def time_period(self):
216 return self._time_period
218 def label(self, key: str) -> str:
219 return self.LABELS.get(key, key)
221 def add_content(self):
224 if self.html_layout and self.spec_tbs:
238 id="offcanvas-metadata",
239 title="Throughput And Latency",
243 dbc.Row(id="metadata-tput-lat"),
244 dbc.Row(id="metadata-hdrh-graph"),
258 self._add_ctrl_col(),
259 self._add_plotting_col(),
277 def _add_navbar(self):
278 """Add nav element with navigation panel. It is placed on the top.
280 return dbc.NavbarSimple(
281 id="navbarsimple-main",
285 "Continuous Performance Trending",
294 brand_external_link=True,
299 def _add_ctrl_col(self) -> dbc.Col:
300 """Add column with controls. It is placed on the left side.
305 self._add_ctrl_panel(),
309 def _add_plotting_col(self) -> dbc.Col:
310 """Add column with plots and tables. It is placed on the right side.
313 id="col-plotting-area",
317 dbc.Row( # Throughput
319 class_name="g-0 p-2",
326 class_name="g-0 p-2",
332 id="row-btn-download",
333 class_name="g-0 p-2",
344 def _add_ctrl_panel(self) -> dbc.Row:
349 class_name="g-0 p-2",
356 dbc.InputGroupText("DUT"),
360 "Select a Device under Test..."
364 {"label": k, "value": k} \
365 for k in self.spec_tbs.keys()
367 key=lambda d: d["label"]
381 dbc.InputGroupText("Infra"),
385 "Select a Physical Test Bed "
400 dbc.InputGroupText("Area"),
403 placeholder="Select an Area...",
417 dbc.InputGroupText("Test"),
420 placeholder="Select a Test...",
440 id="cl-ctrl-core-all",
441 options=self.CL_ALL_DISABLED,
460 id="row-ctrl-framesize",
470 id="cl-ctrl-framesize-all",
471 options=self.CL_ALL_DISABLED,
481 id="cl-ctrl-framesize",
490 id="row-ctrl-testtype",
500 id="cl-ctrl-testtype-all",
501 options=self.CL_ALL_DISABLED,
511 id="cl-ctrl-testtype",
520 class_name="gy-1 p-0",
526 children="Add Selected",
540 className="d-flex justify-content-center",
542 datetime.utcnow() - timedelta(
543 days=self.time_period),
544 max_date_allowed=datetime.utcnow(),
545 initial_visible_month=datetime.utcnow(),
547 datetime.utcnow() - timedelta(
548 days=self.time_period),
549 end_date=datetime.utcnow(),
550 display_format="D MMMM YY"
555 id="row-card-sel-tests",
557 style=self.STYLE_DISABLED,
564 class_name="overflow-auto",
568 style={"max-height": "12em"},
573 id="row-btns-sel-tests",
574 style=self.STYLE_DISABLED,
581 children="Remove Selected",
582 class_name="w-100 me-1",
587 id="btn-sel-remove-all",
588 children="Remove All",
589 class_name="w-100 me-1",
602 def __init__(self, panel: dict) -> None:
610 # Defines also the order of keys
612 "dd-ctrl-dut-value": str(),
613 "dd-ctrl-phy-options": list(),
614 "dd-ctrl-phy-disabled": True,
615 "dd-ctrl-phy-value": str(),
616 "dd-ctrl-area-options": list(),
617 "dd-ctrl-area-disabled": True,
618 "dd-ctrl-area-value": str(),
619 "dd-ctrl-test-options": list(),
620 "dd-ctrl-test-disabled": True,
621 "dd-ctrl-test-value": str(),
622 "cl-ctrl-core-options": list(),
623 "cl-ctrl-core-value": list(),
624 "cl-ctrl-core-all-value": list(),
625 "cl-ctrl-core-all-options": CL_ALL_DISABLED,
626 "cl-ctrl-framesize-options": list(),
627 "cl-ctrl-framesize-value": list(),
628 "cl-ctrl-framesize-all-value": list(),
629 "cl-ctrl-framesize-all-options": CL_ALL_DISABLED,
630 "cl-ctrl-testtype-options": list(),
631 "cl-ctrl-testtype-value": list(),
632 "cl-ctrl-testtype-all-value": list(),
633 "cl-ctrl-testtype-all-options": CL_ALL_DISABLED,
634 "btn-ctrl-add-disabled": True,
635 "cl-selected-options": list(),
638 self._panel = deepcopy(self._defaults)
640 for key in self._defaults:
641 self._panel[key] = panel[key]
644 def defaults(self) -> dict:
645 return self._defaults
648 def panel(self) -> dict:
651 def set(self, kwargs: dict) -> None:
652 for key, val in kwargs.items():
653 if key in self._panel:
654 self._panel[key] = val
656 raise KeyError(f"The key {key} is not defined.")
658 def get(self, key: str) -> any:
659 return self._panel[key]
661 def values(self) -> tuple:
662 return tuple(self._panel.values())
665 def _sync_checklists(opt: list, sel: list, all: list, id: str) -> tuple:
668 options = {v["value"] for v in opt}
670 sel = list(options) if all else list()
672 all = ["all", ] if set(sel) == options else list()
676 def _list_tests(selection: dict) -> list:
677 """Display selected tests with checkboxes
680 return [{"label": v["id"], "value": v["id"]} for v in selection]
684 def callbacks(self, app):
686 def _generate_plotting_arrea(args: tuple) -> tuple:
690 (fig_tput, fig_lat) = args
692 row_fig_tput = self.PLACEHOLDER
693 row_fig_lat = self.PLACEHOLDER
694 row_btn_dwnld = self.PLACEHOLDER
699 id={"type": "graph", "index": "tput"},
704 dcc.Loading(children=[
706 id="btn-download-data",
707 children=["Download Data"],
711 dcc.Download(id="download-data")
717 id={"type": "graph", "index": "lat"},
722 return row_fig_tput, row_fig_lat, row_btn_dwnld
725 Output("control-panel", "data"), # Store
726 Output("selected-tests", "data"), # Store
727 Output("row-graph-tput", "children"),
728 Output("row-graph-lat", "children"),
729 Output("row-btn-download", "children"),
730 Output("row-card-sel-tests", "style"),
731 Output("row-btns-sel-tests", "style"),
732 Output("dd-ctrl-dut", "value"),
733 Output("dd-ctrl-phy", "options"),
734 Output("dd-ctrl-phy", "disabled"),
735 Output("dd-ctrl-phy", "value"),
736 Output("dd-ctrl-area", "options"),
737 Output("dd-ctrl-area", "disabled"),
738 Output("dd-ctrl-area", "value"),
739 Output("dd-ctrl-test", "options"),
740 Output("dd-ctrl-test", "disabled"),
741 Output("dd-ctrl-test", "value"),
742 Output("cl-ctrl-core", "options"),
743 Output("cl-ctrl-core", "value"),
744 Output("cl-ctrl-core-all", "value"),
745 Output("cl-ctrl-core-all", "options"),
746 Output("cl-ctrl-framesize", "options"),
747 Output("cl-ctrl-framesize", "value"),
748 Output("cl-ctrl-framesize-all", "value"),
749 Output("cl-ctrl-framesize-all", "options"),
750 Output("cl-ctrl-testtype", "options"),
751 Output("cl-ctrl-testtype", "value"),
752 Output("cl-ctrl-testtype-all", "value"),
753 Output("cl-ctrl-testtype-all", "options"),
754 Output("btn-ctrl-add", "disabled"),
755 Output("cl-selected", "options"), # User selection
756 State("control-panel", "data"), # Store
757 State("selected-tests", "data"), # Store
758 State("cl-selected", "value"), # User selection
759 Input("dd-ctrl-dut", "value"),
760 Input("dd-ctrl-phy", "value"),
761 Input("dd-ctrl-area", "value"),
762 Input("dd-ctrl-test", "value"),
763 Input("cl-ctrl-core", "value"),
764 Input("cl-ctrl-core-all", "value"),
765 Input("cl-ctrl-framesize", "value"),
766 Input("cl-ctrl-framesize-all", "value"),
767 Input("cl-ctrl-testtype", "value"),
768 Input("cl-ctrl-testtype-all", "value"),
769 Input("btn-ctrl-add", "n_clicks"),
770 Input("dpr-period", "start_date"),
771 Input("dpr-period", "end_date"),
772 Input("btn-sel-remove", "n_clicks"),
773 Input("btn-sel-remove-all", "n_clicks"),
775 def _update_ctrl_panel(cp_data: dict, store_sel: list, list_sel: list,
776 dd_dut: str, dd_phy: str, dd_area: str, dd_test: str, cl_core: list,
777 cl_core_all: list, cl_framesize: list, cl_framesize_all: list,
778 cl_testtype: list, cl_testtype_all: list, btn_add: int,
779 d_start: str, d_end: str, btn_remove: int,
780 btn_remove_all: int) -> tuple:
784 d_start = datetime(int(d_start[0:4]), int(d_start[5:7]),
786 d_end = datetime(int(d_end[0:4]), int(d_end[5:7]), int(d_end[8:10]))
788 row_fig_tput = no_update
789 row_fig_lat = no_update
790 row_btn_dwnld = no_update
791 row_card_sel_tests = no_update
792 row_btns_sel_tests = no_update
794 ctrl_panel = self.ControlPanel(cp_data)
796 trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
798 if trigger_id == "dd-ctrl-dut":
802 {"label": v, "value": v}
803 for v in self.spec_tbs[dd_dut].keys()
805 key=lambda d: d["label"]
812 "dd-ctrl-dut-value": dd_dut,
813 "dd-ctrl-phy-value": str(),
814 "dd-ctrl-phy-options": options,
815 "dd-ctrl-phy-disabled": disabled,
816 "dd-ctrl-area-value": str(),
817 "dd-ctrl-area-options": list(),
818 "dd-ctrl-area-disabled": True,
819 "dd-ctrl-test-options": list(),
820 "dd-ctrl-test-disabled": True,
821 "cl-ctrl-core-options": list(),
822 "cl-ctrl-core-value": list(),
823 "cl-ctrl-core-all-value": list(),
824 "cl-ctrl-core-all-options": self.CL_ALL_DISABLED,
825 "cl-ctrl-framesize-options": list(),
826 "cl-ctrl-framesize-value": list(),
827 "cl-ctrl-framesize-all-value": list(),
828 "cl-ctrl-framesize-all-options": self.CL_ALL_DISABLED,
829 "cl-ctrl-testtype-options": list(),
830 "cl-ctrl-testtype-value": list(),
831 "cl-ctrl-testtype-all-value": list(),
832 "cl-ctrl-testtype-all-options": self.CL_ALL_DISABLED,
834 if trigger_id == "dd-ctrl-phy":
836 dut = ctrl_panel.get("dd-ctrl-dut-value")
839 {"label": self.label(v), "value": v}
840 for v in self.spec_tbs[dut][dd_phy].keys()
842 key=lambda d: d["label"]
849 "dd-ctrl-phy-value": dd_phy,
850 "dd-ctrl-area-value": str(),
851 "dd-ctrl-area-options": options,
852 "dd-ctrl-area-disabled": disabled,
853 "dd-ctrl-test-options": list(),
854 "dd-ctrl-test-disabled": True,
855 "cl-ctrl-core-options": list(),
856 "cl-ctrl-core-value": list(),
857 "cl-ctrl-core-all-value": list(),
858 "cl-ctrl-core-all-options": self.CL_ALL_DISABLED,
859 "cl-ctrl-framesize-options": list(),
860 "cl-ctrl-framesize-value": list(),
861 "cl-ctrl-framesize-all-value": list(),
862 "cl-ctrl-framesize-all-options": self.CL_ALL_DISABLED,
863 "cl-ctrl-testtype-options": list(),
864 "cl-ctrl-testtype-value": list(),
865 "cl-ctrl-testtype-all-value": list(),
866 "cl-ctrl-testtype-all-options": self.CL_ALL_DISABLED,
868 elif trigger_id == "dd-ctrl-area":
870 dut = ctrl_panel.get("dd-ctrl-dut-value")
871 phy = ctrl_panel.get("dd-ctrl-phy-value")
874 {"label": v, "value": v}
875 for v in self.spec_tbs[dut][phy][dd_area].keys()
877 key=lambda d: d["label"]
884 "dd-ctrl-area-value": dd_area,
885 "dd-ctrl-test-value": str(),
886 "dd-ctrl-test-options": options,
887 "dd-ctrl-test-disabled": disabled,
888 "cl-ctrl-core-options": list(),
889 "cl-ctrl-core-value": list(),
890 "cl-ctrl-core-all-value": list(),
891 "cl-ctrl-core-all-options": self.CL_ALL_DISABLED,
892 "cl-ctrl-framesize-options": list(),
893 "cl-ctrl-framesize-value": list(),
894 "cl-ctrl-framesize-all-value": list(),
895 "cl-ctrl-framesize-all-options": self.CL_ALL_DISABLED,
896 "cl-ctrl-testtype-options": list(),
897 "cl-ctrl-testtype-value": list(),
898 "cl-ctrl-testtype-all-value": list(),
899 "cl-ctrl-testtype-all-options": self.CL_ALL_DISABLED,
901 elif trigger_id == "dd-ctrl-test":
903 framesize_opts = list()
904 testtype_opts = list()
905 dut = ctrl_panel.get("dd-ctrl-dut-value")
906 phy = ctrl_panel.get("dd-ctrl-phy-value")
907 area = ctrl_panel.get("dd-ctrl-area-value")
908 cores = self.spec_tbs[dut][phy][area][dd_test]["core"]
909 fsizes = self.spec_tbs[dut][phy][area][dd_test]["frame-size"]
910 ttypes = self.spec_tbs[dut][phy][area][dd_test]["test-type"]
911 if dut and phy and area and dd_test:
913 {"label": v, "value": v} for v in sorted(cores)
916 {"label": v, "value": v} for v in sorted(fsizes)
919 {"label": v, "value": v}for v in sorted(ttypes)
922 "dd-ctrl-test-value": dd_test,
923 "cl-ctrl-core-options": core_opts,
924 "cl-ctrl-core-value": list(),
925 "cl-ctrl-core-all-value": list(),
926 "cl-ctrl-core-all-options": self.CL_ALL_ENABLED,
927 "cl-ctrl-framesize-options": framesize_opts,
928 "cl-ctrl-framesize-value": list(),
929 "cl-ctrl-framesize-all-value": list(),
930 "cl-ctrl-framesize-all-options": self.CL_ALL_ENABLED,
931 "cl-ctrl-testtype-options": testtype_opts,
932 "cl-ctrl-testtype-value": list(),
933 "cl-ctrl-testtype-all-value": list(),
934 "cl-ctrl-testtype-all-options": self.CL_ALL_ENABLED,
936 elif trigger_id == "cl-ctrl-core":
937 val_sel, val_all = self._sync_checklists(
938 opt=ctrl_panel.get("cl-ctrl-core-options"),
944 "cl-ctrl-core-value": val_sel,
945 "cl-ctrl-core-all-value": val_all,
947 elif trigger_id == "cl-ctrl-core-all":
948 val_sel, val_all = self._sync_checklists(
949 opt = ctrl_panel.get("cl-ctrl-core-options"),
955 "cl-ctrl-core-value": val_sel,
956 "cl-ctrl-core-all-value": val_all,
958 elif trigger_id == "cl-ctrl-framesize":
959 val_sel, val_all = self._sync_checklists(
960 opt = ctrl_panel.get("cl-ctrl-framesize-options"),
966 "cl-ctrl-framesize-value": val_sel,
967 "cl-ctrl-framesize-all-value": val_all,
969 elif trigger_id == "cl-ctrl-framesize-all":
970 val_sel, val_all = self._sync_checklists(
971 opt = ctrl_panel.get("cl-ctrl-framesize-options"),
973 all=cl_framesize_all,
977 "cl-ctrl-framesize-value": val_sel,
978 "cl-ctrl-framesize-all-value": val_all,
980 elif trigger_id == "cl-ctrl-testtype":
981 val_sel, val_all = self._sync_checklists(
982 opt = ctrl_panel.get("cl-ctrl-testtype-options"),
988 "cl-ctrl-testtype-value": val_sel,
989 "cl-ctrl-testtype-all-value": val_all,
991 elif trigger_id == "cl-ctrl-testtype-all":
992 val_sel, val_all = self._sync_checklists(
993 opt = ctrl_panel.get("cl-ctrl-testtype-options"),
999 "cl-ctrl-testtype-value": val_sel,
1000 "cl-ctrl-testtype-all-value": val_all,
1002 elif trigger_id == "btn-ctrl-add":
1004 dut = ctrl_panel.get("dd-ctrl-dut-value")
1005 phy = ctrl_panel.get("dd-ctrl-phy-value")
1006 area = ctrl_panel.get("dd-ctrl-area-value")
1007 test = ctrl_panel.get("dd-ctrl-test-value")
1008 cores = ctrl_panel.get("cl-ctrl-core-value")
1009 framesizes = ctrl_panel.get("cl-ctrl-framesize-value")
1010 testtypes = ctrl_panel.get("cl-ctrl-testtype-value")
1011 # Add selected test to the list of tests in store:
1012 if all((dut, phy, area, test, cores, framesizes, testtypes)):
1013 if store_sel is None:
1016 for framesize in framesizes:
1017 for ttype in testtypes:
1021 dut, phy.replace('af_xdp', 'af-xdp'), area,
1022 framesize.lower(), core.lower(), test,
1025 if tid not in [itm["id"] for itm in store_sel]:
1032 "framesize": framesize.lower(),
1033 "core": core.lower(),
1034 "testtype": ttype.lower()
1036 store_sel = sorted(store_sel, key=lambda d: d["id"])
1037 row_card_sel_tests = self.STYLE_ENABLED
1038 row_btns_sel_tests = self.STYLE_ENABLED
1039 ctrl_panel.set(ctrl_panel.defaults)
1041 "cl-selected-options": self._list_tests(store_sel)
1043 row_fig_tput, row_fig_lat, row_btn_dwnld = \
1044 _generate_plotting_arrea(
1046 self.data, store_sel, self.layout, d_start,
1050 elif trigger_id == "dpr-period":
1051 row_fig_tput, row_fig_lat, row_btn_dwnld = \
1052 _generate_plotting_arrea(
1054 self.data, store_sel, self.layout, d_start, d_end
1057 elif trigger_id == "btn-sel-remove-all":
1059 row_fig_tput = self.PLACEHOLDER
1060 row_fig_lat = self.PLACEHOLDER
1061 row_btn_dwnld = self.PLACEHOLDER
1062 row_card_sel_tests = self.STYLE_DISABLED
1063 row_btns_sel_tests = self.STYLE_DISABLED
1066 "cl-selected-options": list()
1068 elif trigger_id == "btn-sel-remove":
1071 new_store_sel = list()
1072 for item in store_sel:
1073 if item["id"] not in list_sel:
1074 new_store_sel.append(item)
1075 store_sel = new_store_sel
1077 row_fig_tput, row_fig_lat, row_btn_dwnld = \
1078 _generate_plotting_arrea(
1080 self.data, store_sel, self.layout, d_start,
1085 "cl-selected-options": self._list_tests(store_sel)
1088 row_fig_tput = self.PLACEHOLDER
1089 row_fig_lat = self.PLACEHOLDER
1090 row_btn_dwnld = self.PLACEHOLDER
1091 row_card_sel_tests = self.STYLE_DISABLED
1092 row_btns_sel_tests = self.STYLE_DISABLED
1095 "cl-selected-options": list()
1098 if ctrl_panel.get("cl-ctrl-core-value") and \
1099 ctrl_panel.get("cl-ctrl-framesize-value") and \
1100 ctrl_panel.get("cl-ctrl-testtype-value"):
1105 "btn-ctrl-add-disabled": disabled
1109 ctrl_panel.panel, store_sel,
1110 row_fig_tput, row_fig_lat, row_btn_dwnld,
1111 row_card_sel_tests, row_btns_sel_tests
1113 ret_val.extend(ctrl_panel.values())
1117 Output("metadata-tput-lat", "children"),
1118 Output("metadata-hdrh-graph", "children"),
1119 Output("offcanvas-metadata", "is_open"),
1120 Input({"type": "graph", "index": ALL}, "clickData"),
1121 prevent_initial_call=True
1123 def _show_metadata_from_graphs(graph_data: dict) -> tuple:
1128 callback_context.triggered[0]["prop_id"].split(".")[0]
1130 idx = 0 if trigger_id == "tput" else 1
1131 graph_data = graph_data[idx]["points"][0]
1132 except (JSONDecodeError, IndexError, KeyError, ValueError,
1136 metadata = no_update
1141 [dbc.Badge(x.split(":")[0]), x.split(": ")[1]]
1142 ) for x in graph_data.get("text", "").split("<br>")
1144 if trigger_id == "tput":
1145 title = "Throughput"
1146 elif trigger_id == "lat":
1148 hdrh_data = graph_data.get("customdata", None)
1151 class_name="gy-2 p-0",
1153 dbc.CardHeader(hdrh_data.pop("name")),
1154 dbc.CardBody(children=[
1156 id="hdrh-latency-graph",
1157 figure=graph_hdrh_latency(
1158 hdrh_data, self.layout
1166 class_name="gy-2 p-0",
1168 dbc.CardHeader(children=[
1170 target_id="tput-lat-metadata",
1172 style={"display": "inline-block"}
1177 id="tput-lat-metadata",
1179 children=[dbc.ListGroup(children, flush=True), ]
1185 return metadata, graph, True
1188 Output("download-data", "data"),
1189 State("selected-tests", "data"),
1190 Input("btn-download-data", "n_clicks"),
1191 prevent_initial_call=True
1193 def _download_data(store_sel, n_clicks):
1204 for itm in store_sel:
1205 sel_data = select_trending_data(self.data, itm)
1206 if sel_data is None:
1208 df = pd.concat([df, sel_data], ignore_index=True)
1210 return dcc.send_data_frame(df.to_csv, "trending_data.csv")