Skip to content

Commit 00c8c47

Browse files
authored
Reset all when a new file is uploaded (#46)
* clear all when a file is uploaded again * add spinner for uploading larger files
1 parent e522fa1 commit 00c8c47

File tree

3 files changed

+122
-14
lines changed

3 files changed

+122
-14
lines changed

app/callbacks.py

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414
import plotly.graph_objects as go
1515
from config import GM_FILTER_DROPDOWN_BGC_CLASS_OPTIONS
1616
from config import GM_FILTER_DROPDOWN_MENU_OPTIONS
17+
from config import GM_RESULTS_TABLE_CHECKL_OPTIONAL_COLUMNS
1718
from config import GM_RESULTS_TABLE_MANDATORY_COLUMNS
1819
from config import GM_RESULTS_TABLE_OPTIONAL_COLUMNS
1920
from config import MG_FILTER_DROPDOWN_MENU_OPTIONS
21+
from config import MG_RESULTS_TABLE_CHECKL_OPTIONAL_COLUMNS
2022
from config import MG_RESULTS_TABLE_MANDATORY_COLUMNS
2123
from config import MG_RESULTS_TABLE_OPTIONAL_COLUMNS
2224
from config import SCORING_DROPDOWN_MENU_OPTIONS
@@ -58,9 +60,13 @@
5860
# ------------------ Upload and Process Data ------------------ #
5961
@du.callback(
6062
id="dash-uploader",
61-
output=[Output("dash-uploader-output", "children"), Output("file-store", "data")],
63+
output=[
64+
Output("dash-uploader-output", "children"),
65+
Output("file-store", "data"),
66+
Output("loading-spinner-container", "children", allow_duplicate=True),
67+
],
6268
)
63-
def upload_data(status: du.UploadStatus) -> tuple[str, str | None]:
69+
def upload_data(status: du.UploadStatus) -> tuple[str, str | None, None]:
6470
"""Handle file upload and validate pickle files.
6571
6672
Args:
@@ -77,22 +83,26 @@ def upload_data(status: du.UploadStatus) -> tuple[str, str | None]:
7783
return (
7884
f"Successfully uploaded: {os.path.basename(latest_file)} [{round(status.uploaded_size_mb, 2)} MB]",
7985
str(latest_file),
86+
None,
8087
)
8188
except (pickle.UnpicklingError, EOFError, AttributeError):
82-
return f"Error: {os.path.basename(latest_file)} is not a valid pickle file.", None
89+
return f"Error: {os.path.basename(latest_file)} is not a valid pickle file.", None, None
8390
except Exception as e:
8491
# Handle any other unexpected errors
85-
return f"Error uploading file: {str(e)}", None
86-
return "No file uploaded", None
92+
return f"Error uploading file: {str(e)}", None, None
93+
return "No file uploaded", None, None
8794

8895

8996
@app.callback(
9097
Output("processed-data-store", "data"),
9198
Output("processed-links-store", "data"),
99+
Output("loading-spinner-container", "children", allow_duplicate=True),
92100
Input("file-store", "data"),
93101
prevent_initial_call=True,
94102
)
95-
def process_uploaded_data(file_path: Path | str | None) -> tuple[str | None, str | None]:
103+
def process_uploaded_data(
104+
file_path: Path | str | None,
105+
) -> tuple[str | None, str | None, str | None]:
96106
"""Process the uploaded pickle file and store the processed data.
97107
98108
Args:
@@ -102,7 +112,7 @@ def process_uploaded_data(file_path: Path | str | None) -> tuple[str | None, str
102112
JSON string of processed data or None if processing fails.
103113
"""
104114
if file_path is None:
105-
return None, None
115+
return None, None, None
106116

107117
try:
108118
with open(file_path, "rb") as f:
@@ -230,10 +240,10 @@ def process_mg_link(mf, gcf, methods_data):
230240
else:
231241
processed_links = {}
232242

233-
return json.dumps(processed_data), json.dumps(processed_links)
243+
return json.dumps(processed_data), json.dumps(processed_links), None
234244
except Exception as e:
235245
print(f"Error processing file: {str(e)}")
236-
return None, None
246+
return None, None, None
237247

238248

239249
@app.callback(
@@ -249,6 +259,11 @@ def process_mg_link(mf, gcf, methods_data):
249259
Output("gm-scoring-blocks-id", "data", allow_duplicate=True),
250260
Output("gm-scoring-blocks-container", "children", allow_duplicate=True),
251261
Output("gm-results-button", "disabled"),
262+
Output("gm-table", "selected_rows", allow_duplicate=True),
263+
Output("gm-table-select-all-checkbox", "value", allow_duplicate=True),
264+
Output("gm-filter-accordion-component", "value", allow_duplicate=True),
265+
Output("gm-scoring-accordion-component", "value", allow_duplicate=True),
266+
Output("gm-results-table-column-toggle", "value", allow_duplicate=True),
252267
# MG tab outputs
253268
Output("mg-tab", "disabled"),
254269
Output("mg-filter-accordion-control", "disabled"),
@@ -260,6 +275,11 @@ def process_mg_link(mf, gcf, methods_data):
260275
Output("mg-scoring-blocks-id", "data", allow_duplicate=True),
261276
Output("mg-scoring-blocks-container", "children", allow_duplicate=True),
262277
Output("mg-results-button", "disabled"),
278+
Output("mg-table", "selected_rows", allow_duplicate=True),
279+
Output("mg-table-select-all-checkbox", "value", allow_duplicate=True),
280+
Output("mg-filter-accordion-component", "value", allow_duplicate=True),
281+
Output("mg-scoring-accordion-component", "value", allow_duplicate=True),
282+
Output("mg-results-table-column-toggle", "value", allow_duplicate=True),
263283
],
264284
[Input("file-store", "data")],
265285
prevent_initial_call=True,
@@ -275,6 +295,16 @@ def disable_tabs_and_reset_blocks(
275295
Returns:
276296
Tuple containing boolean values for disabling tabs, styles, and new block data.
277297
"""
298+
default_gm_column_value = (
299+
[GM_RESULTS_TABLE_CHECKL_OPTIONAL_COLUMNS[0]]
300+
if GM_RESULTS_TABLE_CHECKL_OPTIONAL_COLUMNS
301+
else []
302+
)
303+
default_mg_column_value = (
304+
[MG_RESULTS_TABLE_CHECKL_OPTIONAL_COLUMNS[0]]
305+
if MG_RESULTS_TABLE_CHECKL_OPTIONAL_COLUMNS
306+
else []
307+
)
278308
if file_path is None:
279309
# Disable all tabs and controls when no file is uploaded
280310
return (
@@ -289,6 +319,11 @@ def disable_tabs_and_reset_blocks(
289319
[],
290320
[],
291321
True,
322+
[],
323+
[],
324+
[],
325+
[],
326+
default_gm_column_value,
292327
# MG tab - disabled
293328
True,
294329
True,
@@ -300,6 +335,11 @@ def disable_tabs_and_reset_blocks(
300335
[],
301336
[],
302337
True,
338+
[],
339+
[],
340+
[],
341+
[],
342+
default_mg_column_value,
303343
)
304344

305345
# Enable the tabs and reset blocks
@@ -327,6 +367,11 @@ def disable_tabs_and_reset_blocks(
327367
gm_scoring_initial_block_id,
328368
gm_scoring_new_blocks,
329369
False,
370+
[],
371+
[],
372+
[],
373+
[],
374+
default_gm_column_value,
330375
# MG tab - enabled with initial blocks
331376
False,
332377
False,
@@ -338,6 +383,11 @@ def disable_tabs_and_reset_blocks(
338383
mg_scoring_initial_block_id,
339384
mg_scoring_new_blocks,
340385
False,
386+
[],
387+
[],
388+
[],
389+
[],
390+
default_mg_column_value,
341391
)
342392

343393

app/layouts.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,9 @@ def create_filter_accordion(
216216
value=f"{control_id.split('-')[0]}-filter-accordion",
217217
),
218218
],
219+
value=[],
219220
className="mt-5 mb-3",
221+
id=f"{control_id.split('-')[0]}-filter-accordion-component",
220222
)
221223

222224

@@ -258,7 +260,9 @@ def create_scoring_accordion(control_id, blocks_store_id, blocks_container_id):
258260
value=f"{control_id.split('-')[0]}-scoring-accordion",
259261
),
260262
],
263+
value=[],
261264
className="mt-5 mb-3",
265+
id=f"{control_id.split('-')[0]}-scoring-accordion-component",
262266
)
263267

264268

@@ -577,6 +581,17 @@ def create_tab_content(prefix, filter_title, checkl_options, no_sort_columns):
577581
className="p-5 ml-5 mr-5",
578582
)
579583

584+
loading_spinner = dbc.Spinner(
585+
html.Div(id="loading-spinner-container"),
586+
color="primary",
587+
size="lg",
588+
type="border",
589+
fullscreen=True,
590+
fullscreen_style={
591+
"backgroundColor": "rgba(0, 0, 0, 0.3)",
592+
"zIndex": "9999",
593+
},
594+
)
580595

581596
# ------------------ Tab Content Configuration ------------------ #
582597
# No-sort columns definitions
@@ -640,5 +655,5 @@ def create_tab_content(prefix, filter_title, checkl_options, no_sort_columns):
640655
# ------------------ Layout Function ------------------ #
641656
def create_layout(): # noqa: D103
642657
return dmc.MantineProvider(
643-
[dbc.Container([navbar, uploader, tabs], fluid=True, className="p-0")]
658+
[dbc.Container([navbar, uploader, loading_spinner, tabs], fluid=True, className="p-0")]
644659
)

tests/test_callbacks.py

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
from app.callbacks import process_uploaded_data
2525
from app.callbacks import scoring_apply
2626
from app.callbacks import upload_data
27+
from app.config import GM_RESULTS_TABLE_CHECKL_OPTIONAL_COLUMNS
28+
from app.config import MG_RESULTS_TABLE_CHECKL_OPTIONAL_COLUMNS
2729
from . import DATA_DIR
2830

2931

@@ -95,7 +97,7 @@ def test_upload_data():
9597
status = UploadStatus(
9698
uploaded_files=[MOCK_FILE_PATH], n_total=1, uploaded_size_mb=5.39, total_size_mb=5.39
9799
)
98-
upload_string, path_string = upload_data(status)
100+
upload_string, path_string, _ = upload_data(status)
99101

100102
# Check the result
101103
assert upload_string == f"Successfully uploaded: {MOCK_FILE_PATH.name} [5.39 MB]"
@@ -104,14 +106,14 @@ def test_upload_data():
104106

105107
@pytest.mark.parametrize("input_path", [None, Path("non_existent_file.pkl")])
106108
def test_process_uploaded_data_invalid_input(input_path):
107-
processed_data, processed_links = process_uploaded_data(input_path)
109+
processed_data, processed_links, _ = process_uploaded_data(input_path)
108110
assert processed_data is None
109111
assert processed_links is None
110112

111113

112114
def test_process_uploaded_data_structure():
113-
processed_data, processed_links = process_uploaded_data(MOCK_FILE_PATH)
114-
processed_data_no_links, processed_links_no_links = process_uploaded_data(
115+
processed_data, processed_links, _ = process_uploaded_data(MOCK_FILE_PATH)
116+
processed_data_no_links, processed_links_no_links, _ = process_uploaded_data(
115117
MOCK_FILE_PATH_NO_LINKS
116118
)
117119

@@ -241,6 +243,17 @@ def test_process_uploaded_data_structure():
241243

242244

243245
def test_disable_tabs(mock_uuid):
246+
default_gm_column_value = (
247+
[GM_RESULTS_TABLE_CHECKL_OPTIONAL_COLUMNS[0]]
248+
if GM_RESULTS_TABLE_CHECKL_OPTIONAL_COLUMNS
249+
else []
250+
)
251+
default_mg_column_value = (
252+
[MG_RESULTS_TABLE_CHECKL_OPTIONAL_COLUMNS[0]]
253+
if MG_RESULTS_TABLE_CHECKL_OPTIONAL_COLUMNS
254+
else []
255+
)
256+
244257
# Test with None as input
245258
result = disable_tabs_and_reset_blocks(None)
246259
assert result == (
@@ -255,6 +268,11 @@ def test_disable_tabs(mock_uuid):
255268
[],
256269
[],
257270
True,
271+
[],
272+
[],
273+
[],
274+
[],
275+
default_gm_column_value,
258276
# MG tab - disabled
259277
True,
260278
True,
@@ -266,6 +284,11 @@ def test_disable_tabs(mock_uuid):
266284
[],
267285
[],
268286
True,
287+
[],
288+
[],
289+
[],
290+
[],
291+
default_mg_column_value,
269292
)
270293

271294
# Test with a string as input
@@ -284,6 +307,11 @@ def test_disable_tabs(mock_uuid):
284307
gm_scoring_block_ids,
285308
gm_scoring_blocks,
286309
gm_results_disabled,
310+
gm_table_selected_rows,
311+
gm_table_checkbox_value,
312+
gm_filter_accordion_value,
313+
gm_scoring_accordion_value,
314+
gm_results_table_column_toggle,
287315
# MG tab outputs
288316
mg_tab_disabled,
289317
mg_filter_accordion_disabled,
@@ -295,6 +323,11 @@ def test_disable_tabs(mock_uuid):
295323
mg_scoring_block_ids,
296324
mg_scoring_blocks,
297325
mg_results_disabled,
326+
mg_table_selected_rows,
327+
mg_table_checkbox_value,
328+
mg_filter_accordion_value,
329+
mg_scoring_accordion_value,
330+
mg_results_table_column_toggle,
298331
) = result
299332

300333
# Assert GM tab outputs
@@ -310,6 +343,11 @@ def test_disable_tabs(mock_uuid):
310343
assert len(gm_scoring_blocks) == 1
311344
assert isinstance(gm_scoring_blocks[0], dmc.Grid)
312345
assert gm_results_disabled is False
346+
assert gm_table_selected_rows == []
347+
assert gm_table_checkbox_value == []
348+
assert gm_filter_accordion_value == []
349+
assert gm_scoring_accordion_value == []
350+
assert gm_results_table_column_toggle == default_gm_column_value
313351

314352
# Assert MG tab outputs
315353
assert mg_tab_disabled is False
@@ -324,6 +362,11 @@ def test_disable_tabs(mock_uuid):
324362
assert len(mg_scoring_blocks) == 1
325363
assert isinstance(mg_scoring_blocks[0], dmc.Grid)
326364
assert mg_results_disabled is False
365+
assert mg_table_selected_rows == []
366+
assert mg_table_checkbox_value == []
367+
assert mg_filter_accordion_value == []
368+
assert mg_scoring_accordion_value == []
369+
assert mg_results_table_column_toggle == default_mg_column_value
327370

328371

329372
def test_scoring_apply_metcalf_raw():

0 commit comments

Comments
 (0)