fix: 🐛 speeds got wiped for some reason, add top event names
This commit is contained in:
4
server/src/external/tinybird/initTinybird.ts
vendored
4
server/src/external/tinybird/initTinybird.ts
vendored
@@ -3,6 +3,7 @@ import { z } from "zod"; // zod-bird requires zod v3, not zod/v4
|
||||
import { createAggregateGroupablePipe } from "./pipes/aggregateGroupablePipe.js";
|
||||
import { createAggregatePipe } from "./pipes/aggregatePipe.js";
|
||||
import { createAggregateSimplePipe } from "./pipes/aggregateSimplePipe.js";
|
||||
import { createListEventNamesPipe } from "./pipes/listEventNamesPipe.js";
|
||||
import { createListEventsPipe } from "./pipes/listEventsPipe.js";
|
||||
|
||||
const TINYBIRD_API_URL = process.env.TINYBIRD_API_URL;
|
||||
@@ -47,6 +48,7 @@ export const tinybirdPipes = tinybirdClient
|
||||
aggregateSimple: createAggregateSimplePipe(tinybirdClient),
|
||||
aggregateGroupable: createAggregateGroupablePipe(tinybirdClient),
|
||||
listEvents: createListEventsPipe(tinybirdClient),
|
||||
listEventNames: createListEventNamesPipe(tinybirdClient),
|
||||
}
|
||||
: null;
|
||||
|
||||
@@ -90,6 +92,8 @@ export type {
|
||||
AggregatePipeRow,
|
||||
AggregateSimplePipeParams,
|
||||
AggregateSimplePipeRow,
|
||||
ListEventNamesPipeParams,
|
||||
ListEventNamesPipeRow,
|
||||
ListEventsPipeParams,
|
||||
ListEventsPipeRow,
|
||||
} from "./pipes/index.js";
|
||||
|
||||
7
server/src/external/tinybird/pipes/index.ts
vendored
7
server/src/external/tinybird/pipes/index.ts
vendored
@@ -26,3 +26,10 @@ export {
|
||||
listEventsPipeParamsSchema,
|
||||
listEventsPipeResponseSchema,
|
||||
} from "./listEventsPipe.js";
|
||||
export {
|
||||
createListEventNamesPipe,
|
||||
type ListEventNamesPipeParams,
|
||||
type ListEventNamesPipeRow,
|
||||
listEventNamesPipeParamsSchema,
|
||||
listEventNamesPipeResponseSchema,
|
||||
} from "./listEventNamesPipe.js";
|
||||
|
||||
31
server/src/external/tinybird/pipes/listEventNamesPipe.ts
vendored
Normal file
31
server/src/external/tinybird/pipes/listEventNamesPipe.ts
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
import type { Tinybird } from "@chronark/zod-bird";
|
||||
import { z } from "zod";
|
||||
|
||||
/** Response schema for the list_event_names pipe */
|
||||
export const listEventNamesPipeResponseSchema = z.object({
|
||||
event_name: z.string(),
|
||||
event_count: z.number(),
|
||||
});
|
||||
|
||||
export type ListEventNamesPipeRow = z.infer<
|
||||
typeof listEventNamesPipeResponseSchema
|
||||
>;
|
||||
|
||||
/** Parameters schema for the list_event_names pipe */
|
||||
export const listEventNamesPipeParamsSchema = z.object({
|
||||
org_id: z.string(),
|
||||
env: z.string(),
|
||||
limit: z.number().optional(),
|
||||
});
|
||||
|
||||
export type ListEventNamesPipeParams = z.infer<
|
||||
typeof listEventNamesPipeParamsSchema
|
||||
>;
|
||||
|
||||
/** Creates the list_event_names pipe caller */
|
||||
export const createListEventNamesPipe = (tb: Tinybird) =>
|
||||
tb.buildPipe({
|
||||
pipe: "list_event_names",
|
||||
parameters: listEventNamesPipeParamsSchema,
|
||||
data: listEventNamesPipeResponseSchema,
|
||||
});
|
||||
@@ -2,6 +2,7 @@ import { aggregate } from "./aggregate.js";
|
||||
import { getCountAndSum } from "./getCountAndSum.js";
|
||||
import { getEventById } from "./getEventById.js";
|
||||
import { getTopEventNames } from "./getTopEventNames.js";
|
||||
import { listEventNames } from "./listEventNames.js";
|
||||
import { listRawEvents } from "./listRawEvents.js";
|
||||
|
||||
export const eventActions = {
|
||||
@@ -9,5 +10,6 @@ export const eventActions = {
|
||||
getCountAndSum,
|
||||
getEventById,
|
||||
getTopEventNames,
|
||||
listEventNames,
|
||||
listRawEvents,
|
||||
} as const;
|
||||
|
||||
34
server/src/internal/analytics/actions/listEventNames.ts
Normal file
34
server/src/internal/analytics/actions/listEventNames.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { getTinybirdPipes } from "@/external/tinybird/initTinybird.js";
|
||||
import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
|
||||
|
||||
export type EventNameWithCount = {
|
||||
event_name: string;
|
||||
event_count: number;
|
||||
};
|
||||
|
||||
/** Lists distinct event names for the org sorted by popularity */
|
||||
export const listEventNames = async ({
|
||||
ctx,
|
||||
limit,
|
||||
}: {
|
||||
ctx: AutumnContext;
|
||||
limit?: number;
|
||||
}): Promise<EventNameWithCount[]> => {
|
||||
const { org, env, logger } = ctx;
|
||||
const pipes = getTinybirdPipes();
|
||||
|
||||
const startTime = performance.now();
|
||||
|
||||
const result = await pipes.listEventNames({
|
||||
org_id: org.id,
|
||||
env,
|
||||
limit,
|
||||
});
|
||||
|
||||
logger.debug("Listed event names", {
|
||||
queryMs: Math.round(performance.now() - startTime),
|
||||
count: result.data.length,
|
||||
});
|
||||
|
||||
return result.data;
|
||||
};
|
||||
@@ -1,11 +1,13 @@
|
||||
import { Hono } from "hono";
|
||||
import type { HonoEnv } from "@/honoUtils/HonoEnv.js";
|
||||
import { handleGetEventNames } from "./internalHandlers/handleGetEventNames.js";
|
||||
import { handleListEventNames } from "./internalHandlers/handleListEventNames.js";
|
||||
import { handleQueryEvents } from "./internalHandlers/handleQueryEvents.js";
|
||||
import { handleQueryRawEvents } from "./internalHandlers/handleQueryRawEvents.js";
|
||||
|
||||
export const internalAnalyticsRouter = new Hono<HonoEnv>();
|
||||
|
||||
internalAnalyticsRouter.get("/event_names", ...handleGetEventNames);
|
||||
internalAnalyticsRouter.get("/event_names/list", ...handleListEventNames);
|
||||
internalAnalyticsRouter.post("/events", ...handleQueryEvents);
|
||||
internalAnalyticsRouter.post("/raw", ...handleQueryRawEvents);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { type Feature, FeatureType } from "@autumn/shared";
|
||||
import { createRoute } from "@/honoMiddlewares/routeHandler.js";
|
||||
import { queryWithCache } from "@/utils/cacheUtils/queryWithCache.js";
|
||||
import { eventActions } from "../actions/index.js";
|
||||
import { AnalyticsService } from "../AnalyticsService.js";
|
||||
|
||||
/**
|
||||
@@ -13,17 +13,8 @@ export const handleGetEventNames = createRoute({
|
||||
|
||||
AnalyticsService.handleEarlyExit();
|
||||
|
||||
const result = await queryWithCache({
|
||||
ttl: 3600,
|
||||
key: `top_events:${org.id}_${env}`,
|
||||
fn: async () => {
|
||||
const res = await AnalyticsService.getTopEventNames({
|
||||
ctx,
|
||||
});
|
||||
|
||||
return res?.eventNames;
|
||||
},
|
||||
});
|
||||
const res = await eventActions.getTopEventNames({ ctx });
|
||||
const result = res.eventNames;
|
||||
|
||||
const featureIds: string[] = [];
|
||||
const eventNames: string[] = [];
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { z } from "zod/v4";
|
||||
import { createRoute } from "@/honoMiddlewares/routeHandler.js";
|
||||
import { eventActions } from "../actions/index.js";
|
||||
import { AnalyticsService } from "../AnalyticsService.js";
|
||||
|
||||
const ListEventNamesSchema = z.object({
|
||||
limit: z.number().optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* List all distinct event names for the org sorted by popularity
|
||||
*/
|
||||
export const handleListEventNames = createRoute({
|
||||
query: ListEventNamesSchema,
|
||||
handler: async (c) => {
|
||||
const ctx = c.get("ctx");
|
||||
const { limit } = c.req.valid("query");
|
||||
|
||||
AnalyticsService.handleEarlyExit();
|
||||
|
||||
const eventNames = await eventActions.listEventNames({
|
||||
ctx,
|
||||
limit,
|
||||
});
|
||||
|
||||
return c.json({
|
||||
eventNames,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -12,7 +12,7 @@ import { z } from "zod/v4";
|
||||
import { createRoute } from "@/honoMiddlewares/routeHandler.js";
|
||||
import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import * as eventActions from "../actions/aggregate.js";
|
||||
import { eventActions } from "../actions/index.js";
|
||||
import { AnalyticsService } from "../AnalyticsService.js";
|
||||
|
||||
const QueryEventsSchema = z.object({
|
||||
@@ -27,7 +27,7 @@ const QueryEventsSchema = z.object({
|
||||
const getTopEvents = async ({ ctx }: { ctx: AutumnContext }) => {
|
||||
const { features } = ctx;
|
||||
|
||||
const topEventNamesRes = await AnalyticsService.getTopEventNames({
|
||||
const topEventNamesRes = await eventActions.getTopEventNames({
|
||||
ctx,
|
||||
});
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { z } from "zod/v4";
|
||||
import { createRoute } from "@/honoMiddlewares/routeHandler.js";
|
||||
import { CusService } from "@/internal/customers/CusService.js";
|
||||
import { AnalyticsService } from "../AnalyticsService.js";
|
||||
import { eventActions } from "../actions/index.js";
|
||||
|
||||
const QueryRawEventsSchema = z.object({
|
||||
interval: z.string().nullish(),
|
||||
@@ -47,14 +48,14 @@ export const handleQueryRawEvents = createRoute({
|
||||
}
|
||||
}
|
||||
|
||||
const events = await AnalyticsService.getRawEvents({
|
||||
const events = await eventActions.listRawEvents({
|
||||
ctx,
|
||||
params: {
|
||||
customer_id: customer?.id,
|
||||
interval,
|
||||
customer_id: customer?.id ?? undefined,
|
||||
interval: interval ?? undefined,
|
||||
customer,
|
||||
aggregateAll,
|
||||
},
|
||||
customer,
|
||||
aggregateAll,
|
||||
});
|
||||
|
||||
return c.json({
|
||||
|
||||
12
server/tinybird/materializations/event_names_mv.datasource
Normal file
12
server/tinybird/materializations/event_names_mv.datasource
Normal file
@@ -0,0 +1,12 @@
|
||||
DESCRIPTION >
|
||||
Aggregates distinct event names with counts per org/env.
|
||||
Used for listing available event names sorted by popularity.
|
||||
|
||||
SCHEMA >
|
||||
`org_id` String,
|
||||
`env` String,
|
||||
`event_name` String,
|
||||
`event_count` SimpleAggregateFunction(sum, UInt64)
|
||||
|
||||
ENGINE "AggregatingMergeTree"
|
||||
ENGINE_SORTING_KEY "org_id, env, event_name"
|
||||
16
server/tinybird/materializations/event_names_mv_pipe.pipe
Normal file
16
server/tinybird/materializations/event_names_mv_pipe.pipe
Normal file
@@ -0,0 +1,16 @@
|
||||
DESCRIPTION >
|
||||
Materializes event names with counts from events table.
|
||||
Used for listing available event names sorted by popularity.
|
||||
|
||||
NODE materialize
|
||||
SQL >
|
||||
SELECT
|
||||
org_id,
|
||||
env,
|
||||
event_name,
|
||||
count() as event_count
|
||||
FROM events
|
||||
GROUP BY org_id, env, event_name
|
||||
|
||||
TYPE materialized
|
||||
DATASOURCE event_names_mv
|
||||
21
server/tinybird/pipes/list_event_names.pipe
Normal file
21
server/tinybird/pipes/list_event_names.pipe
Normal file
@@ -0,0 +1,21 @@
|
||||
DESCRIPTION >
|
||||
Lists distinct event names for an org/env sorted by popularity (event count).
|
||||
|
||||
TOKEN "list_event_names_read" READ
|
||||
|
||||
NODE endpoint
|
||||
TYPE endpoint
|
||||
SQL >
|
||||
%
|
||||
SELECT
|
||||
event_name,
|
||||
sum(event_count) as event_count
|
||||
FROM event_names_mv
|
||||
WHERE
|
||||
org_id = {{ String(org_id, '') }}
|
||||
AND env = {{ String(env, 'test') }}
|
||||
GROUP BY event_name
|
||||
ORDER BY event_count DESC
|
||||
{% if defined(limit) %}
|
||||
LIMIT {{ Int32(limit, 100) }}
|
||||
{% end %}
|
||||
@@ -13,6 +13,7 @@ import { useOrg } from "@/hooks/common/useOrg";
|
||||
import { useDevQuery } from "@/hooks/queries/useDevQuery";
|
||||
import { useFeaturesQuery } from "@/hooks/queries/useFeaturesQuery";
|
||||
import { useRewardsQuery } from "@/hooks/queries/useRewardsQuery";
|
||||
import { useEventNames } from "@/views/customers/customer/analytics/hooks/useEventNames";
|
||||
import { useSession } from "@/lib/auth-client";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useEnv } from "@/utils/envUtils";
|
||||
@@ -126,6 +127,7 @@ const MainContent = ({
|
||||
useFeaturesQuery();
|
||||
useRewardsQuery();
|
||||
useCusSearchQuery();
|
||||
useEventNames();
|
||||
|
||||
return (
|
||||
<AppContext.Provider value={{}}>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { ErrCode } from "@autumn/shared";
|
||||
import { usePostSWR } from "@/services/useAxiosSwr.js";
|
||||
|
||||
export type EventNameWithCount = {
|
||||
event_name: string;
|
||||
event_count: number;
|
||||
};
|
||||
|
||||
export const useEventNames = (limit?: number) => {
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
error,
|
||||
} = usePostSWR<{ eventNames: EventNameWithCount[] }>({
|
||||
method: "get",
|
||||
url: `/query/event_names/list${limit ? `?limit=${limit}` : ""}`,
|
||||
queryKey: ["query-event-names-list", limit],
|
||||
options: {
|
||||
refreshInterval: 0,
|
||||
onError: (error) => {
|
||||
if (error.code === ErrCode.ClickHouseDisabled) {
|
||||
return error;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
eventNames: data?.eventNames ?? [],
|
||||
isLoading,
|
||||
error,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user