UTI: PoC - Dash application for Trending
[csit.git] / resources / tools / dash / app / pal / trending / layout.py
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:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
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.
13
14 """Plotly Dash HTML layout override.
15 """
16
17 import logging
18
19 from dash import dcc
20 from dash import html
21 from dash import Input, Output, callback
22 from dash.exceptions import PreventUpdate
23 from yaml import load, FullLoader, YAMLError
24
25
26 class Layout:
27     """
28     """
29
30     def __init__(self, app, html_layout_file, spec_file):
31         """
32         """
33
34         # Inputs
35         self._app = app
36         self._html_layout_file = html_layout_file
37         self._spec_file = spec_file
38
39         # Read from files:
40         self._html_layout = ""
41         self._spec_test = None
42
43         try:
44             with open(self._html_layout_file, "r") as layout_file:
45                 self._html_layout = layout_file.read()
46         except IOError as err:
47             logging.error(f"Not possible to open the file {layout_file}\n{err}")
48
49         try:
50             with open(self._spec_file, "r") as file_read:
51                 self._spec_test = load(file_read, Loader=FullLoader)
52         except IOError as err:
53             logging.error(f"Not possible to open the file {spec_file}\n{err}")
54         except YAMLError as err:
55             logging.error(
56                 f"An error occurred while parsing the specification file "
57                 f"{spec_file}\n"
58                 f"{err}"
59             )
60
61         # Callbacks:
62         if self._app is not None and hasattr(self, 'callbacks'):
63             self.callbacks(self._app)
64
65         # User choice (one test):
66         self._test_selection = {
67             "phy": "",
68             "area": "",
69             "test": "",
70             "core": "",
71             "frame-size": "",
72             "test-type": ""
73         }
74
75     @property
76     def html_layout(self):
77         return self._html_layout
78
79     @property
80     def spec_test(self):
81         return self._spec_test
82
83     def _reset_test_selection(self):
84         self._test_selection = {
85             "phy": "",
86             "area": "",
87             "test": "",
88             "core": "",
89             "frame-size": "",
90             "test-type": ""
91         }
92
93     def add_content(self):
94         """
95         """
96         if self._html_layout and self._spec_test:
97             return html.Div(
98                 id="div-main",
99                 children=[
100                     self._add_ctrl_div(),
101                     self._add_plotting_div()
102                 ]
103             )
104         else:
105             return html.Div(
106             id="div-main-error",
107             children="An Error Occured."
108         )
109
110     def _add_ctrl_div(self):
111         """Add div with controls. It is placed on the left side.
112         """
113         return html.Div(
114             id="div-controls",
115             children=[
116                 html.Div(
117                     id="div-controls-tabs",
118                     children=[
119                         self._add_ctrl_select(),
120                         self._add_ctrl_shown()
121                     ]
122                 )
123             ],
124             style={
125                 "display": "inline-block",
126                 "width": "18%",
127                 "padding": "5px"
128             }
129         )
130
131     def _add_plotting_div(self):
132         """Add div with plots and tables. It is placed on the right side.
133         """
134         return html.Div(
135             id="div-plotting-area",
136             children=[
137                 # Only a visible note.
138                 # TODO: Add content.
139                 html.H3(
140                     "Graphs and Tables",
141                     style={
142                         "vertical-align": "middle",
143                         "text-align": "center"
144                     }
145                 )
146             ],
147             style={
148                 "vertical-align": "middle",
149                 "display": "inline-block",
150                 "width": "80%",
151                 "padding": "5px"
152             }
153         )
154
155     def _add_ctrl_shown(self):
156         """
157         """
158         return html.Div(
159             id="div-ctrl-shown",
160             children="List of selected tests"
161         )
162
163     def _add_ctrl_select(self):
164         """
165         """
166         return html.Div(
167             id="div-ctrl-select",
168             children=[
169                 html.Br(),
170                 html.Div(
171                     children="Physical Test Bed Topology, NIC and Driver"
172                 ),
173                 dcc.Dropdown(
174                     id="dd-ctrl-phy",
175                     placeholder="Select a Physical Test Bed Topology...",
176                     multi=False,
177                     clearable=False,
178                     options=[
179                         {"label": k, "value": k} for k in self._spec_test.keys()
180                     ],
181                 ),
182                 html.Br(),
183                 html.Div(
184                     children="Area"
185                 ),
186                 dcc.Dropdown(
187                     id="dd-ctrl-area",
188                     placeholder="Select an Area...",
189                     disabled=True,
190                     multi=False,
191                     clearable=False,
192                 ),
193                 html.Br(),
194                 html.Div(
195                     children="Test"
196                 ),
197                 dcc.Dropdown(
198                     id="dd-ctrl-test",
199                     placeholder="Select a Test...",
200                     disabled=True,
201                     multi=False,
202                     clearable=False,
203                 ),
204
205                 # Change to radio buttons:
206                 html.Br(),
207                 html.Div(
208                     children="Number of Cores"
209                 ),
210                 dcc.Dropdown(
211                     id="dd-ctrl-core",
212                     placeholder="Select a Number of Cores...",
213                     disabled=True,
214                     multi=False,
215                     clearable=False,
216                 ),
217                 html.Br(),
218                 html.Div(
219                     children="Frame Size"
220                 ),
221                 dcc.Dropdown(
222                     id="dd-ctrl-framesize",
223                     placeholder="Select a Frame Size...",
224                     disabled=True,
225                     multi=False,
226                     clearable=False,
227                 ),
228                 html.Br(),
229                 html.Div(
230                     children="Test Type"
231                 ),
232                 dcc.Dropdown(
233                     id="dd-ctrl-testtype",
234                     placeholder="Select a Test Type...",
235                     disabled=True,
236                     multi=False,
237                     clearable=False,
238                 ),
239                             html.Br(),
240                 html.Button(
241                     id="btn-ctrl-add",
242                     children="Add",
243                     disabled=True
244                 ),
245                 html.Br(),
246                 html.Div(
247                     id="div-ctrl-info"
248                 )
249             ]
250         )
251
252     def callbacks(self, app):
253
254         @app.callback(
255             Output("dd-ctrl-area", "options"),
256             Output("dd-ctrl-area", "disabled"),
257             Input("dd-ctrl-phy", "value"),
258         )
259         def _update_dd_area(phy):
260             """
261             """
262
263             if phy is None:
264                 raise PreventUpdate
265
266             try:
267                 options = [
268                     {"label": self._spec_test[phy][v]["label"], "value": v}
269                         for v in [v for v in self._spec_test[phy].keys()]
270                 ]
271             except KeyError:
272                 options = list()
273
274             return options, False
275
276         @app.callback(
277             Output("dd-ctrl-test", "options"),
278             Output("dd-ctrl-test", "disabled"),
279             Input("dd-ctrl-phy", "value"),
280             Input("dd-ctrl-area", "value"),
281         )
282         def _update_dd_test(phy, area):
283             """
284             """
285
286             if not all((phy, area, )):
287                 raise PreventUpdate
288
289             try:
290                 options = [
291                     {"label": v, "value": v}
292                         for v in self._spec_test[phy][area]["test"]
293                 ]
294             except KeyError:
295                 options = list()
296
297             return options, False
298
299         @app.callback(
300             Output("btn-ctrl-add", "disabled"),
301             Input("dd-ctrl-phy", "value"),
302             Input("dd-ctrl-area", "value"),
303             Input("dd-ctrl-test", "value"),
304         )
305         def _update_btn_add(phy, area, test):
306             """
307             """
308
309             if all((phy, area, test, )):
310                 self._test_selection["phy"] = phy
311                 self._test_selection["area"] = area
312                 self._test_selection["test"] = test
313                 return False
314             else:
315                 return True
316
317         @app.callback(
318             Output("div-ctrl-info", "children"),
319             Output("dd-ctrl-phy", "value"),
320             Output("dd-ctrl-area", "value"),
321             Output("dd-ctrl-test", "value"),
322             Output("btn-ctrl-add", "n_clicks"),
323             Input("btn-ctrl-add", "n_clicks")
324         )
325         def _print_user_selection(n_clicks):
326             """
327             """
328
329             logging.info(f"\n\n{n_clicks}\n\n")
330
331             if not n_clicks:
332                 raise PreventUpdate
333
334             selected = (
335                 f"{self._test_selection['phy']} # "
336                 f"{self._test_selection['area']} # "
337                 f"{self._test_selection['test']} # "
338                 f"{n_clicks}\n"
339             )
340
341             self._reset_test_selection()
342
343             return (
344                 selected,
345                 None,
346                 None,
347                 None,
348                 0,
349             )