Spaces:
Running on Zero
Running on Zero
feat: add pagination to My QR Codes dashboard
Browse files- app.py +186 -6
- hf_dashboard.py +23 -6
- tests-unit/hf_dashboard_test.py +29 -2
app.py
CHANGED
|
@@ -340,6 +340,26 @@ def _coerce_pil_image(image: Any) -> Image.Image:
|
|
| 340 |
return Image.fromarray(np.asarray(image))
|
| 341 |
|
| 342 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 343 |
def _dashboard_empty_payload(message: str):
|
| 344 |
return (
|
| 345 |
message,
|
|
@@ -347,6 +367,10 @@ def _dashboard_empty_payload(message: str):
|
|
| 347 |
[],
|
| 348 |
[],
|
| 349 |
[],
|
|
|
|
|
|
|
|
|
|
|
|
|
| 350 |
None,
|
| 351 |
"Click a row in the table to inspect analytics and reload settings.",
|
| 352 |
"",
|
|
@@ -397,6 +421,10 @@ def _load_my_qr_dashboard(
|
|
| 397 |
[],
|
| 398 |
records,
|
| 399 |
[],
|
|
|
|
|
|
|
|
|
|
|
|
|
| 400 |
None,
|
| 401 |
"No saved QR codes yet. Generate one while signed in, then come back here.",
|
| 402 |
"",
|
|
@@ -410,13 +438,17 @@ def _load_my_qr_dashboard(
|
|
| 410 |
gr.update(visible=False),
|
| 411 |
)
|
| 412 |
|
| 413 |
-
table_rows, row_map,
|
| 414 |
return (
|
| 415 |
str(payload.get("status") or "Loaded."),
|
| 416 |
summary,
|
| 417 |
table_rows,
|
| 418 |
records,
|
| 419 |
row_map,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 420 |
None,
|
| 421 |
"Click a row in the table to inspect a saved QR and reload settings.",
|
| 422 |
"",
|
|
@@ -530,21 +562,81 @@ def _select_my_qr_generation_from_table(
|
|
| 530 |
return _select_my_qr_generation(records, selected_index, request)
|
| 531 |
|
| 532 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 533 |
def _update_my_qr_destination(
|
| 534 |
records: list[dict[str, Any]],
|
|
|
|
| 535 |
selected_index: int | None,
|
| 536 |
new_destination: str,
|
| 537 |
request: Union[gr.Request, None] = None,
|
| 538 |
):
|
| 539 |
if not records or selected_index is None:
|
| 540 |
detail = _select_my_qr_generation(records, None, request)
|
| 541 |
-
table_rows, row_map,
|
|
|
|
|
|
|
|
|
|
| 542 |
return (
|
| 543 |
"Select a saved QR first.",
|
| 544 |
summary,
|
| 545 |
table_rows,
|
| 546 |
records,
|
| 547 |
row_map,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 548 |
*detail,
|
| 549 |
)
|
| 550 |
|
|
@@ -557,30 +649,47 @@ def _update_my_qr_destination(
|
|
| 557 |
)
|
| 558 |
except DashboardError as exc:
|
| 559 |
detail = _select_my_qr_generation(records, selected_index, request)
|
| 560 |
-
table_rows, row_map,
|
|
|
|
|
|
|
|
|
|
| 561 |
return (
|
| 562 |
f"Could not update QR destination: {exc}",
|
| 563 |
summary,
|
| 564 |
table_rows,
|
| 565 |
records,
|
| 566 |
row_map,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 567 |
*detail,
|
| 568 |
)
|
| 569 |
except Exception as exc:
|
| 570 |
print("[dashboard-update-destination]", traceback.format_exc())
|
| 571 |
detail = _select_my_qr_generation(records, selected_index, request)
|
| 572 |
-
table_rows, row_map,
|
|
|
|
|
|
|
|
|
|
| 573 |
return (
|
| 574 |
f"Could not update QR destination: {_format_exception_message(exc)}",
|
| 575 |
summary,
|
| 576 |
table_rows,
|
| 577 |
records,
|
| 578 |
row_map,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 579 |
*detail,
|
| 580 |
)
|
| 581 |
|
| 582 |
updated_records = result["records"]
|
| 583 |
-
table_rows, row_map,
|
|
|
|
|
|
|
|
|
|
| 584 |
status = "Updated QR destination."
|
| 585 |
short_url = str(result.get("short_url") or "").strip()
|
| 586 |
normalized_url = str(result.get("normalized_url") or "").strip()
|
|
@@ -594,6 +703,10 @@ def _update_my_qr_destination(
|
|
| 594 |
table_rows,
|
| 595 |
updated_records,
|
| 596 |
row_map,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 597 |
*detail,
|
| 598 |
)
|
| 599 |
|
|
@@ -6814,6 +6927,7 @@ with gr.Blocks(delete_cache=(3600, 3600), css=DASHBOARD_CSS) as demo:
|
|
| 6814 |
)
|
| 6815 |
my_qr_records_state = gr.State([])
|
| 6816 |
my_qr_row_map_state = gr.State([])
|
|
|
|
| 6817 |
my_qr_selected_index_state = gr.State(value=None)
|
| 6818 |
my_qr_table_summary = gr.Markdown("No saved QR codes loaded yet.")
|
| 6819 |
my_qr_table = gr.Dataframe(
|
|
@@ -6826,6 +6940,10 @@ with gr.Blocks(delete_cache=(3600, 3600), css=DASHBOARD_CSS) as demo:
|
|
| 6826 |
value=[],
|
| 6827 |
wrap=True,
|
| 6828 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6829 |
with gr.Row():
|
| 6830 |
with gr.Column(scale=1):
|
| 6831 |
my_qr_detail_image = gr.HTML(
|
|
@@ -6892,6 +7010,10 @@ with gr.Blocks(delete_cache=(3600, 3600), css=DASHBOARD_CSS) as demo:
|
|
| 6892 |
my_qr_table,
|
| 6893 |
my_qr_records_state,
|
| 6894 |
my_qr_row_map_state,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6895 |
my_qr_selected_index_state,
|
| 6896 |
my_qr_detail_markdown,
|
| 6897 |
my_qr_detail_image,
|
|
@@ -6915,6 +7037,10 @@ with gr.Blocks(delete_cache=(3600, 3600), css=DASHBOARD_CSS) as demo:
|
|
| 6915 |
my_qr_table,
|
| 6916 |
my_qr_records_state,
|
| 6917 |
my_qr_row_map_state,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6918 |
my_qr_selected_index_state,
|
| 6919 |
my_qr_detail_markdown,
|
| 6920 |
my_qr_detail_image,
|
|
@@ -6947,15 +7073,69 @@ with gr.Blocks(delete_cache=(3600, 3600), css=DASHBOARD_CSS) as demo:
|
|
| 6947 |
],
|
| 6948 |
)
|
| 6949 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6950 |
update_my_qr_destination_btn.click(
|
| 6951 |
fn=_update_my_qr_destination,
|
| 6952 |
-
inputs=[my_qr_records_state, my_qr_selected_index_state, my_qr_new_destination],
|
| 6953 |
outputs=[
|
| 6954 |
dashboard_status,
|
| 6955 |
my_qr_table_summary,
|
| 6956 |
my_qr_table,
|
| 6957 |
my_qr_records_state,
|
| 6958 |
my_qr_row_map_state,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6959 |
my_qr_selected_index_state,
|
| 6960 |
my_qr_detail_markdown,
|
| 6961 |
my_qr_detail_image,
|
|
|
|
| 340 |
return Image.fromarray(np.asarray(image))
|
| 341 |
|
| 342 |
|
| 343 |
+
def _build_dashboard_page_view(
|
| 344 |
+
records: list[dict[str, Any]],
|
| 345 |
+
current_page: int = 1,
|
| 346 |
+
):
|
| 347 |
+
table_rows, row_map, summary, safe_page, total_pages = build_generation_table_rows(
|
| 348 |
+
records,
|
| 349 |
+
page=current_page,
|
| 350 |
+
)
|
| 351 |
+
page_label = f"Page {safe_page} of {total_pages}"
|
| 352 |
+
return (
|
| 353 |
+
summary,
|
| 354 |
+
table_rows,
|
| 355 |
+
row_map,
|
| 356 |
+
safe_page,
|
| 357 |
+
page_label,
|
| 358 |
+
gr.update(interactive=safe_page > 1),
|
| 359 |
+
gr.update(interactive=safe_page < total_pages),
|
| 360 |
+
)
|
| 361 |
+
|
| 362 |
+
|
| 363 |
def _dashboard_empty_payload(message: str):
|
| 364 |
return (
|
| 365 |
message,
|
|
|
|
| 367 |
[],
|
| 368 |
[],
|
| 369 |
[],
|
| 370 |
+
1,
|
| 371 |
+
"",
|
| 372 |
+
gr.update(interactive=False),
|
| 373 |
+
gr.update(interactive=False),
|
| 374 |
None,
|
| 375 |
"Click a row in the table to inspect analytics and reload settings.",
|
| 376 |
"",
|
|
|
|
| 421 |
[],
|
| 422 |
records,
|
| 423 |
[],
|
| 424 |
+
1,
|
| 425 |
+
"",
|
| 426 |
+
gr.update(interactive=False),
|
| 427 |
+
gr.update(interactive=False),
|
| 428 |
None,
|
| 429 |
"No saved QR codes yet. Generate one while signed in, then come back here.",
|
| 430 |
"",
|
|
|
|
| 438 |
gr.update(visible=False),
|
| 439 |
)
|
| 440 |
|
| 441 |
+
summary, table_rows, row_map, safe_page, page_label, prev_update, next_update = _build_dashboard_page_view(records, 1)
|
| 442 |
return (
|
| 443 |
str(payload.get("status") or "Loaded."),
|
| 444 |
summary,
|
| 445 |
table_rows,
|
| 446 |
records,
|
| 447 |
row_map,
|
| 448 |
+
safe_page,
|
| 449 |
+
page_label,
|
| 450 |
+
prev_update,
|
| 451 |
+
next_update,
|
| 452 |
None,
|
| 453 |
"Click a row in the table to inspect a saved QR and reload settings.",
|
| 454 |
"",
|
|
|
|
| 562 |
return _select_my_qr_generation(records, selected_index, request)
|
| 563 |
|
| 564 |
|
| 565 |
+
def _change_my_qr_page(
|
| 566 |
+
records: list[dict[str, Any]],
|
| 567 |
+
current_page: int,
|
| 568 |
+
delta: int,
|
| 569 |
+
):
|
| 570 |
+
if not records:
|
| 571 |
+
return (
|
| 572 |
+
"",
|
| 573 |
+
[],
|
| 574 |
+
[],
|
| 575 |
+
1,
|
| 576 |
+
"",
|
| 577 |
+
gr.update(interactive=False),
|
| 578 |
+
gr.update(interactive=False),
|
| 579 |
+
None,
|
| 580 |
+
"No saved QR codes are loaded yet.",
|
| 581 |
+
"",
|
| 582 |
+
[],
|
| 583 |
+
"",
|
| 584 |
+
"",
|
| 585 |
+
"{}",
|
| 586 |
+
gr.update(visible=False),
|
| 587 |
+
gr.update(visible=False),
|
| 588 |
+
gr.update(visible=False),
|
| 589 |
+
gr.update(visible=False),
|
| 590 |
+
)
|
| 591 |
+
summary, table_rows, row_map, safe_page, page_label, prev_update, next_update = _build_dashboard_page_view(
|
| 592 |
+
records,
|
| 593 |
+
current_page + delta,
|
| 594 |
+
)
|
| 595 |
+
return (
|
| 596 |
+
summary,
|
| 597 |
+
table_rows,
|
| 598 |
+
row_map,
|
| 599 |
+
safe_page,
|
| 600 |
+
page_label,
|
| 601 |
+
prev_update,
|
| 602 |
+
next_update,
|
| 603 |
+
None,
|
| 604 |
+
"Click a row in the table to inspect a saved QR and reload settings.",
|
| 605 |
+
"",
|
| 606 |
+
[],
|
| 607 |
+
"",
|
| 608 |
+
"",
|
| 609 |
+
"{}",
|
| 610 |
+
gr.update(visible=False),
|
| 611 |
+
gr.update(visible=False),
|
| 612 |
+
gr.update(visible=False),
|
| 613 |
+
gr.update(visible=False),
|
| 614 |
+
)
|
| 615 |
+
|
| 616 |
+
|
| 617 |
def _update_my_qr_destination(
|
| 618 |
records: list[dict[str, Any]],
|
| 619 |
+
current_page: int,
|
| 620 |
selected_index: int | None,
|
| 621 |
new_destination: str,
|
| 622 |
request: Union[gr.Request, None] = None,
|
| 623 |
):
|
| 624 |
if not records or selected_index is None:
|
| 625 |
detail = _select_my_qr_generation(records, None, request)
|
| 626 |
+
summary, table_rows, row_map, safe_page, page_label, prev_update, next_update = _build_dashboard_page_view(
|
| 627 |
+
records,
|
| 628 |
+
current_page,
|
| 629 |
+
)
|
| 630 |
return (
|
| 631 |
"Select a saved QR first.",
|
| 632 |
summary,
|
| 633 |
table_rows,
|
| 634 |
records,
|
| 635 |
row_map,
|
| 636 |
+
safe_page,
|
| 637 |
+
page_label,
|
| 638 |
+
prev_update,
|
| 639 |
+
next_update,
|
| 640 |
*detail,
|
| 641 |
)
|
| 642 |
|
|
|
|
| 649 |
)
|
| 650 |
except DashboardError as exc:
|
| 651 |
detail = _select_my_qr_generation(records, selected_index, request)
|
| 652 |
+
summary, table_rows, row_map, safe_page, page_label, prev_update, next_update = _build_dashboard_page_view(
|
| 653 |
+
records,
|
| 654 |
+
current_page,
|
| 655 |
+
)
|
| 656 |
return (
|
| 657 |
f"Could not update QR destination: {exc}",
|
| 658 |
summary,
|
| 659 |
table_rows,
|
| 660 |
records,
|
| 661 |
row_map,
|
| 662 |
+
safe_page,
|
| 663 |
+
page_label,
|
| 664 |
+
prev_update,
|
| 665 |
+
next_update,
|
| 666 |
*detail,
|
| 667 |
)
|
| 668 |
except Exception as exc:
|
| 669 |
print("[dashboard-update-destination]", traceback.format_exc())
|
| 670 |
detail = _select_my_qr_generation(records, selected_index, request)
|
| 671 |
+
summary, table_rows, row_map, safe_page, page_label, prev_update, next_update = _build_dashboard_page_view(
|
| 672 |
+
records,
|
| 673 |
+
current_page,
|
| 674 |
+
)
|
| 675 |
return (
|
| 676 |
f"Could not update QR destination: {_format_exception_message(exc)}",
|
| 677 |
summary,
|
| 678 |
table_rows,
|
| 679 |
records,
|
| 680 |
row_map,
|
| 681 |
+
safe_page,
|
| 682 |
+
page_label,
|
| 683 |
+
prev_update,
|
| 684 |
+
next_update,
|
| 685 |
*detail,
|
| 686 |
)
|
| 687 |
|
| 688 |
updated_records = result["records"]
|
| 689 |
+
summary, table_rows, row_map, safe_page, page_label, prev_update, next_update = _build_dashboard_page_view(
|
| 690 |
+
updated_records,
|
| 691 |
+
current_page,
|
| 692 |
+
)
|
| 693 |
status = "Updated QR destination."
|
| 694 |
short_url = str(result.get("short_url") or "").strip()
|
| 695 |
normalized_url = str(result.get("normalized_url") or "").strip()
|
|
|
|
| 703 |
table_rows,
|
| 704 |
updated_records,
|
| 705 |
row_map,
|
| 706 |
+
safe_page,
|
| 707 |
+
page_label,
|
| 708 |
+
prev_update,
|
| 709 |
+
next_update,
|
| 710 |
*detail,
|
| 711 |
)
|
| 712 |
|
|
|
|
| 6927 |
)
|
| 6928 |
my_qr_records_state = gr.State([])
|
| 6929 |
my_qr_row_map_state = gr.State([])
|
| 6930 |
+
my_qr_page_state = gr.State(value=1)
|
| 6931 |
my_qr_selected_index_state = gr.State(value=None)
|
| 6932 |
my_qr_table_summary = gr.Markdown("No saved QR codes loaded yet.")
|
| 6933 |
my_qr_table = gr.Dataframe(
|
|
|
|
| 6940 |
value=[],
|
| 6941 |
wrap=True,
|
| 6942 |
)
|
| 6943 |
+
with gr.Row():
|
| 6944 |
+
my_qr_prev_page_btn = gr.Button("← Previous", interactive=False)
|
| 6945 |
+
my_qr_page_label = gr.Markdown("")
|
| 6946 |
+
my_qr_next_page_btn = gr.Button("Next →", interactive=False)
|
| 6947 |
with gr.Row():
|
| 6948 |
with gr.Column(scale=1):
|
| 6949 |
my_qr_detail_image = gr.HTML(
|
|
|
|
| 7010 |
my_qr_table,
|
| 7011 |
my_qr_records_state,
|
| 7012 |
my_qr_row_map_state,
|
| 7013 |
+
my_qr_page_state,
|
| 7014 |
+
my_qr_page_label,
|
| 7015 |
+
my_qr_prev_page_btn,
|
| 7016 |
+
my_qr_next_page_btn,
|
| 7017 |
my_qr_selected_index_state,
|
| 7018 |
my_qr_detail_markdown,
|
| 7019 |
my_qr_detail_image,
|
|
|
|
| 7037 |
my_qr_table,
|
| 7038 |
my_qr_records_state,
|
| 7039 |
my_qr_row_map_state,
|
| 7040 |
+
my_qr_page_state,
|
| 7041 |
+
my_qr_page_label,
|
| 7042 |
+
my_qr_prev_page_btn,
|
| 7043 |
+
my_qr_next_page_btn,
|
| 7044 |
my_qr_selected_index_state,
|
| 7045 |
my_qr_detail_markdown,
|
| 7046 |
my_qr_detail_image,
|
|
|
|
| 7073 |
],
|
| 7074 |
)
|
| 7075 |
|
| 7076 |
+
my_qr_prev_page_btn.click(
|
| 7077 |
+
fn=lambda records, current_page: _change_my_qr_page(records, current_page, -1),
|
| 7078 |
+
inputs=[my_qr_records_state, my_qr_page_state],
|
| 7079 |
+
outputs=[
|
| 7080 |
+
my_qr_table_summary,
|
| 7081 |
+
my_qr_table,
|
| 7082 |
+
my_qr_row_map_state,
|
| 7083 |
+
my_qr_page_state,
|
| 7084 |
+
my_qr_page_label,
|
| 7085 |
+
my_qr_prev_page_btn,
|
| 7086 |
+
my_qr_next_page_btn,
|
| 7087 |
+
my_qr_selected_index_state,
|
| 7088 |
+
my_qr_detail_markdown,
|
| 7089 |
+
my_qr_detail_image,
|
| 7090 |
+
my_qr_scan_breakdown,
|
| 7091 |
+
my_qr_current_destination,
|
| 7092 |
+
my_qr_new_destination,
|
| 7093 |
+
my_qr_settings_json,
|
| 7094 |
+
my_qr_settings_accordion,
|
| 7095 |
+
load_standard_from_dashboard_btn,
|
| 7096 |
+
load_artistic_from_dashboard_btn,
|
| 7097 |
+
my_qr_retarget_group,
|
| 7098 |
+
],
|
| 7099 |
+
)
|
| 7100 |
+
|
| 7101 |
+
my_qr_next_page_btn.click(
|
| 7102 |
+
fn=lambda records, current_page: _change_my_qr_page(records, current_page, 1),
|
| 7103 |
+
inputs=[my_qr_records_state, my_qr_page_state],
|
| 7104 |
+
outputs=[
|
| 7105 |
+
my_qr_table_summary,
|
| 7106 |
+
my_qr_table,
|
| 7107 |
+
my_qr_row_map_state,
|
| 7108 |
+
my_qr_page_state,
|
| 7109 |
+
my_qr_page_label,
|
| 7110 |
+
my_qr_prev_page_btn,
|
| 7111 |
+
my_qr_next_page_btn,
|
| 7112 |
+
my_qr_selected_index_state,
|
| 7113 |
+
my_qr_detail_markdown,
|
| 7114 |
+
my_qr_detail_image,
|
| 7115 |
+
my_qr_scan_breakdown,
|
| 7116 |
+
my_qr_current_destination,
|
| 7117 |
+
my_qr_new_destination,
|
| 7118 |
+
my_qr_settings_json,
|
| 7119 |
+
my_qr_settings_accordion,
|
| 7120 |
+
load_standard_from_dashboard_btn,
|
| 7121 |
+
load_artistic_from_dashboard_btn,
|
| 7122 |
+
my_qr_retarget_group,
|
| 7123 |
+
],
|
| 7124 |
+
)
|
| 7125 |
+
|
| 7126 |
update_my_qr_destination_btn.click(
|
| 7127 |
fn=_update_my_qr_destination,
|
| 7128 |
+
inputs=[my_qr_records_state, my_qr_page_state, my_qr_selected_index_state, my_qr_new_destination],
|
| 7129 |
outputs=[
|
| 7130 |
dashboard_status,
|
| 7131 |
my_qr_table_summary,
|
| 7132 |
my_qr_table,
|
| 7133 |
my_qr_records_state,
|
| 7134 |
my_qr_row_map_state,
|
| 7135 |
+
my_qr_page_state,
|
| 7136 |
+
my_qr_page_label,
|
| 7137 |
+
my_qr_prev_page_btn,
|
| 7138 |
+
my_qr_next_page_btn,
|
| 7139 |
my_qr_selected_index_state,
|
| 7140 |
my_qr_detail_markdown,
|
| 7141 |
my_qr_detail_image,
|
hf_dashboard.py
CHANGED
|
@@ -4,6 +4,7 @@ import copy
|
|
| 4 |
import hashlib
|
| 5 |
import io
|
| 6 |
import json
|
|
|
|
| 7 |
import os
|
| 8 |
import re
|
| 9 |
from collections import Counter
|
|
@@ -1012,20 +1013,36 @@ def update_generation_destination(
|
|
| 1012 |
def build_generation_table_rows(
|
| 1013 |
records: list[dict[str, Any]],
|
| 1014 |
*,
|
|
|
|
| 1015 |
limit: int = 10,
|
| 1016 |
-
) -> tuple[list[list[Any]], list[int], str]:
|
| 1017 |
-
|
| 1018 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1019 |
table_rows: list[list[Any]] = []
|
| 1020 |
-
for position, record in enumerate(visible_records, start=1):
|
| 1021 |
created = str(record.get("created_at") or "")[:16].replace("T", " ")
|
| 1022 |
qr_mode = str(record.get("qr_mode") or "qr").title()
|
| 1023 |
destination = str(record.get("destination_url_original") or "—")
|
| 1024 |
short_url = str(record.get("short_url") or "—")
|
| 1025 |
status = str(record.get("status") or "unknown")
|
| 1026 |
table_rows.append([position, created, qr_mode, destination, short_url, status])
|
| 1027 |
-
|
| 1028 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1029 |
|
| 1030 |
|
| 1031 |
def resolve_generation_index_from_table_event(
|
|
|
|
| 4 |
import hashlib
|
| 5 |
import io
|
| 6 |
import json
|
| 7 |
+
import math
|
| 8 |
import os
|
| 9 |
import re
|
| 10 |
from collections import Counter
|
|
|
|
| 1013 |
def build_generation_table_rows(
|
| 1014 |
records: list[dict[str, Any]],
|
| 1015 |
*,
|
| 1016 |
+
page: int = 1,
|
| 1017 |
limit: int = 10,
|
| 1018 |
+
) -> tuple[list[list[Any]], list[int], str, int, int]:
|
| 1019 |
+
safe_limit = max(1, int(limit))
|
| 1020 |
+
total_records = len(records)
|
| 1021 |
+
total_pages = max(1, math.ceil(total_records / safe_limit))
|
| 1022 |
+
safe_page = min(max(1, int(page)), total_pages)
|
| 1023 |
+
start_idx = (safe_page - 1) * safe_limit
|
| 1024 |
+
end_idx = start_idx + safe_limit
|
| 1025 |
+
visible_records = list(records[start_idx:end_idx])
|
| 1026 |
+
row_map = list(range(start_idx, start_idx + len(visible_records)))
|
| 1027 |
table_rows: list[list[Any]] = []
|
| 1028 |
+
for position, record in enumerate(visible_records, start=start_idx + 1):
|
| 1029 |
created = str(record.get("created_at") or "")[:16].replace("T", " ")
|
| 1030 |
qr_mode = str(record.get("qr_mode") or "qr").title()
|
| 1031 |
destination = str(record.get("destination_url_original") or "—")
|
| 1032 |
short_url = str(record.get("short_url") or "—")
|
| 1033 |
status = str(record.get("status") or "unknown")
|
| 1034 |
table_rows.append([position, created, qr_mode, destination, short_url, status])
|
| 1035 |
+
if total_records:
|
| 1036 |
+
shown_start = start_idx + 1
|
| 1037 |
+
shown_end = start_idx + len(visible_records)
|
| 1038 |
+
range_label = f"Showing {shown_start}-{shown_end} of {total_records}"
|
| 1039 |
+
else:
|
| 1040 |
+
range_label = "Showing 0-0 of 0"
|
| 1041 |
+
summary = (
|
| 1042 |
+
f"{range_label} saved QR code(s). "
|
| 1043 |
+
f"Page {safe_page} of {total_pages}. Click a row to inspect it."
|
| 1044 |
+
)
|
| 1045 |
+
return table_rows, row_map, summary, safe_page, total_pages
|
| 1046 |
|
| 1047 |
|
| 1048 |
def resolve_generation_index_from_table_event(
|
tests-unit/hf_dashboard_test.py
CHANGED
|
@@ -466,14 +466,41 @@ def test_build_generation_table_rows_limits_to_10_and_returns_row_map() -> None:
|
|
| 466 |
}
|
| 467 |
)
|
| 468 |
|
| 469 |
-
table_rows, row_map, summary = hf_dashboard.build_generation_table_rows(rows)
|
| 470 |
|
| 471 |
assert len(table_rows) == 10
|
| 472 |
assert len(row_map) == 10
|
| 473 |
assert row_map == list(range(10))
|
| 474 |
assert table_rows[0][0] == 1
|
| 475 |
assert table_rows[0][2] in {"Standard", "Artistic"}
|
| 476 |
-
assert "Showing 10 of 12" in summary
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 477 |
|
| 478 |
|
| 479 |
def test_resolve_generation_index_from_table_event_handles_tuple_index() -> None:
|
|
|
|
| 466 |
}
|
| 467 |
)
|
| 468 |
|
| 469 |
+
table_rows, row_map, summary, page, total_pages = hf_dashboard.build_generation_table_rows(rows)
|
| 470 |
|
| 471 |
assert len(table_rows) == 10
|
| 472 |
assert len(row_map) == 10
|
| 473 |
assert row_map == list(range(10))
|
| 474 |
assert table_rows[0][0] == 1
|
| 475 |
assert table_rows[0][2] in {"Standard", "Artistic"}
|
| 476 |
+
assert "Showing 1-10 of 12" in summary
|
| 477 |
+
assert page == 1
|
| 478 |
+
assert total_pages == 2
|
| 479 |
+
|
| 480 |
+
|
| 481 |
+
def test_build_generation_table_rows_returns_second_page_slice() -> None:
|
| 482 |
+
rows = []
|
| 483 |
+
for idx in range(12):
|
| 484 |
+
rows.append(
|
| 485 |
+
{
|
| 486 |
+
"id": f"gen-{idx}",
|
| 487 |
+
"created_at": f"2026-05-31T1{idx % 10}:00:00+00:00",
|
| 488 |
+
"qr_mode": "standard",
|
| 489 |
+
"destination_url_original": f"https://example.com/{idx}",
|
| 490 |
+
"short_url": f"https://qrcut.co/{idx}",
|
| 491 |
+
"status": "completed",
|
| 492 |
+
}
|
| 493 |
+
)
|
| 494 |
+
|
| 495 |
+
table_rows, row_map, summary, page, total_pages = hf_dashboard.build_generation_table_rows(rows, page=2)
|
| 496 |
+
|
| 497 |
+
assert len(table_rows) == 2
|
| 498 |
+
assert row_map == [10, 11]
|
| 499 |
+
assert table_rows[0][0] == 11
|
| 500 |
+
assert table_rows[1][0] == 12
|
| 501 |
+
assert "Showing 11-12 of 12" in summary
|
| 502 |
+
assert page == 2
|
| 503 |
+
assert total_pages == 2
|
| 504 |
|
| 505 |
|
| 506 |
def test_resolve_generation_index_from_table_event_handles_tuple_index() -> None:
|