Skip to content

Commit

Permalink
feat(web): improve ab testing overview page (#159)
Browse files Browse the repository at this point in the history
  • Loading branch information
cstrnt authored Aug 22, 2024
1 parent d0fac0b commit ce663df
Show file tree
Hide file tree
Showing 9 changed files with 83 additions and 91 deletions.
23 changes: 4 additions & 19 deletions apps/web/src/components/Test/Metrics.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,22 @@
import type { Event } from "@prisma/client";
import { DonutChart } from "components/charts/Donut";
import { useMemo } from "react";
import type { ProjectClientEvents } from "pages/projects/[projectId]";
import type { ClientOption } from "server/trpc/router/project";

const Metrics = ({
actEvents,
options,
}: {
actEvents: Event[];
actEvents: ProjectClientEvents;
options: ClientOption[];
}) => {
const labels = options.map((option) => option.identifier);
const actualData = useMemo(() => {
return options.map((option) => {
return {
variant: option.identifier,
events: actEvents.filter(
(event) => event.selectedVariant === option.identifier
).length,
};
});
}, [options, actEvents]);

const absPings = actualData.reduce((accumulator, value) => {
return accumulator + value.events;
}, 0);

return (
<div className="relative h-full w-full">
<DonutChart
totalVisits={absPings}
totalVisits={actEvents.reduce((acc, e) => acc + e._count._all, 0)}
variants={labels}
events={actualData}
events={actEvents}
totalText="Interactions"
/>
</div>
Expand Down
26 changes: 9 additions & 17 deletions apps/web/src/components/Test/Section.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { Event, Test } from "@prisma/client";
import type { Test } from "@prisma/client";
import * as Popover from "@radix-ui/react-popover";
import { AbbyEventType } from "@tryabby/core";
import { Modal } from "components/Modal";
import { TitleEdit } from "components/TitleEdit";
import { Button } from "components/ui/button";
import { useFeatureFlag } from "lib/abby";
import { cn } from "lib/utils";
import Link from "next/link";
import { useRouter } from "next/router";
import type { ProjectClientEvents } from "pages/projects/[projectId]";
import { type ReactNode, useState } from "react";
import { toast } from "react-hot-toast";
import { AiOutlineDelete } from "react-icons/ai";
Expand Down Expand Up @@ -128,19 +128,21 @@ export const Card = ({
const Section = ({
name,
options = [],
events = [],
actEvents,
pingEvents,
id,
}: Test & {
options: ClientOption[];
events: Event[];
pingEvents: ProjectClientEvents;
actEvents: ProjectClientEvents;
}) => {
const router = useRouter();
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const trpcContext = trpc.useContext();
const showAdvancedTestStats = useFeatureFlag("AdvancedTestStats");

const bestVariant = getBestVariant({
absPings: events.filter((event) => event.type === AbbyEventType.ACT).length,
absPings: actEvents.length + pingEvents.length,
options,
}).identifier;

Expand Down Expand Up @@ -201,12 +203,7 @@ const Section = ({
</p>
}
>
<Serves
options={options}
pingEvents={events.filter(
(event) => event.type === AbbyEventType.PING
)}
/>
<Serves options={options} pingEvents={pingEvents} />
</Card>
<Card
title="Interactions"
Expand All @@ -220,12 +217,7 @@ const Section = ({
</p>
}
>
<Metrics
options={options}
actEvents={events.filter(
(event) => event.type === AbbyEventType.ACT
)}
/>
<Metrics options={options} actEvents={actEvents} />
</Card>
</div>
<div className="mt-3 flex">
Expand Down
24 changes: 4 additions & 20 deletions apps/web/src/components/Test/Serves.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,22 @@
import type { Event } from "@prisma/client";
import { DonutChart } from "components/charts/Donut";
import { useMemo } from "react";
import type { ProjectClientEvents } from "pages/projects/[projectId]";
import type { ClientOption } from "server/trpc/router/project";

const Serves = ({
pingEvents,
options,
}: {
pingEvents: Event[];
pingEvents: ProjectClientEvents;
options: ClientOption[];
}) => {
const labels = options.map((option) => option.identifier);

const actualData = useMemo(() => {
return options.map((option) => {
return {
variant: option.identifier,
events: pingEvents.filter(
(event) => event.selectedVariant === option.identifier
).length,
};
});
}, [options, pingEvents]);

const absPings = actualData.reduce((accumulator, value) => {
return accumulator + value.events;
}, 0);

return (
<div className="relative h-full w-full">
<DonutChart
totalVisits={absPings}
totalVisits={pingEvents.reduce((acc, e) => acc + e._count._all, 0)}
variants={labels}
events={actualData}
events={pingEvents}
totalText="Visits"
/>
</div>
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/analytics/EventGraph.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import * as React from "react";
import { LineChart, CartesianGrid, XAxis, YAxis, Line } from "recharts";
import { CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts";

import {
Card,
Expand Down
9 changes: 5 additions & 4 deletions apps/web/src/components/charts/Donut.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
ChartTooltip,
ChartTooltipContent,
} from "components/ui/chart";
import type { ProjectClientEvents } from "pages/projects/[projectId]";
import { useMemo } from "react";

export function DonutChart({
Expand All @@ -21,7 +22,7 @@ export function DonutChart({
totalVisits: number;
totalText: string;
variants: string[];
events: Array<{ variant: string; events: number }>;
events: ProjectClientEvents;
}) {
const chartConfig = useMemo(
() =>
Expand All @@ -42,9 +43,9 @@ export function DonutChart({

const chartData = useMemo(() => {
return events.map((event) => ({
variant: event.variant,
events: event.events,
fill: `var(--color-${event.variant})`,
variant: event.selectedVariant,
events: event._count._all,
fill: `var(--color-${event.selectedVariant})`,
}));
}, [events]);

Expand Down
22 changes: 11 additions & 11 deletions apps/web/src/components/ui/tabs.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import * as TabsPrimitive from "@radix-ui/react-tabs";
import * as React from "react";

import { cn } from "lib/utils"
import { cn } from "lib/utils";

const Tabs = TabsPrimitive.Root
const Tabs = TabsPrimitive.Root;

const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
Expand All @@ -17,8 +17,8 @@ const TabsList = React.forwardRef<
)}
{...props}
/>
))
TabsList.displayName = TabsPrimitive.List.displayName
));
TabsList.displayName = TabsPrimitive.List.displayName;

const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
Expand All @@ -32,8 +32,8 @@ const TabsTrigger = React.forwardRef<
)}
{...props}
/>
))
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
));
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;

const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
Expand All @@ -47,7 +47,7 @@ const TabsContent = React.forwardRef<
)}
{...props}
/>
))
TabsContent.displayName = TabsPrimitive.Content.displayName
));
TabsContent.displayName = TabsPrimitive.Content.displayName;

export { Tabs, TabsList, TabsTrigger, TabsContent }
export { Tabs, TabsList, TabsTrigger, TabsContent };
12 changes: 11 additions & 1 deletion apps/web/src/pages/projects/[projectId]/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { inferRouterOutputs } from "@trpc/server";
import { AddABTestModal } from "components/AddABTestModal";
import { DashboardHeader } from "components/DashboardHeader";
import { Layout } from "components/Layout";
Expand All @@ -9,8 +10,12 @@ import type { GetStaticPaths, GetStaticProps } from "next";
import type { NextPageWithLayout } from "pages/_app";
import { useState } from "react";
import { AiOutlinePlus } from "react-icons/ai";
import type { AppRouter } from "server/trpc/router/_app";
import { trpc } from "utils/trpc";

export type ProjectClientEvents =
inferRouterOutputs<AppRouter>["project"]["getProjectData"]["project"]["tests"][number]["pingEvents"];

const Projects: NextPageWithLayout = () => {
const [isCreateTestModalOpen, setIsCreateTestModalOpen] = useState(false);

Expand Down Expand Up @@ -59,7 +64,12 @@ const Projects: NextPageWithLayout = () => {
</div>
<div className="space-y-8">
{data?.project?.tests.map((test) => (
<Section key={test.id} {...test} events={test.events} />
<Section
key={test.id}
{...test}
actEvents={test.actEvents}
pingEvents={test.pingEvents}
/>
))}
</div>
</>
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/pages/projects/[projectId]/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ import { getProjectPaidPlan, useAbbyStripe } from "lib/stripe";
import { useTracking } from "lib/tracking";
import type { GetStaticPaths, GetStaticProps } from "next";
import { useSession } from "next-auth/react";
import Link from "next/link";
import { useRouter } from "next/router";
import { parseAsStringLiteral, useQueryState } from "nuqs";
import type { NextPageWithLayout } from "pages/_app";
import { type FormEvent, useRef, useState } from "react";
import { toast } from "react-hot-toast";
import { getLimitByPlan } from "server/common/plans";
import { trpc } from "utils/trpc";
import { parseAsStringLiteral, useQueryState } from "nuqs";
import Link from "next/link";

const SETTINGS_TABS = {
General: "general",
Expand Down
52 changes: 36 additions & 16 deletions apps/web/src/server/trpc/router/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import { z } from "zod";
export type ClientOption = Omit<Option, "chance"> & {
chance: number;
};

import ms from "ms";
import { AbbyEventType } from "@tryabby/core";
import dayjs from "dayjs";
import { updateProjectsOnSession } from "utils/updateSession";
import { protectedProcedure, router } from "../trpc";

Expand All @@ -32,13 +32,6 @@ export const projectRouter = router({
tests: {
include: {
options: true,
events: {
where: {
createdAt: {
gte: new Date(Date.now() - ms("30d")),
},
},
},
},
},
environments: true,
Expand All @@ -53,17 +46,44 @@ export const projectRouter = router({
const { events: eventsThisPeriod } =
await EventService.getEventsForCurrentPeriod(project.id);

const events = await ctx.prisma.event.groupBy({
by: ["selectedVariant", "type", "testId"],
_count: { _all: true },
where: {
testId: { in: project.tests.map((test) => test.id) },
createdAt: {
gte: dayjs().subtract(30, "days").toDate(),
},
},
});

return {
project: {
...project,
eventsThisPeriod,
tests: project.tests.map((test) => ({
...test,
options: test.options.map((option) => ({
...option,
chance: option.chance.toNumber(),
})),
})),
tests: project.tests.map((test) => {
const actEvents: typeof events = [];
const pingEvents: typeof events = [];
for (const event of events) {
if (event.testId === test.id) {
if (event.type === AbbyEventType.ACT) {
actEvents.push(event);
} else {
pingEvents.push(event);
}
}
}
return {
...test,

actEvents,
pingEvents,
options: test.options.map((option) => ({
...option,
chance: option.chance.toNumber(),
})),
};
}),
},
};
}),
Expand Down

0 comments on commit ce663df

Please sign in to comment.