Skip to content

Commit

Permalink
πŸ› fix last_seen calculation is metrics API (#151)
Browse files Browse the repository at this point in the history
* 🎨 feat: added user_ips table to collection types

* πŸ› fix: user last_seen field calculation

* 🏷️ chore: added matrix user ip info type

* πŸ§ͺ chore: fix tests
  • Loading branch information
rezk2ll authored Dec 11, 2024
1 parent 0c8a035 commit 4c8e9b9
Show file tree
Hide file tree
Showing 5 changed files with 314 additions and 15 deletions.
1 change: 1 addition & 0 deletions packages/matrix-identity-server/src/matrixDb/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type Collections =
| 'room_stats_state'
| 'event_json'
| 'events'
| 'user_ips'

type Get = (
table: Collections,
Expand Down
52 changes: 48 additions & 4 deletions packages/tom-server/src/metrics-api/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { MatrixDBBackend } from '@twake/matrix-identity-server'
import type {
IMetricsService,
MatrixUserInfo,
MatrixUserIpInfo,
UserActivityStats,
UserMessageCount,
UserMessageEvent
Expand Down Expand Up @@ -79,13 +80,24 @@ class MetricsService implements IMetricsService {
*/
private readonly _fetchUsersList = async (): Promise<MatrixUserInfo[]> => {
try {
const queryResult = (await this.matrixDb.getAll('users', [
const matrixUsers: MatrixUserInfo[] = []
const usersList = (await this.matrixDb.getAll('users', [
'name',
'creation_ts',
'last_seen_ts'
'creation_ts'
])) as unknown as MatrixUserInfo[]

return queryResult
await Promise.all(
usersList.map(async (user) => {
const lastSeenTs = await this._fetchUserLastSeenTime(user.name)

matrixUsers.push({
...user,
last_seen_ts: lastSeenTs
})
})
)

return matrixUsers
} catch (error) {
this.logger.error(`Failed to fetch users list`, { error })

Expand Down Expand Up @@ -196,6 +208,38 @@ class MetricsService implements IMetricsService {
throw Error('Failed to fetch user message count')
}
}

/**
* Fetches the last time a user was seen.
*
* @param {string} userId - the user id.
* @returns {Promise<number>} - the last time the user was seen.
*/
private readonly _fetchUserLastSeenTime = async (
userId: string
): Promise<number> => {
try {
const queryResult = (await this.matrixDb.get(
'user_ips',
['user_id', 'last_seen'],
{
user_id: userId
}
)) as unknown as MatrixUserIpInfo[]

const latestSeenTime = queryResult.reduce((latestTime, userIpInfo) => {
const parsedLastSeen = parseInt(userIpInfo.last_seen, 10)

return parsedLastSeen > latestTime ? parsedLastSeen : latestTime
}, 0)

return latestSeenTime
} catch (error) {
this.logger.warn(`Failed to fetch user access info`, { error })

return 0
}
}
}

export default MetricsService
100 changes: 98 additions & 2 deletions packages/tom-server/src/metrics-api/tests/controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ describe('the mectrics API controller', () => {
describe('the getActivityStats handler', () => {
it('should try to fetch the user activity metrics', async () => {
dbMock.getAll.mockResolvedValue([TODAY_USER])
dbMock.get.mockImplementation(async () => {
return await Promise.resolve([
{
user_id: TODAY_USER.name,
last_seen: TODAY_USER.last_seen_ts
}
])
})

const response = await supertest(app).get(`${PATH}/activity`).send()

Expand All @@ -131,7 +139,30 @@ describe('the mectrics API controller', () => {

it('should calculate daily active users correctly', async () => {
dbMock.getAll.mockResolvedValue([TODAY_USER, PRE_TODAY_USER])

dbMock.get.mockImplementation(
async (
_table: string,
_fields: string[],
filters: Record<string, string | number>
) => {
switch (filters.user_id) {
case TODAY_USER.name:
return await Promise.resolve([
{
user_id: TODAY_USER.name,
last_seen: TODAY_USER.last_seen_ts
}
])
case PRE_TODAY_USER.name:
return await Promise.resolve([
{
user_id: PRE_TODAY_USER.name,
last_seen: PRE_TODAY_USER.last_seen_ts
}
])
}
}
)
const response = await supertest(app).get(`${PATH}/activity`).send()

expect(response.status).toBe(200)
Expand All @@ -146,6 +177,30 @@ describe('the mectrics API controller', () => {

it('should calculate weekly active users correctly', async () => {
dbMock.getAll.mockResolvedValue([TODAY_USER, PRE_WEEK_USER])
dbMock.get.mockImplementation(
async (
_table: string,
_fields: string[],
filters: Record<string, string | number>
) => {
switch (filters.user_id) {
case TODAY_USER.name:
return await Promise.resolve([
{
user_id: TODAY_USER.name,
last_seen: TODAY_USER.last_seen_ts
}
])
case PRE_WEEK_USER.name:
return await Promise.resolve([
{
user_id: PRE_WEEK_USER.name,
last_seen: PRE_WEEK_USER.last_seen_ts
}
])
}
}
)

const response = await supertest(app).get(`${PATH}/activity`).send()

Expand All @@ -161,6 +216,30 @@ describe('the mectrics API controller', () => {

it('should calculate monthly active users correctly', async () => {
dbMock.getAll.mockResolvedValue([PRE_WEEK_USER, PRE_MONTH_USER])
dbMock.get.mockImplementation(
async (
_table: string,
_fields: string[],
filters: Record<string, string | number>
) => {
switch (filters.user_id) {
case PRE_WEEK_USER.name:
return await Promise.resolve([
{
user_id: PRE_WEEK_USER.name,
last_seen: PRE_WEEK_USER.last_seen_ts
}
])
case PRE_MONTH_USER.name:
return await Promise.resolve([
{
user_id: PRE_MONTH_USER.name,
last_seen: PRE_MONTH_USER.last_seen_ts
}
])
}
}
)

const response = await supertest(app).get(`${PATH}/activity`).send()

Expand All @@ -187,7 +266,24 @@ describe('the mectrics API controller', () => {
it('should try to fetch the message stats', async () => {
dbMock.getAll.mockResolvedValue([TODAY_USER])

dbMock.get.mockResolvedValue(messages)
dbMock.get.mockImplementation(
async (
table: string,
_fields: string[],
filters: Record<string, string | number>
) => {
if (table === 'events') {
return messages
}

return await Promise.resolve([
{
user_id: TODAY_USER.name,
last_seen: TODAY_USER.last_seen_ts
}
])
}
)

const response = await supertest(app).get(`${PATH}/messages`).send()

Expand Down
Loading

0 comments on commit 4c8e9b9

Please sign in to comment.