refactor: change website stat endpoint to trpc
This commit is contained in:
parent
f153145e0d
commit
537edcf506
@ -63,59 +63,3 @@ export function useWorkspaceWebsitePageview(
|
||||
refetch,
|
||||
};
|
||||
}
|
||||
|
||||
export interface StatsItemType {
|
||||
value: number;
|
||||
change: number;
|
||||
}
|
||||
export async function getWorkspaceWebsiteStats(
|
||||
workspaceId: string,
|
||||
websiteId: string,
|
||||
filter: Record<string, any>
|
||||
): Promise<{
|
||||
bounces: StatsItemType;
|
||||
pageviews: StatsItemType;
|
||||
totaltime: StatsItemType;
|
||||
uniques: StatsItemType;
|
||||
}> {
|
||||
const { data } = await request.get(
|
||||
`/api/workspace/${workspaceId}/website/${websiteId}/stats`,
|
||||
{
|
||||
params: {
|
||||
...filter,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return data.stats;
|
||||
}
|
||||
|
||||
export function useWorkspaceWebsiteStats(
|
||||
workspaceId: string,
|
||||
websiteId: string,
|
||||
startAt: number,
|
||||
endAt: number,
|
||||
unit: DateUnit
|
||||
) {
|
||||
const {
|
||||
data: stats,
|
||||
isLoading,
|
||||
refetch,
|
||||
} = useQuery(
|
||||
['websiteStats', { workspaceId, websiteId, startAt, endAt }],
|
||||
() => {
|
||||
return getWorkspaceWebsiteStats(workspaceId, websiteId, {
|
||||
startAt,
|
||||
endAt,
|
||||
unit,
|
||||
timezone: getUserTimezone(),
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
stats,
|
||||
isLoading,
|
||||
refetch,
|
||||
};
|
||||
}
|
||||
|
@ -4,9 +4,7 @@ import { Column, ColumnConfig } from '@ant-design/charts';
|
||||
import { SyncOutlined } from '@ant-design/icons';
|
||||
import { DateFilter } from '../DateFilter';
|
||||
import {
|
||||
StatsItemType,
|
||||
useWorkspaceWebsitePageview,
|
||||
useWorkspaceWebsiteStats,
|
||||
WebsiteInfo,
|
||||
} from '../../api/model/website';
|
||||
import {
|
||||
@ -23,6 +21,8 @@ import { WebsiteOnlineCount } from './WebsiteOnlineCount';
|
||||
import { useGlobalRangeDate } from '../../hooks/useGlobalRangeDate';
|
||||
import { MonitorHealthBar } from '../monitor/MonitorHealthBar';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { AppRouterOutput, trpc } from '../../api/trpc';
|
||||
import { getUserTimezone } from '../../api/model/user';
|
||||
|
||||
export const WebsiteOverview: React.FC<{
|
||||
website: WebsiteInfo;
|
||||
@ -47,16 +47,17 @@ export const WebsiteOverview: React.FC<{
|
||||
);
|
||||
|
||||
const {
|
||||
stats,
|
||||
data: stats,
|
||||
isLoading: isLoadingStats,
|
||||
refetch: refetchStats,
|
||||
} = useWorkspaceWebsiteStats(
|
||||
website.workspaceId,
|
||||
website.id,
|
||||
startDate.unix() * 1000,
|
||||
endDate.unix() * 1000,
|
||||
unit
|
||||
);
|
||||
} = trpc.website.stats.useQuery({
|
||||
workspaceId: website.workspaceId,
|
||||
websiteId: website.id,
|
||||
startAt: startDate.unix() * 1000,
|
||||
endAt: endDate.unix() * 1000,
|
||||
timezone: getUserTimezone(),
|
||||
unit,
|
||||
});
|
||||
|
||||
const handleRefresh = useEvent(async () => {
|
||||
refresh();
|
||||
@ -109,8 +110,8 @@ export const WebsiteOverview: React.FC<{
|
||||
<div>{actions}</div>
|
||||
</div>
|
||||
|
||||
<div className="flex mb-10 flex-wrap">
|
||||
{stats && <MetricsBar stats={stats} />}
|
||||
<div className="flex mb-10 flex-wrap justify-between">
|
||||
<div className="flex-1">{stats && <MetricsBar stats={stats} />}</div>
|
||||
|
||||
<div className="flex items-center gap-2 justify-end w-full lg:w-1/3">
|
||||
<Button
|
||||
@ -136,12 +137,7 @@ export const WebsiteOverview: React.FC<{
|
||||
WebsiteOverview.displayName = 'WebsiteOverview';
|
||||
|
||||
export const MetricsBar: React.FC<{
|
||||
stats: {
|
||||
bounces: StatsItemType;
|
||||
pageviews: StatsItemType;
|
||||
totaltime: StatsItemType;
|
||||
uniques: StatsItemType;
|
||||
};
|
||||
stats: AppRouterOutput['website']['stats'];
|
||||
}> = React.memo((props) => {
|
||||
const { pageviews, uniques, bounces, totaltime } = props.stats || {};
|
||||
const num = Math.min(uniques.value, bounces.value);
|
||||
@ -153,7 +149,7 @@ export const MetricsBar: React.FC<{
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex gap-5 flex-wrap w-full lg:w-2/3">
|
||||
<div className="flex gap-5 flex-wrap w-full">
|
||||
<MetricCard
|
||||
label="Views"
|
||||
value={pageviews.value}
|
||||
|
26
src/server/model/_schema/filter.ts
Normal file
26
src/server/model/_schema/filter.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const websiteFilterSchema = z.object({
|
||||
timezone: z.string(),
|
||||
url: z.string(),
|
||||
referrer: z.string(),
|
||||
title: z.string(),
|
||||
os: z.string(),
|
||||
browser: z.string(),
|
||||
device: z.string(),
|
||||
country: z.string(),
|
||||
region: z.string(),
|
||||
city: z.string(),
|
||||
});
|
||||
|
||||
const websiteStatsItemType = z.object({
|
||||
value: z.number(),
|
||||
change: z.number(),
|
||||
});
|
||||
|
||||
export const websiteStatsSchema = z.object({
|
||||
bounces: websiteStatsItemType,
|
||||
pageviews: websiteStatsItemType,
|
||||
totaltime: websiteStatsItemType,
|
||||
uniques: websiteStatsItemType,
|
||||
});
|
@ -91,83 +91,3 @@ workspaceRouter.get(
|
||||
res.json({ pageviews, sessions });
|
||||
}
|
||||
);
|
||||
|
||||
workspaceRouter.get(
|
||||
'/:workspaceId/website/:websiteId/stats',
|
||||
validate(
|
||||
param('workspaceId').isString(),
|
||||
param('websiteId').isString(),
|
||||
query('startAt').isNumeric().withMessage('startAt should be number'),
|
||||
query('endAt').isNumeric().withMessage('startAt should be number')
|
||||
),
|
||||
auth(),
|
||||
workspacePermission(),
|
||||
async (req, res) => {
|
||||
const workspaceId = req.params.workspaceId;
|
||||
const websiteId = req.params.websiteId;
|
||||
const {
|
||||
timezone,
|
||||
url,
|
||||
referrer,
|
||||
title,
|
||||
os,
|
||||
browser,
|
||||
device,
|
||||
country,
|
||||
region,
|
||||
city,
|
||||
startAt,
|
||||
endAt,
|
||||
} = req.query;
|
||||
|
||||
const { startDate, endDate, unit } = await parseDateRange({
|
||||
websiteId,
|
||||
startAt: Number(startAt),
|
||||
endAt: Number(endAt),
|
||||
unit: String(req.query.unit),
|
||||
});
|
||||
|
||||
const diff = dayjs(endDate).diff(startDate, 'minutes');
|
||||
const prevStartDate = dayjs(startDate).subtract(diff, 'minutes').toDate();
|
||||
const prevEndDate = dayjs(endDate).subtract(diff, 'minutes').toDate();
|
||||
|
||||
const filters = {
|
||||
startDate,
|
||||
endDate,
|
||||
timezone,
|
||||
unit,
|
||||
url,
|
||||
referrer,
|
||||
title,
|
||||
os,
|
||||
browser,
|
||||
device,
|
||||
country,
|
||||
region,
|
||||
city,
|
||||
} as QueryFilters;
|
||||
|
||||
const [metrics, prevPeriod] = await Promise.all([
|
||||
getWorkspaceWebsiteStats(websiteId, {
|
||||
...filters,
|
||||
startDate,
|
||||
endDate,
|
||||
}),
|
||||
getWorkspaceWebsiteStats(websiteId, {
|
||||
...filters,
|
||||
startDate: prevStartDate,
|
||||
endDate: prevEndDate,
|
||||
}),
|
||||
]);
|
||||
|
||||
const stats = Object.keys(metrics[0]).reduce((obj, key) => {
|
||||
obj[key] = {
|
||||
value: Number(metrics[0][key]) || 0,
|
||||
change: Number(metrics[0][key]) - Number(prevPeriod[0][key]) || 0,
|
||||
};
|
||||
return obj;
|
||||
}, {} as Record<string, { value: number; change: number }>);
|
||||
|
||||
res.json({ stats });
|
||||
}
|
||||
);
|
||||
|
@ -18,7 +18,16 @@ import { getSessionMetrics, getPageviewMetrics } from '../../model/website';
|
||||
import { websiteInfoSchema } from '../../model/_schema';
|
||||
import { OpenApiMeta } from 'trpc-openapi';
|
||||
import { hostnameRegex } from '@tianji/shared';
|
||||
import { addWorkspaceWebsite } from '../../model/workspace';
|
||||
import {
|
||||
addWorkspaceWebsite,
|
||||
getWorkspaceWebsiteStats,
|
||||
} from '../../model/workspace';
|
||||
import {
|
||||
websiteFilterSchema,
|
||||
websiteStatsSchema,
|
||||
} from '../../model/_schema/filter';
|
||||
import dayjs from 'dayjs';
|
||||
import { QueryFilters } from '../../utils/prisma';
|
||||
|
||||
const websiteNameSchema = z.string().max(100);
|
||||
const websiteDomainSchema = z.union([
|
||||
@ -93,6 +102,91 @@ export const websiteRouter = router({
|
||||
|
||||
return website;
|
||||
}),
|
||||
stats: workspaceProcedure
|
||||
.meta(
|
||||
buildWebsiteOpenapi({
|
||||
method: 'GET',
|
||||
path: '/stats',
|
||||
})
|
||||
)
|
||||
.input(
|
||||
z
|
||||
.object({
|
||||
websiteId: z.string(),
|
||||
startAt: z.number(),
|
||||
endAt: z.number(),
|
||||
unit: z.string().optional(),
|
||||
})
|
||||
.merge(websiteFilterSchema.partial())
|
||||
)
|
||||
.output(websiteStatsSchema)
|
||||
.query(async ({ input }) => {
|
||||
const {
|
||||
websiteId,
|
||||
timezone,
|
||||
url,
|
||||
referrer,
|
||||
title,
|
||||
os,
|
||||
browser,
|
||||
device,
|
||||
country,
|
||||
region,
|
||||
city,
|
||||
startAt,
|
||||
endAt,
|
||||
} = input;
|
||||
|
||||
const { startDate, endDate, unit } = await parseDateRange({
|
||||
websiteId,
|
||||
startAt: Number(startAt),
|
||||
endAt: Number(endAt),
|
||||
unit: input.unit,
|
||||
});
|
||||
|
||||
const diff = dayjs(endDate).diff(startDate, 'minutes');
|
||||
const prevStartDate = dayjs(startDate).subtract(diff, 'minutes').toDate();
|
||||
const prevEndDate = dayjs(endDate).subtract(diff, 'minutes').toDate();
|
||||
|
||||
const filters = {
|
||||
startDate,
|
||||
endDate,
|
||||
timezone,
|
||||
unit,
|
||||
url,
|
||||
referrer,
|
||||
title,
|
||||
os,
|
||||
browser,
|
||||
device,
|
||||
country,
|
||||
region,
|
||||
city,
|
||||
} as QueryFilters;
|
||||
|
||||
const [metrics, prevPeriod] = await Promise.all([
|
||||
getWorkspaceWebsiteStats(websiteId, {
|
||||
...filters,
|
||||
startDate,
|
||||
endDate,
|
||||
}),
|
||||
getWorkspaceWebsiteStats(websiteId, {
|
||||
...filters,
|
||||
startDate: prevStartDate,
|
||||
endDate: prevEndDate,
|
||||
}),
|
||||
]);
|
||||
|
||||
const stats = Object.keys(metrics[0]).reduce((obj, key) => {
|
||||
obj[key] = {
|
||||
value: Number(metrics[0][key]) || 0,
|
||||
change: Number(metrics[0][key]) - Number(prevPeriod[0][key]) || 0,
|
||||
};
|
||||
return obj;
|
||||
}, {} as Record<string, { value: number; change: number }>);
|
||||
|
||||
return websiteStatsSchema.parse(stats);
|
||||
}),
|
||||
metrics: workspaceProcedure
|
||||
.meta(
|
||||
buildWebsiteOpenapi({
|
||||
|
Loading…
Reference in New Issue
Block a user