Skip to content

Commit 48d0087

Browse files
Unload expired sessions from store (#3033)
* fix: πŸ› unload expired sessions from ember data store * fix: πŸ› add unloadAll & include terminated to admin ui * test: πŸ’ update tests * refactor: πŸ’‘ pr feedback for admin ui * refactor: πŸ’‘ remove unneeded query from desktop ui * refactor: πŸ’‘ check pushToStore var * Revert "refactor: πŸ’‘ remove unneeded query from desktop ui" * fix: πŸ› fix proxy details being removed after unload * refactor: πŸ’‘ remove log
1 parent 58cc9e5 commit 48d0087

File tree

7 files changed

+75
-26
lines changed

7 files changed

+75
-26
lines changed

β€Žaddons/api/addon/handlers/sqlite-handler.jsβ€Ž

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export default class SqliteHandler {
107107
payload,
108108
storeToken,
109109
tokenKey,
110+
pushToStore,
110111
serializer,
111112
store,
112113
schema,
@@ -213,6 +214,7 @@ export default class SqliteHandler {
213214
payload,
214215
storeToken,
215216
tokenKey,
217+
pushToStore,
216218
serializer,
217219
store,
218220
schema,
@@ -224,11 +226,6 @@ export default class SqliteHandler {
224226
]);
225227
}
226228

227-
// Remove any records from the DB if the API indicates they've been deleted
228-
if (payload.removed_ids?.length > 0) {
229-
await this.sqlite.deleteResource(type, payload.removed_ids);
230-
}
231-
232229
const normalizedPayload = serializer.normalizeResponse(
233230
store,
234231
schema,
@@ -239,6 +236,19 @@ export default class SqliteHandler {
239236

240237
const { data: payloadData } = normalizedPayload;
241238

239+
// Remove any records from the DB if the API indicates they've been deleted
240+
// Additionally remove deleted records from ember data store for symmetry
241+
if (payload.removed_ids?.length > 0) {
242+
await this.sqlite.deleteResource(type, payload.removed_ids);
243+
244+
if (pushToStore) {
245+
payload.removed_ids.forEach((id) => {
246+
const record = store.peekRecord(type, id);
247+
store.unloadRecord(record);
248+
});
249+
}
250+
}
251+
242252
// Store the new data we just got back from the API refresh
243253
const items = payloadData.map((datum) => {
244254
const params = Object.values(modelMapping[type]).map((key) => {

β€Žui/admin/app/routes/scopes/scope/targets/index.jsβ€Ž

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ export default class ScopesScopeTargetsIndexRoute extends Route {
9898
'session',
9999
{
100100
scope_id,
101+
include_terminated: true,
101102
query: {
102103
filters: {
103104
scope_id: [{ equals: scope_id }],
@@ -155,7 +156,7 @@ export default class ScopesScopeTargetsIndexRoute extends Route {
155156
},
156157
},
157158
},
158-
{ pushToStore: true, peekDb: true },
159+
{ peekDb: true },
159160
);
160161
}
161162
return { targets, doTargetsExist, totalItems };

β€Žui/admin/tests/acceptance/targets/list-test.jsβ€Ž

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -364,11 +364,11 @@ module('Acceptance | targets | list', function (hooks) {
364364
instances.sshTarget.id,
365365
'session is associated with correct target',
366366
);
367-
const emberDataSessionModel = this.owner
367+
const emberDataSessionModelBefore = this.owner
368368
.lookup('service:store')
369369
.peekRecord('session', instances.session.id);
370370
assert.strictEqual(
371-
emberDataSessionModel.status,
371+
emberDataSessionModelBefore.status,
372372
STATUS_SESSION_ACTIVE,
373373
'ember data session model is active',
374374
);
@@ -389,14 +389,18 @@ module('Acceptance | targets | list', function (hooks) {
389389
instances.session.status = STATUS_SESSION_TERMINATED;
390390

391391
await click(commonSelectors.HREF(urls.targets));
392+
393+
const emberDataSessionModelAfter = this.owner
394+
.lookup('service:store')
395+
.peekRecord('session', instances.session.id);
392396
assert
393397
.dom(selectors.TABLE_TARGETS_ROW(instances.sshTarget.id))
394398
.exists('the target is still listed in the table');
395399
assert
396400
.dom(selectors.TABLE_ACTIVE_SESSIONS(instances.sshTarget.id))
397401
.doesNotExist('the target does not have an active session');
398402
assert.strictEqual(
399-
emberDataSessionModel.status,
403+
emberDataSessionModelAfter.status,
400404
STATUS_SESSION_TERMINATED,
401405
'the session ember data model status is updated',
402406
);

β€Žui/desktop/README.mdβ€Ž

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,8 @@ The static production assets are saved into the `dist/` folder.
122122
The Boundary CLI is downloaded and extracted to `electron-app/cli/` folder as part of
123123
packaging. CLI version is defined in `electron-app/config/cli.js`.
124124

125-
Similar to running in development, you can also use `BYPASS_CLI_SETUP=true` to
126-
bypass the download of the CLI, which can be useful for pre-release testing. See
125+
Similar to running in development, you can also use `SETUP_CLI=true` to
126+
enable the download of the CLI, which can be useful for pre-release testing. See
127127
[Developing Using Non-Release Versions of
128128
Boundary](#developing-using-non-release-versions-of-boundary) for more details.
129129

β€Žui/desktop/app/routes/scopes/scope/projects/targets/index.jsβ€Ž

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -159,21 +159,22 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route {
159159
// To correctly show targets with active sessions, the associated
160160
// sessions need to be queried to sync all the session models in
161161
// ember data and retrieve their updated `status` properties
162-
const sessionsPromise = this.store.query(
163-
'session',
164-
{
165-
query: {
166-
filters: {
167-
target_id: targets.map((target) => ({ equals: target.id })),
168-
},
162+
const sessionsPromise = this.store.query('session', {
163+
query: {
164+
filters: {
165+
target_id: targets.map((target) => ({ equals: target.id })),
169166
},
170167
},
171-
{ pushToStore: true },
172-
);
168+
});
173169

170+
let allAssociatedSessions;
174171
// Load the sessions and aliases for the targets on the current page
175172
try {
176-
await Promise.all([aliasPromise, sessionsPromise]);
173+
const loadedResults = await Promise.all([
174+
aliasPromise,
175+
sessionsPromise,
176+
]);
177+
allAssociatedSessions = loadedResults[1];
177178
} catch (e) {
178179
__electronLog?.warn(
179180
'Could not retrieve aliases and/or sessions for targets',
@@ -183,6 +184,27 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route {
183184
// the page in case the controller doesn't support aliases yet
184185
}
185186

187+
// Remove expired sessions in ember data store for symmetry with cache.
188+
if (allAssociatedSessions) {
189+
const targetIds = targets.map(({ id }) => id);
190+
const allAssociatedSessionIds = new Set(
191+
allAssociatedSessions.map(({ id }) => id),
192+
);
193+
const storedSessionIds = new Set(
194+
this.store
195+
.peekAll('session')
196+
.filter((s) => targetIds.includes(s?.target_id))
197+
.map(({ id }) => id),
198+
);
199+
const removedSessions = storedSessionIds.difference(
200+
allAssociatedSessionIds,
201+
);
202+
removedSessions.forEach((id) => {
203+
const record = this.store.peekRecord('session', id);
204+
this.store.unloadRecord(record);
205+
});
206+
}
207+
186208
return {
187209
targets,
188210
projects,

β€Žui/desktop/electron-app/src/models/session.jsβ€Ž

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const {
88
spawnAsyncJSONPromise,
99
spawnSync,
1010
} = require('../helpers/spawn-promise.js');
11+
const log = require('electron-log/main');
1112

1213
class Session {
1314
#id;
@@ -89,7 +90,6 @@ class Session {
8990
).then((spawnedSession) => {
9091
this.#process = spawnedSession.childProcess;
9192
this.#proxyDetails = spawnedSession.response;
92-
this.#process = spawnedSession.childProcess;
9393
this.#id = this.#proxyDetails.session_id;
9494
return this.#proxyDetails;
9595
});
@@ -102,7 +102,10 @@ class Session {
102102
return new Promise((resolve, reject) => {
103103
if (this.isRunning) {
104104
this.#process.on('close', () => resolve());
105-
this.#process.on('error', (e) => reject(e));
105+
this.#process.on('error', (e) => {
106+
log.error('Process error in session stop method: ', e);
107+
return reject(e);
108+
});
106109

107110
// Cancel session before killing process
108111
const sanitizedToken = sanitizer.base62EscapeAndValidate(this.#token);

β€Žui/desktop/tests/acceptance/projects/targets/index-test.jsβ€Ž

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -673,11 +673,14 @@ module('Acceptance | projects | targets | index', function (hooks) {
673673

674674
assert.strictEqual(instances.session.status, STATUS_SESSION_ACTIVE);
675675
await visit(urls.targets);
676-
const emberDataSessionModel = this.owner
676+
const emberDataSessionModelBefore = this.owner
677677
.lookup('service:store')
678678
.peekRecord('session', instances.session.id);
679679

680-
assert.strictEqual(emberDataSessionModel.status, STATUS_SESSION_ACTIVE);
680+
assert.strictEqual(
681+
emberDataSessionModelBefore.status,
682+
STATUS_SESSION_ACTIVE,
683+
);
681684

682685
assert
683686
.dom(activeSessionFlyoutButtonSelector(instances.session.targetId))
@@ -695,7 +698,13 @@ module('Acceptance | projects | targets | index', function (hooks) {
695698

696699
await click(`[href="${urls.targets}"]`);
697700

698-
assert.strictEqual(emberDataSessionModel.status, STATUS_SESSION_TERMINATED);
701+
const emberDataSessionModelAfter = this.owner
702+
.lookup('service:store')
703+
.peekRecord('session', instances.session.id);
704+
assert.strictEqual(
705+
emberDataSessionModelAfter.status,
706+
STATUS_SESSION_TERMINATED,
707+
);
699708
assert
700709
.dom(activeSessionFlyoutButtonSelector(instances.session.targetId))
701710
.doesNotExist();

0 commit comments

Comments
Β (0)