ruvector/studio/hooks/misc/useRealtimeExperiment.ts
rUv 814f595995 feat(studio): Add complete RuVector Studio application
Major additions:
- Complete Next.js studio application with 1600+ components
- Docker support (Dockerfile.combined, docker-compose.yml)
- GCP deployment documentation and benchmarks
- SQL benchmark scripts for performance testing
- Sentry integration for monitoring
- Comprehensive test suite and mocks

Studio features:
- Dashboard and admin interfaces
- Data visualization components
- Authentication and user management
- API integration with RuVector backend
- Static data and public assets

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-06 23:04:48 +00:00

112 lines
3.1 KiB
TypeScript

import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'
import { useEffect, useMemo, useRef } from 'react'
import { usePHFlag } from 'hooks/ui/useFlag'
import { IS_PLATFORM } from 'lib/constants'
import { useTrack } from 'lib/telemetry/track'
dayjs.extend(utc)
/**
* Days after project creation to be considered "new" for experiment targeting
*/
export const NEW_PROJECT_THRESHOLD_DAYS = 7
export enum RealtimeButtonVariant {
CONTROL = 'control',
HIDE_BUTTON = 'hide-button',
TRIGGERS = 'triggers',
}
interface UseRealtimeExperimentOptions {
/**
* Project creation timestamp
*/
projectInsertedAt?: string
/**
* Whether the current context is a table (not a view/foreign table)
*/
isTable?: boolean
/**
* Whether realtime is currently enabled for the table
*/
isRealtimeEnabled?: boolean
}
interface UseRealtimeExperimentResult {
/**
* The active variant for this user/project, or null if not in experiment
*/
activeVariant: RealtimeButtonVariant | null
/**
* Whether this project is considered "new" for experiment targeting
*/
isNewProject: boolean
}
/**
* Hook to manage the realtime button A/B experiment logic.
* Handles variant determination, exposure tracking, and date validation.
*
* @param options Configuration for experiment targeting
* @returns Experiment state including active variant and project age
*/
export function useRealtimeExperiment({
projectInsertedAt,
isTable = false,
isRealtimeEnabled = false,
}: UseRealtimeExperimentOptions): UseRealtimeExperimentResult {
const track = useTrack()
const realtimeButtonVariant = usePHFlag<RealtimeButtonVariant>('realtimeButtonVariant')
const hasTrackedExposure = useRef(false)
const isNewProject = useMemo(() => {
if (!projectInsertedAt) return false
const insertedDate = dayjs.utc(projectInsertedAt)
if (!insertedDate.isValid()) {
return false
}
return dayjs.utc().diff(insertedDate, 'day') < NEW_PROJECT_THRESHOLD_DAYS
}, [projectInsertedAt])
const activeVariant = useMemo(() => {
if (!IS_PLATFORM) return null
if (!isTable || !isNewProject) return null
if (!realtimeButtonVariant || realtimeButtonVariant === RealtimeButtonVariant.CONTROL) {
return null
}
return realtimeButtonVariant
}, [isTable, isNewProject, realtimeButtonVariant])
useEffect(() => {
if (!IS_PLATFORM) return
if (hasTrackedExposure.current) return
if (!isTable || !isNewProject || !projectInsertedAt) return
if (!realtimeButtonVariant) return
hasTrackedExposure.current = true
try {
const insertedDate = dayjs.utc(projectInsertedAt)
if (!insertedDate.isValid()) return
const daysSinceCreation = dayjs.utc().diff(insertedDate, 'day')
track('realtime_experiment_exposed', {
variant: realtimeButtonVariant,
table_has_realtime_enabled: isRealtimeEnabled,
days_since_project_creation: daysSinceCreation,
})
} catch {
hasTrackedExposure.current = false
}
}, [isTable, isNewProject, realtimeButtonVariant, projectInsertedAt, isRealtimeEnabled, track])
return {
activeVariant,
isNewProject,
}
}