Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions web-common/src/features/dashboards/pivot/PivotDisplay.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@
rowId,
columnId,
)}
setPivotOutermostRowLimit={(limit) =>
metricsExplorerStore.setPivotOutermostRowLimit($exploreName, limit)}
setPivotRowLimitForExpanded={(expandIndex, limit) =>
metricsExplorerStore.setPivotRowLimitForExpandedRow(
$exploreName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@
export let row: Row<PivotDataRow>;
export let value: string;
export let assembled = true;
export let hasNestedDimensions = false;

$: canExpand = row.getCanExpand();
$: expanded = row.getIsExpanded();
$: assembledAndCanExpand = assembled && canExpand;

$: needsSpacer = row.depth >= 1 || (hasNestedDimensions && !canExpand);
</script>

<div
Expand All @@ -26,7 +29,7 @@
<div class="caret opacity-100 shrink-0" class:expanded>
<ChevronRight size="16px" color="#9CA3AF" />
</div>
{:else if row.depth >= 1}
{:else if needsSpacer}
<span class="shrink-0"><Spacer size="16px" /></span>
{/if}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,15 @@
export let row: Row<PivotDataRow>;
export let value: string;
export let assembled = true;
export let hasNestedDimensions = false;

$: needsSpacer = row.depth >= 1 || hasNestedDimensions;
</script>

<div class="show-more-cell" style:padding-left="{row?.depth * 14}px">
<Spacer size="16px" />
{#if needsSpacer}
<Spacer size="16px" />
{/if}
<Tooltip distance={8} location="right">
<span class={assembled ? "ui-copy" : "ui-copy-inactive"}>
Show more ...
Expand Down
24 changes: 20 additions & 4 deletions web-common/src/features/dashboards/pivot/PivotTable.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
export let setPivotActiveCell:
| ((rowId: string, columnId: string) => void)
| undefined = undefined;
export let setPivotOutermostRowLimit: ((limit: number) => void) | undefined =
undefined;
export let setPivotRowLimitForExpanded:
| ((expandIndex: string, limit: number) => void)
| undefined = undefined;
Expand Down Expand Up @@ -209,13 +211,27 @@
// Handle "Show More" button clicks
if (value === SHOW_MORE_BUTTON && rowHeader) {
const rowData = row.original;

const expandIndex = rowId.split(".").slice(0, -1).join(".");
const currentLimit = rowData.__currentLimit as number;
const nextLimit = getNextRowLimit(currentLimit);

if (expandIndex && nextLimit && setPivotRowLimitForExpanded) {
setPivotRowLimitForExpanded(expandIndex, nextLimit);
if (!nextLimit) return;

// Check if this is the outermost dimension or a nested dimension
// Outermost dimension has rowId like "0", "1", etc. (no dots)
// Nested dimensions have rowId like "0.1", "0.1.2", etc.
const isOutermostDimension = !rowId.includes(".");

if (isOutermostDimension) {
// Handle outermost dimension "Show more" click
if (setPivotOutermostRowLimit) {
setPivotOutermostRowLimit(nextLimit);
}
} else {
// Handle nested dimension "Show more" click
const expandIndex = rowId.split(".").slice(0, -1).join(".");
if (expandIndex && setPivotRowLimitForExpanded) {
setPivotRowLimitForExpanded(expandIndex, nextLimit);
}
}
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,9 @@ function getNestedColumnDef(
const rowDimensionsForColumnDef = rowDimensions.slice(0, 1);
const nestedLabel = getRowNestedLabel(rowDimensions);

// Check if there are nested dimensions (more than one row dimension)
const hasNestedDimensions = rowDimensions.length > 1;

// Create row dimension columns
const rowDefinitions: ColumnDef<PivotDataRow>[] =
rowDimensionsForColumnDef.map((d) => {
Expand All @@ -450,6 +453,7 @@ function getNestedColumnDef(
return cellComponent(PivotShowMoreCell, {
value: label,
row,
hasNestedDimensions,
});
}

Expand All @@ -463,6 +467,7 @@ function getNestedColumnDef(
return cellComponent(PivotExpandableCell, {
value: formattedDimensionValue,
row,
hasNestedDimensions,
});
},
};
Expand Down
7 changes: 7 additions & 0 deletions web-common/src/features/dashboards/pivot/pivot-constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ export const PIVOT_ROW_LIMIT_OPTIONS = [5, 10, 25, 50, 100] as const;
* @param rowLimit - The maximum number of rows to fetch (undefined = unlimited)
* @param rowOffset - The current row offset (for pagination)
* @param pageSize - The number of rows per page
* @param respectPageSize - Whether to constrain by page size (default: true). Set to false for explicit user-requested limits.
* @returns The limit to apply as a string for the query
*/
export function calculateEffectiveRowLimit(
rowLimit: number | undefined,
rowOffset: number,
pageSize: number,
respectPageSize: boolean = true,
): string {
if (rowLimit === undefined) {
return pageSize.toString();
Expand All @@ -31,6 +33,11 @@ export function calculateEffectiveRowLimit(
if (remainingRows <= 0) {
return "0";
}
// When respectPageSize is false (e.g., for explicit user-requested limits like "Show more" button),
// don't constrain by page size to allow fetching the full requested amount
if (!respectPageSize) {
return remainingRows.toString();
}
return Math.min(remainingRows, pageSize).toString();
}

Expand Down
65 changes: 60 additions & 5 deletions web-common/src/features/dashboards/pivot/pivot-data-store.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { getDimensionFilterWithSearch } from "@rilldata/web-common/features/dashboards/dimension-table/dimension-table-utils";
import { calculateEffectiveRowLimit } from "@rilldata/web-common/features/dashboards/pivot/pivot-constants";
import {
calculateEffectiveRowLimit,
MAX_ROW_EXPANSION_LIMIT,
SHOW_MORE_BUTTON,
} from "@rilldata/web-common/features/dashboards/pivot/pivot-constants";
import { mergeFilters } from "@rilldata/web-common/features/dashboards/pivot/pivot-merge-filters";
import { memoizeMetricsStore } from "@rilldata/web-common/features/dashboards/state-managers/memoize-metrics-store";
import type { StateManagers } from "@rilldata/web-common/features/dashboards/state-managers/state-managers";
Expand Down Expand Up @@ -299,13 +303,27 @@ export function createPivotDataStore(
readable(null);

if (!isFlat) {
// Calculate the effective limit based on rowLimit, offset, and page size
// Use outermostRowLimit if set, otherwise fall back to rowLimit
const effectiveOutermostLimit =
config.pivot.outermostRowLimit ?? config.pivot.rowLimit;

// Calculate the effective limit based on outermostRowLimit, offset, and page size
// When outermostRowLimit is explicitly set, don't constrain by page size
const isExplicitOutermostLimit =
config.pivot.outermostRowLimit !== undefined;
const limitToApply = calculateEffectiveRowLimit(
config.pivot.rowLimit,
effectiveOutermostLimit,
rowOffset,
NUM_ROWS_PER_PAGE,
!isExplicitOutermostLimit, // Don't respect page size for explicit outermost limit
);

// Query for limit + 1 to detect if there's more data
const limitToQuery =
effectiveOutermostLimit !== undefined
? (parseInt(limitToApply) + 1).toString()
: limitToApply;

// Get sort order for the anchor dimension
rowDimensionAxisQuery = getAxisForDimensions(
ctx,
Expand All @@ -315,7 +333,7 @@ export function createPivotDataStore(
whereFilter,
sortPivotBy,
timeRange,
limitToApply,
limitToQuery,
rowOffset.toString(),
);
}
Expand Down Expand Up @@ -424,9 +442,30 @@ export function createPivotDataStore(
globalTotalsResponse?.data?.data,
);

const rowDimensionValues =
let rowDimensionValues =
rowDimensionAxes?.data?.[anchorDimension] || [];

// Detect if there's more data for the outermost dimension
Copy link
Collaborator

Choose a reason for hiding this comment

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

While this is removing the extra dimension for nested query it is still displayed in the table. Are we missing code to remove it from the display list?

// and trim to the actual limit
let hasMoreRows = false;
const effectiveOutermostLimit =
config.pivot.outermostRowLimit ?? config.pivot.rowLimit;
if (!isFlat && effectiveOutermostLimit !== undefined) {
const isExplicitOutermostLimit =
config.pivot.outermostRowLimit !== undefined;
const limitToApply = calculateEffectiveRowLimit(
effectiveOutermostLimit,
rowOffset,
NUM_ROWS_PER_PAGE,
!isExplicitOutermostLimit, // Don't respect page size for explicit outermost limit
);
const actualLimit = parseInt(limitToApply);
if (rowDimensionValues.length > actualLimit) {
hasMoreRows = true;
rowDimensionValues = rowDimensionValues.slice(0, actualLimit);
}
}

const totalColumns = getTotalColumnCount(totalsRowData);

const axesRowTotals =
Expand Down Expand Up @@ -605,6 +644,22 @@ export function createPivotDataStore(
expandedTableMap[key] = tableDataExpanded;
}

// Add "Show more" row for outermost dimension if needed
const effectiveOutermostLimit =
config.pivot.outermostRowLimit ?? config.pivot.rowLimit;

if (
hasMoreRows &&
effectiveOutermostLimit &&
effectiveOutermostLimit < MAX_ROW_EXPANSION_LIMIT
) {
const showMoreRow: PivotDataRow = {
[anchorDimension]: SHOW_MORE_BUTTON,
__currentLimit: effectiveOutermostLimit,
} as PivotDataRow;
tableDataExpanded = [...tableDataExpanded, showMoreRow];
}

const activeCell = config.pivot.activeCell;
let activeCellFilters: PivotFilter | undefined = undefined;
if (activeCell) {
Expand Down
9 changes: 7 additions & 2 deletions web-common/src/features/dashboards/pivot/pivot-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,12 @@ export function getPivotConfigKey(config: PivotDataStoreConfig) {
pivot,
} = config;

const { sorting, tableMode: tableModeKey, rowLimit } = pivot;
const {
sorting,
tableMode: tableModeKey,
rowLimit,
outermostRowLimit,
} = pivot;
const timeKey = JSON.stringify(time);
const sortingKey = JSON.stringify(sorting);
const filterKey = JSON.stringify(whereFilter);
Expand All @@ -63,7 +68,7 @@ export function getPivotConfigKey(config: PivotDataStoreConfig) {
.concat(measureNames, colDimensionNames)
.join("_");

return `${dimsAndMeasures}_${timeKey}_${sortingKey}_${tableModeKey}_${filterKey}_${enableComparison}_${comparisonTimeKey}_${rowLimit ?? "all"}`;
return `${dimsAndMeasures}_${timeKey}_${sortingKey}_${tableModeKey}_${filterKey}_${enableComparison}_${comparisonTimeKey}_${rowLimit ?? "all"}_${outermostRowLimit ?? "none"}`;
}

/**
Expand Down
122 changes: 122 additions & 0 deletions web-common/src/features/dashboards/pivot/tests/pivot-constants.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import {
calculateEffectiveRowLimit,
getNextRowLimit,
getNextLimitLabel,
} from "@rilldata/web-common/features/dashboards/pivot/pivot-constants";
import { describe, it, expect } from "vitest";

describe("calculateEffectiveRowLimit", () => {
describe("with respectPageSize=true (default)", () => {
it("returns pageSize when rowLimit is undefined", () => {
expect(calculateEffectiveRowLimit(undefined, 0, 50)).toBe("50");
expect(calculateEffectiveRowLimit(undefined, 10, 50)).toBe("50");
});

it("returns 0 when remainingRows is 0 or negative", () => {
expect(calculateEffectiveRowLimit(50, 50, 50)).toBe("0");
expect(calculateEffectiveRowLimit(50, 60, 50)).toBe("0");
});

it("returns min of remainingRows and pageSize when both are positive", () => {
// remainingRows < pageSize
expect(calculateEffectiveRowLimit(30, 0, 50)).toBe("30");
expect(calculateEffectiveRowLimit(100, 80, 50)).toBe("20");

// remainingRows > pageSize
expect(calculateEffectiveRowLimit(100, 0, 50)).toBe("50");
expect(calculateEffectiveRowLimit(150, 50, 50)).toBe("50");

// remainingRows == pageSize
expect(calculateEffectiveRowLimit(50, 0, 50)).toBe("50");
});

it("handles pagination correctly with rowOffset", () => {
const rowLimit = 100;
const pageSize = 50;

// First page
expect(calculateEffectiveRowLimit(rowLimit, 0, pageSize)).toBe("50");

// Second page
expect(calculateEffectiveRowLimit(rowLimit, 50, pageSize)).toBe("50");

// Third page (only 10 rows left)
expect(calculateEffectiveRowLimit(rowLimit, 90, pageSize)).toBe("10");
});
});

describe("with respectPageSize=false", () => {
it("returns pageSize when rowLimit is undefined", () => {
expect(calculateEffectiveRowLimit(undefined, 0, 50, false)).toBe("50");
});

it("returns 0 when remainingRows is 0 or negative", () => {
expect(calculateEffectiveRowLimit(50, 50, 50, false)).toBe("0");
expect(calculateEffectiveRowLimit(50, 60, 50, false)).toBe("0");
});

it("returns full remainingRows without pageSize constraint", () => {
// This is the key difference - no min() with pageSize
expect(calculateEffectiveRowLimit(100, 0, 50, false)).toBe("100");
expect(calculateEffectiveRowLimit(75, 0, 50, false)).toBe("75");
expect(calculateEffectiveRowLimit(150, 0, 50, false)).toBe("150");
});

it("handles offset correctly without pageSize constraint", () => {
expect(calculateEffectiveRowLimit(100, 20, 50, false)).toBe("80");
expect(calculateEffectiveRowLimit(100, 50, 50, false)).toBe("50");
expect(calculateEffectiveRowLimit(100, 90, 50, false)).toBe("10");
});

it("allows fetching more than one page at once", () => {
// When user clicks "Show more" to 100, we want to fetch all 100 rows
// not just 50 (one page)
expect(calculateEffectiveRowLimit(100, 0, 50, false)).toBe("100");
});
});
});

describe("getNextRowLimit", () => {
it("returns next limit in progression: 5 → 10 → 25 → 50 → 100", () => {
expect(getNextRowLimit(5)).toBe(10);
expect(getNextRowLimit(10)).toBe(25);
expect(getNextRowLimit(25)).toBe(50);
expect(getNextRowLimit(50)).toBe(100);
});

it("returns undefined when at or beyond 100", () => {
expect(getNextRowLimit(100)).toBeUndefined();
expect(getNextRowLimit(150)).toBeUndefined();
});

it("finds next higher value when current limit is not in progression", () => {
expect(getNextRowLimit(7)).toBe(10);
expect(getNextRowLimit(15)).toBe(25);
expect(getNextRowLimit(30)).toBe(50);
expect(getNextRowLimit(60)).toBe(100);
});

it("handles edge cases", () => {
expect(getNextRowLimit(1)).toBe(5);
expect(getNextRowLimit(99)).toBe(100);
});
});

describe("getNextLimitLabel", () => {
it("returns string representation of next limit", () => {
expect(getNextLimitLabel(5)).toBe("10");
expect(getNextLimitLabel(10)).toBe("25");
expect(getNextLimitLabel(25)).toBe("50");
expect(getNextLimitLabel(50)).toBe("100");
});

it("returns '100' when at or beyond max limit", () => {
expect(getNextLimitLabel(100)).toBe("100");
expect(getNextLimitLabel(150)).toBe("100");
});

it("handles non-standard limits", () => {
expect(getNextLimitLabel(7)).toBe("10");
expect(getNextLimitLabel(30)).toBe("50");
});
});
Loading
Loading