diff --git a/packages/backend/src/api/v1/analytics/index.ts b/packages/backend/src/api/v1/analytics/index.ts index 7874d529..9e4199be 100644 --- a/packages/backend/src/api/v1/analytics/index.ts +++ b/packages/backend/src/api/v1/analytics/index.ts @@ -1,4 +1,3 @@ -import { checkAccess } from "@/src/utils/authorization"; import sql from "@/src/utils/db"; import Context from "@/src/utils/koa"; import Router from "koa-router"; @@ -14,6 +13,7 @@ analytics.get("/tokens", async (ctx: Context) => { const { projectId } = ctx.state; const { datesQuery, filteredRunsQuery, granularity } = parseQuery( projectId, + ctx.querystring, ctx.query, ); @@ -79,6 +79,7 @@ analytics.get("/costs", async (ctx: Context) => { const { projectId } = ctx.state; const { datesQuery, filteredRunsQuery, granularity } = parseQuery( projectId, + ctx.querystring, ctx.query, ); @@ -144,6 +145,7 @@ analytics.get("/errors", async (ctx: Context) => { const { projectId } = ctx.state; const { datesQuery, filteredRunsQuery, granularity } = parseQuery( projectId, + ctx.querystring, ctx.query, ); @@ -216,7 +218,7 @@ analytics.get("/users/new", async (ctx: Context) => { startDate, endDate, filteredRunsQuery, - } = parseQuery(projectId, ctx.query); + } = parseQuery(projectId, ctx.querystring, ctx.query); const distinctMap = { hourly: sql`distinct on (r.external_user_id, date_trunc('hour', r.created_at at time zone ${timeZone})::timestamp)`, @@ -539,7 +541,7 @@ analytics.get("/users/active", async (ctx: Context) => { startDate, endDate, filteredRunsQuery, - } = parseQuery(projectId, ctx.query); + } = parseQuery(projectId, ctx.querystring, ctx.query); const distinctMap = { hourly: sql`distinct on (r.external_user_id, date_trunc('hour', r.created_at at time zone ${timeZone})::timestamp)`, @@ -776,7 +778,7 @@ analytics.get("/users/average-cost", async (ctx: Context) => { localCreatedAt, startDate, endDate, - } = parseQuery(projectId, ctx.query); + } = parseQuery(projectId, ctx.querystring, ctx.query); const [{ stat }] = await sql` with total_costs as ( @@ -888,6 +890,7 @@ analytics.get("/run-types", async (ctx: Context) => { const { projectId } = ctx.state; const { datesQuery, filteredRunsQuery, granularity } = parseQuery( projectId, + ctx.querystring, ctx.query, ); @@ -959,7 +962,7 @@ analytics.get("/latency", async (ctx: Context) => { startDate, endDate, timeZone, - } = parseQuery(projectId, ctx.query); + } = parseQuery(projectId, ctx.querystring, ctx.query); const [{ stat }] = await sql` select @@ -1032,6 +1035,7 @@ analytics.get("/feedback-ratio", async (ctx: Context) => { const { projectId } = ctx.state; const { datesQuery, filteredRunsQuery, granularity } = parseQuery( projectId, + ctx.querystring, ctx.query, ); @@ -1130,9 +1134,12 @@ analytics.get("/models/top", async (ctx: Context) => { checks: z.string().optional(), }); const { projectId } = ctx.state; - const { startDate, endDate, timeZone, userId, name, checks } = - querySchema.parse(ctx.request.query); - const filtersQuery = buildFiltersQuery(checks || ""); + const { startDate, endDate, timeZone, userId, name } = querySchema.parse( + ctx.request.query, + ); + + const deserializedChecks = deserializeLogic(ctx.querystring); + const filtersQuery = buildFiltersQuery(deserializedChecks); let dateFilter = sql``; if (startDate && endDate && timeZone) { @@ -1191,7 +1198,8 @@ analytics.get("/templates/top", async (ctx: Context) => { const { startDate, endDate, timeZone, checks } = querySchema.parse( ctx.request.query, ); - const filtersQuery = buildFiltersQuery(checks || ""); + const deserializedChecks = deserializeLogic(ctx.querystring); + const filtersQuery = buildFiltersQuery(deserializedChecks); const topTemplates = await sql` select @@ -1223,22 +1231,13 @@ analytics.get("/templates/top", async (ctx: Context) => { analytics.get("/languages/top", async (ctx: Context) => { const { projectId } = ctx.state; + ctx.query.granularity = "daily"; const { datesQuery, startDate, endDate, timeZone, filteredRunsQuery } = - parseQuery(projectId, ctx.query); + parseQuery(projectId, ctx.querystring, ctx.query); const data = await sql` with dates as ( - select - * - from ( - select generate_series( - ${startDate} at time zone ${timeZone}, - ${endDate} at time zone ${timeZone}, - '1 day'::interval - )::timestamp as date) t - where - date <=current_timestamp at time zone ${timeZone} - + ${datesQuery} ), filtered_runs as ( ${filteredRunsQuery} @@ -1274,6 +1273,7 @@ analytics.get("/custom-events", async (ctx: Context) => { const { projectId } = ctx.state; const { startDate, endDate, timeZone, filteredRunsQuery } = parseQuery( projectId, + ctx.querystring, ctx.query, ); diff --git a/packages/backend/src/api/v1/analytics/utils.ts b/packages/backend/src/api/v1/analytics/utils.ts index 9119d4b3..062e4ed9 100644 --- a/packages/backend/src/api/v1/analytics/utils.ts +++ b/packages/backend/src/api/v1/analytics/utils.ts @@ -1,17 +1,18 @@ import { convertChecksToSQL } from "@/src/utils/checks"; import sql from "@/src/utils/db"; -import Context from "@/src/utils/koa"; -import { deserializeLogic } from "shared"; +import { deserializeLogic, LogicNode } from "shared"; import { z } from "zod"; -export function buildFiltersQuery(checks: string) { - const deserializedChecks = deserializeLogic(checks); +export function buildFiltersQuery(deserializedChecks: LogicNode) { return deserializedChecks?.length && deserializedChecks.length > 1 ? convertChecksToSQL(deserializedChecks) : sql`1 = 1`; } -export function parseQuery(projectId: string, query: unknown) { +export function parseQuery(projectId: string, queryString: string, query: any) { + const deserializedChecks = deserializeLogic(queryString); + const filtersQuery = buildFiltersQuery(deserializedChecks); + return z .object({ startDate: z.string().datetime(), @@ -26,7 +27,6 @@ export function parseQuery(projectId: string, query: unknown) { checks: z.string().optional(), }) .transform(({ startDate, endDate, timeZone, granularity, checks }) => { - const filtersQuery = buildFiltersQuery(checks || ""); const granularityToIntervalMap = { hourly: "1 hour", daily: "1 day", diff --git a/packages/backend/src/api/v1/external-users.ts b/packages/backend/src/api/v1/external-users.ts index dba31b17..a6b78f3c 100644 --- a/packages/backend/src/api/v1/external-users.ts +++ b/packages/backend/src/api/v1/external-users.ts @@ -1,7 +1,8 @@ +import { checkAccess } from "@/src/utils/authorization"; import sql from "@/src/utils/db"; -import Router from "koa-router"; import { Context } from "koa"; -import { checkAccess } from "@/src/utils/authorization"; +import Router from "koa-router"; +import { deserializeLogic } from "shared"; import { z } from "zod"; import { buildFiltersQuery } from "./analytics/utils"; @@ -112,6 +113,9 @@ users.get( checks, } = querySchema.parse(ctx.request.query); + const deserializedChecks = deserializeLogic(ctx.querystring); + const filtersQuery = buildFiltersQuery(deserializedChecks); + let searchQuery = sql``; if (search) { searchQuery = sql`and ( @@ -129,8 +133,6 @@ users.get( `; } - const filtersQuery = buildFiltersQuery(checks || ""); - const sortMapping = { createdAt: "eu.created_at", lastSeen: "eu.last_seen", diff --git a/packages/frontend/pages/users/[[...id]].tsx b/packages/frontend/pages/users/[[...id]].tsx index d395beeb..c855f9b5 100644 --- a/packages/frontend/pages/users/[[...id]].tsx +++ b/packages/frontend/pages/users/[[...id]].tsx @@ -51,6 +51,8 @@ import LineChart from "@/components/analytics/OldLineChart"; import Link from "next/link"; import { parseAsString, useQueryState } from "nuqs"; import { deserializeLogic, getDefaultDateRange } from "shared"; +import ChartComponent from "@/components/analytics/Charts/ChartComponent"; +import AnalyticsCard from "@/components/analytics/AnalyticsCard"; const columns = [ { @@ -132,13 +134,7 @@ function SelectedUser({ id, onClose }) { }); } - const [dateRange, setDateRange] = useLocalStorage({ - key: "dateRange-analytics", - getInitialValueInEffect: false, - deserialize: deserializeDateRange, - defaultValue: getDefaultDateRange(), - }); - const [startDate, endDate] = dateRange; + const [startDate, endDate] = getDefaultDateRange(90); const [granularity] = useState("daily"); @@ -151,15 +147,6 @@ function SelectedUser({ id, onClose }) { deserializeLogic(`users=${id}`), ); - const { data: runCountData, isLoading: runCountLoading } = - useAnalyticsChartData( - id && "run-types", - startDate, - endDate, - granularity, - deserializeLogic(`users=${id}`), - ); - const commonChartData: { startDate: Date; endDate: Date; @@ -250,33 +237,57 @@ function SelectedUser({ id, onClose }) { - d.type === "chat")} - loading={runCountLoading} - props={["runs"]} - splitBy="type" - agg="sum" + + > + + - + > + + - {topModels && ( - - )} + + + + +