mirror of
https://github.com/supabase/supabase.git
synced 2026-05-08 01:40:13 -04:00
01c178e159
## 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
222 lines
7.0 KiB
TypeScript
222 lines
7.0 KiB
TypeScript
import dayjs from 'dayjs'
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
import { normalizeChartBuckets } from './ChartDataTransform.utils'
|
|
import type { LogsBarChartDatum } from './ProjectUsage.metrics'
|
|
|
|
describe('normalizeChartBuckets', () => {
|
|
const now = dayjs('2024-01-28T12:00:00.000Z')
|
|
|
|
describe('1hr interval', () => {
|
|
it('should create exactly 30 buckets with 2-minute intervals', () => {
|
|
const result = normalizeChartBuckets([], '1hr', now.toDate())
|
|
|
|
expect(result).toHaveLength(30)
|
|
|
|
// Check first bucket
|
|
expect(result[0].timestamp).toBe(now.subtract(60, 'minute').toISOString())
|
|
|
|
// Check last bucket
|
|
expect(result[29].timestamp).toBe(now.subtract(2, 'minute').toISOString())
|
|
|
|
// Check all buckets are 2 minutes apart
|
|
for (let i = 0; i < result.length - 1; i++) {
|
|
const diff = dayjs(result[i + 1].timestamp).diff(dayjs(result[i].timestamp), 'minute')
|
|
expect(diff).toBe(2)
|
|
}
|
|
})
|
|
|
|
it('should aggregate data points into correct 2-minute buckets', () => {
|
|
const data: LogsBarChartDatum[] = [
|
|
{
|
|
// First bucket starts at -60 minutes, so -60 to -59 minutes is in bucket 0
|
|
timestamp: now.subtract(60, 'minute').toISOString(),
|
|
ok_count: 10,
|
|
warning_count: 1,
|
|
error_count: 2,
|
|
},
|
|
{
|
|
timestamp: now.subtract(59, 'minute').add(30, 'second').toISOString(),
|
|
ok_count: 5,
|
|
warning_count: 0,
|
|
error_count: 1,
|
|
},
|
|
{
|
|
timestamp: now.subtract(30, 'minute').toISOString(),
|
|
ok_count: 20,
|
|
warning_count: 2,
|
|
error_count: 0,
|
|
},
|
|
]
|
|
|
|
const result = normalizeChartBuckets(data, '1hr', now.toDate())
|
|
|
|
// First bucket (60-58 minutes ago) should contain aggregated data
|
|
const firstBucket = result[0]
|
|
expect(firstBucket.ok_count).toBe(15) // 10 + 5
|
|
expect(firstBucket.warning_count).toBe(1) // 1 + 0
|
|
expect(firstBucket.error_count).toBe(3) // 2 + 1
|
|
|
|
// Bucket at 30 minutes ago
|
|
const bucket15 = result[15] // 30 minutes / 2 minutes per bucket = bucket 15
|
|
expect(bucket15.ok_count).toBe(20)
|
|
expect(bucket15.warning_count).toBe(2)
|
|
expect(bucket15.error_count).toBe(0)
|
|
})
|
|
|
|
it('should return empty buckets when no data provided', () => {
|
|
const result = normalizeChartBuckets([], '1hr', now.toDate())
|
|
|
|
expect(result).toHaveLength(30)
|
|
result.forEach((bucket) => {
|
|
expect(bucket.ok_count).toBe(0)
|
|
expect(bucket.warning_count).toBe(0)
|
|
expect(bucket.error_count).toBe(0)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('1day interval', () => {
|
|
it('should create exactly 24 buckets with 1-hour intervals', () => {
|
|
const result = normalizeChartBuckets([], '1day', now.toDate())
|
|
|
|
expect(result).toHaveLength(24)
|
|
|
|
// Check first bucket
|
|
expect(result[0].timestamp).toBe(now.subtract(24, 'hour').toISOString())
|
|
|
|
// Check last bucket
|
|
expect(result[23].timestamp).toBe(now.subtract(1, 'hour').toISOString())
|
|
|
|
// Check all buckets are 1 hour apart
|
|
for (let i = 0; i < result.length - 1; i++) {
|
|
const diff = dayjs(result[i + 1].timestamp).diff(dayjs(result[i].timestamp), 'hour')
|
|
expect(diff).toBe(1)
|
|
}
|
|
})
|
|
|
|
it('should aggregate multiple data points into hourly buckets', () => {
|
|
const data: LogsBarChartDatum[] = [
|
|
{
|
|
timestamp: now.subtract(23, 'hour').subtract(30, 'minute').toISOString(),
|
|
ok_count: 100,
|
|
warning_count: 5,
|
|
error_count: 3,
|
|
},
|
|
{
|
|
timestamp: now.subtract(23, 'hour').subtract(15, 'minute').toISOString(),
|
|
ok_count: 50,
|
|
warning_count: 2,
|
|
error_count: 1,
|
|
},
|
|
]
|
|
|
|
const result = normalizeChartBuckets(data, '1day', now.toDate())
|
|
|
|
// First bucket should contain aggregated data
|
|
expect(result[0].ok_count).toBe(150)
|
|
expect(result[0].warning_count).toBe(7)
|
|
expect(result[0].error_count).toBe(4)
|
|
})
|
|
})
|
|
|
|
describe('7day interval', () => {
|
|
it('should create exactly 28 buckets with 6-hour intervals', () => {
|
|
const result = normalizeChartBuckets([], '7day', now.toDate())
|
|
|
|
expect(result).toHaveLength(28)
|
|
|
|
// Check first bucket (7 days = 168 hours ago)
|
|
expect(result[0].timestamp).toBe(now.subtract(168, 'hour').toISOString())
|
|
|
|
// Check last bucket
|
|
expect(result[27].timestamp).toBe(now.subtract(6, 'hour').toISOString())
|
|
|
|
// Check all buckets are 6 hours apart
|
|
for (let i = 0; i < result.length - 1; i++) {
|
|
const diff = dayjs(result[i + 1].timestamp).diff(dayjs(result[i].timestamp), 'hour')
|
|
expect(diff).toBe(6)
|
|
}
|
|
})
|
|
|
|
it('should aggregate data points into 6-hour buckets', () => {
|
|
const data: LogsBarChartDatum[] = [
|
|
{
|
|
timestamp: now.subtract(167, 'hour').toISOString(),
|
|
ok_count: 1000,
|
|
warning_count: 10,
|
|
error_count: 5,
|
|
},
|
|
{
|
|
timestamp: now.subtract(165, 'hour').toISOString(),
|
|
ok_count: 500,
|
|
warning_count: 5,
|
|
error_count: 2,
|
|
},
|
|
]
|
|
|
|
const result = normalizeChartBuckets(data, '7day', now.toDate())
|
|
|
|
// First bucket should contain aggregated data
|
|
expect(result[0].ok_count).toBe(1500)
|
|
expect(result[0].warning_count).toBe(15)
|
|
expect(result[0].error_count).toBe(7)
|
|
})
|
|
})
|
|
|
|
describe('edge cases', () => {
|
|
it('should handle data points outside the time range', () => {
|
|
const data: LogsBarChartDatum[] = [
|
|
{
|
|
timestamp: now.subtract(120, 'minute').toISOString(), // Outside 1hr range
|
|
ok_count: 100,
|
|
warning_count: 10,
|
|
error_count: 5,
|
|
},
|
|
{
|
|
timestamp: now.add(10, 'minute').toISOString(), // Future data
|
|
ok_count: 50,
|
|
warning_count: 5,
|
|
error_count: 2,
|
|
},
|
|
{
|
|
timestamp: now.subtract(30, 'minute').toISOString(), // Within range
|
|
ok_count: 25,
|
|
warning_count: 2,
|
|
error_count: 1,
|
|
},
|
|
]
|
|
|
|
const result = normalizeChartBuckets(data, '1hr', now.toDate())
|
|
|
|
// Should only include the data within range
|
|
const validBucket = result[15] // 30 minutes ago
|
|
expect(validBucket.ok_count).toBe(25)
|
|
expect(validBucket.warning_count).toBe(2)
|
|
expect(validBucket.error_count).toBe(1)
|
|
|
|
// Other buckets should be empty
|
|
expect(result[0].ok_count).toBe(0)
|
|
expect(result[29].ok_count).toBe(0)
|
|
})
|
|
|
|
it('should handle undefined/null values in data', () => {
|
|
const data: LogsBarChartDatum[] = [
|
|
{
|
|
timestamp: now.subtract(30, 'minute').toISOString(),
|
|
ok_count: undefined as any,
|
|
warning_count: null as any,
|
|
error_count: 5,
|
|
},
|
|
]
|
|
|
|
const result = normalizeChartBuckets(data, '1hr', now.toDate())
|
|
|
|
const bucket = result[15]
|
|
expect(bucket.ok_count).toBe(0)
|
|
expect(bucket.warning_count).toBe(0)
|
|
expect(bucket.error_count).toBe(5)
|
|
})
|
|
})
|
|
})
|