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 { sql } from 'slonik';
|
||||||
|
|
||||||
import { buildInsertInto } from '@/database/insert-into';
|
import { buildInsertInto } from '@/database/insert-into';
|
||||||
|
@ -51,3 +51,24 @@ export const findLogById = async (id: string) =>
|
||||||
from ${table}
|
from ${table}
|
||||||
where ${fields.id}=${id}
|
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 dashboardRoutes from '@/routes/dashboard';
|
||||||
import { createRequester } from '@/utils/test-utils';
|
import { createRequester } from '@/utils/test-utils';
|
||||||
|
|
||||||
|
@ -8,6 +10,31 @@ jest.mock('@/queries/user', () => ({
|
||||||
countUsers: async () => countUsers(),
|
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', () => {
|
describe('dashboardRoutes', () => {
|
||||||
const logRequest = createRequester({ authedRoutes: dashboardRoutes });
|
const logRequest = createRequester({ authedRoutes: dashboardRoutes });
|
||||||
|
|
||||||
|
@ -27,4 +54,33 @@ describe('dashboardRoutes', () => {
|
||||||
expect(response.body).toEqual({ totalUserCount });
|
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 { countUsers } from '@/queries/user';
|
||||||
|
|
||||||
import { AuthedRouter } from './types';
|
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) {
|
export default function dashboardRoutes<T extends AuthedRouter>(router: T) {
|
||||||
router.get('/dashboard/users/total', async (ctx, next) => {
|
router.get('/dashboard/users/total', async (ctx, next) => {
|
||||||
const { count: totalUserCount } = await countUsers();
|
const { count: totalUserCount } = await countUsers();
|
||||||
|
@ -9,4 +16,44 @@ export default function dashboardRoutes<T extends AuthedRouter>(router: T) {
|
||||||
|
|
||||||
return next();
|
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