Files
supabase/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceConfiguration.constants.ts
Charis 3aec96f9c6 fix(studio): use measured node heights for instance diagram layout (#46075)
## Summary

The homepage instance diagram looks visually off when a project has read
replicas — see
[FE-3390](https://linear.app/supabase/issue/FE-3390/adjust-homepage-diagram-spacing-for-read-replicas).

Root cause: the dagre layout in `InstanceConfiguration.utils.ts` was
passing a fake `height: 25` (and a `-70` hack for the load balancer) for
every node, regardless of how tall the rendered node actually is. When
the Primary card grew taller after #45274 added the compute-metrics row,
the gap between the Primary node and the Read Replica node became
visually tight while the gap between the Load Balancer and Primary
stayed loose — the diagram looked lopsided.

## Fix

- Pass the **real measured height** of each node to dagre, sourced from
React Flow's internal measurement (`node.measured.height`).
- Run the layout in two passes:
1. First pass uses small per-type fallback heights so React Flow has
something to render and measure against.
2. Once `useNodesInitialized()` flips true, re-run dagre with the
measured heights and fitView.
- Keep the diagram at `opacity-0` until the measured pass completes, so
the fallback positions are never visible to the user. Subsequent data
refetches don't re-hide the diagram — once measured, it stays visible.
- Drop `NODE_ROW_HEIGHT` and the load-balancer `-70` hack. Replaced with
a small `NODE_HEIGHT_FALLBACKS` table used only on first paint.
- `ranksep` reduced from 160 → 60 since dagre now knows real heights.

Closes FE-3390.

## Test plan

- [x] Open the project homepage for a project with no read replicas —
diagram renders centered, single Primary node.
- [x] Open the project homepage for a project **with** a read replica —
Load Balancer → Primary → Replica spacing is visually balanced; no flash
of fallback positions on initial render; the diagram fades in once.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Improvements**
* More accurate infrastructure diagram layout with better node sizing
and spacing for initial and measured renders.
* Introduced fallback heights so diagrams render correctly on first
paint before measurements are available.
* Region containers now use a consistent fixed height for stable layout.
* Smoother, visually consistent diagram initialization with improved
opacity transition during layout setup.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46075?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-19 16:13:26 -04:00

127 lines
4.2 KiB
TypeScript

import { ReadReplicaSetupError, ReadReplicaSetupProgress } from '@supabase/shared-types/out/events'
import type { AWS_REGIONS_KEYS } from 'shared-data'
import { AWS_REGIONS } from 'shared-data'
import { components } from '@/data/api'
import { PROJECT_STATUS } from '@/lib/constants'
export interface Region {
key: AWS_REGIONS_KEYS
name: string
region: string
coordinates: [number, number]
}
export type NodeData = {
id: string
provider: string
region: Region
computeSize?: string
status: string
inserted_at: string
}
export type PrimaryNodeData = NodeData & {
numReplicas: number
numRegions: number
hasLoadBalancer: boolean
}
export type LoadBalancerData = NodeData & {
numDatabases: number
}
export type ReplicaNodeData = NodeData & {
onSelectRestartReplica: () => void
onSelectResizeReplica: () => void
onSelectDropReplica: () => void
}
export type EdgeData = {
status: string
identifier: string
connectionString: string | null | undefined
}
// ReactFlow is scaling everything by the factor of 2
export const NODE_WIDTH = 660
export const NODE_SEP = 20
// The region wrapper is a static, non-measured sibling node with a fixed size.
export const REGION_NODE_HEIGHT = 162
// First-paint fallback heights for the dagre layout, only used before React
// Flow has measured the real nodes. Subsequent layouts use node.measured.height.
export const NODE_HEIGHT_FALLBACKS: Record<string, number> = {
LOAD_BALANCER: 64,
PRIMARY: 140,
READ_REPLICA: 140,
REGION: REGION_NODE_HEIGHT,
}
export const REPLICA_STATUS: {
[key: string]: components['schemas']['DatabaseStatusResponse']['status']
} = {
...PROJECT_STATUS,
INIT_READ_REPLICA: 'INIT_READ_REPLICA',
INIT_READ_REPLICA_FAILED: 'INIT_READ_REPLICA_FAILED',
}
// [Joshen] Coordinates from https://github.com/tobilg/aws-edge-locations/blob/main/data/aws-edge-locations.json
// In the format of [lon, lat]
export const AWS_REGIONS_COORDINATES: { [key: string]: [number, number] } = {
SOUTHEAST_ASIA: [103.8, 1.37],
NORTHEAST_ASIA: [139.42, 35.41],
NORTHEAST_ASIA_2: [126.98, 37.56],
CENTRAL_CANADA: [-73.6, 45.5],
WEST_US: [-121.96, 37.35],
WEST_US_2: [-122.67, 45.51],
EAST_US: [-78.45, 38.13],
WEST_EU: [-8, 53],
WEST_EU_2: [-0.1, 51],
CENTRAL_EU: [8, 50],
SOUTH_ASIA: [72.88, 19.08],
OCEANIA: [151.2, -33.86],
SOUTH_AMERICA: [-46.38, -23.34],
CENTRAL_EU_2: [8.54, 47.45],
EAST_US_2: [-83, 39.96],
NORTH_EU: [17.91, 59.65],
WEST_EU_3: [2.35, 48.86],
}
export const FLY_REGIONS_COORDINATES: { [key: string]: [number, number] } = {
SOUTHEAST_ASIA: [103.8, 1.37],
}
// [Joshen] Just to make sure that we just depend on AWS_REGIONS to determine available
// regions for replicas. Just FYI - might need to update this if we support Fly in future
export const AVAILABLE_REPLICA_REGIONS: Region[] = Object.keys(AWS_REGIONS)
.map((key) => {
return {
key: key as AWS_REGIONS_KEYS,
name: AWS_REGIONS?.[key as AWS_REGIONS_KEYS].displayName,
region: AWS_REGIONS?.[key as AWS_REGIONS_KEYS].code,
coordinates: AWS_REGIONS_COORDINATES[key],
}
})
.filter((x) => x.coordinates !== undefined)
// [Joshen] Just a more user friendly language, so that all the verbs are progressive
export const INIT_PROGRESS = {
[ReadReplicaSetupProgress.Requested]: 'Requesting replica instance',
[ReadReplicaSetupProgress.Started]: 'Launching replica instance',
[ReadReplicaSetupProgress.LaunchedReadReplicaInstance]: 'Initiating replica setup',
[ReadReplicaSetupProgress.InitiatedReadReplicaSetup]: 'Downloading base backup',
[ReadReplicaSetupProgress.DownloadedBaseBackup]: 'Replaying WAL archives',
[ReadReplicaSetupProgress.ReplayedWalArchives]: 'Completing set up',
[ReadReplicaSetupProgress.CompletedReadReplicaSetup]: 'Completed',
}
export const ERROR_STATES = {
[ReadReplicaSetupError.ReadReplicaInstanceLaunchFailed]: 'Failed to launch replica',
[ReadReplicaSetupError.InitiateReadReplicaSetupFailed]: 'Failed to initiate replica',
[ReadReplicaSetupError.DownloadBaseBackupFailed]: 'Failed to download backup',
[ReadReplicaSetupError.ReplayWalArchivesFailed]: 'Failed to replay WAL archives',
[ReadReplicaSetupError.CompleteReadReplicaSetupFailed]: 'Failed to set up replica',
}