Skip to content

feat: Add pagination buttons (prev/next) to anywidget mode for DataFrames #1841

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from

Conversation

shuoweil
Copy link
Contributor

Adding interactive pagination buttons controls (Previous/Next buttons) to the anywidget mode for BigFrames DataFrames, enabling users to navigate through large datasets page by page in Jupyter notebooks.

b/391599608

@shuoweil shuoweil requested a review from tswast June 23, 2025 23:31
@shuoweil shuoweil self-assigned this Jun 23, 2025
@shuoweil shuoweil requested review from a team as code owners June 23, 2025 23:31
Copy link

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

@product-auto-label product-auto-label bot added size: l Pull request size is large. api: bigquery Issues related to the googleapis/python-bigquery-dataframes API. labels Jun 23, 2025
@shuoweil shuoweil force-pushed the shuowei-anywidget-button branch from 080c4cb to 8125614 Compare June 24, 2025 22:22
@shuoweil shuoweil force-pushed the shuowei-anywidget-button branch from 8125614 to 0f871a9 Compare June 25, 2025 00:28
@shuoweil shuoweil requested a review from tswast June 25, 2025 03:03
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So cool! Thanks for adding.

Could you also add a formatter / linter to our pre-commit (https://github.com/googleapis/python-bigquery-dataframes/blob/main/.pre-commit-config.yaml)

https://github.com/biomejs/pre-commit?tab=readme-ov-file#using-biomes-pre-commit-hooks seems a good option.

repos:
  - repo: https://github.com/biomejs/pre-commit
    rev: v2.0.2 # Use the sha / tag you want to point at
    hooks:
      - id: biome-check

It's the first listed under "web" at https://pre-commit.com/hooks.html#featured-hooks

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once you add it, you can run the pre-commit manually:

pre-commit run --files bigframes/display/table_widget.js

Comment on lines +20 to +25


def test_repr_anywidget_initial_state(
penguins_df_default_index: bf.dataframe.DataFrame,
):
pytest.importorskip("anywidget")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Since all tests in this file require anywidget, let's move the importorskip to the module level.

I think this should work.

Suggested change
def test_repr_anywidget_initial_state(
penguins_df_default_index: bf.dataframe.DataFrame,
):
pytest.importorskip("anywidget")
pytest.importorskip("anywidget")
def test_repr_anywidget_initial_state(
penguins_df_default_index: bf.dataframe.DataFrame,
):

Same for the other functions in this file.


# Simulate next page click
widget.page = 1
assert widget.page == 1
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we tell if the data displayed has changed? Can we check the table HTML that it doesn't contain the first page of rows and that it does contain the second page?

Rather than the penguin data, it might be better to define our own DataFrame in this test and set a really small page size so we can make sure we visualize the correct rows. See: https://testing.googleblog.com/2018/02/testing-on-toilet-cleanly-create-test.html

tests should never rely on default values that are specified by a helper method since that forces readers to read the helper method’s implementation details in order to understand the test.

# Test going beyond last page
total_pages = math.ceil(widget.row_count / widget.page_size)
widget.page = total_pages + 1
# Should be clamped to last valid page
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing an assertion here?

# Should stay at 0 (handled by frontend)

# Test going beyond last page
total_pages = math.ceil(widget.row_count / widget.page_size)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit too much math in a test.

https://testing.googleblog.com/2014/07/testing-on-toilet-dont-put-logic-in.html

Like my comment above, I recommend defining a DataFrame inside the test so that we can infer the number of pages just by looking at it rather than duplicating logic.


# Test going below page 0
widget.page = -1
# Should stay at 0 (handled by frontend)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do some assertion here? Maybe check that the table HTML returns the first page?

In general, we shouldn't trust the frontend to do the only validation.

widget = TableWidget(penguins_df_default_index)

assert widget.page_size == 5
total_pages = math.ceil(widget.row_count / 5)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. Too much math. Let's define a DataFrame inside this test.

assert widget.row_count > 0

# Calculate expected pages
total_pages = math.ceil(widget.row_count / widget.page_size)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto re: logic in tests.

assert widget.page == page


def test_repr_anywidget_pagination_buttons_functionality(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: How is this test different from test_repr_anywidget_pagination_navigation? Please make the different purpose clearer in the function name.

Comment on lines +31 to +34
@property
def _esm(self):
"""Load JavaScript code from external file."""
return resources.read_text(bigframes.display, "table_widget.js")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to cache this so that we don't have to read the file every time.

Suggested change
@property
def _esm(self):
"""Load JavaScript code from external file."""
return resources.read_text(bigframes.display, "table_widget.js")
@functools.cached_property
def _esm(self):
"""Load JavaScript code from external file."""
return resources.read_text(bigframes.display, "table_widget.js")

https://docs.python.org/3/library/functools.html#functools.cached_property

@shuoweil shuoweil added the kokoro:force-run Add this label to force Kokoro to re-run the tests. label Jun 25, 2025
@bigframes-bot bigframes-bot removed the kokoro:force-run Add this label to force Kokoro to re-run the tests. label Jun 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api: bigquery Issues related to the googleapis/python-bigquery-dataframes API. size: l Pull request size is large.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants