Skip to content

Commit fc3ce68

Browse files
committed
Clean up
1 parent b31210c commit fc3ce68

14 files changed

Lines changed: 109 additions & 50 deletions

File tree

README.md

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ Returns a `PivotTableResult` dict containing the current `config` state.
5757
| `values` | `list[str] \| None` | `None` | Column names to aggregate as measures. |
5858
| `synthetic_measures` | `list[dict] \| None` | `None` | Derived measures computed from source-field sums (for example, ratio of sums). See [Synthetic Measures](#synthetic-measures-v1). |
5959
| `aggregation` | `str \| dict[str, str]` | `"sum"` | Aggregation setting for raw value fields. A single string applies to every raw measure; a dict enables per-measure aggregation. See [Aggregation Functions](#aggregation-functions). |
60-
| `interactive` | `bool` | `True` | Enable toolbar controls for reconfiguring the pivot. |
60+
| `interactive` | `bool` | `True` | Enable end-user config controls. When `False`, the toolbar is hidden and header-menu sort/filter/show-values-as actions are disabled. |
6161

6262
#### Totals and Subtotals
6363

@@ -99,9 +99,9 @@ Returns a `PivotTableResult` dict containing the current `config` state.
9999
| Parameter | Type | Default | Description |
100100
|-----------|------|---------|-------------|
101101
| `on_cell_click` | `Callable[[], None] \| None` | `None` | Called when a user clicks a data cell. Read the payload from `st.session_state[key]`. |
102-
| `on_config_change` | `Callable[[], None] \| None` | `None` | Called when the user changes the pivot config via the toolbar. |
102+
| `on_config_change` | `Callable[[], None] \| None` | `None` | Called when the user changes the pivot config interactively, including toolbar and header-menu actions. |
103103
| `enable_drilldown` | `bool` | `True` | Show an inline drill-down panel with source records when a cell is clicked. |
104-
| `locked` | `bool` | `False` | Freeze toolbar config controls (rows/columns/values/per-measure aggregation/settings). Sorting and filtering via header menus remain available. |
104+
| `locked` | `bool` | `False` | Viewer mode with exploration enabled. Toolbar config controls and settings toggles are disabled, while header-menu sorting/filtering and drill-down remain available. |
105105
| `export_filename` | `str \| None` | `None` | Base filename (without extension) for exported files. Date and extension are appended automatically. Defaults to `"pivot-table"`. |
106106

107107
#### Data Control
@@ -137,6 +137,7 @@ Returns a `PivotTableResult` dict containing the current `config` state.
137137
```python
138138
st_pivot_table(
139139
df,
140+
key="aggregation_example",
140141
rows=["Region"],
141142
columns=["Year"],
142143
values=["Revenue", "Units", "Price"],
@@ -166,6 +167,7 @@ Optional synthetic-measure fields:
166167
```python
167168
st_pivot_table(
168169
df,
170+
key="synthetic_measures_example",
169171
rows=["Region"],
170172
columns=["Year"],
171173
values=["Revenue"],
@@ -238,6 +240,7 @@ Display measures as percentages instead of raw numbers.
238240
```python
239241
st_pivot_table(
240242
df,
243+
key="show_values_as_example",
241244
rows=["Region"],
242245
columns=["Year"],
243246
values=["Revenue", "Profit"],
@@ -266,12 +269,18 @@ A single string applies to all value fields. A dict maps field names to patterns
266269
# Per-field formatting
267270
st_pivot_table(
268271
df,
272+
key="number_format_per_field_example",
269273
values=["Revenue", "Profit"],
270274
number_format={"Revenue": "$,.0f", "Profit": ",.2f"},
271275
)
272276

273277
# Global format for all fields
274-
st_pivot_table(df, values=["Revenue"], number_format="$,.0f")
278+
st_pivot_table(
279+
df,
280+
key="number_format_global_example",
281+
values=["Revenue"],
282+
number_format="$,.0f",
283+
)
275284
```
276285

277286
### Conditional Formatting
@@ -331,6 +340,7 @@ Multiple rules can be combined:
331340
```python
332341
st_pivot_table(
333342
df,
343+
key="conditional_formatting_example",
334344
values=["Revenue", "Profit", "Units"],
335345
conditional_formatting=[
336346
{"type": "data_bars", "apply_to": ["Revenue"], "color": "#1976d2", "fill": "gradient"},
@@ -354,10 +364,14 @@ Control how null/NaN values in the source data are treated.
354364

355365
```python
356366
# Global mode
357-
st_pivot_table(df, null_handling="zero")
367+
st_pivot_table(df, key="null_handling_global_example", null_handling="zero")
358368

359369
# Per-field modes
360-
st_pivot_table(df, null_handling={"Region": "separate", "Revenue": "zero"})
370+
st_pivot_table(
371+
df,
372+
key="null_handling_per_field_example",
373+
null_handling={"Region": "separate", "Revenue": "zero"},
374+
)
361375
```
362376

363377
### Subtotals and Row Grouping
@@ -367,6 +381,7 @@ With 2+ row dimensions, enable subtotals to see group-level aggregations with co
367381
```python
368382
st_pivot_table(
369383
df,
384+
key="subtotals_example",
370385
rows=["Region", "Category"],
371386
columns=["Year"],
372387
values=["Revenue"],
@@ -402,6 +417,7 @@ With 2+ column dimensions, column groups can be collapsed into subtotal columns.
402417
```python
403418
st_pivot_table(
404419
df,
420+
key="column_groups_example",
405421
rows=["Region"],
406422
columns=["Year", "Category"],
407423
values=["Revenue"],
@@ -442,11 +458,12 @@ result = st_pivot_table(
442458

443459
### Locked Mode
444460

445-
Freeze toolbar config controls so end-users cannot change rows, columns, values, per-measure aggregation, or display settings. The entire utility menu (reset, swap, config import/export, data export, settings) is hidden. Sorting and filtering via header menus remain available.
461+
Use `locked=True` for a viewer-mode experience with exploration enabled. Toolbar config controls stay locked so end-users cannot change rows, columns, values, per-measure aggregation, or settings toggles. Reset, Swap, config import/export, and data export are hidden, while the Settings gear remains visible so users can inspect settings and use Expand/Collapse All group controls. Header-menu sorting and filtering remain available, and drill-down still works.
446462

447463
```python
448464
st_pivot_table(
449465
df,
466+
key="locked_mode_example",
450467
rows=["Region"],
451468
columns=["Year"],
452469
values=["Revenue"],
@@ -467,7 +484,11 @@ When `interactive=True`, hovering over the top-right of the toolbar reveals util
467484
| **Export Data** | Open the export popover (CSV / TSV / Clipboard). Use `export_filename` to customize the download filename. |
468485
| **Settings** (gear icon) | Opens a popover with display toggles: Row Totals, Column Totals, Subtotals, Repeat Labels, Sticky Headers, and Expand/Collapse All group controls |
469486

470-
In **locked mode**, the entire utility menu (including Settings) is hidden. Only sorting and filtering via header menus remain available.
487+
In **locked mode**, Reset, Swap, config import/export, and data export are hidden. The Settings gear remains visible, settings toggles are disabled, group expand/collapse actions remain available, and header-menu sorting and filtering stay enabled.
488+
489+
### Non-Interactive Mode
490+
491+
Set `interactive=False` to render a read-only pivot view. This hides the toolbar and disables header-menu config actions (sorting, filtering, and `Show Values As`). Cell clicks and drill-down remain available.
471492

472493
---
473494

@@ -516,7 +537,7 @@ For total cells, `rowKey` or `colKey` will be `["Total"]` and the corresponding
516537

517538
### Config State
518539

519-
The returned `config` dict contains the full current configuration including any changes the user made via the toolbar. Use this to persist user customizations or synchronize multiple components.
540+
The returned `config` dict contains the current supported configuration state, including interactive changes such as rows, columns, values, aggregation, totals, sorting, filtering, and display options. Use this to persist user customizations or synchronize multiple components.
520541

521542
---
522543

@@ -550,7 +571,7 @@ uv pip install -e '.[with-streamlit]' --force-reinstall
550571
uv run streamlit run streamlit_app.py
551572
```
552573

553-
The example app (`streamlit_app.py`) contains 12 sections demonstrating every feature with interactive examples and inline documentation.
574+
The example app (`streamlit_app.py`) contains 13 sections demonstrating every feature with interactive examples and inline documentation.
554575

555576
### Building the frontend
556577

@@ -583,7 +604,7 @@ npx vitest run
583604
uv build
584605
```
585606

586-
Output: `dist/streamlit_pivot_table-0.0.1-py3-none-any.whl`
607+
Output: `dist/streamlit_pivot_table-0.1.0-py3-none-any.whl`
587608

588609
### Requirements
589610

SKILL.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,7 @@ When `interactive=True`, hovering over the top-right of the toolbar reveals util
471471
| **Export Data** | Open the export popover (CSV / TSV / Clipboard). Use `export_filename` to customize the download filename. |
472472
| **Settings** (gear icon) | Opens a popover with display toggles: Row Totals, Column Totals, Subtotals, Repeat Labels, Sticky Headers, and Expand/Collapse All group controls |
473473

474-
In **locked mode**, the entire utility menu (including Settings) is hidden. Only sorting and filtering via header menus remain available.
474+
In **locked mode**, Reset, Swap, config import/export, and data export are hidden. The Settings gear remains visible, its toggles are disabled, and sorting/filtering via header menus remain available.
475475

476476
---
477477

@@ -543,7 +543,7 @@ This section covers deploying the pivot table component (as a `.whl` file) into
543543

544544
```
545545
To set up your SiS on SPCS app with the pivot table component, I need:
546-
1. The path to your .whl file (e.g., ~/Downloads/streamlit_pivot_table-0.0.1-py3-none-any.whl)
546+
1. The path to your .whl file (e.g., ~/Downloads/streamlit_pivot_table-0.1.0-py3-none-any.whl)
547547
2. Do you have an existing SiS project directory with snowflake.yml, or should I create one from scratch?
548548
3. What Snowflake table(s) will the app query?
549549
4. What compute pool should the app run on? (e.g., MY_COMPUTE_POOL)
@@ -553,7 +553,7 @@ To set up your SiS on SPCS app with the pivot table component, I need:
553553

554554
**After the user responds**, derive these values from the `.whl` filename and use them in ALL subsequent steps:
555555

556-
- **`WHL_FILENAME`**: The `.whl` file name (e.g., `streamlit_pivot_table-0.0.1-py3-none-any.whl`)
556+
- **`WHL_FILENAME`**: The `.whl` file name (e.g., `streamlit_pivot_table-0.1.0-py3-none-any.whl`)
557557
- **`PACKAGE_NAME`**: The portion before the first version segment, with hyphens replaced by underscores (e.g., `streamlit_pivot_table`)
558558
- **`TABLE_NAMES`**: The Snowflake table(s) the user wants to query
559559
- **`COMPUTE_POOL`**: The SPCS compute pool name for the app runtime

e2e_playwright/pivot_table_test.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@ def get_pivot(page: Page, key: str) -> Locator:
7575

7676
def open_settings_popover(container: Locator):
7777
"""Open the gear settings popover in the toolbar."""
78-
container.get_by_test_id("toolbar-settings").click()
78+
button = container.get_by_test_id("toolbar-settings")
79+
button.scroll_into_view_if_needed()
80+
button.evaluate("el => el.click()")
7981
expect(container.get_by_test_id("toolbar-settings-panel")).to_be_visible(
8082
timeout=5000
8183
)
@@ -527,7 +529,7 @@ def test_header_menu_sort_key_asc(page_at_app: Page):
527529
expect(container.get_by_test_id("pivot-table")).to_be_visible(timeout=15000)
528530

529531
container.get_by_test_id("header-menu-trigger-Region").click()
530-
menu = container.get_by_test_id("header-menu-Region")
532+
menu = page.get_by_test_id("header-menu-Region")
531533
expect(menu).to_be_visible(timeout=5000)
532534

533535
menu.get_by_test_id("header-sort-key-asc").click()
@@ -549,10 +551,10 @@ def test_header_menu_sort_key_desc(page_at_app: Page):
549551
expect(container.get_by_test_id("pivot-table")).to_be_visible(timeout=15000)
550552

551553
container.get_by_test_id("header-menu-trigger-Region").click()
552-
menu = container.get_by_test_id("header-menu-Region")
554+
menu = page.get_by_test_id("header-menu-Region")
553555
expect(menu).to_be_visible(timeout=5000)
554556

555-
menu.get_by_test_id("header-sort-key-desc").click()
557+
menu.get_by_test_id("header-sort-key-desc").evaluate("el => el.click()")
556558

557559
expect(container.get_by_test_id("pivot-row-header").first).to_have_text(
558560
"West", timeout=10000
@@ -881,28 +883,29 @@ def test_locked_mode_toolbar_disabled(page_at_app: Page):
881883
expect(container.get_by_test_id("toolbar-settings")).to_be_visible()
882884

883885

884-
def test_locked_mode_header_filter_still_works(page_at_app: Page):
885-
"""In locked mode, header menu opens and filtering still functions."""
886+
def test_locked_mode_header_sort_and_filter_still_work(page_at_app: Page):
887+
"""In locked mode, header-menu exploration remains available."""
886888
page = page_at_app
887889
container = get_pivot(page, "test_pivot_locked")
888890
expect(container.get_by_test_id("pivot-table")).to_be_visible(timeout=15000)
889891

890892
trigger = container.get_by_test_id("header-menu-trigger-Region")
891893
expect(trigger).to_be_visible()
892894

893-
trigger.click()
895+
trigger.evaluate("el => el.click()")
894896
menu = container.get_by_test_id("header-menu-Region")
895897
expect(menu).to_be_visible(timeout=5000)
896898

897-
# Sort section is hidden in locked mode
898-
expect(menu.get_by_test_id("header-menu-sort")).to_have_count(0)
899-
900-
# Filter section remains functional
899+
sort_section = menu.get_by_test_id("header-menu-sort")
900+
expect(sort_section).to_be_visible()
901901
filter_section = menu.get_by_test_id("header-menu-filter")
902902
expect(filter_section).to_be_visible()
903903
checkboxes = filter_section.locator("input[type=checkbox]")
904904
assert checkboxes.count() > 0
905905

906+
menu.get_by_test_id("header-sort-key-desc").click()
907+
expect(container.get_by_test_id("pivot-row-header").first).to_have_text("West")
908+
906909

907910
# =====================================================================
908911
# 12. Empty / Edge Cases (3 tests)

e2e_playwright/test-requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ pytest-xdist
66
requests>=2.31.0
77
pandas>=1.5
88

9-
dist/streamlit_pivot_table-0.0.1-py3-none-any.whl
9+
dist/streamlit_pivot_table-0.1.0-py3-none-any.whl
1010
streamlit>=1.51.0

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ build-backend = "setuptools.build_meta"
1818

1919
[project]
2020
name = "streamlit-pivot-table"
21-
version = "0.0.1"
21+
version = "0.1.0"
2222
description = "BI-focused pivot table component for Streamlit"
2323
readme = "README.md"
2424
license = "Apache-2.0"
2525
license-files = ["LICENSE", "NOTICES"]
2626
authors = [{ name = "Snowflake Inc", email = "hello@streamlit.io" }]
2727
maintainers = [{ name = "Snowflake Inc", email = "hello@streamlit.io" }]
2828
classifiers = [
29-
"Development Status :: 3 - Alpha",
29+
"Development Status :: 4 - Beta",
3030
"Intended Audience :: Developers",
3131
"Programming Language :: Python :: 3",
3232
"Programming Language :: Python :: 3.10",
@@ -41,7 +41,7 @@ requires-python = ">=3.10"
4141
[project.urls]
4242
Homepage = "https://streamlit.io"
4343
"Source Code" = "https://github.com/streamlit/streamlit-pivot-table"
44-
"Bug Tracker" = "https://github.com/streamlit/streamlit/issues"
44+
"Bug Tracker" = "https://github.com/streamlit/streamlit-pivot-table/issues"
4545
Community = "https://discuss.streamlit.io/"
4646

4747
[project.optional-dependencies]

streamlit_app.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,9 @@
172172
- Click the **⋮** menu icon on the "Region" header → uncheck regions to filter them out.
173173
- Use the search box to find specific values quickly.
174174
175-
**Right table** is **locked** — the toolbar config controls and utility menu
176-
(reset, swap, import/export, settings) are all hidden, but you can still
177-
sort and filter via the column header menus.
175+
**Right table** is **locked** — the toolbar config controls and utility actions
176+
(reset, swap, import/export) are hidden, the **Settings** gear remains visible
177+
for inspection only, and you can still sort and filter via the column header menus.
178178
179179
**API parameters used:** `hidden_from_aggregators`, `sorters`, `locked`
180180
"""

streamlit_pivot_table/__init__.py

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
import json
2121
import warnings
22-
from typing import TYPE_CHECKING, Any, TypedDict
22+
from typing import TYPE_CHECKING, Any, TypedDict, cast
2323

2424
if TYPE_CHECKING:
2525
from collections.abc import Callable
@@ -54,6 +54,7 @@ class SortConfig(TypedDict, total=False):
5454
direction: str # "asc" | "desc"
5555
value_field: str # required when by="value"
5656
col_key: list[str] # for row sort: sort within this specific column
57+
dimension: str # optional: scope sort to this dimension level and below
5758

5859

5960
class PivotConfig(TypedDict, total=False):
@@ -410,8 +411,8 @@ def st_pivot_table(
410411
sorters: dict[str, list[str]] | None = None,
411412
locked: bool = False,
412413
menu_limit: int | None = None,
413-
row_sort: dict[str, Any] | None = None,
414-
col_sort: dict[str, Any] | None = None,
414+
row_sort: SortConfig | None = None,
415+
col_sort: SortConfig | None = None,
415416
# Phase 3 parameters
416417
sticky_headers: bool = True,
417418
show_subtotals: bool | list[str] = False,
@@ -460,7 +461,9 @@ def st_pivot_table(
460461
empty_cell_value : str
461462
Display string for cells with no data.
462463
interactive : bool
463-
If True, the user can reconfigure the pivot via toolbar controls.
464+
If True, the user can reconfigure the pivot via toolbar controls and
465+
header-menu actions. If False, the toolbar is hidden and header-menu
466+
sort/filter/show-values-as actions are disabled.
464467
height : int or None
465468
Fixed height in pixels. None means auto-size (capped by ``max_height``).
466469
max_height : int
@@ -493,8 +496,10 @@ def st_pivot_table(
493496
Custom sort orderings per dimension. Maps column name to a list
494497
of values in the desired order.
495498
locked : bool
496-
If True, toolbar config controls are disabled (filtering still
497-
allowed). Defaults to False.
499+
If True, toolbar config controls are disabled. The settings gear stays
500+
visible so users can inspect settings and expand/collapse groups, but
501+
settings toggles are disabled. Header-menu sorting and filtering remain
502+
available. Defaults to False.
498503
menu_limit : int or None
499504
Max items to show in the header-menu filter checklist. Defaults
500505
to 50 when None.
@@ -628,11 +633,15 @@ def st_pivot_table(
628633
if sorters is not None:
629634
if not isinstance(sorters, dict):
630635
raise TypeError(f"sorters must be a dict, got {type(sorters).__name__}")
631-
for k, v in sorters.items():
632-
if not isinstance(k, str):
633-
raise TypeError(f"sorters keys must be strings, got {type(k).__name__}")
634-
if not isinstance(v, list) or not all(isinstance(s, str) for s in v):
635-
raise TypeError(f"sorters[{k!r}] must be a list of strings")
636+
for sorter_key, sorter_values in sorters.items():
637+
if not isinstance(sorter_key, str):
638+
raise TypeError(
639+
f"sorters keys must be strings, got {type(sorter_key).__name__}"
640+
)
641+
if not isinstance(sorter_values, list) or not all(
642+
isinstance(s, str) for s in sorter_values
643+
):
644+
raise TypeError(f"sorters[{sorter_key!r}] must be a list of strings")
636645

637646
if not isinstance(locked, bool):
638647
raise TypeError(f"locked must be a bool, got {type(locked).__name__}")
@@ -930,4 +939,4 @@ def st_pivot_table(
930939
if on_cell_click is not None:
931940
mount_kwargs["on_cell_click_change"] = on_cell_click
932941

933-
return _component(**mount_kwargs)
942+
return cast(PivotTableResult, _component(**mount_kwargs))

streamlit_pivot_table/frontend/src/config/Toolbar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export interface ToolbarProps {
6363
onConfigChange?: (config: PivotConfigV1) => void;
6464
/** Original config from Python for reset. If omitted, reset is hidden. */
6565
initialConfig?: PivotConfigV1;
66-
/** Lock config controls (filtering still allowed). */
66+
/** Lock config controls while keeping header-menu exploration enabled. */
6767
locked?: boolean;
6868
/** Columns that cannot be reassigned between zones. */
6969
frozenColumns?: Set<string>;

0 commit comments

Comments
 (0)