chore: more dead code cleanup (#47312)

## Problem

There's still more unused code in the repository which slows down
everything:
- checkouts
- tooling
- probably builds (not sure how good turbopack is at handling this)

## Solution

- remove old unused code
- remove more recent code after checking git history to ensure it's not
unfinished/ongoing work

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

* **Chores**
* Removed several outdated UI components and helper utilities to
streamline the app.
* Cleaned up unused analytics, database, and observability hooks and
queries.
* **Refactor**
* Simplified data table, unified logs, and assistant panel internals by
removing legacy display and navigation pieces.
* **Bug Fixes**
* Reduced the chance of showing stale or inconsistent status, chart, and
metric views by eliminating obsolete display paths.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Gildas Garcia authored and GitHub committed 2026-06-26 11:48:58 +02:00
1 parent b1b29ad011
commit 77bf0a4ec9
24 files changed
-1701

No files matched your search

@@ -1,81 +0,0 @@
import { SchemaInstallationStatus } from '@stripe/sync-engine/supabase'
import { AlertCircle, Check, RefreshCwIcon } from 'lucide-react'
import {
hasInstallError,
hasUninstallError,
isInstalled,
isInstalling,
isUninstalling,
} from './stripe-sync-status'
export const StatusDisplay = ({
status,
isInstallRequested,
isInstallInitiated,
isUninstallRequested,
isUninstallInitiated,
isUpgrade,
timedOut,
}: {
status: SchemaInstallationStatus
isInstallRequested: boolean
isInstallInitiated: boolean
isUninstallRequested: boolean
isUninstallInitiated: boolean
isUpgrade?: boolean
timedOut: boolean
}) => {
const installed = isInstalled(status)
const installError = hasInstallError(status)
const uninstallError = hasUninstallError(status)
const installInProgress = isInstalling(status)
const uninstallInProgress = isUninstalling(status)
const installing = (installInProgress || isInstallRequested || isInstallInitiated) && !timedOut
const uninstalling =
(uninstallInProgress || isUninstallRequested || isUninstallInitiated) && !timedOut
if (uninstalling) {
return (
<span className="flex items-center gap-2 text-foreground-light text-sm">
<RefreshCwIcon size={14} className="animate-spin text-foreground-lighter" />
Uninstalling...
</span>
)
}
if (uninstallError) {
return (
<span className="flex items-center gap-2 text-foreground-light text-sm">
<AlertCircle size={14} className="text-destructive" />
Uninstallation error
</span>
)
}
if (installing) {
return (
<span className="flex items-center gap-2 text-foreground-light text-sm">
<RefreshCwIcon size={14} className="animate-spin text-foreground-lighter" />
{isUpgrade ? 'Upgrading...' : 'Installing...'}
</span>
)
}
if (installError) {
return (
<span className="flex items-center gap-2 text-foreground-light text-sm">
<AlertCircle size={14} className="text-destructive" />
{isUpgrade ? 'Upgrade error' : 'Installation error'}
</span>
)
}
if (installed) {
return (
<span className="flex items-center gap-2 text-foreground-light text-sm">
<Check size={14} strokeWidth={1.5} className="text-brand" /> Installed
</span>
)
}
return (
<span className="flex items-center gap-2 text-foreground-light text-sm">Not installed</span>
)
}
@@ -1,110 +0,0 @@
import Link from 'next/link'
import { Button, Card, CardContent, CardFooter, CardHeader, CardTitle, cn, Loading } from 'ui'
import { LogsBarChart } from 'ui-patterns/LogsBarChart'
import type { LogsBarChartDatum } from '../ProjectHome/ProjectUsage.metrics'
import { getHealthStatus, type ServiceKey } from './ObservabilityOverview.utils'
import NoDataPlaceholder from '@/components/ui/Charts/NoDataPlaceholder'
const colorClassMap: Record<string, string> = {
muted: 'bg-muted',
destructive: 'bg-destructive',
warning: 'bg-warning',
brand: 'bg-brand',
}
export type ServiceHealthCardProps = {
serviceName: string
serviceKey: ServiceKey
total: number
errorRate: number
errorCount: number
warningCount: number
chartData: LogsBarChartDatum[]
reportUrl?: string // undefined if feature flag disabled or no report available
logsUrl: string
isLoading: boolean
onBarClick: (datum: any) => void
datetimeFormat: string
}
export const ServiceHealthCard = ({
serviceName,
serviceKey: _serviceKey,
total,
errorRate,
errorCount,
warningCount,
chartData,
reportUrl,
logsUrl,
isLoading,
onBarClick,
datetimeFormat,
}: ServiceHealthCardProps) => {
const { color } = getHealthStatus(errorRate, total)
return (
<Card className="mb-0 md:mb-0 h-full flex flex-col">
<CardHeader className="flex flex-row items-start justify-between gap-2 space-y-0 pb-0 border-b-0">
<div className="flex flex-col">
<CardTitle className="text-foreground-light flex items-center gap-2 text-xs font-medium">
<div className={cn('w-1.5 h-1.5 rounded-full', colorClassMap[color] || 'bg-muted')} />
{serviceName}
</CardTitle>
<div className="flex items-start gap-6 mt-2">
<div className="flex flex-col">
<span className="text-foreground text-xl">{total.toLocaleString()}</span>
<span className="text-foreground-light text-xs">requests</span>
</div>
<div className="flex flex-col">
<span className="text-foreground text-xl">{errorRate.toFixed(1)}%</span>
<span className="text-foreground-light text-xs">error rate</span>
</div>
</div>
</div>
<div className="flex items-end gap-4 text-foreground-light">
<div className="flex flex-col items-end">
<div className="flex items-center gap-2">
<div className="w-1.5 h-1.5 bg-warning rounded-full" />
<span className="heading-meta">Warn</span>
</div>
<span className="text-foreground text-xl">{warningCount.toLocaleString()}</span>
</div>
<div className="flex flex-col items-end">
<div className="flex items-center gap-2">
<div className="w-1.5 h-1.5 bg-destructive rounded-full" />
<span className="heading-meta">Err</span>
</div>
<span className="text-foreground text-xl">{errorCount.toLocaleString()}</span>
</div>
</div>
</CardHeader>
<CardContent className="p-card pt-4 flex-1 overflow-hidden max-h-[200px]">
<Loading isFullHeight active={isLoading}>
<LogsBarChart
isFullHeight
data={chartData}
DateTimeFormat={datetimeFormat}
onBarClick={onBarClick}
EmptyState={
<NoDataPlaceholder size="small" message="No data for selected period" isFullHeight />
}
/>
</Loading>
</CardContent>
<CardFooter className="border-t pt-3 pb-4 px-card flex gap-2">
{reportUrl && (
<Button variant="default" size="tiny" asChild className="flex-1">
<Link href={reportUrl}>View Report</Link>
</Button>
)}
<Button variant="default" size="tiny" asChild className="flex-1">
<Link href={logsUrl}>View Logs</Link>
</Button>
</CardFooter>
</Card>
)
}
@@ -1,98 +0,0 @@
import { useQuery } from '@tanstack/react-query'
import type { LogsBarChartDatum } from '../ProjectHome/ProjectUsage.metrics'
import { executeAnalyticsSql } from '@/data/logs/execute-analytics-sql'
import { safeSql, type SafeLogSqlFragment } from '@/data/logs/safe-analytics-sql'
type PostgrestMetricsVariables = {
projectRef: string
startDate: string
endDate: string
interval: '1hr' | '1day' | '7day'
}
function getIntervalTrunc(interval: '1hr' | '1day' | '7day'): SafeLogSqlFragment {
switch (interval) {
case '1hr':
return safeSql`minute` // 1-minute buckets for 1 hour
case '1day':
return safeSql`hour` // 1-hour buckets for 1 day
case '7day':
return safeSql`day` // 1-day buckets for 7 days
default:
return safeSql`hour`
}
}
const POSTGREST_METRICS_SQL = (interval: '1hr' | '1day' | '7day'): SafeLogSqlFragment => {
const trunc = getIntervalTrunc(interval)
return safeSql`
-- postgrest-overview-metrics
select
cast(timestamp_trunc(t.timestamp, ${trunc}) as datetime) as timestamp,
countif(response.status_code < 300) as ok_count,
countif(response.status_code >= 300 and response.status_code < 400) as warning_count,
countif(response.status_code >= 400) as error_count
FROM edge_logs t
cross join unnest(metadata) as m
cross join unnest(m.response) as response
cross join unnest(m.request) as request
WHERE
request.path like '/rest/%'
GROUP BY
timestamp
ORDER BY
timestamp ASC
`
}
type MetricsRow = {
timestamp: string
ok_count: number
warning_count: number
error_count: number
}
async function fetchPostgrestMetrics(
{ projectRef, startDate, endDate, interval }: PostgrestMetricsVariables,
signal?: AbortSignal
) {
const sql = POSTGREST_METRICS_SQL(interval)
const data = await executeAnalyticsSql({
projectRef,
endpoint: '/platform/projects/{ref}/analytics/endpoints/logs.all',
sql,
iso_timestamp_start: startDate,
iso_timestamp_end: endDate,
method: 'get',
signal,
})
if (data?.error) throw data.error
return (data?.result || []) as MetricsRow[]
}
export const usePostgrestOverviewMetrics = (
{ projectRef, startDate, endDate, interval }: PostgrestMetricsVariables,
options?: { enabled?: boolean }
) => {
return useQuery({
queryKey: ['postgrest-overview-metrics', projectRef, startDate, endDate, interval],
queryFn: ({ signal }) =>
fetchPostgrestMetrics({ projectRef, startDate, endDate, interval }, signal),
enabled: (options?.enabled ?? true) && Boolean(projectRef),
staleTime: 1000 * 60,
})
}
export const transformPostgrestMetrics = (rows: MetricsRow[]): LogsBarChartDatum[] => {
return rows.map((row) => ({
timestamp: row.timestamp,
ok_count: row.ok_count,
warning_count: row.warning_count,
error_count: row.error_count,
}))
}
@@ -1,82 +0,0 @@
import { Skeleton } from 'ui'
import { BlockFieldProps } from '../../types'
import { TruncatedTextWithPopover } from './TruncatedTextWithPopover'
import { DataTableColumnStatusCode } from '@/components/ui/DataTable/DataTableColumn/DataTableColumnStatusCode'
import { DataTableSheetRowAction } from '@/components/ui/DataTable/DataTableSheetRowAction'
export const BlockField = ({
config,
data,
enrichedData,
isLoading,
filterFields,
table,
}: BlockFieldProps) => {
const value = config.getValue(data, enrichedData)
const displayValue = value ?? 'No value'
const stringValue = String(displayValue)
const shouldShowSkeleton = config.requiresEnrichedData && isLoading && !value
const filterField = filterFields.find((field) => field.value === config.id)
const isFilterable = !!filterField
// Special handling for status field and key fields
const isStatusField = config.id === 'status'
const isApiKeyField = config.id === 'api_key_role'
// Truncation config
const maxLength = config.maxLength || 50 // Default to 50 characters
const fieldContent = (
<>
<dt className="text-[13.5px] text-foreground-light">{config.label}</dt>
<dd className="text-right">
{shouldShowSkeleton ? (
<Skeleton className="h-4 w-24" />
) : isStatusField && displayValue !== 'N/A' ? (
<DataTableColumnStatusCode value={displayValue} level={data?.level} className="text-xs" />
) : isApiKeyField && displayValue !== 'N/A' ? (
<TruncatedTextWithPopover
text={stringValue}
maxLength={maxLength}
className="px-2 py-1 text-xs font-mono border border-border rounded-sm bg-surface-100"
/>
) : displayValue !== 'N/A' ? (
<TruncatedTextWithPopover
text={stringValue}
maxLength={maxLength}
className={`text-sm font-mono ${
isFilterable ? 'text-foreground cursor-pointer hover:underline' : 'text-foreground'
}`}
/>
) : (
<span
className={`text-sm font-mono ${
isFilterable ? 'text-foreground cursor-pointer hover:underline' : 'text-foreground'
} text-foreground-light`}
>
{displayValue}
</span>
)}
</dd>
</>
)
if (isFilterable && !shouldShowSkeleton && displayValue !== 'N/A') {
return (
<DataTableSheetRowAction
fieldValue={config.id}
filterFields={filterFields}
value={stringValue}
table={table}
className="flex justify-between items-center py-1 px-2 rounded-sm hover:bg-accent/50 cursor-pointer w-full hover:bg-surface-400"
>
{fieldContent}
</DataTableSheetRowAction>
)
}
return <div className="flex justify-between items-center py-1 px-2">{fieldContent}</div>
}
@@ -1,123 +0,0 @@
import { Table } from '@tanstack/react-table'
import { X } from 'lucide-react'
import { useState } from 'react'
import { Badge, Collapsible, CollapsibleContent, CollapsibleTrigger, Skeleton } from 'ui'
import { BlockFieldConfig } from '../../types'
import { BlockField } from './BlockField'
import { DataTableFilterField } from '@/components/ui/DataTable/DataTable.types'
// Single source of truth for field row styling
export const FieldRow = ({
label,
value,
expandButton,
}: {
label: string
value: React.ReactNode
expandButton?: React.ReactNode
}) => (
<div className="flex justify-between items-center py-1 px-2">
<dt className="flex items-center gap-2 text-[13.5px] text-foreground-light">
<span>{label}</span>
{expandButton}
</dt>
<dd className="text-right">{value}</dd>
</div>
)
interface FieldWithSeeMoreProps {
primaryField: BlockFieldConfig
additionalFields: BlockFieldConfig[]
data: any
enrichedData?: any
isLoading?: boolean
filterFields: DataTableFilterField<any>[]
table: Table<any>
showValueAsBadge?: boolean
}
// Primary field with expandable additional details
export const FieldWithSeeMore = ({
primaryField,
additionalFields,
data,
enrichedData,
isLoading,
filterFields,
table,
showValueAsBadge = false,
}: FieldWithSeeMoreProps) => {
const [isOpen, setIsOpen] = useState(false)
const hasAdditionalData = additionalFields.some(
(field) => field.getValue(data, enrichedData) && field.getValue(data, enrichedData) !== 'N/A'
)
const primaryValue = primaryField.getValue(data, enrichedData)
const shouldShowSkeleton = primaryField.requiresEnrichedData && isLoading && !primaryValue
const isApiKeyField = primaryField.id === 'api_key_role'
// Common value rendering logic
const renderValue = () => {
if (shouldShowSkeleton) {
return <Skeleton className="h-4 w-24" />
}
if (isApiKeyField && primaryValue && primaryValue !== 'N/A') {
return (
<span className="border border-border rounded-sm px-2 py-1 bg-surface-100 text-xs font-mono text-foreground">
{primaryValue}
</span>
)
}
if (showValueAsBadge && primaryValue && primaryValue !== 'N/A') {
return <Badge variant="secondary">{primaryValue}</Badge>
}
return (
<span
className={`text-sm font-mono text-foreground ${
primaryValue === 'N/A' ? 'text-foreground-light' : ''
}`}
>
{primaryValue ?? 'N/A'}
</span>
)
}
const expandButton = hasAdditionalData ? (
<CollapsibleTrigger asChild>
<button className="w-3 h-3 flex items-center justify-center selection:font-mono text-foreground-lighter text-xs hover:text-foreground-light bg-foreground-muted/75 rounded-sm [&[data-state=open]>svg]:rotate-180 hover:bg-foreground-lighter">
<X size={10} className="text-background-surface-400 rotate-45" strokeWidth={3} />
</button>
</CollapsibleTrigger>
) : undefined
return (
<div className="border-t border-border">
{hasAdditionalData ? (
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
<FieldRow label={primaryField.label} value={renderValue()} expandButton={expandButton} />
<CollapsibleContent className="transition-all data-closed:animate-collapsible-up data-open:animate-collapsible-down">
<div className="mt-1">
{additionalFields.map((field) => (
<BlockField
key={field.id}
config={field}
data={data}
enrichedData={enrichedData}
isLoading={isLoading}
filterFields={filterFields}
table={table}
/>
))}
</div>
</CollapsibleContent>
</Collapsible>
) : (
<FieldRow label={primaryField.label} value={renderValue()} />
)}
</div>
)
}
@@ -1,37 +0,0 @@
import { useState } from 'react'
import { Popover, PopoverContent, PopoverTrigger } from 'ui'
interface TruncatedTextWithPopoverProps {
text: string
maxLength?: number
className?: string
children?: React.ReactNode
}
export const TruncatedTextWithPopover = ({
text,
maxLength = 50,
className = '',
children,
}: TruncatedTextWithPopoverProps) => {
const [isOpen, setIsOpen] = useState(false)
const isTruncated = text.length > maxLength
const truncatedText = isTruncated ? text.substring(0, maxLength) + '...' : text
// If not truncated, just render the text without popover
if (!isTruncated) {
return children ? <>{children}</> : <span className={className}>{text}</span>
}
// If truncated, render with popover
return (
<Popover open={isOpen} onOpenChange={setIsOpen}>
<PopoverTrigger asChild>
<span className={`cursor-help ${className}`}>{children || truncatedText}</span>
</PopoverTrigger>
<PopoverContent className="max-w-md p-3 wrap-break-word" side="top">
<div className="text-sm font-mono whitespace-pre-wrap">{text}</div>
</PopoverContent>
</Popover>
)
}
@@ -1,142 +0,0 @@
import { BlockFieldConfig } from '../types'
/**
* Common field configuration helpers to reduce duplication
*/
// Helper for simple fields that fallback from enrichedData to data
export const createField = (
id: string,
label: string,
enrichedKey?: string,
dataKey?: string,
requiresEnrichedData = false
): BlockFieldConfig => ({
id,
label,
getValue: (data, enrichedData) => {
const eKey = enrichedKey || id
const dKey = dataKey || id
return enrichedData?.[eKey] || data?.[dKey]
},
requiresEnrichedData,
})
// Helper for truncated text fields
export const createTruncatedField = (
id: string,
label: string,
maxLength: number,
enrichedKey?: string,
dataKey?: string,
requiresEnrichedData = false
): BlockFieldConfig => ({
id,
label,
getValue: (data, enrichedData) => {
const eKey = enrichedKey || id
const dKey = dataKey || id
const value = enrichedData?.[eKey] || data?.[dKey]
if (!value || typeof value !== 'string') return value
return value.length > maxLength ? `${value.substring(0, maxLength)}...` : value
},
requiresEnrichedData,
})
// Helper for ID fields that show truncated version
export const createIdField = (
id: string,
label: string,
truncateLength = 8,
enrichedKey?: string,
dataKey?: string,
requiresEnrichedData = false
): BlockFieldConfig => ({
id,
label,
getValue: (data, enrichedData) => {
const eKey = enrichedKey || id
const dKey = dataKey || id
const value = enrichedData?.[eKey] || data?.[dKey]
if (!value) return null
const stringValue = String(value)
return truncateLength ? `${stringValue.substring(0, truncateLength)}...` : stringValue
},
requiresEnrichedData,
})
// Helper for time/duration fields
export const createTimeField = (
id: string,
label: string,
unit = 'ms',
enrichedKey?: string,
dataKey?: string,
requiresEnrichedData = false
): BlockFieldConfig => ({
id,
label,
getValue: (data, enrichedData) => {
const eKey = enrichedKey || id
const dKey = dataKey || id
const time = enrichedData?.[eKey] || data?.[dKey]
return time ? `${time}${unit}` : null
},
requiresEnrichedData,
})
// Helper for status fields with fallback logic
export const createStatusField = (
id: string = 'status',
label: string = 'Status'
): BlockFieldConfig => ({
id,
label,
getValue: (data, enrichedData) => enrichedData?.status || data?.status,
})
// Helper for path/filename extraction
export const createFileNameField = (
id: string,
label: string,
pathKey = 'path',
requiresEnrichedData = false
): BlockFieldConfig => ({
id,
label,
getValue: (data, enrichedData) => {
const path = enrichedData?.[pathKey] || data?.[pathKey]
if (!path) return null
// Remove query parameters and hash, get last segment
const cleanPath = path.split('?')[0].split('#')[0]
const segments = cleanPath.split('/')
return segments[segments.length - 1] || null
},
requiresEnrichedData,
})
// Helper for conditional fields based on error status
export const createConditionalField = (
id: string,
label: string,
getValue: (data: any, enrichedData?: any) => any,
errorValue: string = 'Unavailable',
deletedValue: string = 'Object deleted',
requiresEnrichedData = false
): BlockFieldConfig => ({
id,
label,
getValue: (data, enrichedData) => {
const status = enrichedData?.status || data?.status
const numStatus = Number(status)
if (status === 404 || status === '404') {
return deletedValue
} else if (numStatus >= 400) {
return errorValue
}
return getValue(data, enrichedData)
},
requiresEnrichedData,
})
@@ -1,36 +0,0 @@
import { User } from 'lucide-react'
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from 'ui/src/components/shadcn/ui/hover-card'
interface AuthUserHoverCardProps {
authUser: string
}
export const AuthUserHoverCard = ({ authUser }: AuthUserHoverCardProps) => {
if (!authUser) return null
return (
<HoverCard closeDelay={0}>
<HoverCardTrigger className="flex items-center justify-center bg-surface-100 border rounded-full">
<User size={14} className="text-foreground-lighter" />
</HoverCardTrigger>
<HoverCardContent className="w-80 p-4">
<div className="space-y-2">
<div className="flex items-center gap-2">
<User size={20} className="text-foreground" />
<h4 className="font-medium">User Details</h4>
</div>
<div className="space-y-1">
<div className="text-sm">
<span className="font-medium">ID/Email:</span> {authUser}
</div>
{/* Additional user details can be added here if available */}
</div>
</div>
</HoverCardContent>
</HoverCard>
)
}
@@ -1,92 +0,0 @@
import { useParams } from 'common'
import { useMemo } from 'react'
import type { CommandOptions, ICommand } from 'ui-patterns/CommandMenu'
import { orderSectionFirst, useQuery, useRegisterCommands } from 'ui-patterns/CommandMenu'
import { COMMAND_MENU_SECTIONS } from '@/components/interfaces/App/CommandMenu/CommandMenu.utils'
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
const QUERY_PERFORMANCE_COMMAND_ID = 'nav-reports-query-performance'
export function useReportsGotoCommands(options?: CommandOptions) {
let { ref } = useParams()
ref ||= '_'
const { reportsAll } = useIsFeatureEnabled(['reports:all'])
const commandQuery = useQuery()?.toLowerCase() ?? ''
const prioritizeQueryPerformance = commandQuery.includes('query')
const orderQueryPerformanceCommand = useMemo(() => {
if (!prioritizeQueryPerformance) return undefined
return (existingCommands: ICommand[], incomingCommands: ICommand[]) => {
const filteredExisting = existingCommands.filter(
(command) => command.id !== QUERY_PERFORMANCE_COMMAND_ID
)
return [...incomingCommands, ...filteredExisting]
}
}, [prioritizeQueryPerformance])
const orderNavigateSection = useMemo<CommandOptions['orderSection'] | undefined>(() => {
return prioritizeQueryPerformance ? orderSectionFirst : options?.orderSection
}, [options?.orderSection, prioritizeQueryPerformance])
useRegisterCommands(
COMMAND_MENU_SECTIONS.NAVIGATE,
reportsAll
? [
{
id: 'nav-reports',
name: 'Reports',
route: `/project/${ref}/observability`,
defaultHidden: true,
},
{
id: 'nav-reports-api',
name: 'API Reports',
route: `/project/${ref}/observability/api-overview`,
defaultHidden: true,
},
{
id: 'nav-reports-storage',
name: 'Storage Reports',
route: `/project/${ref}/observability/storage`,
defaultHidden: true,
},
{
id: 'nav-reports-database',
name: 'Database Reports',
route: `/project/${ref}/observability/database`,
defaultHidden: true,
},
]
: [],
{
...options,
orderSection: orderNavigateSection,
deps: [ref, orderNavigateSection, ...(options?.deps ?? [])],
}
)
useRegisterCommands(
COMMAND_MENU_SECTIONS.NAVIGATE,
reportsAll
? [
{
id: QUERY_PERFORMANCE_COMMAND_ID,
name: 'Query Performance Reports',
route: `/project/${ref}/observability/query-performance`,
defaultHidden: true,
},
]
: [],
{
...options,
orderCommands: orderQueryPerformanceCommand ?? options?.orderCommands,
orderSection: orderNavigateSection,
deps: [ref, orderQueryPerformanceCommand, orderNavigateSection, ...(options?.deps ?? [])],
}
)
}
@@ -1,73 +0,0 @@
import { useCallback, useEffect, useRef, useState } from 'react'
interface UseAutoScrollProps {
enabled?: boolean
}
export function useAutoScroll({ enabled = true }: UseAutoScrollProps = {}) {
const [container, setContainer] = useState<HTMLDivElement | null>(null)
const [isSticky, setIsSticky] = useState(true)
const isStickyRef = useRef(true)
const lastScrollHeightRef = useRef<number>(null)
const ref = useCallback((element: HTMLDivElement | null) => {
if (element) {
setContainer(element)
}
}, [])
const scrollToEnd = useCallback(() => {
if (container) {
isStickyRef.current = true
setIsSticky(true)
container.scrollTo({
top: container.scrollHeight,
behavior: 'smooth',
})
}
}, [container])
useEffect(() => {
if (!container || !enabled) return
let timeoutId: NodeJS.Timeout
const resizeObserver = new ResizeObserver(() => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => {
if (
lastScrollHeightRef.current != null &&
container.scrollHeight !== lastScrollHeightRef.current
) {
lastScrollHeightRef.current = container.scrollHeight
if (isStickyRef.current) {
scrollToEnd()
}
}
}, 100)
})
const handleScroll = () => {
const isAtBottom =
Math.abs(container.scrollHeight - container.scrollTop - container.clientHeight) < 10
isStickyRef.current = isAtBottom
setIsSticky(isAtBottom)
}
// Observe all children of the container
Array.from(container.children).forEach((child) => {
resizeObserver.observe(child)
})
container.addEventListener('scroll', handleScroll)
return () => {
clearTimeout(timeoutId)
resizeObserver.disconnect()
container.removeEventListener('scroll', handleScroll)
}
}, [container, enabled, scrollToEnd])
return { ref, isSticky, scrollToEnd, setIsSticky }
}
@@ -1,208 +0,0 @@
import dayjs from 'dayjs'
import { Activity, BarChartIcon, Loader2 } from 'lucide-react'
import { useRouter } from 'next/router'
import { PropsWithChildren, useMemo, useState } from 'react'
import { Button, Tooltip, TooltipContent, TooltipTrigger, WarningIcon } from 'ui'
import type { ChartData } from './Charts.types'
import AreaChart from '@/components/ui/Charts/AreaChart'
import BarChart from '@/components/ui/Charts/BarChart'
import { AnalyticsInterval } from '@/data/analytics/constants'
import { mapMultiResponseToAnalyticsData } from '@/data/analytics/infra-monitoring-queries'
import {
InfraMonitoringAttribute,
useInfraMonitoringAttributesQuery,
} from '@/data/analytics/infra-monitoring-query'
import {
ProjectDailyStatsAttribute,
useProjectDailyStatsQuery,
} from '@/data/analytics/project-daily-stats-query'
import { useDatabaseSelectorStateSnapshot } from '@/state/database-selector'
interface ChartHandlerProps {
id?: string
label: string
attribute: string
provider: 'infra-monitoring' | 'daily-stats'
startDate: string
endDate: string
interval: string
customDateFormat?: string
defaultChartStyle?: 'bar' | 'line'
hideChartType?: boolean
data?: ChartData
isLoading?: boolean
format?: string
highlightedValue?: string | number
syncId?: string
}
/**
* Controls chart display state. Optionally fetches static chart data if data is not provided.
*
* If the `data` prop is provided, it will disable automatic chart data fetching and pass the data directly to the chart render.
* - loading state can also be provided through the `isLoading` prop, to display loading placeholders. Ignored if `data` key not provided.
* - if `isLoading=true` and `data` is `undefined`, loading error message will be shown.
*
* Provided data must be in the expected chart format.
*/
const ChartHandler = ({
label,
attribute,
provider,
startDate,
endDate,
interval,
customDateFormat,
children = null,
defaultChartStyle = 'bar',
hideChartType = false,
data,
isLoading,
format,
highlightedValue,
syncId,
...otherProps
}: PropsWithChildren<ChartHandlerProps>) => {
const router = useRouter()
const { ref } = router.query
const state = useDatabaseSelectorStateSnapshot()
const [chartStyle, setChartStyle] = useState<string>(defaultChartStyle)
const databaseIdentifier = state.selectedDatabaseId
const { data: dailyStatsData, isPending: isFetchingDailyStats } = useProjectDailyStatsQuery(
{
projectRef: ref as string,
attribute: attribute as ProjectDailyStatsAttribute,
startDate: dayjs(startDate).format('YYYY-MM-DD'),
endDate: dayjs(endDate).format('YYYY-MM-DD'),
},
{ enabled: provider === 'daily-stats' && data === undefined }
)
const { data: infraMonitoringData, isPending: isFetchingInfraMonitoring } =
useInfraMonitoringAttributesQuery(
{
projectRef: ref as string,
attributes: [attribute as InfraMonitoringAttribute],
startDate,
endDate,
interval: interval as AnalyticsInterval,
databaseIdentifier,
},
{ enabled: provider === 'infra-monitoring' && data === undefined }
)
const transformedInfraData = useMemo(() => {
if (!infraMonitoringData) return undefined
const mapped = mapMultiResponseToAnalyticsData(infraMonitoringData, [
attribute as InfraMonitoringAttribute,
])
return mapped[attribute]
}, [infraMonitoringData, attribute])
const chartData =
data ||
(provider === 'infra-monitoring'
? transformedInfraData
: provider === 'daily-stats'
? dailyStatsData
: undefined)
const loading =
isLoading ||
(provider === 'infra-monitoring'
? isFetchingInfraMonitoring
: provider === 'daily-stats'
? isFetchingDailyStats
: isLoading)
const shouldHighlightMaxValue =
provider === 'daily-stats' &&
!attribute.includes('ingress') &&
!attribute.includes('egress') &&
chartData !== undefined &&
'maximum' in chartData
const shouldHighlightTotalGroupedValue = chartData !== undefined && 'totalGrouped' in chartData
const _highlightedValue =
highlightedValue !== undefined
? highlightedValue
: shouldHighlightMaxValue
? chartData?.maximum
: provider === 'daily-stats'
? chartData?.total
: shouldHighlightTotalGroupedValue
? chartData?.totalGrouped?.[attribute]
: (chartData?.data[chartData?.data.length - 1] as any)?.[attribute as any]
if (loading) {
return (
<div className="flex h-52 w-full flex-col items-center justify-center gap-y-2">
<Loader2 size={18} className="animate-spin text-border-strong" />
<p className="text-xs text-foreground-lighter">Loading data for {label}</p>
</div>
)
}
if (chartData === undefined) {
return (
<div className="flex h-64 w-full flex-col items-center justify-center gap-y-2 border border-dashed rounded-md">
<WarningIcon />
<p className="text-xs text-foreground-lighter">Unable to load data for {label}</p>
</div>
)
}
return (
<div className="h-full w-full">
<div className="absolute right-6 z-10 flex justify-between">
{!hideChartType && (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="default"
className="px-1.5"
icon={chartStyle === 'bar' ? <Activity /> : <BarChartIcon />}
onClick={() => setChartStyle(chartStyle === 'bar' ? 'line' : 'bar')}
/>
</TooltipTrigger>
<TooltipContent side="left" align="center">
View as {chartStyle === 'bar' ? 'line chart' : 'bar chart'}
</TooltipContent>
</Tooltip>
)}
{children}
</div>
{chartStyle === 'bar' ? (
<BarChart
data={(chartData?.data ?? []) as any}
format={format || chartData?.format}
xAxisKey={'period_start'}
yAxisKey={attribute}
highlightedValue={_highlightedValue}
title={label}
customDateFormat={customDateFormat}
syncId={syncId}
{...otherProps}
/>
) : (
<AreaChart
data={(chartData?.data ?? []) as any}
format={format || chartData?.format}
xAxisKey="period_start"
yAxisKey={attribute}
highlightedValue={_highlightedValue}
title={label}
customDateFormat={customDateFormat}
syncId={syncId}
{...otherProps}
/>
)}
</div>
)
}
export default ChartHandler
@@ -1,23 +0,0 @@
import { type Column } from '@tanstack/react-table'
import { TanStackTableHeadSort } from 'ui-patterns/Table'
interface DataTableColumnHeaderProps<TData, TValue> {
column: Column<TData, TValue>
title: string
className?: string
}
/**
* @deprecated Use `TanStackTableHeadSort` from `ui-patterns/Table` instead.
*/
export const DataTableColumnHeader = <TData, TValue>({
column,
title,
className,
}: DataTableColumnHeaderProps<TData, TValue>) => {
return (
<TanStackTableHeadSort column={column} className={className}>
{title}
</TanStackTableHeadSort>
)
}
@@ -1,35 +0,0 @@
import { forwardRef, PropsWithChildren, useEffect, useRef } from 'react'
import { cn } from 'ui'
export const DataTableHeaderLayout = forwardRef<
HTMLDivElement,
PropsWithChildren<{
setTopBarHeight: (height: number) => void
}>
>(({ setTopBarHeight, ...props }, _ref) => {
const topBarRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const observer = new ResizeObserver(() => {
const rect = topBarRef.current?.getBoundingClientRect()
if (rect) {
setTopBarHeight(rect.height)
}
})
const topBar = topBarRef.current
if (!topBar) return
observer.observe(topBar)
return () => observer.unobserve(topBar)
}, [topBarRef])
return (
<div
ref={topBarRef}
className={cn('flex flex-col gap-4 bg-background p-2', 'top-0 z-10 pb-4')}
{...props}
/>
)
})
DataTableHeaderLayout.displayName = 'DataTableHeaderLayout'
@@ -1,123 +0,0 @@
import { ChevronDown, ChevronUp, X } from 'lucide-react'
import { ReactNode, useCallback, useEffect, useMemo } from 'react'
import { Button, cn, Separator, Skeleton, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
import { Kbd } from './primitives/Kbd'
import { useDataTable } from './providers/DataTableProvider'
export interface DataTableSheetDetailsProps {
title?: ReactNode
titleClassName?: string
children?: ReactNode
}
export function DataTableSheetDetails({
title,
titleClassName,
children,
}: DataTableSheetDetailsProps) {
const { table, rowSelection, isLoading } = useDataTable()
const selectedRowKey = Object.keys(rowSelection)?.[0]
const selectedRow = useMemo(() => {
if (isLoading && !selectedRowKey) return
return table.getRowModel().rows.find((row) => row.id === selectedRowKey)
}, [selectedRowKey, isLoading, table])
const index = table.getRowModel().rows.findIndex((row) => row.id === selectedRow?.id)
const nextId = useMemo(() => table.getRowModel().rows[index + 1]?.id, [index, table])
const prevId = useMemo(() => table.getRowModel().rows[index - 1]?.id, [index, table])
const onPrev = useCallback(() => {
if (prevId) table.setRowSelection({ [prevId]: true })
}, [prevId, table])
const onNext = useCallback(() => {
if (nextId) table.setRowSelection({ [nextId]: true })
}, [nextId, table])
useEffect(() => {
const down = (e: KeyboardEvent) => {
if (!selectedRowKey) return
// REMINDER: prevent dropdown navigation inside of sheet to change row selection
const activeElement = document.activeElement
const isMenuActive = activeElement?.closest('[role="menu"]')
if (isMenuActive) return
if (e.key === 'ArrowUp') {
e.preventDefault()
onPrev()
}
if (e.key === 'ArrowDown') {
e.preventDefault()
onNext()
}
}
document.addEventListener('keydown', down)
return () => document.removeEventListener('keydown', down)
}, [selectedRowKey, onNext, onPrev])
return (
<div className="relative bg-sidebar">
<div className="flex items-center justify-between gap-2 pl-5 pr-2 py-1">
<h5 className={cn(titleClassName, 'truncate text-left')}>
{isLoading && !selectedRowKey ? <Skeleton className="h-7 w-36" /> : title}
</h5>
<div className="flex h-7 items-center gap-1">
<Tooltip>
<TooltipTrigger asChild>
<Button
size="tiny"
variant="text"
disabled={!prevId}
onClick={onPrev}
className="px-1"
icon={<ChevronUp />}
/>
</TooltipTrigger>
<TooltipContent>
<p>
Navigate <Kbd>↑</Kbd>
</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
size="tiny"
variant="text"
disabled={!nextId}
onClick={onNext}
className="px-1"
icon={<ChevronDown />}
/>
</TooltipTrigger>
<TooltipContent>
<p>
Navigate <Kbd>↓</Kbd>
</p>
</TooltipContent>
</Tooltip>
<Separator orientation="vertical" className="mx-1" />
<Button
size="tiny"
variant="text"
onClick={() => table.resetRowSelection()}
className="px-1"
icon={<X />}
/>
</div>
</div>
<Separator />
{children}
</div>
)
}
@@ -1,85 +0,0 @@
import { PanelLeftClose, PanelLeftOpen } from 'lucide-react'
import { ReactNode, useMemo } from 'react'
import { Button, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
import { formatCompactNumber } from './DataTable.utils'
import { DataTableFilterControlsDrawer } from './DataTableFilters/DataTableFilterControlsDrawer'
import { DataTableResetButton } from './DataTableResetButton'
import { DataTableViewOptions } from './DataTableViewOptions'
import { Kbd } from './primitives/Kbd'
import { useDataTable } from './providers/DataTableProvider'
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
import { useShortcut } from '@/state/shortcuts/useShortcut'
interface DataTableToolbarProps {
renderActions?: () => ReactNode
isFilterBarOpen: boolean
setIsFilterBarOpen: React.Dispatch<React.SetStateAction<boolean>>
}
export function DataTableToolbar({
renderActions,
isFilterBarOpen,
setIsFilterBarOpen,
}: DataTableToolbarProps) {
const { table, isLoading, columnFilters } = useDataTable()
const filters = table.getState().columnFilters
useShortcut(SHORTCUT_IDS.DATA_TABLE_TOGGLE_FILTERS, () => setIsFilterBarOpen((prev) => !prev))
const rows = useMemo(
() => ({
total: table.getCoreRowModel().rows.length,
filtered: table.getFilteredRowModel().rows.length,
}),
[isLoading, columnFilters]
)
return (
<div className="flex flex-wrap items-center justify-between gap-4">
<div className="flex flex-wrap items-center gap-2">
<Tooltip>
<TooltipTrigger asChild>
<Button
size="tiny"
variant="default"
icon={isFilterBarOpen ? <PanelLeftClose /> : <PanelLeftOpen />}
onClick={() => setIsFilterBarOpen((prev) => !prev)}
className="hidden sm:flex"
>
<span className="hidden md:block">{isFilterBarOpen ? 'Hide' : 'Show'} Controls</span>
</Button>
</TooltipTrigger>
<TooltipContent side="right">
<p>
Toggle controls with{' '}
<Kbd className="ml-1 text-muted-foreground group-hover:text-accent-foreground">
<span className="mr-1">⌘</span>
<span>B</span>
</Kbd>
</p>
</TooltipContent>
</Tooltip>
<div className="block sm:hidden">
<DataTableFilterControlsDrawer />
</div>
<div>
<p className="hidden text-sm text-muted-foreground sm:block">
<span className="font-mono font-medium">{formatCompactNumber(rows.filtered)}</span> of{' '}
<span className="font-mono font-medium">{formatCompactNumber(rows.total)}</span> row(s){' '}
<span className="sr-only sm:not-sr-only">filtered</span>
</p>
<p className="block text-sm text-muted-foreground sm:hidden">
<span className="font-mono font-medium">{formatCompactNumber(rows.filtered)}</span>{' '}
row(s)
</p>
</div>
</div>
<div className="ml-auto flex items-center gap-2">
{filters.length ? <DataTableResetButton /> : null}
{renderActions?.()}
<DataTableViewOptions />
</div>
</div>
)
}
@@ -1,17 +0,0 @@
import { TableCell, TableRow } from 'ui'
export function LiveRow({ colSpan }: { colSpan: number }) {
return (
<TableRow>
<TableCell className="relative w-(--header-level-size) min-w-(--header-level-size) max-w-(--header-level-size) border-b border-r border-t border-info">
<div className="flex items-center justify-center">
<span className="absolute inline-flex h-2.5 w-2.5 animate-ping rounded-full bg-brand opacity-75"></span>
<span className="relative inline-flex h-2.5 w-2.5 rounded-full bg-brand"></span>
</div>
</TableCell>
<TableCell colSpan={colSpan} className="border-b border-r border-t text-foreground">
Live Mode
</TableCell>
</TableRow>
)
}
@@ -1,17 +0,0 @@
import * as z from 'zod'
export const StringNumberOrNull = z
.string()
.transform((v) => (v === '' ? null : v))
.nullable()
.refine((value) => value === null || !isNaN(Number(value)), {
message: 'Invalid number',
})
.transform((value) => (value === null ? null : Number(value)))
/**
* [Joshen] After wrangling with RHF I think this is the easiest way to handle nullable number fields
* - Declare the field normally as you would in the zod form schema (e.g field: z.number().nullable())
* - In the InputField, add a form.register call `{...form.register('field_name', { setValueAs: setValueAsNullableNumber })}`
*/
export const setValueAsNullableNumber = (v: any) => (v === '' || v === null ? null : parseInt(v))
@@ -1,70 +0,0 @@
import { useQuery } from '@tanstack/react-query'
import { operations } from 'api-types'
import { analyticsKeys } from './keys'
import { get, handleError } from '@/data/fetchers'
import { UseCustomQueryOptions } from '@/types'
export type FunctionsReqStatsVariables = {
projectRef?: string
functionId?: string
interval?: operations['FunctionsLogsController_getRequestStats']['parameters']['query']['interval']
}
export type FunctionsReqStatsResponse = any
export async function getFunctionsReqStats(
{ projectRef, functionId, interval }: FunctionsReqStatsVariables,
signal?: AbortSignal
) {
if (!projectRef) {
throw new Error('projectRef is required')
}
if (!functionId) {
throw new Error('functionId is required')
}
if (!interval) {
throw new Error('interval is required')
}
const { data, error } = await get(
'/platform/projects/{ref}/analytics/endpoints/functions.req-stats',
{
params: {
path: {
ref: projectRef,
},
query: {
function_id: functionId,
interval,
},
},
signal,
}
)
if (error) handleError(error)
return data
}
export type FunctionsReqStatsData = Awaited<ReturnType<typeof getFunctionsReqStats>>
export type FunctionsReqStatsError = unknown
export const useFunctionsReqStatsQuery = <TData = FunctionsReqStatsData>(
{ projectRef, functionId, interval }: FunctionsReqStatsVariables,
{
enabled = true,
...options
}: UseCustomQueryOptions<FunctionsReqStatsData, FunctionsReqStatsError, TData> = {}
) =>
useQuery<FunctionsReqStatsData, FunctionsReqStatsError, TData>({
queryKey: analyticsKeys.functionsReqStats(projectRef, { functionId, interval }),
queryFn: ({ signal }) => getFunctionsReqStats({ projectRef, functionId, interval }, signal),
enabled:
enabled &&
typeof projectRef !== 'undefined' &&
typeof functionId !== 'undefined' &&
typeof interval !== 'undefined',
...options,
})
@@ -1,71 +0,0 @@
import { useQuery } from '@tanstack/react-query'
import { operations } from 'api-types'
import { analyticsKeys } from './keys'
import { get, handleError } from '@/data/fetchers'
import { UseCustomQueryOptions } from '@/types'
export type FunctionsResourceUsageVariables = {
projectRef?: string
functionId?: string
interval?: operations['FunctionsLogsController_getRequestStats']['parameters']['query']['interval']
}
export type FunctionsResourceUsageResponse = any
export async function getFunctionsResourceUsage(
{ projectRef, functionId, interval }: FunctionsResourceUsageVariables,
signal?: AbortSignal
) {
if (!projectRef) {
throw new Error('projectRef is required')
}
if (!functionId) {
throw new Error('functionId is required')
}
if (!interval) {
throw new Error('interval is required')
}
const { data, error } = await get(
'/platform/projects/{ref}/analytics/endpoints/functions.resource-usage',
{
params: {
path: {
ref: projectRef,
},
query: {
function_id: functionId,
interval,
},
},
signal,
}
)
if (error) handleError(error)
return data
}
export type FunctionsResourceUsageData = Awaited<ReturnType<typeof getFunctionsResourceUsage>>
export type FunctionsResourceUsageError = unknown
export const useFunctionsResourceUsageQuery = <TData = FunctionsResourceUsageData>(
{ projectRef, functionId, interval }: FunctionsResourceUsageVariables,
{
enabled = true,
...options
}: UseCustomQueryOptions<FunctionsResourceUsageData, FunctionsResourceUsageError, TData> = {}
) =>
useQuery<FunctionsResourceUsageData, FunctionsResourceUsageError, TData>({
queryKey: analyticsKeys.functionsResourceUsage(projectRef, { functionId, interval }),
queryFn: ({ signal }) =>
getFunctionsResourceUsage({ projectRef, functionId, interval }, signal),
enabled:
enabled &&
typeof projectRef !== 'undefined' &&
typeof functionId !== 'undefined' &&
typeof interval !== 'undefined',
...options,
})
@@ -1,69 +0,0 @@
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { toast } from 'sonner'
import { apiKeysKeys } from './keys'
import { handleError, patch } from '@/data/fetchers'
import type { ResponseError, UseCustomMutationOptions } from '@/types'
export interface UpdateAPIKeybyIdVariables {
projectRef?: string
id?: string
description?: string
}
export async function updateAPIKeysById(
{ projectRef, id, description }: UpdateAPIKeybyIdVariables,
signal?: AbortSignal
) {
if (typeof projectRef === 'undefined') throw new Error('projectRef is required')
if (typeof id === 'undefined') throw new Error('API key ID is required')
const { data, error } = await patch('/v1/projects/{ref}/api-keys/{id}', {
params: {
path: { ref: projectRef, id },
query: { reveal: false },
},
body: {
description: description,
},
signal,
})
if (error) {
handleError(error)
}
return data
}
export type ResourceUpdateData = Awaited<ReturnType<typeof updateAPIKeysById>>
export const useResourceUpdateMutation = ({
onSuccess,
onError,
...options
}: Omit<
UseCustomMutationOptions<ResourceUpdateData, ResponseError, UpdateAPIKeybyIdVariables>,
'mutationFn'
> = {}) => {
const queryClient = useQueryClient()
return useMutation<ResourceUpdateData, ResponseError, UpdateAPIKeybyIdVariables>({
mutationFn: (vars) => updateAPIKeysById(vars),
async onSuccess(data, variables, context) {
const { projectRef } = variables
await queryClient.invalidateQueries({ queryKey: apiKeysKeys.list(projectRef) })
await onSuccess?.(data, variables, context)
},
async onError(data, variables, context) {
if (onError === undefined) {
toast.error(`Failed to mutate: ${data.message}`)
} else {
onError(data, variables, context)
}
},
...options,
})
}
@@ -1,8 +0,0 @@
// https://www.postgresql.org/docs/current/catalog-pg-constraint.html
export enum FOREIGN_KEY_CASCADE_ACTION {
NO_ACTION = 'a',
RESTRICT = 'r',
CASCADE = 'c',
SET_NULL = 'n',
SET_DEFAULT = 'd',
}
@@ -1,50 +0,0 @@
import { getEntityDefinitionsSql } from '@supabase/pg-meta'
import { useQuery } from '@tanstack/react-query'
import { databaseKeys } from './keys'
import { executeSql } from '@/data/sql/execute-sql-mutation'
import { ResponseError, UseCustomQueryOptions } from '@/types'
export type EntityDefinitionsVariables = {
limit?: number
projectRef?: string
connectionString?: string | null
schemas: string[]
}
export async function getEntityDefinitions(
{ projectRef, connectionString, schemas, limit }: EntityDefinitionsVariables,
signal?: AbortSignal
) {
const sql = getEntityDefinitionsSql({ schemas, limit })
const { result } = await executeSql(
{
projectRef,
connectionString,
sql,
queryKey: ['entity-definitions', schemas],
},
signal
)
return result[0].data.definitions
}
type EntityDefinition = { id: number; sql: string }
export type EntityDefinitionsData = EntityDefinition[]
export type EntityDefinitionsError = ResponseError
export const useEntityDefinitionsQuery = <TData = EntityDefinitionsData>(
{ projectRef, connectionString, schemas, limit }: EntityDefinitionsVariables,
{
enabled = true,
...options
}: UseCustomQueryOptions<EntityDefinitionsData, EntityDefinitionsError, TData> = {}
) =>
useQuery<EntityDefinitionsData, EntityDefinitionsError, TData>({
queryKey: databaseKeys.entityDefinitions(projectRef, schemas),
queryFn: ({ signal }) =>
getEntityDefinitions({ projectRef, connectionString, schemas, limit }, signal),
enabled: enabled && typeof projectRef !== 'undefined' && schemas.length > 0,
...options,
})
@@ -1,40 +0,0 @@
import { useQuery } from '@tanstack/react-query'
import { databaseKeys } from './keys'
import { get, handleError } from '@/data/fetchers'
import type { ResponseError, UseCustomQueryOptions } from '@/types'
export type PgbouncerStatusVariables = {
projectRef?: string
}
export async function getPgbouncerStatus(
{ projectRef }: PgbouncerStatusVariables,
signal?: AbortSignal
) {
if (!projectRef) throw new Error('projectRef is required')
const { data, error } = await get('/platform/projects/{ref}/config/pgbouncer/status', {
params: { path: { ref: projectRef } },
signal,
})
if (error) handleError(error)
return data
}
export type PgbouncerStatusData = Awaited<ReturnType<typeof getPgbouncerStatus>>
export type PgbouncerStatusError = ResponseError
export const usePgbouncerStatusQuery = <TData = PgbouncerStatusData>(
{ projectRef }: PgbouncerStatusVariables,
{
enabled = true,
...options
}: UseCustomQueryOptions<PgbouncerStatusData, PgbouncerStatusError, TData> = {}
) =>
useQuery<PgbouncerStatusData, PgbouncerStatusError, TData>({
queryKey: databaseKeys.pgbouncerStatus(projectRef),
queryFn: ({ signal }) => getPgbouncerStatus({ projectRef }, signal),
enabled: enabled && typeof projectRef !== 'undefined',
...options,
})
-11
View File
@@ -1,11 +0,0 @@
/* Utils for use in queries / mutations */
export const DEFAULT_PAGE_SIZE = 100
export function getPagination(page?: number, size: number = DEFAULT_PAGE_SIZE) {
const limit = size
const from = page ? page * limit : 0
const to = page ? from + size - 1 : size - 1
return { from, to }
}