0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-23 20:33:16 -05:00

feat(core): get /dashboard/users/new (#940)

This commit is contained in:
IceHe.xyz 2022-05-26 11:24:52 +08:00 committed by GitHub
parent da41369bfd
commit 45a977790e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 125 additions and 1 deletions

View file

@ -1,4 +1,4 @@
import { CreateLog, Log, Logs } from '@logto/schemas';
import { CreateLog, Log, Logs, LogType } from '@logto/schemas';
import { sql } from 'slonik';
import { buildInsertInto } from '@/database/insert-into';
@ -51,3 +51,24 @@ export const findLogById = async (id: string) =>
from ${table}
where ${fields.id}=${id}
`);
const registerLogTypes: LogType[] = [
'RegisterUsernamePassword',
'RegisterEmail',
'RegisterSms',
'RegisterSocial',
];
export const getDailyNewUserCountsByTimeInterval = async (
startTimeExclusive: number,
endTimeInclusive: number
) =>
envSet.pool.any<{ date: string; count: number }>(sql`
select date(${fields.createdAt}), count(*)
from ${table}
where ${fields.createdAt} > to_timestamp(${startTimeExclusive}::double precision / 1000)
and ${fields.createdAt} <= to_timestamp(${endTimeInclusive}::double precision / 1000)
and ${fields.type} in (${sql.join(registerLogTypes, sql`, `)})
and ${fields.payload}->>'result' = 'Success'
group by date(${fields.createdAt})
`);

View file

@ -1,3 +1,5 @@
import dayjs from 'dayjs';
import dashboardRoutes from '@/routes/dashboard';
import { createRequester } from '@/utils/test-utils';
@ -8,6 +10,31 @@ jest.mock('@/queries/user', () => ({
countUsers: async () => countUsers(),
}));
const mockDailyNewUserCounts = [
{ date: '2022-05-01', count: 1 },
{ date: '2022-05-02', count: 2 },
{ date: '2022-05-03', count: 3 },
{ date: '2022-05-06', count: 6 },
{ date: '2022-05-07', count: 7 },
{ date: '2022-05-08', count: 8 },
{ date: '2022-05-09', count: 9 },
{ date: '2022-05-10', count: 10 },
{ date: '2022-05-13', count: 13 },
{ date: '2022-05-14', count: 14 },
];
const getDailyNewUserCountsByTimeInterval = jest.fn(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async (startTimeExclusive: number, endTimeInclusive: number) => mockDailyNewUserCounts
);
jest.mock('@/queries/log', () => ({
getDailyNewUserCountsByTimeInterval: async (
startTimeExclusive: number,
endTimeInclusive: number
) => getDailyNewUserCountsByTimeInterval(startTimeExclusive, endTimeInclusive),
}));
describe('dashboardRoutes', () => {
const logRequest = createRequester({ authedRoutes: dashboardRoutes });
@ -27,4 +54,33 @@ describe('dashboardRoutes', () => {
expect(response.body).toEqual({ totalUserCount });
});
});
describe('GET /dashboard/users/new', () => {
beforeEach(() => {
jest.useFakeTimers().setSystemTime(new Date('2022-05-14'));
});
it('should call getDailyNewUserCountsByTimeInterval with the time interval (14 days ago 23:59:59.999, today 23:59:59.999]', async () => {
await logRequest.get('/dashboard/users/new');
expect(getDailyNewUserCountsByTimeInterval).toHaveBeenCalledWith(
dayjs().endOf('day').subtract(14, 'day').valueOf(),
dayjs().endOf('day').valueOf()
);
});
it('should return correct response', async () => {
const response = await logRequest.get('/dashboard/users/new');
expect(response.status).toEqual(200);
expect(response.body).toEqual({
today: {
count: 14,
delta: 1,
},
last7Days: {
count: 54,
delta: 35,
},
});
});
});
});

View file

@ -1,7 +1,14 @@
import dayjs, { Dayjs } from 'dayjs';
import { getDailyNewUserCountsByTimeInterval } from '@/queries/log';
import { countUsers } from '@/queries/user';
import { AuthedRouter } from './types';
const getDateString = (day: Dayjs) => day.format('YYYY-MM-DD');
const indicesFrom0To6 = [...Array.from({ length: 7 }).keys()];
export default function dashboardRoutes<T extends AuthedRouter>(router: T) {
router.get('/dashboard/users/total', async (ctx, next) => {
const { count: totalUserCount } = await countUsers();
@ -9,4 +16,44 @@ export default function dashboardRoutes<T extends AuthedRouter>(router: T) {
return next();
});
router.get('/dashboard/users/new', async (ctx, next) => {
const today = dayjs();
const fourteenDaysAgo = today.subtract(14, 'day');
const dailyNewUserCounts = await getDailyNewUserCountsByTimeInterval(
// Time interval: (14 days ago 23:59:59.999, today 23:59:59.999]
fourteenDaysAgo.endOf('day').valueOf(),
today.endOf('day').valueOf()
);
const last14DaysNewUserCounts = new Map(
dailyNewUserCounts.map(({ date, count }) => [date, count])
);
const todayNewUserCount = last14DaysNewUserCounts.get(getDateString(today)) ?? 0;
const yesterday = today.subtract(1, 'day');
const yesterdayNewUserCount = last14DaysNewUserCounts.get(getDateString(yesterday)) ?? 0;
const todayDelta = todayNewUserCount - yesterdayNewUserCount;
const last7DaysNewUserCount = indicesFrom0To6
.map((index) => getDateString(today.subtract(index, 'day')))
.reduce((sum, date) => sum + (last14DaysNewUserCounts.get(date) ?? 0), 0);
const newUserCountFrom13DaysAgoTo7DaysAgo = indicesFrom0To6
.map((index) => getDateString(today.subtract(7 + index, 'day')))
.reduce((sum, date) => sum + (last14DaysNewUserCounts.get(date) ?? 0), 0);
const last7DaysDelta = last7DaysNewUserCount - newUserCountFrom13DaysAgoTo7DaysAgo;
ctx.body = {
today: {
count: todayNewUserCount,
delta: todayDelta,
},
last7Days: {
count: last7DaysNewUserCount,
delta: last7DaysDelta,
},
};
return next();
});
}