feat: add session count

This commit is contained in:
moonrailgun 2023-09-16 16:12:19 +08:00
parent 90166346e8
commit d5acf872a9
5 changed files with 77 additions and 38 deletions

View File

@ -140,5 +140,9 @@ export function useWorkspaceWebsitePageview(
} }
); );
return { stats: data?.stats ?? [], isLoading }; return {
pageviews: data?.pageviews ?? [],
sessions: data?.sessions ?? [],
isLoading,
};
} }

View File

@ -47,7 +47,7 @@ const WebsiteOverviewItem: React.FC<{
const startDate = dayjs().subtract(1, 'day').add(1, unit).startOf(unit); const startDate = dayjs().subtract(1, 'day').add(1, unit).startOf(unit);
const endDate = dayjs().endOf(unit); const endDate = dayjs().endOf(unit);
const { stats, isLoading } = useWorkspaceWebsitePageview( const { pageviews, sessions, isLoading } = useWorkspaceWebsitePageview(
props.website.workspaceId, props.website.workspaceId,
props.website.id, props.website.id,
startDate.unix() * 1000, startDate.unix() * 1000,
@ -55,8 +55,14 @@ const WebsiteOverviewItem: React.FC<{
); );
const chartData = useMemo(() => { const chartData = useMemo(() => {
return getDateArray(stats, startDate, endDate, unit); const pageviewsArr = getDateArray(pageviews, startDate, endDate, unit);
}, [stats, unit]); const sessionsArr = getDateArray(sessions, startDate, endDate, unit);
return [
...pageviewsArr.map((item) => ({ ...item, type: 'pageview' })),
...sessionsArr.map((item) => ({ ...item, type: 'session' })),
];
}, [pageviews, sessions, unit]);
if (isLoading) { if (isLoading) {
return <Loading />; return <Loading />;
@ -138,14 +144,16 @@ const MetricCard: React.FC<{
MetricCard.displayName = 'MetricCard'; MetricCard.displayName = 'MetricCard';
export const StatsChart: React.FC<{ export const StatsChart: React.FC<{
data: { x: string; y: number }[]; data: { x: string; y: number; type: string }[];
unit: DateUnit; unit: DateUnit;
}> = React.memo((props) => { }> = React.memo((props) => {
const config: ColumnConfig = useMemo( const config: ColumnConfig = useMemo(
() => ({ () => ({
data: props.data, data: props.data,
isStack: true,
xField: 'x', xField: 'x',
yField: 'y', yField: 'y',
seriesField: 'type',
label: { label: {
position: 'middle' as const, position: 'middle' as const,
style: { style: {

View File

@ -104,32 +104,6 @@ export async function deleteWorkspaceWebsite(
return website; return website;
} }
export async function getWorkspaceWebsitePageviewStats(
websiteId: string,
filters: QueryFilters
) {
const { timezone = 'utc', unit = 'day' } = filters;
const { filterQuery, joinSession, params } = await parseFilters(websiteId, {
...filters,
eventType: EVENT_TYPE.pageView,
});
return prisma.$queryRaw`
select
${getDateQuery('"WebsiteEvent"."createdAt"', unit, timezone)} x,
count(1) y
from "WebsiteEvent"
${joinSession ? Prisma.sql([joinSession]) : Prisma.empty}
where "WebsiteEvent"."websiteId" = ${params.websiteId}::uuid
and "WebsiteEvent"."createdAt" between ${
params.startDate
}::timestamptz and ${params.endDate}::timestamptz
and "WebsiteEvent"."eventType" = ${EVENT_TYPE.pageView}
${filterQuery}
group by 1
`;
}
export async function getWorkspaceWebsiteDateRange(websiteId: string) { export async function getWorkspaceWebsiteDateRange(websiteId: string) {
const { params } = await parseFilters(websiteId, { const { params } = await parseFilters(websiteId, {
startDate: new Date(DEFAULT_RESET_DATE), startDate: new Date(DEFAULT_RESET_DATE),
@ -155,3 +129,53 @@ export async function getWorkspaceWebsiteDateRange(websiteId: string) {
min: res._min.createdAt, min: res._min.createdAt,
}; };
} }
export async function getWorkspaceWebsitePageviewStats(
websiteId: string,
filters: QueryFilters
) {
const { timezone = 'utc', unit = 'day' } = filters;
const { filterQuery, joinSession, params } = await parseFilters(websiteId, {
...filters,
});
return prisma.$queryRaw`
select
${getDateQuery('"WebsiteEvent"."createdAt"', unit, timezone)} x,
count(1) y
from "WebsiteEvent"
${joinSession}
where "WebsiteEvent"."websiteId" = ${params.websiteId}::uuid
and "WebsiteEvent"."createdAt" between ${
params.startDate
}::timestamptz and ${params.endDate}::timestamptz
and "WebsiteEvent"."eventType" = ${EVENT_TYPE.pageView}
${filterQuery}
group by 1
`;
}
export async function getWorkspaceWebsiteSessionStats(
websiteId: string,
filters: QueryFilters
) {
const { timezone = 'utc', unit = 'day' } = filters;
const { filterQuery, joinSession, params } = await parseFilters(websiteId, {
...filters,
});
return prisma.$queryRaw`
select
${getDateQuery('"WebsiteEvent"."createdAt"', unit, timezone)} x,
count(distinct "WebsiteEvent"."sessionId") y
from "WebsiteEvent"
${joinSession}
where "WebsiteEvent"."websiteId" = ${params.websiteId}::uuid
and "WebsiteEvent"."createdAt" between ${
params.startDate
}::timestamptz and ${params.endDate}::timestamptz
and "WebsiteEvent"."eventType" = ${EVENT_TYPE.pageView}
${filterQuery}
group by 1
`;
}

View File

@ -8,6 +8,7 @@ import {
getWorkspaceWebsiteInfo, getWorkspaceWebsiteInfo,
getWorkspaceWebsitePageviewStats, getWorkspaceWebsitePageviewStats,
getWorkspaceWebsites, getWorkspaceWebsites,
getWorkspaceWebsiteSessionStats,
updateWorkspaceWebsiteInfo, updateWorkspaceWebsiteInfo,
} from '../model/workspace'; } from '../model/workspace';
import { parseDateRange } from '../utils/common'; import { parseDateRange } from '../utils/common';
@ -186,11 +187,11 @@ workspaceRouter.get(
city, city,
}; };
const stats = await getWorkspaceWebsitePageviewStats( const [pageviews, sessions] = await Promise.all([
websiteId, getWorkspaceWebsitePageviewStats(websiteId, filters as QueryFilters),
filters as QueryFilters getWorkspaceWebsiteSessionStats(websiteId, filters as QueryFilters),
); ]);
res.json({ stats }); res.json({ pageviews, sessions });
} }
); );

View File

@ -58,8 +58,10 @@ export async function parseFilters(
([key, value]) => ([key, value]) =>
typeof value !== 'undefined' && SESSION_COLUMNS.includes(key) typeof value !== 'undefined' && SESSION_COLUMNS.includes(key)
) )
? `inner join "WebsiteSession" on "WebsiteEvent"."sessionId" = "WebsiteSession"."id"` ? Prisma.sql([
: '', `inner join "WebsiteSession" on "WebsiteEvent"."sessionId" = "WebsiteSession"."id"`,
])
: Prisma.empty,
filterQuery: getFilterQuery(filters, options, websiteDomain), filterQuery: getFilterQuery(filters, options, websiteDomain),
params: { params: {
...normalizeFilters(filters), ...normalizeFilters(filters),