1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-06-04 01:18:20 +02:00
unleash.unleash/src/lib/features/instance-stats/getActiveUsers.ts
Tymoteusz Czech 2c826bdbba
feat: Add active users statistics to metrics (#4674)
## About the changes
- `getActiveUsers` is using multiple stores, so it is refactored into
read-model
- Refactored Instance stats service into `features` to co-locate related
code

Closes https://linear.app/unleash/issue/UNL-230/active-users-prometheus

### Important files
`src/lib/features/instance-stats/getActiveUsers.ts`


## Discussion points
`getActiveUsers` is coded less _class-based_ then previous similar
read-models. In one file instead of 3 (read-model interface, fake read
model, sql read model). I find types and functions way more readable,
but I'm ready to refactor it to interfaces and classes if consistency is
more important.
2023-09-18 15:05:17 +02:00

57 lines
1.8 KiB
TypeScript

import { type Db } from 'lib/server-impl';
export type GetActiveUsers = () => Promise<{
last7: number;
last30: number;
last60: number;
last90: number;
}>;
export const createGetActiveUsers =
(db: Db): GetActiveUsers =>
async () => {
const combinedQuery = db
.select('id as user_id', 'seen_at')
.from('users')
.unionAll(
db.select('user_id', 'seen_at').from('personal_access_tokens'),
);
const result = await db
.with('Combined', combinedQuery)
.select({
last_week: db.raw(
"COUNT(DISTINCT CASE WHEN seen_at > NOW() - INTERVAL '1 week' THEN user_id END)",
),
last_month: db.raw(
"COUNT(DISTINCT CASE WHEN seen_at > NOW() - INTERVAL '1 month' THEN user_id END)",
),
last_two_months: db.raw(
"COUNT(DISTINCT CASE WHEN seen_at > NOW() - INTERVAL '2 months' THEN user_id END)",
),
last_quarter: db.raw(
"COUNT(DISTINCT CASE WHEN seen_at > NOW() - INTERVAL '3 months' THEN user_id END)",
),
})
.from('Combined');
return {
last7: parseInt(result?.[0]?.last_week || '0', 10),
last30: parseInt(result?.[0]?.last_month || '0', 10),
last60: parseInt(result?.[0]?.last_two_months || '0', 10),
last90: parseInt(result?.[0]?.last_quarter || '0', 10),
};
};
export const createFakeGetActiveUsers =
(
activeUsers: Awaited<ReturnType<GetActiveUsers>> = {
last7: 0,
last30: 0,
last60: 0,
last90: 0,
},
): GetActiveUsers =>
() =>
Promise.resolve(activeUsers);