mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 18:05:11 +03:00
## Summary
The `homeNew` PostHog experiment has concluded. This PR graduates it by
making the new homepage (`ProjectHome`, formerly `HomeV2`) the permanent
default for all users, and removes all dead code from the old
experiment.
## Changes
- Remove `homeNew` PostHog feature flag checks and `home_new` experiment
exposure tracking from 3 files
- Rename `HomeNew/` → `ProjectHome/` directory and `HomeV2` →
`ProjectHome` export
- Delete old `Home/Home.tsx` component (shared components like
`ProjectList/` are kept — still used by org pages)
- Delete `pages/project/[ref]/building.tsx` and add a server-side
redirect from `/project/:ref/building` → `/project/:ref` to prevent 404s
during rollout (old cached JS bundles may still route to `/building`)
- Simplify `ContentWrapper` building-state logic in `ProjectLayout` —
always redirect building projects to home, always suppress building
interstitial on home page
- Always route to `/project/{ref}` after project creation (remove
`/building` path)
- Update all Observability imports from `HomeNew` → `ProjectHome`
## Self-hosted behavior change
Self-hosted Studio previously showed the old `Home` component (client
libraries + example projects) since PostHog flags don't load. This PR
changes self-hosted to show `ProjectHome` (TopSection with service
status + instance diagram, advisor, custom reports). All sections query
backend APIs that exist on self-hosted. E2E tests pass against the
self-hosted build.
## Testing
- [x] `pnpm turbo run build --filter=studio` passes
- [x] No remaining references to `homeNew`, `home_new`, or `HomeNew` in
codebase
- [x] No broken imports to deleted files
- [x] Self-hosted E2E tests pass (145 passed, 1 flaky, 4 skipped)
- [x] `/building` redirect added to both platform and self-hosted config
blocks
**Quick test:**
1. Navigate to any project homepage — should render the ProjectHome
component
2. Create a new project — should redirect to `/project/{ref}` (not
`/building`)
3. Visit a project in `COMING_UP` state on a non-home route — should
redirect to home
4. Visit `/project/{ref}/building` directly — should 302 redirect to
`/project/{ref}`
## Linear
- fixes GROWTH-671
219 lines
6.6 KiB
TypeScript
219 lines
6.6 KiB
TypeScript
import { ChevronRight, HelpCircle } from 'lucide-react'
|
|
import Link from 'next/link'
|
|
import { Badge, Card, CardContent, Loading, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
|
|
import { LogsBarChart } from 'ui-patterns/LogsBarChart'
|
|
|
|
import { ButtonTooltip } from '../../ui/ButtonTooltip'
|
|
import type { LogsBarChartDatum } from '../ProjectHome/ProjectUsage.metrics'
|
|
import { getHealthStatus, type ServiceKey } from './ObservabilityOverview.utils'
|
|
|
|
type ServiceConfig = {
|
|
key: ServiceKey
|
|
name: string
|
|
description: string
|
|
reportUrl?: string
|
|
logsUrl: string
|
|
}
|
|
|
|
type ServiceData = {
|
|
total: number
|
|
errorRate: number
|
|
errorCount: number
|
|
warningCount: number
|
|
eventChartData: LogsBarChartDatum[]
|
|
isLoading: boolean
|
|
}
|
|
|
|
type IntervalKey = '1hr' | '1day' | '7day'
|
|
|
|
export type ServiceHealthTableProps = {
|
|
services: ServiceConfig[]
|
|
serviceData: Record<string, ServiceData>
|
|
onBarClick: (serviceKey: string, logsUrl: string) => (datum: LogsBarChartDatum) => void
|
|
interval: IntervalKey
|
|
datetimeFormat: string
|
|
}
|
|
|
|
const SERVICE_DESCRIPTIONS: Record<ServiceKey, string> = {
|
|
db: 'PostgreSQL database health and performance',
|
|
auth: 'Authentication and user management',
|
|
functions: 'Serverless Edge Functions execution',
|
|
storage: 'Object storage for files and assets',
|
|
realtime: 'WebSocket connections and broadcasts',
|
|
postgrest: 'Auto-generated REST API for your database',
|
|
}
|
|
|
|
const getStatusLabel = (status: 'healthy' | 'error' | 'unknown'): string => {
|
|
switch (status) {
|
|
case 'healthy':
|
|
return 'Healthy'
|
|
case 'error':
|
|
return 'Unhealthy'
|
|
case 'unknown':
|
|
return 'Unknown'
|
|
}
|
|
}
|
|
|
|
const getStatusTooltip = (status: 'healthy' | 'error' | 'unknown'): string => {
|
|
switch (status) {
|
|
case 'healthy':
|
|
return 'Error rate is below 1%'
|
|
case 'error':
|
|
return 'Error rate is 1% or higher'
|
|
case 'unknown':
|
|
return 'Insufficient data (fewer than 100 requests)'
|
|
}
|
|
}
|
|
|
|
const getStatusVariant = (
|
|
status: 'healthy' | 'error' | 'unknown'
|
|
): 'success' | 'warning' | 'destructive' | 'default' => {
|
|
switch (status) {
|
|
case 'healthy':
|
|
return 'success'
|
|
case 'error':
|
|
return 'destructive'
|
|
case 'unknown':
|
|
return 'default'
|
|
}
|
|
}
|
|
|
|
type ServiceRowProps = {
|
|
service: ServiceConfig
|
|
data: ServiceData
|
|
onBarClick: (datum: LogsBarChartDatum) => void
|
|
datetimeFormat: string
|
|
}
|
|
|
|
const ServiceRow = ({ service, data, onBarClick, datetimeFormat }: ServiceRowProps) => {
|
|
const { status } = getHealthStatus(data.errorRate, data.total)
|
|
const statusLabel = getStatusLabel(status)
|
|
const statusVariant = getStatusVariant(status)
|
|
const statusTooltip = getStatusTooltip(status)
|
|
|
|
const errorRate = data.total > 0 ? data.errorRate : 0
|
|
const warningRate = data.total > 0 ? (data.warningCount / data.total) * 100 : 0
|
|
|
|
const reportUrl = service.reportUrl || service.logsUrl
|
|
|
|
return (
|
|
<Link
|
|
href={reportUrl}
|
|
className="block group py-4 px-card border-b border-default last:border-b-0 hover:bg-surface-200 transition-colors cursor-pointer"
|
|
>
|
|
<div className="flex items-center justify-between mb-3">
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-foreground font-medium">{service.name}</span>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<button
|
|
onClick={(e) => e.preventDefault()}
|
|
className="text-foreground-lighter hover:text-foreground-light transition-colors"
|
|
>
|
|
<HelpCircle size={14} />
|
|
</button>
|
|
</TooltipTrigger>
|
|
<TooltipContent side="right" className="max-w-xs">
|
|
<p>{SERVICE_DESCRIPTIONS[service.key as ServiceKey] || service.description}</p>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<Badge variant={statusVariant}>{statusLabel}</Badge>
|
|
</TooltipTrigger>
|
|
<TooltipContent side="top">
|
|
<p>{statusTooltip}</p>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
<ButtonTooltip
|
|
type="text"
|
|
size="tiny"
|
|
className="p-1.5"
|
|
tooltip={{ content: { text: `Go to ${service.name} report` } }}
|
|
>
|
|
<ChevronRight
|
|
size={14}
|
|
className="text-foreground-lighter group-hover:text-foreground"
|
|
/>
|
|
</ButtonTooltip>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="h-16" onClick={(e) => e.preventDefault()}>
|
|
<Loading active={data.isLoading}>
|
|
{data.isLoading ? (
|
|
<div />
|
|
) : (
|
|
<LogsBarChart
|
|
data={data.eventChartData}
|
|
DateTimeFormat={datetimeFormat}
|
|
onBarClick={onBarClick}
|
|
EmptyState={
|
|
<div className="flex items-center justify-center h-full text-xs text-foreground-lighter">
|
|
No data
|
|
</div>
|
|
}
|
|
/>
|
|
)}
|
|
</Loading>
|
|
</div>
|
|
|
|
{data.total > 0 && (
|
|
<div className="flex items-center justify-center mt-2 text-xs text-foreground-lighter gap-4 font-mono tabular-nums tracking-tight">
|
|
{errorRate > 0 && (
|
|
<span className="flex items-center gap-1.5">
|
|
<div className="w-1.5 h-1.5 rounded-full bg-destructive" />
|
|
{errorRate.toFixed(2)}% errors
|
|
</span>
|
|
)}
|
|
{warningRate > 0 && (
|
|
<span className="flex items-center gap-1.5">
|
|
<div className="w-1.5 h-1.5 rounded-full bg-warning" />
|
|
{warningRate.toFixed(2)}% warnings
|
|
</span>
|
|
)}
|
|
{errorRate === 0 && warningRate === 0 && (
|
|
<span className="flex items-center gap-1.5">
|
|
<div className="w-1.5 h-1.5 rounded-full bg-brand" />
|
|
0% errors
|
|
</span>
|
|
)}
|
|
</div>
|
|
)}
|
|
</Link>
|
|
)
|
|
}
|
|
|
|
export const ServiceHealthTable = ({
|
|
services,
|
|
serviceData,
|
|
onBarClick,
|
|
datetimeFormat,
|
|
}: ServiceHealthTableProps) => {
|
|
return (
|
|
<div>
|
|
<h2 className="heading-section mb-4">Service Health</h2>
|
|
<Card>
|
|
<CardContent className="p-0">
|
|
{services.map((service) => {
|
|
const data = serviceData[service.key]
|
|
if (!data) return null
|
|
|
|
return (
|
|
<ServiceRow
|
|
key={service.key}
|
|
service={service}
|
|
data={data}
|
|
onBarClick={onBarClick(service.key, service.logsUrl)}
|
|
datetimeFormat={datetimeFormat}
|
|
/>
|
|
)
|
|
})}
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
)
|
|
}
|