chore(webapp): Concurrency page UI improvements (#2825)
### UI Improvements to the Concurrency page: - Truncates long branch names and includes a tooltip - The Tables have a new variant if you don't want the rows to highlight on hover - Small fix to pluralize some words in the purchase modal - Fix to prevent tooltip buttons being `type=submit` - Updates the /limits docs page to include purchasing more concurrency - Adds a clear banner when you have a positive balance of unallocated concurrency https://github.com/user-attachments/assets/54d927c3-84e3-4d55-8f42-726098f4daf0
This commit is contained in:
@@ -51,10 +51,14 @@ export function EnvironmentCombo({
|
||||
environment,
|
||||
className,
|
||||
iconClassName,
|
||||
tooltipSideOffset,
|
||||
tooltipSide,
|
||||
}: {
|
||||
environment: Environment;
|
||||
className?: string;
|
||||
iconClassName?: string;
|
||||
tooltipSideOffset?: number;
|
||||
tooltipSide?: "top" | "right" | "bottom" | "left";
|
||||
}) {
|
||||
return (
|
||||
<span className={cn("flex items-center gap-1.5 text-sm text-text-bright", className)}>
|
||||
@@ -62,7 +66,11 @@ export function EnvironmentCombo({
|
||||
environment={environment}
|
||||
className={cn("size-4.5 shrink-0", iconClassName)}
|
||||
/>
|
||||
<EnvironmentLabel environment={environment} />
|
||||
<EnvironmentLabel
|
||||
environment={environment}
|
||||
tooltipSideOffset={tooltipSideOffset}
|
||||
tooltipSide={tooltipSide}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -70,9 +78,13 @@ export function EnvironmentCombo({
|
||||
export function EnvironmentLabel({
|
||||
environment,
|
||||
className,
|
||||
tooltipSideOffset = 34,
|
||||
tooltipSide = "right",
|
||||
}: {
|
||||
environment: Environment;
|
||||
className?: string;
|
||||
tooltipSideOffset?: number;
|
||||
tooltipSide?: "top" | "right" | "bottom" | "left";
|
||||
}) {
|
||||
const spanRef = useRef<HTMLSpanElement>(null);
|
||||
const [isTruncated, setIsTruncated] = useState(false);
|
||||
@@ -115,9 +127,10 @@ export function EnvironmentLabel({
|
||||
{text}
|
||||
</span>
|
||||
}
|
||||
side="right"
|
||||
side={tooltipSide}
|
||||
variant="dark"
|
||||
sideOffset={34}
|
||||
sideOffset={tooltipSideOffset}
|
||||
disableHoverableContent
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -389,7 +389,7 @@ export const LinkButton = ({
|
||||
to={to}
|
||||
ref={innerRef}
|
||||
replace={replace}
|
||||
className={cn("group/button block focus-custom", props.fullWidth ? "w-full" : "")}
|
||||
className={cn("group/button block focus-custom", props.fullWidth ? "w-full" : "w-fit")}
|
||||
onClick={onClick}
|
||||
onMouseDown={onMouseDown}
|
||||
onMouseEnter={onMouseEnter}
|
||||
|
||||
@@ -15,6 +15,14 @@ const variants = {
|
||||
menuButtonDivider: "group-hover/table-row:border-charcoal-600/70",
|
||||
rowSelected: "bg-charcoal-750 group-hover/table-row:bg-charcoal-750",
|
||||
},
|
||||
"bright/no-hover": {
|
||||
header: "bg-transparent",
|
||||
cell: "group-hover/table-row:bg-transparent",
|
||||
stickyCell: "bg-background-bright",
|
||||
menuButton: "bg-background-bright",
|
||||
menuButtonDivider: "",
|
||||
rowSelected: "bg-charcoal-750",
|
||||
},
|
||||
dimmed: {
|
||||
header: "bg-background-dimmed",
|
||||
cell: "group-hover/table-row:bg-charcoal-800 group-has-[[tabindex='0']:focus]/table-row:bg-background-bright",
|
||||
|
||||
@@ -85,6 +85,7 @@ function SimpleTooltip({
|
||||
<TooltipProvider disableHoverableContent={disableHoverableContent}>
|
||||
<Tooltip open={open} onOpenChange={onOpenChange}>
|
||||
<TooltipTrigger
|
||||
type="button"
|
||||
tabIndex={-1}
|
||||
className={cn("h-fit", buttonClassName)}
|
||||
style={buttonStyle}
|
||||
|
||||
+37
-10
@@ -1,6 +1,7 @@
|
||||
import { conform, useFieldList, useForm } from "@conform-to/react";
|
||||
import { parse } from "@conform-to/zod";
|
||||
import {
|
||||
ArrowDownIcon,
|
||||
EnvelopeIcon,
|
||||
ExclamationTriangleIcon,
|
||||
InformationCircleIcon,
|
||||
@@ -18,6 +19,7 @@ import {
|
||||
import { json, type ActionFunctionArgs, type LoaderFunctionArgs } from "@remix-run/server-runtime";
|
||||
import { tryCatch } from "@trigger.dev/core";
|
||||
import { useEffect, useState } from "react";
|
||||
import simplur from "simplur";
|
||||
import { typedjson, useTypedLoaderData } from "remix-typedjson";
|
||||
import { z } from "zod";
|
||||
import { AdminDebugTooltip } from "~/components/admin/debugTooltip";
|
||||
@@ -331,7 +333,7 @@ function Upgradable({
|
||||
disabled={unallocated < 0 ? false : allocationModified}
|
||||
/>
|
||||
</div>
|
||||
<Table>
|
||||
<Table variant="bright/no-hover">
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell className="pl-0 text-text-bright">Extra concurrency purchased</TableCell>
|
||||
@@ -379,8 +381,15 @@ function Upgradable({
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow className={allocationModified ? undefined : "after:bg-transparent"}>
|
||||
<TableCell colSpan={2} className="py-0">
|
||||
<TableRow
|
||||
className={
|
||||
allocationModified || unallocated > 0 ? undefined : "after:bg-transparent"
|
||||
}
|
||||
>
|
||||
<TableCell
|
||||
colSpan={2}
|
||||
className={cn("py-0", (unallocated > 0 || allocationModified) && "pr-0")}
|
||||
>
|
||||
<div className="flex h-10 items-center">
|
||||
{allocationModified ? (
|
||||
unallocated < 0 ? (
|
||||
@@ -419,6 +428,16 @@ function Upgradable({
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
) : unallocated > 0 ? (
|
||||
<div className="flex h-full w-full items-center justify-between bg-success/10 px-2.5">
|
||||
<div className="flex items-center justify-start gap-1">
|
||||
<InformationCircleIcon className="size-4 text-success" />
|
||||
<span className="text-success">
|
||||
You have {unallocated} extra concurrency available to allocate below.
|
||||
</span>
|
||||
</div>
|
||||
<ArrowDownIcon className="size-4 animate-bounce text-success" />
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
@@ -434,7 +453,7 @@ function Upgradable({
|
||||
<div className="flex items-center pb-1">
|
||||
<Header3 className="grow">Concurrency allocation</Header3>
|
||||
</div>
|
||||
<Table>
|
||||
<Table variant="bright/no-hover">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHeaderCell className="pl-0">Environment</TableHeaderCell>
|
||||
@@ -452,7 +471,12 @@ function Upgradable({
|
||||
{environments.map((environment, index) => (
|
||||
<TableRow key={environment.id}>
|
||||
<TableCell>
|
||||
<EnvironmentCombo environment={environment} />
|
||||
<EnvironmentCombo
|
||||
environment={environment}
|
||||
className="max-w-[18ch]"
|
||||
tooltipSideOffset={6}
|
||||
tooltipSide="top"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell alignment="right">{environment.planConcurrencyLimit}</TableCell>
|
||||
<TableCell alignment="right">
|
||||
@@ -523,7 +547,7 @@ function NotUpgradable({ environments }: { environments: EnvironmentWithConcurre
|
||||
</>
|
||||
) : null}
|
||||
<div className="mt-3 flex flex-col gap-3">
|
||||
<Table>
|
||||
<Table variant="bright/no-hover">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHeaderCell className="pl-0">Environment</TableHeaderCell>
|
||||
@@ -682,7 +706,7 @@ function PurchaseConcurrencyModal({
|
||||
</div>
|
||||
<div className="grid grid-cols-2 text-xs">
|
||||
<span className="text-text-dimmed">
|
||||
({extraConcurrency / concurrencyPricing.stepSize} bundles)
|
||||
({simplur`${extraConcurrency / concurrencyPricing.stepSize} bundle[|s]`})
|
||||
</span>
|
||||
<span className="justify-self-end text-text-dimmed">/mth</span>
|
||||
</div>
|
||||
@@ -703,8 +727,11 @@ function PurchaseConcurrencyModal({
|
||||
</div>
|
||||
<div className="grid grid-cols-2 text-xs">
|
||||
<span className="text-text-dimmed">
|
||||
({(amountValue - extraConcurrency) / concurrencyPricing.stepSize} bundles @{" "}
|
||||
{formatCurrency(concurrencyPricing.centsPerStep / 100, true)}/mth)
|
||||
(
|
||||
{simplur`${
|
||||
(amountValue - extraConcurrency) / concurrencyPricing.stepSize
|
||||
} bundle[|s]`}{" "}
|
||||
@ {formatCurrency(concurrencyPricing.centsPerStep / 100, true)}/mth)
|
||||
</span>
|
||||
<span className="justify-self-end text-text-dimmed">/mth</span>
|
||||
</div>
|
||||
@@ -723,7 +750,7 @@ function PurchaseConcurrencyModal({
|
||||
</div>
|
||||
<div className="grid grid-cols-2 text-xs">
|
||||
<span className="text-text-dimmed">
|
||||
({amountValue / concurrencyPricing.stepSize} bundles)
|
||||
({simplur`${amountValue / concurrencyPricing.stepSize} bundle[|s]`})
|
||||
</span>
|
||||
<span className="justify-self-end text-text-dimmed">/mth</span>
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import RateLimitHitUseBatchTrigger from "/snippets/rate-limit-hit-use-batchtrigg
|
||||
| Hobby | 25 concurrent runs |
|
||||
| Pro | 100+ concurrent runs |
|
||||
|
||||
Additional bundles above the Pro tier are available for $50/month per 50 concurrent runs. Contact us via [email](https://trigger.dev/contact) or [Discord](https://trigger.dev/discord) to request more.
|
||||
Extra concurrency above the Pro tier limit is available via the dashboard. Click the "Concurrency" page from the left sidebar when on the Pro plan to purchase more.
|
||||
|
||||
## Rate limits
|
||||
|
||||
|
||||
Reference in New Issue
Block a user