diff --git a/mypy.ini b/mypy.ini --- a/mypy.ini +++ b/mypy.ini @@ -23,5 +23,8 @@ [mypy-dash_html_components.*] ignore_missing_imports = True +[mypy-dash_bootstrap_components.*] +ignore_missing_imports = True + [mypy-plotly.*] -ignore_missing_imports = True \ No newline at end of file +ignore_missing_imports = True diff --git a/requirements.txt b/requirements.txt --- a/requirements.txt +++ b/requirements.txt @@ -9,4 +9,5 @@ pandas numpy dash +dash_bootstrap_components dulwich diff --git a/swh/scanner/dashboard.py b/swh/scanner/dashboard.py --- a/swh/scanner/dashboard.py +++ b/swh/scanner/dashboard.py @@ -3,22 +3,95 @@ # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information +from pathlib import PosixPath +from typing import List + from .model import Tree import plotly.graph_objects as go import dash import dash_core_components as dcc import dash_html_components as html +import dash_bootstrap_components as dbc +from dash.dependencies import Input, Output + + +def generate_data_table(dir_path: PosixPath, source: Tree, table_header: List): + """ + Generate the data_table from the path taken from the chart. + + For each file builds the html table rows showing the known status, a local link to + the file and the relative Software Heritage Persistent Identifier. + + """ + data = [] + for file_info in source.getFilesFromDir(dir_path): + for file_path, attr in file_info.items(): + file_path = PosixPath(file_path) + file_name = file_path.parts[len(file_path.parts) - 1] + data.append( + html.Tr( + [ + html.Td(str(attr["known"]).upper()), + html.Td( + html.A(file_name, href="file://" + str(file_path.resolve())) + ), + html.Td(attr["swhid"]), + ] + ) + ) + + return table_header + [html.Tbody(data)] def run_app(graph_obj: go, source: Tree): - app = dash.Dash(__name__) + external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"] + app = dash.Dash(__name__, external_stylesheets=external_stylesheets) fig = go.Figure().add_trace(graph_obj) fig.update_layout(height=800,) + table_header = [ + html.Thead(html.Tr([html.Th("KNOWN"), html.Th("FILE NAME"), html.Th("SWHID")])) + ] + app.layout = html.Div( - [html.Div([html.Div([dcc.Graph(id="sunburst_chart", figure=fig),]),]),] + [ + html.Div( + [ + html.Div( + [dcc.Graph(id="sunburst_chart", figure=fig),], + className="six columns", + ), + html.Div( + [html.H3(id="directory_title"), dbc.Table(id="files_table")], + className="six columns", + ), + ], + className="row", + ), + ] ) - app.run_server(debug=True, use_reloader=False) + @app.callback( + [Output("files_table", "children"), Output("directory_title", "children")], + [Input("sunburst_chart", "clickData")], + ) + def update_files_table(click_data): + """ + Callback that takes the input (directory path) from the chart and + update the `files_table` children with the relative files. + + """ + if click_data is not None: + raw_path = click_data["points"][0]["label"] + full_path = ( + source.path.joinpath(raw_path) + if raw_path != str(source.path) + else PosixPath(raw_path) + ) + return generate_data_table(full_path, source, table_header), str(full_path) + else: + return "", "" + + app.run_server(debug=True, use_reloader=True)