mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
312 lines
10 KiB
TypeScript
312 lines
10 KiB
TypeScript
import dayjs from 'dayjs'
|
|
import { createParser, useQueryState } from 'nuqs'
|
|
import { useCallback, useMemo, useState } from 'react'
|
|
|
|
import {
|
|
REPORT_DATERANGE_HELPER_LABELS,
|
|
REPORTS_DATEPICKER_HELPERS,
|
|
ReportsDatetimeHelper,
|
|
} from '@/components/interfaces/Reports/Reports.constants'
|
|
import { DatePickerValue } from '@/components/interfaces/Settings/Logs/Logs.DatePickers'
|
|
import { maybeShowUpgradePromptIfNotEntitled } from '@/components/interfaces/Settings/Logs/Logs.utils'
|
|
import { AnalyticsInterval } from '@/data/analytics/constants'
|
|
import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements'
|
|
import { useCurrentOrgPlan } from '@/hooks/misc/useCurrentOrgPlan'
|
|
|
|
export const getIntervalGranularity = (from: string, to: string): AnalyticsInterval => {
|
|
const diffInDays = dayjs(to).diff(from, 'day', true)
|
|
const diffInHours = dayjs(to).diff(from, 'hour', true)
|
|
|
|
if (diffInHours <= 1) return '1m'
|
|
if (diffInHours <= 12) return '2m'
|
|
if (diffInDays <= 7) return '1h'
|
|
return '1d'
|
|
}
|
|
|
|
export const resolveHelperFromUrl = (
|
|
isHelper: boolean,
|
|
helperText: string
|
|
): ReportsDatetimeHelper | undefined => {
|
|
if (!isHelper || helperText === '') return undefined
|
|
return REPORTS_DATEPICKER_HELPERS.find((helper) => helper.text === helperText)
|
|
}
|
|
|
|
const resolveHelperFromArgument = (
|
|
defaultHelper: REPORT_DATERANGE_HELPER_LABELS | string | ReportsDatetimeHelper
|
|
): ReportsDatetimeHelper | undefined => {
|
|
if (typeof defaultHelper === 'string') {
|
|
return REPORTS_DATEPICKER_HELPERS.find((helper) => helper.text === defaultHelper)
|
|
}
|
|
if (defaultHelper && typeof defaultHelper === 'object' && 'text' in defaultHelper) {
|
|
return defaultHelper
|
|
}
|
|
return undefined
|
|
}
|
|
|
|
export const DATERANGE_LIMITS: { [key: string]: number } = {
|
|
free: 1,
|
|
pro: 7,
|
|
team: 28,
|
|
enterprise: 90,
|
|
platform: 1,
|
|
}
|
|
|
|
export interface ReportDateRange {
|
|
period_start: { date: string; time_period: string }
|
|
period_end: { date: string; time_period: string }
|
|
interval: AnalyticsInterval
|
|
}
|
|
|
|
// Create parsers for individual URL parameters
|
|
const stringWithDefault = (defaultValue: string) =>
|
|
createParser({
|
|
parse: (v) => v ?? defaultValue,
|
|
serialize: (v) => v || '',
|
|
})
|
|
|
|
const booleanWithDefault = (defaultValue: boolean) =>
|
|
createParser({
|
|
parse: (v) => {
|
|
if (v === 'true') return true
|
|
if (v === 'false') return false
|
|
return defaultValue
|
|
},
|
|
serialize: (v) => String(v),
|
|
})
|
|
|
|
export const useReportDateRange = (
|
|
defaultHelper:
|
|
| REPORT_DATERANGE_HELPER_LABELS
|
|
| string
|
|
| ReportsDatetimeHelper = REPORT_DATERANGE_HELPER_LABELS.LAST_60_MINUTES
|
|
) => {
|
|
const { plan: orgPlan, isLoading: isOrgPlanLoading } = useCurrentOrgPlan()
|
|
const { getEntitlementNumericValue } = useCheckEntitlements('log.retention_days')
|
|
const entitledToAuditLogDays = getEntitlementNumericValue()
|
|
const [showUpgradePrompt, setShowUpgradePrompt] = useState(false)
|
|
|
|
// Get filtered date picker helpers based on organization plan
|
|
const datePickerHelpers: ReportsDatetimeHelper[] = useMemo(
|
|
() =>
|
|
REPORTS_DATEPICKER_HELPERS.map((helper) => ({
|
|
...helper,
|
|
disabled: false,
|
|
})),
|
|
[]
|
|
)
|
|
|
|
// Use nuqs for URL state management with individual parameters (empty defaults)
|
|
const [timestampStartValue, setTimestampStart] = useQueryState('its', stringWithDefault(''))
|
|
const [timestampEndValue, setTimestampEnd] = useQueryState('ite', stringWithDefault(''))
|
|
const [isHelperValue, setIsHelper] = useQueryState('isHelper', booleanWithDefault(false))
|
|
const [helperTextValue, setHelperText] = useQueryState('helperText', stringWithDefault(''))
|
|
|
|
const hasAccessToHelper = useCallback(
|
|
(helper: ReportsDatetimeHelper) => {
|
|
if (!entitledToAuditLogDays) return true
|
|
const days = Math.abs(dayjs().diff(dayjs(helper.calcFrom()), 'day'))
|
|
return days <= entitledToAuditLogDays
|
|
},
|
|
[entitledToAuditLogDays]
|
|
)
|
|
|
|
const getDefaultHelper = useCallback(() => {
|
|
const targetHelper =
|
|
resolveHelperFromUrl(Boolean(isHelperValue), helperTextValue ?? '') ??
|
|
resolveHelperFromArgument(defaultHelper)
|
|
|
|
// Check if the target helper is available for the current entitlement
|
|
if (targetHelper && hasAccessToHelper(targetHelper)) {
|
|
return {
|
|
start: targetHelper.calcFrom(),
|
|
end: targetHelper.calcTo(),
|
|
helper: { isHelper: true, text: targetHelper.text },
|
|
}
|
|
}
|
|
|
|
// Fallback: look for default helper marked in REPORTS_DATEPICKER_HELPERS
|
|
const fallbackHelper = REPORTS_DATEPICKER_HELPERS.find(
|
|
(helper) => helper.default && hasAccessToHelper(helper)
|
|
)
|
|
|
|
if (fallbackHelper) {
|
|
return {
|
|
start: fallbackHelper.calcFrom(),
|
|
end: fallbackHelper.calcTo(),
|
|
helper: { isHelper: true, text: fallbackHelper.text },
|
|
}
|
|
}
|
|
|
|
// Final fallback: use first available helper
|
|
const firstAvailable = REPORTS_DATEPICKER_HELPERS.find((helper) => hasAccessToHelper(helper))
|
|
|
|
if (firstAvailable) {
|
|
return {
|
|
start: firstAvailable.calcFrom(),
|
|
end: firstAvailable.calcTo(),
|
|
helper: { isHelper: true, text: firstAvailable.text },
|
|
}
|
|
}
|
|
|
|
// Ultimate fallback to manual date calculation (should rarely happen)
|
|
const defaultStart = dayjs().subtract(1, 'hour').toISOString()
|
|
const defaultEnd = dayjs().toISOString()
|
|
return {
|
|
start: defaultStart,
|
|
end: defaultEnd,
|
|
helper: { isHelper: false },
|
|
}
|
|
}, [defaultHelper, hasAccessToHelper, helperTextValue, isHelperValue])
|
|
|
|
// Get current effective values (from URL or defaults, but don't set URL)
|
|
const timestampStart = useMemo(() => {
|
|
if (timestampStartValue) {
|
|
// Validate that stored date is reasonable
|
|
const storedFrom = dayjs(timestampStartValue)
|
|
const now = dayjs()
|
|
if (
|
|
storedFrom.isValid() &&
|
|
storedFrom.isAfter(now.subtract(90, 'day')) &&
|
|
storedFrom.isBefore(now.add(1, 'day')) &&
|
|
!maybeShowUpgradePromptIfNotEntitled(timestampStartValue, entitledToAuditLogDays)
|
|
) {
|
|
return timestampStartValue
|
|
}
|
|
}
|
|
// Return default without setting URL
|
|
return getDefaultHelper().start
|
|
}, [timestampStartValue, getDefaultHelper, entitledToAuditLogDays])
|
|
|
|
const timestampEnd = useMemo(() => {
|
|
if (timestampEndValue) {
|
|
// Validate that stored date is reasonable
|
|
const storedTo = dayjs(timestampEndValue)
|
|
const now = dayjs()
|
|
if (
|
|
storedTo.isValid() &&
|
|
storedTo.isAfter(now.subtract(90, 'day')) &&
|
|
storedTo.isBefore(now.add(1, 'day'))
|
|
) {
|
|
return timestampEndValue
|
|
}
|
|
}
|
|
// Return default without setting URL
|
|
return getDefaultHelper().end
|
|
}, [timestampEndValue, getDefaultHelper])
|
|
|
|
// For helper state, use URL values or defaults from helper when no URL params exist
|
|
const isHelper = useMemo(() => {
|
|
if (timestampStartValue && timestampEndValue) {
|
|
return Boolean(isHelperValue)
|
|
}
|
|
// If no URL params, get default from helper
|
|
return getDefaultHelper().helper.isHelper
|
|
}, [timestampStartValue, timestampEndValue, isHelperValue, getDefaultHelper])
|
|
|
|
const helperText = useMemo(() => {
|
|
if (timestampStartValue && timestampEndValue) {
|
|
return helperTextValue || undefined
|
|
}
|
|
// If no URL params, get default from helper
|
|
return getDefaultHelper().helper.text
|
|
}, [timestampStartValue, timestampEndValue, helperTextValue, getDefaultHelper])
|
|
|
|
// Derive selectedDateRange from current values
|
|
const selectedDateRange: ReportDateRange = useMemo(
|
|
() => ({
|
|
period_start: { date: timestampStart, time_period: '1d' },
|
|
period_end: { date: timestampEnd, time_period: 'today' },
|
|
interval: getIntervalGranularity(timestampStart, timestampEnd),
|
|
}),
|
|
[timestampStart, timestampEnd]
|
|
)
|
|
|
|
const updateDateRange = useCallback(
|
|
(from: string, to: string) => {
|
|
setTimestampStart(from)
|
|
setTimestampEnd(to)
|
|
setIsHelper(false)
|
|
setHelperText('')
|
|
},
|
|
[setTimestampStart, setTimestampEnd, setIsHelper, setHelperText]
|
|
)
|
|
|
|
// For backward compatibility, provide a setter that updates the derived state
|
|
const setSelectedDateRange = useCallback(
|
|
(newRange: ReportDateRange) => {
|
|
setTimestampStart(newRange.period_start.date)
|
|
setTimestampEnd(newRange.period_end.date)
|
|
// When setting directly, assume it's not from a helper
|
|
setIsHelper(false)
|
|
setHelperText('')
|
|
},
|
|
[setTimestampStart, setTimestampEnd, setIsHelper, setHelperText]
|
|
)
|
|
|
|
// Convert to DatePickerValue format for LogsDatePicker
|
|
const datePickerValue: DatePickerValue = useMemo(
|
|
() => ({
|
|
from: timestampStart,
|
|
to: timestampEnd,
|
|
isHelper,
|
|
text: helperText,
|
|
}),
|
|
[timestampStart, timestampEnd, isHelper, helperText]
|
|
)
|
|
|
|
const handleDatePickerChange = (values: DatePickerValue) => {
|
|
const shouldShowUpgradePrompt = maybeShowUpgradePromptIfNotEntitled(
|
|
values.from,
|
|
entitledToAuditLogDays
|
|
)
|
|
if (shouldShowUpgradePrompt) {
|
|
setShowUpgradePrompt(true)
|
|
return true
|
|
} else {
|
|
if (values.from && values.to) {
|
|
setTimestampStart(values.from)
|
|
setTimestampEnd(values.to)
|
|
setIsHelper(values.isHelper || false)
|
|
setHelperText(values.text || '')
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
|
|
return {
|
|
selectedDateRange,
|
|
setSelectedDateRange,
|
|
updateDateRange,
|
|
datePickerValue,
|
|
datePickerHelpers,
|
|
isOrgPlanLoading,
|
|
orgPlan,
|
|
showUpgradePrompt,
|
|
setShowUpgradePrompt,
|
|
handleDatePickerChange,
|
|
}
|
|
}
|
|
|
|
export function useRefreshHandler(
|
|
datePickerValue: DatePickerValue,
|
|
datePickerHelpers: ReportsDatetimeHelper[],
|
|
handleDatePickerChange: (value: DatePickerValue) => boolean | void,
|
|
refresh: () => void | Promise<void>
|
|
): () => void | Promise<void> {
|
|
return useCallback(async () => {
|
|
if (datePickerValue.isHelper && datePickerValue.text) {
|
|
const selectedHelper = datePickerHelpers.find((h) => h.text === datePickerValue.text)
|
|
if (selectedHelper) {
|
|
handleDatePickerChange({
|
|
from: selectedHelper.calcFrom(),
|
|
to: selectedHelper.calcTo(),
|
|
isHelper: true,
|
|
text: selectedHelper.text,
|
|
})
|
|
}
|
|
}
|
|
|
|
await refresh()
|
|
}, [datePickerValue, datePickerHelpers, handleDatePickerChange, refresh])
|
|
}
|