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, \
35 get_date, 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:
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(),
715 "dpr-start-date": datetime.utcnow() - \
716 timedelta(days=C.TIME_PERIOD),
717 "dpr-end-date": datetime.utcnow()
720 self._panel = deepcopy(self._defaults)
722 for key in self._defaults:
723 self._panel[key] = panel[key]
726 def defaults(self) -> dict:
727 return self._defaults
730 def panel(self) -> dict:
733 def set(self, kwargs: dict) -> None:
734 """Set the values of the Control panel.
736 :param kwargs: key - value pairs to be set.
738 :raises KeyError: If the key in kwargs is not present in the Control
741 for key, val in kwargs.items():
742 if key in self._panel:
743 self._panel[key] = val
745 raise KeyError(f"The key {key} is not defined.")
747 def get(self, key: str) -> any:
748 """Returns the value of a key from the Control panel.
750 :param key: The key which value should be returned.
752 :returns: The value of the key.
754 :raises KeyError: If the key in kwargs is not present in the Control
757 return self._panel[key]
759 def values(self) -> tuple:
760 """Returns the values from the Control panel as a list.
762 :returns: The values from the Control panel.
765 return tuple(self._panel.values())
767 def callbacks(self, app):
768 """Callbacks for the whole application.
770 :param app: The application.
774 def _generate_plotting_area(figs: tuple, url: str) -> tuple:
775 """Generate the plotting area with all its content.
777 :param figs: Figures to be placed in the plotting area.
778 :param utl: The URL to be placed in the plotting area bellow the
780 :type figs: tuple of plotly.graph_objects.Figure
782 :returns: tuple of elements to be shown in the plotting area.
783 :rtype: tuple(dcc.Graph, dcc.Graph, list(dbc.Col, dbc.Col))
786 (fig_tput, fig_lat) = figs
788 row_fig_tput = C.PLACEHOLDER
789 row_fig_lat = C.PLACEHOLDER
790 row_btn_dwnld = C.PLACEHOLDER
795 id={"type": "graph", "index": "tput"},
803 dcc.Loading(children=[
805 id="btn-download-data",
806 children=show_tooltip(self._tooltips,
807 "help-download", "Download Data"),
811 dcc.Download(id="download-data")
823 children=show_tooltip(self._tooltips,
824 "help-url", "URL", "input-url")
841 id={"type": "graph", "index": "lat"},
846 return row_fig_tput, row_fig_lat, row_btn_dwnld
849 Output("control-panel", "data"), # Store
850 Output("selected-tests", "data"), # Store
851 Output("row-graph-tput", "children"),
852 Output("row-graph-lat", "children"),
853 Output("row-btn-download", "children"),
854 Output("row-card-sel-tests", "style"),
855 Output("row-btns-sel-tests", "style"),
856 Output("dd-ctrl-dut", "value"),
857 Output("dd-ctrl-phy", "options"),
858 Output("dd-ctrl-phy", "disabled"),
859 Output("dd-ctrl-phy", "value"),
860 Output("dd-ctrl-area", "options"),
861 Output("dd-ctrl-area", "disabled"),
862 Output("dd-ctrl-area", "value"),
863 Output("dd-ctrl-test", "options"),
864 Output("dd-ctrl-test", "disabled"),
865 Output("dd-ctrl-test", "value"),
866 Output("cl-ctrl-core", "options"),
867 Output("cl-ctrl-core", "value"),
868 Output("cl-ctrl-core-all", "value"),
869 Output("cl-ctrl-core-all", "options"),
870 Output("cl-ctrl-framesize", "options"),
871 Output("cl-ctrl-framesize", "value"),
872 Output("cl-ctrl-framesize-all", "value"),
873 Output("cl-ctrl-framesize-all", "options"),
874 Output("cl-ctrl-testtype", "options"),
875 Output("cl-ctrl-testtype", "value"),
876 Output("cl-ctrl-testtype-all", "value"),
877 Output("cl-ctrl-testtype-all", "options"),
878 Output("btn-ctrl-add", "disabled"),
879 Output("cl-ctrl-normalize", "value"),
880 Output("cl-selected", "options"), # User selection
881 Output("dpr-period", "start_date"),
882 Output("dpr-period", "end_date"),
883 State("control-panel", "data"), # Store
884 State("selected-tests", "data"), # Store
885 State("cl-selected", "value"), # User selection
886 Input("dd-ctrl-dut", "value"),
887 Input("dd-ctrl-phy", "value"),
888 Input("dd-ctrl-area", "value"),
889 Input("dd-ctrl-test", "value"),
890 Input("cl-ctrl-core", "value"),
891 Input("cl-ctrl-core-all", "value"),
892 Input("cl-ctrl-framesize", "value"),
893 Input("cl-ctrl-framesize-all", "value"),
894 Input("cl-ctrl-testtype", "value"),
895 Input("cl-ctrl-testtype-all", "value"),
896 Input("cl-ctrl-normalize", "value"),
897 Input("btn-ctrl-add", "n_clicks"),
898 Input("dpr-period", "start_date"),
899 Input("dpr-period", "end_date"),
900 Input("btn-sel-remove", "n_clicks"),
901 Input("btn-sel-remove-all", "n_clicks"),
904 def _update_ctrl_panel(cp_data: dict, store_sel: list, list_sel: list,
905 dd_dut: str, dd_phy: str, dd_area: str, dd_test: str, cl_core: list,
906 cl_core_all: list, cl_framesize: list, cl_framesize_all: list,
907 cl_testtype: list, cl_testtype_all: list, cl_normalize: list,
908 btn_add: int, d_start: str, d_end: str, btn_remove: int,
909 btn_remove_all: int, href: str) -> tuple:
910 """Update the application when the event is detected.
912 :param cp_data: Current status of the control panel stored in
914 :param store_sel: List of tests selected by user stored in the
916 :param list_sel: List of tests selected by the user shown in the
918 :param dd_dut: Input - DUTs.
919 :param dd_phy: Input - topo- arch-nic-driver.
920 :param dd_area: Input - Tested area.
921 :param dd_test: Input - Test.
922 :param cl_core: Input - Number of cores.
923 :param cl_core_all: Input - All numbers of cores.
924 :param cl_framesize: Input - Frame sizes.
925 :param cl_framesize_all: Input - All frame sizes.
926 :param cl_testtype: Input - Test type (NDR, PDR, MRR).
927 :param cl_testtype_all: Input - All test types.
928 :param cl_normalize: Input - Normalize the results.
929 :param btn_add: Input - Button "Add Selected" tests.
930 :param d_start: Date and time where the data processing starts.
931 :param d_end: Date and time where the data processing ends.
932 :param btn_remove: Input - Button "Remove selected" tests.
933 :param btn_remove_all: Input - Button "Remove All" tests.
934 :param href: Input - The URL provided by the browser.
936 :type store_sel: list
943 :type cl_core_all: list
944 :type cl_framesize: list
945 :type cl_framesize_all: list
946 :type cl_testtype: list
947 :type cl_testtype_all: list
948 :type cl_normalize: list
952 :type btn_remove: int
953 :type btn_remove_all: int
955 :returns: New values for web page elements.
959 ctrl_panel = self.ControlPanel(cp_data)
962 d_start = get_date(d_start)
963 d_end = get_date(d_end)
966 parsed_url = url_decode(href)
968 url_params = parsed_url["params"]
972 row_fig_tput = no_update
973 row_fig_lat = no_update
974 row_btn_dwnld = no_update
975 row_card_sel_tests = no_update
976 row_btns_sel_tests = no_update
978 trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
980 if trigger_id == "dd-ctrl-dut":
983 generate_options(sorted(self.spec_tbs[dd_dut].keys()))
989 "dd-ctrl-dut-value": dd_dut,
990 "dd-ctrl-phy-value": str(),
991 "dd-ctrl-phy-options": options,
992 "dd-ctrl-phy-disabled": disabled,
993 "dd-ctrl-area-value": str(),
994 "dd-ctrl-area-options": list(),
995 "dd-ctrl-area-disabled": True,
996 "dd-ctrl-test-value": str(),
997 "dd-ctrl-test-options": list(),
998 "dd-ctrl-test-disabled": True,
999 "cl-ctrl-core-options": list(),
1000 "cl-ctrl-core-value": list(),
1001 "cl-ctrl-core-all-value": list(),
1002 "cl-ctrl-core-all-options": C.CL_ALL_DISABLED,
1003 "cl-ctrl-framesize-options": list(),
1004 "cl-ctrl-framesize-value": list(),
1005 "cl-ctrl-framesize-all-value": list(),
1006 "cl-ctrl-framesize-all-options": C.CL_ALL_DISABLED,
1007 "cl-ctrl-testtype-options": list(),
1008 "cl-ctrl-testtype-value": list(),
1009 "cl-ctrl-testtype-all-value": list(),
1010 "cl-ctrl-testtype-all-options": C.CL_ALL_DISABLED,
1012 elif trigger_id == "dd-ctrl-phy":
1014 dut = ctrl_panel.get("dd-ctrl-dut-value")
1015 phy = self.spec_tbs[dut][dd_phy]
1016 options = [{"label": label(v), "value": v} \
1017 for v in sorted(phy.keys())]
1023 "dd-ctrl-phy-value": dd_phy,
1024 "dd-ctrl-area-value": str(),
1025 "dd-ctrl-area-options": options,
1026 "dd-ctrl-area-disabled": disabled,
1027 "dd-ctrl-test-value": str(),
1028 "dd-ctrl-test-options": list(),
1029 "dd-ctrl-test-disabled": True,
1030 "cl-ctrl-core-options": list(),
1031 "cl-ctrl-core-value": list(),
1032 "cl-ctrl-core-all-value": list(),
1033 "cl-ctrl-core-all-options": C.CL_ALL_DISABLED,
1034 "cl-ctrl-framesize-options": list(),
1035 "cl-ctrl-framesize-value": list(),
1036 "cl-ctrl-framesize-all-value": list(),
1037 "cl-ctrl-framesize-all-options": C.CL_ALL_DISABLED,
1038 "cl-ctrl-testtype-options": list(),
1039 "cl-ctrl-testtype-value": list(),
1040 "cl-ctrl-testtype-all-value": list(),
1041 "cl-ctrl-testtype-all-options": C.CL_ALL_DISABLED,
1043 elif trigger_id == "dd-ctrl-area":
1045 dut = ctrl_panel.get("dd-ctrl-dut-value")
1046 phy = ctrl_panel.get("dd-ctrl-phy-value")
1047 area = self.spec_tbs[dut][phy][dd_area]
1048 options = generate_options(sorted(area.keys()))
1054 "dd-ctrl-area-value": dd_area,
1055 "dd-ctrl-test-value": str(),
1056 "dd-ctrl-test-options": options,
1057 "dd-ctrl-test-disabled": disabled,
1058 "cl-ctrl-core-options": list(),
1059 "cl-ctrl-core-value": list(),
1060 "cl-ctrl-core-all-value": list(),
1061 "cl-ctrl-core-all-options": C.CL_ALL_DISABLED,
1062 "cl-ctrl-framesize-options": list(),
1063 "cl-ctrl-framesize-value": list(),
1064 "cl-ctrl-framesize-all-value": list(),
1065 "cl-ctrl-framesize-all-options": C.CL_ALL_DISABLED,
1066 "cl-ctrl-testtype-options": list(),
1067 "cl-ctrl-testtype-value": list(),
1068 "cl-ctrl-testtype-all-value": list(),
1069 "cl-ctrl-testtype-all-options": C.CL_ALL_DISABLED,
1071 elif trigger_id == "dd-ctrl-test":
1072 dut = ctrl_panel.get("dd-ctrl-dut-value")
1073 phy = ctrl_panel.get("dd-ctrl-phy-value")
1074 area = ctrl_panel.get("dd-ctrl-area-value")
1075 if all((dut, phy, area, dd_test, )):
1076 test = self.spec_tbs[dut][phy][area][dd_test]
1078 "dd-ctrl-test-value": dd_test,
1079 "cl-ctrl-core-options": \
1080 generate_options(sorted(test["core"])),
1081 "cl-ctrl-core-value": list(),
1082 "cl-ctrl-core-all-value": list(),
1083 "cl-ctrl-core-all-options": C.CL_ALL_ENABLED,
1084 "cl-ctrl-framesize-options": \
1085 generate_options(sorted(test["frame-size"])),
1086 "cl-ctrl-framesize-value": list(),
1087 "cl-ctrl-framesize-all-value": list(),
1088 "cl-ctrl-framesize-all-options": C.CL_ALL_ENABLED,
1089 "cl-ctrl-testtype-options": \
1090 generate_options(sorted(test["test-type"])),
1091 "cl-ctrl-testtype-value": list(),
1092 "cl-ctrl-testtype-all-value": list(),
1093 "cl-ctrl-testtype-all-options": C.CL_ALL_ENABLED,
1095 elif trigger_id == "cl-ctrl-core":
1096 val_sel, val_all = sync_checklists(
1097 options=ctrl_panel.get("cl-ctrl-core-options"),
1103 "cl-ctrl-core-value": val_sel,
1104 "cl-ctrl-core-all-value": val_all,
1106 elif trigger_id == "cl-ctrl-core-all":
1107 val_sel, val_all = sync_checklists(
1108 options = ctrl_panel.get("cl-ctrl-core-options"),
1114 "cl-ctrl-core-value": val_sel,
1115 "cl-ctrl-core-all-value": val_all,
1117 elif trigger_id == "cl-ctrl-framesize":
1118 val_sel, val_all = sync_checklists(
1119 options = ctrl_panel.get("cl-ctrl-framesize-options"),
1125 "cl-ctrl-framesize-value": val_sel,
1126 "cl-ctrl-framesize-all-value": val_all,
1128 elif trigger_id == "cl-ctrl-framesize-all":
1129 val_sel, val_all = sync_checklists(
1130 options = ctrl_panel.get("cl-ctrl-framesize-options"),
1132 all=cl_framesize_all,
1136 "cl-ctrl-framesize-value": val_sel,
1137 "cl-ctrl-framesize-all-value": val_all,
1139 elif trigger_id == "cl-ctrl-testtype":
1140 val_sel, val_all = sync_checklists(
1141 options = ctrl_panel.get("cl-ctrl-testtype-options"),
1147 "cl-ctrl-testtype-value": val_sel,
1148 "cl-ctrl-testtype-all-value": val_all,
1150 elif trigger_id == "cl-ctrl-testtype-all":
1151 val_sel, val_all = sync_checklists(
1152 options = ctrl_panel.get("cl-ctrl-testtype-options"),
1154 all=cl_testtype_all,
1158 "cl-ctrl-testtype-value": val_sel,
1159 "cl-ctrl-testtype-all-value": val_all,
1161 elif trigger_id == "btn-ctrl-add":
1163 dut = ctrl_panel.get("dd-ctrl-dut-value")
1164 phy = ctrl_panel.get("dd-ctrl-phy-value")
1165 area = ctrl_panel.get("dd-ctrl-area-value")
1166 test = ctrl_panel.get("dd-ctrl-test-value")
1167 cores = ctrl_panel.get("cl-ctrl-core-value")
1168 framesizes = ctrl_panel.get("cl-ctrl-framesize-value")
1169 testtypes = ctrl_panel.get("cl-ctrl-testtype-value")
1170 # Add selected test to the list of tests in store:
1171 if all((dut, phy, area, test, cores, framesizes, testtypes)):
1172 if store_sel is None:
1175 for framesize in framesizes:
1176 for ttype in testtypes:
1180 dut, phy.replace('af_xdp', 'af-xdp'), area,
1181 framesize.lower(), core.lower(), test,
1184 if tid not in [itm["id"] for itm in store_sel]:
1191 "framesize": framesize.lower(),
1192 "core": core.lower(),
1193 "testtype": ttype.lower()
1195 store_sel = sorted(store_sel, key=lambda d: d["id"])
1196 row_card_sel_tests = C.STYLE_ENABLED
1197 row_btns_sel_tests = C.STYLE_ENABLED
1198 if C.CLEAR_ALL_INPUTS:
1199 ctrl_panel.set(ctrl_panel.defaults)
1200 elif trigger_id == "btn-sel-remove-all":
1202 row_fig_tput = C.PLACEHOLDER
1203 row_fig_lat = C.PLACEHOLDER
1204 row_btn_dwnld = C.PLACEHOLDER
1205 row_card_sel_tests = C.STYLE_DISABLED
1206 row_btns_sel_tests = C.STYLE_DISABLED
1208 ctrl_panel.set({"cl-selected-options": list()})
1209 elif trigger_id == "btn-sel-remove":
1212 new_store_sel = list()
1213 for item in store_sel:
1214 if item["id"] not in list_sel:
1215 new_store_sel.append(item)
1216 store_sel = new_store_sel
1217 elif trigger_id == "url":
1220 store_sel = literal_eval(url_params["store_sel"][0])
1221 d_start = get_date(url_params["start"][0])
1222 d_end = get_date(url_params["end"][0])
1223 norm = literal_eval(url_params["norm"][0])
1224 except (KeyError, IndexError):
1227 row_card_sel_tests = C.STYLE_ENABLED
1228 row_btns_sel_tests = C.STYLE_ENABLED
1229 last_test = store_sel[-1]
1230 test = self.spec_tbs[last_test["dut"]]\
1231 [last_test["phy"]][last_test["area"]]\
1234 "dd-ctrl-dut-value": last_test["dut"],
1235 "dd-ctrl-phy-value": last_test["phy"],
1236 "dd-ctrl-phy-options": generate_options(sorted(
1237 self.spec_tbs[last_test["dut"]].keys())),
1238 "dd-ctrl-phy-disabled": False,
1239 "dd-ctrl-area-value": last_test["area"],
1240 "dd-ctrl-area-options": [
1241 {"label": label(v), "value": v} \
1243 self.spec_tbs[last_test["dut"]]\
1244 [last_test["phy"]].keys())
1246 "dd-ctrl-area-disabled": False,
1247 "dd-ctrl-test-value": last_test["test"],
1248 "dd-ctrl-test-options": generate_options(sorted(
1249 self.spec_tbs[last_test["dut"]]\
1251 [last_test["area"]].keys())),
1252 "dd-ctrl-test-disabled": False,
1253 "cl-ctrl-core-options": generate_options(sorted(
1255 "cl-ctrl-core-value": [last_test["core"].upper(), ],
1256 "cl-ctrl-core-all-value": list(),
1257 "cl-ctrl-core-all-options": C.CL_ALL_ENABLED,
1258 "cl-ctrl-framesize-options": generate_options(
1259 sorted(test["frame-size"])),
1260 "cl-ctrl-framesize-value": \
1261 [last_test["framesize"].upper(), ],
1262 "cl-ctrl-framesize-all-value": list(),
1263 "cl-ctrl-framesize-all-options": C.CL_ALL_ENABLED,
1264 "cl-ctrl-testtype-options": generate_options(sorted(
1265 test["test-type"])),
1266 "cl-ctrl-testtype-value": \
1267 [last_test["testtype"].upper(), ],
1268 "cl-ctrl-testtype-all-value": list(),
1269 "cl-ctrl-testtype-all-options": C.CL_ALL_ENABLED
1272 if trigger_id in ("btn-ctrl-add", "url", "dpr-period",
1273 "btn-sel-remove", "cl-ctrl-normalize"):
1275 row_fig_tput, row_fig_lat, row_btn_dwnld = \
1276 _generate_plotting_area(
1277 graph_trending(self.data, store_sel, self.layout,
1278 d_start, d_end, bool(norm)),
1282 "store_sel": store_sel,
1290 "cl-selected-options": list_tests(store_sel),
1291 "dpr-start-date": d_start,
1292 "dpr-end-date": d_end
1295 row_fig_tput = C.PLACEHOLDER
1296 row_fig_lat = C.PLACEHOLDER
1297 row_btn_dwnld = C.PLACEHOLDER
1298 row_card_sel_tests = C.STYLE_DISABLED
1299 row_btns_sel_tests = C.STYLE_DISABLED
1301 ctrl_panel.set({"cl-selected-options": list()})
1303 if ctrl_panel.get("cl-ctrl-core-value") and \
1304 ctrl_panel.get("cl-ctrl-framesize-value") and \
1305 ctrl_panel.get("cl-ctrl-testtype-value"):
1310 "btn-ctrl-add-disabled": disabled,
1311 "cl-normalize-value": norm
1315 ctrl_panel.panel, store_sel,
1316 row_fig_tput, row_fig_lat, row_btn_dwnld,
1317 row_card_sel_tests, row_btns_sel_tests
1319 ret_val.extend(ctrl_panel.values())
1323 Output("metadata-tput-lat", "children"),
1324 Output("metadata-hdrh-graph", "children"),
1325 Output("offcanvas-metadata", "is_open"),
1326 Input({"type": "graph", "index": ALL}, "clickData"),
1327 prevent_initial_call=True
1329 def _show_metadata_from_graphs(graph_data: dict) -> tuple:
1330 """Generates the data for the offcanvas displayed when a particular
1331 point in a graph is clicked on.
1333 :param graph_data: The data from the clicked point in the graph.
1334 :type graph_data: dict
1335 :returns: The data to be displayed on the offcanvas and the
1336 information to show the offcanvas.
1337 :rtype: tuple(list, list, bool)
1341 callback_context.triggered[0]["prop_id"].split(".")[0]
1343 idx = 0 if trigger_id == "tput" else 1
1344 graph_data = graph_data[idx]["points"][0]
1345 except (JSONDecodeError, IndexError, KeyError, ValueError,
1349 metadata = no_update
1354 [dbc.Badge(x.split(":")[0]), x.split(": ")[1]]
1355 ) for x in graph_data.get("text", "").split("<br>")
1357 if trigger_id == "tput":
1358 title = "Throughput"
1359 elif trigger_id == "lat":
1361 hdrh_data = graph_data.get("customdata", None)
1364 class_name="gy-2 p-0",
1366 dbc.CardHeader(hdrh_data.pop("name")),
1367 dbc.CardBody(children=[
1369 id="hdrh-latency-graph",
1370 figure=graph_hdrh_latency(
1371 hdrh_data, self.layout
1379 class_name="gy-2 p-0",
1381 dbc.CardHeader(children=[
1383 target_id="tput-lat-metadata",
1385 style={"display": "inline-block"}
1390 id="tput-lat-metadata",
1392 children=[dbc.ListGroup(children, flush=True), ]
1398 return metadata, graph, True
1401 Output("download-data", "data"),
1402 State("selected-tests", "data"),
1403 Input("btn-download-data", "n_clicks"),
1404 prevent_initial_call=True
1406 def _download_data(store_sel, n_clicks):
1407 """Download the data
1409 :param store_sel: List of tests selected by user stored in the
1411 :param n_clicks: Number of clicks on the button "Download".
1412 :type store_sel: list
1414 :returns: dict of data frame content (base64 encoded) and meta data
1415 used by the Download component.
1426 for itm in store_sel:
1427 sel_data = select_trending_data(self.data, itm)
1428 if sel_data is None:
1430 df = pd.concat([df, sel_data], ignore_index=True)
1432 return dcc.send_data_frame(df.to_csv, C.TREND_DOWNLOAD_FILE_NAME)