Skip to content

Commit 648198d

Browse files
committed
refactor: use tableIds as input to get table conn
1 parent a0d5b15 commit 648198d

File tree

4 files changed

+144
-41
lines changed

4 files changed

+144
-41
lines changed

packages/backend/src/graphql/__tests__/queries/tiles/get-table-connections.itest.ts

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,20 @@ describe('get table connections query', () => {
3333

3434
const tableConnections = await getTableConnections(
3535
null,
36-
{ limit: 10, offset: 0 },
36+
{ tableIds: [] },
37+
context,
38+
)
39+
expect(tableConnections).toEqual({})
40+
expect(Object.keys(tableConnections).length).toBe(0)
41+
})
42+
43+
it('should return empty object if user has no access to any tables', async () => {
44+
const numTables = 5
45+
const testIds = Array.from({ length: numTables }, () => crypto.randomUUID())
46+
47+
const tableConnections = await getTableConnections(
48+
null,
49+
{ tableIds: testIds },
3750
context,
3851
)
3952
expect(tableConnections).toEqual({})
@@ -79,13 +92,12 @@ describe('get table connections query', () => {
7992

8093
const tableConnections = await getTableConnections(
8194
null,
82-
{ limit, offset },
95+
{ tableIds: pageTableIds },
8396
context,
8497
)
8598
expect(Object.keys(tableConnections).length).toBe(expectedLength)
8699
expect(Object.keys(tableConnections).sort()).toEqual(pageTableIds.sort())
87100

88-
// Object.keys(tableConnections).forEach()
89101
Object.entries(tableConnections).forEach(([key, value]) => {
90102
expect(value).toBe(tablePipeCount[key])
91103
})
@@ -140,16 +152,77 @@ describe('get table connections query', () => {
140152

141153
const tableConnections = await getTableConnections(
142154
null,
143-
{ limit, offset },
155+
{ tableIds: pageTableIds },
144156
context,
145157
)
146158
expect(Object.keys(tableConnections).length).toBe(expectedLength)
147159
expect(Object.keys(tableConnections).sort()).toEqual(pageTableIds.sort())
148160

149-
// Object.keys(tableConnections).forEach()
150161
Object.entries(tableConnections).forEach(([key, value]) => {
151162
expect(value).toBe(tablePipeCount[key])
152163
})
153164
}
154165
})
166+
167+
it('should not return table connections for tables the user does not have access to', async () => {
168+
const numTables = 5
169+
const tablePipeCount: TablePipeCountObj = {}
170+
for (let i = 0; i < numTables; i++) {
171+
const res = await generateMockTable({ userId: context.currentUser.id })
172+
const { id: tableId } = res.table
173+
const { id: editorId } = res.editor
174+
175+
const [numFlows, numSteps] = [getRandNum(), getRandNum()]
176+
const [editorFlows, editorSteps] = [getRandNum(), getRandNum()]
177+
178+
tablePipeCount[tableId] = numFlows + editorFlows
179+
for (let i = 0; i < numFlows; i++) {
180+
await generateMockFlow({
181+
userId: context.currentUser.id,
182+
tableId,
183+
numSteps,
184+
})
185+
}
186+
for (let i = 0; i < editorFlows; i++) {
187+
await generateMockFlow({
188+
userId: editorId,
189+
tableId,
190+
numSteps: editorSteps,
191+
})
192+
}
193+
}
194+
195+
// test as a whole
196+
const { edges, pageInfo } = await getTables(
197+
null,
198+
{ limit: 10, offset: 0 },
199+
context,
200+
)
201+
202+
const pageTables = edges.map((edge) => edge.node)
203+
expect(pageTables).toHaveLength(numTables)
204+
expect(pageInfo.currentPage).toBe(1)
205+
expect(pageInfo.totalCount).toBe(numTables)
206+
207+
const pageTableIds = edges.map((edge) => edge.node.id)
208+
const testIds = [...pageTableIds, crypto.randomUUID()] // add a random table id
209+
expect(testIds).toHaveLength(numTables + 1)
210+
const tableConnections = await getTableConnections(
211+
null,
212+
{ tableIds: testIds },
213+
context,
214+
)
215+
expect(Object.keys(tableConnections).length).toBe(numTables)
216+
expect(Object.keys(tableConnections).sort()).toEqual(pageTableIds.sort())
217+
218+
Object.entries(tableConnections).forEach(([key, value]) => {
219+
expect(value).toBe(tablePipeCount[key])
220+
})
221+
})
222+
223+
it('should throw error if tableIds is null', async () => {
224+
await expect(
225+
getTableConnections(null, { tableIds: null }, context),
226+
).rejects.toThrow('tableIds is required')
227+
})
155228
})
Lines changed: 50 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
import { raw } from 'objection'
2+
3+
import logger from '@/helpers/logger'
14
import Flow from '@/models/flow'
5+
import Step from '@/models/step'
6+
import TableCollaborator from '@/models/table-collaborators'
27

38
import type { QueryResolvers } from '../../__generated__/types.generated'
49

5-
import getTables from './get-tables'
6-
710
interface ExtendedFlow extends Flow {
811
tableid: string
912
count: number
@@ -15,39 +18,55 @@ interface TableConnection {
1518

1619
const getTableConnections: QueryResolvers['getTableConnections'] = async (
1720
_parent,
18-
{ limit, offset, name },
21+
params,
1922
context,
2023
) => {
21-
// get tables of currentUser in the current page
22-
const tables = await getTables(_parent, { limit, offset, name }, context)
24+
const { tableIds } = params
25+
if (!tableIds) {
26+
throw new Error('tableIds is required')
27+
}
28+
29+
try {
30+
// get distinct rows of tables used in flows
31+
// returns flow id and table id
32+
// MONITOR (ogp-kevin): add index on steps.parameters->>'tableId' if query is slow
33+
const distinctTableFlows = await Flow.query()
34+
.innerJoin(
35+
Step.query()
36+
.as('tileSteps')
37+
.select(
38+
raw("steps.parameters->>'tableId' AS tableid"),
39+
'steps.flow_id',
40+
)
41+
.andWhere('steps.app_key', 'tiles')
42+
.whereNotNull(raw("steps.parameters->>'tableId'"))
43+
.whereIn(
44+
raw("steps.parameters->>'tableId'"),
45+
TableCollaborator.query()
46+
.select(raw('"table_id"::TEXT AS table_id')) // Cast to TEXT, steps.parameters is JSONB
47+
.whereIn('table_id', tableIds)
48+
.where('user_id', context.currentUser.id),
49+
), // Only include tables that the user has access to
50+
'flows.id',
51+
'tileSteps.flow_id',
52+
)
53+
.select('tileSteps.tableid')
54+
.countDistinct('flows.id')
55+
.groupBy('tileSteps.tableid')
56+
57+
const result = distinctTableFlows.reduce(
58+
(acc: TableConnection, row: ExtendedFlow) => {
59+
acc[row.tableid] = row.count
60+
return acc
61+
},
62+
{},
63+
)
2364

24-
if (tables.edges.length === 0) {
25-
return {}
65+
return result
66+
} catch (e) {
67+
logger.error(e)
68+
throw new Error('Error fetching table connections')
2669
}
27-
const tableIds = tables.edges.map((t) => t.node.id)
28-
const tableIdStr = tableIds.map((id) => `'${id}'`).join(', ')
29-
30-
// get distinct rows of tables used in flows
31-
// returns flow id and table id
32-
const distinctTableFlows = await Flow.query()
33-
.select(Flow.raw("steps.parameters ->> 'tableId' AS tableid"))
34-
.countDistinct('flows.id')
35-
.innerJoinRelated('steps')
36-
.innerJoinRelated('user')
37-
.where('steps.app_key', 'tiles')
38-
.whereRaw("steps.parameters ->> 'tableId' IS NOT NULL")
39-
.whereRaw(`steps.parameters ->> 'tableId' IN (${tableIdStr})`)
40-
.groupByRaw("steps.parameters ->> 'tableId'")
41-
42-
const result = distinctTableFlows.reduce(
43-
(acc: TableConnection, row: ExtendedFlow) => {
44-
acc[row.tableid] = row.count
45-
return acc
46-
},
47-
{},
48-
)
49-
50-
return result
5170
}
5271

5372
export default getTableConnections
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { gql } from '@apollo/client'
22

33
export const GET_TABLE_CONNECTIONS = gql`
4-
query GetTableConnections($limit: Int!, $offset: Int!, $name: String) {
5-
getTableConnections(limit: $limit, offset: $offset, name: $name)
4+
query GetTableConnections($tableIds: [String!]!) {
5+
getTableConnections(tableIds: $tableIds)
66
}
77
`

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,19 @@ export default function Tiles(): JSX.Element {
8787
variables: queryVars,
8888
})
8989

90-
const { pageInfo, edges } = data?.getTables ?? {}
91-
const tilesToDisplay = edges?.map(({ node }) => node) ?? []
90+
const { pageInfo, edges = [] } = data?.getTables ?? {}
91+
92+
const { tilesToDisplay, tableIds } = edges.reduce<{
93+
tilesToDisplay: TableMetadata[]
94+
tableIds: string[]
95+
}>(
96+
(acc, { node }) => {
97+
acc.tilesToDisplay.push(node)
98+
acc.tableIds.push(node.id)
99+
return acc
100+
},
101+
{ tilesToDisplay: [], tableIds: [] },
102+
)
92103

93104
const hasNoUserTiles = tilesToDisplay.length === 0 && !isSearching
94105
const totalCount: number = pageInfo?.totalCount ?? 0
@@ -97,7 +108,7 @@ export default function Tiles(): JSX.Element {
97108
const { data: connectionsData, loading: isConnectionsLoading } = useQuery<{
98109
getTableConnections: JSON
99110
}>(GET_TABLE_CONNECTIONS, {
100-
variables: queryVars,
111+
variables: { tableIds },
101112
skip: hasNoUserTiles,
102113
})
103114
const tileConnections = connectionsData?.getTableConnections ?? {}

0 commit comments

Comments
 (0)