feat(webapp): consolidate the queue detail header into one top bar (TRI-12315, TRI-12316)

Move the Overview/Concurrency-keys tabs up into the filter bar (tabs left, inset by pl-2), with the date filter and the relocated Override limit + Pause {queue} queue buttons on the right (gap-1.5). Both controls move out of the Concurrency panel and gain visible labels; the override button gets an explanatory tooltip. Adds withQueueName to QueuePauseResumeButton and a labeled+tooltip 'button' trigger to QueueOverrideConcurrencyButton.
This commit is contained in:
James Ritchie
2026-07-22 19:48:10 +01:00
parent 632df63deb
commit dd04bae65a
2 changed files with 70 additions and 54 deletions
@@ -32,6 +32,7 @@ export function QueuePauseResumeButton({
fullWidth = false,
showTooltip = true,
iconOnly = false,
withQueueName = false,
}: {
/** The "id" here is a friendlyId */
queue: { id: string; name: string; paused: boolean };
@@ -41,6 +42,8 @@ export function QueuePauseResumeButton({
/** Icon-only trigger (label moves to the tooltip). For compact placements like the detail-page
* live blocks. */
iconOnly?: boolean;
/** Render the full "Pause/Resume {name} queue" label instead of the short "Pause"/"Resume". */
withQueueName?: boolean;
}) {
const [isOpen, setIsOpen] = useState(false);
@@ -64,7 +67,13 @@ export function QueuePauseResumeButton({
textAlignLeft={fullWidth}
aria-label={label}
>
{iconOnly ? undefined : queue.paused ? "Resume" : "Pause"}
{iconOnly
? undefined
: withQueueName
? `${queue.paused ? "Resume" : "Pause"} ${queue.name} queue`
: queue.paused
? "Resume"
: "Pause"}
</Button>
</DialogTrigger>
</div>
@@ -209,24 +218,38 @@ export function QueueOverrideConcurrencyButton({
</TooltipContent>
</Tooltip>
</TooltipProvider>
) : trigger === "button" ? (
<TooltipProvider disableHoverableContent={true}>
<Tooltip>
<TooltipTrigger asChild>
<div className="cursor-pointer [&_button]:cursor-pointer">
<DialogTrigger asChild>
<Button
type="button"
variant="secondary/small"
LeadingIcon={AdjustmentsHorizontalIcon}
leadingIconClassName="text-text-dimmed"
aria-label={
isOverridden ? "Edit concurrency override" : "Override concurrency limit"
}
>
{isOverridden ? "Edit override" : "Override limit"}
</Button>
</DialogTrigger>
</div>
</TooltipTrigger>
<TooltipContent side="bottom" className="max-w-[230px] text-xs">
Set a custom concurrency limit for this queue, overriding the environment default as
an absolute number or a percentage of the environment limit.
</TooltipContent>
</Tooltip>
</TooltipProvider>
) : (
<DialogTrigger asChild>
{trigger === "button" ? (
<Button
type="button"
variant="minimal/small"
LeadingIcon={AdjustmentsHorizontalIcon}
leadingIconClassName="text-text-dimmed"
aria-label={isOverridden ? "Edit concurrency override" : "Override concurrency limit"}
>
{isOverridden ? "Edit override" : "Override"}
</Button>
) : (
<PopoverMenuItem
icon={AdjustmentsHorizontalIcon}
title={isOverridden ? "Edit override…" : "Override limit…"}
/>
)}
<PopoverMenuItem
icon={AdjustmentsHorizontalIcon}
title={isOverridden ? "Edit override…" : "Override limit…"}
/>
</DialogTrigger>
)}
<DialogContent>
@@ -245,7 +245,25 @@ export default function Page() {
everything, like the Queues list. The time filter scopes the tab charts; search filters
the keys table. The bar is pinned by the layout while the page scrolls. */}
<MetricsLayout.Filters>
<div className="flex items-center gap-2">
<div className="pl-2">
<TabContainer>
<TabButton
isActive={view === "overview"}
layoutId="queue-detail-view"
onClick={() => replace({ view: undefined, key: undefined })}
>
Overview
</TabButton>
<TabButton
isActive={view === "keys"}
layoutId="queue-detail-view"
onClick={() => replace({ view: "keys" })}
>
Concurrency keys
</TabButton>
</TabContainer>
</div>
<div className="flex items-center gap-1.5">
{view === "keys" && hasKeys ? (
<SearchInput placeholder="Search keys…" paramName="query" resetParams={["key"]} />
) : null}
@@ -257,6 +275,16 @@ export default function Page() {
valueClassName="text-text-bright"
shortcut={{ key: "d" }}
/>
<QueueOverrideConcurrencyButton
queue={queue}
environmentConcurrencyLimit={environmentConcurrencyLimit}
trigger="button"
/>
<QueuePauseResumeButton
queue={{ id: queue.id, name: queue.name, paused: queue.paused }}
variant="secondary/small"
withQueueName
/>
</div>
</MetricsLayout.Filters>
@@ -275,23 +303,6 @@ export default function Page() {
{/* Tabs + charts share the padded (inset) column. Both tabs always render; the keys tab
shows an empty state when the queue has no concurrency keys. */}
<MetricsLayout.Content inset>
<TabContainer>
<TabButton
isActive={view === "overview"}
layoutId="queue-detail-view"
onClick={() => replace({ view: undefined, key: undefined })}
>
Overview
</TabButton>
<TabButton
isActive={view === "keys"}
layoutId="queue-detail-view"
onClick={() => replace({ view: "keys" })}
>
Concurrency keys
</TabButton>
</TabContainer>
{view === "keys" ? (
hasKeys ? (
<ConcurrencyKeyCharts
@@ -983,25 +994,7 @@ function QueueStats({
return (
<MetricsLayout.Grid>
<ConcurrencyBlock
running={runningDisplay}
limit={limitDisplay}
paused={queue.paused}
accessory={
<div className="flex items-center gap-1">
<QueuePauseResumeButton
queue={{ id: queue.id, name: queue.name, paused: queue.paused }}
variant="secondary/small-icon"
iconOnly
/>
<QueueOverrideConcurrencyButton
queue={queue}
environmentConcurrencyLimit={environmentConcurrencyLimit}
trigger="icon"
/>
</div>
}
/>
<ConcurrencyBlock running={runningDisplay} limit={limitDisplay} paused={queue.paused} />
<BigNumber
title="Queued"
value={queuedDisplay}