Skip to content

Commit aa75505

Browse files
committed
test: memoizing app list
1 parent ecd01d2 commit aa75505

File tree

6 files changed

+95
-126
lines changed

6 files changed

+95
-126
lines changed

packages/backend/src/apps/__tests__/index.test.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,7 @@ import fs from 'fs'
22
import { join } from 'path'
33
import { describe, expect, it } from 'vitest'
44

5-
import exportedApps from '@/apps'
6-
import {
7-
ACTION_APPS_RANKING,
8-
TRIGGER_APPS_RANKING,
9-
} from '@/graphql/queries/get-apps'
5+
import exportedApps, { ACTION_APPS_RANKING, TRIGGER_APPS_RANKING } from '@/apps'
106

117
describe('index.ts', () => {
128
it('should export all apps keys that also match their folder names', () => {

packages/backend/src/apps/index.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,38 @@ const apps: Record<string, IApp> = {
4444
[gathersgApp.key]: gathersgApp,
4545
}
4646

47+
/**
48+
* Note: Remember to add the priority of the app here whenever a new app
49+
* is created, this is to determine which app dropdown option appears first.
50+
* Note that triggers and actions have separate rankings!
51+
* Triggers: formsg, scheduler, webhook
52+
* Actions: email by postman, tiles, m365, toolbox, formatter, calculator, delay,
53+
* paysg, lettersg, sms by postman, telegram, slack, custom-api, vault, twilio
54+
*/
55+
56+
export const TRIGGER_APPS_RANKING = [
57+
formsgApp.key,
58+
schedulerApp.key,
59+
webhookApp.key,
60+
]
61+
export const ACTION_APPS_RANKING = [
62+
postmanApp.key,
63+
tilesApp.key,
64+
m365ExcelApp.key,
65+
toolboxApp.key,
66+
formatterApp.key,
67+
calculatorApp.key,
68+
delayApp.key,
69+
paysgApp.key,
70+
lettersgApp.key,
71+
postmanSmsApp.key,
72+
telegramBotApp.key,
73+
slackApp.key,
74+
aisayApp.key,
75+
gathersgApp.key,
76+
customApiApp.key,
77+
vaultWorkspaceApp.key,
78+
twilioApp.key,
79+
]
80+
4781
export default apps

packages/backend/src/graphql/queries/get-apps.ts

Lines changed: 36 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,46 @@
11
import type { IApp } from '@plumber/types'
22

3-
import aisayApp from '@/apps/aisay'
4-
import calculatorApp from '@/apps/calculator'
5-
import customApiApp from '@/apps/custom-api'
6-
import delayApp from '@/apps/delay'
7-
import formatterApp from '@/apps/formatter'
8-
import formsgApp from '@/apps/formsg/'
9-
import gathersgApp from '@/apps/gathersg'
10-
import lettersgApp from '@/apps/lettersg'
11-
import m365ExcelApp from '@/apps/m365-excel'
12-
import paysgApp from '@/apps/paysg'
13-
import postmanApp from '@/apps/postman'
14-
import postmanSmsApp from '@/apps/postman-sms'
15-
import schedulerApp from '@/apps/scheduler'
16-
import slackApp from '@/apps/slack'
17-
import telegramBotApp from '@/apps/telegram-bot'
18-
import tilesApp from '@/apps/tiles'
19-
import toolboxApp from '@/apps/toolbox'
20-
import twilioApp from '@/apps/twilio'
21-
import vaultWorkspaceApp from '@/apps/vault-workspace'
22-
import webhookApp from '@/apps/webhook'
3+
import { memoize } from 'lodash'
4+
5+
import { ACTION_APPS_RANKING, TRIGGER_APPS_RANKING } from '@/apps'
236
import App from '@/models/app'
247

258
import type { QueryResolvers } from '../__generated__/types.generated'
269

27-
/**
28-
* Note: Remember to add the priority of the app here whenever a new app
29-
* is created, this is to determine which app dropdown option appears first.
30-
* Note that triggers and actions have separate rankings!
31-
* Triggers: formsg, scheduler, webhook
32-
* Actions: email by postman, tiles, m365, toolbox, formatter, calculator, delay,
33-
* paysg, lettersg, sms by postman, telegram, slack, custom-api, vault, twilio
34-
*/
35-
36-
export const TRIGGER_APPS_RANKING = [
37-
formsgApp.key,
38-
schedulerApp.key,
39-
webhookApp.key,
40-
]
41-
export const ACTION_APPS_RANKING = [
42-
postmanApp.key,
43-
tilesApp.key,
44-
m365ExcelApp.key,
45-
toolboxApp.key,
46-
formatterApp.key,
47-
calculatorApp.key,
48-
delayApp.key,
49-
paysgApp.key,
50-
lettersgApp.key,
51-
postmanSmsApp.key,
52-
telegramBotApp.key,
53-
slackApp.key,
54-
aisayApp.key,
55-
gathersgApp.key,
56-
customApiApp.key,
57-
vaultWorkspaceApp.key,
58-
twilioApp.key,
59-
]
60-
61-
function sortApps(apps: IApp[]): IApp[] {
62-
// trade off for increased time complexity but easier to add a new app to the ranking
63-
return apps.sort((a, b) => {
64-
const firstPriority = a.triggers
65-
? TRIGGER_APPS_RANKING.findIndex((app) => app === a.key)
66-
: ACTION_APPS_RANKING.findIndex((app) => app === a.key)
67-
const secondPriority = b.triggers
68-
? TRIGGER_APPS_RANKING.findIndex((app) => app === b.key)
69-
: ACTION_APPS_RANKING.findIndex((app) => app === b.key)
70-
71-
// sort by newApp flag, followed by priority
72-
if (a.isNewApp && b.isNewApp) {
10+
const getSortedApps = memoize(
11+
async (): Promise<IApp[]> => {
12+
const apps = await App.findAll()
13+
14+
console.log('no cache hit')
15+
// trade off for increased time complexity but easier to add a new app to the ranking
16+
return apps.sort((a, b) => {
17+
const firstPriority = a.triggers
18+
? TRIGGER_APPS_RANKING.findIndex((app) => app === a.key)
19+
: ACTION_APPS_RANKING.findIndex((app) => app === a.key)
20+
const secondPriority = b.triggers
21+
? TRIGGER_APPS_RANKING.findIndex((app) => app === b.key)
22+
: ACTION_APPS_RANKING.findIndex((app) => app === b.key)
23+
24+
// sort by newApp flag, followed by priority
25+
if (a.isNewApp && b.isNewApp) {
26+
return firstPriority - secondPriority
27+
}
28+
if (a.isNewApp) {
29+
return -1
30+
}
31+
if (b.isNewApp) {
32+
return 1
33+
}
7334
return firstPriority - secondPriority
74-
}
75-
if (a.isNewApp) {
76-
return -1
77-
}
78-
if (b.isNewApp) {
79-
return 1
80-
}
81-
return firstPriority - secondPriority
82-
})
83-
}
84-
85-
const getApps: QueryResolvers['getApps'] = async (_parent, params) => {
86-
const apps = sortApps(await App.findAll(params.name))
87-
if (params.onlyWithTriggers) {
88-
return apps.filter((app) => app.triggers?.length)
89-
}
90-
91-
if (params.onlyWithActions) {
92-
return apps.filter((app) => app.actions?.length)
93-
}
94-
35+
})
36+
},
37+
() => 'getApps',
38+
)
39+
40+
const getApps: QueryResolvers['getApps'] = async () => {
41+
console.time('getApps')
42+
const apps = await getSortedApps()
43+
console.timeEnd('getApps')
9544
return apps
9645
}
9746

packages/backend/src/graphql/schema.graphql

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
type Query {
2-
getApps(
3-
name: String
4-
onlyWithTriggers: Boolean
5-
onlyWithActions: Boolean
6-
): [App]
2+
getApps: [App]
73
getApp(key: String!): App
84
getConnectedApps: [App]
95
testConnection(connectionId: String!, flowId: String): TestConnectionResult

packages/backend/src/models/app.ts

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,36 +9,38 @@ import getApp from '@/helpers/get-app'
99
class App {
1010
static list = Object.keys(apps)
1111

12-
static async findAll(name?: string, stripFuncs = true): Promise<IApp[]> {
13-
if (!name) {
12+
static findAll = memoize(
13+
async (stripFuncs = true): Promise<IApp[]> => {
1414
return Promise.all(
1515
this.list.map(
1616
async (name) => await this.findOneByName(name, stripFuncs),
1717
),
1818
)
19-
}
19+
},
20+
(stripFuncs = true) => `findAll-${stripFuncs}`,
21+
)
2022

21-
return Promise.all(
22-
this.list
23-
.filter((app) => app.includes(name.toLowerCase()))
24-
.map((name) => this.findOneByName(name, stripFuncs)),
25-
)
26-
}
23+
static findOneByName = memoize(
24+
async (name: string, stripFuncs = false): Promise<IApp> => {
25+
const rawAppData = await getApp(name.toLocaleLowerCase(), stripFuncs)
2726

28-
static async findOneByName(name: string, stripFuncs = false): Promise<IApp> {
29-
const rawAppData = await getApp(name.toLocaleLowerCase(), stripFuncs)
27+
return appInfoConverter(rawAppData)
28+
},
29+
(name: string, stripFuncs = false) => `findOneByName-${name}-${stripFuncs}`,
30+
)
3031

31-
return appInfoConverter(rawAppData)
32-
}
32+
static findOneByKey = memoize(
33+
async (key: string, stripFuncs = false): Promise<IApp> => {
34+
const rawAppData = await getApp(key, stripFuncs)
3335

34-
static async findOneByKey(key: string, stripFuncs = false): Promise<IApp> {
35-
const rawAppData = await getApp(key, stripFuncs)
36-
37-
return appInfoConverter(rawAppData)
38-
}
36+
return appInfoConverter(rawAppData)
37+
},
38+
(key: string, stripFuncs = false) =>
39+
`findOneByKey-${key}-stripFuncs-${stripFuncs}`,
40+
)
3941

4042
static getAllAppsWithFunctions = memoize(async () => {
41-
return await this.findAll(null, false)
43+
return await this.findAll(false)
4244
})
4345
}
4446

packages/frontend/src/graphql/queries/get-apps.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,8 @@
11
import { gql } from '@apollo/client'
22

33
export const GET_APPS = gql`
4-
query GetApps(
5-
$name: String
6-
$onlyWithTriggers: Boolean
7-
$onlyWithActions: Boolean
8-
) {
9-
getApps(
10-
name: $name
11-
onlyWithTriggers: $onlyWithTriggers
12-
onlyWithActions: $onlyWithActions
13-
) {
4+
query GetApps {
5+
getApps {
146
name
157
key
168
iconUrl

0 commit comments

Comments
 (0)