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, \
43 """The layout of the dash app and the callbacks.
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:
50 - save the input parameters,
51 - read and pre-process the data,
52 - prepare data for the control panel,
53 - read HTML layout file,
54 - read tooltips from the tooltip file.
56 :param app: Flask application running the dash application.
57 :param html_layout_file: Path and name of the file specifying the HTML
58 layout of the dash application.
59 :param graph_layout_file: Path and name of the file with layout of
61 :param data_spec_file: Path and name of the file specifying the data to
62 be read from parquets for this application.
63 :param tooltip_file: Path and name of the yaml file specifying the
65 :param time_period: It defines the time period for data read from the
66 parquets in days from now back to the past.
68 :type html_layout_file: str
69 :type graph_layout_file: str
70 :type data_spec_file: str
71 :type tooltip_file: str
72 :type time_period: int
77 self._html_layout_file = html_layout_file
78 self._graph_layout_file = graph_layout_file
79 self._data_spec_file = data_spec_file
80 self._tooltip_file = tooltip_file
81 self._time_period = time_period
85 data_spec_file=self._data_spec_file,
87 ).read_trending_mrr(days=self._time_period)
90 data_spec_file=self._data_spec_file,
92 ).read_trending_ndrpdr(days=self._time_period)
94 self._data = pd.concat([data_mrr, data_ndrpdr], ignore_index=True)
97 (datetime.utcnow() - self._data["start_time"].min()).days
98 if self._time_period > data_time_period:
99 self._time_period = data_time_period
102 # Get structure of tests:
104 for _, row in self._data[["job", "test_id"]].drop_duplicates().\
106 lst_job = row["job"].split("-")
109 tbed = "-".join(lst_job[-2:])
110 lst_test = row["test_id"].split(".")
114 area = "-".join(lst_test[3:-2])
115 suite = lst_test[-2].replace("2n1l-", "").replace("1n1l-", "").\
118 nic = suite.split("-")[0]
119 for drv in C.DRIVERS:
125 test = test.replace(f"{drv}-", "")
129 infra = "-".join((tbed, nic, driver))
130 lst_test = test.split("-")
131 framesize = lst_test[0]
132 core = lst_test[1] if lst_test[1] else "8C"
133 test = "-".join(lst_test[2: -1])
135 if tbs.get(dut, None) is None:
137 if tbs[dut].get(infra, None) is None:
138 tbs[dut][infra] = dict()
139 if tbs[dut][infra].get(area, None) is None:
140 tbs[dut][infra][area] = dict()
141 if tbs[dut][infra][area].get(test, None) is None:
142 tbs[dut][infra][area][test] = dict()
143 tbs[dut][infra][area][test]["core"] = list()
144 tbs[dut][infra][area][test]["frame-size"] = list()
145 tbs[dut][infra][area][test]["test-type"] = list()
146 if core.upper() not in tbs[dut][infra][area][test]["core"]:
147 tbs[dut][infra][area][test]["core"].append(core.upper())
148 if framesize.upper() not in \
149 tbs[dut][infra][area][test]["frame-size"]:
150 tbs[dut][infra][area][test]["frame-size"].append(
153 if "MRR" not in tbs[dut][infra][area][test]["test-type"]:
154 tbs[dut][infra][area][test]["test-type"].append("MRR")
155 elif ttype == "ndrpdr":
156 if "NDR" not in tbs[dut][infra][area][test]["test-type"]:
157 tbs[dut][infra][area][test]["test-type"].extend(
162 self._html_layout = ""
163 self._graph_layout = None
164 self._tooltips = dict()
167 with open(self._html_layout_file, "r") as file_read:
168 self._html_layout = file_read.read()
169 except IOError as err:
171 f"Not possible to open the file {self._html_layout_file}\n{err}"
175 with open(self._graph_layout_file, "r") as file_read:
176 self._graph_layout = load(file_read, Loader=FullLoader)
177 except IOError as err:
179 f"Not possible to open the file {self._graph_layout_file}\n"
182 except YAMLError as err:
184 f"An error occurred while parsing the specification file "
185 f"{self._graph_layout_file}\n{err}"
189 with open(self._tooltip_file, "r") as file_read:
190 self._tooltips = load(file_read, Loader=FullLoader)
191 except IOError as err:
193 f"Not possible to open the file {self._tooltip_file}\n{err}"
195 except YAMLError as err:
197 f"An error occurred while parsing the specification file "
198 f"{self._tooltip_file}\n{err}"
202 if self._app is not None and hasattr(self, 'callbacks'):
203 self.callbacks(self._app)
206 def html_layout(self):
207 return self._html_layout
211 return self._spec_tbs
219 return self._graph_layout
222 def time_period(self):
223 return self._time_period
225 def add_content(self):
226 """Top level method which generated the web page.
229 - Store for user input data,
231 - Main area with control panel and ploting area.
233 If no HTML layout is provided, an error message is displayed instead.
235 :returns: The HTML div with the whole page.
239 if self.html_layout and self.spec_tbs:
253 id="offcanvas-metadata",
254 title="Throughput And Latency",
258 dbc.Row(id="metadata-tput-lat"),
259 dbc.Row(id="metadata-hdrh-graph"),
267 dcc.Store(id="selected-tests"),
268 dcc.Store(id="control-panel"),
269 dcc.Location(id="url", refresh=False),
270 self._add_ctrl_col(),
271 self._add_plotting_col(),
289 def _add_navbar(self):
290 """Add nav element with navigation panel. It is placed on the top.
292 :returns: Navigation bar.
293 :rtype: dbc.NavbarSimple
295 return dbc.NavbarSimple(
296 id="navbarsimple-main",
300 "Continuous Performance Trending",
309 brand_external_link=True,
314 def _add_ctrl_col(self) -> dbc.Col:
315 """Add column with controls. It is placed on the left side.
317 :returns: Column with the control panel.
323 self._add_ctrl_panel(),
327 def _add_plotting_col(self) -> dbc.Col:
328 """Add column with plots and tables. It is placed on the right side.
330 :returns: Column with tables.
334 id="col-plotting-area",
338 dbc.Row( # Throughput
340 class_name="g-0 p-2",
347 class_name="g-0 p-2",
353 id="row-btn-download",
354 class_name="g-0 p-2",
365 def _add_ctrl_panel(self) -> dbc.Row:
366 """Add control panel.
368 :returns: Control panel.
373 class_name="g-0 p-2",
381 children=show_tooltip(self._tooltips,
387 "Select a Device under Test..."
391 {"label": k, "value": k} \
392 for k in self.spec_tbs.keys()
394 key=lambda d: d["label"]
409 children=show_tooltip(self._tooltips,
410 "help-infra", "Infra")
415 "Select a Physical Test Bed "
431 children=show_tooltip(self._tooltips,
436 placeholder="Select an Area...",
451 children=show_tooltip(self._tooltips,
456 placeholder="Select a Test...",
466 id="row-ctrl-framesize",
470 children=show_tooltip(self._tooltips,
471 "help-framesize", "Frame Size"),
477 id="cl-ctrl-framesize-all",
478 options=C.CL_ALL_DISABLED,
488 id="cl-ctrl-framesize",
501 children=show_tooltip(self._tooltips,
502 "help-cores", "Number of Cores"),
508 id="cl-ctrl-core-all",
509 options=C.CL_ALL_DISABLED,
528 id="row-ctrl-testtype",
532 children=show_tooltip(self._tooltips,
533 "help-ttype", "Test Type"),
539 id="cl-ctrl-testtype-all",
540 options=C.CL_ALL_DISABLED,
550 id="cl-ctrl-testtype",
559 id="row-ctrl-normalize",
563 children=show_tooltip(self._tooltips,
564 "help-normalize", "Normalize"),
570 id="cl-ctrl-normalize",
572 "value": "normalize",
574 "Normalize results to CPU"
587 class_name="gy-1 p-0",
593 children="Add Selected",
607 children=show_tooltip(self._tooltips,
608 "help-time-period", "Time Period"),
612 className="d-flex justify-content-center",
614 datetime.utcnow() - timedelta(
615 days=self.time_period),
616 max_date_allowed=datetime.utcnow(),
617 initial_visible_month=datetime.utcnow(),
619 datetime.utcnow() - timedelta(
620 days=self.time_period),
621 end_date=datetime.utcnow(),
622 display_format="D MMM YY"
627 id="row-card-sel-tests",
629 style=C.STYLE_DISABLED,
636 class_name="overflow-auto",
640 style={"max-height": "12em"},
645 id="row-btns-sel-tests",
646 style=C.STYLE_DISABLED,
653 children="Remove Selected",
654 class_name="w-100 me-1",
659 id="btn-sel-remove-all",
660 children="Remove All",
661 class_name="w-100 me-1",
674 """A class representing the control panel.
677 def __init__(self, panel: dict) -> None:
678 """Initialisation of the control pannel by default values. If
679 particular values are provided (parameter "panel") they are set
682 :param panel: Custom values to be set to the control panel.
683 :param default: Default values to be set to the control panel.
688 # Defines also the order of keys
690 "dd-ctrl-dut-value": str(),
691 "dd-ctrl-phy-options": list(),
692 "dd-ctrl-phy-disabled": True,
693 "dd-ctrl-phy-value": str(),
694 "dd-ctrl-area-options": list(),
695 "dd-ctrl-area-disabled": True,
696 "dd-ctrl-area-value": str(),
697 "dd-ctrl-test-options": list(),
698 "dd-ctrl-test-disabled": True,
699 "dd-ctrl-test-value": str(),
700 "cl-ctrl-core-options": list(),
701 "cl-ctrl-core-value": list(),
702 "cl-ctrl-core-all-value": list(),
703 "cl-ctrl-core-all-options": C.CL_ALL_DISABLED,
704 "cl-ctrl-framesize-options": list(),
705 "cl-ctrl-framesize-value": list(),
706 "cl-ctrl-framesize-all-value": list(),
707 "cl-ctrl-framesize-all-options": C.CL_ALL_DISABLED,
708 "cl-ctrl-testtype-options": list(),
709 "cl-ctrl-testtype-value": list(),
710 "cl-ctrl-testtype-all-value": list(),
711 "cl-ctrl-testtype-all-options": C.CL_ALL_DISABLED,
712 "btn-ctrl-add-disabled": True,
713 "cl-normalize-value": list(),
714 "cl-selected-options": list(),
717 self._panel = deepcopy(self._defaults)
719 for key in self._defaults:
720 self._panel[key] = panel[key]
723 def defaults(self) -> dict:
724 return self._defaults
727 def panel(self) -> dict:
730 def set(self, kwargs: dict) -> None:
731 """Set the values of the Control panel.
733 :param kwargs: key - value pairs to be set.
735 :raises KeyError: If the key in kwargs is not present in the Control
738 for key, val in kwargs.items():
739 if key in self._panel:
740 self._panel[key] = val
742 raise KeyError(f"The key {key} is not defined.")
744 def get(self, key: str) -> any:
745 """Returns the value of a key from the Control panel.
747 :param key: The key which value should be returned.
749 :returns: The value of the key.
751 :raises KeyError: If the key in kwargs is not present in the Control
754 return self._panel[key]
756 def values(self) -> tuple:
757 """Returns the values from the Control panel as a list.
759 :returns: The values from the Control panel.
762 return tuple(self._panel.values())
764 def callbacks(self, app):
765 """Callbacks for the whole application.
767 :param app: The application.
771 def _generate_plotting_area(figs: tuple, url: str) -> tuple:
772 """Generate the plotting area with all its content.
774 :param figs: Figures to be placed in the plotting area.
775 :param utl: The URL to be placed in the plotting area bellow the
777 :type figs: tuple of plotly.graph_objects.Figure
779 :returns: tuple of elements to be shown in the plotting area.
780 :rtype: tuple(dcc.Graph, dcc.Graph, list(dbc.Col, dbc.Col))
783 (fig_tput, fig_lat) = figs
785 row_fig_tput = C.PLACEHOLDER
786 row_fig_lat = C.PLACEHOLDER
787 row_btn_dwnld = C.PLACEHOLDER
792 id={"type": "graph", "index": "tput"},
800 dcc.Loading(children=[
802 id="btn-download-data",
803 children=show_tooltip(self._tooltips,
804 "help-download", "Download Data"),
808 dcc.Download(id="download-data")
820 children=show_tooltip(self._tooltips,
821 "help-url", "URL", "input-url")
838 id={"type": "graph", "index": "lat"},
843 return row_fig_tput, row_fig_lat, row_btn_dwnld
846 Output("control-panel", "data"), # Store
847 Output("selected-tests", "data"), # Store
848 Output("row-graph-tput", "children"),
849 Output("row-graph-lat", "children"),
850 Output("row-btn-download", "children"),
851 Output("row-card-sel-tests", "style"),
852 Output("row-btns-sel-tests", "style"),
853 Output("dd-ctrl-dut", "value"),
854 Output("dd-ctrl-phy", "options"),
855 Output("dd-ctrl-phy", "disabled"),
856 Output("dd-ctrl-phy", "value"),
857 Output("dd-ctrl-area", "options"),
858 Output("dd-ctrl-area", "disabled"),
859 Output("dd-ctrl-area", "value"),
860 Output("dd-ctrl-test", "options"),
861 Output("dd-ctrl-test", "disabled"),
862 Output("dd-ctrl-test", "value"),
863 Output("cl-ctrl-core", "options"),
864 Output("cl-ctrl-core", "value"),
865 Output("cl-ctrl-core-all", "value"),
866 Output("cl-ctrl-core-all", "options"),
867 Output("cl-ctrl-framesize", "options"),
868 Output("cl-ctrl-framesize", "value"),
869 Output("cl-ctrl-framesize-all", "value"),
870 Output("cl-ctrl-framesize-all", "options"),
871 Output("cl-ctrl-testtype", "options"),
872 Output("cl-ctrl-testtype", "value"),
873 Output("cl-ctrl-testtype-all", "value"),
874 Output("cl-ctrl-testtype-all", "options"),
875 Output("btn-ctrl-add", "disabled"),
876 Output("cl-ctrl-normalize", "value"),
877 Output("cl-selected", "options"), # User selection
878 State("control-panel", "data"), # Store
879 State("selected-tests", "data"), # Store
880 State("cl-selected", "value"), # User selection
881 Input("dd-ctrl-dut", "value"),
882 Input("dd-ctrl-phy", "value"),
883 Input("dd-ctrl-area", "value"),
884 Input("dd-ctrl-test", "value"),
885 Input("cl-ctrl-core", "value"),
886 Input("cl-ctrl-core-all", "value"),
887 Input("cl-ctrl-framesize", "value"),
888 Input("cl-ctrl-framesize-all", "value"),
889 Input("cl-ctrl-testtype", "value"),
890 Input("cl-ctrl-testtype-all", "value"),
891 Input("cl-ctrl-normalize", "value"),
892 Input("btn-ctrl-add", "n_clicks"),
893 Input("dpr-period", "start_date"),
894 Input("dpr-period", "end_date"),
895 Input("btn-sel-remove", "n_clicks"),
896 Input("btn-sel-remove-all", "n_clicks"),
899 def _update_ctrl_panel(cp_data: dict, store_sel: list, list_sel: list,
900 dd_dut: str, dd_phy: str, dd_area: str, dd_test: str, cl_core: list,
901 cl_core_all: list, cl_framesize: list, cl_framesize_all: list,
902 cl_testtype: list, cl_testtype_all: list, cl_normalize: list,
903 btn_add: int, d_start: str, d_end: str, btn_remove: int,
904 btn_remove_all: int, href: str) -> tuple:
905 """Update the application when the event is detected.
907 :param cp_data: Current status of the control panel stored in
909 :param store_sel: List of tests selected by user stored in the
911 :param list_sel: List of tests selected by the user shown in the
913 :param dd_dut: Input - DUTs.
914 :param dd_phy: Input - topo- arch-nic-driver.
915 :param dd_area: Input - Tested area.
916 :param dd_test: Input - Test.
917 :param cl_core: Input - Number of cores.
918 :param cl_core_all: Input - All numbers of cores.
919 :param cl_framesize: Input - Frame sizes.
920 :param cl_framesize_all: Input - All frame sizes.
921 :param cl_testtype: Input - Test type (NDR, PDR, MRR).
922 :param cl_testtype_all: Input - All test types.
923 :param cl_normalize: Input - Normalize the results.
924 :param btn_add: Input - Button "Add Selected" tests.
925 :param d_start: Date and time where the data processing starts.
926 :param d_end: Date and time where the data processing ends.
927 :param btn_remove: Input - Button "Remove selected" tests.
928 :param btn_remove_all: Input - Button "Remove All" tests.
929 :param href: Input - The URL provided by the browser.
931 :type store_sel: list
938 :type cl_core_all: list
939 :type cl_framesize: list
940 :type cl_framesize_all: list
941 :type cl_testtype: list
942 :type cl_testtype_all: list
943 :type cl_normalize: list
947 :type btn_remove: int
948 :type btn_remove_all: int
950 :returns: New values for web page elements.
954 ctrl_panel = self.ControlPanel(cp_data)
956 d_start = get_date(d_start)
957 d_end = get_date(d_end)
960 parsed_url = url_decode(href)
962 row_fig_tput = no_update
963 row_fig_lat = no_update
964 row_btn_dwnld = no_update
965 row_card_sel_tests = no_update
966 row_btns_sel_tests = no_update
968 trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
970 if trigger_id == "dd-ctrl-dut":
972 dut = self.spec_tbs[dd_dut]
974 [{"label": v, "value": v}for v in dut.keys()],
975 key=lambda d: d["label"]
982 "dd-ctrl-dut-value": dd_dut,
983 "dd-ctrl-phy-value": str(),
984 "dd-ctrl-phy-options": options,
985 "dd-ctrl-phy-disabled": disabled,
986 "dd-ctrl-area-value": str(),
987 "dd-ctrl-area-options": list(),
988 "dd-ctrl-area-disabled": True,
989 "dd-ctrl-test-value": str(),
990 "dd-ctrl-test-options": list(),
991 "dd-ctrl-test-disabled": True,
992 "cl-ctrl-core-options": list(),
993 "cl-ctrl-core-value": list(),
994 "cl-ctrl-core-all-value": list(),
995 "cl-ctrl-core-all-options": C.CL_ALL_DISABLED,
996 "cl-ctrl-framesize-options": list(),
997 "cl-ctrl-framesize-value": list(),
998 "cl-ctrl-framesize-all-value": list(),
999 "cl-ctrl-framesize-all-options": C.CL_ALL_DISABLED,
1000 "cl-ctrl-testtype-options": list(),
1001 "cl-ctrl-testtype-value": list(),
1002 "cl-ctrl-testtype-all-value": list(),
1003 "cl-ctrl-testtype-all-options": C.CL_ALL_DISABLED,
1005 elif trigger_id == "dd-ctrl-phy":
1007 dut = ctrl_panel.get("dd-ctrl-dut-value")
1008 phy = self.spec_tbs[dut][dd_phy]
1010 [{"label": label(v), "value": v} for v in phy.keys()],
1011 key=lambda d: d["label"]
1018 "dd-ctrl-phy-value": dd_phy,
1019 "dd-ctrl-area-value": str(),
1020 "dd-ctrl-area-options": options,
1021 "dd-ctrl-area-disabled": disabled,
1022 "dd-ctrl-test-value": str(),
1023 "dd-ctrl-test-options": list(),
1024 "dd-ctrl-test-disabled": True,
1025 "cl-ctrl-core-options": list(),
1026 "cl-ctrl-core-value": list(),
1027 "cl-ctrl-core-all-value": list(),
1028 "cl-ctrl-core-all-options": C.CL_ALL_DISABLED,
1029 "cl-ctrl-framesize-options": list(),
1030 "cl-ctrl-framesize-value": list(),
1031 "cl-ctrl-framesize-all-value": list(),
1032 "cl-ctrl-framesize-all-options": C.CL_ALL_DISABLED,
1033 "cl-ctrl-testtype-options": list(),
1034 "cl-ctrl-testtype-value": list(),
1035 "cl-ctrl-testtype-all-value": list(),
1036 "cl-ctrl-testtype-all-options": C.CL_ALL_DISABLED,
1038 elif trigger_id == "dd-ctrl-area":
1040 dut = ctrl_panel.get("dd-ctrl-dut-value")
1041 phy = ctrl_panel.get("dd-ctrl-phy-value")
1042 area = self.spec_tbs[dut][phy][dd_area]
1044 [{"label": v, "value": v} for v in area.keys()],
1045 key=lambda d: d["label"]
1052 "dd-ctrl-area-value": dd_area,
1053 "dd-ctrl-test-value": str(),
1054 "dd-ctrl-test-options": options,
1055 "dd-ctrl-test-disabled": disabled,
1056 "cl-ctrl-core-options": list(),
1057 "cl-ctrl-core-value": list(),
1058 "cl-ctrl-core-all-value": list(),
1059 "cl-ctrl-core-all-options": C.CL_ALL_DISABLED,
1060 "cl-ctrl-framesize-options": list(),
1061 "cl-ctrl-framesize-value": list(),
1062 "cl-ctrl-framesize-all-value": list(),
1063 "cl-ctrl-framesize-all-options": C.CL_ALL_DISABLED,
1064 "cl-ctrl-testtype-options": list(),
1065 "cl-ctrl-testtype-value": list(),
1066 "cl-ctrl-testtype-all-value": list(),
1067 "cl-ctrl-testtype-all-options": C.CL_ALL_DISABLED,
1069 elif trigger_id == "dd-ctrl-test":
1071 framesize_opts = list()
1072 testtype_opts = list()
1073 dut = ctrl_panel.get("dd-ctrl-dut-value")
1074 phy = ctrl_panel.get("dd-ctrl-phy-value")
1075 area = ctrl_panel.get("dd-ctrl-area-value")
1076 test = self.spec_tbs[dut][phy][area][dd_test]
1077 cores = test["core"]
1078 fsizes = test["frame-size"]
1079 ttypes = test["test-type"]
1080 if dut and phy and area and dd_test:
1081 core_opts = [{"label": v, "value": v}
1082 for v in sorted(cores)]
1083 framesize_opts = [{"label": v, "value": v}
1084 for v in sorted(fsizes)]
1085 testtype_opts = [{"label": v, "value": v}
1086 for v in sorted(ttypes)]
1088 "dd-ctrl-test-value": dd_test,
1089 "cl-ctrl-core-options": core_opts,
1090 "cl-ctrl-core-value": list(),
1091 "cl-ctrl-core-all-value": list(),
1092 "cl-ctrl-core-all-options": C.CL_ALL_ENABLED,
1093 "cl-ctrl-framesize-options": framesize_opts,
1094 "cl-ctrl-framesize-value": list(),
1095 "cl-ctrl-framesize-all-value": list(),
1096 "cl-ctrl-framesize-all-options": C.CL_ALL_ENABLED,
1097 "cl-ctrl-testtype-options": testtype_opts,
1098 "cl-ctrl-testtype-value": list(),
1099 "cl-ctrl-testtype-all-value": list(),
1100 "cl-ctrl-testtype-all-options": C.CL_ALL_ENABLED,
1102 elif trigger_id == "cl-ctrl-core":
1103 val_sel, val_all = sync_checklists(
1104 options=ctrl_panel.get("cl-ctrl-core-options"),
1110 "cl-ctrl-core-value": val_sel,
1111 "cl-ctrl-core-all-value": val_all,
1113 elif trigger_id == "cl-ctrl-core-all":
1114 val_sel, val_all = sync_checklists(
1115 options = ctrl_panel.get("cl-ctrl-core-options"),
1121 "cl-ctrl-core-value": val_sel,
1122 "cl-ctrl-core-all-value": val_all,
1124 elif trigger_id == "cl-ctrl-framesize":
1125 val_sel, val_all = sync_checklists(
1126 options = ctrl_panel.get("cl-ctrl-framesize-options"),
1132 "cl-ctrl-framesize-value": val_sel,
1133 "cl-ctrl-framesize-all-value": val_all,
1135 elif trigger_id == "cl-ctrl-framesize-all":
1136 val_sel, val_all = sync_checklists(
1137 options = ctrl_panel.get("cl-ctrl-framesize-options"),
1139 all=cl_framesize_all,
1143 "cl-ctrl-framesize-value": val_sel,
1144 "cl-ctrl-framesize-all-value": val_all,
1146 elif trigger_id == "cl-ctrl-testtype":
1147 val_sel, val_all = sync_checklists(
1148 options = ctrl_panel.get("cl-ctrl-testtype-options"),
1154 "cl-ctrl-testtype-value": val_sel,
1155 "cl-ctrl-testtype-all-value": val_all,
1157 elif trigger_id == "cl-ctrl-testtype-all":
1158 val_sel, val_all = sync_checklists(
1159 options = ctrl_panel.get("cl-ctrl-testtype-options"),
1161 all=cl_testtype_all,
1165 "cl-ctrl-testtype-value": val_sel,
1166 "cl-ctrl-testtype-all-value": val_all,
1168 elif trigger_id == "btn-ctrl-add":
1170 dut = ctrl_panel.get("dd-ctrl-dut-value")
1171 phy = ctrl_panel.get("dd-ctrl-phy-value")
1172 area = ctrl_panel.get("dd-ctrl-area-value")
1173 test = ctrl_panel.get("dd-ctrl-test-value")
1174 cores = ctrl_panel.get("cl-ctrl-core-value")
1175 framesizes = ctrl_panel.get("cl-ctrl-framesize-value")
1176 testtypes = ctrl_panel.get("cl-ctrl-testtype-value")
1177 # Add selected test to the list of tests in store:
1178 if all((dut, phy, area, test, cores, framesizes, testtypes)):
1179 if store_sel is None:
1182 for framesize in framesizes:
1183 for ttype in testtypes:
1187 dut, phy.replace('af_xdp', 'af-xdp'), area,
1188 framesize.lower(), core.lower(), test,
1191 if tid not in [itm["id"] for itm in store_sel]:
1198 "framesize": framesize.lower(),
1199 "core": core.lower(),
1200 "testtype": ttype.lower()
1202 store_sel = sorted(store_sel, key=lambda d: d["id"])
1203 row_card_sel_tests = C.STYLE_ENABLED
1204 row_btns_sel_tests = C.STYLE_ENABLED
1205 if C.CLEAR_ALL_INPUTS:
1206 ctrl_panel.set(ctrl_panel.defaults)
1207 elif trigger_id == "btn-sel-remove-all":
1209 row_fig_tput = C.PLACEHOLDER
1210 row_fig_lat = C.PLACEHOLDER
1211 row_btn_dwnld = C.PLACEHOLDER
1212 row_card_sel_tests = C.STYLE_DISABLED
1213 row_btns_sel_tests = C.STYLE_DISABLED
1215 ctrl_panel.set({"cl-selected-options": list()})
1216 elif trigger_id == "btn-sel-remove":
1219 new_store_sel = list()
1220 for item in store_sel:
1221 if item["id"] not in list_sel:
1222 new_store_sel.append(item)
1223 store_sel = new_store_sel
1224 elif trigger_id == "url":
1225 # TODO: Add verification
1226 url_params = parsed_url["params"]
1228 store_sel = literal_eval(
1229 url_params.get("store_sel", list())[0])
1230 d_start = get_date(url_params.get("start", list())[0])
1231 d_end = get_date(url_params.get("end", list())[0])
1233 row_card_sel_tests = C.STYLE_ENABLED
1234 row_btns_sel_tests = C.STYLE_ENABLED
1236 if trigger_id in ("btn-ctrl-add", "url", "dpr-period",
1237 "btn-sel-remove", "cl-ctrl-normalize"):
1239 row_fig_tput, row_fig_lat, row_btn_dwnld = \
1240 _generate_plotting_area(
1241 graph_trending(self.data, store_sel, self.layout,
1242 d_start, d_end, bool(cl_normalize)),
1246 "store_sel": store_sel,
1253 "cl-selected-options": list_tests(store_sel)
1256 row_fig_tput = C.PLACEHOLDER
1257 row_fig_lat = C.PLACEHOLDER
1258 row_btn_dwnld = C.PLACEHOLDER
1259 row_card_sel_tests = C.STYLE_DISABLED
1260 row_btns_sel_tests = C.STYLE_DISABLED
1262 ctrl_panel.set({"cl-selected-options": list()})
1264 if ctrl_panel.get("cl-ctrl-core-value") and \
1265 ctrl_panel.get("cl-ctrl-framesize-value") and \
1266 ctrl_panel.get("cl-ctrl-testtype-value"):
1271 "btn-ctrl-add-disabled": disabled,
1272 "cl-normalize-value": cl_normalize
1276 ctrl_panel.panel, store_sel,
1277 row_fig_tput, row_fig_lat, row_btn_dwnld,
1278 row_card_sel_tests, row_btns_sel_tests
1280 ret_val.extend(ctrl_panel.values())
1284 Output("metadata-tput-lat", "children"),
1285 Output("metadata-hdrh-graph", "children"),
1286 Output("offcanvas-metadata", "is_open"),
1287 Input({"type": "graph", "index": ALL}, "clickData"),
1288 prevent_initial_call=True
1290 def _show_metadata_from_graphs(graph_data: dict) -> tuple:
1291 """Generates the data for the offcanvas displayed when a particular
1292 point in a graph is clicked on.
1294 :param graph_data: The data from the clicked point in the graph.
1295 :type graph_data: dict
1296 :returns: The data to be displayed on the offcanvas and the
1297 information to show the offcanvas.
1298 :rtype: tuple(list, list, bool)
1302 callback_context.triggered[0]["prop_id"].split(".")[0]
1304 idx = 0 if trigger_id == "tput" else 1
1305 graph_data = graph_data[idx]["points"][0]
1306 except (JSONDecodeError, IndexError, KeyError, ValueError,
1310 metadata = no_update
1315 [dbc.Badge(x.split(":")[0]), x.split(": ")[1]]
1316 ) for x in graph_data.get("text", "").split("<br>")
1318 if trigger_id == "tput":
1319 title = "Throughput"
1320 elif trigger_id == "lat":
1322 hdrh_data = graph_data.get("customdata", None)
1325 class_name="gy-2 p-0",
1327 dbc.CardHeader(hdrh_data.pop("name")),
1328 dbc.CardBody(children=[
1330 id="hdrh-latency-graph",
1331 figure=graph_hdrh_latency(
1332 hdrh_data, self.layout
1340 class_name="gy-2 p-0",
1342 dbc.CardHeader(children=[
1344 target_id="tput-lat-metadata",
1346 style={"display": "inline-block"}
1351 id="tput-lat-metadata",
1353 children=[dbc.ListGroup(children, flush=True), ]
1359 return metadata, graph, True
1362 Output("download-data", "data"),
1363 State("selected-tests", "data"),
1364 Input("btn-download-data", "n_clicks"),
1365 prevent_initial_call=True
1367 def _download_data(store_sel, n_clicks):
1368 """Download the data
1370 :param store_sel: List of tests selected by user stored in the
1372 :param n_clicks: Number of clicks on the button "Download".
1373 :type store_sel: list
1375 :returns: dict of data frame content (base64 encoded) and meta data
1376 used by the Download component.
1387 for itm in store_sel:
1388 sel_data = select_trending_data(self.data, itm)
1389 if sel_data is None:
1391 df = pd.concat([df, sel_data], ignore_index=True)
1393 return dcc.send_data_frame(df.to_csv, C.TREND_DOWNLOAD_FILE_NAME)