Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: Implement single-column sorting for interactive table widget#2255

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
tswast merged 25 commits intomainfromshuowei-anywidget-sort-by-col-name
Nov 26, 2025

Conversation

@shuoweil
Copy link
Contributor

@shuoweilshuoweil commentedNov 11, 2025
edited
Loading

This PR introduces single-column sorting functionality to the interactive table widget.

  1. Three-State Sorting UI

1.1) The sort indicator dot (●) is now hidden by default and only appears when the user hovers the mouse over a column header
1.2) Implemented a sorting cycle: unsorted (●) → ascending (▲) → descending (▼) → unsorted (●).
1.3) Visual indicators (●, ▲, ▼) are displayed in column headers to reflect the current sort state.
1.4) Sorting controls are now only enabled for columns with orderable data types.

  1. Tests
    2.1) Updatedpaginated_pandas_df fixture for better sorting test coverage
    2.2) Added new system tests to verify ascending, descending, and multi-column sorting.

3. Frontend Unit Tests
JavaScript-level unit tests have been added to validate the widget's frontend logic, specifically the new sorting functionality and UI interactions.

How to Run Frontend Unit Tests:
To execute these tests from the project root directory:

cd tests/jsnpm install# Only needed if dependencies haven't been installed or have changednpmtest

Docs has been updated to document the new features. The main description now mentions column sorting and adjustable widths, and a new section has been added to explain how to use the column resizing feature. The sorting section was also updated to mention that the indicators are only visible on hover.

Fixes #<459835971> 🦕

@shuoweilshuoweil self-assigned thisNov 11, 2025
@shuoweilshuoweil requested review froma team ascode ownersNovember 11, 2025 19:19
@review-notebook-app
Copy link

Check out this pull request on ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered byReviewNB

@product-auto-labelproduct-auto-labelbot added size: lPull request size is large. api: bigqueryIssues related to the googleapis/python-bigquery-dataframes API. labelsNov 11, 2025
Comment on lines 229 to 232
exceptKeyError:
logging.warning(
f"Attempted to sort by unknown column:{self.sort_column}"
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why are we catching this exception and dropping it? If we get to this state something bad has happened and the user should know about it.

Copy link
ContributorAuthor

@shuoweilshuoweilNov 12, 2025
edited
Loading

Choose a reason for hiding this comment

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

Here is my plan:
When a KeyError is caught, the current implementation does the following:

  1. Notifies the user: It sets self._error_message to a user-friendly message like "Column '...' not found.". The frontend will then display this error.
  2. Reverts to unsorted: It resets self.sort_column = "".
  3. Displays the unsorted table: The function then continues, but since sort_column is now empty, it fetches and displays the data in its original, unsorted order.

constheaders=tableContainer.querySelectorAll("th");
headers.forEach((header)=>{
constcolumnName=header.textContent.trim();
if(columnName){
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not all data types are sortable. See the orderable property in our dtypes.

We should not add the handler or arrow to columns that we can't sort.

shuoweil reacted with thumbs up emoji
Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Here is the plan:

  1. Display a dot (●) indicator for all sortable columns by default
  2. Allow users to cycle through three states: unsorted (●) → ascending (▲) → descending (▼) → unsorted (●)
  3. Only show sorting UI for columns with orderable data types

@shuoweilshuoweil marked this pull request as draftNovember 14, 2025 19:27
@shuoweilshuoweil marked this pull request as ready for reviewNovember 14, 2025 20:29
@shuoweil
Copy link
ContributorAuthor

@tswast Upon checking, the failed e2e tests
FAILED tests/system/large/functions/test_remote_function.py::test_remote_function_max_instances[set-None]
FAILED tests/system/large/functions/test_remote_function.py::test_remote_function_max_instances[no-set]
are not due to my change.

allow_none=True,
).tag(sync=True)
table_html=traitlets.Unicode().tag(sync=True)
sort_column=traitlets.Unicode("").tag(sync=True)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Have we considered having multiple columns as a possibility? I think a single column is a good starting point, but I think it's an alternative worth considering, especially when a particular column contains lots of duplicate values, like a "date" column.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

I agree that multi-column sorting is particularly valuable when a column has many duplicate values. I would like to get the single column sorting checked in first as a PR. Then check in a second PR for multi-column sorting. This current PR is already complex enough. I prefer two separate PRs as enhancements.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, separate PR makes sense to me, thanks.

shuoweil reacted with thumbs up emoji
self._all_data_loaded=False
self._batch_iter:Optional[Iterator[pd.DataFrame]]=None
self._cached_batches:List[pd.DataFrame]= []
self._last_sort_state:Optional[Tuple[str,bool]]=None
Copy link
Collaborator

Choose a reason for hiding this comment

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

I can guess based on context what these mean, but I think a frozen dataclass would be much easier to understand at a glance.

shuoweil reacted with thumbs up emoji
self.page_size=initial_page_size
self.orderable_columns= [
col
forcolindataframe.columns
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should iterate through dtypes directly to get both the type and the column name in one call.

Calling.dtypes for each iteration is making this into an O(n^2) algorithm. The n could be as large as 10,000, where that would definitely make a difference, though I imagine our widget would break well before then.

Aside: could you file an issue to check our limitations on the number of columns from a ux perspective? I imagine we'll need to solve that at some point soon-ish.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Complexity is reduced based on the suggestion
filed b/462525985 as TODO

df_to_display=df_to_display.sort_values(
by=self.sort_column,ascending=self.sort_ascending
)
exceptKeyError:
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm afraid of this. KeyError is a relatively common error in Python. Just looking at this code, it's not clear to me that this would always be the case of a missing column. Please check for the column presence explicitly, instead.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

It should trigger this error to be honest. The user only allows the click on a sortable column name to trigger this part of code. Theoretically, we do not even need to catch this bug, because this exception should never be hit in our design. I will remove this try catch block.

tswast reacted with thumbs up emoji

// Add click handlers to column headers for sorting
constheaders=tableContainer.querySelectorAll("th");
headers.forEach((header)=>{
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 quite a bit of logic. I would like to get JavaScript-level unit tests setup before merging this PR.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

I've set up a comprehensive JavaScript unit testing environment and implemented tests for theTableWidget's interactive sorting functionality.

Testing Framework & Environment:
*Jest: Chosen as the primary testing framework due to its all-in-one nature (runner, assertion library, mocking) and strong community support.
*JSDOM: Utilized within Jest to simulate a browser environment, enabling DOM manipulation and event handling within Node.js.
*Babel: Configured to transpile modern JavaScript (ES modules) used in the widget and tests, ensuring compatibility.

Test Coverage: Thetable_widget.test.js file now includes unit tests that verify:
* The basic DOM structure generated by the widget.
* The interaction logic for column header clicks, including:
* Initiating ascending sort on the first click.
* Reversing to descending sort on the second click.
* Clearing the sort (returning to unsorted state) on the third click.
* The correct display of sorting indicators (▲ for ascending, ▼ for descending, ● for unsorted).
* Proper interaction with themodel.set andmodel.save_changes methods.

**How to Run**:To execute these tests from the project root directory:```bashcd tests/jsnpm install  # Only needed if dependencies haven't been installed or have changednpm test```

tswast reacted with thumbs up emoji
Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks! Could you also add a workflow for this to our GitHub Actions folder?https://github.com/googleapis/python-bigquery-dataframes/tree/main/.github/workflows

Something like this:

name: js-testson: pushjobs:  build:    runs-on: ubuntu-latest    steps:    - uses: actions/checkout@v2    - name: Install modules      working-directory: ./tests/js      run: npm install    - name: Run tests      working-directory: ./tests/js      run: npm test

shuoweil reacted with thumbs up emoji
@shuoweilshuoweil changed the titlefeat: Implement column sorting for interactive table widgetfeat: Implement single column sorting for interactive table widgetNov 21, 2025
@shuoweilshuoweil changed the titlefeat: Implement single column sorting for interactive table widgetfeat: Implement single-column sorting for interactive table widgetNov 21, 2025
@product-auto-labelproduct-auto-labelbot added size: xlPull request size is extra large. and removed size: lPull request size is large. labelsNov 21, 2025
allow_none=True,
).tag(sync=True)
table_html=traitlets.Unicode().tag(sync=True)
sort_column=traitlets.Unicode("").tag(sync=True)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, separate PR makes sense to me, thanks.

shuoweil reacted with thumbs up emoji
Comment on lines 46 to 52
errorContainer.style.display="none";
errorContainer.style.color="red";
errorContainer.style.padding="8px";
errorContainer.style.marginBottom="8px";
errorContainer.style.border="1px solid red";
errorContainer.style.borderRadius="4px";
errorContainer.style.backgroundColor="#ffebee";
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you move these styles to the CSS file?

shuoweil reacted with thumbs up emoji
Comment on lines 58 to 63
footer.style.display="flex";
footer.style.justifyContent="space-between";
footer.style.alignItems="center";
footer.style.padding="8px";
footer.style.fontFamily=
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same with these.

shuoweil reacted with thumbs up emoji
errorContainer.style.backgroundColor="#ffebee";

consttableContainer=document.createElement("div");
constfooter=document.createElement("div");
Copy link
Collaborator

Choose a reason for hiding this comment

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

Comment on lines 80 to 82
pageSizeLabel.style.marginRight="8px";
pageIndicator.style.margin="0 8px";
rowCountLabel.style.margin="0 8px";
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. I think we can move these to the CSS file.

shuoweil reacted with thumbs up emoji
rowCountLabel.style.margin="0 8px";

// Page size options
constpageSizes=[10,20,50,100,200,500,1000];
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think we want to allow 1000 rows in one page. That'd be a lot to scroll through if you select it by accident. Let's revert this to have[10, 25, 50, 100]

shuoweil reacted with thumbs up emoji

// Add click handlers to column headers for sorting
constheaders=tableContainer.querySelectorAll("th");
headers.forEach((header)=>{
Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks! Could you also add a workflow for this to our GitHub Actions folder?https://github.com/googleapis/python-bigquery-dataframes/tree/main/.github/workflows

Something like this:

name: js-testson: pushjobs:  build:    runs-on: ubuntu-latest    steps:    - uses: actions/checkout@v2    - name: Install modules      working-directory: ./tests/js      run: npm install    - name: Run tests      working-directory: ./tests/js      run: npm test

shuoweil reacted with thumbs up emoji
Copy link
Collaborator

@tswasttswast left a comment

Choose a reason for hiding this comment

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

Thanks! Looking good, but I want to make sure we have tests for some different cases for column labels.

table_html=traitlets.Unicode().tag(sync=True)
sort_column=traitlets.Unicode("").tag(sync=True)
sort_ascending=traitlets.Bool(True).tag(sync=True)
orderable_columns=traitlets.List(traitlets.Unicode(), []).tag(sync=True)
Copy link
Collaborator

Choose a reason for hiding this comment

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

For the general case, column names could be any "Hashable" value, including integers

importbigframes.pandasasbpdimportpandasaspdbpd.options.bigquery.project="swast-scratch"df=bpd.DataFrame([[0,1], [2,3]],columns=pd.Index([1,2]))print(df)print(df[1])print(df[2])

Also, another very important case is the MultiIndex:

df=bpd.DataFrame({'foo': ['one','one','one','two','two','two'],'bar': ['A','B','C','A','B','C'],'baz': [1,2,3,4,5,6],'zoo': ['x','y','z','q','w','t']})pdf=df.pivot(index='foo',columns='bar',values=['baz','zoo'])print(pdf.columns)print(pdf[("baz","A")])

This isn't needed for SQL Cell, but we do need to make sure we function correctly in the general case. Could you test with these types of DataFrames?

If it's not feasible to support these in this PR, please avoid doing the sorting feature for those DataFrames for now and create a bug and a TODO to expand our support for any column label.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

I disable these two cases for now and filed b/463754889

tswast reacted with thumbs up emoji
# Apply sorting if a column is selected
df_to_display=self._dataframe
ifself.sort_column:
df_to_display=df_to_display.sort_values(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could we file a bug and a TODO to support sorting by the index column(s) as well? For those, we'd use sort_index.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

filed b/463715504

tswast reacted with thumbs up emoji
@tswasttswast merged commitd1ecc61 intomainNov 26, 2025
24 of 25 checks passed
@tswasttswast deleted the shuowei-anywidget-sort-by-col-name branchNovember 26, 2025 03:33
@GarrettWuGarrettWu mentioned this pull requestDec 3, 2025
GarrettWu added a commit that referenced this pull requestDec 3, 2025
PR created by the Librarian CLI to initialize a release. Merging this PRwill auto trigger a release.Librarian Version: v0.7.0Language Image:us-central1-docker.pkg.dev/cloud-sdk-librarian-prod/images-prod/python-librarian-generator@sha256:c8612d3fffb3f6a32353b2d1abd16b61e87811866f7ec9d65b59b02eb452a620<details><summary>bigframes: 2.30.0</summary>##[2.30.0](v2.29.0...v2.30.0)(2025-12-03)### Features* Support mixed scalar-analytic expressions (#2239)([20ab469](20ab469d))* Allow drop_duplicates over unordered dataframe (#2303)([52665fa](52665fa5))* Preserve source names better for more readable sql (#2243)([64995d6](64995d65))* use end user credentials for `bigframes.bigquery.ai` functions when`connection_id` is not present (#2272)([7c062a6](7c062a68))* pivot_table supports fill_value arg (#2257)([8f490e6](8f490e68))* Support builtins funcs for df.agg (#2256)([956a5b0](956a5b00))* add bigquery.json_keys (#2286)([b487cf1](b487cf1f))* Add agg/aggregate methods to windows (#2288)([c4cb39d](c4cb39dc))* Add bigframes.pandas.crosstab (#2231)([c62e553](c62e5535))* Implement single-column sorting for interactive table widget (#2255)([d1ecc61](d1ecc61b))### Bug Fixes* Pass credentials properly for read api instantiation (#2280)([3e3fe25](3e3fe259))* Update max_instances default to reflect actual value (#2302)([4489687](4489687e))* Improve Anywidget pagination and display for unknown row counts(#2258)([508deae](508deae5))* Fix issue with stream upload batch size upload limit (#2290)([6cdf64b](6cdf64b0))* calling info() on empty dataframes no longer leads to errors (#2267)([95a83f7](95a83f77))* do not warn with DefaultIndexWarning in partial ordering mode (#2230)([cc2dbae](cc2dbae6))### Documentation* update docs and tests for Gemini 2.5 models (#2279)([08c0c0c](08c0c0c8))* Add Google Analytics configuration to conf.py (#2301)([0b266da](0b266da1))* fix LogisticRegression docs rendering (#2295)([32e5313](32e53134))* update API reference to new `dataframes.bigquery.dev` location (#2293)([da06439](da064397))* use autosummary to split documentation pages (#2251)([f7fd2d2](f7fd2d20))</details>
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@tswasttswasttswast approved these changes

Assignees

@shuoweilshuoweil

Labels

api: bigqueryIssues related to the googleapis/python-bigquery-dataframes API.size: xlPull request size is extra large.

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

2 participants

@shuoweil@tswast

[8]ページ先頭

©2009-2025 Movatter.jp