diff --git a/dashboard/app.py b/dashboard/app.py index 1fcc57f..7831ed4 100644 --- a/dashboard/app.py +++ b/dashboard/app.py @@ -1,6 +1,7 @@ import dash from dash import Dash, html, dcc from .pages import home, config, stage, schema, table +from .datalake import stages external_scripts = [ {'src': 'https://cdn.tailwindcss.com'} @@ -36,8 +37,9 @@ dash.register_page( dash.register_page( table.__name__, path_template='/stg//schm//table/', - layout=table.layout + layout=table.layout_factory(stages) ) +table.callback_factory(app) app.layout = html.Div([ html.Div([ diff --git a/dashboard/pages/table.py b/dashboard/pages/table.py index 782f5ad..ecc8d3d 100644 --- a/dashboard/pages/table.py +++ b/dashboard/pages/table.py @@ -1,126 +1,128 @@ -from dash import html, dcc, dash_table, callback, Input, Output, State +from dash import html, dcc, dash_table, Input, Output, State from dash.exceptions import PreventUpdate -from ..datalake import stages from ..libs.stage.stage import AbstractStage +def layout_factory(stages: list[AbstractStage]): + def layout(stage_name=None, schema_name=None, table_name=None): + stage = stages[stage_name] + df = stage.read(table=table_name, schema=schema_name) + return html.Div([ + dcc.Store(id="table_backup"), + html.Div([ + html.H2([ + dcc.Link( + f"{stage.name}", + href=f"/stage/{stage.name}", + className="hover:underline" + ), + html.Span(" > "), + dcc.Link( + f"{schema_name}", + href=f"/stg/{stage.name}/schema/{schema_name}", + className="hover:underline" + ), + html.Span(" > "), + html.Span(table_name), + ], + className="text-2xl" + ), + html.Div([ + html.Button( + "Editer", + id="btn_edit", + className="rounded border px-2 py-1", + style={"display": "block"} + ), + html.Button( + "Sauver", + id="btn_save", + className="rounded border px-2 py-1 border-green-500 hover:bg-green-500", + style={"display": "none"} + ), + html.Button( + "Annuler", + id="btn_cancel", + className="rounded border px-2 py-1 border-red-500 hover:bg-red-500", + style={"display": "none"} + ), + ], + className="flex flex-row space-x-2", + id="toolbar" + ), + ], + className="flex flex-row justify-between p-4" + ), + html.Div([ + html.Div([ + dash_table.DataTable( + id="datatable", + data=df.to_dict('records'), + columns=[{"name": i, "id": i} for i in df.columns], + filter_action="native", + sort_action="native", + sort_mode="multi", + editable=False + ) + ]) + ], + className="overflow-y-auto" + ), + ], + className="p-2" + ) + return layout -def layout(stage_name=None, schema_name=None, table_name=None): - stage = stages[stage_name] - df = stage.read(table=table_name, schema=schema_name) - return html.Div([ - dcc.Store(id="table_backup"), - html.Div([ - html.H2([ - dcc.Link( - f"{stage.name}", - href=f"/stage/{stage.name}", - className="hover:underline" - ), - html.Span(" > "), - dcc.Link( - f"{schema_name}", - href=f"/stg/{stage.name}/schema/{schema_name}", - className="hover:underline" - ), - html.Span(" > "), - html.Span(table_name), - ], - className="text-2xl" - ), - html.Div([ - html.Button( - "Editer", - id="btn_edit", - className="rounded border px-2 py-1", - style={"display": "block"} - ), - html.Button( - "Sauver", - id="btn_save", - className="rounded border px-2 py-1 border-green-500 hover:bg-green-500", - style={"display": "none"} - ), - html.Button( - "Annuler", - id="btn_cancel", - className="rounded border px-2 py-1 border-red-500 hover:bg-red-500", - style={"display": "none"} - ), - ], - className="flex flex-row space-x-2", - id="toolbar" - ), - ], - className="flex flex-row justify-between p-4" - ), - html.Div([ - html.Div([ - dash_table.DataTable( - id="datatable", - data=df.to_dict('records'), - columns=[{"name": i, "id": i} for i in df.columns], - filter_action="native", - sort_action="native", - sort_mode="multi", - editable=False - ) - ]) - ], - className="overflow-y-auto" - ), - ], - className="p-2" + +def callback_factory(app): + @app.callback( + Output("datatable", 'editable', allow_duplicate=True), + Output("table_backup", 'data'), + Input("btn_edit", "n_clicks"), + State("datatable", 'data'), + prevent_initial_call=True ) - - -@callback( - Output("datatable", 'editable', allow_duplicate=True), - Output("table_backup", 'data'), - Input("btn_edit", "n_clicks"), - State("datatable", 'data'), - prevent_initial_call=True -) -def activate_editable(n_clicks, df_src): - if n_clicks is None: + def activate_editable(n_clicks, df_src): + if n_clicks is None: + raise PreventUpdate + if n_clicks > 0: + df_backup = df_src.copy() + return True, df_backup raise PreventUpdate - if n_clicks > 0: - df_backup = df_src.copy() - return True, df_backup - raise PreventUpdate -@callback( - Output("datatable", 'editable'), - Output("datatable", 'data'), - Input("btn_cancel", "n_clicks"), - State("table_backup", 'data'), - prevent_initial_call=True -) -def cancel_modifications(n_clicks, data): - if n_clicks is None: + @app.callback( + Output("datatable", 'editable', allow_duplicate=True), + Output("datatable", 'data', allow_duplicate=True), + Input("btn_cancel", "n_clicks"), + State("table_backup", 'data'), + prevent_initial_call=True + ) + def cancel_modifications(n_clicks, data): + if n_clicks is None: + raise PreventUpdate + if n_clicks > 0 and data is not None: + return False, data.copy() raise PreventUpdate - if n_clicks > 0 and data is not None: - return False, data.copy() - raise PreventUpdate -# @callback( -# Output("datatable", 'editable'), -# Input("btn_save", "n_clicks"), -# State("datatable", 'editable'), -# ) -# def save_modifications(n_clicks, editable): -# if n_clicks is None: -# raise PreventUpdate -# if n_clicks > 0: -# return not editable -# return editable + @app.callback( + Output("datatable", 'editable'), + Output("datatable", 'data'), + Input("btn_save", "n_clicks"), + State("datatable", 'editable'), + ) + def save_modifications(n_clicks, editable): + if n_clicks is None: + raise PreventUpdate + if n_clicks > 0: + return not editable + return editable -@callback( - Output("btn_edit", "style"), - Output("btn_save", "style"), - Output("btn_cancel", "style"), - Input("datatable", "editable"), -) -def toolbar(editable): - if editable: - return {"display": "none"}, {"display": "block"}, {"display": "block"} - return {"display": "block"}, {"display": "none"}, {"display": "none"} + @app.callback( + Output("btn_edit", "style"), + Output("btn_save", "style"), + Output("btn_cancel", "style"), + Input("datatable", "editable"), + ) + def toolbar(editable): + if editable: + return {"display": "none"}, {"display": "block"}, {"display": "block"} + return {"display": "block"}, {"display": "none"}, {"display": "none"}