fix(deployment): lock GPU and CC-GPU for trials on the configure page - #3435
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3435 +/- ##
==========================================
- Coverage 72.58% 71.48% -1.11%
==========================================
Files 1166 1076 -90
Lines 29510 27189 -2321
Branches 7308 6866 -442
==========================================
- Hits 21421 19437 -1984
+ Misses 7111 6798 -313
+ Partials 978 954 -24
*This pull request uses carry forward flags. Click here to find out more.
🚀 New features to boost your workflow:
|
📝 WalkthroughWalkthroughAdds trial GPU selection blocking across deployment configuration and quote requests, including confidential compute gating and unlock CTAs. It also extends ChangesTrial GPU gating
Loading controls
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
Suggested reviewers: ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install timed out. The project may have too many dependencies for the sandbox. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigureDeploymentHeader/ConfigureDeploymentHeader.tsx`:
- Around line 72-78: Update the trial-blocked GPU validation error in
ConfigureDeploymentHeader to include an actionable “Add Funds” CTA or link that
opens the existing funding flow or credits sheet, rather than only plain
snackbar text. Reuse the established funding action and preserve the validation
behavior for blocked submissions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 3eb05e76-6222-4d1f-96df-8e5351b8c1f3
📒 Files selected for processing (13)
apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/ConfidentialComputeCard/ConfidentialComputeCard.spec.tsxapps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/ConfidentialComputeCard/ConfidentialComputeCard.tsxapps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/GpuCard/GpuCard.gated.spec.tsxapps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/GpuCard/GpuCard.tsxapps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/HardwareSection/HardwareSection.spec.tsxapps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/HardwareSection/HardwareSection.tsxapps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigureDeploymentHeader/ConfigureDeploymentHeader.spec.tsxapps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigureDeploymentHeader/ConfigureDeploymentHeader.tsxapps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsxapps/deploy-web/src/components/shared/SearchableSelect/SearchableSelect.spec.tsxapps/deploy-web/src/components/shared/SearchableSelect/SearchableSelect.tsxapps/deploy-web/src/utils/deploymentData/v1beta3.spec.tsapps/deploy-web/src/utils/deploymentData/v1beta3.ts
On the new "Configure your deployment" page (onboarding_redesign_v1), a
free-trial user who enabled Confidential Compute → CPU-GPU (or left a GPU
card on the empty "Any model" default) requested a GPU trials cannot use.
The deployment was created and entered quoting, but no usable bid ever
arrived, so the CTA stayed on "Requesting…" forever with no explanation
(CON-660).
Lock the unusable options upfront and guard submission:
- New UI-only predicates isTrialBlockedGpuSelection / hasTrialBlockedGpu in
v1beta3 treat an empty ("any") model as blocked when the vendor exposes any
blocked model. This is intentionally stricter than the SDL trial policy and
the backend, which leave "any" to per-bid enforcement — "any" is an
unreliable gamble that only draws a usable bid if an allowed-model provider
happens to bid. The rationale is documented in code to preempt review flags.
- ConfidentialComputeCard: lock the CPU-GPU radio with a warning + add-credits
CTA, keep CPU-only TEE selectable, and defensively reject cpu-gpu in setTee.
- GpuCard: lock the "Any model" empty option; allowed specific models stay
selectable.
- SearchableSelect: support a disabled empty option.
- HardwareSection: route the new predicate and pass isGpuBlocked/onUnlock to
the confidential compute card.
- ConfigureDeploymentHeader: add a load-bearing submit guard — enabling the GPU
card leaves the model at the empty default without opening the locked picker,
so the presentational lock alone can't stop an empty-model submission.
Fixes CON-660
The CPU-GPU trial warning on the configure page rendered at base font size with heavy padding and a muted ghost unlock button that read as disabled text, and its copy implied all GPU access was blocked rather than only high-end models. - Shrink the alert (text-sm, tighter padding) so it reads as a hint. - Add a `prominent` variant to UnlockGpusButton and use it here, so the add-credits CTA is a filled primary button instead of a ghost link. - Reword to "High-end GPUs aren't available on a free trial", matching the unlock tooltip and the actual high-end-only blocklist. - Drop the em dash in the CPU-GPU option description.
isGpuBlocked was derived from isBlockedModel("nvidia", ""), so the
confidential-compute CPU-GPU lock only worked as a side effect of the
stricter "any model" UI rule. Gate it directly instead: a restricted trial
while the GPU restriction is switched on.
- Add isTrialGpuRestrictionActive() (true when the blocklist is non-empty).
- HardwareSection: isGpuBlocked = isRestricted && !locked && isTrialGpuRestrictionActive().
- Behavior-preserving in production (all-nvidia blocklist); if the blocklist
is emptied the whole restriction is a no-op, so CC-GPU correctly unblocks
along with every other GPU.
LoadingButton renders the default filled-button spinner head as fill-primary, the same color as the button's bg-primary, so it was invisible while a coupon redemption was in flight. Pass a text-current LoaderCircle as the loading indicator (the in-button spinner idiom already used elsewhere in deploy-web) so it matches the label color and shows.
…buttons
Two bugs in the shared LoadingButton surfaced in the add-credits flow:
1. Double-submit: LoadingButton spread `{...props}` (which carries `disabled`)
after its own `disabled={loading || disabled}`, so a caller's `disabled`
clobbered the combined value and `loading` never disabled the button. The
"Purchase Credits" button stayed clickable while a charge was in flight.
Destructure `disabled` so the loading state always blocks clicks. This also
closes the same latent hole in TrialStartButton and DeploymentAlerts.
2. Invisible spinner: the default filled-button spinner used a Spinner variant
whose arc is fill-primary, identical to bg-primary. Replace it with a ring
tinted by the button's own foreground (border-current), visible on every
variant.
- AddCreditsForm: switch the raw Button + Spinner to LoadingButton.
- RedeemCouponForm: drop the one-off loadingIndicator now that the default is
correct.
- Add a LoadingButton spec (none existed), including a regression test for the
disabled-while-loading behavior.
4d40f01 to
59c7c8b
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/ui/components/loading-button.spec.tsx (1)
1-6: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winSort the imports with
simple-import-sort.
@testing-library/reactmust be grouped and sorted with the other package imports before the relative./loading-buttonimport.Proposed fix
+import { render, screen } from "`@testing-library/react`"; import type { ReactNode } from "react"; import { describe, expect, it } from "vitest"; import { LoadingButton } from "./loading-button"; - -import { render, screen } from "`@testing-library/react`";🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/components/loading-button.spec.tsx` around lines 1 - 6, Reorder the imports in the loading-button spec so the `@testing-library/react` package import is grouped and sorted with the other external imports, before the relative ./loading-button import, following simple-import-sort conventions.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/ui/components/loading-button.spec.tsx`:
- Around line 1-6: Reorder the imports in the loading-button spec so the
`@testing-library/react` package import is grouped and sorted with the other
external imports, before the relative ./loading-button import, following
simple-import-sort conventions.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 9a1195be-2025-476c-8903-89ba0237e078
📒 Files selected for processing (17)
apps/deploy-web/src/components/billing-usage/AddCreditsForm/AddCreditsForm.tsxapps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/ConfidentialComputeCard/ConfidentialComputeCard.spec.tsxapps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/ConfidentialComputeCard/ConfidentialComputeCard.tsxapps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/GpuCard/GpuCard.gated.spec.tsxapps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/GpuCard/GpuCard.tsxapps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/HardwareSection/HardwareSection.spec.tsxapps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/HardwareSection/HardwareSection.tsxapps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/UnlockGpusButton/UnlockGpusButton.tsxapps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigureDeploymentHeader/ConfigureDeploymentHeader.spec.tsxapps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigureDeploymentHeader/ConfigureDeploymentHeader.tsxapps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsxapps/deploy-web/src/components/shared/SearchableSelect/SearchableSelect.spec.tsxapps/deploy-web/src/components/shared/SearchableSelect/SearchableSelect.tsxapps/deploy-web/src/utils/deploymentData/v1beta3.spec.tsapps/deploy-web/src/utils/deploymentData/v1beta3.tspackages/ui/components/loading-button.spec.tsxpackages/ui/components/loading-button.tsx
🚧 Files skipped from review as they are similar to previous changes (14)
- apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx
- apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/UnlockGpusButton/UnlockGpusButton.tsx
- apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/GpuCard/GpuCard.tsx
- apps/deploy-web/src/components/shared/SearchableSelect/SearchableSelect.spec.tsx
- apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/HardwareSection/HardwareSection.spec.tsx
- apps/deploy-web/src/components/shared/SearchableSelect/SearchableSelect.tsx
- apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/GpuCard/GpuCard.gated.spec.tsx
- apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/ConfidentialComputeCard/ConfidentialComputeCard.spec.tsx
- apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigureDeploymentHeader/ConfigureDeploymentHeader.tsx
- apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/HardwareSection/HardwareSection.tsx
- apps/deploy-web/src/utils/deploymentData/v1beta3.ts
- apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigurationPane/ConfidentialComputeCard/ConfidentialComputeCard.tsx
- apps/deploy-web/src/components/deployments/ConfigureDeployment/ConfigureDeploymentHeader/ConfigureDeploymentHeader.spec.tsx
- apps/deploy-web/src/utils/deploymentData/v1beta3.spec.ts
Why
On the new Configure your deployment page (flag
onboarding_redesign_v1), a free-trial user who enables Confidential Compute → CPU-GPU — or leaves a GPU card on the default empty "Any model" selection — requests a GPU that trials cannot use. The deployment is created and enters quoting, but no usable bid ever satisfiesallPlacementsHaveBids(the only TEE-capable GPU,nvidia/pro6000se, is on the trial blocklist), so the CTA stays on "Requesting…" forever with no explanation. The issue asks that trial users be told upfront, with an Add Funds path, instead of hitting an endless spinner.Fixes CON-660
What
Lock the unusable GPU options upfront and guard submission, mirroring the existing GPU/Presets trial locks from #3409:
v1beta3— new UI-only predicatesisTrialBlockedGpuSelection/hasTrialBlockedGpu. They treat an empty ("any") model as blocked when the vendor exposes any blocked model.ConfidentialComputeCard— locks theCPU-GPUradio (lock icon) with a warning + Unlock high-end GPUs CTA; keeps CPU-only TEE selectable (the backend never blocks it); defensively rejectscpu-gpuinsetTee.GpuCard— locks the "Any model" empty option; specific allowed models stay selectable.SearchableSelect— supports adisabledempty option.HardwareSection— routes the new predicate and passesisGpuBlocked/onUnlockto the confidential-compute card.ConfigureDeploymentHeader— a load-bearing submit guard: enabling the GPU card leaves the model at the empty default without ever opening the (locked) picker, so the presentational lock alone can't stop an empty-model submission. On a trial with a blocked GPU selection it appends a clear message to the request-quotes error snackbar and blocks the request.Intended behavior change (flag for review)
Locking "Any model" makes the front-end intentionally stricter than the backend. The backend leaves "any nvidia" to per-bid enforcement (
validateLeaseGpuModels), and the SDL policyapplyTrialGpuPolicydeliberately leaves "any" asnull— both are unchanged. "Any" can succeed if an allowed-model provider happens to bid, but it's an unreliable gamble: if none bids, the deployment spins with no explanation. Forcing a trial to pick a specific allowed model (which stays selectable) or add credits gives a deterministic outcome. This rationale is documented in code comments (isTrialBlockedGpuSelection) so the divergence reads as deliberate.Out of scope
402) is unchanged; this is a proactive front-end gate.Testing
deploy-websuite: 2646 passed. Lint clean; no new type errors.NEXT_PUBLIC_UNLEASH_ENABLE_ALL=true). Recommended before merge: verify CPU-GPU locks with CPU still selectable, "Any model" locks, Request quotes on the empty model is blocked with the trial message, an allowed model proceeds, and a funded wallet is unaffected.Summary by CodeRabbit