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 replace("_title_", C.TREND_TITLE)
170 except IOError as err:
172 f"Not possible to open the file {self._html_layout_file}\n{err}"
176 with open(self._graph_layout_file, "r") as file_read:
177 self._graph_layout = load(file_read, Loader=FullLoader)
178 except IOError as err:
180 f"Not possible to open the file {self._graph_layout_file}\n"
183 except YAMLError as err:
185 f"An error occurred while parsing the specification file "
186 f"{self._graph_layout_file}\n{err}"
190 with open(self._tooltip_file, "r") as file_read:
191 self._tooltips = load(file_read, Loader=FullLoader)
192 except IOError as err:
194 f"Not possible to open the file {self._tooltip_file}\n{err}"
196 except YAMLError as err:
198 f"An error occurred while parsing the specification file "
199 f"{self._tooltip_file}\n{err}"
203 if self._app is not None and hasattr(self, 'callbacks'):
204 self.callbacks(self._app)
207 def html_layout(self):
208 return self._html_layout
212 return self._spec_tbs
220 return self._graph_layout
223 def time_period(self):
224 return self._time_period
226 def add_content(self):
227 """Top level method which generated the web page.
230 - Store for user input data,
232 - Main area with control panel and ploting area.
234 If no HTML layout is provided, an error message is displayed instead.
236 :returns: The HTML div with the whole page.
240 if self.html_layout and self.spec_tbs:
255 id="offcanvas-metadata",
256 title="Throughput And Latency",
260 dbc.Row(id="metadata-tput-lat"),
261 dbc.Row(id="metadata-hdrh-graph"),
269 dcc.Store(id="selected-tests"),
270 dcc.Store(id="control-panel"),
271 dcc.Location(id="url", refresh=False),
272 self._add_ctrl_col(),
273 self._add_plotting_col(),
291 def _add_navbar(self):
292 """Add nav element with navigation panel. It is placed on the top.
294 :returns: Navigation bar.
295 :rtype: dbc.NavbarSimple
297 return dbc.NavbarSimple(
298 id="navbarsimple-main",
311 brand_external_link=True,
316 def _add_ctrl_col(self) -> dbc.Col:
317 """Add column with controls. It is placed on the left side.
319 :returns: Column with the control panel.
324 children=self._add_ctrl_panel(),
325 className="sticky-top"
329 def _add_plotting_col(self) -> dbc.Col:
330 """Add column with plots and tables. It is placed on the right side.
332 :returns: Column with tables.
336 id="col-plotting-area",
340 dbc.Row( # Throughput
342 class_name="g-0 p-2",
349 class_name="g-0 p-2",
355 id="row-btn-download",
356 class_name="g-0 p-2",
367 def _add_ctrl_panel(self) -> dbc.Row:
368 """Add control panel.
370 :returns: Control panel.
375 class_name="g-0 p-1",
380 children=show_tooltip(self._tooltips,
386 "Select a Device under Test..."
390 {"label": k, "value": k} \
391 for k in self.spec_tbs.keys()
393 key=lambda d: d["label"]
402 class_name="g-0 p-1",
407 children=show_tooltip(self._tooltips,
408 "help-infra", "Infra")
413 "Select a Physical Test Bed "
423 class_name="g-0 p-1",
428 children=show_tooltip(self._tooltips,
433 placeholder="Select an Area...",
442 class_name="g-0 p-1",
447 children=show_tooltip(self._tooltips,
452 placeholder="Select a Test...",
461 class_name="g-0 p-1",
464 children=show_tooltip(self._tooltips,
465 "help-framesize", "Frame Size"),
470 id="cl-ctrl-framesize-all",
471 options=C.CL_ALL_DISABLED,
481 id="cl-ctrl-framesize",
490 class_name="g-0 p-1",
493 children=show_tooltip(self._tooltips,
494 "help-cores", "Number of Cores"),
499 id="cl-ctrl-core-all",
500 options=C.CL_ALL_DISABLED,
519 class_name="g-0 p-1",
522 children=show_tooltip(self._tooltips,
523 "help-ttype", "Test Type"),
528 id="cl-ctrl-testtype-all",
529 options=C.CL_ALL_DISABLED,
539 id="cl-ctrl-testtype",
548 class_name="g-0 p-1",
551 children=show_tooltip(self._tooltips,
552 "help-normalize", "Normalize"),
557 id="cl-ctrl-normalize",
559 "value": "normalize",
561 "Normalize results to CPU "
574 class_name="g-0 p-1",
580 children="Add Selected",
588 id="row-card-sel-tests",
589 class_name="g-0 p-1",
590 style=C.STYLE_DISABLED,
592 dbc.Label("Selected tests"),
594 class_name="overflow-auto",
598 style={"max-height": "12em"},
603 id="row-btns-sel-tests",
604 class_name="g-0 p-1",
605 style=C.STYLE_DISABLED,
611 children="Remove Selected",
617 id="btn-sel-remove-all",
618 children="Remove All",
630 """A class representing the control panel.
633 def __init__(self, panel: dict) -> None:
634 """Initialisation of the control pannel by default values. If
635 particular values are provided (parameter "panel") they are set
638 :param panel: Custom values to be set to the control panel.
639 :param default: Default values to be set to the control panel.
644 # Defines also the order of keys
646 "dd-ctrl-dut-value": str(),
647 "dd-ctrl-phy-options": list(),
648 "dd-ctrl-phy-disabled": True,
649 "dd-ctrl-phy-value": str(),
650 "dd-ctrl-area-options": list(),
651 "dd-ctrl-area-disabled": True,
652 "dd-ctrl-area-value": str(),
653 "dd-ctrl-test-options": list(),
654 "dd-ctrl-test-disabled": True,
655 "dd-ctrl-test-value": str(),
656 "cl-ctrl-core-options": list(),
657 "cl-ctrl-core-value": list(),
658 "cl-ctrl-core-all-value": list(),
659 "cl-ctrl-core-all-options": C.CL_ALL_DISABLED,
660 "cl-ctrl-framesize-options": list(),
661 "cl-ctrl-framesize-value": list(),
662 "cl-ctrl-framesize-all-value": list(),
663 "cl-ctrl-framesize-all-options": C.CL_ALL_DISABLED,
664 "cl-ctrl-testtype-options": list(),
665 "cl-ctrl-testtype-value": list(),
666 "cl-ctrl-testtype-all-value": list(),
667 "cl-ctrl-testtype-all-options": C.CL_ALL_DISABLED,
668 "btn-ctrl-add-disabled": True,
669 "cl-normalize-value": list(),
670 "cl-selected-options": list()
673 self._panel = deepcopy(self._defaults)
675 for key in self._defaults:
676 self._panel[key] = panel[key]
679 def defaults(self) -> dict:
680 return self._defaults
683 def panel(self) -> dict:
686 def set(self, kwargs: dict) -> None:
687 """Set the values of the Control panel.
689 :param kwargs: key - value pairs to be set.
691 :raises KeyError: If the key in kwargs is not present in the Control
694 for key, val in kwargs.items():
695 if key in self._panel:
696 self._panel[key] = val
698 raise KeyError(f"The key {key} is not defined.")
700 def get(self, key: str) -> any:
701 """Returns the value of a key from the Control panel.
703 :param key: The key which value should be returned.
705 :returns: The value of the key.
707 :raises KeyError: If the key in kwargs is not present in the Control
710 return self._panel[key]
712 def values(self) -> tuple:
713 """Returns the values from the Control panel as a list.
715 :returns: The values from the Control panel.
718 return tuple(self._panel.values())
720 def callbacks(self, app):
721 """Callbacks for the whole application.
723 :param app: The application.
727 def _generate_plotting_area(figs: tuple, url: str) -> tuple:
728 """Generate the plotting area with all its content.
730 :param figs: Figures to be placed in the plotting area.
731 :param utl: The URL to be placed in the plotting area bellow the
733 :type figs: tuple of plotly.graph_objects.Figure
735 :returns: tuple of elements to be shown in the plotting area.
736 :rtype: tuple(dcc.Graph, dcc.Graph, list(dbc.Col, dbc.Col))
739 (fig_tput, fig_lat) = figs
741 row_fig_tput = C.PLACEHOLDER
742 row_fig_lat = C.PLACEHOLDER
743 row_btn_dwnld = C.PLACEHOLDER
748 id={"type": "graph", "index": "tput"},
756 dcc.Loading(children=[
758 id="btn-download-data",
759 children=show_tooltip(self._tooltips,
760 "help-download", "Download Data"),
764 dcc.Download(id="download-data")
776 children=show_tooltip(self._tooltips,
777 "help-url", "URL", "input-url")
794 id={"type": "graph", "index": "lat"},
799 return row_fig_tput, row_fig_lat, row_btn_dwnld
802 Output("control-panel", "data"), # Store
803 Output("selected-tests", "data"), # Store
804 Output("row-graph-tput", "children"),
805 Output("row-graph-lat", "children"),
806 Output("row-btn-download", "children"),
807 Output("row-card-sel-tests", "style"),
808 Output("row-btns-sel-tests", "style"),
809 Output("dd-ctrl-dut", "value"),
810 Output("dd-ctrl-phy", "options"),
811 Output("dd-ctrl-phy", "disabled"),
812 Output("dd-ctrl-phy", "value"),
813 Output("dd-ctrl-area", "options"),
814 Output("dd-ctrl-area", "disabled"),
815 Output("dd-ctrl-area", "value"),
816 Output("dd-ctrl-test", "options"),
817 Output("dd-ctrl-test", "disabled"),
818 Output("dd-ctrl-test", "value"),
819 Output("cl-ctrl-core", "options"),
820 Output("cl-ctrl-core", "value"),
821 Output("cl-ctrl-core-all", "value"),
822 Output("cl-ctrl-core-all", "options"),
823 Output("cl-ctrl-framesize", "options"),
824 Output("cl-ctrl-framesize", "value"),
825 Output("cl-ctrl-framesize-all", "value"),
826 Output("cl-ctrl-framesize-all", "options"),
827 Output("cl-ctrl-testtype", "options"),
828 Output("cl-ctrl-testtype", "value"),
829 Output("cl-ctrl-testtype-all", "value"),
830 Output("cl-ctrl-testtype-all", "options"),
831 Output("btn-ctrl-add", "disabled"),
832 Output("cl-ctrl-normalize", "value"),
833 Output("cl-selected", "options"), # User selection
834 State("control-panel", "data"), # Store
835 State("selected-tests", "data"), # Store
836 State("cl-selected", "value"), # User selection
837 Input("dd-ctrl-dut", "value"),
838 Input("dd-ctrl-phy", "value"),
839 Input("dd-ctrl-area", "value"),
840 Input("dd-ctrl-test", "value"),
841 Input("cl-ctrl-core", "value"),
842 Input("cl-ctrl-core-all", "value"),
843 Input("cl-ctrl-framesize", "value"),
844 Input("cl-ctrl-framesize-all", "value"),
845 Input("cl-ctrl-testtype", "value"),
846 Input("cl-ctrl-testtype-all", "value"),
847 Input("cl-ctrl-normalize", "value"),
848 Input("btn-ctrl-add", "n_clicks"),
849 Input("btn-sel-remove", "n_clicks"),
850 Input("btn-sel-remove-all", "n_clicks"),
853 def _update_ctrl_panel(cp_data: dict, store_sel: list, list_sel: list,
854 dd_dut: str, dd_phy: str, dd_area: str, dd_test: str, cl_core: list,
855 cl_core_all: list, cl_framesize: list, cl_framesize_all: list,
856 cl_testtype: list, cl_testtype_all: list, cl_normalize: list,
857 btn_add: int, btn_remove: int,
858 btn_remove_all: int, href: str) -> tuple:
859 """Update the application when the event is detected.
861 :param cp_data: Current status of the control panel stored in
863 :param store_sel: List of tests selected by user stored in the
865 :param list_sel: List of tests selected by the user shown in the
867 :param dd_dut: Input - DUTs.
868 :param dd_phy: Input - topo- arch-nic-driver.
869 :param dd_area: Input - Tested area.
870 :param dd_test: Input - Test.
871 :param cl_core: Input - Number of cores.
872 :param cl_core_all: Input - All numbers of cores.
873 :param cl_framesize: Input - Frame sizes.
874 :param cl_framesize_all: Input - All frame sizes.
875 :param cl_testtype: Input - Test type (NDR, PDR, MRR).
876 :param cl_testtype_all: Input - All test types.
877 :param cl_normalize: Input - Normalize the results.
878 :param btn_add: Input - Button "Add Selected" tests.
879 :param btn_remove: Input - Button "Remove selected" tests.
880 :param btn_remove_all: Input - Button "Remove All" tests.
881 :param href: Input - The URL provided by the browser.
883 :type store_sel: list
890 :type cl_core_all: list
891 :type cl_framesize: list
892 :type cl_framesize_all: list
893 :type cl_testtype: list
894 :type cl_testtype_all: list
895 :type cl_normalize: list
897 :type btn_remove: int
898 :type btn_remove_all: int
900 :returns: New values for web page elements.
904 ctrl_panel = self.ControlPanel(cp_data)
908 parsed_url = url_decode(href)
910 url_params = parsed_url["params"]
914 row_fig_tput = no_update
915 row_fig_lat = no_update
916 row_btn_dwnld = no_update
917 row_card_sel_tests = no_update
918 row_btns_sel_tests = no_update
920 trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
922 if trigger_id == "dd-ctrl-dut":
925 generate_options(sorted(self.spec_tbs[dd_dut].keys()))
931 "dd-ctrl-dut-value": dd_dut,
932 "dd-ctrl-phy-value": str(),
933 "dd-ctrl-phy-options": options,
934 "dd-ctrl-phy-disabled": disabled,
935 "dd-ctrl-area-value": str(),
936 "dd-ctrl-area-options": list(),
937 "dd-ctrl-area-disabled": True,
938 "dd-ctrl-test-value": str(),
939 "dd-ctrl-test-options": list(),
940 "dd-ctrl-test-disabled": True,
941 "cl-ctrl-core-options": list(),
942 "cl-ctrl-core-value": list(),
943 "cl-ctrl-core-all-value": list(),
944 "cl-ctrl-core-all-options": C.CL_ALL_DISABLED,
945 "cl-ctrl-framesize-options": list(),
946 "cl-ctrl-framesize-value": list(),
947 "cl-ctrl-framesize-all-value": list(),
948 "cl-ctrl-framesize-all-options": C.CL_ALL_DISABLED,
949 "cl-ctrl-testtype-options": list(),
950 "cl-ctrl-testtype-value": list(),
951 "cl-ctrl-testtype-all-value": list(),
952 "cl-ctrl-testtype-all-options": C.CL_ALL_DISABLED,
954 elif trigger_id == "dd-ctrl-phy":
956 dut = ctrl_panel.get("dd-ctrl-dut-value")
957 phy = self.spec_tbs[dut][dd_phy]
958 options = [{"label": label(v), "value": v} \
959 for v in sorted(phy.keys())]
965 "dd-ctrl-phy-value": dd_phy,
966 "dd-ctrl-area-value": str(),
967 "dd-ctrl-area-options": options,
968 "dd-ctrl-area-disabled": disabled,
969 "dd-ctrl-test-value": str(),
970 "dd-ctrl-test-options": list(),
971 "dd-ctrl-test-disabled": True,
972 "cl-ctrl-core-options": list(),
973 "cl-ctrl-core-value": list(),
974 "cl-ctrl-core-all-value": list(),
975 "cl-ctrl-core-all-options": C.CL_ALL_DISABLED,
976 "cl-ctrl-framesize-options": list(),
977 "cl-ctrl-framesize-value": list(),
978 "cl-ctrl-framesize-all-value": list(),
979 "cl-ctrl-framesize-all-options": C.CL_ALL_DISABLED,
980 "cl-ctrl-testtype-options": list(),
981 "cl-ctrl-testtype-value": list(),
982 "cl-ctrl-testtype-all-value": list(),
983 "cl-ctrl-testtype-all-options": C.CL_ALL_DISABLED,
985 elif trigger_id == "dd-ctrl-area":
987 dut = ctrl_panel.get("dd-ctrl-dut-value")
988 phy = ctrl_panel.get("dd-ctrl-phy-value")
989 area = self.spec_tbs[dut][phy][dd_area]
990 options = generate_options(sorted(area.keys()))
996 "dd-ctrl-area-value": dd_area,
997 "dd-ctrl-test-value": str(),
998 "dd-ctrl-test-options": options,
999 "dd-ctrl-test-disabled": disabled,
1000 "cl-ctrl-core-options": list(),
1001 "cl-ctrl-core-value": list(),
1002 "cl-ctrl-core-all-value": list(),
1003 "cl-ctrl-core-all-options": C.CL_ALL_DISABLED,
1004 "cl-ctrl-framesize-options": list(),
1005 "cl-ctrl-framesize-value": list(),
1006 "cl-ctrl-framesize-all-value": list(),
1007 "cl-ctrl-framesize-all-options": C.CL_ALL_DISABLED,
1008 "cl-ctrl-testtype-options": list(),
1009 "cl-ctrl-testtype-value": list(),
1010 "cl-ctrl-testtype-all-value": list(),
1011 "cl-ctrl-testtype-all-options": C.CL_ALL_DISABLED,
1013 elif trigger_id == "dd-ctrl-test":
1014 dut = ctrl_panel.get("dd-ctrl-dut-value")
1015 phy = ctrl_panel.get("dd-ctrl-phy-value")
1016 area = ctrl_panel.get("dd-ctrl-area-value")
1017 if all((dut, phy, area, dd_test, )):
1018 test = self.spec_tbs[dut][phy][area][dd_test]
1020 "dd-ctrl-test-value": dd_test,
1021 "cl-ctrl-core-options": \
1022 generate_options(sorted(test["core"])),
1023 "cl-ctrl-core-value": list(),
1024 "cl-ctrl-core-all-value": list(),
1025 "cl-ctrl-core-all-options": C.CL_ALL_ENABLED,
1026 "cl-ctrl-framesize-options": \
1027 generate_options(sorted(test["frame-size"])),
1028 "cl-ctrl-framesize-value": list(),
1029 "cl-ctrl-framesize-all-value": list(),
1030 "cl-ctrl-framesize-all-options": C.CL_ALL_ENABLED,
1031 "cl-ctrl-testtype-options": \
1032 generate_options(sorted(test["test-type"])),
1033 "cl-ctrl-testtype-value": list(),
1034 "cl-ctrl-testtype-all-value": list(),
1035 "cl-ctrl-testtype-all-options": C.CL_ALL_ENABLED,
1037 elif trigger_id == "cl-ctrl-core":
1038 val_sel, val_all = sync_checklists(
1039 options=ctrl_panel.get("cl-ctrl-core-options"),
1045 "cl-ctrl-core-value": val_sel,
1046 "cl-ctrl-core-all-value": val_all,
1048 elif trigger_id == "cl-ctrl-core-all":
1049 val_sel, val_all = sync_checklists(
1050 options = ctrl_panel.get("cl-ctrl-core-options"),
1056 "cl-ctrl-core-value": val_sel,
1057 "cl-ctrl-core-all-value": val_all,
1059 elif trigger_id == "cl-ctrl-framesize":
1060 val_sel, val_all = sync_checklists(
1061 options = ctrl_panel.get("cl-ctrl-framesize-options"),
1067 "cl-ctrl-framesize-value": val_sel,
1068 "cl-ctrl-framesize-all-value": val_all,
1070 elif trigger_id == "cl-ctrl-framesize-all":
1071 val_sel, val_all = sync_checklists(
1072 options = ctrl_panel.get("cl-ctrl-framesize-options"),
1074 all=cl_framesize_all,
1078 "cl-ctrl-framesize-value": val_sel,
1079 "cl-ctrl-framesize-all-value": val_all,
1081 elif trigger_id == "cl-ctrl-testtype":
1082 val_sel, val_all = sync_checklists(
1083 options = ctrl_panel.get("cl-ctrl-testtype-options"),
1089 "cl-ctrl-testtype-value": val_sel,
1090 "cl-ctrl-testtype-all-value": val_all,
1092 elif trigger_id == "cl-ctrl-testtype-all":
1093 val_sel, val_all = sync_checklists(
1094 options = ctrl_panel.get("cl-ctrl-testtype-options"),
1096 all=cl_testtype_all,
1100 "cl-ctrl-testtype-value": val_sel,
1101 "cl-ctrl-testtype-all-value": val_all,
1103 elif trigger_id == "btn-ctrl-add":
1105 dut = ctrl_panel.get("dd-ctrl-dut-value")
1106 phy = ctrl_panel.get("dd-ctrl-phy-value")
1107 area = ctrl_panel.get("dd-ctrl-area-value")
1108 test = ctrl_panel.get("dd-ctrl-test-value")
1109 cores = ctrl_panel.get("cl-ctrl-core-value")
1110 framesizes = ctrl_panel.get("cl-ctrl-framesize-value")
1111 testtypes = ctrl_panel.get("cl-ctrl-testtype-value")
1112 # Add selected test to the list of tests in store:
1113 if all((dut, phy, area, test, cores, framesizes, testtypes)):
1114 if store_sel is None:
1117 for framesize in framesizes:
1118 for ttype in testtypes:
1122 dut, phy.replace('af_xdp', 'af-xdp'), area,
1123 framesize.lower(), core.lower(), test,
1126 if tid not in [itm["id"] for itm in store_sel]:
1133 "framesize": framesize.lower(),
1134 "core": core.lower(),
1135 "testtype": ttype.lower()
1137 store_sel = sorted(store_sel, key=lambda d: d["id"])
1138 row_card_sel_tests = C.STYLE_ENABLED
1139 row_btns_sel_tests = C.STYLE_ENABLED
1140 if C.CLEAR_ALL_INPUTS:
1141 ctrl_panel.set(ctrl_panel.defaults)
1142 elif trigger_id == "btn-sel-remove-all":
1144 row_fig_tput = C.PLACEHOLDER
1145 row_fig_lat = C.PLACEHOLDER
1146 row_btn_dwnld = C.PLACEHOLDER
1147 row_card_sel_tests = C.STYLE_DISABLED
1148 row_btns_sel_tests = C.STYLE_DISABLED
1150 ctrl_panel.set({"cl-selected-options": list()})
1151 elif trigger_id == "btn-sel-remove":
1154 new_store_sel = list()
1155 for item in store_sel:
1156 if item["id"] not in list_sel:
1157 new_store_sel.append(item)
1158 store_sel = new_store_sel
1159 elif trigger_id == "url":
1162 store_sel = literal_eval(url_params["store_sel"][0])
1163 norm = literal_eval(url_params["norm"][0])
1164 except (KeyError, IndexError):
1167 row_card_sel_tests = C.STYLE_ENABLED
1168 row_btns_sel_tests = C.STYLE_ENABLED
1169 last_test = store_sel[-1]
1170 test = self.spec_tbs[last_test["dut"]]\
1171 [last_test["phy"]][last_test["area"]]\
1174 "dd-ctrl-dut-value": last_test["dut"],
1175 "dd-ctrl-phy-value": last_test["phy"],
1176 "dd-ctrl-phy-options": generate_options(sorted(
1177 self.spec_tbs[last_test["dut"]].keys())),
1178 "dd-ctrl-phy-disabled": False,
1179 "dd-ctrl-area-value": last_test["area"],
1180 "dd-ctrl-area-options": [
1181 {"label": label(v), "value": v} \
1183 self.spec_tbs[last_test["dut"]]\
1184 [last_test["phy"]].keys())
1186 "dd-ctrl-area-disabled": False,
1187 "dd-ctrl-test-value": last_test["test"],
1188 "dd-ctrl-test-options": generate_options(sorted(
1189 self.spec_tbs[last_test["dut"]]\
1191 [last_test["area"]].keys())),
1192 "dd-ctrl-test-disabled": False,
1193 "cl-ctrl-core-options": generate_options(sorted(
1195 "cl-ctrl-core-value": [last_test["core"].upper(), ],
1196 "cl-ctrl-core-all-value": list(),
1197 "cl-ctrl-core-all-options": C.CL_ALL_ENABLED,
1198 "cl-ctrl-framesize-options": generate_options(
1199 sorted(test["frame-size"])),
1200 "cl-ctrl-framesize-value": \
1201 [last_test["framesize"].upper(), ],
1202 "cl-ctrl-framesize-all-value": list(),
1203 "cl-ctrl-framesize-all-options": C.CL_ALL_ENABLED,
1204 "cl-ctrl-testtype-options": generate_options(sorted(
1205 test["test-type"])),
1206 "cl-ctrl-testtype-value": \
1207 [last_test["testtype"].upper(), ],
1208 "cl-ctrl-testtype-all-value": list(),
1209 "cl-ctrl-testtype-all-options": C.CL_ALL_ENABLED
1212 if trigger_id in ("btn-ctrl-add", "url", "btn-sel-remove",
1213 "cl-ctrl-normalize"):
1215 row_fig_tput, row_fig_lat, row_btn_dwnld = \
1216 _generate_plotting_area(
1217 graph_trending(self.data, store_sel, self.layout,
1222 "store_sel": store_sel,
1228 "cl-selected-options": list_tests(store_sel)
1231 row_fig_tput = C.PLACEHOLDER
1232 row_fig_lat = C.PLACEHOLDER
1233 row_btn_dwnld = C.PLACEHOLDER
1234 row_card_sel_tests = C.STYLE_DISABLED
1235 row_btns_sel_tests = C.STYLE_DISABLED
1237 ctrl_panel.set({"cl-selected-options": list()})
1239 if ctrl_panel.get("cl-ctrl-core-value") and \
1240 ctrl_panel.get("cl-ctrl-framesize-value") and \
1241 ctrl_panel.get("cl-ctrl-testtype-value"):
1246 "btn-ctrl-add-disabled": disabled,
1247 "cl-normalize-value": norm
1251 ctrl_panel.panel, store_sel,
1252 row_fig_tput, row_fig_lat, row_btn_dwnld,
1253 row_card_sel_tests, row_btns_sel_tests
1255 ret_val.extend(ctrl_panel.values())
1259 Output("metadata-tput-lat", "children"),
1260 Output("metadata-hdrh-graph", "children"),
1261 Output("offcanvas-metadata", "is_open"),
1262 Input({"type": "graph", "index": ALL}, "clickData"),
1263 prevent_initial_call=True
1265 def _show_metadata_from_graphs(graph_data: dict) -> tuple:
1266 """Generates the data for the offcanvas displayed when a particular
1267 point in a graph is clicked on.
1269 :param graph_data: The data from the clicked point in the graph.
1270 :type graph_data: dict
1271 :returns: The data to be displayed on the offcanvas and the
1272 information to show the offcanvas.
1273 :rtype: tuple(list, list, bool)
1277 callback_context.triggered[0]["prop_id"].split(".")[0]
1279 idx = 0 if trigger_id == "tput" else 1
1280 graph_data = graph_data[idx]["points"][0]
1281 except (JSONDecodeError, IndexError, KeyError, ValueError,
1285 metadata = no_update
1290 [dbc.Badge(x.split(":")[0]), x.split(": ")[1]]
1291 ) for x in graph_data.get("text", "").split("<br>")
1293 if trigger_id == "tput":
1294 title = "Throughput"
1295 elif trigger_id == "lat":
1297 hdrh_data = graph_data.get("customdata", None)
1300 class_name="gy-2 p-0",
1302 dbc.CardHeader(hdrh_data.pop("name")),
1303 dbc.CardBody(children=[
1305 id="hdrh-latency-graph",
1306 figure=graph_hdrh_latency(
1307 hdrh_data, self.layout
1315 class_name="gy-2 p-0",
1317 dbc.CardHeader(children=[
1319 target_id="tput-lat-metadata",
1321 style={"display": "inline-block"}
1326 id="tput-lat-metadata",
1328 children=[dbc.ListGroup(children, flush=True), ]
1334 return metadata, graph, True
1337 Output("download-data", "data"),
1338 State("selected-tests", "data"),
1339 Input("btn-download-data", "n_clicks"),
1340 prevent_initial_call=True
1342 def _download_data(store_sel, n_clicks):
1343 """Download the data
1345 :param store_sel: List of tests selected by user stored in the
1347 :param n_clicks: Number of clicks on the button "Download".
1348 :type store_sel: list
1350 :returns: dict of data frame content (base64 encoded) and meta data
1351 used by the Download component.
1362 for itm in store_sel:
1363 sel_data = select_trending_data(self.data, itm)
1364 if sel_data is None:
1366 df = pd.concat([df, sel_data], ignore_index=True)
1368 return dcc.send_data_frame(df.to_csv, C.TREND_DOWNLOAD_FILE_NAME)