Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a3b2e0b
add clickhouse docker
Tim-53 Apr 26, 2024
33c2a91
wip
Tim-53 Jul 4, 2024
b7b6aa4
Merge remote-tracking branch 'origin/main' into feat/analytics_clickh…
Tim-53 Jul 4, 2024
0d5aac6
Save event in clickhouse
Tim-53 Jul 4, 2024
e9083a2
Add get Event by projectID
Tim-53 Jul 5, 2024
0fbc973
Save events in prisma and clickhouse with same id
Tim-53 Jul 5, 2024
429e83c
Feat: calculate test data on server
Tim-53 Jul 7, 2024
e632f09
Fix: best variant calculation
Tim-53 Jul 7, 2024
7ee0077
FEAT: load test data from clickhouse
Tim-53 Jul 7, 2024
916e23d
Feat: parse clickhouse result with zod
Tim-53 Jul 8, 2024
84cda10
Prevent oom with 1mio events in db
Tim-53 Jul 9, 2024
f836d01
Merge branch 'feat/analytics_clickhouse_db' of https://github.com/try…
Tim-53 Jul 9, 2024
4030222
Get aggregated Events via clickhouse
Tim-53 Jul 9, 2024
0ffa447
wip
Tim-53 Jul 10, 2024
e7ca898
Fix scale for high test volumes
Tim-53 Jul 20, 2024
4cae247
Remove Event Service
Tim-53 Jul 20, 2024
9431e15
clean up
Tim-53 Jul 20, 2024
1e07e39
Merge remote-tracking branch 'origin/main' into feat/analytics_clickh…
Tim-53 Jul 20, 2024
e5f43ef
Merge remote-tracking branch 'origin/main' into feat/analytics_clickh…
Tim-53 Jul 26, 2024
61de3da
add csv generation for sample data
Tim-53 Jul 28, 2024
1adaa80
Move ApiRequest into clickchouse
Tim-53 Jul 28, 2024
f69dabb
.
Tim-53 Jul 28, 2024
492c17a
init setup bullmq-receiver
Tim-53 Jul 30, 2024
14a492c
Merge remote-tracking branch 'origin/main' into feat/analytics_clickh…
Tim-53 Aug 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"eslint.workingDirectories": ["apps", "packages"],
"prettier.enable": true
"prettier.enable": true,
"cSpell.words": ["clvh", "clyetopos"]
}
22 changes: 22 additions & 0 deletions apps/bullmq-receiver/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "bullmq-receiver",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "ts-node-dev --respawn --transpile-only src/index.ts",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"bullmq": "^5.8.2",
"ioredis": "^5.4.1"
},
"devDependencies": {
"@types/node": "^14.14.31",
"ts-node-dev": "^1.1.6",
"typescript": "^4.2.3"
}
}
1 change: 1 addition & 0 deletions apps/bullmq-receiver/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log("hi");
64 changes: 64 additions & 0 deletions apps/web/clickhouse/createDatabase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { createClient } from "@clickhouse/client";
import { Prisma, PrismaClient } from "@prisma/client";
import { AbbyEventType } from "@tryabby/core";

const client = createClient({
url: "http://localhost:8123",
});
Comment on lines +5 to +7
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use environment variables for the ClickHouse client URL.

Hardcoding URLs can cause issues when deploying to different environments. Consider using environment variables for better flexibility.

- const client = createClient({
-   url: "http://localhost:8123",
- });
+ const client = createClient({
+   url: process.env.CLICKHOUSE_URL || "http://localhost:8123",
+ });
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const client = createClient({
url: "http://localhost:8123",
});
const client = createClient({
url: process.env.CLICKHOUSE_URL || "http://localhost:8123",
});


// client
// .command({
// query: "CREATE DATABASE IF NOT EXISTS abby",
// })
// .then((res) => {
// client.command({
// query: `
// DROP TABLE IF EXISTS abby.Event;
// `,
// });
// })
// .then((res) => {
// client.command({
// query: `
// CREATE TABLE IF NOT EXISTS abby.Event (
// id UUID,
// project_id String,
// testName String,
// type Int,
// selectedVariant String,
// createdAt DateTime DEFAULT toDateTime(now()) NOT NULL,
// )
// ENGINE = MergeTree()
// ORDER BY (project_id, testName)
// `,
// });
// })
// .catch((error) => {
// console.error("Error creating table:", error);
// });

async function insertEvents() {
const projectId = "clvh4sv5n0001furg6tj08z63";
const testName = "clyetopos0001yd6wa4yybkvw";

for (let i = 0; i < 100_000; i++) {
await client.insert({
table: "abby.Event",
format: "JSONEachRow",
values: [
{
project_id: projectId,
testName: testName,
type: Math.random() < 0.5 ? AbbyEventType.PING : AbbyEventType.ACT,
selectedVariant:
Math.random() < 0.5 ? "New Variant 1" : "New Variant 2",
createdAt:
Math.floor(Date.now() / 1000) -
Math.floor(Math.random() * 24 * 60 * 60),
},
],
});
}
Comment on lines +40 to +61
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handle potential errors and rate limits in insertEvents.

Inserting 100,000 events without error handling or rate limiting can lead to issues. Consider adding error handling and rate limiting.

async function insertEvents() {
  const projectId = "clvh4sv5n0001furg6tj08z63";
  const testName = "clyetopos0001yd6wa4yybkvw";

  for (let i = 0; i < 100_000; i++) {
    try {
      await client.insert({
        table: "abby.Event",
        format: "JSONEachRow",
        values: [
          {
            project_id: projectId,
            testName: testName,
            type: Math.random() < 0.5 ? AbbyEventType.PING : AbbyEventType.ACT,
            selectedVariant:
              Math.random() < 0.5 ? "New Variant 1" : "New Variant 2",
            createdAt:
              Math.floor(Date.now() / 1000) -
              Math.floor(Math.random() * 24 * 60 * 60),
          },
        ],
      });
    } catch (error) {
      console.error("Error inserting event:", error);
    }

    // Optional: Add rate limiting
    if (i % 1000 === 0) {
      await new Promise((resolve) => setTimeout(resolve, 100)); // Rate limit
    }
  }
}
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function insertEvents() {
const projectId = "clvh4sv5n0001furg6tj08z63";
const testName = "clyetopos0001yd6wa4yybkvw";
for (let i = 0; i < 100_000; i++) {
await client.insert({
table: "abby.Event",
format: "JSONEachRow",
values: [
{
project_id: projectId,
testName: testName,
type: Math.random() < 0.5 ? AbbyEventType.PING : AbbyEventType.ACT,
selectedVariant:
Math.random() < 0.5 ? "New Variant 1" : "New Variant 2",
createdAt:
Math.floor(Date.now() / 1000) -
Math.floor(Math.random() * 24 * 60 * 60),
},
],
});
}
async function insertEvents() {
const projectId = "clvh4sv5n0001furg6tj08z63";
const testName = "clyetopos0001yd6wa4yybkvw";
for (let i = 0; i < 100_000; i++) {
try {
await client.insert({
table: "abby.Event",
format: "JSONEachRow",
values: [
{
project_id: projectId,
testName: testName,
type: Math.random() < 0.5 ? AbbyEventType.PING : AbbyEventType.ACT,
selectedVariant:
Math.random() < 0.5 ? "New Variant 1" : "New Variant 2",
createdAt:
Math.floor(Date.now() / 1000) -
Math.floor(Math.random() * 24 * 60 * 60),
},
],
});
} catch (error) {
console.error("Error inserting event:", error);
}
// Optional: Add rate limiting
if (i % 1000 === 0) {
await new Promise((resolve) => setTimeout(resolve, 100)); // Rate limit
}
}
}

}

insertEvents();
3 changes: 3 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@
"seed:events": "ts-node --compiler-options {\\\"module\\\":\\\"CommonJS\\\"} prisma/seedEvents.ts",
"db:migrate": "prisma migrate dev",
"generate:coupons": "pnpm ts-node -r tsconfig-paths/register --compiler-options {\\\"module\\\":\\\"CommonJS\\\"} prisma/generateCoupons.ts",
"clickhouse:migrate": "pnpm ts-node -r tsconfig-paths/register --compiler-options {\\\"module\\\":\\\"CommonJS\\\"} clickhouse/setupDB.ts",
"clickhouse:create": "pnpm ts-node -r tsconfig-paths/register --compiler-options {\\\"module\\\":\\\"CommonJS\\\"} clickhouse/createDatabase.ts",
"mailhog:up": "docker-compose -f docker-compose.mailhog.yaml up",
"mailhog:down": "docker-compose -f docker-compose.mailhog.yaml down",
"test": "vitest",
"start:docker": "npx prisma migrate deploy || { echo 'Migration failed, exiting'; exit 1; } && node server.js"
},
"dependencies": {
"@clickhouse/client": "^1.0.1",
"@code-hike/mdx": "0.9.0",
"@databases/cache": "^1.0.0",
"@dnd-kit/core": "^6.0.8",
Expand Down
5 changes: 1 addition & 4 deletions apps/web/src/api/routes/v1_event.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import { zValidator } from "@hono/zod-validator";
import { abbyEventSchema, AbbyEventType } from "@tryabby/core";
import { abbyEventSchema } from "@tryabby/core";
import { Hono } from "hono";

import isbot from "isbot";
import { EventService } from "server/services/EventService";
import { RequestCache } from "server/services/RequestCache";
import { RequestService } from "server/services/RequestService";
import { checkRateLimit } from "server/common/ratelimit";
import { eventQueue } from "server/queue/queues";

Expand Down
33 changes: 12 additions & 21 deletions apps/web/src/components/Test/Metrics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ export const OPTIONS: ChartOptions<"bar"> = {
maintainAspectRatio: false,
scales: {
y: {
min: 0,
max: 100,
beginAtZero: true,
},
},

plugins: {
legend: {
position: "top" as const,
Expand All @@ -42,28 +42,18 @@ export const OPTIONS: ChartOptions<"bar"> = {
};

const Metrics = ({
pingEvents,
options,
visitData,
}: {
pingEvents: Event[];
options: ClientOption[];
visitData: (ClientOption & { actEventCount: number })[];
}) => {
const labels = options.map((option) => option.identifier);
const actualData = useMemo(() => {
return options.map((option) => {
return {
pings: pingEvents.filter(
(event) => event.selectedVariant === option.identifier
).length,
weight: option.chance,
};
});
}, [options, pingEvents]);
const labels = visitData.map((data) => data.identifier);

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

console.log(absPings, visitData);

return (
<div className="relative mb-6 h-full w-full">
<Bar
Expand All @@ -74,12 +64,13 @@ const Metrics = ({
datasets: [
{
label: "Actual",
data: actualData.map((d) => d.pings),
data: visitData.map((data) => data.actEventCount),

backgroundColor: "#A9E4EF",
},
{
label: "Expected",
data: actualData.map((data) => absPings * data.weight),
data: visitData.map((data) => absPings * data.chance),
backgroundColor: "#f472b6",
},
],
Expand Down
84 changes: 40 additions & 44 deletions apps/web/src/components/Test/Section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,27 @@ import { TitleEdit } from "components/TitleEdit";
import { Modal } from "components/Modal";
import { cn } from "lib/utils";

function getBestVariant({
absPings,
options,
}: {
absPings: number;
options: ClientOption[];
}) {
const bestVariant = options.reduce(
(accumulator, option) => {
const pings = absPings * option.chance;
if (pings > accumulator.pings) {
return {
pings,
identifier: option.identifier,
};
}
return accumulator;
},
{ pings: 0, identifier: "" }
);
function getBestVariant(visitData: VisitData) {
const absPings = visitData
.map((data) => data.actEventCount)
.reduce((acc, curr) => acc + curr);

return bestVariant;
const diffToExpected = visitData.map((data) => {
return {
variantName: data.variantName,
difference: data.actEventCount - data.chance * absPings,
};
});

let currentMax = diffToExpected[0];

diffToExpected.forEach((diff) => {
if (currentMax!.difference < diff.difference) {
currentMax = diff;
}
});

return currentMax?.variantName;
}

const DeleteTestModal = ({
Expand Down Expand Up @@ -125,24 +124,31 @@ export const Card = ({
);
};

export type VisitData = {
visitedEventCount: number;
actEventCount: number;
id: string;
identifier: string;
testId: string;
chance: number;

variantName: string;
}[];

const Section = ({
name,
options = [],
events = [],
id,
}: Test & {
options: ClientOption[];
events: Event[];
visitData,
}: {
name: string;
id: string;
visitData: VisitData;
}) => {
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,
options,
}).identifier;
const bestVariant = getBestVariant(visitData);

const { mutate: updateTestName } = trpc.tests.updateName.useMutation({
onSuccess() {
Expand Down Expand Up @@ -190,7 +196,7 @@ const Section = ({
</p>
}
>
<Weights options={options} />
<Weights options={visitData} />
</Card>
<Card
title="Visits"
Expand All @@ -201,12 +207,7 @@ const Section = ({
</p>
}
>
<Serves
options={options}
pingEvents={events.filter(
(event) => event.type === AbbyEventType.PING
)}
/>
<Serves visitData={visitData} />
</Card>
<Card
title="Interactions"
Expand All @@ -220,12 +221,7 @@ const Section = ({
</p>
}
>
<Metrics
options={options}
pingEvents={events.filter(
(event) => event.type === AbbyEventType.ACT
)}
/>
<Metrics visitData={visitData} />
</Card>
</div>
<div className="mt-3 flex">
Expand Down
35 changes: 12 additions & 23 deletions apps/web/src/components/Test/Serves.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,28 +57,17 @@ export const OPTIONS: ChartOptions<"bar"> = {
};

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

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

const absPings = actualData.reduce((accumulator, value) => {
return accumulator + value;
const labels = visitData.map((data) => data.identifier);
const absPings = visitData.reduce((accumulator, value) => {
return accumulator + value.visitedEventCount;
}, 0);

return (
<div className="relative h-full w-full">
<div className="relative h-full w-full ">
<Bar
className="self-end"
options={OPTIONS}
Expand All @@ -87,16 +76,16 @@ const Serves = ({
datasets: [
{
label: "Target",
data: options.map(
(option) => parseFloat(option.chance.toString()) * 100
),
data: visitData.map((data) => {
return parseFloat(data.chance.toString()) * 100;
}),
backgroundColor: "#A9E4EF",
},
{
label: "Actual",
data: actualData.map((data) =>
Math.round((data / absPings) * 100)
),
data: visitData.map((data) => {
return Math.round((data.visitedEventCount / absPings) * 100);
}),
backgroundColor: "#f472b6",
},
],
Expand Down
Loading