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.utils import show_tooltip, label, sync_checklists, list_tests, \
36 from ..utils.url_processing import url_decode
37 from ..data.data import Data
38 from .graphs import graph_trending, graph_hdrh_latency, \
46 def __init__(self, app: Flask, html_layout_file: str,
47 graph_layout_file: str, data_spec_file: str, tooltip_file: str,
48 time_period: str=None) -> None:
54 self._html_layout_file = html_layout_file
55 self._graph_layout_file = graph_layout_file
56 self._data_spec_file = data_spec_file
57 self._tooltip_file = tooltip_file
58 self._time_period = time_period
62 data_spec_file=self._data_spec_file,
64 ).read_trending_mrr(days=self._time_period)
67 data_spec_file=self._data_spec_file,
69 ).read_trending_ndrpdr(days=self._time_period)
71 self._data = pd.concat([data_mrr, data_ndrpdr], ignore_index=True)
74 (datetime.utcnow() - self._data["start_time"].min()).days
75 if self._time_period > data_time_period:
76 self._time_period = data_time_period
79 # Get structure of tests:
81 for _, row in self._data[["job", "test_id"]].drop_duplicates().\
83 lst_job = row["job"].split("-")
86 tbed = "-".join(lst_job[-2:])
87 lst_test = row["test_id"].split(".")
91 area = "-".join(lst_test[3:-2])
92 suite = lst_test[-2].replace("2n1l-", "").replace("1n1l-", "").\
95 nic = suite.split("-")[0]
102 test = test.replace(f"{drv}-", "")
106 infra = "-".join((tbed, nic, driver))
107 lst_test = test.split("-")
108 framesize = lst_test[0]
109 core = lst_test[1] if lst_test[1] else "8C"
110 test = "-".join(lst_test[2: -1])
112 if tbs.get(dut, None) is None:
114 if tbs[dut].get(infra, None) is None:
115 tbs[dut][infra] = dict()
116 if tbs[dut][infra].get(area, None) is None:
117 tbs[dut][infra][area] = dict()
118 if tbs[dut][infra][area].get(test, None) is None:
119 tbs[dut][infra][area][test] = dict()
120 tbs[dut][infra][area][test]["core"] = list()
121 tbs[dut][infra][area][test]["frame-size"] = list()
122 tbs[dut][infra][area][test]["test-type"] = list()
123 if core.upper() not in tbs[dut][infra][area][test]["core"]:
124 tbs[dut][infra][area][test]["core"].append(core.upper())
125 if framesize.upper() not in \
126 tbs[dut][infra][area][test]["frame-size"]:
127 tbs[dut][infra][area][test]["frame-size"].append(
130 if "MRR" not in tbs[dut][infra][area][test]["test-type"]:
131 tbs[dut][infra][area][test]["test-type"].append("MRR")
132 elif ttype == "ndrpdr":
133 if "NDR" not in tbs[dut][infra][area][test]["test-type"]:
134 tbs[dut][infra][area][test]["test-type"].extend(
139 self._html_layout = ""
140 self._graph_layout = None
141 self._tooltips = dict()
144 with open(self._html_layout_file, "r") as file_read:
145 self._html_layout = file_read.read()
146 except IOError as err:
148 f"Not possible to open the file {self._html_layout_file}\n{err}"
152 with open(self._graph_layout_file, "r") as file_read:
153 self._graph_layout = load(file_read, Loader=FullLoader)
154 except IOError as err:
156 f"Not possible to open the file {self._graph_layout_file}\n"
159 except YAMLError as err:
161 f"An error occurred while parsing the specification file "
162 f"{self._graph_layout_file}\n{err}"
166 with open(self._tooltip_file, "r") as file_read:
167 self._tooltips = load(file_read, Loader=FullLoader)
168 except IOError as err:
170 f"Not possible to open the file {self._tooltip_file}\n{err}"
172 except YAMLError as err:
174 f"An error occurred while parsing the specification file "
175 f"{self._tooltip_file}\n{err}"
179 if self._app is not None and hasattr(self, 'callbacks'):
180 self.callbacks(self._app)
183 def html_layout(self):
184 return self._html_layout
188 return self._spec_tbs
196 return self._graph_layout
199 def time_period(self):
200 return self._time_period
202 def add_content(self):
205 if self.html_layout and self.spec_tbs:
219 id="offcanvas-metadata",
220 title="Throughput And Latency",
224 dbc.Row(id="metadata-tput-lat"),
225 dbc.Row(id="metadata-hdrh-graph"),
233 dcc.Store(id="selected-tests"),
234 dcc.Store(id="control-panel"),
235 dcc.Location(id="url", refresh=False),
236 self._add_ctrl_col(),
237 self._add_plotting_col(),
255 def _add_navbar(self):
256 """Add nav element with navigation panel. It is placed on the top.
258 return dbc.NavbarSimple(
259 id="navbarsimple-main",
263 "Continuous Performance Trending",
272 brand_external_link=True,
277 def _add_ctrl_col(self) -> dbc.Col:
278 """Add column with controls. It is placed on the left side.
283 self._add_ctrl_panel(),
287 def _add_plotting_col(self) -> dbc.Col:
288 """Add column with plots and tables. It is placed on the right side.
291 id="col-plotting-area",
295 dbc.Row( # Throughput
297 class_name="g-0 p-2",
304 class_name="g-0 p-2",
310 id="row-btn-download",
311 class_name="g-0 p-2",
322 def _add_ctrl_panel(self) -> dbc.Row:
327 class_name="g-0 p-2",
335 children=show_tooltip(self._tooltips,
341 "Select a Device under Test..."
345 {"label": k, "value": k} \
346 for k in self.spec_tbs.keys()
348 key=lambda d: d["label"]
363 children=show_tooltip(self._tooltips,
364 "help-infra", "Infra")
369 "Select a Physical Test Bed "
385 children=show_tooltip(self._tooltips,
390 placeholder="Select an Area...",
405 children=show_tooltip(self._tooltips,
410 placeholder="Select a Test...",
420 id="row-ctrl-framesize",
424 children=show_tooltip(self._tooltips,
425 "help-framesize", "Frame Size"),
431 id="cl-ctrl-framesize-all",
432 options=C.CL_ALL_DISABLED,
442 id="cl-ctrl-framesize",
455 children=show_tooltip(self._tooltips,
456 "help-cores", "Number of Cores"),
462 id="cl-ctrl-core-all",
463 options=C.CL_ALL_DISABLED,
482 id="row-ctrl-testtype",
486 children=show_tooltip(self._tooltips,
487 "help-ttype", "Test Type"),
493 id="cl-ctrl-testtype-all",
494 options=C.CL_ALL_DISABLED,
504 id="cl-ctrl-testtype",
513 id="row-ctrl-normalize",
517 children=show_tooltip(self._tooltips,
518 "help-normalize", "Normalize"),
524 id="cl-ctrl-normalize",
526 "value": "normalize",
528 "Normalize results to CPU"
541 class_name="gy-1 p-0",
547 children="Add Selected",
561 children=show_tooltip(self._tooltips,
562 "help-time-period", "Time Period"),
566 className="d-flex justify-content-center",
568 datetime.utcnow() - timedelta(
569 days=self.time_period),
570 max_date_allowed=datetime.utcnow(),
571 initial_visible_month=datetime.utcnow(),
573 datetime.utcnow() - timedelta(
574 days=self.time_period),
575 end_date=datetime.utcnow(),
576 display_format="D MMM YY"
581 id="row-card-sel-tests",
583 style=C.STYLE_DISABLED,
590 class_name="overflow-auto",
594 style={"max-height": "12em"},
599 id="row-btns-sel-tests",
600 style=C.STYLE_DISABLED,
607 children="Remove Selected",
608 class_name="w-100 me-1",
613 id="btn-sel-remove-all",
614 children="Remove All",
615 class_name="w-100 me-1",
628 def __init__(self, panel: dict) -> None:
630 # Defines also the order of keys
632 "dd-ctrl-dut-value": str(),
633 "dd-ctrl-phy-options": list(),
634 "dd-ctrl-phy-disabled": True,
635 "dd-ctrl-phy-value": str(),
636 "dd-ctrl-area-options": list(),
637 "dd-ctrl-area-disabled": True,
638 "dd-ctrl-area-value": str(),
639 "dd-ctrl-test-options": list(),
640 "dd-ctrl-test-disabled": True,
641 "dd-ctrl-test-value": str(),
642 "cl-ctrl-core-options": list(),
643 "cl-ctrl-core-value": list(),
644 "cl-ctrl-core-all-value": list(),
645 "cl-ctrl-core-all-options": C.CL_ALL_DISABLED,
646 "cl-ctrl-framesize-options": list(),
647 "cl-ctrl-framesize-value": list(),
648 "cl-ctrl-framesize-all-value": list(),
649 "cl-ctrl-framesize-all-options": C.CL_ALL_DISABLED,
650 "cl-ctrl-testtype-options": list(),
651 "cl-ctrl-testtype-value": list(),
652 "cl-ctrl-testtype-all-value": list(),
653 "cl-ctrl-testtype-all-options": C.CL_ALL_DISABLED,
654 "btn-ctrl-add-disabled": True,
655 "cl-normalize-value": list(),
656 "cl-selected-options": list(),
659 self._panel = deepcopy(self._defaults)
661 for key in self._defaults:
662 self._panel[key] = panel[key]
665 def defaults(self) -> dict:
666 return self._defaults
669 def panel(self) -> dict:
672 def set(self, kwargs: dict) -> None:
673 for key, val in kwargs.items():
674 if key in self._panel:
675 self._panel[key] = val
677 raise KeyError(f"The key {key} is not defined.")
679 def get(self, key: str) -> any:
680 return self._panel[key]
682 def values(self) -> tuple:
683 return tuple(self._panel.values())
685 def callbacks(self, app):
687 def _generate_plotting_area(figs: tuple, url: str) -> tuple:
691 (fig_tput, fig_lat) = figs
693 row_fig_tput = C.PLACEHOLDER
694 row_fig_lat = C.PLACEHOLDER
695 row_btn_dwnld = C.PLACEHOLDER
700 id={"type": "graph", "index": "tput"},
708 dcc.Loading(children=[
710 id="btn-download-data",
711 children=show_tooltip(self._tooltips,
712 "help-download", "Download Data"),
716 dcc.Download(id="download-data")
728 children=show_tooltip(self._tooltips,
729 "help-url", "URL", "input-url")
746 id={"type": "graph", "index": "lat"},
751 return row_fig_tput, row_fig_lat, row_btn_dwnld
754 Output("control-panel", "data"), # Store
755 Output("selected-tests", "data"), # Store
756 Output("row-graph-tput", "children"),
757 Output("row-graph-lat", "children"),
758 Output("row-btn-download", "children"),
759 Output("row-card-sel-tests", "style"),
760 Output("row-btns-sel-tests", "style"),
761 Output("dd-ctrl-dut", "value"),
762 Output("dd-ctrl-phy", "options"),
763 Output("dd-ctrl-phy", "disabled"),
764 Output("dd-ctrl-phy", "value"),
765 Output("dd-ctrl-area", "options"),
766 Output("dd-ctrl-area", "disabled"),
767 Output("dd-ctrl-area", "value"),
768 Output("dd-ctrl-test", "options"),
769 Output("dd-ctrl-test", "disabled"),
770 Output("dd-ctrl-test", "value"),
771 Output("cl-ctrl-core", "options"),
772 Output("cl-ctrl-core", "value"),
773 Output("cl-ctrl-core-all", "value"),
774 Output("cl-ctrl-core-all", "options"),
775 Output("cl-ctrl-framesize", "options"),
776 Output("cl-ctrl-framesize", "value"),
777 Output("cl-ctrl-framesize-all", "value"),
778 Output("cl-ctrl-framesize-all", "options"),
779 Output("cl-ctrl-testtype", "options"),
780 Output("cl-ctrl-testtype", "value"),
781 Output("cl-ctrl-testtype-all", "value"),
782 Output("cl-ctrl-testtype-all", "options"),
783 Output("btn-ctrl-add", "disabled"),
784 Output("cl-ctrl-normalize", "value"),
785 Output("cl-selected", "options"), # User selection
786 State("control-panel", "data"), # Store
787 State("selected-tests", "data"), # Store
788 State("cl-selected", "value"), # User selection
789 Input("dd-ctrl-dut", "value"),
790 Input("dd-ctrl-phy", "value"),
791 Input("dd-ctrl-area", "value"),
792 Input("dd-ctrl-test", "value"),
793 Input("cl-ctrl-core", "value"),
794 Input("cl-ctrl-core-all", "value"),
795 Input("cl-ctrl-framesize", "value"),
796 Input("cl-ctrl-framesize-all", "value"),
797 Input("cl-ctrl-testtype", "value"),
798 Input("cl-ctrl-testtype-all", "value"),
799 Input("cl-ctrl-normalize", "value"),
800 Input("btn-ctrl-add", "n_clicks"),
801 Input("dpr-period", "start_date"),
802 Input("dpr-period", "end_date"),
803 Input("btn-sel-remove", "n_clicks"),
804 Input("btn-sel-remove-all", "n_clicks"),
807 def _update_ctrl_panel(cp_data: dict, store_sel: list, list_sel: list,
808 dd_dut: str, dd_phy: str, dd_area: str, dd_test: str, cl_core: list,
809 cl_core_all: list, cl_framesize: list, cl_framesize_all: list,
810 cl_testtype: list, cl_testtype_all: list, cl_normalize: list,
811 btn_add: int, d_start: str, d_end: str, btn_remove: int,
812 btn_remove_all: int, href: str) -> tuple:
816 ctrl_panel = self.ControlPanel(cp_data)
818 d_start = get_date(d_start)
819 d_end = get_date(d_end)
822 parsed_url = url_decode(href)
824 row_fig_tput = no_update
825 row_fig_lat = no_update
826 row_btn_dwnld = no_update
827 row_card_sel_tests = no_update
828 row_btns_sel_tests = no_update
830 trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
832 if trigger_id == "dd-ctrl-dut":
834 dut = self.spec_tbs[dd_dut]
836 [{"label": v, "value": v}for v in dut.keys()],
837 key=lambda d: d["label"]
844 "dd-ctrl-dut-value": dd_dut,
845 "dd-ctrl-phy-value": str(),
846 "dd-ctrl-phy-options": options,
847 "dd-ctrl-phy-disabled": disabled,
848 "dd-ctrl-area-value": str(),
849 "dd-ctrl-area-options": list(),
850 "dd-ctrl-area-disabled": True,
851 "dd-ctrl-test-value": str(),
852 "dd-ctrl-test-options": list(),
853 "dd-ctrl-test-disabled": True,
854 "cl-ctrl-core-options": list(),
855 "cl-ctrl-core-value": list(),
856 "cl-ctrl-core-all-value": list(),
857 "cl-ctrl-core-all-options": C.CL_ALL_DISABLED,
858 "cl-ctrl-framesize-options": list(),
859 "cl-ctrl-framesize-value": list(),
860 "cl-ctrl-framesize-all-value": list(),
861 "cl-ctrl-framesize-all-options": C.CL_ALL_DISABLED,
862 "cl-ctrl-testtype-options": list(),
863 "cl-ctrl-testtype-value": list(),
864 "cl-ctrl-testtype-all-value": list(),
865 "cl-ctrl-testtype-all-options": C.CL_ALL_DISABLED,
867 elif trigger_id == "dd-ctrl-phy":
869 dut = ctrl_panel.get("dd-ctrl-dut-value")
870 phy = self.spec_tbs[dut][dd_phy]
872 [{"label": label(v), "value": v} for v in phy.keys()],
873 key=lambda d: d["label"]
880 "dd-ctrl-phy-value": dd_phy,
881 "dd-ctrl-area-value": str(),
882 "dd-ctrl-area-options": options,
883 "dd-ctrl-area-disabled": disabled,
884 "dd-ctrl-test-value": str(),
885 "dd-ctrl-test-options": list(),
886 "dd-ctrl-test-disabled": True,
887 "cl-ctrl-core-options": list(),
888 "cl-ctrl-core-value": list(),
889 "cl-ctrl-core-all-value": list(),
890 "cl-ctrl-core-all-options": C.CL_ALL_DISABLED,
891 "cl-ctrl-framesize-options": list(),
892 "cl-ctrl-framesize-value": list(),
893 "cl-ctrl-framesize-all-value": list(),
894 "cl-ctrl-framesize-all-options": C.CL_ALL_DISABLED,
895 "cl-ctrl-testtype-options": list(),
896 "cl-ctrl-testtype-value": list(),
897 "cl-ctrl-testtype-all-value": list(),
898 "cl-ctrl-testtype-all-options": C.CL_ALL_DISABLED,
900 elif trigger_id == "dd-ctrl-area":
902 dut = ctrl_panel.get("dd-ctrl-dut-value")
903 phy = ctrl_panel.get("dd-ctrl-phy-value")
904 area = self.spec_tbs[dut][phy][dd_area]
906 [{"label": v, "value": v} for v in area.keys()],
907 key=lambda d: d["label"]
914 "dd-ctrl-area-value": dd_area,
915 "dd-ctrl-test-value": str(),
916 "dd-ctrl-test-options": options,
917 "dd-ctrl-test-disabled": disabled,
918 "cl-ctrl-core-options": list(),
919 "cl-ctrl-core-value": list(),
920 "cl-ctrl-core-all-value": list(),
921 "cl-ctrl-core-all-options": C.CL_ALL_DISABLED,
922 "cl-ctrl-framesize-options": list(),
923 "cl-ctrl-framesize-value": list(),
924 "cl-ctrl-framesize-all-value": list(),
925 "cl-ctrl-framesize-all-options": C.CL_ALL_DISABLED,
926 "cl-ctrl-testtype-options": list(),
927 "cl-ctrl-testtype-value": list(),
928 "cl-ctrl-testtype-all-value": list(),
929 "cl-ctrl-testtype-all-options": C.CL_ALL_DISABLED,
931 elif trigger_id == "dd-ctrl-test":
933 framesize_opts = list()
934 testtype_opts = list()
935 dut = ctrl_panel.get("dd-ctrl-dut-value")
936 phy = ctrl_panel.get("dd-ctrl-phy-value")
937 area = ctrl_panel.get("dd-ctrl-area-value")
938 test = self.spec_tbs[dut][phy][area][dd_test]
940 fsizes = test["frame-size"]
941 ttypes = test["test-type"]
942 if dut and phy and area and dd_test:
943 core_opts = [{"label": v, "value": v}
944 for v in sorted(cores)]
945 framesize_opts = [{"label": v, "value": v}
946 for v in sorted(fsizes)]
947 testtype_opts = [{"label": v, "value": v}
948 for v in sorted(ttypes)]
950 "dd-ctrl-test-value": dd_test,
951 "cl-ctrl-core-options": core_opts,
952 "cl-ctrl-core-value": list(),
953 "cl-ctrl-core-all-value": list(),
954 "cl-ctrl-core-all-options": C.CL_ALL_ENABLED,
955 "cl-ctrl-framesize-options": framesize_opts,
956 "cl-ctrl-framesize-value": list(),
957 "cl-ctrl-framesize-all-value": list(),
958 "cl-ctrl-framesize-all-options": C.CL_ALL_ENABLED,
959 "cl-ctrl-testtype-options": testtype_opts,
960 "cl-ctrl-testtype-value": list(),
961 "cl-ctrl-testtype-all-value": list(),
962 "cl-ctrl-testtype-all-options": C.CL_ALL_ENABLED,
964 elif trigger_id == "cl-ctrl-core":
965 val_sel, val_all = sync_checklists(
966 options=ctrl_panel.get("cl-ctrl-core-options"),
972 "cl-ctrl-core-value": val_sel,
973 "cl-ctrl-core-all-value": val_all,
975 elif trigger_id == "cl-ctrl-core-all":
976 val_sel, val_all = sync_checklists(
977 options = ctrl_panel.get("cl-ctrl-core-options"),
983 "cl-ctrl-core-value": val_sel,
984 "cl-ctrl-core-all-value": val_all,
986 elif trigger_id == "cl-ctrl-framesize":
987 val_sel, val_all = sync_checklists(
988 options = ctrl_panel.get("cl-ctrl-framesize-options"),
994 "cl-ctrl-framesize-value": val_sel,
995 "cl-ctrl-framesize-all-value": val_all,
997 elif trigger_id == "cl-ctrl-framesize-all":
998 val_sel, val_all = sync_checklists(
999 options = ctrl_panel.get("cl-ctrl-framesize-options"),
1001 all=cl_framesize_all,
1005 "cl-ctrl-framesize-value": val_sel,
1006 "cl-ctrl-framesize-all-value": val_all,
1008 elif trigger_id == "cl-ctrl-testtype":
1009 val_sel, val_all = sync_checklists(
1010 options = ctrl_panel.get("cl-ctrl-testtype-options"),
1016 "cl-ctrl-testtype-value": val_sel,
1017 "cl-ctrl-testtype-all-value": val_all,
1019 elif trigger_id == "cl-ctrl-testtype-all":
1020 val_sel, val_all = sync_checklists(
1021 options = ctrl_panel.get("cl-ctrl-testtype-options"),
1023 all=cl_testtype_all,
1027 "cl-ctrl-testtype-value": val_sel,
1028 "cl-ctrl-testtype-all-value": val_all,
1030 elif trigger_id == "btn-ctrl-add":
1032 dut = ctrl_panel.get("dd-ctrl-dut-value")
1033 phy = ctrl_panel.get("dd-ctrl-phy-value")
1034 area = ctrl_panel.get("dd-ctrl-area-value")
1035 test = ctrl_panel.get("dd-ctrl-test-value")
1036 cores = ctrl_panel.get("cl-ctrl-core-value")
1037 framesizes = ctrl_panel.get("cl-ctrl-framesize-value")
1038 testtypes = ctrl_panel.get("cl-ctrl-testtype-value")
1039 # Add selected test to the list of tests in store:
1040 if all((dut, phy, area, test, cores, framesizes, testtypes)):
1041 if store_sel is None:
1044 for framesize in framesizes:
1045 for ttype in testtypes:
1049 dut, phy.replace('af_xdp', 'af-xdp'), area,
1050 framesize.lower(), core.lower(), test,
1053 if tid not in [itm["id"] for itm in store_sel]:
1060 "framesize": framesize.lower(),
1061 "core": core.lower(),
1062 "testtype": ttype.lower()
1064 store_sel = sorted(store_sel, key=lambda d: d["id"])
1065 row_card_sel_tests = C.STYLE_ENABLED
1066 row_btns_sel_tests = C.STYLE_ENABLED
1067 if C.CLEAR_ALL_INPUTS:
1068 ctrl_panel.set(ctrl_panel.defaults)
1069 elif trigger_id == "btn-sel-remove-all":
1071 row_fig_tput = C.PLACEHOLDER
1072 row_fig_lat = C.PLACEHOLDER
1073 row_btn_dwnld = C.PLACEHOLDER
1074 row_card_sel_tests = C.STYLE_DISABLED
1075 row_btns_sel_tests = C.STYLE_DISABLED
1077 ctrl_panel.set({"cl-selected-options": list()})
1078 elif trigger_id == "btn-sel-remove":
1081 new_store_sel = list()
1082 for item in store_sel:
1083 if item["id"] not in list_sel:
1084 new_store_sel.append(item)
1085 store_sel = new_store_sel
1086 elif trigger_id == "url":
1087 # TODO: Add verification
1088 url_params = parsed_url["params"]
1090 store_sel = literal_eval(
1091 url_params.get("store_sel", list())[0])
1092 d_start = get_date(url_params.get("start", list())[0])
1093 d_end = get_date(url_params.get("end", list())[0])
1095 row_card_sel_tests = C.STYLE_ENABLED
1096 row_btns_sel_tests = C.STYLE_ENABLED
1098 if trigger_id in ("btn-ctrl-add", "url", "dpr-period",
1099 "btn-sel-remove", "cl-ctrl-normalize"):
1101 row_fig_tput, row_fig_lat, row_btn_dwnld = \
1102 _generate_plotting_area(
1103 graph_trending(self.data, store_sel, self.layout,
1104 d_start, d_end, bool(cl_normalize)),
1108 "store_sel": store_sel,
1115 "cl-selected-options": list_tests(store_sel)
1118 row_fig_tput = C.PLACEHOLDER
1119 row_fig_lat = C.PLACEHOLDER
1120 row_btn_dwnld = C.PLACEHOLDER
1121 row_card_sel_tests = C.STYLE_DISABLED
1122 row_btns_sel_tests = C.STYLE_DISABLED
1124 ctrl_panel.set({"cl-selected-options": list()})
1126 if ctrl_panel.get("cl-ctrl-core-value") and \
1127 ctrl_panel.get("cl-ctrl-framesize-value") and \
1128 ctrl_panel.get("cl-ctrl-testtype-value"):
1133 "btn-ctrl-add-disabled": disabled,
1134 "cl-normalize-value": cl_normalize
1138 ctrl_panel.panel, store_sel,
1139 row_fig_tput, row_fig_lat, row_btn_dwnld,
1140 row_card_sel_tests, row_btns_sel_tests
1142 ret_val.extend(ctrl_panel.values())
1146 Output("metadata-tput-lat", "children"),
1147 Output("metadata-hdrh-graph", "children"),
1148 Output("offcanvas-metadata", "is_open"),
1149 Input({"type": "graph", "index": ALL}, "clickData"),
1150 prevent_initial_call=True
1152 def _show_metadata_from_graphs(graph_data: dict) -> tuple:
1157 callback_context.triggered[0]["prop_id"].split(".")[0]
1159 idx = 0 if trigger_id == "tput" else 1
1160 graph_data = graph_data[idx]["points"][0]
1161 except (JSONDecodeError, IndexError, KeyError, ValueError,
1165 metadata = no_update
1170 [dbc.Badge(x.split(":")[0]), x.split(": ")[1]]
1171 ) for x in graph_data.get("text", "").split("<br>")
1173 if trigger_id == "tput":
1174 title = "Throughput"
1175 elif trigger_id == "lat":
1177 hdrh_data = graph_data.get("customdata", None)
1180 class_name="gy-2 p-0",
1182 dbc.CardHeader(hdrh_data.pop("name")),
1183 dbc.CardBody(children=[
1185 id="hdrh-latency-graph",
1186 figure=graph_hdrh_latency(
1187 hdrh_data, self.layout
1195 class_name="gy-2 p-0",
1197 dbc.CardHeader(children=[
1199 target_id="tput-lat-metadata",
1201 style={"display": "inline-block"}
1206 id="tput-lat-metadata",
1208 children=[dbc.ListGroup(children, flush=True), ]
1214 return metadata, graph, True
1217 Output("download-data", "data"),
1218 State("selected-tests", "data"),
1219 Input("btn-download-data", "n_clicks"),
1220 prevent_initial_call=True
1222 def _download_data(store_sel, n_clicks):
1233 for itm in store_sel:
1234 sel_data = select_trending_data(self.data, itm)
1235 if sel_data is None:
1237 df = pd.concat([df, sel_data], ignore_index=True)
1239 return dcc.send_data_frame(df.to_csv, "trending_data.csv")