Skip to content

Commit a68ef30

Browse files
committed
feat(dashboard): Display environment queue length limits on queues and limits page
1 parent b221719 commit a68ef30

File tree

4 files changed

+92
-20
lines changed

4 files changed

+92
-20
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { env } from "~/env.server";
12
import { type AuthenticatedEnvironment } from "~/services/apiAuth.server";
23
import { marqs } from "~/v3/marqs/index.server";
34
import { engine } from "~/v3/runEngine.server";
@@ -9,6 +10,7 @@ export type Environment = {
910
concurrencyLimit: number;
1011
burstFactor: number;
1112
runsEnabled: boolean;
13+
queueSizeLimit: number | null;
1214
};
1315

1416
export class EnvironmentQueuePresenter extends BasePresenter {
@@ -30,19 +32,27 @@ export class EnvironmentQueuePresenter extends BasePresenter {
3032
},
3133
select: {
3234
runsEnabled: true,
35+
maximumDevQueueSize: true,
36+
maximumDeployedQueueSize: true,
3337
},
3438
});
3539

3640
if (!organization) {
3741
throw new Error("Organization not found");
3842
}
3943

44+
const queueSizeLimit =
45+
environment.type === "DEVELOPMENT"
46+
? (organization.maximumDevQueueSize ?? env.MAXIMUM_DEV_QUEUE_SIZE ?? null)
47+
: (organization.maximumDeployedQueueSize ?? env.MAXIMUM_DEPLOYED_QUEUE_SIZE ?? null);
48+
4049
return {
4150
running,
4251
queued,
4352
concurrencyLimit: environment.maximumConcurrencyLimit,
4453
burstFactor: environment.concurrencyLimitBurstFactor.toNumber(),
4554
runsEnabled: environment.type === "DEVELOPMENT" || organization.runsEnabled,
55+
queueSizeLimit,
4656
};
4757
}
4858
}

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

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { BasePresenter } from "./basePresenter.server";
1212
import { singleton } from "~/utils/singleton";
1313
import { logger } from "~/services/logger.server";
1414
import { CheckScheduleService } from "~/v3/services/checkSchedule.server";
15+
import { engine } from "~/v3/runEngine.server";
1516

1617
// Create a singleton Redis client for rate limit queries
1718
const rateLimitRedisClient = singleton("rateLimitQueryRedisClient", () =>
@@ -66,8 +67,7 @@ export type LimitsResult = {
6667
logRetentionDays: QuotaInfo | null;
6768
realtimeConnections: QuotaInfo | null;
6869
batchProcessingConcurrency: QuotaInfo;
69-
devQueueSize: QuotaInfo;
70-
deployedQueueSize: QuotaInfo;
70+
queueSize: QuotaInfo;
7171
};
7272
features: {
7373
hasStagingEnvironment: FeatureInfo;
@@ -167,6 +167,32 @@ export class LimitsPresenter extends BasePresenter {
167167
batchRateLimitConfig
168168
);
169169

170+
// Get current queue size for this environment
171+
const runtimeEnv = await this._replica.runtimeEnvironment.findFirst({
172+
where: { id: environmentId },
173+
select: {
174+
id: true,
175+
type: true,
176+
organizationId: true,
177+
projectId: true,
178+
maximumConcurrencyLimit: true,
179+
concurrencyLimitBurstFactor: true,
180+
},
181+
});
182+
183+
let currentQueueSize = 0;
184+
if (runtimeEnv) {
185+
const engineEnv = {
186+
id: runtimeEnv.id,
187+
type: runtimeEnv.type,
188+
maximumConcurrencyLimit: runtimeEnv.maximumConcurrencyLimit,
189+
concurrencyLimitBurstFactor: runtimeEnv.concurrencyLimitBurstFactor,
190+
organization: { id: runtimeEnv.organizationId },
191+
project: { id: runtimeEnv.projectId },
192+
};
193+
currentQueueSize = (await engine.lengthOfEnvQueue(engineEnv)) ?? 0;
194+
}
195+
170196
// Get plan-level limits
171197
const schedulesLimit = limits?.schedules?.number ?? null;
172198
const teamMembersLimit = limits?.teamMembers?.number ?? null;
@@ -282,19 +308,24 @@ export class LimitsPresenter extends BasePresenter {
282308
canExceed: true,
283309
isUpgradable: true,
284310
},
285-
devQueueSize: {
286-
name: "Dev queue size",
287-
description: "Maximum pending runs in development environments",
288-
limit: organization.maximumDevQueueSize ?? null,
289-
currentUsage: 0, // Would need to query Redis for this
290-
source: organization.maximumDevQueueSize ? "override" : "default",
291-
},
292-
deployedQueueSize: {
293-
name: "Deployed queue size",
294-
description: "Maximum pending runs in deployed environments",
295-
limit: organization.maximumDeployedQueueSize ?? null,
296-
currentUsage: 0, // Would need to query Redis for this
297-
source: organization.maximumDeployedQueueSize ? "override" : "default",
311+
queueSize: {
312+
name: "Max queue size",
313+
description: "Maximum pending runs in this environment",
314+
limit:
315+
runtimeEnv?.type === "DEVELOPMENT"
316+
? (organization.maximumDevQueueSize ?? env.MAXIMUM_DEV_QUEUE_SIZE ?? null)
317+
: (organization.maximumDeployedQueueSize ?? env.MAXIMUM_DEPLOYED_QUEUE_SIZE ?? null),
318+
currentUsage: currentQueueSize,
319+
// "plan" = org has a value (typically set by billing sync)
320+
// "default" = no org value, using env var fallback
321+
source:
322+
runtimeEnv?.type === "DEVELOPMENT"
323+
? organization.maximumDevQueueSize
324+
? "plan"
325+
: "default"
326+
: organization.maximumDeployedQueueSize
327+
? "plan"
328+
: "default",
298329
},
299330
},
300331
features: {

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.limits/route.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -507,9 +507,8 @@ function QuotasSection({
507507
// Include batch processing concurrency
508508
quotaRows.push(quotas.batchProcessingConcurrency);
509509

510-
// Add queue size quotas if set
511-
if (quotas.devQueueSize.limit !== null) quotaRows.push(quotas.devQueueSize);
512-
if (quotas.deployedQueueSize.limit !== null) quotaRows.push(quotas.deployedQueueSize);
510+
// Add queue size quota if set
511+
if (quotas.queueSize.limit !== null) quotaRows.push(quotas.queueSize);
513512

514513
return (
515514
<div className="flex flex-col gap-3">

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ import { EnvironmentQueuePresenter } from "~/presenters/v3/EnvironmentQueuePrese
6868
import { QueueListPresenter } from "~/presenters/v3/QueueListPresenter.server";
6969
import { requireUserId } from "~/services/session.server";
7070
import { cn } from "~/utils/cn";
71+
import { formatNumberCompact } from "~/utils/numberFormatter";
7172
import {
7273
concurrencyPath,
7374
docsPath,
@@ -345,7 +346,27 @@ export default function Page() {
345346
<BigNumber
346347
title="Queued"
347348
value={environment.queued}
348-
suffix={env.paused && environment.queued > 0 ? "paused" : undefined}
349+
suffix={
350+
environment.queueSizeLimit ? (
351+
<span className="flex items-center gap-1">
352+
<span className="text-text-dimmed">/</span>
353+
<span
354+
className={getQueueUsageColorClass(
355+
environment.queued,
356+
environment.queueSizeLimit
357+
)}
358+
>
359+
{formatNumberCompact(environment.queueSizeLimit)}
360+
</span>
361+
<InfoIconTooltip
362+
content="Maximum pending runs in this environment"
363+
contentClassName="max-w-xs"
364+
/>
365+
</span>
366+
) : env.paused && environment.queued > 0 ? (
367+
"paused"
368+
) : undefined
369+
}
349370
animate
350371
accessory={
351372
<div className="flex items-start gap-1">
@@ -364,7 +385,10 @@ export default function Page() {
364385
/>
365386
</div>
366387
}
367-
valueClassName={env.paused ? "text-warning" : undefined}
388+
valueClassName={
389+
getQueueUsageColorClass(environment.queued, environment.queueSizeLimit) ??
390+
(env.paused ? "text-warning" : undefined)
391+
}
368392
compactThreshold={1000000}
369393
/>
370394
<BigNumber
@@ -1118,3 +1142,11 @@ function BurstFactorTooltip({
11181142
/>
11191143
);
11201144
}
1145+
1146+
function getQueueUsageColorClass(current: number, limit: number | null): string | undefined {
1147+
if (!limit) return undefined;
1148+
const percentage = current / limit;
1149+
if (percentage >= 1) return "text-error";
1150+
if (percentage >= 0.9) return "text-warning";
1151+
return undefined;
1152+
}

0 commit comments

Comments
 (0)