Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 3.8k
fix(tables): enforce row limits against the current plan, not a frozen per-table cap#5120
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
4911e60498bd20ce6d4316382f9cc42018e153c49aada0be4c8f4149c2a1b30File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -30,6 +30,7 @@ import { | ||
| csvProxyBodyCapResponse, | ||
| multipartErrorResponse, | ||
| normalizeColumn, | ||
| rowWriteErrorResponse, | ||
| } from '@/app/api/table/utils' | ||
| const logger = createLogger('TableImportCSV') | ||
| @@ -105,12 +106,18 @@ export const POST = withRouteHandler(async (request: NextRequest) => { | ||
| headerToColumn: Map<string, string> | ||
| } | ||
| const insertRows = async (rows: Record<string, unknown>[], state: ImportState) => { | ||
| const insertRows = async ( | ||
| rows: Record<string, unknown>[], | ||
| state: ImportState, | ||
| currentRowCount: number | ||
| ) => { | ||
| if (rows.length === 0) return 0 | ||
| const coerced = coerceRowsForTable(rows, state.schema, state.headerToColumn) | ||
| const result = await batchInsertRows( | ||
| { tableId: state.table.id, rows: coerced, workspaceId, userId }, | ||
| state.table, | ||
| // The created table's rowCount is frozen at 0; pass the running total so the | ||
| // per-batch capacity check sees cumulative rows, not an always-empty table. | ||
| { ...state.table, rowCount: currentRowCount }, | ||
| generateId().slice(0, 8) | ||
| ) | ||
| return result.length | ||
| @@ -132,7 +139,6 @@ export const POST = withRouteHandler(async (request: NextRequest) => { | ||
| schema, | ||
| workspaceId, | ||
| userId, | ||
| maxRows: planLimits.maxRowsPerTable, | ||
| maxTables: planLimits.maxTables, | ||
| }, | ||
| requestId | ||
| @@ -153,13 +159,13 @@ export const POST = withRouteHandler(async (request: NextRequest) => { | ||
| sample.push(record) | ||
| if (sample.length >= CSV_SCHEMA_SAMPLE_SIZE) { | ||
| state = await buildTable(sample) | ||
| inserted += await insertRows(sample, state) | ||
| inserted += await insertRows(sample, state, inserted) | ||
| } | ||
| continue | ||
| } | ||
| batch.push(record) | ||
| if (batch.length >= CSV_MAX_BATCH_SIZE) { | ||
| inserted += await insertRows(batch, state) | ||
| inserted += await insertRows(batch, state, inserted) | ||
| batch = [] | ||
| } | ||
| } | ||
| @@ -169,9 +175,9 @@ export const POST = withRouteHandler(async (request: NextRequest) => { | ||
| return NextResponse.json({ error: 'CSV file has no data rows' }, { status: 400 }) | ||
| } | ||
| state = await buildTable(sample) | ||
| inserted += await insertRows(sample, state) | ||
| inserted += await insertRows(sample, state, inserted) | ||
| } else { | ||
| inserted += await insertRows(batch, state) | ||
| inserted += await insertRows(batch, state, inserted) | ||
cursor[bot] marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| } catch (streamError) { | ||
| if (state) await deleteTable(state.table.id, requestId).catch(() => {}) | ||
| @@ -200,9 +206,13 @@ export const POST = withRouteHandler(async (request: NextRequest) => { | ||
| } catch (error) { | ||
| if (isMultipartError(error)) return multipartErrorResponse(error) | ||
| const message = toError(error).message | ||
| logger.error(`[${requestId}] CSV import failed:`, error) | ||
| // Row-write failures (e.g. the plan row-limit check) map to a 400 with the real reason. | ||
| const rowWriteError = rowWriteErrorResponse(error) | ||
| if (rowWriteError) return rowWriteError | ||
| const message = toError(error).message | ||
| const isClientError = | ||
| message.includes('maximum table limit') || | ||
| message.includes('CSV file has no') || | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -52,12 +52,12 @@ export function rowSelectionCoversAll(sel: RowSelection, rows: TableRowType[]): | ||
| return true | ||
| } | ||
| /** Returns sticky row-number column dimensions sized to the digit count of `maxRows`. */ | ||
| /** Returns sticky row-number column dimensions sized to the digit count of `rowCount`. */ | ||
| export function checkboxColLayout( | ||
| maxRows: number, | ||
| rowCount: number, | ||
| hasWorkflowCols: boolean | ||
| ): { colWidth: number; numRegionWidth: number } { | ||
TheodoreSpeaks marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| const digits = maxRows > 0 ? Math.floor(Math.log10(maxRows)) + 1 : 1 | ||
| const digits = rowCount > 0 ? Math.floor(Math.log10(rowCount)) + 1 : 1 | ||
| const numWidth = Math.max(20, digits * 8 + 4) | ||
| // Region the number/checkbox is centered within (digit width + 12px breathing | ||
| // room, min 32). The select-all header checkbox centers in the same region so it | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.