Skip to content

Highlight queues when concurrency limit reached and new table col #2008

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
May 1, 2025
Merged
34 changes: 26 additions & 8 deletions apps/webapp/app/components/metrics/BigNumber.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { type ReactNode } from "react";
import { AnimatedNumber } from "../primitives/AnimatedNumber";
import { Spinner } from "../primitives/Spinner";
import { cn } from "~/utils/cn";
import { formatNumber, formatNumberCompact } from "~/utils/numberFormatter";
import { Header3 } from "../primitives/Headers";
import { Spinner } from "../primitives/Spinner";
import { SimpleTooltip } from "../primitives/Tooltip";
import { AnimatedNumber } from "../primitives/AnimatedNumber";

interface BigNumberProps {
title: ReactNode;
Expand All @@ -13,6 +16,7 @@ interface BigNumberProps {
accessory?: ReactNode;
suffix?: string;
suffixClassName?: string;
compactThreshold?: number;
}

export function BigNumber({
Expand All @@ -25,25 +29,39 @@ export function BigNumber({
accessory,
animate = false,
loading = false,
compactThreshold,
}: BigNumberProps) {
const v = value ?? defaultValue;

const shouldCompact =
typeof compactThreshold === "number" && v !== undefined && v >= compactThreshold;

return (
<div className="grid grid-rows-[1.5rem_auto] gap-4 rounded-sm border border-grid-dimmed bg-background-bright p-4">
<div className="flex items-center justify-between">
<div className="text-2sm text-text-dimmed">{title}</div>
<div className="flex flex-col justify-between gap-4 rounded-sm border border-grid-dimmed bg-background-bright p-4">
<div className="flex flex-wrap items-center justify-between gap-2">
<Header3 className="leading-6">{title}</Header3>
{accessory && <div className="flex-shrink-0">{accessory}</div>}
</div>
<div
className={cn(
"h-[3.75rem] text-[3.75rem] font-normal tabular-nums leading-none text-text-bright",
"text-[3.75rem] font-normal tabular-nums leading-none text-text-bright",
valueClassName
)}
>
{loading ? (
<Spinner className="size-6" />
) : v !== undefined ? (
<div className="flex items-baseline gap-1">
{animate ? <AnimatedNumber value={v} /> : v}
<div className="flex flex-wrap items-baseline gap-2">
{shouldCompact ? (
<SimpleTooltip
button={animate ? <AnimatedNumber value={v} /> : formatNumberCompact(v)}
content={formatNumber(v)}
/>
) : animate ? (
<AnimatedNumber value={v} />
) : (
formatNumber(v)
)}
{suffix && <div className={cn("text-xs", suffixClassName)}>{suffix}</div>}
</div>
) : (
Expand Down
2 changes: 1 addition & 1 deletion apps/webapp/app/components/primitives/AnimatedNumber.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { motion, useSpring, useTransform, useMotionValue, animate } from "framer-motion";
import { animate, motion, useMotionValue, useTransform } from "framer-motion";
import { useEffect } from "react";

export function AnimatedNumber({ value }: { value: number }) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import { docsPath, EnvironmentParamSchema, v3BillingPath } from "~/utils/pathBui
import { PauseEnvironmentService } from "~/v3/services/pauseEnvironment.server";
import { PauseQueueService } from "~/v3/services/pauseQueue.server";
import { useCurrentPlan } from "../_app.orgs.$organizationSlug/route";
import { Header3 } from "~/components/primitives/Headers";
import { Input } from "~/components/primitives/Input";
import { useThrottle } from "~/hooks/useThrottle";

Expand Down Expand Up @@ -257,13 +258,30 @@ export default function Page() {
suffix={env.paused && environment.queued > 0 ? "paused" : undefined}
animate
accessory={<EnvironmentPauseResumeButton env={env} />}
valueClassName={env.paused ? "text-amber-500" : undefined}
valueClassName={env.paused ? "text-warning" : undefined}
compactThreshold={1000000}
/>
<BigNumber
title="Running"
value={environment.running}
animate
valueClassName={
environment.running === environment.concurrencyLimit ? "text-warning" : undefined
}
suffix={
environment.running === environment.concurrencyLimit
? "At concurrency limit"
: undefined
}
compactThreshold={1000000}
/>
<BigNumber title="Running" value={environment.running} animate />
<BigNumber
title="Concurrency limit"
value={environment.concurrencyLimit}
animate
valueClassName={
environment.running === environment.concurrencyLimit ? "text-warning" : undefined
}
accessory={
plan ? (
plan?.v3Subscription?.plan?.limits.concurrentRuns.canExceed ? (
Expand Down Expand Up @@ -307,7 +325,37 @@ export default function Page() {
<TableRow>
<TableHeaderCell>Name</TableHeaderCell>
<TableHeaderCell alignment="right">Queued</TableHeaderCell>
<TableHeaderCell alignment="right">Running</TableHeaderCell>
<TableHeaderCell alignment="right">Running/limit</TableHeaderCell>
<TableHeaderCell
alignment="right"
tooltip={
<div className="max-w-xs space-y-2 p-1 text-left">
<div className="space-y-0.5">
<Header3>Environment</Header3>
<Paragraph
variant="small"
className="!text-wrap text-text-dimmed"
spacing
>
This queue is limited by your environment's concurrency limit of{" "}
{environment.concurrencyLimit}.
</Paragraph>
</div>
<div className="space-y-0.5">
<Header3>User</Header3>
<Paragraph
variant="small"
className="!text-wrap text-text-dimmed"
spacing
>
This queue is limited by a concurrency limit set in your code.
</Paragraph>
</div>
</div>
}
>
Limited by
</TableHeaderCell>
<TableHeaderCell
alignment="right"
tooltip={
Expand All @@ -334,88 +382,110 @@ export default function Page() {
>
Release on waitpoint
</TableHeaderCell>
<TableHeaderCell alignment="right">Concurrency limit</TableHeaderCell>
<TableHeaderCell className="w-[1%] pl-24">
<span className="sr-only">Pause/resume</span>
</TableHeaderCell>
</TableRow>
</TableHeader>
<TableBody>
{queues.length > 0 ? (
queues.map((queue) => (
<TableRow key={queue.name}>
<TableCell>
<span className="flex items-center gap-2">
{queue.type === "task" ? (
<SimpleTooltip
button={
<TaskIconSmall
className={cn(
"size-[1.125rem] text-blue-500",
queue.paused && "opacity-50"
)}
/>
}
content={`This queue was automatically created from your "${queue.name}" task`}
/>
) : (
<SimpleTooltip
button={
<RectangleStackIcon
className={cn(
"size-[1.125rem] text-purple-500",
queue.paused && "opacity-50"
)}
/>
}
content={`This is a custom queue you added in your code.`}
/>
)}
<span className={queue.paused ? "opacity-50" : undefined}>
{queue.name}
queues.map((queue) => {
const limit = queue.concurrencyLimit ?? environment.concurrencyLimit;
const isAtLimit = queue.running === limit;
return (
<TableRow key={queue.name}>
<TableCell>
<span className="flex items-center gap-2">
{queue.type === "task" ? (
<SimpleTooltip
button={
<TaskIconSmall
className={cn(
"size-[1.125rem] text-blue-500",
queue.paused && "opacity-50"
)}
/>
}
content={`This queue was automatically created from your "${queue.name}" task`}
/>
) : (
<SimpleTooltip
button={
<RectangleStackIcon
className={cn(
"size-[1.125rem] text-purple-500",
queue.paused && "opacity-50"
)}
/>
}
content={`This is a custom queue you added in your code.`}
/>
)}
<span className={queue.paused ? "opacity-50" : undefined}>
{queue.name}
</span>
{queue.paused ? (
<Badge variant="extra-small" className="text-warning">
Paused
</Badge>
) : null}
{isAtLimit ? (
<Badge variant="extra-small" className="text-warning">
At concurrency limit
</Badge>
) : null}
</span>
{queue.paused ? (
<Badge variant="extra-small" className="text-warning">
Paused
</Badge>
) : null}
</span>
</TableCell>
<TableCell
alignment="right"
className={queue.paused ? "opacity-50" : undefined}
>
{queue.queued}
</TableCell>
<TableCell
alignment="right"
className={queue.paused ? "opacity-50" : undefined}
>
{queue.running}
</TableCell>
<TableCell
alignment="right"
className={queue.paused ? "opacity-50" : undefined}
>
{queue.releaseConcurrencyOnWaitpoint ? "Yes" : "No"}
</TableCell>
<TableCell
alignment="right"
className={queue.paused ? "opacity-50" : undefined}
>
{queue.concurrencyLimit ?? (
<span className="text-text-dimmed">
Max ({environment.concurrencyLimit})
</TableCell>
<TableCell
alignment="right"
className={queue.paused ? "opacity-50" : undefined}
>
{queue.queued}
</TableCell>
<TableCell
alignment="right"
className={cn(
queue.paused ? "tabular-nums opacity-50" : undefined,
isAtLimit && "text-warning"
)}
>
{queue.running}/
<span
className={cn(
"tabular-nums text-text-dimmed",
isAtLimit && "text-warning"
)}
>
{limit}
</span>
)}
</TableCell>
<TableCellMenu
isSticky
visibleButtons={queue.paused && <QueuePauseResumeButton queue={queue} />}
hiddenButtons={!queue.paused && <QueuePauseResumeButton queue={queue} />}
/>
</TableRow>
))
</TableCell>
<TableCell
alignment="right"
className={cn(
queue.paused ? "opacity-50" : undefined,
isAtLimit && "text-warning"
)}
>
{queue.concurrencyLimit ? "User" : "Environment"}
</TableCell>
<TableCell
alignment="right"
className={queue.paused ? "opacity-50" : undefined}
>
{queue.releaseConcurrencyOnWaitpoint ? "Yes" : "No"}
</TableCell>
<TableCellMenu
isSticky
visibleButtons={
queue.paused && <QueuePauseResumeButton queue={queue} />
}
hiddenButtons={
!queue.paused && <QueuePauseResumeButton queue={queue} />
}
/>
</TableRow>
);
})
) : (
<TableRow>
<TableCell colSpan={6}>
Expand Down Expand Up @@ -503,7 +573,7 @@ function EnvironmentPauseResumeButton({
type="button"
variant="secondary/small"
LeadingIcon={env.paused ? PlayIcon : PauseIcon}
leadingIconClassName={env.paused ? "text-success" : "text-amber-500"}
leadingIconClassName={env.paused ? "text-success" : "text-warning"}
>
{env.paused ? "Resume..." : "Pause environment..."}
</Button>
Expand All @@ -512,8 +582,8 @@ function EnvironmentPauseResumeButton({
</TooltipTrigger>
<TooltipContent className={"text-xs"}>
{env.paused
? `Resume processing runs in ${environmentFullTitle(env)}.`
: `Pause processing runs in ${environmentFullTitle(env)}.`}
? `Resume processing runs in ${environmentFullTitle(env)}`
: `Pause processing runs in ${environmentFullTitle(env)}`}
</TooltipContent>
</Tooltip>
</TooltipProvider>
Expand Down Expand Up @@ -582,7 +652,7 @@ function QueuePauseResumeButton({
type="button"
variant="tertiary/small"
LeadingIcon={queue.paused ? PlayIcon : PauseIcon}
leadingIconClassName={queue.paused ? "text-success" : "text-amber-500"}
leadingIconClassName={queue.paused ? "text-success" : "text-warning"}
>
{queue.paused ? "Resume..." : "Pause..."}
</Button>
Expand Down Expand Up @@ -703,7 +773,7 @@ export function QueueFilters() {
const search = searchParams.get("query") ?? "";

return (
<div className="flex w-full px-3 pb-3">
<div className="flex w-full border-t border-grid-dimmed px-1.5 py-1.5">
<Input
name="search"
placeholder="Search queue name"
Expand Down