Skip to content

Commit 57b6dc2

Browse files
committed
feat: show num pipes connected to a tile
1 parent 531c736 commit 57b6dc2

File tree

8 files changed

+369
-64
lines changed

8 files changed

+369
-64
lines changed

packages/backend/src/graphql/__tests__/mutations/tiles/table.mock.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { randomUUID } from 'crypto'
22

3+
import Step from '@/models/step'
34
import TableCollaborator from '@/models/table-collaborators'
45
import TableColumnMetadata from '@/models/table-column-metadata'
56
import TableMetadata from '@/models/table-metadata'
@@ -52,6 +53,56 @@ export async function generateMockTable({
5253
}
5354
}
5455

56+
// generate mock flow including steps
57+
export async function generateMockFlow({
58+
userId,
59+
tableId,
60+
numSteps,
61+
}: {
62+
userId: string
63+
tableId: string
64+
numSteps: number
65+
}) {
66+
const currentUser = await User.query().findById(userId)
67+
68+
const tableName = `test-flow-${tableId}`
69+
const flowRes = await currentUser.$relatedQuery('flows').insert({
70+
name: tableName,
71+
})
72+
const flowId = flowRes.id
73+
74+
for (let i = 0; i < numSteps; i++) {
75+
await generateMockStep({
76+
tableId,
77+
flowId,
78+
position: i + 2,
79+
})
80+
}
81+
}
82+
83+
export async function generateMockStep({
84+
tableId,
85+
flowId,
86+
position,
87+
}: {
88+
tableId: string
89+
flowId: string
90+
position?: number
91+
}) {
92+
await Step.query().insert({
93+
key: 'createTileRow',
94+
appKey: 'tiles',
95+
type: 'action',
96+
parameters: {
97+
rowData: [],
98+
tableId: tableId,
99+
},
100+
flowId: flowId,
101+
status: 'incomplete',
102+
position: position,
103+
})
104+
}
105+
55106
export async function generateMockTableColumns({
56107
tableId,
57108
numColumns = 5,
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import { beforeEach, describe, expect, it } from 'vitest'
2+
3+
import getTableConnections from '@/graphql/queries/tiles/get-table-connections'
4+
import getTables from '@/graphql/queries/tiles/get-tables'
5+
import Context from '@/types/express/context'
6+
7+
import {
8+
generateMockContext,
9+
generateMockFlow,
10+
generateMockTable,
11+
} from '../../mutations/tiles/table.mock'
12+
13+
interface TablePipeCountObj {
14+
[key: string]: number
15+
}
16+
17+
function getRandNum() {
18+
return Math.floor(Math.random() * 5) + 1
19+
}
20+
21+
describe('get table connections query', () => {
22+
let context: Context
23+
24+
beforeEach(async () => {
25+
context = await generateMockContext()
26+
})
27+
28+
it('should return empty object if no tables found', async () => {
29+
const { edges } = await getTables(null, { limit: 10, offset: 0 }, context)
30+
const tables = edges.map((edge) => edge.node)
31+
expect(tables).toHaveLength(0)
32+
33+
const tableConnections = await getTableConnections(
34+
null,
35+
{ limit: 10, offset: 0 },
36+
context,
37+
)
38+
expect(tableConnections).toEqual({})
39+
expect(Object.keys(tableConnections).length).toBe(0)
40+
})
41+
42+
it('should return the correct number of table connections for user flows only', async () => {
43+
const numTables = 5
44+
const tablePipeCount: TablePipeCountObj = {}
45+
for (let i = 0; i < numTables; i++) {
46+
const res = await generateMockTable({ userId: context.currentUser.id })
47+
const { id: tableId } = res.table
48+
49+
const numFlows = Math.floor(Math.random() * 5) + 1
50+
const numSteps = Math.floor(Math.random() * 5) + 1
51+
tablePipeCount[tableId] = numFlows
52+
53+
for (let i = 0; i < numFlows; i++) {
54+
await generateMockFlow({
55+
userId: context.currentUser.id,
56+
tableId,
57+
numSteps,
58+
})
59+
}
60+
}
61+
62+
// tests each page
63+
const limit = 2
64+
for (let i = 0; i < Math.ceil(numTables / 2); i++) {
65+
const offset = limit * i
66+
const { edges, pageInfo } = await getTables(
67+
null,
68+
{ limit, offset },
69+
context,
70+
)
71+
72+
const pageTables = edges.map((edge) => edge.node)
73+
const expectedLength = Math.min(limit, numTables - limit * i)
74+
expect(pageTables).toHaveLength(expectedLength)
75+
expect(pageInfo.currentPage).toBe(i + 1)
76+
expect(pageInfo.totalCount).toBe(numTables)
77+
78+
const pageTableIds = edges.map((edge) => edge.node.id)
79+
80+
const tableConnections = await getTableConnections(
81+
null,
82+
{ limit, offset },
83+
context,
84+
)
85+
expect(Object.keys(tableConnections).length).toBe(expectedLength)
86+
expect(Object.keys(tableConnections).sort()).toEqual(pageTableIds.sort())
87+
88+
// Object.keys(tableConnections).forEach()
89+
Object.entries(tableConnections).forEach(([key, value]) => {
90+
expect(value).toBe(tablePipeCount[key])
91+
})
92+
}
93+
})
94+
95+
it('should return the correct number of table connections for flows by user and editor', async () => {
96+
const numTables = 5
97+
const tablePipeCount: TablePipeCountObj = {}
98+
for (let i = 0; i < numTables; i++) {
99+
const res = await generateMockTable({ userId: context.currentUser.id })
100+
const { id: tableId } = res.table
101+
const { id: editorId } = res.editor
102+
103+
const [numFlows, numSteps] = [getRandNum(), getRandNum()]
104+
const [editorFlows, editorSteps] = [getRandNum(), getRandNum()]
105+
106+
tablePipeCount[tableId] = numFlows + editorFlows
107+
for (let i = 0; i < numFlows; i++) {
108+
await generateMockFlow({
109+
userId: context.currentUser.id,
110+
tableId,
111+
numSteps,
112+
})
113+
}
114+
for (let i = 0; i < editorFlows; i++) {
115+
await generateMockFlow({
116+
userId: editorId,
117+
tableId,
118+
numSteps: editorSteps,
119+
})
120+
}
121+
}
122+
123+
// tests each page
124+
const limit = 2
125+
for (let i = 0; i < Math.ceil(numTables / 2); i++) {
126+
const offset = limit * i
127+
const { edges, pageInfo } = await getTables(
128+
null,
129+
{ limit, offset },
130+
context,
131+
)
132+
133+
const pageTables = edges.map((edge) => edge.node)
134+
const expectedLength = Math.min(limit, numTables - limit * i)
135+
expect(pageTables).toHaveLength(expectedLength)
136+
expect(pageInfo.currentPage).toBe(i + 1)
137+
expect(pageInfo.totalCount).toBe(numTables)
138+
139+
const pageTableIds = edges.map((edge) => edge.node.id)
140+
141+
const tableConnections = await getTableConnections(
142+
null,
143+
{ limit, offset },
144+
context,
145+
)
146+
expect(Object.keys(tableConnections).length).toBe(expectedLength)
147+
expect(Object.keys(tableConnections).sort()).toEqual(pageTableIds.sort())
148+
149+
// Object.keys(tableConnections).forEach()
150+
Object.entries(tableConnections).forEach(([key, value]) => {
151+
expect(value).toBe(tablePipeCount[key])
152+
})
153+
}
154+
})
155+
})
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import Flow from '@/models/flow'
2+
3+
import type { QueryResolvers } from '../../__generated__/types.generated'
4+
5+
import getTables from './get-tables'
6+
7+
interface ExtendedFlow extends Flow {
8+
tableid: string
9+
}
10+
11+
interface TableConnection {
12+
[key: string]: number
13+
}
14+
15+
const getTableConnections: QueryResolvers['getTableConnections'] = async (
16+
_parent,
17+
{ limit, offset, name },
18+
context,
19+
) => {
20+
// get tables of currentUser in the current page
21+
const tables = await getTables(_parent, { limit, offset, name }, context)
22+
23+
if (tables.edges.length === 0) {
24+
return {}
25+
}
26+
const tableIds = tables.edges.map((t) => t.node.id)
27+
const tableIdStr = tableIds.map((id) => `'${id}'`).join(', ')
28+
29+
// get distinct rows of tables used in flows
30+
// returns flow id and table id
31+
const distinctTableFlows = await Flow.query()
32+
.distinct('flows.id', Flow.raw("steps.parameters ->> 'tableId' AS tableId"))
33+
.innerJoinRelated('steps')
34+
.innerJoinRelated('user')
35+
.where('steps.app_key', 'tiles')
36+
.whereRaw("steps.parameters ->> 'tableId' IS NOT NULL")
37+
.whereRaw(`steps.parameters ->> 'tableId' IN (${tableIdStr})`)
38+
39+
const result = distinctTableFlows.reduce(
40+
(acc: TableConnection, obj: ExtendedFlow) => {
41+
acc[obj.tableid] = (acc[obj.tableid] || 0) + 1
42+
return acc
43+
},
44+
{},
45+
)
46+
47+
return result
48+
}
49+
50+
export default getTableConnections

packages/backend/src/graphql/queries/tiles/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ import type { QueryResolvers } from '../../__generated__/types.generated'
22

33
import getAllRows from './get-all-rows'
44
import getTable from './get-table'
5+
import getTableConnections from './get-table-connections'
56
import getTables from './get-tables'
67

7-
export default { getTable, getTables, getAllRows } satisfies QueryResolvers
8+
export default {
9+
getTable,
10+
getTableConnections,
11+
getTables,
12+
getAllRows,
13+
} satisfies QueryResolvers

packages/backend/src/graphql/schema.graphql

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,8 @@ type Query {
3636
): [JSONObject]
3737
# Tiles
3838
getTable(tableId: String!): TableMetadata!
39-
getTables(
40-
limit: Int!
41-
offset: Int!
42-
name: String
43-
): PaginatedTables!
39+
getTableConnections(limit: Int!, offset: Int!, name: String): JSONObject
40+
getTables(limit: Int!, offset: Int!, name: String): PaginatedTables!
4441
# Tiles rows
4542
getAllRows(tableId: String!): Any!
4643
getCurrentUser: User
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { gql } from '@apollo/client'
2+
3+
export const GET_TABLE_CONNECTIONS = gql`
4+
query GetTableConnections($limit: Int!, $offset: Int!, $name: String) {
5+
getTableConnections(limit: $limit, offset: $offset, name: $name)
6+
}
7+
`

0 commit comments

Comments
 (0)