Skip to content

Commit

Permalink
feat: start hooking up time controls
Browse files Browse the repository at this point in the history
  • Loading branch information
wesbillman committed Sep 15, 2023
1 parent 9d67c9c commit 09af837
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 128 deletions.
2 changes: 1 addition & 1 deletion backend/controller/console.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ func eventsQueryProtoToDAL(pb *pbconsole.EventsQuery) ([]dal.EventFilter, error)
if filter.Time.OlderThan != nil {
olderThan = filter.Time.OlderThan.AsTime()
}
query = append(query, dal.FilterTimeRange(newerThan, olderThan))
query = append(query, dal.FilterTimeRange(olderThan, newerThan))

case *pbconsole.EventsQuery_Filter_Id:
var lowerThan, higherThan int64
Expand Down
45 changes: 17 additions & 28 deletions console/client/src/features/timeline/Timeline.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { Timestamp } from '@bufbuild/protobuf'
import React from 'react'
import { useClient } from '../../hooks/use-client.ts'
import { ConsoleService } from '../../protos/xyz/block/ftl/v1/console/console_connect.ts'
import { Event } from '../../protos/xyz/block/ftl/v1/console/console_pb.ts'
import { Event, EventsQuery_Filter } from '../../protos/xyz/block/ftl/v1/console/console_pb.ts'
import { SidePanelContext } from '../../providers/side-panel-provider.tsx'
import { getEvents } from '../../services/console.service.ts'
import { getEvents, timeFilter } from '../../services/console.service.ts'
import { formatTimestampShort } from '../../utils/date.utils.ts'
import { panelColor } from '../../utils/style.utils.ts'
import { TimelineCall } from './TimelineCall.tsx'
Expand All @@ -14,41 +12,32 @@ import { TimelineLog } from './TimelineLog.tsx'
import { TimelineCallDetails } from './details/TimelineCallDetails.tsx'
import { TimelineDeploymentDetails } from './details/TimelineDeploymentDetails.tsx'
import { TimelineLogDetails } from './details/TimelineLogDetails.tsx'
import { TIME_RANGES } from './filters/TimeFilter.tsx'
import { TimeSettings } from './filters/TimelineTimeControls.tsx'

export const Timeline = () => {
const client = useClient(ConsoleService)
interface Props {
timeSettings: TimeSettings
filters: EventsQuery_Filter[]
}

export const Timeline = ({ timeSettings, filters }: Props) => {
const { openPanel, closePanel, isOpen } = React.useContext(SidePanelContext)
const [entries, setEntries] = React.useState<Event[]>([])
const [selectedEntry, setSelectedEntry] = React.useState<Event | null>(null)
const [selectedEventTypes] = React.useState<string[]>(['log', 'call', 'deployment'])
const [selectedLogLevels] = React.useState<number[]>([1, 5, 9, 13, 17])
const [selectedTimeRange] = React.useState('24h')

React.useEffect(() => {
const abortController = new AbortController()

const streamTimeline = async () => {
setEntries((_) => [])
const events = await getEvents()
console.log(events)
const afterTime = new Date(Date.now() - TIME_RANGES[selectedTimeRange].value)

for await (const response of client.streamEvents(
{ afterTime: Timestamp.fromDate(afterTime) },
{ signal: abortController.signal },
)) {
if (response.event != null) {
setEntries((prevEntries) => [response.event!, ...prevEntries])
}
const fetchEvents = async () => {
let eventFilters = filters
if (timeSettings.newerThan || timeSettings.olderThan) {
eventFilters = [timeFilter(timeSettings.olderThan, timeSettings.newerThan), ...filters]
}
const events = await getEvents(eventFilters)
setEntries(events)
}

streamTimeline()
return () => {
abortController.abort()
}
}, [client, selectedTimeRange])
fetchEvents()
}, [filters, timeSettings])

React.useEffect(() => {
if (!isOpen) {
Expand Down
21 changes: 17 additions & 4 deletions console/client/src/features/timeline/TimelinePage.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
import { ListBulletIcon } from '@heroicons/react/24/outline'
import React from 'react'
import { PageHeader } from '../../components/PageHeader'
import { EventsQuery_Filter } from '../../protos/xyz/block/ftl/v1/console/console_pb'
import { Timeline } from './Timeline'
import { TimelineFilterPanel } from './filters/TimelineFilterPanel'
import { TimelineTimeControls } from './filters/TimelineTimeControls'
import { TimeSettings, TimelineTimeControls } from './filters/TimelineTimeControls'

export const TimelinePage = () => {
const [timeSettings, setTimeSettings] = React.useState<TimeSettings>({ isTailing: true, isPaused: false })
const [filters, setFilters] = React.useState<EventsQuery_Filter[]>([])

const handleTimeSettingsChanged = (settings: TimeSettings) => {
setTimeSettings(settings)
}

const handleFiltersChanged = (filters: EventsQuery_Filter[]) => {
setFilters(filters)
}

return (
<>
<PageHeader icon={<ListBulletIcon />} title='Events'>
<TimelineTimeControls />
<TimelineTimeControls onTimeSettingsChange={handleTimeSettingsChanged} />
</PageHeader>
<div className='flex h-full'>
<TimelineFilterPanel />
<TimelineFilterPanel onFiltersChanged={handleFiltersChanged} />
<div className='flex-grow'>
<Timeline />
<Timeline timeSettings={timeSettings} filters={filters} />
</div>
</div>
</>
Expand Down
69 changes: 0 additions & 69 deletions console/client/src/features/timeline/filters/TimelineFilterBar.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { PhoneIcon, RocketLaunchIcon } from '@heroicons/react/24/outline'
import React from 'react'
import { LogLevel } from '../../../protos/xyz/block/ftl/v1/console/console_pb'
import { EventsQuery_Filter, LogLevel } from '../../../protos/xyz/block/ftl/v1/console/console_pb'
import { modulesContext } from '../../../providers/modules-provider'
import { textColor } from '../../../utils'
import { LogLevelBadgeSmall } from '../../logs/LogLevelBadgeSmall'
Expand All @@ -27,7 +27,11 @@ const LOG_LEVELS: Record<number, string> = {
17: 'Error',
}

export const TimelineFilterPanel = () => {
interface Props {
onFiltersChanged: (filters: EventsQuery_Filter[]) => void
}

export const TimelineFilterPanel = ({ onFiltersChanged }: Props) => {
const modules = React.useContext(modulesContext)
const [selectedEventTypes, setSelectedEventTypes] = React.useState<string[]>(Object.keys(EVENT_TYPES))
const [selectedModules, setSelectedModules] = React.useState<string[]>([])
Expand All @@ -39,6 +43,10 @@ export const TimelineFilterPanel = () => {
}
}, [modules])

React.useEffect(() => {
onFiltersChanged([])
}, [selectedEventTypes, setSelectedLogLevel, selectedModules])

const handleTypeChanged = (eventType: string, checked: boolean) => {
if (checked) {
setSelectedEventTypes((prev) => [...prev, eventType])
Expand Down
142 changes: 119 additions & 23 deletions console/client/src/features/timeline/filters/TimelineTimeControls.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { Timestamp } from '@bufbuild/protobuf'
import { Listbox, Transition } from '@headlessui/react'
import { BackwardIcon, CheckIcon, ChevronUpDownIcon, ForwardIcon, PlayIcon } from '@heroicons/react/24/outline'
import {
BackwardIcon,
CheckIcon,
ChevronUpDownIcon,
ForwardIcon,
PauseIcon,
PlayIcon,
} from '@heroicons/react/24/outline'
import React, { Fragment } from 'react'
import { bgColor, borderColor, classNames, panelColor, textColor } from '../../../utils'
import { bgColor, borderColor, classNames, formatTimestampShort, panelColor, textColor } from '../../../utils'

interface TimeRange {
label: string
Expand All @@ -11,18 +19,93 @@ interface TimeRange {
export const TIME_RANGES: Record<string, TimeRange> = {
tail: { label: 'Live tail', value: 0 },
'5m': { label: 'Past 5 minutes', value: 5 * 60 * 1000 },
'15m': { label: 'Past 15 minutes', value: 15 * 60 * 1000 },
'30m': { label: 'Past 30 minutes', value: 30 * 60 * 1000 },
'1h': { label: 'Past 1 hour', value: 60 * 60 * 1000 },
'24h': { label: 'Past 24 hours', value: 24 * 60 * 60 * 1000 },
}

export const TimelineTimeControls = () => {
export interface TimeSettings {
isTailing: boolean
isPaused: boolean
olderThan?: Timestamp
newerThan?: Timestamp
}

interface Props {
onTimeSettingsChange: (settings: TimeSettings) => void
}

export const TimelineTimeControls = ({ onTimeSettingsChange }: Props) => {
const [selected, setSelected] = React.useState(TIME_RANGES['tail'])
const [isPaused, setIsPaused] = React.useState(false)
const [newerThan, setNewerThan] = React.useState<Timestamp | undefined>()

const isTailing = selected.value === TIME_RANGES['tail'].value

React.useEffect(() => {
if (isTailing) {
onTimeSettingsChange({ isTailing, isPaused })
return
}

if (newerThan) {
const startTime = (newerThan.toDate() ?? new Date()).getTime()
const olderThanDate = new Date(startTime + selected.value)

onTimeSettingsChange({
isTailing,
isPaused,
olderThan: Timestamp.fromDate(olderThanDate),
newerThan: newerThan,
})
}
}, [selected, isPaused, newerThan])

const handleRangeChanged = (range: TimeRange) => {
setSelected(range)
if (!newerThan) {
const newerThanDate = new Date(new Date().getTime() - range.value)
setNewerThan(Timestamp.fromDate(newerThanDate))
}
if (range.value === TIME_RANGES['tail'].value) {
setNewerThan(undefined)
}
}

const handleTimeBackward = () => {
if (!newerThan) {
return
}
const newerThanDate = new Date(newerThan.toDate().getTime() - selected.value)
setNewerThan(Timestamp.fromDate(newerThanDate))
}

const handleTimeForward = () => {
if (!newerThan) {
return
}
const newerThanTime = newerThan.toDate().getTime()
const newerThanDate = new Date(newerThanTime + selected.value)
const maxNewTime = new Date().getTime() - selected.value
if (newerThanDate.getTime() > maxNewTime) {
setNewerThan(Timestamp.fromDate(new Date(maxNewTime)))
} else {
setNewerThan(Timestamp.fromDate(newerThanDate))
}
}

const olderThan = newerThan ? Timestamp.fromDate(new Date(newerThan.toDate().getTime() - selected.value)) : undefined
return (
<>
<div className='flex items-center h-6'>
<Listbox value={selected} onChange={setSelected}>
{newerThan && (
<span className='text-xs font-roboto-mono mr-2 text-gray-400'>
{formatTimestampShort(olderThan)} - {formatTimestampShort(newerThan)}
</span>
)}

<Listbox value={selected} onChange={handleRangeChanged}>
{({ open }) => (
<>
<div className='relative w-40 mr-2 -mt-0.5 items-center'>
Expand Down Expand Up @@ -87,26 +170,39 @@ export const TimelineTimeControls = () => {
</>
)}
</Listbox>
<span className={`isolate inline-flex rounded-md shadow-sm h-6 ${textColor} ${bgColor}`}>
<button
type='button'
className={`relative inline-flex items-center rounded-l-md px-3 text-sm font-semibold ring-1 ring-inset ${borderColor} hover:bg-gray-50 dark:hover:bg-gray-700 focus:z-10`}
>
<BackwardIcon className='w-4 h-4' />
</button>
<button
type='button'
className={`relative -ml-px inline-flex items-center px-3 text-sm font-semibold ring-1 ring-inset ${borderColor} hover:bg-gray-50 dark:hover:bg-gray-700 focus:z-10`}
>
<PlayIcon className='w-4 h-4' />
</button>
<button
type='button'
className={`relative -ml-px inline-flex items-center rounded-r-md px-3 text-sm font-semibold ring-1 ring-inset ${borderColor} hover:bg-gray-50 dark:hover:bg-gray-700 focus:z-10`}
{isTailing && (
<span
className={`isolate inline-flex rounded-md shadow-sm h-6 ${textColor} ${
isPaused ? bgColor : 'bg-indigo-600 text-white'
} `}
>
<ForwardIcon className='w-4 h-4' />
</button>
</span>
<button
type='button'
onClick={() => setIsPaused(!isPaused)}
className={`relative inline-flex items-center rounded-md px-3 text-sm font-semibold ring-1 ring-inset ${borderColor} hover:bg-gray-50 dark:hover:bg-indigo-700 focus:z-10`}
>
{isPaused ? <PlayIcon className='w-4 h-4' /> : <PauseIcon className='w-4 h-4' />}
</button>
</span>
)}
{!isTailing && (
<span className={`isolate inline-flex rounded-md shadow-sm h-6 ${textColor} ${bgColor}`}>
<button
type='button'
onClick={handleTimeBackward}
className={`relative inline-flex items-center rounded-l-md px-3 text-sm font-semibold ring-1 ring-inset ${borderColor} hover:bg-gray-50 dark:hover:bg-indigo-700 focus:z-10`}
>
<BackwardIcon className='w-4 h-4' />
</button>
<button
type='button'
onClick={handleTimeForward}
className={`relative -ml-px inline-flex items-center rounded-r-md px-3 text-sm font-semibold ring-1 ring-inset ${borderColor} hover:bg-gray-50 dark:hover:bg-indigo-700 focus:z-10`}
>
<ForwardIcon className='w-4 h-4' />
</button>
</span>
)}
</div>
</>
)
Expand Down
Loading

0 comments on commit 09af837

Please sign in to comment.