Skip to content

Commit 14e42de

Browse files
committed
Switch to using error_occurrences_v1 so we can do proper time filtering
1 parent 7c390a8 commit 14e42de

File tree

8 files changed

+522
-127
lines changed

8 files changed

+522
-127
lines changed

apps/webapp/app/presenters/v3/ErrorGroupPresenter.server.ts

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { z } from "zod";
2-
import { type ClickHouse } from "@internal/clickhouse";
2+
import {
3+
type ClickHouse,
4+
type TimeGranularity,
5+
detectTimeGranularity,
6+
granularityToInterval,
7+
granularityToStepMs,
8+
} from "@internal/clickhouse";
39
import { type PrismaClientOrTransaction } from "@trigger.dev/database";
410
import { type Direction } from "~/components/ListPagination";
511
import { findDisplayableEnvironment } from "~/models/runtimeEnvironment.server";
@@ -78,7 +84,8 @@ export type ErrorGroupSummary = {
7884
lastSeen: Date;
7985
};
8086

81-
export type ErrorGroupHourlyActivity = Array<{ date: Date; count: number }>;
87+
export type ErrorGroupOccurrences = Awaited<ReturnType<ErrorGroupPresenter["getOccurrences"]>>;
88+
export type ErrorGroupActivity = ErrorGroupOccurrences["data"];
8289

8390
export class ErrorGroupPresenter extends BasePresenter {
8491
constructor(
@@ -155,42 +162,67 @@ export class ErrorGroupPresenter extends BasePresenter {
155162
};
156163
}
157164

158-
public async getHourlyOccurrences(
165+
/**
166+
* Returns bucketed occurrence counts for a single fingerprint over a time range.
167+
* Granularity is determined automatically from the range span.
168+
*/
169+
public async getOccurrences(
159170
organizationId: string,
160171
projectId: string,
161172
environmentId: string,
162-
fingerprint: string
163-
): Promise<ErrorGroupHourlyActivity> {
164-
const hours = 168; // 7 days
165-
166-
const [queryError, records] = await this.clickhouse.errors.getHourlyOccurrences({
167-
organizationId,
168-
projectId,
169-
environmentId,
170-
fingerprints: [fingerprint],
171-
hours,
173+
fingerprint: string,
174+
from: Date,
175+
to: Date
176+
): Promise<{
177+
granularity: TimeGranularity;
178+
data: Array<{ date: Date; count: number }>;
179+
}> {
180+
const granularity = detectTimeGranularity(from, to);
181+
const intervalExpr = granularityToInterval(granularity);
182+
const stepMs = granularityToStepMs(granularity);
183+
184+
const queryBuilder = this.clickhouse.errors.createOccurrencesQueryBuilder(intervalExpr);
185+
186+
queryBuilder.where("organization_id = {organizationId: String}", { organizationId });
187+
queryBuilder.where("project_id = {projectId: String}", { projectId });
188+
queryBuilder.where("environment_id = {environmentId: String}", { environmentId });
189+
queryBuilder.where("error_fingerprint = {fingerprint: String}", { fingerprint });
190+
queryBuilder.where("minute >= toStartOfMinute(fromUnixTimestamp64Milli({fromTimeMs: Int64}))", {
191+
fromTimeMs: from.getTime(),
172192
});
193+
queryBuilder.where("minute <= toStartOfMinute(fromUnixTimestamp64Milli({toTimeMs: Int64}))", {
194+
toTimeMs: to.getTime(),
195+
});
196+
197+
queryBuilder.groupBy("error_fingerprint, bucket_epoch");
198+
queryBuilder.orderBy("bucket_epoch ASC");
199+
200+
const [queryError, records] = await queryBuilder.execute();
173201

174202
if (queryError) {
175203
throw queryError;
176204
}
177205

206+
// Build time buckets covering the full range
178207
const buckets: number[] = [];
179-
const nowMs = Date.now();
180-
for (let i = hours - 1; i >= 0; i--) {
181-
const hourStart = Math.floor((nowMs - i * 3_600_000) / 3_600_000) * 3_600;
182-
buckets.push(hourStart);
208+
const startEpoch = Math.floor(from.getTime() / stepMs) * (stepMs / 1000);
209+
const endEpoch = Math.ceil(to.getTime() / 1000);
210+
for (let epoch = startEpoch; epoch <= endEpoch; epoch += stepMs / 1000) {
211+
buckets.push(epoch);
183212
}
184213

185-
const byHour = new Map<number, number>();
214+
const byBucket = new Map<number, number>();
186215
for (const row of records ?? []) {
187-
byHour.set(row.hour_epoch, row.count);
216+
byBucket.set(row.bucket_epoch, (byBucket.get(row.bucket_epoch) ?? 0) + row.count);
188217
}
189218

190-
return buckets.map((epoch) => ({
191-
date: new Date(epoch * 1000),
192-
count: byHour.get(epoch) ?? 0,
193-
}));
219+
return {
220+
granularity,
221+
data: buckets.map((epoch) => ({
222+
date: new Date(epoch * 1000),
223+
count: byBucket.get(epoch) ?? 0,
224+
})),
225+
};
194226
}
195227

196228
private async getSummary(

0 commit comments

Comments
 (0)