Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 23 additions & 3 deletions apps/api/src/__tests__/analytics.integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ import {
DeveloperProjectSchema,
DeveloperSessionSchema,
DeveloperSummarySchema,
DeveloperTeamCardSchema,
DeveloperTimelineSchema,
DeveloperTrendDataPointSchema,
DimensionAnalysisDataPointSchema,
ErrorsDashboardSchema,
ErrorTrendDataPointSchema,
InsightSchema,
LearningEntrySchema,
Expand Down Expand Up @@ -267,7 +269,7 @@ beforeAll(async () => {
session_id: string;
project_path: string;
}>;
}, 120_000);
});

beforeEach(async () => {
await server.ensureAlive();
Expand Down Expand Up @@ -326,6 +328,12 @@ describe("analytics/developers", () => {
expect(parsed.length).toBeGreaterThanOrEqual(1);
}, 30_000);

test("teamCards", async () => {
const result = await rpc("analytics/developers/teamCards", { days: DAYS });
const parsed = parseArray(DeveloperTeamCardSchema, result);
expect(parsed.length).toBeGreaterThanOrEqual(1);
}, 30_000);

test("details", async () => {
const result = await rpc("analytics/developers/details", {
days: DAYS,
Expand Down Expand Up @@ -533,6 +541,16 @@ describe("analytics/roi", () => {
// ── Errors ──────────────────────────────────────────────────────────

describe("analytics/errors", () => {
test("dashboard", async () => {
const result = await rpc("analytics/errors/dashboard", {
startDate: START_DATE,
endDate: END_DATE,
});
const parsed = ErrorsDashboardSchema.parse(result);
expect(parsed.summary.total_errors).toBeGreaterThanOrEqual(0);
expect(Array.isArray(parsed.recurring)).toBe(true);
}, 30_000);

test("topRecurring", async () => {
const result = await rpc("analytics/errors/topRecurring", {
days: DAYS,
Expand All @@ -546,10 +564,12 @@ describe("analytics/errors", () => {
const result = await rpc("analytics/errors/trends", {
startDate: START_DATE,
endDate: END_DATE,
splitBy: "repository",
splitBy: "project_path",
});
const parsed = parseArray(ErrorTrendDataPointSchema, result);
expect(Array.isArray(parsed)).toBe(true);
expect(parsed.length).toBeGreaterThan(0);
expect(Array.isArray(parsed[0]?.error_types)).toBe(true);
expect(Array.isArray(parsed[0]?.error_type_occurrences)).toBe(true);
}, 30_000);
});

Expand Down
8 changes: 8 additions & 0 deletions apps/api/src/clickhouse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,14 @@ export function buildAbsoluteDateFilter(
return `toDate(${column}) >= toDate({${startParamName}:String}) AND toDate(${column}) <= toDate({${endParamName}:String})`;
}

export function buildInclusiveDateRangeFilter(
startParamName: string,
endParamName: string,
column = "session_date",
): string {
return buildAbsoluteDateFilter(startParamName, endParamName, column);
}

export async function queryClickhouse<T>(
statement: ClickHouseStatement,
): Promise<T[]> {
Expand Down
1 change: 1 addition & 0 deletions apps/api/src/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ if (!connectionString) {
}

const client = postgres(connectionString);
export const sqlClient = client;
export const db = drizzle(client, { schema });
8 changes: 8 additions & 0 deletions apps/api/src/handlers/analytics/developers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
getDeveloperList,
getDeveloperProjects,
getDeveloperSessions,
getDeveloperTeamCards,
getDeveloperTimeline,
getDeveloperTrends,
} from "../../services/developer.service.js";
Expand All @@ -17,6 +18,12 @@ const list = os.analytics.developers.list
return getDeveloperList(context.organizationId, input.days);
});

const teamCards = os.analytics.developers.teamCards
.use(orgMiddleware)
.handler(async ({ input, context }) => {
return getDeveloperTeamCards(context.organizationId, input.days);
});

const details = os.analytics.developers.details
.use(orgMiddleware)
.handler(async ({ input, context }) => {
Expand Down Expand Up @@ -89,6 +96,7 @@ const trends = os.analytics.developers.trends

export const developersRouter = os.analytics.developers.router({
list,
teamCards,
details,
sessions,
projects,
Expand Down
11 changes: 11 additions & 0 deletions apps/api/src/handlers/analytics/errors.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import { orgMiddleware, os } from "../../middleware.js";
import {
getErrorsDashboard,
getErrorTrends,
getTopRecurringErrors,
} from "../../services/error.service.js";

const dashboard = os.analytics.errors.dashboard
.use(orgMiddleware)
.handler(async ({ input, context }) => {
return getErrorsDashboard(context.organizationId, {
start_date: input.startDate,
end_date: input.endDate,
});
});

const topRecurring = os.analytics.errors.topRecurring
.use(orgMiddleware)
.handler(async ({ input, context }) => {
Expand All @@ -25,6 +35,7 @@ const trends = os.analytics.errors.trends
});

export const errorsRouter = os.analytics.errors.router({
dashboard,
topRecurring,
trends,
});
36 changes: 36 additions & 0 deletions apps/api/src/handlers/analytics/overview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ import {
getModelTokensTrend,
getOverviewInsights,
getOverviewKPIs,
getRepositoriesDailyTrend,
getSuccessRateMetrics,
getTeamSummaryWithComparison,
getUsageTrendDetailed,
getUsersDailyTrend,
getUsersTokenUsage,
} from "../../services/overview.service.js";

const kpis = os.analytics.overview.kpis
Expand Down Expand Up @@ -38,6 +41,36 @@ const modelTokensTrend = os.analytics.overview.modelTokensTrend
);
});

const usersTokenUsage = os.analytics.overview.usersTokenUsage
.use(orgMiddleware)
.handler(async ({ input, context }) => {
return getUsersTokenUsage(
context.organizationId,
input.startDate,
input.endDate,
);
});

const usersDailyTrend = os.analytics.overview.usersDailyTrend
.use(orgMiddleware)
.handler(async ({ input, context }) => {
return getUsersDailyTrend(
context.organizationId,
input.startDate,
input.endDate,
);
});

const repositoriesDailyTrend = os.analytics.overview.repositoriesDailyTrend
.use(orgMiddleware)
.handler(async ({ input, context }) => {
return getRepositoriesDailyTrend(
context.organizationId,
input.startDate,
input.endDate,
);
});

const insights = os.analytics.overview.insights
.use(orgMiddleware)
.handler(async ({ input, context }) => {
Expand Down Expand Up @@ -72,6 +105,9 @@ export const overviewRouter = os.analytics.overview.router({
kpis,
usageTrend,
modelTokensTrend,
usersTokenUsage,
usersDailyTrend,
repositoriesDailyTrend,
insights,
teamSummaryComparison,
successRate,
Expand Down
11 changes: 11 additions & 0 deletions apps/api/src/handlers/analytics/roi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,20 @@ import { orgMiddleware, os } from "../../middleware.js";
import {
getDeveloperCostBreakdown,
getProjectCostBreakdown,
getROIDashboard,
getROIMetrics,
getROITrends,
} from "../../services/roi.service.js";

const dashboard = os.analytics.roi.dashboard
.use(orgMiddleware)
.handler(async ({ input, context }) => {
return getROIDashboard(context.organizationId, {
start_date: input.startDate,
end_date: input.endDate,
});
});

const metrics = os.analytics.roi.metrics
.use(orgMiddleware)
.handler(async ({ input, context }) => {
Expand All @@ -31,6 +41,7 @@ const breakdownProjects = os.analytics.roi.breakdownProjects
});

export const roiRouter = os.analytics.roi.router({
dashboard,
metrics,
trends,
breakdownDevelopers,
Expand Down
Loading
Loading