Skip to content
This repository was archived by the owner on Sep 17, 2024. It is now read-only.

Commit 52bc217

Browse files
committed
feat: push_event and query_instant
1 parent 9c0ad96 commit 52bc217

File tree

9 files changed

+91
-22
lines changed

9 files changed

+91
-22
lines changed

Diff for: modules/analytics/db/migrations/20240813023547_init/migration.sql

-16
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
-- CreateTable
2+
CREATE TABLE "Event" (
3+
"id" UUID NOT NULL,
4+
"timestamp" TIMESTAMPTZ NOT NULL,
5+
"name" TEXT NOT NULL,
6+
"metadata" JSONB
7+
);
8+
9+
-- CreateIndex
10+
CREATE UNIQUE INDEX "Event_id_key" ON "Event"("id");
11+
12+
-- CreateIndex
13+
CREATE INDEX "Event_name_time_idx" ON "Event"("name", "timestamp" DESC);

Diff for: modules/analytics/db/schema.prisma

+1-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,5 @@ model Event {
1212
1313
// in the init migration, we add the timescale extension and call create_hypertable().
1414
15-
@@index(fields: [name, timestamp(sort: Desc)], map: "event_name_time_idx")
16-
@@map("event")
15+
@@index(fields: [name, timestamp(sort: Desc)], map: "Event_name_time_idx")
1716
}

Diff for: modules/analytics/module.json

+3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
"scripts": {
1212
"push_event": {
1313
"public": true
14+
},
15+
"query_instant": {
16+
"public": true
1417
}
1518
},
1619
"errors": {},

Diff for: modules/analytics/scripts/push_event.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ScriptContext } from "../module.gen.ts";
2-
import { createHypertable } from "../utils/hypertable_init.ts";
2+
import { checkHypertable } from "../utils/hypertable_init.ts";
33

44
export interface Request {
55
name: string,
@@ -16,7 +16,7 @@ export async function run(
1616
ctx: ScriptContext,
1717
req: Request,
1818
): Promise<Response> {
19-
createHypertable(ctx);
19+
checkHypertable(ctx);
2020
const timestamp = req.timestampOverride ? new Date(req.timestampOverride) : new Date();
2121
const event = await ctx.db.event.create({
2222
data: {

Diff for: modules/analytics/scripts/query_instant.ts

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { ScriptContext } from "../module.gen.ts";
2+
import { checkHypertable } from "../utils/hypertable_init.ts";
3+
import { stringifyFilters } from "../utils/stringify_filters.ts";
4+
import { AggregationMethod, Filter } from "../utils/types.ts";
5+
6+
export interface Request {
7+
event: string;
8+
aggregate: AggregationMethod;
9+
filters: Filter[]
10+
groupBy: string[];
11+
startAt: number;
12+
stopAt: number;
13+
}
14+
15+
export interface Response {
16+
results: { groups: Record<string, any>, count: number}[]
17+
}
18+
19+
export async function run(
20+
ctx: ScriptContext,
21+
req: Request,
22+
): Promise<Response> {
23+
checkHypertable(ctx);
24+
25+
const props = req.groupBy.map((col) => `metadata->>'${col}'`);
26+
27+
// A query that counts the amount of events in the database, per name (should return an array of counts per name)
28+
// the name isn't an actual field but instead a value in the metadata field
29+
const result = await ctx.db.$queryRawUnsafe(`
30+
SELECT ${req.groupBy.map(col => `metadata->>'${col}' as _${col}`).join(', ')}, COUNT(*) as count
31+
FROM "${ctx.dbSchema}"."Event"
32+
WHERE name = '${req.event}'
33+
AND timestamp >= '${new Date(req.startAt).toISOString()}'
34+
AND timestamp <= '${new Date(req.stopAt).toISOString()}'
35+
${req.filters.length ? " AND " + stringifyFilters(req.filters) : ""}
36+
GROUP BY ${props.join(', ')}
37+
ORDER BY ${props.join(', ')}
38+
`) as any;
39+
40+
return {
41+
results: result.map((e: any) => ({
42+
// TODO: optimize
43+
groups: props.reduce<Record<string, any>>((acc, k) => (acc[k] = e["_" + k], acc), {}),
44+
count: e.count
45+
}))
46+
}
47+
}
48+

Diff for: modules/analytics/utils/hypertable_init.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { ScriptContext } from "../module.gen.ts";
22

33
let hasDefinitelyRun = false;
4-
export const createHypertable = async (ctx: ScriptContext) => {
4+
export const checkHypertable = async (ctx: ScriptContext) => {
55
if (hasDefinitelyRun) return;
66

7-
await ctx.db.$queryRaw`SELECT create_hypertable('event', 'timestamp');`;
7+
// await ctx.db.$queryRaw`SELECT create_hypertable('event', 'timestamp');`;
88

99
hasDefinitelyRun = true;
1010
}

Diff for: modules/analytics/utils/stringify_filters.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Filter } from "./types.ts";
2+
3+
export const stringifyFilters = (filters: Filter[]) => filters.map((filter: Filter) => {
4+
if ("greaterThan" in filter) return "(metadata->>'" + filter.greaterThan.key + "')::int" + " > " + filter.greaterThan.value;
5+
if ("lessThan" in filter) return "(metadata->>'" + filter.lessThan.key + "')::int" + " < " + filter.lessThan.value;
6+
if ("equals" in filter) return "(metadata->>'" + filter.equals.key + "')::int" + " = " + filter.equals.value;
7+
if ("notEquals" in filter) return "(metadata->>'" + filter.notEquals.key + "')::int" + " != " + filter.notEquals.value;
8+
if ("greaterThanOrEquals" in filter) return "(metadata->>'" + filter.greaterThanOrEquals.key + "')::int" + " >= " + filter.greaterThanOrEquals.value;
9+
if ("lessThanOrEquals" in filter) return "(metadata->>'" + filter.lessThanOrEquals.key + "')::int" + " <= " + filter.lessThanOrEquals.value;
10+
11+
throw new Error("Unknown filter type");
12+
}).join(' AND ');

Diff for: modules/analytics/utils/types.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export type AggregationMethod = { count: {} } |
2+
{ averageByKey: string } |
3+
{ sumByKey: string };
4+
5+
export type Filter = { greaterThan: { key: string, value: number } } |
6+
{ lessThan: { key: string, value: number } } |
7+
{ equals: { key: string, value: number } } |
8+
{ notEquals: { key: string, value: number } } |
9+
{ greaterThanOrEquals: { key: string, value: number } } |
10+
{ lessThanOrEquals: { key: string, value: number } };

0 commit comments

Comments
 (0)