Add tags support for pivot tables#9514
Conversation
Adds tag-aware bulk actions to the Explore pivot. The sidebar now has a dedicated Tags column when the metrics view defines tags on dimensions or measures, mirroring the Explore dropdown's two-column layout. Each tag row supports: - Click to filter the Measures and Dimensions sections to items in that tag - Drag the tag onto Rows / Columns to bulk-add (dims skipped from Rows) - Drag onto a contextual Auto-arrange zone (dims to Rows, measures to Columns) - CMD / Ctrl + Click or + Drop to replace the target zone instead of appending Replace paths also clean the opposite zone so a dimension never appears in both rows and columns at once, and append paths skip items already placed in either zone. Reuses the existing tag-utils module and hoists the tag index into a new shared state-manager selector consumed by both the Explore dropdown and the pivot sidebar. Extracts the CMD/Ctrl modifier store and the "Filtered by tag" banner into reusable pieces. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| // Replace the rows zone with the given values. Anything in the new rows | ||
| // that already lived in columns is removed from columns, so the same | ||
| // dimension never appears in both zones. | ||
| replacePivotRows(name: string, values: PivotChipData[]) { |
There was a problem hiding this comment.
Lets keep the state update method light. Other places in pivot calculate the rows/columns and call setPivotRows or setPivotColumns. Note that it has code to update sorting and other related fields.
We want to make as little changes to dashboard-stores.ts as possible, we will eventually replace it with class based state management.
| </span> | ||
| <Tooltip.Root delayDuration={200}> | ||
| <Tooltip.Trigger> | ||
| <button |
There was a problem hiding this comment.
nit: Add {#snippet child()} here so that items-center applies correctly. Otherwise the cancel icon is not aligned.
| const replace = e.metaKey || e.ctrlKey; | ||
| const { dimensions, measures } = dragData.tagPayload; | ||
| if (replace) { | ||
| metricsExplorerStore.replacePivotRows($exploreName, dimensions); |
There was a problem hiding this comment.
Same here, lets call setRows or setColumns.
| </div> | ||
| {/if} | ||
| {#if showAutoArrange && dragData?.tagPayload} | ||
| <div class="header-row" transition:slide={{ duration: 160, axis: "y" }}> |
There was a problem hiding this comment.
nit: maybe good to move this to a component? That way header will only render things, drag-drop handling will be in the new component or DragList
| return splitTagItems(tagName, dimensionTagIndex, measureTagIndex); | ||
| } | ||
|
|
||
| function handleTagDragStart( |
There was a problem hiding this comment.
Lets move the drag-drop logic to PivotTagRow.
| function addTagToRows(tagName: string, replace: boolean) { | ||
| const { dimensions: dims } = tagItemsFor(tagName); | ||
| if (replace) { | ||
| metricsExplorerStore.replacePivotRows(exploreName, dims); |
There was a problem hiding this comment.
Lets take in setRows and setColumns and use those instead of calling methods on metricsExplorerStore
- Keep dashboard-stores.ts light: remove addPivotFields, replacePivotRows,
replacePivotColumns. Restore addPivotField to its original form. Callers
compute the new row/column arrays and call the existing setPivotRows /
setPivotColumns instead.
- Add pure helpers in pivot-utils.ts (appendChipsToZone,
replaceZoneCleaningOther) for cross-zone-aware bulk updates.
- Move drag setup, portal rendering, and bulk-update logic into
PivotTagRow so the sidebar just wires props.
- PivotSidebar takes setRows / setColumns instead of importing
metricsExplorerStore. PivotDisplay wires the setters from the store.
- DragList tag-drop computes new items for its zone and calls onUpdate
instead of invoking the store directly. The component is no longer
coupled to the explore concept.
- Extract PivotAutoArrangeZone component. It owns the drop handling and
modifier-aware copy; PivotHeader just renders it with the rows /
columns / setters props.
- TagFilterBanner: wrap the Tooltip.Trigger button in a {#snippet child()}
so flex alignment of the cancel icon is correct.
- AddField bulk-add now loops addPivotField for each item (since
addPivotFields is removed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The pre-existing handleRowClick/handleColumnClick handlers still need the state managers; only the tag-drop path was meant to be decoupled. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
svelte-check flagged the variable as updated-but-non-reactive. Wrapping with $state makes the assignment in beginDrag drive reactivity correctly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| const { dimensions, measures } = dragData.tagPayload; | ||
| const newItems = | ||
| zone === "rows" ? dimensions : [...dimensions, ...measures]; | ||
| if (replace) { |
There was a problem hiding this comment.
newItems.length > 0 check is needed for this as well no?
| ); | ||
| const toAdd = zone === "rows" ? dims : [...dims, ...meas]; | ||
| for (const item of toAdd) { | ||
| metricsExplorerStore.addPivotField($exploreName, item, zone === "rows"); |
There was a problem hiding this comment.
One edge case that this leads to is there is no dedupe. Lets reuse appendChipsToZone and also replace all calls of addPivotField in this file.
| window.addEventListener("blur", () => _modifierHeld.set(false)); | ||
| } | ||
|
|
||
| export const modifierHeld: Readable<boolean> = derived( |
There was a problem hiding this comment.
Lets just do modifierHeld = _modifierHeld as Readable<boolean>
- DragList tag-drop now also guards the replace branch on newItems.length > 0, so a pure-measure tag CMD-dropped on rows is a no-op instead of wiping the zone. - AddField now routes every selection through appendChipsToZone + setPivotRows / setPivotColumns. This adds cross-zone dedup (a time grain or tag bulk-add cannot duplicate something already placed in the other zone) and removes the remaining addPivotField calls in this file. - modifier-key.ts: drop the unnecessary derived store and just cast the writable to Readable<boolean>. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings the merged Explore tag feature into the pivot sidebar. When a metrics view defines tags on dimensions or measures, the sidebar shows a dedicated Tags column on the left (mirroring the Explore dropdown's two-column layout).
×to clear.⌘/ Ctrl + Click or + Drop replaces the target zone instead of appending. Replace paths also clean the opposite zone, so the same dimension never lands in both rows and columns. Append paths skip items already placed in either zone.The tag index is hoisted into a shared
state-managers/selectors/tags.tsso the Explore dropdown and the pivot sidebar consume the same derived index.DashboardMetricsDraggableListnow takestagIndexas a prop; its two callers pass the new selector value. SharedTagFilterBannerandmodifier-keymodules deduplicate the banner UI and CMD/Ctrl tracking across surfaces.Closes APP-1234
Checklist:
Developed in collaboration with Claude Code