Skip to content

Commit a8fa12f

Browse files
committed
Merge remote-tracking branch 'origin/main' into feat/light-theme
# Conflicts: # apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx
2 parents bee8557 + 9d57aff commit a8fa12f

96 files changed

Lines changed: 4198 additions & 999 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@trigger.dev/core": patch
3+
"@trigger.dev/sdk": patch
4+
---
5+
6+
Allow task-scoped environment API keys to run batch operations for their permitted tasks. The SDK declares the batch's task set before creation, and `@trigger.dev/core/v3/apiKeys` now exports the additional-key format helper.

.github/VOUCHED.td

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ jrossi
2424
ThullyoCunha
2525
ConProgramming
2626
saasjesus
27-
brentshulman-silkline
27+
brentshulman-silkline
28+
Leafgard
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: fix
4+
---
5+
6+
Requests spanning multiple tasks now require permission for every requested task instead of accepting permission for only one task.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: fix
4+
---
5+
6+
The four charts at the top of the Queues page now always cover the whole environment, so paging through or re-sorting your queues no longer changes them. The scheduling delay chart also leaves a gap where no runs started, instead of dropping to zero.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: fix
4+
---
5+
6+
Fix the health report failing with an internal error when requested through the API.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: feature
4+
---
5+
6+
Additional environment API keys can authenticate API requests using their configured permissions, with revoked and expired keys rejected. Batch responses use server-issued public access tokens so additional keys never need the environment signing secret.

apps/webapp/app/components/queues/QueueMetricCards.tsx

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ export function useQueueMetric(
5454
defaultPeriod?: string;
5555
/** Poll ClickHouse on this cadence (ms). Omit to use the query's default interval. */
5656
refreshIntervalMs?: number;
57+
/** Floor for the bucket width, for series too sparse to read at the range's natural width. */
58+
minBucketSeconds?: number;
5759
}
5860
) {
5961
return useMetricResourceQuery(query, {
@@ -62,6 +64,7 @@ export function useQueueMetric(
6264
defaultPeriod: opts.defaultPeriod ?? QUEUE_METRICS_DEFAULT_PERIOD,
6365
queues: [opts.queueName],
6466
fillGaps: opts.fillGaps,
67+
minBucketSeconds: opts.minBucketSeconds,
6568
refreshIntervalMs: opts.refreshIntervalMs,
6669
});
6770
}
@@ -120,6 +123,14 @@ type QueueMetricChartProps = {
120123
/** Reports whether the chart has data to plot (false once it settles on the "no activity" state),
121124
* so a wrapping card can hide the legend to match. */
122125
onHasDataChange?: (hasData: boolean) => void;
126+
/** Floor for the bucket width, for series too sparse to read at the range's natural width. */
127+
minBucketSeconds?: number;
128+
/**
129+
* Column whose value counts the samples behind the plotted series. Where it is zero the metric
130+
* has nothing to report, so every series breaks there instead of reading as a real zero. Keep it
131+
* out of `series` — it is read for this test only, never drawn.
132+
*/
133+
sampleCountColumn?: string;
123134
};
124135

125136
// Bare chart (no card chrome) so it can live inside a shared card, e.g. a tabbed panel.
@@ -136,22 +147,26 @@ export function QueueMetricChart({
136147
carryBackfill,
137148
thresholdStroke,
138149
onHasDataChange,
150+
minBucketSeconds,
151+
sampleCountColumn,
139152
}: QueueMetricChartProps) {
140153
const { rows, showLoading, failed } = useQueueMetric(query, {
141154
ids,
142155
timeRange,
143156
queueName,
144157
fillGaps,
145158
defaultPeriod,
159+
minBucketSeconds,
146160
});
147161

148162
const data = useMemo(() => {
149163
const points = rows
150164
.map((r) => {
151-
const point: { bucket: number } & Record<string, number> = {
165+
const point: { bucket: number } & Record<string, number | null> = {
152166
bucket: clickhouseTimeToMs(r.t),
153167
};
154-
for (const s of series) point[s.key] = toNumber(r[s.key]);
168+
const hasSamples = sampleCountColumn ? toNumber(r[sampleCountColumn]) > 0 : true;
169+
for (const s of series) point[s.key] = hasSamples ? toNumber(r[s.key]) : null;
155170
return point;
156171
})
157172
.filter((p) => Number.isFinite(p.bucket));
@@ -160,15 +175,15 @@ export function QueueMetricChart({
160175
// value and carry it back over the earlier buckets so the line doesn't start at a false 0.
161176
if (carryBackfill?.length) {
162177
for (const key of carryBackfill) {
163-
const first = points.findIndex((p) => p[key] > 0);
178+
const first = points.findIndex((p) => toNumber(p[key]) > 0);
164179
if (first > 0) {
165180
const value = points[first]![key]!;
166181
for (let i = 0; i < first; i++) points[i]![key] = value;
167182
}
168183
}
169184
}
170185
return points;
171-
}, [rows, series, carryBackfill]);
186+
}, [rows, series, carryBackfill, sampleCountColumn]);
172187

173188
const chartConfig = useMemo(() => {
174189
const cfg: ChartConfig = {};
@@ -205,9 +220,14 @@ export function QueueMetricChart({
205220

206221
// Report data presence so a wrapping card can hide its legend when the chart settles on the
207222
// "no activity" state. Only report once loaded, so the legend stays put while loading.
223+
const hasPlottedData = useMemo(
224+
() => data.some((point) => series.some((s) => point[s.key] != null)),
225+
[data, series]
226+
);
227+
208228
useEffect(() => {
209-
if (!showLoading) onHasDataChange?.(!failed && data.length > 0);
210-
}, [showLoading, failed, data.length, onHasDataChange]);
229+
if (!showLoading) onHasDataChange?.(!failed && hasPlottedData);
230+
}, [showLoading, failed, hasPlottedData, onHasDataChange]);
211231

212232
return (
213233
<Chart.Root

apps/webapp/app/hooks/useMetricResourceQuery.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ export type MetricResourceQueryOptions = {
2121
defaultPeriod: string;
2222
queues?: string[];
2323
fillGaps?: boolean;
24+
/** Floor for the query's bucket width, for series too sparse to read at the range's width. */
25+
minBucketSeconds?: number;
2426
refreshIntervalMs?: number;
2527
};
2628

@@ -49,13 +51,19 @@ function cacheSet(key: string, rows: MetricResourceRow[]) {
4951
* back-navigation to the queues list) shows its last data immediately and revalidates in the
5052
* background rather than flashing a loading skeleton.
5153
*/
54+
/**
55+
* An empty query means the caller has nothing to ask for, so no request is made and any rows or
56+
* failure left by a previous query are dropped — a caller that stops asking must not keep reading
57+
* the last answer, or a stale failure would outlive the query that caused it.
58+
*/
5259
export function useMetricResourceQuery(query: string, opts: MetricResourceQueryOptions) {
5360
const {
5461
organizationId,
5562
projectId,
5663
environmentId,
5764
defaultPeriod,
5865
fillGaps,
66+
minBucketSeconds,
5967
refreshIntervalMs = 60_000,
6068
} = opts;
6169
const { period, from, to } = opts.timeRange;
@@ -71,10 +79,22 @@ export function useMetricResourceQuery(query: string, opts: MetricResourceQueryO
7179
from ?? "",
7280
to ?? "",
7381
fillGaps ? 1 : 0,
82+
minBucketSeconds ?? "",
7483
queuesKey ?? "",
7584
query,
7685
].join("|"),
77-
[organizationId, projectId, environmentId, resolvedPeriod, from, to, fillGaps, queuesKey, query]
86+
[
87+
organizationId,
88+
projectId,
89+
environmentId,
90+
resolvedPeriod,
91+
from,
92+
to,
93+
fillGaps,
94+
minBucketSeconds,
95+
queuesKey,
96+
query,
97+
]
7898
);
7999

80100
const [rows, setRows] = useState<MetricResourceRow[] | null>(
@@ -86,6 +106,14 @@ export function useMetricResourceQuery(query: string, opts: MetricResourceQueryO
86106
const loadedKeyRef = useRef<string | null>(null);
87107

88108
const load = useCallback(() => {
109+
if (!query) {
110+
abortRef.current?.abort();
111+
loadedKeyRef.current = cacheKey;
112+
setRows(null);
113+
setFailed(false);
114+
setIsLoading(false);
115+
return;
116+
}
89117
abortRef.current?.abort();
90118
const controller = new AbortController();
91119
abortRef.current = controller;
@@ -112,6 +140,7 @@ export function useMetricResourceQuery(query: string, opts: MetricResourceQueryO
112140
organizationId,
113141
projectId,
114142
environmentId,
143+
...(minBucketSeconds !== undefined ? { minBucketSeconds } : {}),
115144
...(queuesKey !== undefined ? { queues: queuesKey.split(",") } : {}),
116145
}),
117146
signal: controller.signal,
@@ -142,6 +171,7 @@ export function useMetricResourceQuery(query: string, opts: MetricResourceQueryO
142171
from,
143172
to,
144173
fillGaps,
174+
minBucketSeconds,
145175
organizationId,
146176
projectId,
147177
environmentId,

0 commit comments

Comments
 (0)