"""Plotly Dash HTML layout override.
"""
+import logging
import pandas as pd
import dash_bootstrap_components as dbc
from datetime import datetime, timedelta
from copy import deepcopy
from json import loads, JSONDecodeError
+from ast import literal_eval
from ..data.data import Data
+from ..data.url_processing import url_decode, url_encode
from .graphs import graph_trending, graph_hdrh_latency, \
select_trending_data
"""
"""
+ # If True, clear all inputs in control panel when button "ADD SELECTED" is
+ # pressed.
+ CLEAR_ALL_INPUTS = False
+
STYLE_DISABLED = {"display": "none"}
STYLE_ENABLED = {"display": "inherit"}
"nfv_density-dcr_memif-chain": "CNF Service Chains Routing",
}
- def __init__(self, app: Flask, html_layout_file: str, spec_file: str,
- graph_layout_file: str, data_spec_file: str,
+ URL_STYLE = {
+ "background-color": "#d2ebf5",
+ "border-color": "#bce1f1",
+ "color": "#135d7c"
+ }
+
+ def __init__(self, app: Flask, html_layout_file: str,
+ graph_layout_file: str, data_spec_file: str, tooltip_file: str,
time_period: str=None) -> None:
"""
"""
# Inputs
self._app = app
self._html_layout_file = html_layout_file
- self._spec_file = spec_file
self._graph_layout_file = graph_layout_file
self._data_spec_file = data_spec_file
+ self._tooltip_file = tooltip_file
self._time_period = time_period
# Read the data:
infra = "-".join((tbed, nic, driver))
lst_test = test.split("-")
framesize = lst_test[0]
- core = lst_test[1] if lst_test[1] else "1C"
+ core = lst_test[1] if lst_test[1] else "8C"
test = "-".join(lst_test[2: -1])
if tbs.get(dut, None) is None:
# Read from files:
self._html_layout = ""
self._graph_layout = None
+ self._tooltips = dict()
try:
with open(self._html_layout_file, "r") as file_read:
except YAMLError as err:
raise RuntimeError(
f"An error occurred while parsing the specification file "
- f"{self._graph_layout_file}\n"
- f"{err}"
+ f"{self._graph_layout_file}\n{err}"
+ )
+
+ try:
+ with open(self._tooltip_file, "r") as file_read:
+ self._tooltips = load(file_read, Loader=FullLoader)
+ except IOError as err:
+ logging.warning(
+ f"Not possible to open the file {self._tooltip_file}\n{err}"
+ )
+ except YAMLError as err:
+ logging.warning(
+ f"An error occurred while parsing the specification file "
+ f"{self._tooltip_file}\n{err}"
)
# Callbacks:
def label(self, key: str) -> str:
return self.LABELS.get(key, key)
+ def _show_tooltip(self, id: str, title: str,
+ clipboard_id: str=None) -> list:
+ """
+ """
+ return [
+ dcc.Clipboard(target_id=clipboard_id, title="Copy URL") \
+ if clipboard_id else str(),
+ f"{title} ",
+ dbc.Badge(
+ id=id,
+ children="?",
+ pill=True,
+ color="white",
+ text_color="info",
+ class_name="border ms-1",
+ ),
+ dbc.Tooltip(
+ children=self._tooltips.get(id, str()),
+ target=id,
+ placement="auto"
+ )
+ ]
+
def add_content(self):
"""
"""
id="row-main",
class_name="g-0",
children=[
- dcc.Store(
- id="selected-tests"
- ),
- dcc.Store(
- id="control-panel"
- ),
+ dcc.Store(id="selected-tests"),
+ dcc.Store(id="control-panel"),
+ dcc.Location(id="url", refresh=False),
self._add_ctrl_col(),
self._add_plotting_col(),
]
children=[
dbc.InputGroup(
[
- dbc.InputGroupText("DUT"),
+ dbc.InputGroupText(
+ children=self._show_tooltip(
+ "help-dut", "DUT")
+ ),
dbc.Select(
id="dd-ctrl-dut",
placeholder=(
children=[
dbc.InputGroup(
[
- dbc.InputGroupText("Infra"),
+ dbc.InputGroupText(
+ children=self._show_tooltip(
+ "help-infra", "Infra")
+ ),
dbc.Select(
id="dd-ctrl-phy",
placeholder=(
children=[
dbc.InputGroup(
[
- dbc.InputGroupText("Area"),
+ dbc.InputGroupText(
+ children=self._show_tooltip(
+ "help-area", "Area")
+ ),
dbc.Select(
id="dd-ctrl-area",
placeholder="Select an Area...",
children=[
dbc.InputGroup(
[
- dbc.InputGroupText("Test"),
+ dbc.InputGroupText(
+ children=self._show_tooltip(
+ "help-test", "Test")
+ ),
dbc.Select(
id="dd-ctrl-test",
placeholder="Select a Test...",
]
),
dbc.Row(
- id="row-ctrl-core",
+ id="row-ctrl-framesize",
class_name="gy-1",
children=[
dbc.Label(
- "Number of Cores",
+ children=self._show_tooltip(
+ "help-framesize", "Frame Size"),
class_name="p-0"
),
dbc.Col(
children=[
dbc.Checklist(
- id="cl-ctrl-core-all",
+ id="cl-ctrl-framesize-all",
options=self.CL_ALL_DISABLED,
- inline=False,
+ inline=True,
switch=False
- )
+ ),
],
width=3
),
dbc.Col(
children=[
dbc.Checklist(
- id="cl-ctrl-core",
+ id="cl-ctrl-framesize",
inline=True,
switch=False
)
]
),
dbc.Row(
- id="row-ctrl-framesize",
+ id="row-ctrl-core",
class_name="gy-1",
children=[
dbc.Label(
- "Frame Size",
+ children=self._show_tooltip(
+ "help-cores", "Number of Cores"),
class_name="p-0"
),
dbc.Col(
children=[
dbc.Checklist(
- id="cl-ctrl-framesize-all",
+ id="cl-ctrl-core-all",
options=self.CL_ALL_DISABLED,
- inline=True,
+ inline=False,
switch=False
- ),
+ )
],
width=3
),
dbc.Col(
children=[
dbc.Checklist(
- id="cl-ctrl-framesize",
+ id="cl-ctrl-core",
inline=True,
switch=False
)
class_name="gy-1",
children=[
dbc.Label(
- "Test Type",
+ children=self._show_tooltip(
+ "help-ttype", "Test Type"),
class_name="p-0"
),
dbc.Col(
)
]
),
+ dbc.Row(
+ id="row-ctrl-normalize",
+ class_name="gy-1",
+ children=[
+ dbc.Label(
+ children=self._show_tooltip(
+ "help-normalize", "Normalize"),
+ class_name="p-0"
+ ),
+ dbc.Col(
+ children=[
+ dbc.Checklist(
+ id="cl-ctrl-normalize",
+ options=[{
+ "value": "normalize",
+ "label": (
+ "Normalize results to CPU"
+ "frequency 2GHz"
+ )
+ }],
+ value=[],
+ inline=True,
+ switch=False
+ ),
+ ]
+ )
+ ]
+ ),
dbc.Row(
class_name="gy-1 p-0",
children=[
dbc.Row(
class_name="gy-1",
children=[
+ dbc.Label(
+ class_name="gy-1",
+ children=self._show_tooltip(
+ "help-time-period", "Time Period"),
+ ),
dcc.DatePickerRange(
id="dpr-period",
className="d-flex justify-content-center",
datetime.utcnow() - timedelta(
days=self.time_period),
end_date=datetime.utcnow(),
- display_format="D MMMM YY"
+ display_format="D MMM YY"
)
]
),
"cl-ctrl-testtype-all-value": list(),
"cl-ctrl-testtype-all-options": CL_ALL_DISABLED,
"btn-ctrl-add-disabled": True,
+ "cl-normalize-value": list(),
"cl-selected-options": list(),
}
else:
return list()
+ @staticmethod
+ def _get_date(s_date: str) -> datetime:
+ return datetime(int(s_date[0:4]), int(s_date[5:7]), int(s_date[8:10]))
+
def callbacks(self, app):
- def _generate_plotting_arrea(args: tuple) -> tuple:
+ def _generate_plotting_area(figs: tuple, url: str) -> tuple:
"""
"""
- (fig_tput, fig_lat) = args
+ (fig_tput, fig_lat) = figs
row_fig_tput = self.PLACEHOLDER
row_fig_lat = self.PLACEHOLDER
)
]
row_btn_dwnld = [
- dcc.Loading(children=[
- dbc.Button(
- id="btn-download-data",
- children=["Download Data"],
- class_name="me-1",
- color="info"
- ),
- dcc.Download(id="download-data")
- ]),
+ dbc.Col( # Download
+ width=2,
+ children=[
+ dcc.Loading(children=[
+ dbc.Button(
+ id="btn-download-data",
+ children=self._show_tooltip(
+ "help-download", "Download Data"),
+ class_name="me-1",
+ color="info"
+ ),
+ dcc.Download(id="download-data")
+ ]),
+ ]
+ ),
+ dbc.Col( # Show URL
+ width=10,
+ children=[
+ dbc.InputGroup(
+ class_name="me-1",
+ children=[
+ dbc.InputGroupText(
+ style=self.URL_STYLE,
+ children=self._show_tooltip(
+ "help-url", "URL", "input-url")
+ ),
+ dbc.Input(
+ id="input-url",
+ readonly=True,
+ type="url",
+ style=self.URL_STYLE,
+ value=url
+ )
+ ]
+ )
+ ]
+ )
]
if fig_lat:
row_fig_lat = [
Output("cl-ctrl-testtype-all", "value"),
Output("cl-ctrl-testtype-all", "options"),
Output("btn-ctrl-add", "disabled"),
+ Output("cl-ctrl-normalize", "value"),
Output("cl-selected", "options"), # User selection
State("control-panel", "data"), # Store
State("selected-tests", "data"), # Store
Input("cl-ctrl-framesize-all", "value"),
Input("cl-ctrl-testtype", "value"),
Input("cl-ctrl-testtype-all", "value"),
+ Input("cl-ctrl-normalize", "value"),
Input("btn-ctrl-add", "n_clicks"),
Input("dpr-period", "start_date"),
Input("dpr-period", "end_date"),
Input("btn-sel-remove", "n_clicks"),
Input("btn-sel-remove-all", "n_clicks"),
+ Input("url", "href")
)
def _update_ctrl_panel(cp_data: dict, store_sel: list, list_sel: list,
dd_dut: str, dd_phy: str, dd_area: str, dd_test: str, cl_core: list,
cl_core_all: list, cl_framesize: list, cl_framesize_all: list,
- cl_testtype: list, cl_testtype_all: list, btn_add: int,
- d_start: str, d_end: str, btn_remove: int,
- btn_remove_all: int) -> tuple:
+ cl_testtype: list, cl_testtype_all: list, cl_normalize: list,
+ btn_add: int, d_start: str, d_end: str, btn_remove: int,
+ btn_remove_all: int, href: str) -> tuple:
"""
"""
- d_start = datetime(int(d_start[0:4]), int(d_start[5:7]),
- int(d_start[8:10]))
- d_end = datetime(int(d_end[0:4]), int(d_end[5:7]), int(d_end[8:10]))
+ def _gen_new_url(parsed_url: dict, store_sel: list,
+ start: datetime, end: datetime) -> str:
+
+ if parsed_url:
+ new_url = url_encode({
+ "scheme": parsed_url["scheme"],
+ "netloc": parsed_url["netloc"],
+ "path": parsed_url["path"],
+ "params": {
+ "store_sel": store_sel,
+ "start": start,
+ "end": end
+ }
+ })
+ else:
+ new_url = str()
+ return new_url
+
+
+ ctrl_panel = self.ControlPanel(cp_data)
+
+ d_start = self._get_date(d_start)
+ d_end = self._get_date(d_end)
+
+ # Parse the url:
+ parsed_url = url_decode(href)
row_fig_tput = no_update
row_fig_lat = no_update
row_card_sel_tests = no_update
row_btns_sel_tests = no_update
- ctrl_panel = self.ControlPanel(cp_data)
-
trigger_id = callback_context.triggered[0]["prop_id"].split(".")[0]
if trigger_id == "dd-ctrl-dut":
try:
+ dut = self.spec_tbs[dd_dut]
options = sorted(
- [
- {"label": v, "value": v}
- for v in self.spec_tbs[dd_dut].keys()
- ],
+ [{"label": v, "value": v}for v in dut.keys()],
key=lambda d: d["label"]
)
disabled = False
"dd-ctrl-area-value": str(),
"dd-ctrl-area-options": list(),
"dd-ctrl-area-disabled": True,
+ "dd-ctrl-test-value": str(),
"dd-ctrl-test-options": list(),
"dd-ctrl-test-disabled": True,
"cl-ctrl-core-options": list(),
"cl-ctrl-testtype-all-value": list(),
"cl-ctrl-testtype-all-options": self.CL_ALL_DISABLED,
})
- if trigger_id == "dd-ctrl-phy":
+ elif trigger_id == "dd-ctrl-phy":
try:
dut = ctrl_panel.get("dd-ctrl-dut-value")
+ phy = self.spec_tbs[dut][dd_phy]
options = sorted(
- [
- {"label": self.label(v), "value": v}
- for v in self.spec_tbs[dut][dd_phy].keys()
- ],
+ [{"label": self.label(v), "value": v}
+ for v in phy.keys()],
key=lambda d: d["label"]
)
disabled = False
"dd-ctrl-area-value": str(),
"dd-ctrl-area-options": options,
"dd-ctrl-area-disabled": disabled,
+ "dd-ctrl-test-value": str(),
"dd-ctrl-test-options": list(),
"dd-ctrl-test-disabled": True,
"cl-ctrl-core-options": list(),
try:
dut = ctrl_panel.get("dd-ctrl-dut-value")
phy = ctrl_panel.get("dd-ctrl-phy-value")
+ area = self.spec_tbs[dut][phy][dd_area]
options = sorted(
- [
- {"label": v, "value": v}
- for v in self.spec_tbs[dut][phy][dd_area].keys()
- ],
+ [{"label": v, "value": v} for v in area.keys()],
key=lambda d: d["label"]
)
disabled = False
dut = ctrl_panel.get("dd-ctrl-dut-value")
phy = ctrl_panel.get("dd-ctrl-phy-value")
area = ctrl_panel.get("dd-ctrl-area-value")
- cores = self.spec_tbs[dut][phy][area][dd_test]["core"]
- fsizes = self.spec_tbs[dut][phy][area][dd_test]["frame-size"]
- ttypes = self.spec_tbs[dut][phy][area][dd_test]["test-type"]
+ test = self.spec_tbs[dut][phy][area][dd_test]
+ cores = test["core"]
+ fsizes = test["frame-size"]
+ ttypes = test["test-type"]
if dut and phy and area and dd_test:
- core_opts = [
- {"label": v, "value": v} for v in sorted(cores)
- ]
- framesize_opts = [
- {"label": v, "value": v} for v in sorted(fsizes)
- ]
- testtype_opts = [
- {"label": v, "value": v}for v in sorted(ttypes)
- ]
+ core_opts = [{"label": v, "value": v}
+ for v in sorted(cores)]
+ framesize_opts = [{"label": v, "value": v}
+ for v in sorted(fsizes)]
+ testtype_opts = [{"label": v, "value": v}
+ for v in sorted(ttypes)]
ctrl_panel.set({
"dd-ctrl-test-value": dd_test,
"cl-ctrl-core-options": core_opts,
store_sel = sorted(store_sel, key=lambda d: d["id"])
row_card_sel_tests = self.STYLE_ENABLED
row_btns_sel_tests = self.STYLE_ENABLED
- ctrl_panel.set(ctrl_panel.defaults)
- ctrl_panel.set({
- "cl-selected-options": self._list_tests(store_sel)
- })
- row_fig_tput, row_fig_lat, row_btn_dwnld = \
- _generate_plotting_arrea(
- graph_trending(
- self.data, store_sel, self.layout, d_start,
- d_end
- )
- )
- elif trigger_id == "dpr-period":
- row_fig_tput, row_fig_lat, row_btn_dwnld = \
- _generate_plotting_arrea(
- graph_trending(
- self.data, store_sel, self.layout, d_start, d_end
- )
- )
+ if self.CLEAR_ALL_INPUTS:
+ ctrl_panel.set(ctrl_panel.defaults)
elif trigger_id == "btn-sel-remove-all":
_ = btn_remove_all
row_fig_tput = self.PLACEHOLDER
row_card_sel_tests = self.STYLE_DISABLED
row_btns_sel_tests = self.STYLE_DISABLED
store_sel = list()
- ctrl_panel.set({
- "cl-selected-options": list()
- })
+ ctrl_panel.set({"cl-selected-options": list()})
elif trigger_id == "btn-sel-remove":
_ = btn_remove
if list_sel:
if item["id"] not in list_sel:
new_store_sel.append(item)
store_sel = new_store_sel
+ elif trigger_id == "url":
+ # TODO: Add verification
+ url_params = parsed_url["params"]
+ if url_params:
+ store_sel = literal_eval(
+ url_params.get("store_sel", list())[0])
+ d_start = self._get_date(url_params.get("start", list())[0])
+ d_end = self._get_date(url_params.get("end", list())[0])
+ if store_sel:
+ row_card_sel_tests = self.STYLE_ENABLED
+ row_btns_sel_tests = self.STYLE_ENABLED
+
+ if trigger_id in ("btn-ctrl-add", "url", "dpr-period"
+ "btn-sel-remove", "cl-ctrl-normalize"):
if store_sel:
row_fig_tput, row_fig_lat, row_btn_dwnld = \
- _generate_plotting_arrea(
- graph_trending(
- self.data, store_sel, self.layout, d_start,
- d_end
- )
+ _generate_plotting_area(
+ graph_trending(self.data, store_sel, self.layout,
+ d_start, d_end, bool(cl_normalize)),
+ _gen_new_url(parsed_url, store_sel, d_start, d_end)
)
ctrl_panel.set({
"cl-selected-options": self._list_tests(store_sel)
row_card_sel_tests = self.STYLE_DISABLED
row_btns_sel_tests = self.STYLE_DISABLED
store_sel = list()
- ctrl_panel.set({
- "cl-selected-options": list()
- })
+ ctrl_panel.set({"cl-selected-options": list()})
if ctrl_panel.get("cl-ctrl-core-value") and \
ctrl_panel.get("cl-ctrl-framesize-value") and \
else:
disabled = True
ctrl_panel.set({
- "btn-ctrl-add-disabled": disabled
+ "btn-ctrl-add-disabled": disabled,
+ "cl-normalize-value": cl_normalize
})
ret_val = [