From 834132af5b64159391a17193caa84530159add21 Mon Sep 17 00:00:00 2001 From: intraQ <251956840+intraq-dev-ai@users.noreply.github.com> Date: Mon, 27 Jul 2026 14:15:23 +1000 Subject: [PATCH] Align public export row caps --- .../public-access/embed-data-routes.ts | 4 +- .../sql-chart/foundation-route-types.ts | 2 +- apps/web/src/modules/analyzer/api.ts | 2 +- .../analyzer/plan-table-data-request.ts | 2 +- .../components/canvas/component-download.ts | 4 +- tests/smoke/sql-editor-export-limits.test.ts | 40 +++++++++++++++++++ 6 files changed, 49 insertions(+), 5 deletions(-) diff --git a/apps/api/src/modules/public-access/embed-data-routes.ts b/apps/api/src/modules/public-access/embed-data-routes.ts index 73a24d7..e963c09 100644 --- a/apps/api/src/modules/public-access/embed-data-routes.ts +++ b/apps/api/src/modules/public-access/embed-data-routes.ts @@ -41,6 +41,8 @@ import { } from './embed-common.js'; import type { EmbedTokenService } from './embed-token-service.js'; +export const EMBED_DATA_SOURCE_DOWNLOAD_ROW_LIMIT = 100_000; + export class EmbedDataRoutes { constructor( private readonly tokenService: EmbedTokenService, @@ -318,7 +320,7 @@ export class EmbedDataRoutes { const rows = accessFilters.denyAll ? [] : filterRowsByEmbedScope(rowsForTable(lookup.source.id, lookup.table.name), [...scopeFilters, ...accessFilters.filters]); - const limit = readDownloadInteger(body.limit, rows.length || 1, 1, 1_000_000); + const limit = readDownloadInteger(body.limit, rows.length || 1, 1, EMBED_DATA_SOURCE_DOWNLOAD_ROW_LIMIT); const offset = readDownloadInteger(body.offset, 0, 0, rows.length); const selectedRows = rows.slice(offset, offset + limit); const format = (readOptionalString(body.format) ?? 'csv').toLowerCase(); diff --git a/apps/api/src/modules/sql-chart/foundation-route-types.ts b/apps/api/src/modules/sql-chart/foundation-route-types.ts index 6224f87..693f94f 100644 --- a/apps/api/src/modules/sql-chart/foundation-route-types.ts +++ b/apps/api/src/modules/sql-chart/foundation-route-types.ts @@ -13,7 +13,7 @@ export type Row = SqlEditorRow; export const ROW_COUNT_FIELD = '__row_count'; export const ANALYZER_CHART_DATA_SAFE_LIMIT = 100; export const DEFAULT_EXPORT_LIMIT = 100_000; -export const MAX_EXPORT_LIMIT = 250_000; +export const MAX_EXPORT_LIMIT = 100_000; export const DEFAULT_API_CHART_ROUTE_TIMEOUT_MS = 15_000; export const API_CHART_ROUTE_TIMEOUT_BUFFER_MS = 5_000; export const MAX_API_CHART_ROUTE_TIMEOUT_MS = 300_000; diff --git a/apps/web/src/modules/analyzer/api.ts b/apps/web/src/modules/analyzer/api.ts index bf923cd..db08604 100644 --- a/apps/web/src/modules/analyzer/api.ts +++ b/apps/web/src/modules/analyzer/api.ts @@ -18,7 +18,7 @@ import { } from './plan-table-data-request'; import { ANALYZER_RESULT_PAGE_SIZE } from './result-data'; -const ANALYZER_EXPORT_ROW_LIMIT = 250_000; +const ANALYZER_EXPORT_ROW_LIMIT = 100_000; interface ApiEnvelope { success: boolean; diff --git a/apps/web/src/modules/analyzer/plan-table-data-request.ts b/apps/web/src/modules/analyzer/plan-table-data-request.ts index b559da9..0b4d8cf 100644 --- a/apps/web/src/modules/analyzer/plan-table-data-request.ts +++ b/apps/web/src/modules/analyzer/plan-table-data-request.ts @@ -7,7 +7,7 @@ export interface AnalyzerPlanTableDataRequest { } const ANALYZER_CHART_DATA_ROW_LIMIT = 100; -const ANALYZER_CHART_DATA_EXPORT_ROW_LIMIT = 250_000; +const ANALYZER_CHART_DATA_EXPORT_ROW_LIMIT = 100_000; const DATE_PARAMETER_FIELDS = new Set([ 'from', 'to', diff --git a/apps/web/src/modules/dashboard-builder/components/canvas/component-download.ts b/apps/web/src/modules/dashboard-builder/components/canvas/component-download.ts index 6b2206a..e50f508 100644 --- a/apps/web/src/modules/dashboard-builder/components/canvas/component-download.ts +++ b/apps/web/src/modules/dashboard-builder/components/canvas/component-download.ts @@ -8,6 +8,8 @@ import { visualizationSpecFromElement } from '../../visualization/spec'; export type DashboardComponentDownloadFormat = 'csv' | 'excel'; +export const DASHBOARD_COMPONENT_DOWNLOAD_ROW_LIMIT = 100_000; + export interface DashboardComponentDownloadTarget { downloadSourceId: string; sourceId: string; @@ -68,7 +70,7 @@ export function buildDashboardComponentDownloadPayload( dashboardFilters, dataSource: target.downloadSourceId, format, - limit: 1_000_000, + limit: DASHBOARD_COMPONENT_DOWNLOAD_ROW_LIMIT, offset: 0, ...(parameterValues && Object.keys(parameterValues).length > 0 ? { parameterValues } : {}) }; diff --git a/tests/smoke/sql-editor-export-limits.test.ts b/tests/smoke/sql-editor-export-limits.test.ts index 0a62be4..dab8c08 100644 --- a/tests/smoke/sql-editor-export-limits.test.ts +++ b/tests/smoke/sql-editor-export-limits.test.ts @@ -1,5 +1,15 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; import { executeDataSourceSqlQuery } from '../../apps/api/src/modules/data-source/sql-query-engine.js'; +import { EMBED_DATA_SOURCE_DOWNLOAD_ROW_LIMIT } from '../../apps/api/src/modules/public-access/embed-data-routes.js'; +import { + DEFAULT_EXPORT_LIMIT, + MAX_EXPORT_LIMIT +} from '../../apps/api/src/modules/sql-chart/foundation-route-types.js'; +import { + buildDashboardComponentDownloadPayload, + DASHBOARD_COMPONENT_DOWNLOAD_ROW_LIMIT +} from '../../apps/web/src/modules/dashboard-builder/components/canvas/component-download.js'; +import type { DashboardElement } from '../../apps/web/src/modules/dashboard-builder/types.js'; import { executeSqlEditorQuery, SQL_EDITOR_EXPORT_ROW_LIMIT, @@ -88,4 +98,34 @@ describe('SQL Editor row limits', () => { expect(result.data.rowCount).toBe(1_250); expect(result.data.rows).toHaveLength(1_250); }); + + it('keeps dashboard and embed export caps aligned to the public export limit', () => { + expect(DEFAULT_EXPORT_LIMIT).toBe(SQL_EDITOR_EXPORT_ROW_LIMIT); + expect(MAX_EXPORT_LIMIT).toBe(SQL_EDITOR_EXPORT_ROW_LIMIT); + expect(DASHBOARD_COMPONENT_DOWNLOAD_ROW_LIMIT).toBe(SQL_EDITOR_EXPORT_ROW_LIMIT); + expect(EMBED_DATA_SOURCE_DOWNLOAD_ROW_LIMIT).toBe(SQL_EDITOR_EXPORT_ROW_LIMIT); + + const payload = buildDashboardComponentDownloadPayload( + { + id: 'component-1', + dashboardId: 'dashboard-1', + isVisible: true, + name: 'Sales table', + order: 1, + type: 'table', + config: { dataSourceId: 'source-1', dataSourceTableId: 'sales_table' } + } satisfies DashboardElement, + [], + 'csv', + { + downloadSourceId: 'sales_table', + sourceId: 'source-1', + sourceName: 'Sales', + tableId: 'sales_table', + tableName: 'sales_table' + } + ); + + expect(payload.limit).toBe(SQL_EDITOR_EXPORT_ROW_LIMIT); + }); });