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
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, \
35 gen_new_url, generate_options
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:
254 id="offcanvas-metadata",
255 title="Throughput And Latency",
259 dbc.Row(id="metadata-tput-lat"),
260 dbc.Row(id="metadata-hdrh-graph"),
268 dcc.Store(id="selected-tests"),
269 dcc.Store(id="control-panel"),
270 dcc.Location(id="url", refresh=False),
271 self._add_ctrl_col(),
272 self._add_plotting_col(),
290 def _add_navbar(self):
291 """Add nav element with navigation panel. It is placed on the top.
293 :returns: Navigation bar.
294 :rtype: dbc.NavbarSimple
296 return dbc.NavbarSimple(
297 id="navbarsimple-main",
301 "Continuous Performance Trending",
310 brand_external_link=True,
315 def _add_ctrl_col(self) -> dbc.Col:
316 """Add column with controls. It is placed on the left side.
318 :returns: Column with the control panel.
324 self._add_ctrl_panel(),
328 def _add_plotting_col(self) -> dbc.Col:
329 """Add column with plots and tables. It is placed on the right side.
331 :returns: Column with tables.
335 id="col-plotting-area",
339 dbc.Row( # Throughput
341 class_name="g-0 p-2",
348 class_name="g-0 p-2",
354 id="row-btn-download",
355 class_name="g-0 p-2",
366 def _add_ctrl_panel(self) -> dbc.Row:
367 """Add control panel.
369 :returns: Control panel.
374 class_name="g-0 p-2",
382 children=show_tooltip(self._tooltips,
388 "Select a Device under Test..."
392 {"label": k, "value": k} \
393 for k in self.spec_tbs.keys()
395 key=lambda d: d["label"]
410 children=show_tooltip(self._tooltips,
411 "help-infra", "Infra")
416 "Select a Physical Test Bed "
432 children=show_tooltip(self._tooltips,
437 placeholder="Select an Area...",
452 children=show_tooltip(self._tooltips,
457 placeholder="Select a Test...",
467 id="row-ctrl-framesize",
471 children=show_tooltip(self._tooltips,
472 "help-framesize", "Frame Size"),
478 id="cl-ctrl-framesize-all",
479 options=C.CL_ALL_DISABLED,
489 id="cl-ctrl-framesize",
502 children=show_tooltip(self._tooltips,
503 "help-cores", "Number of Cores"),
509 id="cl-ctrl-core-all",
510 options=C.CL_ALL_DISABLED,
529 id="row-ctrl-testtype",
533 children=show_tooltip(self._tooltips,
534 "help-ttype", "Test Type"),
540 id="cl-ctrl-testtype-all",
541 options=C.CL_ALL_DISABLED,
551 id="cl-ctrl-testtype",
560 id="row-ctrl-normalize",
564 children=show_tooltip(self._tooltips,
565 "help-normalize", "Normalize"),
571 id="cl-ctrl-normalize",
573 "value": "normalize",
575 "Normalize results to CPU"
588 class_name="gy-1 p-0",
594 children="Add Selected",
603 id="row-card-sel-tests",
605 style=C.STYLE_DISABLED,
612 class_name="overflow-auto",
616 style={"max-height": "12em"},
621 id="row-btns-sel-tests",
622 style=C.STYLE_DISABLED,
629 children="Remove Selected",
630 class_name="w-100 me-1",
635 id="btn-sel-remove-all",
636 children="Remove All",
637 class_name="w-100 me-1",
649 """A class representing the control panel.
652 def __init__(self, panel: dict) -> None:
653 """Initialisation of the control pannel by default values. If
654 particular values are provided (parameter "panel") they are set
657 :param panel: Custom values to be set to the control panel.
658 :param default: Default values to be set to the control panel.
663 # Defines also the order of keys
665 "dd-ctrl-dut-value": str(),
666 "dd-ctrl-phy-options": list(),
667 "dd-ctrl-phy-disabled": True,
668 "dd-ctrl-phy-value": str(),
669 "dd-ctrl-area-options": list(),
670 "dd-ctrl-area-disabled": True,
671 "dd-ctrl-area-value": str(),
672 "dd-ctrl-test-options": list(),
673 "dd-ctrl-test-disabled": True,
674 "dd-ctrl-test-value": str(),
675 "cl-ctrl-core-options": list(),
676 "cl-ctrl-core-value": list(),
677 "cl-ctrl-core-all-value": list(),
678 "cl-ctrl-core-all-options": C.CL_ALL_DISABLED,
679 "cl-ctrl-framesize-options": list(),
680 "cl-ctrl-framesize-value": list(),
681 "cl-ctrl-framesize-all-value": list(),
682 "cl-ctrl-framesize-all-options": C.CL_ALL_DISABLED,
683 "cl-ctrl-testtype-options": list(),
684 "cl-ctrl-testtype-value": list(),
685 "cl-ctrl-testtype-all-value": list(),
686 "cl-ctrl-testtype-all-options": C.CL_ALL_DISABLED,
687 "btn-ctrl-add-disabled": True,
688 "cl-normalize-value": list(),
689 "cl-selected-options": list()
692 self._panel = deepcopy(self._defaults)
694 for key in self._defaults:
695 self._panel[key] = panel[key]
698 def defaults(self) -> dict:
699 return self._defaults
702 def panel(self) -> dict:
705 def set(self, kwargs: dict) -> None:
706 """Set the values of the Control panel.
708 :param kwargs: key - value pairs to be set.
710 :raises KeyError: If the key in kwargs is not present in the Control
713 for key, val in kwargs.items():
714 if key in self._panel:
715 self._panel[key] = val
717 raise KeyError(f"The key {key} is not defined.")
719 def get(self, key: str) -> any:
720 """Returns the value of a key from the Control panel.
722 :param key: The key which value should be returned.
724 :returns: The value of the key.
726 :raises KeyError: If the key in kwargs is not present in the Control
729 return self._panel[key]
731 def values(self) -> tuple:
732 """Returns the values from the Control panel as a list.
734 :returns: The values from the Control panel.
737 return tuple(self._panel.values())
739 def callbacks(self, app):
740 """Callbacks for the whole application.
742 :param app: The application.
746 def _generate_plotting_area(figs: tuple, url: str) -> tuple:
747 """Generate the plotting area with all its content.
749 :param figs: Figures to be placed in the plotting area.
750 :param utl: The URL to be placed in the plotting area bellow the
752 :type figs: tuple of plotly.graph_objects.Figure
754 :returns: tuple of elements to be shown in the plotting area.
755 :rtype: tuple(dcc.Graph, dcc.Graph, list(dbc.Col, dbc.Col))
758 (fig_tput, fig_lat) = figs
760 row_fig_tput = C.PLACEHOLDER
761 row_fig_lat = C.PLACEHOLDER
762 row_btn_dwnld = C.PLACEHOLDER
767 id={"type": "graph", "index": "tput"},
775 dcc.Loading(children=[
777 id="btn-download-data",
778 children=show_tooltip(self._tooltips,
779 "help-download", "Download Data"),
783 dcc.Download(id="download-data")
795 children=show_tooltip(self._tooltips,
796 "help-url", "URL", "input-url")
813 id={"type": "graph", "index": "lat"},
818 return row_fig_tput, row_fig_lat, row_btn_dwnld
821 Output("control-panel", "data"), # Store
822 Output("selected-tests", "data"), # Store
823 Output("row-graph-tput", "children"),
824 Output("row-graph-lat", "children"),
825 Output("row-btn-download", "children"),
826 Output("row-card-sel-tests", "style"),
827 Output("row-btns-sel-tests", "style"),
828 Output("dd-ctrl-dut", "value"),
829 Output("dd-ctrl-phy", "options"),
830 Output("dd-ctrl-phy", "disabled"),
831 Output("dd-ctrl-phy", "value"),
832 Output("dd-ctrl-area", "options"),
833 Output("dd-ctrl-area", "disabled"),
834 Output("dd-ctrl-area", "value"),
835 Output("dd-ctrl-test", "options"),
836 Output("dd-ctrl-test", "disabled"),
837 Output("dd-ctrl-test", "value"),
838 Output("cl-ctrl-core", "options"),
839 Output("cl-ctrl-core", "value"),
840 Output("cl-ctrl-core-all", "value"),
841 Output("cl-ctrl-core-all", "options"),
842 Output("cl-ctrl-framesize", "options"),
843 Output("cl-ctrl-framesize", "value"),
844 Output("cl-ctrl-framesize-all", "value"),
845 Output("cl-ctrl-framesize-all", "options"),
846 Output("cl-ctrl-testtype", "options"),
847 Output("cl-ctrl-testtype", "value"),
848 Output("cl-ctrl-testtype-all", "value"),
849 Output("cl-ctrl-testtype-all", "options"),
850 Output("btn-ctrl-add", "disabled"),
851 Output("cl-ctrl-normalize", "value"),
852 Output("cl-selected", "options"), # User selection
853 State("control-panel", "data"), # Store
854 State("selected-tests", "data"), # Store
855 State("cl-selected", "value"), # User selection
856 Input("dd-ctrl-dut", "value"),
857 Input("dd-ctrl-phy", "value"),
858 Input("dd-ctrl-area", "value"),
859 Input("dd-ctrl-test", "value"),
860 Input("cl-ctrl-core", "value"),
861 Input("cl-ctrl-core-all", "value"),
862 Input("cl-ctrl-framesize", "value"),
863 Input("cl-ctrl-framesize-all", "value"),
864 Input("cl-ctrl-testtype", "value"),
865 Input("cl-ctrl-testtype-all", "value"),
866 Input("cl-ctrl-normalize", "value"),
867 Input("btn-ctrl-add", "n_clicks"),
868 Input("btn-sel-remove", "n_clicks"),
869 Input("btn-sel-remove-all", "n_clicks"),
872 def _update_ctrl_panel(cp_data: dict, store_sel: list, list_sel: list,
873 dd_dut: str, dd_phy: str, dd_area: str, dd_test: str, cl_core: list,
874 cl_core_all: list, cl_framesize: list, cl_framesize_all: list,
875 cl_testtype: list, cl_testtype_all: list, cl_normalize: list,
876 btn_add: int, btn_remove: int,
877 btn_remove_all: int, href: str) -> tuple:
878 """Update the application when the event is detected.
880 :param cp_data: Current status of the control panel stored in
882 :param store_sel: List of tests selected by user stored in the
884 :param list_sel: List of tests selected by the user shown in the
886 :param dd_dut: Input - DUTs.
887 :param dd_phy: Input - topo- arch-nic-driver.
888 :param dd_area: Input - Tested area.
889 :param dd_test: Input - Test.
890 :param cl_core: Input - Number of cores.
891 :param cl_core_all: Input - All numbers of cores.
892 :param cl_framesize: Input - Frame sizes.
893 :param cl_framesize_all: Input - All frame sizes.
894 :param cl_testtype: Input - Test type (NDR, PDR, MRR).
895 :param cl_testtype_all: Input - All test types.
896 :param cl_normalize: Input - Normalize the results.
897 :param btn_add: Input - Button "Add Selected" tests.
898 :param btn_remove: Input - Button "Remove selected" tests.
899 :param btn_remove_all: Input - Button "Remove All" tests.
900 :param href: Input - The URL provided by the browser.
902 :type store_sel: list
909 :type cl_core_all: list
910 :type cl_framesize: list
911 :type cl_framesize_all: list
912 :type cl_testtype: list
913 :type cl_testtype_all: list
914 :type cl_normalize: list
916 :type btn_remove: int
917 :type btn_remove_all: int
919 :returns: New values for web page elements.
923 ctrl_panel = self.ControlPanel(cp_data)
927 parsed_url = url_decode(href)
929 url_params = parsed_url["params"]
933 row_fig_tput = no_update
934 row_fig_lat = no_update
935 row_btn_dwnld = no_update
936 row_card_sel_tests = no_update
937 row_btns_sel_tests = no_update
939 trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
941 if trigger_id == "dd-ctrl-dut":
944 generate_options(sorted(self.spec_tbs[dd_dut].keys()))
950 "dd-ctrl-dut-value": dd_dut,
951 "dd-ctrl-phy-value": str(),
952 "dd-ctrl-phy-options": options,
953 "dd-ctrl-phy-disabled": disabled,
954 "dd-ctrl-area-value": str(),
955 "dd-ctrl-area-options": list(),
956 "dd-ctrl-area-disabled": True,
957 "dd-ctrl-test-value": str(),
958 "dd-ctrl-test-options": list(),
959 "dd-ctrl-test-disabled": True,
960 "cl-ctrl-core-options": list(),
961 "cl-ctrl-core-value": list(),
962 "cl-ctrl-core-all-value": list(),
963 "cl-ctrl-core-all-options": C.CL_ALL_DISABLED,
964 "cl-ctrl-framesize-options": list(),
965 "cl-ctrl-framesize-value": list(),
966 "cl-ctrl-framesize-all-value": list(),
967 "cl-ctrl-framesize-all-options": C.CL_ALL_DISABLED,
968 "cl-ctrl-testtype-options": list(),
969 "cl-ctrl-testtype-value": list(),
970 "cl-ctrl-testtype-all-value": list(),
971 "cl-ctrl-testtype-all-options": C.CL_ALL_DISABLED,
973 elif trigger_id == "dd-ctrl-phy":
975 dut = ctrl_panel.get("dd-ctrl-dut-value")
976 phy = self.spec_tbs[dut][dd_phy]
977 options = [{"label": label(v), "value": v} \
978 for v in sorted(phy.keys())]
984 "dd-ctrl-phy-value": dd_phy,
985 "dd-ctrl-area-value": str(),
986 "dd-ctrl-area-options": options,
987 "dd-ctrl-area-disabled": disabled,
988 "dd-ctrl-test-value": str(),
989 "dd-ctrl-test-options": list(),
990 "dd-ctrl-test-disabled": True,
991 "cl-ctrl-core-options": list(),
992 "cl-ctrl-core-value": list(),
993 "cl-ctrl-core-all-value": list(),
994 "cl-ctrl-core-all-options": C.CL_ALL_DISABLED,
995 "cl-ctrl-framesize-options": list(),
996 "cl-ctrl-framesize-value": list(),
997 "cl-ctrl-framesize-all-value": list(),
998 "cl-ctrl-framesize-all-options": C.CL_ALL_DISABLED,
999 "cl-ctrl-testtype-options": list(),
1000 "cl-ctrl-testtype-value": list(),
1001 "cl-ctrl-testtype-all-value": list(),
1002 "cl-ctrl-testtype-all-options": C.CL_ALL_DISABLED,
1004 elif trigger_id == "dd-ctrl-area":
1006 dut = ctrl_panel.get("dd-ctrl-dut-value")
1007 phy = ctrl_panel.get("dd-ctrl-phy-value")
1008 area = self.spec_tbs[dut][phy][dd_area]
1009 options = generate_options(sorted(area.keys()))
1015 "dd-ctrl-area-value": dd_area,
1016 "dd-ctrl-test-value": str(),
1017 "dd-ctrl-test-options": options,
1018 "dd-ctrl-test-disabled": disabled,
1019 "cl-ctrl-core-options": list(),
1020 "cl-ctrl-core-value": list(),
1021 "cl-ctrl-core-all-value": list(),
1022 "cl-ctrl-core-all-options": C.CL_ALL_DISABLED,
1023 "cl-ctrl-framesize-options": list(),
1024 "cl-ctrl-framesize-value": list(),
1025 "cl-ctrl-framesize-all-value": list(),
1026 "cl-ctrl-framesize-all-options": C.CL_ALL_DISABLED,
1027 "cl-ctrl-testtype-options": list(),
1028 "cl-ctrl-testtype-value": list(),
1029 "cl-ctrl-testtype-all-value": list(),
1030 "cl-ctrl-testtype-all-options": C.CL_ALL_DISABLED,
1032 elif trigger_id == "dd-ctrl-test":
1033 dut = ctrl_panel.get("dd-ctrl-dut-value")
1034 phy = ctrl_panel.get("dd-ctrl-phy-value")
1035 area = ctrl_panel.get("dd-ctrl-area-value")
1036 if all((dut, phy, area, dd_test, )):
1037 test = self.spec_tbs[dut][phy][area][dd_test]
1039 "dd-ctrl-test-value": dd_test,
1040 "cl-ctrl-core-options": \
1041 generate_options(sorted(test["core"])),
1042 "cl-ctrl-core-value": list(),
1043 "cl-ctrl-core-all-value": list(),
1044 "cl-ctrl-core-all-options": C.CL_ALL_ENABLED,
1045 "cl-ctrl-framesize-options": \
1046 generate_options(sorted(test["frame-size"])),
1047 "cl-ctrl-framesize-value": list(),
1048 "cl-ctrl-framesize-all-value": list(),
1049 "cl-ctrl-framesize-all-options": C.CL_ALL_ENABLED,
1050 "cl-ctrl-testtype-options": \
1051 generate_options(sorted(test["test-type"])),
1052 "cl-ctrl-testtype-value": list(),
1053 "cl-ctrl-testtype-all-value": list(),
1054 "cl-ctrl-testtype-all-options": C.CL_ALL_ENABLED,
1056 elif trigger_id == "cl-ctrl-core":
1057 val_sel, val_all = sync_checklists(
1058 options=ctrl_panel.get("cl-ctrl-core-options"),
1064 "cl-ctrl-core-value": val_sel,
1065 "cl-ctrl-core-all-value": val_all,
1067 elif trigger_id == "cl-ctrl-core-all":
1068 val_sel, val_all = sync_checklists(
1069 options = ctrl_panel.get("cl-ctrl-core-options"),
1075 "cl-ctrl-core-value": val_sel,
1076 "cl-ctrl-core-all-value": val_all,
1078 elif trigger_id == "cl-ctrl-framesize":
1079 val_sel, val_all = sync_checklists(
1080 options = ctrl_panel.get("cl-ctrl-framesize-options"),
1086 "cl-ctrl-framesize-value": val_sel,
1087 "cl-ctrl-framesize-all-value": val_all,
1089 elif trigger_id == "cl-ctrl-framesize-all":
1090 val_sel, val_all = sync_checklists(
1091 options = ctrl_panel.get("cl-ctrl-framesize-options"),
1093 all=cl_framesize_all,
1097 "cl-ctrl-framesize-value": val_sel,
1098 "cl-ctrl-framesize-all-value": val_all,
1100 elif trigger_id == "cl-ctrl-testtype":
1101 val_sel, val_all = sync_checklists(
1102 options = ctrl_panel.get("cl-ctrl-testtype-options"),
1108 "cl-ctrl-testtype-value": val_sel,
1109 "cl-ctrl-testtype-all-value": val_all,
1111 elif trigger_id == "cl-ctrl-testtype-all":
1112 val_sel, val_all = sync_checklists(
1113 options = ctrl_panel.get("cl-ctrl-testtype-options"),
1115 all=cl_testtype_all,
1119 "cl-ctrl-testtype-value": val_sel,
1120 "cl-ctrl-testtype-all-value": val_all,
1122 elif trigger_id == "btn-ctrl-add":
1124 dut = ctrl_panel.get("dd-ctrl-dut-value")
1125 phy = ctrl_panel.get("dd-ctrl-phy-value")
1126 area = ctrl_panel.get("dd-ctrl-area-value")
1127 test = ctrl_panel.get("dd-ctrl-test-value")
1128 cores = ctrl_panel.get("cl-ctrl-core-value")
1129 framesizes = ctrl_panel.get("cl-ctrl-framesize-value")
1130 testtypes = ctrl_panel.get("cl-ctrl-testtype-value")
1131 # Add selected test to the list of tests in store:
1132 if all((dut, phy, area, test, cores, framesizes, testtypes)):
1133 if store_sel is None:
1136 for framesize in framesizes:
1137 for ttype in testtypes:
1141 dut, phy.replace('af_xdp', 'af-xdp'), area,
1142 framesize.lower(), core.lower(), test,
1145 if tid not in [itm["id"] for itm in store_sel]:
1152 "framesize": framesize.lower(),
1153 "core": core.lower(),
1154 "testtype": ttype.lower()
1156 store_sel = sorted(store_sel, key=lambda d: d["id"])
1157 row_card_sel_tests = C.STYLE_ENABLED
1158 row_btns_sel_tests = C.STYLE_ENABLED
1159 if C.CLEAR_ALL_INPUTS:
1160 ctrl_panel.set(ctrl_panel.defaults)
1161 elif trigger_id == "btn-sel-remove-all":
1163 row_fig_tput = C.PLACEHOLDER
1164 row_fig_lat = C.PLACEHOLDER
1165 row_btn_dwnld = C.PLACEHOLDER
1166 row_card_sel_tests = C.STYLE_DISABLED
1167 row_btns_sel_tests = C.STYLE_DISABLED
1169 ctrl_panel.set({"cl-selected-options": list()})
1170 elif trigger_id == "btn-sel-remove":
1173 new_store_sel = list()
1174 for item in store_sel:
1175 if item["id"] not in list_sel:
1176 new_store_sel.append(item)
1177 store_sel = new_store_sel
1178 elif trigger_id == "url":
1181 store_sel = literal_eval(url_params["store_sel"][0])
1182 norm = literal_eval(url_params["norm"][0])
1183 except (KeyError, IndexError):
1186 row_card_sel_tests = C.STYLE_ENABLED
1187 row_btns_sel_tests = C.STYLE_ENABLED
1188 last_test = store_sel[-1]
1189 test = self.spec_tbs[last_test["dut"]]\
1190 [last_test["phy"]][last_test["area"]]\
1193 "dd-ctrl-dut-value": last_test["dut"],
1194 "dd-ctrl-phy-value": last_test["phy"],
1195 "dd-ctrl-phy-options": generate_options(sorted(
1196 self.spec_tbs[last_test["dut"]].keys())),
1197 "dd-ctrl-phy-disabled": False,
1198 "dd-ctrl-area-value": last_test["area"],
1199 "dd-ctrl-area-options": [
1200 {"label": label(v), "value": v} \
1202 self.spec_tbs[last_test["dut"]]\
1203 [last_test["phy"]].keys())
1205 "dd-ctrl-area-disabled": False,
1206 "dd-ctrl-test-value": last_test["test"],
1207 "dd-ctrl-test-options": generate_options(sorted(
1208 self.spec_tbs[last_test["dut"]]\
1210 [last_test["area"]].keys())),
1211 "dd-ctrl-test-disabled": False,
1212 "cl-ctrl-core-options": generate_options(sorted(
1214 "cl-ctrl-core-value": [last_test["core"].upper(), ],
1215 "cl-ctrl-core-all-value": list(),
1216 "cl-ctrl-core-all-options": C.CL_ALL_ENABLED,
1217 "cl-ctrl-framesize-options": generate_options(
1218 sorted(test["frame-size"])),
1219 "cl-ctrl-framesize-value": \
1220 [last_test["framesize"].upper(), ],
1221 "cl-ctrl-framesize-all-value": list(),
1222 "cl-ctrl-framesize-all-options": C.CL_ALL_ENABLED,
1223 "cl-ctrl-testtype-options": generate_options(sorted(
1224 test["test-type"])),
1225 "cl-ctrl-testtype-value": \
1226 [last_test["testtype"].upper(), ],
1227 "cl-ctrl-testtype-all-value": list(),
1228 "cl-ctrl-testtype-all-options": C.CL_ALL_ENABLED
1231 if trigger_id in ("btn-ctrl-add", "url", "btn-sel-remove",
1232 "cl-ctrl-normalize"):
1234 row_fig_tput, row_fig_lat, row_btn_dwnld = \
1235 _generate_plotting_area(
1236 graph_trending(self.data, store_sel, self.layout,
1241 "store_sel": store_sel,
1247 "cl-selected-options": list_tests(store_sel)
1250 row_fig_tput = C.PLACEHOLDER
1251 row_fig_lat = C.PLACEHOLDER
1252 row_btn_dwnld = C.PLACEHOLDER
1253 row_card_sel_tests = C.STYLE_DISABLED
1254 row_btns_sel_tests = C.STYLE_DISABLED
1256 ctrl_panel.set({"cl-selected-options": list()})
1258 if ctrl_panel.get("cl-ctrl-core-value") and \
1259 ctrl_panel.get("cl-ctrl-framesize-value") and \
1260 ctrl_panel.get("cl-ctrl-testtype-value"):
1265 "btn-ctrl-add-disabled": disabled,
1266 "cl-normalize-value": norm
1270 ctrl_panel.panel, store_sel,
1271 row_fig_tput, row_fig_lat, row_btn_dwnld,
1272 row_card_sel_tests, row_btns_sel_tests
1274 ret_val.extend(ctrl_panel.values())
1278 Output("metadata-tput-lat", "children"),
1279 Output("metadata-hdrh-graph", "children"),
1280 Output("offcanvas-metadata", "is_open"),
1281 Input({"type": "graph", "index": ALL}, "clickData"),
1282 prevent_initial_call=True
1284 def _show_metadata_from_graphs(graph_data: dict) -> tuple:
1285 """Generates the data for the offcanvas displayed when a particular
1286 point in a graph is clicked on.
1288 :param graph_data: The data from the clicked point in the graph.
1289 :type graph_data: dict
1290 :returns: The data to be displayed on the offcanvas and the
1291 information to show the offcanvas.
1292 :rtype: tuple(list, list, bool)
1296 callback_context.triggered[0]["prop_id"].split(".")[0]
1298 idx = 0 if trigger_id == "tput" else 1
1299 graph_data = graph_data[idx]["points"][0]
1300 except (JSONDecodeError, IndexError, KeyError, ValueError,
1304 metadata = no_update
1309 [dbc.Badge(x.split(":")[0]), x.split(": ")[1]]
1310 ) for x in graph_data.get("text", "").split("<br>")
1312 if trigger_id == "tput":
1313 title = "Throughput"
1314 elif trigger_id == "lat":
1316 hdrh_data = graph_data.get("customdata", None)
1319 class_name="gy-2 p-0",
1321 dbc.CardHeader(hdrh_data.pop("name")),
1322 dbc.CardBody(children=[
1324 id="hdrh-latency-graph",
1325 figure=graph_hdrh_latency(
1326 hdrh_data, self.layout
1334 class_name="gy-2 p-0",
1336 dbc.CardHeader(children=[
1338 target_id="tput-lat-metadata",
1340 style={"display": "inline-block"}
1345 id="tput-lat-metadata",
1347 children=[dbc.ListGroup(children, flush=True), ]
1353 return metadata, graph, True
1356 Output("download-data", "data"),
1357 State("selected-tests", "data"),
1358 Input("btn-download-data", "n_clicks"),
1359 prevent_initial_call=True
1361 def _download_data(store_sel, n_clicks):
1362 """Download the data
1364 :param store_sel: List of tests selected by user stored in the
1366 :param n_clicks: Number of clicks on the button "Download".
1367 :type store_sel: list
1369 :returns: dict of data frame content (base64 encoded) and meta data
1370 used by the Download component.
1381 for itm in store_sel:
1382 sel_data = select_trending_data(self.data, itm)
1383 if sel_data is None:
1385 df = pd.concat([df, sel_data], ignore_index=True)
1387 return dcc.send_data_frame(df.to_csv, C.TREND_DOWNLOAD_FILE_NAME)