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:
parent
da41369bfd
commit
45a977790e
3 changed files with 125 additions and 1 deletions
|
@ -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})
|
||||
`);
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue