Skip to content

Commit 4c7ea16

Browse files
lisbet-alvarezZedLi
authored andcommitted
feat: 🎸 Add sqlite support to sessions (#2978)
* feat: 🎸 Add sqlite support to sessions * refactor: 💡 only peek sqlite db when batching queries ✅ Closes: https://hashicorp.atlassian.net/browse/ICU-17411
1 parent dc026c4 commit 4c7ea16

File tree

3 files changed

+144
-43
lines changed

3 files changed

+144
-43
lines changed

‎addons/api/addon/services/sqlite.js‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,16 @@ export const modelMapping = {
9999
target_scope_name: 'create_time_values.target.scope.name',
100100
created_time: 'created_time',
101101
},
102+
session: {
103+
id: 'id',
104+
type: 'type',
105+
status: 'status',
106+
endpoint: 'endpoint',
107+
target_id: 'target_id',
108+
user_id: 'user_id',
109+
scope_id: 'scope.scope_id',
110+
created_time: 'created_time',
111+
},
102112
};
103113

104114
// A list of tables that we support searching using FTS5 in SQLite.
@@ -113,6 +123,7 @@ export const searchTables = new Set([
113123
'auth-method',
114124
'host-catalog',
115125
'session-recording',
126+
'session',
116127
]);
117128

118129
export default class SqliteDbService extends Service {

‎addons/api/addon/workers/utils/schema.js‎

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,45 @@ CREATE TRIGGER IF NOT EXISTS session_recording_ad AFTER DELETE ON session_record
388388
VALUES('delete', old.rowid, old.id, old.type, old.state, old.start_time, old.end_time, old.duration, old.scope_id, old.user_id, old.user_name, old.target_id, old.target_name, old.target_scope_id, old.target_scope_name, old.created_time);
389389
END;`;
390390

391+
const createSessionTables = `
392+
CREATE TABLE IF NOT EXISTS session (
393+
id TEXT NOT NULL PRIMARY KEY,
394+
type TEXT,
395+
status TEXT,
396+
endpoint TEXT,
397+
target_id TEXT,
398+
user_id TEXT,
399+
scope_id TEXT NOT NULL,
400+
created_time TEXT NOT NULL,
401+
data TEXT NOT NULL
402+
);
403+
CREATE INDEX IF NOT EXISTS idx_session_scope_id_created_time ON session(scope_id, created_time DESC);
404+
405+
CREATE VIRTUAL TABLE IF NOT EXISTS session_fts USING fts5(
406+
id,
407+
type,
408+
status,
409+
endpoint,
410+
target_id,
411+
user_id,
412+
scope_id,
413+
created_time,
414+
content='',
415+
);
416+
417+
CREATE TRIGGER IF NOT EXISTS session_ai AFTER INSERT ON session BEGIN
418+
INSERT INTO session_fts(
419+
id, type, status, endpoint, target_id, user_id, scope_id, created_time
420+
) VALUES (
421+
new.id, new.type, new.status, new.endpoint, new.target_id, new.user_id, new.scope_id, new.created_time
422+
);
423+
END;
424+
425+
CREATE TRIGGER IF NOT EXISTS session_ad AFTER DELETE ON session BEGIN
426+
INSERT INTO session_fts(session_fts, rowid, id, type, status, endpoint, target_id, user_id, scope_id, created_time)
427+
VALUES('delete', old.rowid, old.id, old.type, old.status, old.endpoint, old.target_id, old.user_id, old.scope_id, old.created_time);
428+
END;`;
429+
391430
export const CREATE_TABLES = (version) => `
392431
BEGIN;
393432
@@ -408,16 +447,7 @@ ${createScopeTables}
408447
${createAuthMethodTables}
409448
${createHostCatalogTables}
410449
${createSessionRecordingTables}
411-
412-
CREATE TABLE IF NOT EXISTS "group" (
413-
id TEXT NOT NULL PRIMARY KEY,
414-
name TEXT,
415-
description TEXT,
416-
scope_id TEXT NOT NULL,
417-
created_time TEXT NOT NULL,
418-
data TEXT NOT NULL
419-
);
420-
CREATE INDEX IF NOT EXISTS idx_group_scope_id_created_time ON "group"(scope_id, created_time DESC);
450+
${createSessionTables}
421451
422452
COMMIT;`;
423453

‎ui/admin/app/routes/scopes/scope/sessions/index.js‎

Lines changed: 93 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ import Route from '@ember/routing/route';
77
import { service } from '@ember/service';
88
import { action } from '@ember/object';
99
import { restartableTask, timeout } from 'ember-concurrency';
10+
import chunk from 'lodash/chunk';
11+
12+
// Maximum expression tree depth is 1000 so
13+
// we limit the number of expressions in a query.
14+
// See "Maximum Depth Of An Expression Tree" in
15+
// https://www.sqlite.org/limits.html
16+
const MAX_EXPR_NUM = 999;
1017

1118
export default class ScopesScopeSessionsIndexRoute extends Route {
1219
// =services
@@ -114,10 +121,11 @@ export default class ScopesScopeSessionsIndexRoute extends Route {
114121
const totalItems = sessions.meta?.totalItems;
115122

116123
const allSessions = await this.getAllSessions(scope_id);
117-
const [associatedUsers, associatedTargets] = await Promise.all([
118-
this.getAssociatedUsers(scope, allSessions),
119-
this.getAssociatedTargets(scope, allSessions),
120-
]);
124+
const associatedUsers = await this.getAssociatedUsers(scope, allSessions);
125+
const associatedTargets = await this.getAssociatedTargets(
126+
scope,
127+
allSessions,
128+
);
121129

122130
return {
123131
sessions,
@@ -159,24 +167,45 @@ export default class ScopesScopeSessionsIndexRoute extends Route {
159167
this.can.can('list model', globalScope, { collection: 'users' }) &&
160168
this.can.can('list model', orgScope, { collection: 'users' })
161169
) {
162-
const uniqueSessionUserIds = new Set(
163-
allSessions
164-
.filter((session) => session.user_id)
165-
.map((session) => session.user_id),
170+
const users = await this.store.query(
171+
'user',
172+
{
173+
scope_id: 'global',
174+
recursive: true,
175+
page: 1,
176+
pageSize: 1,
177+
},
178+
{ pushToStore: false },
166179
);
167-
const filters = {
168-
id: { values: [] },
169-
};
170-
uniqueSessionUserIds.forEach((userId) => {
171-
filters.id.values.push({ equals: userId });
172-
});
173-
const associatedUsersQuery = {
174-
scope_id: 'global',
175-
recursive: true,
176-
query: { filters },
177-
};
178180

179-
return this.store.query('user', associatedUsersQuery);
181+
if (!users.length) {
182+
return [];
183+
}
184+
185+
const uniqueSessionUserIds = [
186+
...new Set(
187+
allSessions
188+
.filter((session) => session.user_id)
189+
.map((session) => session.user_id),
190+
),
191+
];
192+
const chunkedUserIds = chunk(uniqueSessionUserIds, MAX_EXPR_NUM);
193+
const associatedUsersPromises = chunkedUserIds.map((userIds) =>
194+
this.store.query(
195+
'user',
196+
{
197+
scope_id: 'global',
198+
query: {
199+
filters: {
200+
id: { values: userIds.map((userId) => ({ equals: userId })) },
201+
},
202+
},
203+
},
204+
{ peekDb: true },
205+
),
206+
);
207+
const usersArray = await Promise.all(associatedUsersPromises);
208+
return usersArray.flat();
180209
}
181210

182211
return [];
@@ -189,21 +218,52 @@ export default class ScopesScopeSessionsIndexRoute extends Route {
189218
*/
190219
async getAssociatedTargets(scope, allSessions) {
191220
if (this.can.can('list model', scope, { collection: 'targets' })) {
192-
const uniqueSessionTargetIds = new Set(
193-
allSessions
194-
.filter((session) => session.target_id)
195-
.map((session) => session.target_id),
221+
const targets = await this.store.query(
222+
'target',
223+
{
224+
scope_id: scope.id,
225+
query: {
226+
filters: {
227+
scope_id: [{ equals: scope.id }],
228+
},
229+
},
230+
page: 1,
231+
pageSize: 1,
232+
},
233+
{ pushToStore: false },
196234
);
197-
const filters = { id: { values: [] } };
198-
uniqueSessionTargetIds.forEach((targetId) => {
199-
filters.id.values.push({ equals: targetId });
200-
});
201-
const associatedTargetsQuery = {
202-
scope_id: scope.id,
203-
query: { filters },
204-
};
205235

206-
return this.store.query('target', associatedTargetsQuery);
236+
if (!targets.length) {
237+
return [];
238+
}
239+
240+
const uniqueSessionTargetIds = [
241+
...new Set(
242+
allSessions
243+
.filter((session) => session.target_id)
244+
.map((session) => session.target_id),
245+
),
246+
];
247+
248+
const chunkedTargetIds = chunk(uniqueSessionTargetIds, MAX_EXPR_NUM);
249+
const associatedTargetsPromises = chunkedTargetIds.map((targetIds) =>
250+
this.store.query(
251+
'target',
252+
{
253+
scope_id: scope.id,
254+
query: {
255+
filters: {
256+
id: {
257+
values: targetIds.map((targetId) => ({ equals: targetId })),
258+
},
259+
},
260+
},
261+
},
262+
{ peekDb: true },
263+
),
264+
);
265+
const targetsArray = await Promise.all(associatedTargetsPromises);
266+
return targetsArray.flat();
207267
}
208268

209269
return [];

0 commit comments

Comments
 (0)