From 77ee1ec127c334b52f7f9f5995acc90fc780f7f3 Mon Sep 17 00:00:00 2001 From: Lukas Bernert Date: Thu, 17 Sep 2026 11:18:48 +0100 Subject: [PATCH] chore(studio): describe compute CPU by size tier (#50401) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What kind of change does this PR introduce? Copy/label update in Studio's compute surfaces. ## Description Compute CPU descriptions now branch on the compute size tier: - Sizes below Large read **"Shared compute"** (no core count) - Large and up read **"Dedicated · N vCPUs"** — the unit is always vCPU Changes: - New `lib/compute-labels.ts` helper (`isSharedComputeSize`, `getComputeCpuLabel`) with unit tests - Compute badge hover card, compute size picker, and project-creation selector use the new labels - `new-project.constants.ts` cpu strings updated accordingly - ">16XL" card: "Custom CPU" → "Custom compute"; upsell copy now says "64 vCPUs" - The synthetic Nano/Micro addon `meta` no longer has `cpu_cores`/`cpu_dedicated`; removed the now-unused cpu fields from the hardcoded instance specs - Project-creation sub-text: "Larger, dedicated compute available after creation" ## Tests - New unit tests for the label helper - Infrastructure settings page test now asserts the rendered labels Fixes PROD-663 Related #49998 #49996 ## Summary by CodeRabbit * **User Interface** * Updated compute-size labels to use “Shared compute” and vCPU terminology. * Clarified dedicated compute options and availability messaging. * Updated custom instance and upgrade labels, including “Custom compute” and “64 vCPUs.” * **Consistency** * Standardized compute labels across project creation, infrastructure settings, and compute details. * **Tests** * Added coverage verifying shared and dedicated compute classifications and displayed labels. --------- Co-authored-by: Claude Fable 5 --- .../DiskManagement/DiskManagement.utils.ts | 4 -- .../fields/ComputeSizeField.tsx | 14 ++----- .../ProjectCreation/ComputeSizeSelector.tsx | 5 +-- .../components/ui/ComputeBadgeWrapper.tsx | 7 ++-- .../data/projects/new-project.constants.ts | 36 ++++++++-------- apps/studio/lib/compute-labels.test.ts | 42 +++++++++++++++++++ apps/studio/lib/compute-labels.ts | 14 +++++++ apps/studio/lib/constants/infrastructure.ts | 6 --- apps/studio/tests/pages/new/[slug].test.tsx | 2 +- .../[ref]/settings/infrastructure.test.tsx | 5 +++ 10 files changed, 89 insertions(+), 46 deletions(-) create mode 100644 apps/studio/lib/compute-labels.test.ts create mode 100644 apps/studio/lib/compute-labels.ts diff --git a/apps/studio/components/interfaces/DiskManagement/DiskManagement.utils.ts b/apps/studio/components/interfaces/DiskManagement/DiskManagement.utils.ts index ef2cfe64285..f9ecdd27aae 100644 --- a/apps/studio/components/interfaces/DiskManagement/DiskManagement.utils.ts +++ b/apps/studio/components/interfaces/DiskManagement/DiskManagement.utils.ts @@ -248,8 +248,6 @@ export function getAvailableComputeOptions( price_interval: 'hourly', price_type: 'usage', meta: { - cpu_cores: INSTANCE_MICRO_SPECS.cpu_cores, - cpu_dedicated: INSTANCE_MICRO_SPECS.cpu_dedicated, memory_gb: INSTANCE_MICRO_SPECS.memory_gb, baseline_disk_io_mbs: INSTANCE_MICRO_SPECS.baseline_disk_io_mbs, max_disk_io_mbs: INSTANCE_MICRO_SPECS.max_disk_io_mbs, @@ -268,8 +266,6 @@ export function getAvailableComputeOptions( price_type: 'usage', // @ts-ignore API types it as Record meta: { - cpu_cores: INSTANCE_NANO_SPECS.cpu_cores, - cpu_dedicated: INSTANCE_NANO_SPECS.cpu_dedicated, memory_gb: INSTANCE_NANO_SPECS.memory_gb, baseline_disk_io_mbs: INSTANCE_NANO_SPECS.baseline_disk_io_mbs, max_disk_io_mbs: INSTANCE_NANO_SPECS.max_disk_io_mbs, diff --git a/apps/studio/components/interfaces/DiskManagement/fields/ComputeSizeField.tsx b/apps/studio/components/interfaces/DiskManagement/fields/ComputeSizeField.tsx index 01d3f17f8d4..f4fe245d481 100644 --- a/apps/studio/components/interfaces/DiskManagement/fields/ComputeSizeField.tsx +++ b/apps/studio/components/interfaces/DiskManagement/fields/ComputeSizeField.tsx @@ -34,6 +34,7 @@ import { useProjectAddonsQuery } from '@/data/subscriptions/project-addons-query import { useHighAvailability } from '@/hooks/misc/useHighAvailability' import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' +import { getComputeCpuLabel } from '@/lib/compute-labels' const SKELETON_PLACEHOLDER_COUNT = 6 @@ -175,16 +176,7 @@ export function ComputeSizeField({ form, disabled }: ComputeSizeFieldProps) { )?.price : compute.price - const cpuLabel = (() => { - const cpuCores = compute.meta?.cpu_cores - if (typeof cpuCores === 'number') { - return `${cpuCores}-core CPU` - } - if (cpuCores) { - return `${cpuCores} CPU` - } - return 'CPU' - })() + const cpuLabel = getComputeCpuLabel(compute.identifier, compute.meta?.cpu_cores) return ( - Custom CPU + Custom compute diff --git a/apps/studio/components/interfaces/ProjectCreation/ComputeSizeSelector.tsx b/apps/studio/components/interfaces/ProjectCreation/ComputeSizeSelector.tsx index dde2b366e26..229a9a88e77 100644 --- a/apps/studio/components/interfaces/ProjectCreation/ComputeSizeSelector.tsx +++ b/apps/studio/components/interfaces/ProjectCreation/ComputeSizeSelector.tsx @@ -86,8 +86,7 @@ export const ComputeSizeSelector = ({ form }: ComputeSizeSelectorProps) => {
- {instanceSizeSpecs[option].ram} RAM / {instanceSizeSpecs[option].cpu}{' '} - CPU + {instanceSizeSpecs[option].ram} RAM / {instanceSizeSpecs[option].cpu}

{ {!highAvailability && (

- Larger instance sizes available after creation + Larger, dedicated compute available after creation
)} diff --git a/apps/studio/components/ui/ComputeBadgeWrapper.tsx b/apps/studio/components/ui/ComputeBadgeWrapper.tsx index ebbcf77fc0b..016a62ea1d3 100644 --- a/apps/studio/components/ui/ComputeBadgeWrapper.tsx +++ b/apps/studio/components/ui/ComputeBadgeWrapper.tsx @@ -10,6 +10,7 @@ import { ProjectDetail } from '@/data/projects/project-detail-query' import { useOrgSubscriptionQuery } from '@/data/subscriptions/org-subscription-query' import { useProjectAddonsQuery } from '@/data/subscriptions/project-addons-query' import { ResourceWarning } from '@/data/usage/resource-warnings-query' +import { getComputeCpuLabel } from '@/lib/compute-labels' import { useTrack } from '@/lib/telemetry/track' export const ChevronsUpAnimated = () => ( @@ -149,14 +150,14 @@ export const ComputeBadgeWrapper = ({
{computeSize === 'nano' ? ( <> - + ) : meta !== undefined ? ( <> @@ -179,7 +180,7 @@ export const ComputeBadgeWrapper = ({

{isEligibleForFreeUpgrade ? 'Paid plans include a free upgrade to Micro compute.' - : 'Scale your project up to 64 cores and 256 GB RAM.'} + : 'Scale your project up to 64 vCPUs and 256 GB RAM.'}

diff --git a/apps/studio/data/projects/new-project.constants.ts b/apps/studio/data/projects/new-project.constants.ts index ca08d47059a..b4f36fc32d9 100644 --- a/apps/studio/data/projects/new-project.constants.ts +++ b/apps/studio/data/projects/new-project.constants.ts @@ -29,7 +29,7 @@ export const instanceSizeSpecs: Record< micro: { label: 'Micro', ram: '1 GB', - cpu: '2-core', + cpu: 'Shared compute', priceHourly: 0.01344, priceMonthly: 10, cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.AWS_K8S.id, PROVIDERS.AWS_NIMBUS.id], @@ -37,7 +37,7 @@ export const instanceSizeSpecs: Record< small: { label: 'Small', ram: '2 GB', - cpu: '2-core', + cpu: 'Shared compute', priceHourly: 0.0206, priceMonthly: 15, cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.AWS_K8S.id, PROVIDERS.AWS_NIMBUS.id], @@ -45,7 +45,7 @@ export const instanceSizeSpecs: Record< medium: { label: 'Medium', ram: '4 GB', - cpu: '2-core', + cpu: 'Shared compute', priceHourly: 0.0822, priceMonthly: 60, cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.AWS_K8S.id, PROVIDERS.AWS_NIMBUS.id], @@ -53,7 +53,7 @@ export const instanceSizeSpecs: Record< large: { label: 'Large', ram: '8 GB', - cpu: '2-core', + cpu: '2 vCPUs', priceHourly: 0.1517, priceMonthly: 110, cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.AWS_K8S.id, PROVIDERS.AWS_NIMBUS.id], @@ -61,7 +61,7 @@ export const instanceSizeSpecs: Record< xlarge: { label: 'XL', ram: '16 GB', - cpu: '4-core', + cpu: '4 vCPUs', priceHourly: 0.2877, priceMonthly: 210, cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.AWS_K8S.id, PROVIDERS.AWS_NIMBUS.id], @@ -69,7 +69,7 @@ export const instanceSizeSpecs: Record< '2xlarge': { label: '2XL', ram: '32 GB', - cpu: '8-core', + cpu: '8 vCPUs', priceHourly: 0.562, priceMonthly: 410, cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.AWS_K8S.id, PROVIDERS.AWS_NIMBUS.id], @@ -77,7 +77,7 @@ export const instanceSizeSpecs: Record< '4xlarge': { label: '4XL', ram: '64 GB', - cpu: '16-core', + cpu: '16 vCPUs', priceHourly: 1.32, priceMonthly: 960, cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.AWS_K8S.id, PROVIDERS.AWS_NIMBUS.id], @@ -85,7 +85,7 @@ export const instanceSizeSpecs: Record< '8xlarge': { label: '8XL', ram: '128 GB', - cpu: '32-core', + cpu: '32 vCPUs', priceHourly: 2.562, priceMonthly: 1870, cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.AWS_K8S.id, PROVIDERS.AWS_NIMBUS.id], @@ -93,7 +93,7 @@ export const instanceSizeSpecs: Record< '12xlarge': { label: '12XL', ram: '192 GB', - cpu: '48-core', + cpu: '48 vCPUs', priceHourly: 3.836, priceMonthly: 2800, cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.AWS_K8S.id, PROVIDERS.AWS_NIMBUS.id], @@ -101,7 +101,7 @@ export const instanceSizeSpecs: Record< '16xlarge': { label: '16XL', ram: '256 GB', - cpu: '64-core', + cpu: '64 vCPUs', priceHourly: 5.12, priceMonthly: 3730, cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.AWS_K8S.id, PROVIDERS.AWS_NIMBUS.id], @@ -109,7 +109,7 @@ export const instanceSizeSpecs: Record< '24xlarge': { label: '24XL', ram: '384 GB', - cpu: '96-core', + cpu: '96 vCPUs', priceHourly: 9.73, priceMonthly: 7100, cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.AWS_K8S.id, PROVIDERS.AWS_NIMBUS.id], @@ -117,7 +117,7 @@ export const instanceSizeSpecs: Record< '24xlarge_optimized_cpu': { label: '24XL - Optimized CPU', ram: '192 GB', - cpu: '96-core', + cpu: '96 vCPUs', priceHourly: 8.9, priceMonthly: 6500, cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.AWS_K8S.id, PROVIDERS.AWS_NIMBUS.id], @@ -125,7 +125,7 @@ export const instanceSizeSpecs: Record< '24xlarge_optimized_memory': { label: '24XL - Optimized Memory', ram: '768 GB', - cpu: '96-core', + cpu: '96 vCPUs', priceHourly: 13.84, priceMonthly: 10100, cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.AWS_K8S.id, PROVIDERS.AWS_NIMBUS.id], @@ -133,7 +133,7 @@ export const instanceSizeSpecs: Record< '24xlarge_high_memory': { label: '24XL - High Memory', ram: '1536 GB', - cpu: '96-core', + cpu: '96 vCPUs', priceHourly: 21.91, priceMonthly: 16000, cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.AWS_K8S.id, PROVIDERS.AWS_NIMBUS.id], @@ -141,7 +141,7 @@ export const instanceSizeSpecs: Record< '48xlarge': { label: '48XL', ram: '768 GB', - cpu: '192-core', + cpu: '192 vCPUs', priceHourly: 19.47, priceMonthly: 14200, cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.AWS_K8S.id, PROVIDERS.AWS_NIMBUS.id], @@ -149,7 +149,7 @@ export const instanceSizeSpecs: Record< '48xlarge_optimized_cpu': { label: '48XL - Optimized CPU', ram: '384 GB', - cpu: '192-core', + cpu: '192 vCPUs', priceHourly: 17.8, priceMonthly: 13000, cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.AWS_K8S.id, PROVIDERS.AWS_NIMBUS.id], @@ -157,7 +157,7 @@ export const instanceSizeSpecs: Record< '48xlarge_optimized_memory': { label: '48XL - Optimized Memory', ram: '1536 GB', - cpu: '192-core', + cpu: '192 vCPUs', priceHourly: 27.68, priceMonthly: 20200, cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.AWS_K8S.id, PROVIDERS.AWS_NIMBUS.id], @@ -165,7 +165,7 @@ export const instanceSizeSpecs: Record< '48xlarge_high_memory': { label: '48XL - High Memory', ram: '3072 GB', - cpu: '192-core', + cpu: '192 vCPUs', priceHourly: 43.84, priceMonthly: 32000, cloud_providers: [PROVIDERS.AWS.id, PROVIDERS.AWS_K8S.id, PROVIDERS.AWS_NIMBUS.id], diff --git a/apps/studio/lib/compute-labels.test.ts b/apps/studio/lib/compute-labels.test.ts new file mode 100644 index 00000000000..40738a4143e --- /dev/null +++ b/apps/studio/lib/compute-labels.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, it } from 'vitest' + +import { getComputeCpuLabel, isSharedComputeSize } from './compute-labels' + +describe('isSharedComputeSize', () => { + it('treats sizes below Large as shared', () => { + expect(isSharedComputeSize('pico')).toBe(true) + expect(isSharedComputeSize('nano')).toBe(true) + expect(isSharedComputeSize('micro')).toBe(true) + expect(isSharedComputeSize('small')).toBe(true) + expect(isSharedComputeSize('medium')).toBe(true) + }) + + it('treats Large and up as dedicated', () => { + expect(isSharedComputeSize('large')).toBe(false) + expect(isSharedComputeSize('xlarge')).toBe(false) + expect(isSharedComputeSize('16xlarge')).toBe(false) + expect(isSharedComputeSize('48xlarge_high_memory')).toBe(false) + }) + + it('accepts addon variant identifiers and mixed case', () => { + expect(isSharedComputeSize('ci_micro')).toBe(true) + expect(isSharedComputeSize('ci_large')).toBe(false) + expect(isSharedComputeSize('Medium')).toBe(true) + }) +}) + +describe('getComputeCpuLabel', () => { + it('labels shared sizes without a count', () => { + expect(getComputeCpuLabel('micro', 2)).toBe('Shared compute') + expect(getComputeCpuLabel('medium')).toBe('Shared compute') + }) + + it('labels dedicated sizes with a vCPU count', () => { + expect(getComputeCpuLabel('large', 2)).toBe('Dedicated · 2 vCPUs') + expect(getComputeCpuLabel('16xlarge', 64)).toBe('Dedicated · 64 vCPUs') + }) + + it('falls back to a generic dedicated label without a count', () => { + expect(getComputeCpuLabel('large')).toBe('Dedicated compute') + }) +}) diff --git a/apps/studio/lib/compute-labels.ts b/apps/studio/lib/compute-labels.ts new file mode 100644 index 00000000000..559a1da3ff6 --- /dev/null +++ b/apps/studio/lib/compute-labels.ts @@ -0,0 +1,14 @@ +/** Compute sizes below Large run on shared compute; Large and up are dedicated. */ +const SHARED_COMPUTE_SIZES = ['pico', 'nano', 'micro', 'small', 'medium'] + +export const isSharedComputeSize = (size: string) => + SHARED_COMPUTE_SIZES.includes(size.toLowerCase().replace(/^ci_/, '')) + +/** + * User-facing CPU description for a compute size. Branches on the size itself, + * not on `meta.cpu_cores` — the platform is dropping that field. + */ +export const getComputeCpuLabel = (size: string, vcpuCount?: number) => { + if (isSharedComputeSize(size)) return 'Shared compute' + return typeof vcpuCount === 'number' ? `Dedicated · ${vcpuCount} vCPUs` : 'Dedicated compute' +} diff --git a/apps/studio/lib/constants/infrastructure.ts b/apps/studio/lib/constants/infrastructure.ts index 0cccaa7b513..3504d4f5545 100644 --- a/apps/studio/lib/constants/infrastructure.ts +++ b/apps/studio/lib/constants/infrastructure.ts @@ -121,8 +121,6 @@ export type InstanceSpecs = { baseline_disk_io_mbs: number connections_direct: number connections_pooler: number - cpu_cores: number | 'Shared' - cpu_dedicated: boolean max_disk_io_mbs: number memory_gb: number } @@ -131,8 +129,6 @@ export const INSTANCE_NANO_SPECS: InstanceSpecs = { baseline_disk_io_mbs: 43, connections_direct: 30, connections_pooler: 200, - cpu_cores: 'Shared', - cpu_dedicated: false, max_disk_io_mbs: 2085, memory_gb: 0.5, } @@ -141,8 +137,6 @@ export const INSTANCE_MICRO_SPECS: InstanceSpecs = { baseline_disk_io_mbs: 87, connections_direct: 60, connections_pooler: 200, - cpu_cores: 2, - cpu_dedicated: false, max_disk_io_mbs: 2085, memory_gb: 1, } diff --git a/apps/studio/tests/pages/new/[slug].test.tsx b/apps/studio/tests/pages/new/[slug].test.tsx index 7db99dfd03b..a70ea65ab9d 100644 --- a/apps/studio/tests/pages/new/[slug].test.tsx +++ b/apps/studio/tests/pages/new/[slug].test.tsx @@ -493,7 +493,7 @@ describe('project creation wizard', () => { await generateAndWaitForStrongPassword() await user.click(getSelectTriggerByLabel('Compute size')) - await user.click(await screen.findByText('4 GB RAM / 2-core CPU')) + await user.click(await screen.findByText('4 GB RAM / Shared compute')) fireEvent.click(screen.getByRole('button', { name: 'Create new project' })) diff --git a/apps/studio/tests/pages/project/[ref]/settings/infrastructure.test.tsx b/apps/studio/tests/pages/project/[ref]/settings/infrastructure.test.tsx index 0a818736a0a..5126980fdec 100644 --- a/apps/studio/tests/pages/project/[ref]/settings/infrastructure.test.tsx +++ b/apps/studio/tests/pages/project/[ref]/settings/infrastructure.test.tsx @@ -329,6 +329,11 @@ describe('/project/[ref]/settings/infrastructure', () => { expect(screen.getByTestId('metric-Memory')).toHaveTextContent('50%') expect(screen.getByTestId('metric-Disk')).toHaveTextContent('50%') + // Compute options describe CPU by size: shared below Large, dedicated with vCPUs from Large up + expect(screen.getAllByText('Shared compute')).toHaveLength(3) // Nano, Micro, Small + expect(screen.getByText('Dedicated · 2 vCPUs')).toBeInTheDocument() // Large + expect(screen.queryByText(/-core/)).not.toBeInTheDocument() + await user.click(screen.getByText('Small')) expect(screen.getByRole('button', { name: 'Review changes' })).toBeEnabled()