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
32 from ..data.data import Data
33 from .graphs import graph_trending, graph_hdrh_latency, \
41 STYLE_DISABLED = {"display": "none"}
42 STYLE_ENABLED = {"display": "inherit"}
55 PLACEHOLDER = html.Nobr("")
57 DRIVERS = ("avf", "af-xdp", "rdma", "dpdk")
61 "container_memif": "LXC/DRC Container Memif",
62 "crypto": "IPSec IPv4 Routing",
63 "ip4": "IPv4 Routing",
64 "ip6": "IPv6 Routing",
65 "ip4_tunnels": "IPv4 Tunnels",
66 "l2": "L2 Ethernet Switching",
67 "srv6": "SRv6 Routing",
68 "vm_vhost": "VMs vhost-user",
69 "nfv_density-dcr_memif-chain_ipsec": "CNF Service Chains Routing IPSec",
70 "nfv_density-vm_vhost-chain_dot1qip4vxlan":"VNF Service Chains Tunnels",
71 "nfv_density-vm_vhost-chain": "VNF Service Chains Routing",
72 "nfv_density-dcr_memif-pipeline": "CNF Service Pipelines Routing",
73 "nfv_density-dcr_memif-chain": "CNF Service Chains Routing",
76 def __init__(self, app: Flask, html_layout_file: str, spec_file: str,
77 graph_layout_file: str, data_spec_file: str, tooltip_file: str,
78 time_period: str=None) -> None:
84 self._html_layout_file = html_layout_file
85 self._spec_file = spec_file
86 self._graph_layout_file = graph_layout_file
87 self._data_spec_file = data_spec_file
88 self._tooltip_file = tooltip_file
89 self._time_period = time_period
93 data_spec_file=self._data_spec_file,
95 ).read_trending_mrr(days=self._time_period)
98 data_spec_file=self._data_spec_file,
100 ).read_trending_ndrpdr(days=self._time_period)
102 self._data = pd.concat([data_mrr, data_ndrpdr], ignore_index=True)
105 (datetime.utcnow() - self._data["start_time"].min()).days
106 if self._time_period > data_time_period:
107 self._time_period = data_time_period
110 # Get structure of tests:
112 for _, row in self._data[["job", "test_id"]].drop_duplicates().\
114 lst_job = row["job"].split("-")
117 tbed = "-".join(lst_job[-2:])
118 lst_test = row["test_id"].split(".")
122 area = "-".join(lst_test[3:-2])
123 suite = lst_test[-2].replace("2n1l-", "").replace("1n1l-", "").\
126 nic = suite.split("-")[0]
127 for drv in self.DRIVERS:
133 test = test.replace(f"{drv}-", "")
137 infra = "-".join((tbed, nic, driver))
138 lst_test = test.split("-")
139 framesize = lst_test[0]
140 core = lst_test[1] if lst_test[1] else "8C"
141 test = "-".join(lst_test[2: -1])
143 if tbs.get(dut, None) is None:
145 if tbs[dut].get(infra, None) is None:
146 tbs[dut][infra] = dict()
147 if tbs[dut][infra].get(area, None) is None:
148 tbs[dut][infra][area] = dict()
149 if tbs[dut][infra][area].get(test, None) is None:
150 tbs[dut][infra][area][test] = dict()
151 tbs[dut][infra][area][test]["core"] = list()
152 tbs[dut][infra][area][test]["frame-size"] = list()
153 tbs[dut][infra][area][test]["test-type"] = list()
154 if core.upper() not in tbs[dut][infra][area][test]["core"]:
155 tbs[dut][infra][area][test]["core"].append(core.upper())
156 if framesize.upper() not in \
157 tbs[dut][infra][area][test]["frame-size"]:
158 tbs[dut][infra][area][test]["frame-size"].append(
161 if "MRR" not in tbs[dut][infra][area][test]["test-type"]:
162 tbs[dut][infra][area][test]["test-type"].append("MRR")
163 elif ttype == "ndrpdr":
164 if "NDR" not in tbs[dut][infra][area][test]["test-type"]:
165 tbs[dut][infra][area][test]["test-type"].extend(
170 self._html_layout = ""
171 self._graph_layout = None
172 self._tooltips = dict()
175 with open(self._html_layout_file, "r") as file_read:
176 self._html_layout = file_read.read()
177 except IOError as err:
179 f"Not possible to open the file {self._html_layout_file}\n{err}"
183 with open(self._graph_layout_file, "r") as file_read:
184 self._graph_layout = load(file_read, Loader=FullLoader)
185 except IOError as err:
187 f"Not possible to open the file {self._graph_layout_file}\n"
190 except YAMLError as err:
192 f"An error occurred while parsing the specification file "
193 f"{self._graph_layout_file}\n{err}"
197 with open(self._tooltip_file, "r") as file_read:
198 self._tooltips = load(file_read, Loader=FullLoader)
199 except IOError as err:
201 f"Not possible to open the file {self._tooltip_file}\n{err}"
203 except YAMLError as err:
205 f"An error occurred while parsing the specification file "
206 f"{self._tooltip_file}\n{err}"
210 if self._app is not None and hasattr(self, 'callbacks'):
211 self.callbacks(self._app)
214 def html_layout(self):
215 return self._html_layout
219 return self._spec_tbs
227 return self._graph_layout
230 def time_period(self):
231 return self._time_period
233 def label(self, key: str) -> str:
234 return self.LABELS.get(key, key)
236 def _show_tooltip(self, id: str, title: str) -> list:
247 class_name="border ms-1",
250 children=self._tooltips.get(id, str()),
256 def add_content(self):
259 if self.html_layout and self.spec_tbs:
273 id="offcanvas-metadata",
274 title="Throughput And Latency",
278 dbc.Row(id="metadata-tput-lat"),
279 dbc.Row(id="metadata-hdrh-graph"),
293 self._add_ctrl_col(),
294 self._add_plotting_col(),
312 def _add_navbar(self):
313 """Add nav element with navigation panel. It is placed on the top.
315 return dbc.NavbarSimple(
316 id="navbarsimple-main",
320 "Continuous Performance Trending",
329 brand_external_link=True,
334 def _add_ctrl_col(self) -> dbc.Col:
335 """Add column with controls. It is placed on the left side.
340 self._add_ctrl_panel(),
344 def _add_plotting_col(self) -> dbc.Col:
345 """Add column with plots and tables. It is placed on the right side.
348 id="col-plotting-area",
352 dbc.Row( # Throughput
354 class_name="g-0 p-2",
361 class_name="g-0 p-2",
367 id="row-btn-download",
368 class_name="g-0 p-2",
379 def _add_ctrl_panel(self) -> dbc.Row:
384 class_name="g-0 p-2",
392 children=self._show_tooltip(
398 "Select a Device under Test..."
402 {"label": k, "value": k} \
403 for k in self.spec_tbs.keys()
405 key=lambda d: d["label"]
420 children=self._show_tooltip(
421 "help-infra", "Infra")
426 "Select a Physical Test Bed "
442 children=self._show_tooltip(
447 placeholder="Select an Area...",
462 children=self._show_tooltip(
467 placeholder="Select a Test...",
477 id="row-ctrl-framesize",
481 children=self._show_tooltip(
482 "help-framesize", "Frame Size"),
488 id="cl-ctrl-framesize-all",
489 options=self.CL_ALL_DISABLED,
499 id="cl-ctrl-framesize",
512 children=self._show_tooltip(
513 "help-cores", "Number of Cores"),
519 id="cl-ctrl-core-all",
520 options=self.CL_ALL_DISABLED,
539 id="row-ctrl-testtype",
543 children=self._show_tooltip(
544 "help-ttype", "Test Type"),
550 id="cl-ctrl-testtype-all",
551 options=self.CL_ALL_DISABLED,
561 id="cl-ctrl-testtype",
570 class_name="gy-1 p-0",
576 children="Add Selected",
590 children=self._show_tooltip(
591 "help-time-period", "Time Period"),
595 className="d-flex justify-content-center",
597 datetime.utcnow() - timedelta(
598 days=self.time_period),
599 max_date_allowed=datetime.utcnow(),
600 initial_visible_month=datetime.utcnow(),
602 datetime.utcnow() - timedelta(
603 days=self.time_period),
604 end_date=datetime.utcnow(),
605 display_format="D MMM YY"
610 id="row-card-sel-tests",
612 style=self.STYLE_DISABLED,
619 class_name="overflow-auto",
623 style={"max-height": "12em"},
628 id="row-btns-sel-tests",
629 style=self.STYLE_DISABLED,
636 children="Remove Selected",
637 class_name="w-100 me-1",
642 id="btn-sel-remove-all",
643 children="Remove All",
644 class_name="w-100 me-1",
657 def __init__(self, panel: dict) -> None:
665 # Defines also the order of keys
667 "dd-ctrl-dut-value": str(),
668 "dd-ctrl-phy-options": list(),
669 "dd-ctrl-phy-disabled": True,
670 "dd-ctrl-phy-value": str(),
671 "dd-ctrl-area-options": list(),
672 "dd-ctrl-area-disabled": True,
673 "dd-ctrl-area-value": str(),
674 "dd-ctrl-test-options": list(),
675 "dd-ctrl-test-disabled": True,
676 "dd-ctrl-test-value": str(),
677 "cl-ctrl-core-options": list(),
678 "cl-ctrl-core-value": list(),
679 "cl-ctrl-core-all-value": list(),
680 "cl-ctrl-core-all-options": CL_ALL_DISABLED,
681 "cl-ctrl-framesize-options": list(),
682 "cl-ctrl-framesize-value": list(),
683 "cl-ctrl-framesize-all-value": list(),
684 "cl-ctrl-framesize-all-options": CL_ALL_DISABLED,
685 "cl-ctrl-testtype-options": list(),
686 "cl-ctrl-testtype-value": list(),
687 "cl-ctrl-testtype-all-value": list(),
688 "cl-ctrl-testtype-all-options": CL_ALL_DISABLED,
689 "btn-ctrl-add-disabled": True,
690 "cl-selected-options": list(),
693 self._panel = deepcopy(self._defaults)
695 for key in self._defaults:
696 self._panel[key] = panel[key]
699 def defaults(self) -> dict:
700 return self._defaults
703 def panel(self) -> dict:
706 def set(self, kwargs: dict) -> None:
707 for key, val in kwargs.items():
708 if key in self._panel:
709 self._panel[key] = val
711 raise KeyError(f"The key {key} is not defined.")
713 def get(self, key: str) -> any:
714 return self._panel[key]
716 def values(self) -> tuple:
717 return tuple(self._panel.values())
720 def _sync_checklists(opt: list, sel: list, all: list, id: str) -> tuple:
723 options = {v["value"] for v in opt}
725 sel = list(options) if all else list()
727 all = ["all", ] if set(sel) == options else list()
731 def _list_tests(selection: dict) -> list:
732 """Display selected tests with checkboxes
735 return [{"label": v["id"], "value": v["id"]} for v in selection]
739 def callbacks(self, app):
741 def _generate_plotting_arrea(args: tuple) -> tuple:
745 (fig_tput, fig_lat) = args
747 row_fig_tput = self.PLACEHOLDER
748 row_fig_lat = self.PLACEHOLDER
749 row_btn_dwnld = self.PLACEHOLDER
754 id={"type": "graph", "index": "tput"},
759 dcc.Loading(children=[
761 id="btn-download-data",
762 children=self._show_tooltip(
763 "help-download", "Download"),
767 dcc.Download(id="download-data")
773 id={"type": "graph", "index": "lat"},
778 return row_fig_tput, row_fig_lat, row_btn_dwnld
781 Output("control-panel", "data"), # Store
782 Output("selected-tests", "data"), # Store
783 Output("row-graph-tput", "children"),
784 Output("row-graph-lat", "children"),
785 Output("row-btn-download", "children"),
786 Output("row-card-sel-tests", "style"),
787 Output("row-btns-sel-tests", "style"),
788 Output("dd-ctrl-dut", "value"),
789 Output("dd-ctrl-phy", "options"),
790 Output("dd-ctrl-phy", "disabled"),
791 Output("dd-ctrl-phy", "value"),
792 Output("dd-ctrl-area", "options"),
793 Output("dd-ctrl-area", "disabled"),
794 Output("dd-ctrl-area", "value"),
795 Output("dd-ctrl-test", "options"),
796 Output("dd-ctrl-test", "disabled"),
797 Output("dd-ctrl-test", "value"),
798 Output("cl-ctrl-core", "options"),
799 Output("cl-ctrl-core", "value"),
800 Output("cl-ctrl-core-all", "value"),
801 Output("cl-ctrl-core-all", "options"),
802 Output("cl-ctrl-framesize", "options"),
803 Output("cl-ctrl-framesize", "value"),
804 Output("cl-ctrl-framesize-all", "value"),
805 Output("cl-ctrl-framesize-all", "options"),
806 Output("cl-ctrl-testtype", "options"),
807 Output("cl-ctrl-testtype", "value"),
808 Output("cl-ctrl-testtype-all", "value"),
809 Output("cl-ctrl-testtype-all", "options"),
810 Output("btn-ctrl-add", "disabled"),
811 Output("cl-selected", "options"), # User selection
812 State("control-panel", "data"), # Store
813 State("selected-tests", "data"), # Store
814 State("cl-selected", "value"), # User selection
815 Input("dd-ctrl-dut", "value"),
816 Input("dd-ctrl-phy", "value"),
817 Input("dd-ctrl-area", "value"),
818 Input("dd-ctrl-test", "value"),
819 Input("cl-ctrl-core", "value"),
820 Input("cl-ctrl-core-all", "value"),
821 Input("cl-ctrl-framesize", "value"),
822 Input("cl-ctrl-framesize-all", "value"),
823 Input("cl-ctrl-testtype", "value"),
824 Input("cl-ctrl-testtype-all", "value"),
825 Input("btn-ctrl-add", "n_clicks"),
826 Input("dpr-period", "start_date"),
827 Input("dpr-period", "end_date"),
828 Input("btn-sel-remove", "n_clicks"),
829 Input("btn-sel-remove-all", "n_clicks"),
831 def _update_ctrl_panel(cp_data: dict, store_sel: list, list_sel: list,
832 dd_dut: str, dd_phy: str, dd_area: str, dd_test: str, cl_core: list,
833 cl_core_all: list, cl_framesize: list, cl_framesize_all: list,
834 cl_testtype: list, cl_testtype_all: list, btn_add: int,
835 d_start: str, d_end: str, btn_remove: int,
836 btn_remove_all: int) -> tuple:
840 d_start = datetime(int(d_start[0:4]), int(d_start[5:7]),
842 d_end = datetime(int(d_end[0:4]), int(d_end[5:7]), int(d_end[8:10]))
844 row_fig_tput = no_update
845 row_fig_lat = no_update
846 row_btn_dwnld = no_update
847 row_card_sel_tests = no_update
848 row_btns_sel_tests = no_update
850 ctrl_panel = self.ControlPanel(cp_data)
852 trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
854 if trigger_id == "dd-ctrl-dut":
858 {"label": v, "value": v}
859 for v in self.spec_tbs[dd_dut].keys()
861 key=lambda d: d["label"]
868 "dd-ctrl-dut-value": dd_dut,
869 "dd-ctrl-phy-value": str(),
870 "dd-ctrl-phy-options": options,
871 "dd-ctrl-phy-disabled": disabled,
872 "dd-ctrl-area-value": str(),
873 "dd-ctrl-area-options": list(),
874 "dd-ctrl-area-disabled": True,
875 "dd-ctrl-test-options": list(),
876 "dd-ctrl-test-disabled": True,
877 "cl-ctrl-core-options": list(),
878 "cl-ctrl-core-value": list(),
879 "cl-ctrl-core-all-value": list(),
880 "cl-ctrl-core-all-options": self.CL_ALL_DISABLED,
881 "cl-ctrl-framesize-options": list(),
882 "cl-ctrl-framesize-value": list(),
883 "cl-ctrl-framesize-all-value": list(),
884 "cl-ctrl-framesize-all-options": self.CL_ALL_DISABLED,
885 "cl-ctrl-testtype-options": list(),
886 "cl-ctrl-testtype-value": list(),
887 "cl-ctrl-testtype-all-value": list(),
888 "cl-ctrl-testtype-all-options": self.CL_ALL_DISABLED,
890 if trigger_id == "dd-ctrl-phy":
892 dut = ctrl_panel.get("dd-ctrl-dut-value")
895 {"label": self.label(v), "value": v}
896 for v in self.spec_tbs[dut][dd_phy].keys()
898 key=lambda d: d["label"]
905 "dd-ctrl-phy-value": dd_phy,
906 "dd-ctrl-area-value": str(),
907 "dd-ctrl-area-options": options,
908 "dd-ctrl-area-disabled": disabled,
909 "dd-ctrl-test-options": list(),
910 "dd-ctrl-test-disabled": True,
911 "cl-ctrl-core-options": list(),
912 "cl-ctrl-core-value": list(),
913 "cl-ctrl-core-all-value": list(),
914 "cl-ctrl-core-all-options": self.CL_ALL_DISABLED,
915 "cl-ctrl-framesize-options": list(),
916 "cl-ctrl-framesize-value": list(),
917 "cl-ctrl-framesize-all-value": list(),
918 "cl-ctrl-framesize-all-options": self.CL_ALL_DISABLED,
919 "cl-ctrl-testtype-options": list(),
920 "cl-ctrl-testtype-value": list(),
921 "cl-ctrl-testtype-all-value": list(),
922 "cl-ctrl-testtype-all-options": self.CL_ALL_DISABLED,
924 elif trigger_id == "dd-ctrl-area":
926 dut = ctrl_panel.get("dd-ctrl-dut-value")
927 phy = ctrl_panel.get("dd-ctrl-phy-value")
930 {"label": v, "value": v}
931 for v in self.spec_tbs[dut][phy][dd_area].keys()
933 key=lambda d: d["label"]
940 "dd-ctrl-area-value": dd_area,
941 "dd-ctrl-test-value": str(),
942 "dd-ctrl-test-options": options,
943 "dd-ctrl-test-disabled": disabled,
944 "cl-ctrl-core-options": list(),
945 "cl-ctrl-core-value": list(),
946 "cl-ctrl-core-all-value": list(),
947 "cl-ctrl-core-all-options": self.CL_ALL_DISABLED,
948 "cl-ctrl-framesize-options": list(),
949 "cl-ctrl-framesize-value": list(),
950 "cl-ctrl-framesize-all-value": list(),
951 "cl-ctrl-framesize-all-options": self.CL_ALL_DISABLED,
952 "cl-ctrl-testtype-options": list(),
953 "cl-ctrl-testtype-value": list(),
954 "cl-ctrl-testtype-all-value": list(),
955 "cl-ctrl-testtype-all-options": self.CL_ALL_DISABLED,
957 elif trigger_id == "dd-ctrl-test":
959 framesize_opts = list()
960 testtype_opts = list()
961 dut = ctrl_panel.get("dd-ctrl-dut-value")
962 phy = ctrl_panel.get("dd-ctrl-phy-value")
963 area = ctrl_panel.get("dd-ctrl-area-value")
964 cores = self.spec_tbs[dut][phy][area][dd_test]["core"]
965 fsizes = self.spec_tbs[dut][phy][area][dd_test]["frame-size"]
966 ttypes = self.spec_tbs[dut][phy][area][dd_test]["test-type"]
967 if dut and phy and area and dd_test:
969 {"label": v, "value": v} for v in sorted(cores)
972 {"label": v, "value": v} for v in sorted(fsizes)
975 {"label": v, "value": v}for v in sorted(ttypes)
978 "dd-ctrl-test-value": dd_test,
979 "cl-ctrl-core-options": core_opts,
980 "cl-ctrl-core-value": list(),
981 "cl-ctrl-core-all-value": list(),
982 "cl-ctrl-core-all-options": self.CL_ALL_ENABLED,
983 "cl-ctrl-framesize-options": framesize_opts,
984 "cl-ctrl-framesize-value": list(),
985 "cl-ctrl-framesize-all-value": list(),
986 "cl-ctrl-framesize-all-options": self.CL_ALL_ENABLED,
987 "cl-ctrl-testtype-options": testtype_opts,
988 "cl-ctrl-testtype-value": list(),
989 "cl-ctrl-testtype-all-value": list(),
990 "cl-ctrl-testtype-all-options": self.CL_ALL_ENABLED,
992 elif trigger_id == "cl-ctrl-core":
993 val_sel, val_all = self._sync_checklists(
994 opt=ctrl_panel.get("cl-ctrl-core-options"),
1000 "cl-ctrl-core-value": val_sel,
1001 "cl-ctrl-core-all-value": val_all,
1003 elif trigger_id == "cl-ctrl-core-all":
1004 val_sel, val_all = self._sync_checklists(
1005 opt = ctrl_panel.get("cl-ctrl-core-options"),
1011 "cl-ctrl-core-value": val_sel,
1012 "cl-ctrl-core-all-value": val_all,
1014 elif trigger_id == "cl-ctrl-framesize":
1015 val_sel, val_all = self._sync_checklists(
1016 opt = ctrl_panel.get("cl-ctrl-framesize-options"),
1022 "cl-ctrl-framesize-value": val_sel,
1023 "cl-ctrl-framesize-all-value": val_all,
1025 elif trigger_id == "cl-ctrl-framesize-all":
1026 val_sel, val_all = self._sync_checklists(
1027 opt = ctrl_panel.get("cl-ctrl-framesize-options"),
1029 all=cl_framesize_all,
1033 "cl-ctrl-framesize-value": val_sel,
1034 "cl-ctrl-framesize-all-value": val_all,
1036 elif trigger_id == "cl-ctrl-testtype":
1037 val_sel, val_all = self._sync_checklists(
1038 opt = ctrl_panel.get("cl-ctrl-testtype-options"),
1044 "cl-ctrl-testtype-value": val_sel,
1045 "cl-ctrl-testtype-all-value": val_all,
1047 elif trigger_id == "cl-ctrl-testtype-all":
1048 val_sel, val_all = self._sync_checklists(
1049 opt = ctrl_panel.get("cl-ctrl-testtype-options"),
1051 all=cl_testtype_all,
1055 "cl-ctrl-testtype-value": val_sel,
1056 "cl-ctrl-testtype-all-value": val_all,
1058 elif trigger_id == "btn-ctrl-add":
1060 dut = ctrl_panel.get("dd-ctrl-dut-value")
1061 phy = ctrl_panel.get("dd-ctrl-phy-value")
1062 area = ctrl_panel.get("dd-ctrl-area-value")
1063 test = ctrl_panel.get("dd-ctrl-test-value")
1064 cores = ctrl_panel.get("cl-ctrl-core-value")
1065 framesizes = ctrl_panel.get("cl-ctrl-framesize-value")
1066 testtypes = ctrl_panel.get("cl-ctrl-testtype-value")
1067 # Add selected test to the list of tests in store:
1068 if all((dut, phy, area, test, cores, framesizes, testtypes)):
1069 if store_sel is None:
1072 for framesize in framesizes:
1073 for ttype in testtypes:
1077 dut, phy.replace('af_xdp', 'af-xdp'), area,
1078 framesize.lower(), core.lower(), test,
1081 if tid not in [itm["id"] for itm in store_sel]:
1088 "framesize": framesize.lower(),
1089 "core": core.lower(),
1090 "testtype": ttype.lower()
1092 store_sel = sorted(store_sel, key=lambda d: d["id"])
1093 row_card_sel_tests = self.STYLE_ENABLED
1094 row_btns_sel_tests = self.STYLE_ENABLED
1095 ctrl_panel.set(ctrl_panel.defaults)
1097 "cl-selected-options": self._list_tests(store_sel)
1099 row_fig_tput, row_fig_lat, row_btn_dwnld = \
1100 _generate_plotting_arrea(
1102 self.data, store_sel, self.layout, d_start,
1106 elif trigger_id == "dpr-period":
1107 row_fig_tput, row_fig_lat, row_btn_dwnld = \
1108 _generate_plotting_arrea(
1110 self.data, store_sel, self.layout, d_start, d_end
1113 elif trigger_id == "btn-sel-remove-all":
1115 row_fig_tput = self.PLACEHOLDER
1116 row_fig_lat = self.PLACEHOLDER
1117 row_btn_dwnld = self.PLACEHOLDER
1118 row_card_sel_tests = self.STYLE_DISABLED
1119 row_btns_sel_tests = self.STYLE_DISABLED
1122 "cl-selected-options": list()
1124 elif trigger_id == "btn-sel-remove":
1127 new_store_sel = list()
1128 for item in store_sel:
1129 if item["id"] not in list_sel:
1130 new_store_sel.append(item)
1131 store_sel = new_store_sel
1133 row_fig_tput, row_fig_lat, row_btn_dwnld = \
1134 _generate_plotting_arrea(
1136 self.data, store_sel, self.layout, d_start,
1141 "cl-selected-options": self._list_tests(store_sel)
1144 row_fig_tput = self.PLACEHOLDER
1145 row_fig_lat = self.PLACEHOLDER
1146 row_btn_dwnld = self.PLACEHOLDER
1147 row_card_sel_tests = self.STYLE_DISABLED
1148 row_btns_sel_tests = self.STYLE_DISABLED
1151 "cl-selected-options": list()
1154 if ctrl_panel.get("cl-ctrl-core-value") and \
1155 ctrl_panel.get("cl-ctrl-framesize-value") and \
1156 ctrl_panel.get("cl-ctrl-testtype-value"):
1161 "btn-ctrl-add-disabled": disabled
1165 ctrl_panel.panel, store_sel,
1166 row_fig_tput, row_fig_lat, row_btn_dwnld,
1167 row_card_sel_tests, row_btns_sel_tests
1169 ret_val.extend(ctrl_panel.values())
1173 Output("metadata-tput-lat", "children"),
1174 Output("metadata-hdrh-graph", "children"),
1175 Output("offcanvas-metadata", "is_open"),
1176 Input({"type": "graph", "index": ALL}, "clickData"),
1177 prevent_initial_call=True
1179 def _show_metadata_from_graphs(graph_data: dict) -> tuple:
1184 callback_context.triggered[0]["prop_id"].split(".")[0]
1186 idx = 0 if trigger_id == "tput" else 1
1187 graph_data = graph_data[idx]["points"][0]
1188 except (JSONDecodeError, IndexError, KeyError, ValueError,
1192 metadata = no_update
1197 [dbc.Badge(x.split(":")[0]), x.split(": ")[1]]
1198 ) for x in graph_data.get("text", "").split("<br>")
1200 if trigger_id == "tput":
1201 title = "Throughput"
1202 elif trigger_id == "lat":
1204 hdrh_data = graph_data.get("customdata", None)
1207 class_name="gy-2 p-0",
1209 dbc.CardHeader(hdrh_data.pop("name")),
1210 dbc.CardBody(children=[
1212 id="hdrh-latency-graph",
1213 figure=graph_hdrh_latency(
1214 hdrh_data, self.layout
1222 class_name="gy-2 p-0",
1224 dbc.CardHeader(children=[
1226 target_id="tput-lat-metadata",
1228 style={"display": "inline-block"}
1233 id="tput-lat-metadata",
1235 children=[dbc.ListGroup(children, flush=True), ]
1241 return metadata, graph, True
1244 Output("download-data", "data"),
1245 State("selected-tests", "data"),
1246 Input("btn-download-data", "n_clicks"),
1247 prevent_initial_call=True
1249 def _download_data(store_sel, n_clicks):
1260 for itm in store_sel:
1261 sel_data = select_trending_data(self.data, itm)
1262 if sel_data is None:
1264 df = pd.concat([df, sel_data], ignore_index=True)
1266 return dcc.send_data_frame(df.to_csv, "trending_data.csv")