feat: add others metrics in website detail

This commit is contained in:
moonrailgun 2023-10-07 19:32:46 +08:00
parent 0e71e073fc
commit 086c8ee3df
6 changed files with 85 additions and 29 deletions

View File

@ -3,7 +3,7 @@ import type { AppRouter } from '../../server/trpc/routers';
import { httpBatchLink, TRPCClientErrorLike } from '@trpc/client'; import { httpBatchLink, TRPCClientErrorLike } from '@trpc/client';
import { getJWT } from './auth'; import { getJWT } from './auth';
import { message } from 'antd'; import { message } from 'antd';
import { inferRouterInputs, inferRouterOutputs } from '@trpc/server'; import type { inferRouterInputs, inferRouterOutputs } from '@trpc/server';
export const trpc = createTRPCReact<AppRouter>(); export const trpc = createTRPCReact<AppRouter>();

View File

@ -1,11 +1,13 @@
import { Table } from 'antd'; import { Table } from 'antd';
import { ColumnsType } from 'antd/es/table/interface'; import { ColumnsType } from 'antd/es/table/interface';
import React from 'react'; import React from 'react';
import { trpc } from '../../api/trpc'; import { RouterOutput, trpc } from '../../api/trpc';
import { useCurrentWorkspaceId } from '../../store/user'; import { useCurrentWorkspaceId } from '../../store/user';
import { sum } from 'lodash-es'; import { sum } from 'lodash-es';
import millify from 'millify'; import millify from 'millify';
type MetricsItemType = RouterOutput['website']['metrics'][number];
interface MetricsTableProps { interface MetricsTableProps {
websiteId: string; websiteId: string;
title: [string, string]; title: [string, string];
@ -35,10 +37,11 @@ export const MetricsTable: React.FC<MetricsTableProps> = React.memo((props) => {
const total = sum(metrics.map((m) => m.y)); const total = sum(metrics.map((m) => m.y));
const columns: ColumnsType<{ x: string; y: number }> = [ const columns: ColumnsType<MetricsItemType> = [
{ {
title: title[0], title: title[0],
dataIndex: 'x', dataIndex: 'x',
render: (val) => val ?? <span className="italic">(None)</span>,
}, },
{ {
title: title[1], title: title[1],
@ -55,7 +58,7 @@ export const MetricsTable: React.FC<MetricsTableProps> = React.memo((props) => {
lowercase: true, lowercase: true,
})} })}
</div> </div>
<div className="inline-block w-10 relative border-l ml-1 pl-1"> <div className="inline-block w-12 relative border-l ml-1 px-1">
<div <div
className="bg-blue-300 absolute h-full bg-opacity-25 left-0 top-0 pointer-events-none" className="bg-blue-300 absolute h-full bg-opacity-25 left-0 top-0 pointer-events-none"
style={{ width: `${percent}%` }} style={{ width: `${percent}%` }}

View File

@ -1,12 +0,0 @@
import React from 'react';
import { MetricsTable } from './MetricsTable';
interface PagesTableProps {
websiteId: string;
startAt: number;
endAt: number;
}
export const PagesTable: React.FC<PagesTableProps> = React.memo((props) => {
return <MetricsTable {...props} type="url" title={['Pages', 'Views']} />;
});
PagesTable.displayName = 'PagesTable';

View File

@ -6,7 +6,7 @@ import { trpc } from '../../api/trpc';
import { ErrorTip } from '../../components/ErrorTip'; import { ErrorTip } from '../../components/ErrorTip';
import { Loading } from '../../components/Loading'; import { Loading } from '../../components/Loading';
import { NotFoundTip } from '../../components/NotFoundTip'; import { NotFoundTip } from '../../components/NotFoundTip';
import { PagesTable } from '../../components/website/PagesTable'; import { MetricsTable } from '../../components/website/MetricsTable';
import { WebsiteOverview } from '../../components/website/WebsiteOverview'; import { WebsiteOverview } from '../../components/website/WebsiteOverview';
import { useCurrentWorkspaceId } from '../../store/user'; import { useCurrentWorkspaceId } from '../../store/user';
@ -30,21 +30,83 @@ export const WebsiteDetail: React.FC = React.memo(() => {
return <NotFoundTip />; return <NotFoundTip />;
} }
const startAt = dayjs().subtract(1, 'day').unix() * 1000;
const endAt = dayjs().unix() * 1000;
return ( return (
<div className="py-6"> <div className="py-6">
<Card> <Card>
<Card.Grid hoverable={false} className="!w-full"> <Card.Grid hoverable={false} className="!w-full">
<WebsiteOverview website={website} /> <WebsiteOverview website={website} />
</Card.Grid> </Card.Grid>
<Card.Grid hoverable={false} className="!w-1/2"> <Card.Grid hoverable={false} className="!w-1/2 min-h-[470px]">
<PagesTable <MetricsTable
websiteId={websiteId} websiteId={websiteId}
startAt={dayjs().subtract(1, 'day').unix() * 1000} type="url"
endAt={dayjs().unix() * 1000} title={['Pages', 'Views']}
startAt={startAt}
endAt={endAt}
/> />
</Card.Grid> </Card.Grid>
<Card.Grid hoverable={false} className="!w-1/2"> <Card.Grid hoverable={false} className="!w-1/2 min-h-[470px]">
right <MetricsTable
websiteId={websiteId}
type="referrer"
title={['Referrers', 'Views']}
startAt={startAt}
endAt={endAt}
/>
</Card.Grid>
<Card.Grid hoverable={false} className="!w-1/3 min-h-[470px]">
<MetricsTable
websiteId={websiteId}
type="browser"
title={['Browser', 'Visitors']}
startAt={startAt}
endAt={endAt}
/>
</Card.Grid>
<Card.Grid hoverable={false} className="!w-1/3 min-h-[470px]">
<MetricsTable
websiteId={websiteId}
type="os"
title={['OS', 'Visitors']}
startAt={startAt}
endAt={endAt}
/>
</Card.Grid>
<Card.Grid hoverable={false} className="!w-1/3 min-h-[470px]">
<MetricsTable
websiteId={websiteId}
type="device"
title={['Devices', 'Visitors']}
startAt={startAt}
endAt={endAt}
/>
</Card.Grid>
<Card.Grid hoverable={false} className="!w-2/3 min-h-[470px]">
{/* Map */}
</Card.Grid>
<Card.Grid hoverable={false} className="!w-1/3 min-h-[470px]">
<MetricsTable
websiteId={websiteId}
type="country"
title={['Countries', 'Visitors']}
startAt={startAt}
endAt={endAt}
/>
</Card.Grid>
<Card.Grid hoverable={false} className="!w-1/3 min-h-[470px]">
<MetricsTable
websiteId={websiteId}
type="event"
title={['Events', 'Actions']}
startAt={startAt}
endAt={endAt}
/>
</Card.Grid>
<Card.Grid hoverable={false} className="!w-2/3 min-h-[470px]">
{/* Events */}
</Card.Grid> </Card.Grid>
</Card> </Card>
</div> </div>

View File

@ -292,7 +292,7 @@ export async function getSessionMetrics(
const includeCountry = column === 'city' || column === 'subdivision1'; const includeCountry = column === 'city' || column === 'subdivision1';
return prisma.$queryRaw`select return prisma.$queryRaw`select
${column} x, ${Prisma.sql([`"${column}"`])} x,
count(distinct "WebsiteEvent"."sessionId") y count(distinct "WebsiteEvent"."sessionId") y
${includeCountry ? Prisma.sql([', country']) : Prisma.empty} ${includeCountry ? Prisma.sql([', country']) : Prisma.empty}
from "WebsiteEvent" from "WebsiteEvent"

View File

@ -73,8 +73,8 @@ export const websiteRouter = router({
.output( .output(
z.array( z.array(
z.object({ z.object({
x: z.string(), x: z.string().nullable(),
y: z.bigint(), y: z.number(),
}) })
) )
) )
@ -137,16 +137,19 @@ export const websiteRouter = router({
} }
} }
return Object.values(combined); return Object.values(combined).map((d) => ({
x: d.x,
y: Number(d.y),
}));
} }
return data; return data.map((d) => ({ x: d.x, y: Number(d.y) }));
} }
if (EVENT_COLUMNS.includes(type)) { if (EVENT_COLUMNS.includes(type)) {
const data = await getPageviewMetrics(websiteId, column, filters); const data = await getPageviewMetrics(websiteId, column, filters);
return data; return data.map((d) => ({ x: d.x, y: Number(d.y) }));
} }
return []; return [];