fix datepicker helper state

This commit is contained in:
Jordi Enric committed 2025-06-16 16:15:43 +02:00
1 parent f6d8f52e96
commit 4150bbe2cc
7 files changed
+81 -79

No files matched your search

@@ -12,18 +12,21 @@ interface ReportsDatetimeHelper extends DatetimeHelper {
export const REPORTS_DATEPICKER_HELPERS: ReportsDatetimeHelper[] = [
{
id: 'last_10_minutes',
text: 'Last 10 minutes',
calcFrom: () => dayjs().subtract(10, 'minute').toISOString(),
calcTo: () => dayjs().toISOString(),
availableIn: ['team', 'enterprise'],
},
{
id: 'last_30_minutes',
text: 'Last 30 minutes',
calcFrom: () => dayjs().subtract(30, 'minute').toISOString(),
calcTo: () => dayjs().toISOString(),
availableIn: ['team', 'enterprise'],
},
{
id: 'last_60_minutes',
text: 'Last 60 minutes',
calcFrom: () => dayjs().subtract(1, 'hour').startOf('day').toISOString(),
calcTo: () => dayjs().toISOString(),
@@ -31,30 +34,35 @@ export const REPORTS_DATEPICKER_HELPERS: ReportsDatetimeHelper[] = [
availableIn: ['free', 'pro', 'team', 'enterprise'],
},
{
id: 'last_3_hours',
text: 'Last 3 hours',
calcFrom: () => dayjs().subtract(3, 'hour').startOf('day').toISOString(),
calcTo: () => dayjs().toISOString(),
availableIn: ['free', 'pro', 'team', 'enterprise'],
},
{
id: 'last_24_hours',
text: 'Last 24 hours',
calcFrom: () => dayjs().subtract(1, 'day').startOf('day').toISOString(),
calcTo: () => dayjs().toISOString(),
availableIn: ['free', 'pro', 'team', 'enterprise'],
},
{
id: 'last_7_days',
text: 'Last 7 days',
calcFrom: () => dayjs().subtract(7, 'day').startOf('day').toISOString(),
calcTo: () => '',
availableIn: ['pro', 'team', 'enterprise'],
},
{
id: 'last_14_days',
text: 'Last 14 days',
calcFrom: () => dayjs().subtract(14, 'day').startOf('day').toISOString(),
calcTo: () => '',
availableIn: ['team', 'enterprise'],
},
{
id: 'last_28_days',
text: 'Last 28 days',
calcFrom: () => dayjs().subtract(28, 'day').startOf('day').toISOString(),
calcTo: () => '',
@@ -11,6 +11,7 @@ import { Button, PopoverContent_Shadcn_, PopoverTrigger_Shadcn_, Popover_Shadcn_
import { LOGS_LARGE_DATE_RANGE_DAYS_THRESHOLD } from './Logs.constants'
import type { DatetimeHelper } from './Logs.types'
import { copyToClipboard } from 'lib/helpers'
import { useLogsUrlState } from 'hooks/analytics/useLogsUrlState'
export type DatePickerValue = {
to: string
@@ -20,22 +21,32 @@ export type DatePickerValue = {
}
interface Props {
value: DatePickerValue
onSubmit: (args: DatePickerValue) => void
helpers: DatetimeHelper[]
}
export const LogsDatePicker = ({ onSubmit, helpers, value }: PropsWithChildren<Props>) => {
export const LogsDatePicker = ({ helpers }: PropsWithChildren<Props>) => {
const { timestampStart, timestampEnd, setTimeRange, selectedHelperId, setSelectedHelperId } =
useLogsUrlState()
const [open, setOpen] = useState(false)
useEffect(() => {
if (selectedHelperId) {
const helper = helpers.find((h) => h.id === selectedHelperId)
if (helper) {
setTimeRange(helper.calcFrom(), helper.calcTo())
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedHelperId])
// Reset the state when the popover closes
useEffect(() => {
if (!open) {
setStartDate(value.from ? new Date(value.from) : null)
setEndDate(value.to ? new Date(value.to) : new Date())
setStartDate(timestampStart ? new Date(timestampStart) : null)
setEndDate(timestampEnd ? new Date(timestampEnd) : new Date())
const fromDate = value.from ? new Date(value.from) : null
const toDate = value.to ? new Date(value.to) : null
const fromDate = timestampStart ? new Date(timestampStart) : null
const toDate = timestampEnd ? new Date(timestampEnd) : null
setStartTime({
HH: fromDate?.getHours().toString().padStart(2, '0') || '00',
@@ -54,24 +65,23 @@ export const LogsDatePicker = ({ onSubmit, helpers, value }: PropsWithChildren<P
ss: toDate?.getSeconds().toString().padStart(2, '0') || nowSS,
})
}
}, [open, value])
}, [open, timestampStart, timestampEnd])
const handleHelperChange = (newValue: string) => {
const selectedHelper = helpers.find((h) => h.text === newValue)
if (onSubmit && selectedHelper) {
onSubmit({
to: selectedHelper.calcTo(),
from: selectedHelper.calcFrom(),
isHelper: true,
text: selectedHelper.text,
})
if (selectedHelper) {
setTimeRange(selectedHelper.calcFrom(), selectedHelper.calcTo())
setSelectedHelperId(selectedHelper.id)
}
setOpen(false)
}
const [startDate, setStartDate] = useState<Date | null>(value.from ? new Date(value.from) : null)
const [endDate, setEndDate] = useState<Date | null>(value.to ? new Date(value.to) : new Date())
const [startDate, setStartDate] = useState<Date | null>(
timestampStart ? new Date(timestampStart) : null
)
const [endDate, setEndDate] = useState<Date | null>(
timestampEnd ? new Date(timestampEnd) : new Date()
)
const [startTime, setStartTime] = useState({
HH: startDate?.getHours().toString() || '00',
@@ -99,12 +109,8 @@ export const LogsDatePicker = ({ onSubmit, helpers, value }: PropsWithChildren<P
const finalFrom = new Date(from.setHours(+startTime.HH, +startTime.mm, +startTime.ss))
const finalTo = new Date(to.setHours(+endTime.HH, +endTime.mm, +endTime.ss))
onSubmit({
from: finalFrom.toISOString(),
to: finalTo.toISOString(),
isHelper: false,
})
setTimeRange(finalFrom.toISOString(), finalTo.toISOString())
setSelectedHelperId('')
setOpen(false)
}
@@ -209,13 +215,17 @@ export const LogsDatePicker = ({ onSubmit, helpers, value }: PropsWithChildren<P
Math.abs(dayjs(startDate).diff(dayjs(endDate), 'days')) >
LOGS_LARGE_DATE_RANGE_DAYS_THRESHOLD - 1
const selectedHelper = helpers.find((h) => h.id === selectedHelperId)
return (
<Popover_Shadcn_ open={open} onOpenChange={setOpen}>
<PopoverTrigger_Shadcn_ asChild>
<Button type="default" icon={<Clock size={12} />}>
{value.isHelper
? value.text
: `${dayjs(value.from).format('DD MMM, HH:mm')} - ${dayjs(value.to || new Date()).format('DD MMM, HH:mm')}`}
{selectedHelper
? selectedHelper.text
: timestampStart && timestampEnd
? `${dayjs(timestampStart).format('DD MMM, HH:mm')} - ${dayjs(timestampEnd).format('DD MMM, HH:mm')}`
: helpers[0]?.text}
</Button>
</PopoverTrigger_Shadcn_>
<PopoverContent_Shadcn_
@@ -226,7 +236,10 @@ export const LogsDatePicker = ({ onSubmit, helpers, value }: PropsWithChildren<P
>
<RadioGroup
onValueChange={handleHelperChange}
value={value.isHelper ? value.text : ''}
value={(() => {
const selected = helpers.find((h) => h.id === selectedHelperId)
return selected ? selected.text : ''
})()}
className="border-r p-2 flex flex-col gap-px"
>
{helpers.map((helper) => (
@@ -672,42 +672,50 @@ export const LOGS_TAILWIND_CLASSES = {
export const PREVIEWER_DATEPICKER_HELPERS: DatetimeHelper[] = [
{
id: 'last_15_minutes',
text: 'Last 15 minutes',
calcFrom: () => dayjs().subtract(15, 'minute').toISOString(),
calcTo: () => '',
},
{
id: 'last_30_minutes',
text: 'Last 30 minutes',
calcFrom: () => dayjs().subtract(30, 'minute').toISOString(),
calcTo: () => '',
},
{
id: 'last_hour',
text: 'Last hour',
calcFrom: () => dayjs().subtract(1, 'hour').toISOString(),
calcTo: () => '',
default: true,
},
{
id: 'last_3_hours',
text: 'Last 3 hours',
calcFrom: () => dayjs().subtract(3, 'hour').toISOString(),
calcTo: () => '',
},
{
id: 'last_24_hours',
text: 'Last 24 hours',
calcFrom: () => dayjs().subtract(1, 'day').toISOString(),
calcTo: () => '',
},
{
id: 'last_2_days',
text: 'Last 2 days',
calcFrom: () => dayjs().subtract(2, 'day').toISOString(),
calcTo: () => '',
},
{
id: 'last_3_days',
text: 'Last 3 days',
calcFrom: () => dayjs().subtract(3, 'day').toISOString(),
calcTo: () => '',
},
{
id: 'last_5_days',
text: 'Last 5 days',
calcFrom: () => dayjs().subtract(5, 'day').toISOString(),
calcTo: () => '',
@@ -715,27 +723,32 @@ export const PREVIEWER_DATEPICKER_HELPERS: DatetimeHelper[] = [
]
export const EXPLORER_DATEPICKER_HELPERS: DatetimeHelper[] = [
{
id: 'last_hour',
text: 'Last hour',
calcFrom: () => dayjs().subtract(1, 'hour').toISOString(),
calcTo: () => '',
default: true,
},
{
id: 'last_3_hours',
text: 'Last 3 hours',
calcFrom: () => dayjs().subtract(3, 'hour').toISOString(),
calcTo: () => '',
},
{
id: 'last_24_hours',
text: 'Last 24 hours',
calcFrom: () => dayjs().subtract(1, 'day').toISOString(),
calcTo: () => '',
},
{
id: 'last_3_days',
text: 'Last 3 days',
calcFrom: () => dayjs().subtract(3, 'day').toISOString(),
calcTo: () => '',
},
{
id: 'last_7_days',
text: 'Last 7 days',
calcFrom: () => dayjs().subtract(7, 'day').toISOString(),
calcTo: () => '',
@@ -138,6 +138,7 @@ export type Override = {
}
export interface DatetimeHelper {
id: string
text: string
calcTo: () => string
calcFrom: () => string
@@ -27,23 +27,21 @@ import {
LOGS_SOURCE_DESCRIPTION,
LogsTableName,
} from './Logs.constants'
import { DatePickerValue, LogsDatePicker } from './Logs.DatePickers'
import { LogsDatePicker } from './Logs.DatePickers'
import { LogsWarning, LogTemplate, WarehouseCollection } from './Logs.types'
import { WarehouseQueryTemplate } from './Warehouse.utils'
import { useLogsUrlState } from 'hooks/analytics/useLogsUrlState'
export type SourceType = 'logs' | 'warehouse'
export interface LogsQueryPanelProps {
templates?: LogTemplate[]
warehouseTemplates?: WarehouseQueryTemplate[]
defaultFrom: string
defaultTo: string
warnings: LogsWarning[]
warehouseCollections: WarehouseCollection[]
dataSource: SourceType
onSelectTemplate: (template: LogTemplate) => void
onSelectWarehouseTemplate: (template: WarehouseQueryTemplate) => void
onSelectSource: (source: string) => void
onDateChange: (value: DatePickerValue) => void
onDataSourceChange: (sourceType: SourceType) => void
}
@@ -59,18 +57,16 @@ function DropdownMenuItemContent({ name, desc }: { name: ReactNode; desc?: strin
const LogsQueryPanel = ({
templates = [],
warehouseTemplates = [],
defaultFrom,
defaultTo,
warnings,
warehouseCollections,
dataSource,
onSelectTemplate,
onSelectWarehouseTemplate,
onSelectSource,
onDateChange,
onDataSourceChange,
}: LogsQueryPanelProps) => {
const [showReference, setShowReference] = useState(false)
const { timestampStart, timestampEnd, setTimeRange } = useLogsUrlState()
const {
projectAuthAll: authEnabled,
@@ -91,27 +87,6 @@ const LogsQueryPanel = ({
})
.map(([, value]) => value)
function getDefaultDatePickerValue() {
if (defaultFrom && defaultTo) {
return {
to: defaultTo,
from: defaultFrom,
text: `${dayjs(defaultFrom).format('DD MMM, HH:mm')} - ${dayjs(defaultTo).format('DD MMM, HH:mm')}`,
isHelper: false,
}
}
return {
to: EXPLORER_DATEPICKER_HELPERS[0].calcTo(),
from: EXPLORER_DATEPICKER_HELPERS[0].calcFrom(),
text: EXPLORER_DATEPICKER_HELPERS[0].text,
isHelper: true,
}
}
const [selectedDatePickerValue, setSelectedDatePickerValue] = useState<DatePickerValue>(
getDefaultDatePickerValue()
)
return (
<div className="border-b bg-surface-100">
<div className="flex w-full items-center justify-between px-4 md:px-5 py-2 overflow-x-scroll no-scrollbar">
@@ -222,16 +197,7 @@ const LogsQueryPanel = ({
</DropdownMenu>
)}
{dataSource === 'logs' && (
<LogsDatePicker
value={selectedDatePickerValue}
onSubmit={(value) => {
setSelectedDatePickerValue(value)
onDateChange(value)
}}
helpers={EXPLORER_DATEPICKER_HELPERS}
/>
)}
{dataSource === 'logs' && <LogsDatePicker helpers={EXPLORER_DATEPICKER_HELPERS} />}
<div
data-testid="log-explorer-warnings"
@@ -8,6 +8,7 @@ interface LogsUrlState {
timestampEnd: string
selectedLogId: string | null
filters: Filters
selectedHelperId: string
}
const defaultState: LogsUrlState = {
@@ -16,6 +17,7 @@ const defaultState: LogsUrlState = {
timestampEnd: '',
selectedLogId: null,
filters: {},
selectedHelperId: '',
}
const stringWithDefault = (defaultValue: string) =>
@@ -59,6 +61,10 @@ export function useLogsUrlState() {
'f',
jsonWithDefault<Filters>(defaultState.filters)
)
const [selectedHelperIdValue, setSelectedHelperIdValue] = useQueryState(
'helper',
stringWithDefault(defaultState.selectedHelperId)
)
// Ensure we never return null values
const search = searchValue || defaultState.search
@@ -66,6 +72,7 @@ export function useLogsUrlState() {
const timestampEnd = timestampEndValue || defaultState.timestampEnd
const selectedLogId = selectedLogIdValue || null
const filters = filtersValue || defaultState.filters
const selectedHelperId = selectedHelperIdValue || defaultState.selectedHelperId
const setSearch = (value: string) => {
setSearchValue(value || defaultState.search)
@@ -88,12 +95,17 @@ export function useLogsUrlState() {
setFilters({ ...filters, [key]: value })
}
const setSelectedHelperId = (value: string) => {
setSelectedHelperIdValue(value || defaultState.selectedHelperId)
}
const reset = () => {
setSearch(defaultState.search)
setTimestampStartValue(defaultState.timestampStart)
setTimestampEndValue(defaultState.timestampEnd)
setSelectedLogIdValue('')
setFiltersValue(defaultState.filters)
setSelectedHelperIdValue(defaultState.selectedHelperId)
}
return {
@@ -103,6 +115,7 @@ export function useLogsUrlState() {
timestampEnd,
selectedLogId,
filters,
selectedHelperId,
// Setters
setSearch,
@@ -110,6 +123,7 @@ export function useLogsUrlState() {
setSelectedLogId,
setFilters,
updateFilter,
setSelectedHelperId,
reset,
}
}
@@ -313,16 +313,6 @@ export const LogsExplorerPage: NextPageWithLayout = () => {
setSaveModalOpen(!saveModalOpen)
}
const handleDateChange = ({ to, from }: DatePickerToFrom) => {
const shouldShowUpgradePrompt = maybeShowUpgradePrompt(from, organization?.plan?.id)
if (shouldShowUpgradePrompt) {
setShowUpgradePrompt(!showUpgradePrompt)
} else {
setTimeRange(from || '', to || '')
}
}
useEffect(() => {
if (warehouseError) toast.error(warehouseError.message)
}, [warehouseError])
@@ -379,9 +369,6 @@ export const LogsExplorerPage: NextPageWithLayout = () => {
>
<ResizablePanel collapsible minSize={5}>
<LogsQueryPanel
defaultFrom={timestampStart || ''}
defaultTo={timestampEnd || ''}
onDateChange={handleDateChange}
onSelectSource={handleInsertSource}
templates={TEMPLATES.filter((template) => template.mode === 'custom')}
warehouseCollections={warehouseCollections || []}