Files
supabase/apps/studio/components/interfaces/ProjectHome/ChartDataTransform.utils.ts
Pamela Chia 01c178e159 chore(studio): graduate homeNew experiment (#43437)
## 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
2026-03-10 17:03:58 +09:00

90 lines
2.6 KiB
TypeScript

import dayjs from 'dayjs'
import type { LogsBarChartDatum } from './ProjectUsage.metrics'
/**
* Configuration for chart bucket sizes based on time interval
*/
const BUCKET_CONFIG = {
'1hr': {
bucketMinutes: 2, // 2-minute buckets
expectedBuckets: 30, // 60 minutes / 2 = 30 buckets
},
'1day': {
bucketMinutes: 60, // 1-hour buckets
expectedBuckets: 24, // 24 hours
},
'7day': {
bucketMinutes: 360, // 6-hour buckets
expectedBuckets: 28, // 168 hours / 6 = 28 buckets
},
} as const
type IntervalKey = keyof typeof BUCKET_CONFIG
/**
* Normalizes chart data to consistent bucket sizes regardless of backend data density.
*
* For 1hr interval: Creates 30 buckets of 2 minutes each
* For 1day interval: Creates 24 buckets of 1 hour each
* For 7day interval: Creates 28 buckets of 6 hours each
*
* This ensures consistent bar width in charts and proper data aggregation.
*
* @param data - Raw chart data from backend
* @param interval - Time interval key ('1hr', '1day', '7day')
* @param endDate - End date for the chart (defaults to now)
* @returns Array of exactly the expected number of buckets with aggregated data
*/
export function normalizeChartBuckets(
data: LogsBarChartDatum[],
interval: IntervalKey,
endDate: Date = new Date()
): LogsBarChartDatum[] {
const config = BUCKET_CONFIG[interval]
const { bucketMinutes, expectedBuckets } = config
// Calculate start time based on expected buckets
const end = dayjs(endDate)
const start = end.subtract(expectedBuckets * bucketMinutes, 'minute')
// Create empty buckets
const buckets: LogsBarChartDatum[] = []
let currentBucketStart = start
for (let i = 0; i < expectedBuckets; i++) {
buckets.push({
timestamp: currentBucketStart.toISOString(),
ok_count: 0,
warning_count: 0,
error_count: 0,
})
currentBucketStart = currentBucketStart.add(bucketMinutes, 'minute')
}
// If no data, return empty buckets
if (!data || data.length === 0) {
return buckets
}
// Aggregate data into buckets
for (const datum of data) {
const datumTime = dayjs(datum.timestamp)
// Find which bucket this datum belongs to
const bucketIndex = Math.floor(datumTime.diff(start, 'minute') / bucketMinutes)
// Skip data points outside our time range
if (bucketIndex < 0 || bucketIndex >= expectedBuckets) {
continue
}
// Aggregate counts into the appropriate bucket
buckets[bucketIndex].ok_count += datum.ok_count || 0
buckets[bucketIndex].warning_count += datum.warning_count || 0
buckets[bucketIndex].error_count += datum.error_count || 0
}
return buckets
}