Skip to content

Commit 07104ef

Browse files
committed
[TILES-V2-9]: store database type in TableContext on frontend (#987)
### TL;DR Removed the explicit database type selection when creating tables, instead using LaunchDarkly flags to determine the database type. ### What changed? - Removed `databaseType` from the `CreateTableInput` in GraphQL schema - Added LaunchDarkly integration to determine database type when creating tables - Added `databaseType` field to `TableMetadata` resolver to expose the database type to clients - Updated tests to mock LaunchDarkly flag values - Enhanced CSV import functionality with database-specific limits: - Increased max file size to 5MB for PostgreSQL (vs 2MB for DynamoDB) - Increased chunk size to 1000 rows for PostgreSQL (vs 100 for DynamoDB) - Added pagination support for table rows with `stringifiedCursor` parameter ### How to test? 1. Create a new table and verify it uses the database type determined by LaunchDarkly 2. Import CSV files of different sizes to verify the database-specific limits 3. Test pagination by loading large tables and checking that rows load correctly
1 parent 6095b85 commit 07104ef

File tree

12 files changed

+113
-61
lines changed

12 files changed

+113
-61
lines changed

packages/backend/src/graphql/__tests__/mutations/tiles/create-table.itest.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
import { beforeEach, describe, expect, it } from 'vitest'
1+
import { beforeEach, describe, expect, it, vi } from 'vitest'
22

33
import createTable from '@/graphql/mutations/tiles/create-table'
44
import { getTableRowCount } from '@/models/tiles/dynamodb/table-row'
55
import Context from '@/types/express/context'
66

77
import { generateMockContext } from './table.mock'
88

9+
vi.mock('@/helpers/launch-darkly', () => ({
10+
getLdFlagValue: vi.fn().mockResolvedValue('ddb'),
11+
}))
12+
913
describe('create table mutation', () => {
1014
let context: Context
1115

@@ -17,7 +21,7 @@ describe('create table mutation', () => {
1721
it('should create a blank table', async () => {
1822
const table = await createTable(
1923
null,
20-
{ input: { name: 'Test Table', isBlank: true, databaseType: 'ddb' } },
24+
{ input: { name: 'Test Table', isBlank: true } },
2125
context,
2226
)
2327
const tableColumnCount = await table.$relatedQuery('columns').resultSize()
@@ -30,7 +34,7 @@ describe('create table mutation', () => {
3034
it('should create a table and with placeholder rows and columns', async () => {
3135
const table = await createTable(
3236
null,
33-
{ input: { name: 'Test Table', isBlank: false, databaseType: 'ddb' } },
37+
{ input: { name: 'Test Table', isBlank: false } },
3438
context,
3539
)
3640
const tableColumnCount = await table.$relatedQuery('columns').resultSize()
@@ -43,13 +47,13 @@ describe('create table mutation', () => {
4347
it('should be able create tables with the same name', async () => {
4448
const table = await createTable(
4549
null,
46-
{ input: { name: 'Test Table', isBlank: false, databaseType: 'ddb' } },
50+
{ input: { name: 'Test Table', isBlank: false } },
4751
context,
4852
)
4953

5054
const table2 = await createTable(
5155
null,
52-
{ input: { name: 'Test Table', isBlank: false, databaseType: 'ddb' } },
56+
{ input: { name: 'Test Table', isBlank: false } },
5357
context,
5458
)
5559
expect(table.name).toBe('Test Table')

packages/backend/src/graphql/custom-resolvers/table-metadata.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import type { Resolvers } from '../__generated__/types.generated'
44

55
type TableMetadataResolver = Resolvers['TableMetadata']
66

7+
const databaseType: TableMetadataResolver['databaseType'] = async (parent) => {
8+
return parent.db
9+
}
10+
711
const columns: TableMetadataResolver['columns'] = async (parent) => {
812
const columns = await parent
913
.$relatedQuery('columns')
@@ -33,4 +37,5 @@ const collaborators: TableMetadataResolver['collaborators'] = async (
3337
export default {
3438
columns,
3539
collaborators,
40+
databaseType,
3641
} satisfies TableMetadataResolver

packages/backend/src/graphql/mutations/tiles/create-table.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import { MutationResolvers } from '@/graphql/__generated__/types.generated'
2+
import { getLdFlagValue } from '@/helpers/launch-darkly'
13
import TableMetadata from '@/models/table-metadata'
24
import { getTableOperations } from '@/models/tiles/factory'
3-
4-
import type { MutationResolvers } from '../../__generated__/types.generated'
5+
import { type DatabaseType } from '@/models/tiles/types'
56

67
const PLACEHOLDER_COLUMNS = [
78
{
@@ -19,21 +20,29 @@ const PLACEHOLDER_COLUMNS = [
1920
]
2021
const PLACEHOLDER_ROWS = new Array(5).fill({})
2122

23+
// DELETE THIS FLAG ONCE IT'S NO LONGER IN USE
24+
const DATABASE_TYPE_LD_FLAG_KEY = 'tiles-database-type'
25+
2226
const createTable: MutationResolvers['createTable'] = async (
2327
_parent,
2428
params,
2529
context,
2630
) => {
27-
const {
28-
name: tableName,
29-
isBlank: isBlankTable,
30-
databaseType = 'ddb',
31-
} = params.input
31+
const { name: tableName, isBlank: isBlankTable } = params.input
3232

3333
if (!tableName) {
3434
throw new Error('Table name is required')
3535
}
3636

37+
/**
38+
* Check which database type user is allowed to create
39+
*/
40+
const databaseType = (await getLdFlagValue(
41+
DATABASE_TYPE_LD_FLAG_KEY,
42+
context.currentUser.email,
43+
'ddb',
44+
)) as DatabaseType
45+
3746
const tableOperations = getTableOperations(databaseType)
3847

3948
const table = await TableMetadata.transaction(async (trx) => {

packages/backend/src/graphql/queries/tiles/get-all-rows.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const getAllRows: QueryResolvers['getAllRows'] = async (
1515
params,
1616
context,
1717
) => {
18-
const { tableId } = params
18+
const { tableId, stringifiedCursor } = params
1919

2020
try {
2121
const table = context.tilesViewKey
@@ -43,10 +43,15 @@ const getAllRows: QueryResolvers['getAllRows'] = async (
4343

4444
const columnIds = table.columns.map((column) => column.id)
4545

46-
const { rows } = await tableOperations.getTableRows({
47-
tableId,
48-
columnIds,
49-
})
46+
const { rows, stringifiedCursor: newStringifiedCursor } =
47+
await tableOperations.getTableRows({
48+
tableId,
49+
columnIds,
50+
// If table is postgres, we set pagination limit
51+
// If table is dynamodb, the pagination size is based on dynamodb query limits
52+
scanLimit: table.db === 'pg' ? 10000 : undefined,
53+
stringifiedCursor,
54+
})
5055

5156
// Convert data object to csv to minimize payload size
5257
rows.forEach((row) => {
@@ -56,6 +61,7 @@ const getAllRows: QueryResolvers['getAllRows'] = async (
5661
return {
5762
rows,
5863
columnIds,
64+
stringifiedCursor: newStringifiedCursor,
5965
}
6066
} catch (e) {
6167
logger.error(e)

packages/backend/src/graphql/schema.graphql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -749,7 +749,6 @@ enum DatabaseType {
749749
input CreateTableInput {
750750
name: String!
751751
isBlank: Boolean!
752-
databaseType: DatabaseType
753752
}
754753

755754
input TableColumnConfigInput {
@@ -804,6 +803,7 @@ input DeleteTableCollaboratorInput {
804803

805804
type TableMetadata {
806805
id: ID!
806+
databaseType: DatabaseType!
807807
name: String!
808808
columns: [TableColumnMetadata!]
809809
lastAccessedAt: String!

packages/backend/src/models/tiles/pg/table-row-functions.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ export const getTableRows = async ({
221221
filters,
222222
order = 'asc',
223223
scanLimit,
224+
stringifiedCursor,
224225
}: {
225226
tableId: string
226227
columnIds?: string[]
@@ -230,8 +231,10 @@ export const getTableRows = async ({
230231
* Optional limit on the total number of rows scanned.
231232
*/
232233
scanLimit?: number
234+
stringifiedCursor?: string
233235
}): Promise<{
234236
rows: TableRowOutput[]
237+
stringifiedCursor: string | null
235238
}> => {
236239
const query = tilesClient(tableId).select(['rowId', ...(columnIds ?? [])])
237240
if (filters) {
@@ -240,6 +243,10 @@ export const getTableRows = async ({
240243
if (scanLimit) {
241244
query.limit(scanLimit)
242245
}
246+
const offset = stringifiedCursor ? +stringifiedCursor : 0
247+
if (offset) {
248+
query.offset(offset)
249+
}
243250
try {
244251
const tableRows = []
245252
const stream = query.orderBy('rowId', order).stream()
@@ -249,6 +256,8 @@ export const getTableRows = async ({
249256
}
250257
return {
251258
rows: tableRows,
259+
stringifiedCursor:
260+
tableRows.length === scanLimit ? (offset + scanLimit).toString() : null,
252261
}
253262
} catch (e: unknown) {
254263
logger.error(e)

packages/frontend/src/graphql/queries/tiles/get-table.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export const GET_TABLE = gql`
66
id
77
name
88
viewOnlyKey
9+
databaseType
910
columns {
1011
id
1112
name

packages/frontend/src/pages/Tile/components/TableBanner/ImportCsvButton.tsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import Papa, { ParseMeta, ParseResult } from 'papaparse'
2929
import { SetRequired } from 'type-fest'
3030

3131
import PrimarySpinner from '@/components/PrimarySpinner'
32+
import { DatabaseType } from '@/graphql/__generated__/graphql'
3233
import { CREATE_ROWS } from '@/graphql/mutations/tiles/create-rows'
3334
import { GET_TABLE } from '@/graphql/queries/tiles/get-table'
3435

@@ -46,10 +47,16 @@ type IMPORT_STATUS =
4647
| 'completed'
4748
| 'error'
4849

49-
// 2 MB in bytes
50-
const MAX_FILE_SIZE = 2 * 1000 * 1000
50+
// 5 MB in bytes
51+
const MAX_FILE_SIZE = {
52+
[DatabaseType.Pg]: 5 * 1000 * 1000,
53+
[DatabaseType.Ddb]: 2 * 1000 * 1000,
54+
}
5155
// Add row chunk size
52-
const CHUNK_SIZE = 100
56+
const CHUNK_SIZE = {
57+
[DatabaseType.Pg]: 1000,
58+
[DatabaseType.Ddb]: 100,
59+
}
5360

5461
const ImportStatus = ({
5562
columnsToCreate,
@@ -134,7 +141,7 @@ export const ImportCsvModalContent = ({
134141
onPostImport?: () => void
135142
onBack?: () => void
136143
}) => {
137-
const { tableId, tableColumns, refetch } = useTableContext()
144+
const { tableId, tableColumns, refetch, databaseType } = useTableContext()
138145
const { createColumns } = useUpdateTable()
139146
const [createRows] = useMutation(CREATE_ROWS)
140147
const [getTableData] = useLazyQuery<{
@@ -255,7 +262,7 @@ export const ImportCsvModalContent = ({
255262
setImportStatus('importing')
256263
setRowsToImport(mappedData.length)
257264
setRowsImported(0)
258-
const chunkedData = chunk(mappedData, CHUNK_SIZE)
265+
const chunkedData = chunk(mappedData, CHUNK_SIZE[databaseType])
259266

260267
for (let i = 0; i < chunkedData.length; i++) {
261268
await createRows({
@@ -269,7 +276,7 @@ export const ImportCsvModalContent = ({
269276
if (i === chunkedData.length - 1 && !onPreImport) {
270277
await refetch()
271278
}
272-
setRowsImported((i + 1) * CHUNK_SIZE)
279+
setRowsImported((i + 1) * CHUNK_SIZE[databaseType])
273280
}
274281
}
275282
setImportStatus('completed')
@@ -291,6 +298,7 @@ export const ImportCsvModalContent = ({
291298
onPreImport,
292299
refetch,
293300
result,
301+
databaseType,
294302
tableColumns,
295303
tableId,
296304
])
@@ -317,7 +325,7 @@ export const ImportCsvModalContent = ({
317325
existing values.
318326
</Text>
319327
<Attachment
320-
maxSize={MAX_FILE_SIZE}
328+
maxSize={MAX_FILE_SIZE[databaseType]}
321329
onChange={setFile}
322330
title="Upload CSV"
323331
name="file-upload"

packages/frontend/src/pages/Tile/contexts/TableContext.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,15 @@ import React, {
1313
useState,
1414
} from 'react'
1515

16+
import { DatabaseType } from '@/graphql/__generated__/graphql'
17+
1618
import { flattenRows } from '../helpers/flatten-rows'
1719
import { EditMode, GenericRowData } from '../types'
1820

1921
interface TableContextProps {
2022
tableId: string
2123
tableName: string
24+
databaseType: DatabaseType
2225
flattenedData: GenericRowData[]
2326
tableColumns: ITableColumnMetadata[]
2427
filteredDataRef: MutableRefObject<GenericRowData[]>
@@ -49,6 +52,7 @@ export const useTableContext = () => {
4952
interface TableContextProviderProps {
5053
tableId: string
5154
tableName: string
55+
databaseType: DatabaseType
5256
tableColumns: ITableColumnMetadata[]
5357
tableRows: ITableRow[]
5458
children: React.ReactNode
@@ -64,6 +68,7 @@ interface TableContextProviderProps {
6468
export const TableContextProvider = ({
6569
tableId,
6670
tableName,
71+
databaseType,
6772
tableColumns,
6873
tableRows,
6974
children,
@@ -86,6 +91,7 @@ export const TableContextProvider = ({
8691
value={{
8792
tableId,
8893
tableName,
94+
databaseType,
8995
flattenedData,
9096
tableColumns,
9197
allDataRef,

packages/frontend/src/pages/Tile/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,14 @@ export default function Tile(): JSX.Element | null {
8080
return null
8181
}
8282

83-
const { id, name, columns, viewOnlyKey, collaborators } =
83+
const { id, name, columns, viewOnlyKey, collaborators, databaseType } =
8484
getTableData.getTable
8585

8686
return (
8787
<TableContextProvider
8888
tableName={name}
8989
tableId={id}
90+
databaseType={databaseType}
9091
tableColumns={columns}
9192
tableRows={rows}
9293
viewOnlyKey={viewOnlyKey}

0 commit comments

Comments
 (0)