diff --git a/.server-changes/remove-obsolete-v4-version-copy.md b/.server-changes/remove-obsolete-v4-version-copy.md new file mode 100644 index 0000000000..faece1f752 --- /dev/null +++ b/.server-changes/remove-obsolete-v4-version-copy.md @@ -0,0 +1,6 @@ +--- +area: webapp +type: improvement +--- + +Out-of-date upgrade prompts no longer appear in the dashboard: the "V4" badges and the notices saying preview branches and the queues table need V4 have been removed. The side menu still warns you when a project is on v3, with updated wording and a link to the v4 upgrade guide. diff --git a/apps/webapp/app/assets/images/queues-dashboard.png b/apps/webapp/app/assets/images/queues-dashboard.png deleted file mode 100644 index 321c79e629..0000000000 Binary files a/apps/webapp/app/assets/images/queues-dashboard.png and /dev/null differ diff --git a/apps/webapp/app/components/BlankStatePanels.tsx b/apps/webapp/app/components/BlankStatePanels.tsx index b609ae3bc4..adb5661a4b 100644 --- a/apps/webapp/app/components/BlankStatePanels.tsx +++ b/apps/webapp/app/components/BlankStatePanels.tsx @@ -7,7 +7,6 @@ import { PlusIcon, QuestionMarkCircleIcon, RectangleGroupIcon, - RectangleStackIcon, SparklesIcon, Squares2X2Icon, } from "@heroicons/react/20/solid"; @@ -63,7 +62,6 @@ import { TriggerDevStepV3, } from "./SetupCommands"; import { StepContentContainer } from "./StepContentContainer"; -import { V4Badge } from "./V4Badge"; /** * What the agent is asked when it's opened from a deployment setup panel. The panel is the docs @@ -475,36 +473,6 @@ export function AlertsNoneDeployed() { ); } -export function QueuesHasNoTasks() { - const organization = useOrganization(); - const project = useProject(); - const environment = useEnvironment(); - - return ( - - Create a task - - } - > - - Queues will appear here when you have created a task in this environment. Follow the - instructions on the{" "} - Tasks page{" "} - to create a task, then return here to see its queue. - - - ); -} - export function NoWaitpointTokens() { return ( - + Preview branches in Trigger.dev create isolated environments for testing new features before production. - - You must be on to access preview branches. Read our{" "} - upgrade to v4 guide to learn more. - ); } @@ -648,14 +612,10 @@ export function BranchesNoBranches({ /> } > - + Branches are a way to test new features in isolation before merging them into the main environment. - - Branches are only available when using or above. Read our{" "} - v4 upgrade guide to learn more. - ); } diff --git a/apps/webapp/app/components/V4Badge.tsx b/apps/webapp/app/components/V4Badge.tsx deleted file mode 100644 index c92baabac8..0000000000 --- a/apps/webapp/app/components/V4Badge.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { cn } from "~/utils/cn"; -import { Badge } from "./primitives/Badge"; -import { SimpleTooltip } from "./primitives/Tooltip"; - -export function V4Badge({ inline = false, className }: { inline?: boolean; className?: string }) { - return ( - - V4 - - } - content="This feature is only available in V4 and above." - disableHoverableContent - /> - ); -} - -export function V4Title({ children }: { children: React.ReactNode }) { - return ( - <> - {children} - - - ); -} diff --git a/apps/webapp/app/components/navigation/EnvironmentSelector.tsx b/apps/webapp/app/components/navigation/EnvironmentSelector.tsx index b2c1d593a0..7af60c2a67 100644 --- a/apps/webapp/app/components/navigation/EnvironmentSelector.tsx +++ b/apps/webapp/app/components/navigation/EnvironmentSelector.tsx @@ -11,7 +11,7 @@ import { useFeatures } from "~/hooks/useFeatures"; import { useOrganization, type MatchedOrganization } from "~/hooks/useOrganizations"; import { useProject } from "~/hooks/useProject"; import { cn } from "~/utils/cn"; -import { branchesPath, branchesDevPath, docsPath, v3BillingPath } from "~/utils/pathBuilder"; +import { branchesPath, branchesDevPath, v3BillingPath } from "~/utils/pathBuilder"; import { EnvironmentCombo, EnvironmentIcon, @@ -29,9 +29,7 @@ import { PopoverSectionHeader, PopoverTrigger, } from "../primitives/Popover"; -import { TextLink } from "../primitives/TextLink"; import { SimpleTooltip } from "../primitives/Tooltip"; -import { V4Badge } from "../V4Badge"; import { type SideMenuEnvironment, type SideMenuProject } from "./SideMenu"; import { Badge } from "../primitives/Badge"; @@ -411,14 +409,10 @@ export function BranchesPopoverContent({ Create your first branch - + Branches are a way to test new features in isolation before merging them into the main environment. - - Branches are only available when using or above. Read our{" "} - v4 upgrade guide to learn more. - ) : (
diff --git a/apps/webapp/app/components/navigation/SideMenu.tsx b/apps/webapp/app/components/navigation/SideMenu.tsx index 292921f273..f69d0f16bc 100644 --- a/apps/webapp/app/components/navigation/SideMenu.tsx +++ b/apps/webapp/app/components/navigation/SideMenu.tsx @@ -96,6 +96,7 @@ import { adminPath, branchesPath, concurrencyPath, + docsPath, limitsPath, logoutPath, newOrganizationPath, @@ -1111,7 +1112,7 @@ export function SideMenu({ isDragging={isDragging} className="min-w-0 flex-1" /> - {environment.type === "DEVELOPMENT" && project.engine === "V2" && ( + {environment.type === "DEVELOPMENT" && ( @@ -1604,7 +1605,7 @@ function V3DeprecationPanel({ } - content="V3 deprecation warning" + content="v3 is now deprecated" side="right" sideOffset={8} disableHoverableContent @@ -1625,23 +1626,23 @@ function V3DeprecationContent() {
- V3 deprecation warning + v3 is now deprecated
- This is a v3 project. V3 deploys will stop working on 1 April 2026. Full shutdown is 1 July - 2026 where all v3 runs will stop executing. Migrate to v4 to avoid downtime. + This is a v3 project which is now deprecated so no runs are executing. Upgrade to v4 to + resume executing runs in this project. - View migration guide + Upgrade to v4
); diff --git a/apps/webapp/app/presenters/v3/QueueListPresenter.server.ts b/apps/webapp/app/presenters/v3/QueueListPresenter.server.ts index 50278e8276..17ccc68a03 100644 --- a/apps/webapp/app/presenters/v3/QueueListPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/QueueListPresenter.server.ts @@ -5,7 +5,6 @@ import { type PrismaClientOrTransaction } from "~/db.server"; import { type AuthenticatedEnvironment } from "~/services/apiAuth.server"; import { clickhouseFactory } from "~/services/clickhouse/clickhouseFactoryInstance.server"; import { logger } from "~/services/logger.server"; -import { determineEngineVersion } from "~/v3/engineVersion.server"; import { engine } from "~/v3/runEngine.server"; import { BasePresenter } from "./basePresenter.server"; import { toQueueItem } from "./QueueRetrievePresenter.server"; @@ -51,25 +50,12 @@ type QueueListPagination = | { mode: "filtered"; currentPage: number; hasMore: boolean } | { mode: "unfiltered"; currentPage: number; totalPages: number; count: number }; -// The `?: undefined` markers keep every key reachable across the union, so consumers -// can destructure before narrowing on `success`. -export type QueueListResult = - | { - success: false; - code: string; - totalQueues: number; - hasFilters: boolean; - queues?: undefined; - pagination?: undefined; - } - | { - success: true; - queues: QueueListItem[]; - pagination: QueueListPagination; - totalQueues?: number; - hasFilters: boolean; - code?: undefined; - }; +export type QueueListResult = { + queues: QueueListItem[]; + pagination: QueueListPagination; + totalQueues?: number; + hasFilters: boolean; +}; function formatClickhouseDateTime(date: Date): string { return date.toISOString().slice(0, 19).replace("T", " "); @@ -126,37 +112,6 @@ export class QueueListPresenter extends BasePresenter { }): Promise { const hasFilters = Boolean(query?.trim()) || type !== undefined; - const engineVersion = await determineEngineVersion({ environment }); - if (engineVersion === "V1") { - const totalQueues = await this._replica.taskQueue.count({ - where: buildQueueListWhere(environment.id, query, type), - }); - - if (totalQueues === 0) { - const oldQueue = await this._replica.taskQueue.findFirst({ - where: { - runtimeEnvironmentId: environment.id, - version: "V1", - }, - }); - if (oldQueue) { - return { - success: false as const, - code: "engine-version", - totalQueues: 1, - hasFilters, - }; - } - } - - return { - success: false as const, - code: "engine-version", - totalQueues, - hasFilters, - }; - } - if (sort !== "name") { // Ranking is additive: any failure or unsupported input falls back to name order. try { @@ -173,7 +128,6 @@ export class QueueListPresenter extends BasePresenter { const { queues, hasMore } = await this.getFilteredQueues(environment, query, page, type); return { - success: true as const, queues, pagination: { mode: "filtered" as const, @@ -189,7 +143,6 @@ export class QueueListPresenter extends BasePresenter { }); return { - success: true as const, queues: await this.getUnfilteredQueues(environment, page, type), pagination: { mode: "unfiltered" as const, @@ -300,7 +253,6 @@ export class QueueListPresenter extends BasePresenter { } return { - success: true as const, queues: await this.enrichQueues(environment, [...rankedPageQueues, ...tailQueues]), pagination: { mode: "unfiltered" as const, diff --git a/apps/webapp/app/presenters/v3/QueueRetrievePresenter.server.ts b/apps/webapp/app/presenters/v3/QueueRetrievePresenter.server.ts index 08e0d751e3..f6918394e5 100644 --- a/apps/webapp/app/presenters/v3/QueueRetrievePresenter.server.ts +++ b/apps/webapp/app/presenters/v3/QueueRetrievePresenter.server.ts @@ -7,7 +7,6 @@ import { type TaskQueueType, } from "@trigger.dev/database"; import { type AuthenticatedEnvironment } from "~/services/apiAuth.server"; -import { determineEngineVersion } from "~/v3/engineVersion.server"; import { engine } from "~/v3/runEngine.server"; import { BasePresenter } from "./basePresenter.server"; @@ -80,16 +79,6 @@ export class QueueRetrievePresenter extends BasePresenter { environment: AuthenticatedEnvironment; queueInput: RetrieveQueueParam; }) { - //check the engine is the correct version - const engineVersion = await determineEngineVersion({ environment }); - - if (engineVersion === "V1") { - return { - success: false as const, - code: "engine-version", - }; - } - const queue = await getQueue(this._replica, environment, queueInput); if (!queue) { return { diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.branches/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.branches/route.tsx index caf6334ff9..c3ff335e3b 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.branches/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.branches/route.tsx @@ -14,7 +14,6 @@ import { BranchEnvironmentIconSmall } from "~/assets/icons/EnvironmentIcons"; import { BranchesNoBranchableEnvironment, BranchesNoBranches } from "~/components/BlankStatePanels"; import { Feedback } from "~/components/Feedback"; import { GitMetadata } from "~/components/GitMetadata"; -import { V4Title } from "~/components/V4Badge"; import { AdminDebugTooltip } from "~/components/admin/debugTooltip"; import { MainCenteredContainer, PageBody, PageContainer } from "~/components/layout/AppLayout"; import { Badge } from "~/components/primitives/Badge"; @@ -231,7 +230,7 @@ export default function Page() { return ( - Preview branches} /> + @@ -245,7 +244,7 @@ export default function Page() { return ( - Preview branches} /> + diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dev-branches/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dev-branches/route.tsx index 0b1c728fe9..244b65e204 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dev-branches/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.dev-branches/route.tsx @@ -6,7 +6,6 @@ import { useCallback } from "react"; import { SearchInput } from "~/components/primitives/SearchInput"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { BranchEnvironmentIconSmall } from "~/assets/icons/EnvironmentIcons"; -import { V4Title } from "~/components/V4Badge"; import { AdminDebugTooltip } from "~/components/admin/debugTooltip"; import { PageBody, PageContainer } from "~/components/layout/AppLayout"; import { Badge } from "~/components/primitives/Badge"; @@ -98,7 +97,7 @@ export default function Page() { return ( - Dev branches} /> + diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx index 61af4c1f0f..e4c190f85c 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx @@ -16,15 +16,12 @@ import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { z } from "zod"; import { ConcurrencyIcon } from "~/assets/icons/ConcurrencyIcon"; import { RunsIcon } from "~/assets/icons/RunsIcon"; -import upgradeForQueuesPath from "~/assets/images/queues-dashboard.png"; import { AdminDebugTooltip } from "~/components/admin/debugTooltip"; -import { QueuesHasNoTasks } from "~/components/BlankStatePanels"; import { environmentFullTitle } from "~/components/environments/EnvironmentLabel"; import { PageBody, PageContainer } from "~/components/layout/AppLayout"; import { MetricsLayout } from "~/components/layout/MetricsLayout"; import { Badge } from "~/components/primitives/Badge"; import { Button, LinkButton } from "~/components/primitives/Buttons"; -import { Callout } from "~/components/primitives/Callout"; import { Dialog, DialogContent, DialogHeader, DialogTrigger } from "~/components/primitives/Dialog"; import { FormButtons } from "~/components/primitives/FormButtons"; import { Header3 } from "~/components/primitives/Headers"; @@ -205,7 +202,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { byQueue: Record; } | null = null; - if (queueMetricsUiEnabled && queues.success) { + if (queueMetricsUiEnabled) { // Metrics are additive observability; a ClickHouse hiccup must not take down queue // management. Fail open to metrics: null instead of bubbling to the page-level 400. try { @@ -248,7 +245,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { // Allocation summary (Environment limit + Allocated tiles) is additive; a presenter // failure must not 400 the page, so fail open to null like the metrics block above. let allocation: Awaited> | null = null; - if (queueMetricsUiEnabled && queues.success) { + if (queueMetricsUiEnabled) { try { allocation = await new QueueAllocationPresenter().call({ environment }); } catch (error) { @@ -380,9 +377,7 @@ function QueuesWithMetricsView() { const { environment, queues, - success, pagination, - code, totalQueues, hasFilters, autoReloadPollIntervalMs, @@ -492,153 +487,146 @@ function QueuesWithMetricsView() { {/* Filters — pinned bar directly under the NavBar. Left cluster = search + period; right cluster = pagination. */} - {success ? ( - -
- -
-
- - {environment.runsEnabled && - env.pauseSource !== ENVIRONMENT_PAUSE_SOURCE_BILLING_LIMIT ? ( - - ) : null} - -
-
- ) : null} + +
+ +
+
+ + {environment.runsEnabled && + env.pauseSource !== ENVIRONMENT_PAUSE_SOURCE_BILLING_LIMIT ? ( + + ) : null} + +
+
{/* Queued + Running + Allocated + Environment limit summary. Four stat tiles: the grid derives its columns from the tile count (two-up, four-up from lg). The allocation presenter fails open to null (a ClickHouse/PG hiccup mustn't take down the tiles), so only the Allocated tile depends on it — the other three + controls always render, and Allocated shows a "–" placeholder to keep the 4-tile grid shape stable. */} - {success ? ( - - paused : undefined} - animate - accessory={ - - + + paused : undefined} + animate + accessory={ + + + + } + valueClassName={env.paused ? "text-warning tabular-nums" : "tabular-nums"} + compactThreshold={1000000} + /> + + Including {envRunningLive - environment.concurrencyLimit} burst runs{" "} + - } - valueClassName={env.paused ? "text-warning tabular-nums" : "tabular-nums"} - compactThreshold={1000000} - /> - + + + } + compactThreshold={1000000} + /> + + Allocated + {allocation && overAllocated ? ( + + ) : null} + + } + value={allocation ? allocated : undefined} + formattedValue={allocation ? undefined : "–"} + valueClassName={cn(allocation && overAllocated && "text-warning")} + suffix={allocation ? `${allocationPct}% of the environment limit` : undefined} + suffixClassName="text-text-dimmed" + /> + 1 ? `bursts up to ${burstLimit}` : undefined} + suffixClassName="text-text-dimmed" + accessory={ + plan ? ( + plan?.v3Subscription?.plan?.limits.concurrentRuns.canExceed ? ( + - Including {envRunningLive - environment.concurrencyLimit} burst runs{" "} - - - ) : limitStatus === "limit" ? ( - "At concurrency limit" - ) : undefined - } - accessory={ - + Increase limit + + ) : ( - - } - compactThreshold={1000000} - /> - - Allocated - {allocation && overAllocated ? ( - - ) : null} - - } - value={allocation ? allocated : undefined} - formattedValue={allocation ? undefined : "–"} - valueClassName={cn(allocation && overAllocated && "text-warning")} - suffix={allocation ? `${allocationPct}% of the environment limit` : undefined} - suffixClassName="text-text-dimmed" - /> - 1 ? `bursts up to ${burstLimit}` : undefined} - suffixClassName="text-text-dimmed" - accessory={ - plan ? ( - plan?.v3Subscription?.plan?.limits.concurrentRuns.canExceed ? ( - - Increase limit - - ) : ( - - Increase limit - - ) - ) : undefined - } - /> - - ) : null} + to={v3BillingPath(organization, "Upgrade your plan for more concurrency")} + variant="secondary/small" + LeadingIcon={ArrowUpCircleIcon} + leadingIconClassName="text-indigo-500" + > + Increase limit + + ) + ) : undefined + } + /> + - {success && (hasFilters || totalQueues !== 0) ? ( + {hasFilters || totalQueues !== 0 ? ( {QUEUE_HEADER_TILES.map((tile) => ( @@ -692,356 +680,342 @@ function QueuesWithMetricsView() { ) : null} - {success ? ( - - {/* Default overflow-x-auto container so wide tables still scroll horizontally on + + {/* Default overflow-x-auto container so wide tables still scroll horizontally on narrow viewports; the page (not this region) owns vertical scrolling. */} - - - - Name - Queued - Running - Limit - -

- Environment: uses the - environment limit of {environment.concurrencyLimit}. -

-

- User: a limit you set in your - code. -

-

- Override: a limit you set here - or via the API. -

- - } - > - Limited by -
- Health - - Delay p95 - - - How many runs were waiting, over the selected time. marks - where the queue was throttled. - - } - > - Backlog - - - Pause/resume - -
-
- - {queueRows.length > 0 ? ( - queueRows.map((queue) => { - const limit = queue.concurrencyLimit ?? environment.concurrencyLimit; - const isAtConcurrencyLimit = queue.running >= limit; - const isAtQueueLimit = - environment.queueSizeLimit !== null && - queue.queued >= environment.queueSizeLimit; - const queueFilterableName = queueMetricsKey(queue); - const queueMetric = metricsByQueue[queueFilterableName]; - const queueDetailPath = v3QueuePath(organization, project, env, { - friendlyId: queue.id, - }); - return ( - - s, so - // they render beside the link (leading/trailing), never inside it — - // otherwise the cell is invalid
+ + + Name + Queued + Running + Limit + +

+ Environment: uses the environment + limit of {environment.concurrencyLimit}. +

+

+ User: a limit you set in your + code. +

+

+ Override: a limit you set here or + via the API. +

+ + } + > + Limited by +
+ Health + + Delay p95 + + + How many runs were waiting, over the selected time. marks + where the queue was throttled. + + } + > + Backlog + + + Pause/resume + +
+
+ + {queueRows.length > 0 ? ( + queueRows.map((queue) => { + const limit = queue.concurrencyLimit ?? environment.concurrencyLimit; + const isAtConcurrencyLimit = queue.running >= limit; + const isAtQueueLimit = + environment.queueSizeLimit !== null && + queue.queued >= environment.queueSizeLimit; + const queueFilterableName = queueMetricsKey(queue); + const queueMetric = metricsByQueue[queueFilterableName]; + const queueDetailPath = v3QueuePath(organization, project, env, { + friendlyId: queue.id, + }); + return ( + + s, so + // they render beside the link (leading/trailing), never inside it — + // otherwise the cell is invalid
-
- ) : ( -
- {totalQueues === 0 ? ( -
- -
- ) : code === "engine-version" ? ( - - ) : ( - Something went wrong - )} -
- )} + )} + + + + + + + } + /> + + ); + }) + ) : ( + + +
+ + {hasFilters ? "No queues found matching your filters" : "No queues found"} + +
+
+
+ )} + + +
); @@ -1149,34 +1123,6 @@ function EnvironmentPauseResumeButton({ ); } -function EngineVersionUpgradeCallout() { - return ( -
-
-

New queues table

- - Upgrade guide - -
-
- - Upgrade to SDK version 4+ to view the new queues table, and be able to pause and resume - individual queues. - - Upgrade for queues -
-
- ); -} - export function isEnvironmentPauseResumeFormSubmission( formMethod: string | undefined, formData: FormData | undefined @@ -1667,16 +1613,8 @@ function formatOverridePercent(percent: number): string { // Classic Queues page, restored verbatim from before the Queue Metrics feature. Rendered // when queueMetricsUiEnabled is off so a gated org sees exactly the pre-metrics UI. function ClassicQueuesView() { - const { - environment, - queues, - success, - pagination, - code, - totalQueues, - hasFilters, - autoReloadPollIntervalMs, - } = useTypedLoaderData(); + const { environment, queues, pagination, hasFilters, autoReloadPollIntervalMs } = + useTypedLoaderData(); const organization = useOrganization(); const project = useProject(); @@ -1815,260 +1753,240 @@ function ClassicQueuesView() { /> - {success ? ( -
-
- - -
- - - - Name - Queued - Running - Limit - -
- Environment - - This queue is limited by your environment's concurrency limit of{" "} - {environment.concurrencyLimit}. - -
-
- User - - This queue is limited by a concurrency limit set in your code. - -
-
- Override - - This queue's concurrency limit has been manually overridden from the - dashboard or API. - -
- - } - > - Limited by -
- - Pause/resume - -
-
- - {queues.length > 0 ? ( - queues.map((queue) => { - const limit = queue.concurrencyLimit ?? environment.concurrencyLimit; - const isAtConcurrencyLimit = queue.running >= limit; - const isAtQueueLimit = - environment.queueSizeLimit !== null && - queue.queued >= environment.queueSizeLimit; - const queueFilterableName = `${queue.type === "task" ? "task/" : ""}${ - queue.name - }`; - return ( - - - - - {queue.concurrency?.overriddenAt ? ( - - Concurrency limit overridden - - } - content="This queue's concurrency limit has been manually overridden from the dashboard or API." - className="max-w-[230px]" - disableHoverableContent - /> - ) : null} - {queue.paused ? ( - - Paused - - ) : null} - {isAtQueueLimit ? ( - - At queue limit - - ) : null} - {isAtConcurrencyLimit ? ( - - At concurrency limit - - ) : null} - - - - {queue.queued} - - 0 && "text-text-bright", - isAtConcurrencyLimit && "text-warning" - )} +
+
+ + +
+
+ + + Name + Queued + Running + Limit + +
+ Environment + - {queue.running} - - +
+
+ User + - {limit} - - +
+
+ Override + + This queue's concurrency limit has been manually overridden from the + dashboard or API. + +
+ + } + > + Limited by +
+ + Pause/resume + +
+
+ + {queues.length > 0 ? ( + queues.map((queue) => { + const limit = queue.concurrencyLimit ?? environment.concurrencyLimit; + const isAtConcurrencyLimit = queue.running >= limit; + const isAtQueueLimit = + environment.queueSizeLimit !== null && + queue.queued >= environment.queueSizeLimit; + const queueFilterableName = `${queue.type === "task" ? "task/" : ""}${ + queue.name + }`; + return ( + + + + {queue.concurrency?.overriddenAt ? ( - Override - ) : queue.concurrencyLimit ? ( - "User" - ) : ( - "Environment" - )} - - - } - hiddenButtons={ - !queue.paused && - } - popoverContent={ - <> - {queue.paused ? ( - - ) : ( - - )} - - - - + Concurrency limit overridden + + } + content="This queue's concurrency limit has been manually overridden from the dashboard or API." + className="max-w-[230px]" + disableHoverableContent + /> + ) : null} + {queue.paused ? ( + + Paused + + ) : null} + {isAtQueueLimit ? ( + + At queue limit + + ) : null} + {isAtConcurrencyLimit ? ( + + At concurrency limit + + ) : null} + + + + {queue.queued} + + 0 && "text-text-bright", + isAtConcurrencyLimit && "text-warning" + )} + > + {queue.running} + + + {limit} + + + {queue.concurrency?.overriddenAt ? ( + Override + ) : queue.concurrencyLimit ? ( + "User" + ) : ( + "Environment" + )} + + } + hiddenButtons={!queue.paused && } + popoverContent={ + <> + {queue.paused ? ( + - - - } - /> - - ); - }) - ) : ( - - -
- - {hasFilters - ? "No queues found matching your filters" - : "No queues found"} - -
-
-
- )} -
-
-
- ) : ( -
- {totalQueues === 0 ? ( -
- -
- ) : code === "engine-version" ? ( - - ) : ( - Something went wrong - )} -
- )} + )} + + + + + + + } + /> + + ); + }) + ) : ( + + +
+ + {hasFilters ? "No queues found matching your filters" : "No queues found"} + +
+
+
+ )} + + +
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs._index/route.tsx index 39c32f96cc..50bfb36a58 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs._index/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs._index/route.tsx @@ -166,16 +166,13 @@ export default function Page() { const { data, rootOnlyDefault, filters, canCancelRuns, canReplayRuns } = useTypedLoaderData(); const { isConnected } = useDevPresence(); - const project = useProject(); const environment = useEnvironment(); return ( <> } /> - {environment.type === "DEVELOPMENT" && project.engine === "V2" && ( - - )} + {environment.type === "DEVELOPMENT" && } diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.tokens/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.tokens/route.tsx index 005f10fdd9..192cb2f0e8 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.tokens/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.tokens/route.tsx @@ -35,7 +35,6 @@ import { WaitpointSearchParamsSchema, WaitpointTokenFilters, } from "~/components/runs/v3/WaitpointTokenFilters"; -import { V4Title } from "~/components/V4Badge"; import { useEnvironment } from "~/hooks/useEnvironment"; import { useOrganization } from "~/hooks/useOrganizations"; import { useProject } from "~/hooks/useProject"; @@ -138,7 +137,7 @@ export default function Page() { return ( - Waitpoint Tokens} /> + diff --git a/apps/webapp/app/routes/api.v1.queues.$queueParam.ts b/apps/webapp/app/routes/api.v1.queues.$queueParam.ts index 0ad1df95d2..434046d961 100644 --- a/apps/webapp/app/routes/api.v1.queues.$queueParam.ts +++ b/apps/webapp/app/routes/api.v1.queues.$queueParam.ts @@ -3,6 +3,7 @@ import { type QueueItem, type RetrieveQueueParam, RetrieveQueueType } from "@tri import { z } from "zod"; import { QueueRetrievePresenter } from "~/presenters/v3/QueueRetrievePresenter.server"; import { createLoaderApiRoute } from "~/services/routeBuilders/apiBuilder.server"; +import { determineEngineVersion } from "~/v3/engineVersion.server"; const SearchParamsSchema = z.object({ type: RetrieveQueueType.default("id"), @@ -24,6 +25,15 @@ export const loader = createLoaderApiRoute( }, }, async ({ params, searchParams, authentication }) => { + // v3 (engine V1) has no V2 queues to retrieve, so old clients get a clean 400. + const engineVersion = await determineEngineVersion({ + environment: authentication.environment, + }); + + if (engineVersion === "V1") { + return json({ error: "engine-version" }, { status: 400 }); + } + const input: RetrieveQueueParam = searchParams.type === "id" ? params.queueParam @@ -39,11 +49,7 @@ export const loader = createLoaderApiRoute( }); if (!result.success) { - if (result.code === "queue-not-found") { - return json({ error: result.code }, { status: 404 }); - } - - return json({ error: result.code }, { status: 400 }); + return json({ error: result.code }, { status: 404 }); } const q: QueueItem = result.queue; diff --git a/apps/webapp/app/routes/api.v1.queues.ts b/apps/webapp/app/routes/api.v1.queues.ts index 27fe2eb93d..463aacc38b 100644 --- a/apps/webapp/app/routes/api.v1.queues.ts +++ b/apps/webapp/app/routes/api.v1.queues.ts @@ -8,6 +8,7 @@ import { import { toOffsetLimitQueueListPagination } from "~/presenters/v3/queueListPagination.server"; import { logger } from "~/services/logger.server"; import { createLoaderApiRoute } from "~/services/routeBuilders/apiBuilder.server"; +import { determineEngineVersion } from "~/v3/engineVersion.server"; import { ServiceValidationError } from "~/v3/services/baseService.server"; const SearchParamsSchema = z.object({ @@ -30,15 +31,20 @@ export const loader = createLoaderApiRoute( const service = new QueueListPresenter(searchParams.perPage); try { - const result = await service.call({ + // v3 (engine V1) has no V2 queues to list, so old clients get a clean 400. + const engineVersion = await determineEngineVersion({ environment: authentication.environment, - page: searchParams.page ?? 1, }); - if (!result.success) { - return json({ error: result.code }, { status: 400 }); + if (engineVersion === "V1") { + return json({ error: "engine-version" }, { status: 400 }); } + const result = await service.call({ + environment: authentication.environment, + page: searchParams.page ?? 1, + }); + const queues: QueueItem[] = result.queues; return json( { diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues.ts b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues.ts index b5c1755eae..bf732c5091 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues.ts +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues.ts @@ -51,15 +51,6 @@ export async function loader({ request, params }: LoaderFunctionArgs) { type, }); - if (!result.success) { - return { - queues: [], - currentPage: 1, - hasMore: false, - hasFilters: Boolean(query?.trim()) || Boolean(type), - }; - } - return { queues: result.queues.map((queue) => ({ id: queue.id, diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.ai-filter.tsx b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.ai-filter.tsx index ff289d241b..679bd19da9 100644 --- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.ai-filter.tsx +++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.ai-filter.tsx @@ -88,7 +88,7 @@ export async function action({ request, params }: ActionFunctionArgs) { type, }); return { - queues: queues.success ? queues.queues.map((q) => q.name) : [], + queues: queues.queues.map((q) => q.name), }; }, };