Skip to content

Commit a504c6b

Browse files
committed
perf: optimize recommended apps
1 parent 3da571c commit a504c6b

File tree

2 files changed

+200
-124
lines changed

2 files changed

+200
-124
lines changed

src/backend/src/routers/get-launch-apps.js

+45-40
Original file line numberDiff line numberDiff line change
@@ -32,48 +32,53 @@ module.exports = async (req, res) => {
3232
// -----------------------------------------------------------------------//
3333
// Recommended apps
3434
// -----------------------------------------------------------------------//
35-
let app_names = new Set([
36-
'app-center',
37-
'dev-center',
38-
'editor',
39-
'code',
40-
'terminal',
41-
'draw',
42-
'silex',
43-
'camera',
44-
'recorder',
45-
'shell-shockers-outpan',
46-
'krunker',
47-
'slash-frvr',
48-
'viewer',
49-
'solitaire-frvr',
50-
'markus',
51-
'player',
52-
'pdf',
53-
'polotno',
54-
'basketball-frvr',
55-
'gold-digger-frvr',
56-
'plushie-connect',
57-
'hex-frvr',
58-
'spider-solitaire',
59-
]);
35+
result.recommended = kv.get('global:recommended-apps');
36+
if ( ! result.recommended ) {
37+
let app_names = new Set([
38+
'app-center',
39+
'dev-center',
40+
'editor',
41+
'code',
42+
'terminal',
43+
'draw',
44+
'silex',
45+
'camera',
46+
'recorder',
47+
'shell-shockers-outpan',
48+
'krunker',
49+
'slash-frvr',
50+
'viewer',
51+
'solitaire-frvr',
52+
'markus',
53+
'player',
54+
'pdf',
55+
'polotno',
56+
'basketball-frvr',
57+
'gold-digger-frvr',
58+
'plushie-connect',
59+
'hex-frvr',
60+
'spider-solitaire',
61+
]);
6062

61-
// Prepare each app for returning to user by only returning the necessary fields
62-
// and adding them to the retobj array
63-
result.recommended = [];
64-
for ( const name of app_names ) {
65-
const app = await get_app({ name });
66-
if ( ! app ) continue;
63+
// Prepare each app for returning to user by only returning the necessary fields
64+
// and adding them to the retobj array
65+
result.recommended = [];
66+
for ( const name of app_names ) {
67+
const app = await get_app({ name });
68+
if ( ! app ) continue;
6769

68-
result.recommended.push({
69-
uuid: app.uid,
70-
name: app.name,
71-
title: app.title,
72-
icon: app.icon,
73-
godmode: app.godmode,
74-
maximize_on_start: app.maximize_on_start,
75-
index_url: app.index_url,
76-
});
70+
result.recommended.push({
71+
uuid: app.uid,
72+
name: app.name,
73+
title: app.title,
74+
icon: app.icon,
75+
godmode: app.godmode,
76+
maximize_on_start: app.maximize_on_start,
77+
index_url: app.index_url,
78+
});
79+
}
80+
81+
kv.set('global:recommended-apps', result.recommended);
7782
}
7883

7984
// -----------------------------------------------------------------------//

src/backend/src/routers/get-launch-apps.test.js

+155-84
Original file line numberDiff line numberDiff line change
@@ -64,93 +64,164 @@ const data_appopens = [
6464
},
6565
];
6666

67-
describe('GET /launch-apps', () => {
68-
it('should return expected format', async () => {
69-
globalThis.kv = new kvjs();
70-
const database_mock = {
71-
read: async (query) => {
72-
if ( query.includes('FROM app_opens') ) {
73-
return data_appopens;
74-
}
67+
const get_mock_context = () => {
68+
const database_mock = {
69+
read: async (query) => {
70+
if ( query.includes('FROM app_opens') ) {
71+
return data_appopens;
7572
}
76-
};
77-
const services_mock = {
78-
get: (key) => {
79-
if (key === 'database') {
80-
return {
81-
get: () => database_mock,
82-
}
73+
}
74+
};
75+
const services_mock = {
76+
get: (key) => {
77+
if (key === 'database') {
78+
return {
79+
get: () => database_mock,
8380
}
8481
}
85-
};
86-
87-
const req_mock = {
88-
user: {
89-
id: 1 + Math.floor(Math.random() * 1000**3),
90-
},
91-
services: services_mock,
92-
send: sinon.spy(),
93-
};
94-
95-
const res_mock = {
96-
send: sinon.spy(),
97-
};
98-
99-
const get_launch_apps = proxyquire('./get-launch-apps', {
100-
'../helpers.js': {
101-
get_app: async ({ uid, name }) => {
102-
if ( uid ) {
103-
return data_mockapps.find(app => app.uid === uid);
104-
}
105-
if ( name ) {
106-
return data_mockapps.find(app => app.name === name);
107-
}
108-
}
109-
}
110-
});
82+
}
83+
};
84+
85+
const req_mock = {
86+
user: {
87+
id: 1 + Math.floor(Math.random() * 1000**3),
88+
},
89+
services: services_mock,
90+
send: sinon.spy(),
91+
};
92+
93+
const res_mock = {
94+
send: sinon.spy(),
95+
};
96+
97+
const get_app = sinon.spy(async ({ uid, name }) => {
98+
if ( uid ) {
99+
return data_mockapps.find(app => app.uid === uid);
100+
}
101+
if ( name ) {
102+
return data_mockapps.find(app => app.name === name);
103+
}
104+
});
111105

112-
await get_launch_apps(req_mock, res_mock);
113-
114-
expect(res_mock.send.calledOnce).to.equal(true, 'res.send should be called once');
115-
116-
const call = res_mock.send.firstCall;
117-
response = call.args[0];
118-
console.log('response', response);
119-
120-
expect(response).to.be.an('object');
121-
122-
expect(response).to.have.property('recommended');
123-
expect(response.recommended).to.be.an('array');
124-
expect(response.recommended).to.have.lengthOf(apps_names_expected_to_exist.length);
125-
expect(response.recommended).to.deep.equal(
126-
data_mockapps
127-
.filter(app => apps_names_expected_to_exist.includes(app.name))
128-
.map(app => ({
129-
uuid: app.uid,
130-
name: app.name,
131-
title: app.title,
132-
icon: app.icon,
133-
godmode: app.godmode,
134-
maximize_on_start: app.maximize_on_start,
135-
index_url: app.index_url,
136-
}))
137-
);
138-
139-
expect(response).to.have.property('recent');
140-
expect(response.recent).to.be.an('array');
141-
expect(response.recent).to.have.lengthOf(data_appopens.length);
142-
expect(response.recent).to.deep.equal(
143-
data_mockapps
144-
.filter(app => data_appopens.map(app_open => app_open.app_uid).includes(app.uid))
145-
.map(app => ({
146-
uuid: app.uid,
147-
name: app.name,
148-
title: app.title,
149-
icon: app.icon,
150-
godmode: app.godmode,
151-
maximize_on_start: app.maximize_on_start,
152-
index_url: app.index_url,
153-
}))
154-
);
106+
const get_launch_apps = proxyquire('./get-launch-apps', {
107+
'../helpers.js': {
108+
get_app,
109+
}
110+
});
111+
112+
return {
113+
get_launch_apps, req_mock, res_mock,
114+
spies: {
115+
get_app,
116+
}
117+
};
118+
};
119+
120+
describe('GET /launch-apps', () => {
121+
globalThis.kv = new kvjs();
122+
123+
it('should return expected format', async () => {
124+
// First call
125+
{
126+
const { get_launch_apps, req_mock, res_mock, spies } = get_mock_context();
127+
await get_launch_apps(req_mock, res_mock);
128+
129+
expect(res_mock.send.calledOnce).to.equal(true, 'res.send should be called once');
130+
131+
const call = res_mock.send.firstCall;
132+
response = call.args[0];
133+
console.log('response', response);
134+
135+
expect(response).to.be.an('object');
136+
137+
expect(response).to.have.property('recommended');
138+
expect(response.recommended).to.be.an('array');
139+
expect(response.recommended).to.have.lengthOf(apps_names_expected_to_exist.length);
140+
expect(response.recommended).to.deep.equal(
141+
data_mockapps
142+
.filter(app => apps_names_expected_to_exist.includes(app.name))
143+
.map(app => ({
144+
uuid: app.uid,
145+
name: app.name,
146+
title: app.title,
147+
icon: app.icon,
148+
godmode: app.godmode,
149+
maximize_on_start: app.maximize_on_start,
150+
index_url: app.index_url,
151+
}))
152+
);
153+
154+
expect(response).to.have.property('recent');
155+
expect(response.recent).to.be.an('array');
156+
expect(response.recent).to.have.lengthOf(data_appopens.length);
157+
expect(response.recent).to.deep.equal(
158+
data_mockapps
159+
.filter(app => data_appopens.map(app_open => app_open.app_uid).includes(app.uid))
160+
.map(app => ({
161+
uuid: app.uid,
162+
name: app.name,
163+
title: app.title,
164+
icon: app.icon,
165+
godmode: app.godmode,
166+
maximize_on_start: app.maximize_on_start,
167+
index_url: app.index_url,
168+
}))
169+
);
170+
171+
// << HOW TO FIX >>
172+
// If you updated the list of recommended apps,
173+
// you can simply update this number to match the new length
174+
expect(spies.get_app.callCount).to.equal(26);
175+
}
176+
177+
// Second call
178+
{
179+
const { get_launch_apps, req_mock, res_mock, spies } = get_mock_context();
180+
await get_launch_apps(req_mock, res_mock);
181+
182+
expect(res_mock.send.calledOnce).to.equal(true, 'res.send should be called once');
183+
184+
const call = res_mock.send.firstCall;
185+
response = call.args[0];
186+
187+
expect(response).to.be.an('object');
188+
189+
expect(response).to.have.property('recommended');
190+
expect(response.recommended).to.be.an('array');
191+
expect(response.recommended).to.have.lengthOf(apps_names_expected_to_exist.length);
192+
expect(response.recommended).to.deep.equal(
193+
data_mockapps
194+
.filter(app => apps_names_expected_to_exist.includes(app.name))
195+
.map(app => ({
196+
uuid: app.uid,
197+
name: app.name,
198+
title: app.title,
199+
icon: app.icon,
200+
godmode: app.godmode,
201+
maximize_on_start: app.maximize_on_start,
202+
index_url: app.index_url,
203+
}))
204+
);
205+
206+
expect(response).to.have.property('recent');
207+
expect(response.recent).to.be.an('array');
208+
expect(response.recent).to.have.lengthOf(data_appopens.length);
209+
expect(response.recent).to.deep.equal(
210+
data_mockapps
211+
.filter(app => data_appopens.map(app_open => app_open.app_uid).includes(app.uid))
212+
.map(app => ({
213+
uuid: app.uid,
214+
name: app.name,
215+
title: app.title,
216+
icon: app.icon,
217+
godmode: app.godmode,
218+
maximize_on_start: app.maximize_on_start,
219+
index_url: app.index_url,
220+
}))
221+
);
222+
223+
expect(spies.get_app.callCount).to.equal(
224+
data_appopens.length, 'get_app only called for recents on second call');
225+
}
155226
})
156227
});

0 commit comments

Comments
 (0)