Skip to content

Commit

Permalink
feat: make trace graph generic and show ingree events (#2761)
Browse files Browse the repository at this point in the history
Fixes #2734

We can now show ingress and call events in the trace graph. We can
iterate on styling etc. in the future, but this at least gets the events
in.

Next up will be a trace specific list and page to navigate around traces
and drill into events.

![Screenshot 2024-09-20 at 1 06
27 PM](https://github.com/user-attachments/assets/88657349-995f-41b1-a001-8770ed482143)
![Screenshot 2024-09-20 at 12 59
43 PM](https://github.com/user-attachments/assets/5bef61d1-0e8e-4d45-9208-e10578b52d27)
![Screenshot 2024-09-20 at 12 59
57 PM](https://github.com/user-attachments/assets/a8655792-51c3-49a3-abc1-308fc91ce491)
  • Loading branch information
wesbillman authored Sep 20, 2024
1 parent 8f81947 commit e4b9646
Show file tree
Hide file tree
Showing 11 changed files with 217 additions and 193 deletions.
2 changes: 2 additions & 0 deletions backend/controller/console/console.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,8 @@ func eventsQueryProtoToDAL(pb *pbconsole.EventsQuery) ([]timeline.TimelineFilter
eventTypes = append(eventTypes, timeline.EventTypeDeploymentCreated)
case pbconsole.EventType_EVENT_TYPE_DEPLOYMENT_UPDATED:
eventTypes = append(eventTypes, timeline.EventTypeDeploymentUpdated)
case pbconsole.EventType_EVENT_TYPE_INGRESS:
eventTypes = append(eventTypes, timeline.EventTypeIngress)
default:
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("unknown event type %v", eventType))
}
Expand Down
1 change: 1 addition & 0 deletions backend/controller/timeline/timeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const (
EventTypeCall = sql.EventTypeCall
EventTypeDeploymentCreated = sql.EventTypeDeploymentCreated
EventTypeDeploymentUpdated = sql.EventTypeDeploymentUpdated
EventTypeIngress = sql.EventTypeIngress
)

// TimelineEvent types.
Expand Down
17 changes: 17 additions & 0 deletions frontend/console/src/api/timeline/use-request-trace-events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { type CallEvent, EventType, type EventsQuery_Filter, type IngressEvent } from '../../protos/xyz/block/ftl/v1/console/console_pb.ts'
import { eventTypesFilter, requestKeysFilter } from './timeline-filters.ts'
import { useTimeline } from './use-timeline.ts'

export type TraceEvent = CallEvent | IngressEvent

export const useRequestTraceEvents = (requestKey?: string, filters: EventsQuery_Filter[] = []) => {
const eventTypes = [EventType.CALL, EventType.INGRESS]
const allFilters = [...filters, requestKeysFilter([requestKey || '']), eventTypesFilter(eventTypes)]
const timelineQuery = useTimeline(true, allFilters, !!requestKey)

const data = timelineQuery.data?.filter((event) => event.entry.case === 'call' || event.entry.case === 'ingress') ?? []
return {
...timelineQuery,
data,
}
}
6 changes: 2 additions & 4 deletions frontend/console/src/api/timeline/use-timeline-calls.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { type CallEvent, EventType, type EventsQuery_Filter } from '../../protos/xyz/block/ftl/v1/console/console_pb.ts'
import { EventType, type EventsQuery_Filter } from '../../protos/xyz/block/ftl/v1/console/console_pb.ts'
import { eventTypesFilter } from './timeline-filters.ts'
import { useTimeline } from './use-timeline.ts'

export const useTimelineCalls = (isStreaming: boolean, filters: EventsQuery_Filter[], enabled = true) => {
const allFilters = [...filters, eventTypesFilter([EventType.CALL])]
const timelineQuery = useTimeline(isStreaming, allFilters, enabled)

// Map the events to CallEvent for ease of use
const data = timelineQuery.data?.map((event) => event.entry.value as CallEvent) || []

const data = timelineQuery.data || []
return {
...timelineQuery,
data,
Expand Down
82 changes: 43 additions & 39 deletions frontend/console/src/features/calls/CallList.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import { useContext, useState } from 'react'
import type { CallEvent } from '../../protos/xyz/block/ftl/v1/console/console_pb'
import type { CallEvent, Event } from '../../protos/xyz/block/ftl/v1/console/console_pb'
import { SidePanelContext } from '../../providers/side-panel-provider'
import { formatDuration, formatTimestampShort } from '../../utils'
import { TimelineCallDetails } from '../timeline/details/TimelineCallDetails'
import { verbRefString } from '../verbs/verb.utils'

export const CallList = ({ calls }: { calls: CallEvent[] | undefined }) => {
export const CallList = ({ calls }: { calls: Event[] | undefined }) => {
const { openPanel, closePanel } = useContext(SidePanelContext)
const [selectedCall, setSelectedCall] = useState<CallEvent | undefined>()
const [selectedCallId, setSelectedCallId] = useState<bigint | undefined>()

const handleCallClicked = (call: CallEvent) => {
if (selectedCall?.equals(call)) {
setSelectedCall(undefined)
const handleCallClicked = (event: Event) => {
if (selectedCallId === event.id) {
setSelectedCallId(undefined)
closePanel()
return
}
setSelectedCall(call)
openPanel(<TimelineCallDetails timestamp={call.timeStamp} call={call} />)
setSelectedCallId(event.id)
const call = event.entry.value as CallEvent
openPanel(<TimelineCallDetails timestamp={call.timeStamp} event={event} />)
}

return (
Expand All @@ -38,38 +39,41 @@ export const CallList = ({ calls }: { calls: CallEvent[] | undefined }) => {
<div className='overflow-y-auto h-[calc(100%-1.5rem)]'>
<table className={'w-full table-fixed text-gray-600 dark:text-gray-300'}>
<tbody className='text-xs'>
{calls?.map((call, index) => (
<tr
key={`${index}-${call.timeStamp?.toDate().toUTCString()}`}
className={`border-b border-gray-100 dark:border-slate-700 font-roboto-mono
${selectedCall?.equals(call) ? 'bg-indigo-50 dark:bg-slate-700' : ''} relative flex cursor-pointer hover:bg-indigo-50 dark:hover:bg-slate-700`}
onClick={() => handleCallClicked(call)}
>
<td className='p-1 w-40 items-center flex-none text-gray-400 dark:text-gray-400'>{formatTimestampShort(call.timeStamp)}</td>
<td className='p-1 w-14 items-center flex-none text-gray-400 dark:text-gray-400 truncate'>{formatDuration(call.duration)}</td>
<td
className='p-1 w-40 flex-none text-indigo-500 dark:text-indigo-300 truncate'
title={call.sourceVerbRef && verbRefString(call.sourceVerbRef)}
{calls?.map((callEvent, index) => {
const call = callEvent.entry.value as CallEvent
return (
<tr
key={`${index}-${callEvent.timeStamp?.toDate().toUTCString()}`}
className={`border-b border-gray-100 dark:border-slate-700 font-roboto-mono
${selectedCallId === callEvent.id ? 'bg-indigo-50 dark:bg-slate-700' : ''} relative flex cursor-pointer hover:bg-indigo-50 dark:hover:bg-slate-700`}
onClick={() => handleCallClicked(callEvent)}
>
{call.sourceVerbRef && verbRefString(call.sourceVerbRef)}
</td>
<td
className='p-1 w-40 flex-none text-indigo-500 dark:text-indigo-300 truncate'
title={call.destinationVerbRef && verbRefString(call.destinationVerbRef)}
>
{call.destinationVerbRef && verbRefString(call.destinationVerbRef)}
</td>
<td className='p-1 min-w-[60px] flex-1 flex-grow truncate' title={call.request}>
{call.request}
</td>
<td className='p-1 min-w-[70px] flex-1 flex-grow truncate' title={call.response}>
{call.response}
</td>
<td className='p-1 flex-1 flex-grow truncate text-red-500' title={call.error}>
{call.error}
</td>
</tr>
))}
<td className='p-1 w-40 items-center flex-none text-gray-400 dark:text-gray-400'>{formatTimestampShort(callEvent.timeStamp)}</td>
<td className='p-1 w-14 items-center flex-none text-gray-400 dark:text-gray-400 truncate'>{formatDuration(call.duration)}</td>
<td
className='p-1 w-40 flex-none text-indigo-500 dark:text-indigo-300 truncate'
title={call.sourceVerbRef && verbRefString(call.sourceVerbRef)}
>
{call.sourceVerbRef && verbRefString(call.sourceVerbRef)}
</td>
<td
className='p-1 w-40 flex-none text-indigo-500 dark:text-indigo-300 truncate'
title={call.destinationVerbRef && verbRefString(call.destinationVerbRef)}
>
{call.destinationVerbRef && verbRefString(call.destinationVerbRef)}
</td>
<td className='p-1 min-w-[60px] flex-1 flex-grow truncate' title={call.request}>
{call.request}
</td>
<td className='p-1 min-w-[70px] flex-1 flex-grow truncate' title={call.response}>
{call.response}
</td>
<td className='p-1 flex-1 flex-grow truncate text-red-500' title={call.error}>
{call.error}
</td>
</tr>
)
})}
</tbody>
</table>
</div>
Expand Down
96 changes: 0 additions & 96 deletions frontend/console/src/features/requests/RequestGraph.tsx

This file was deleted.

4 changes: 2 additions & 2 deletions frontend/console/src/features/timeline/Timeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export const Timeline = ({ timeSettings, filters }: { timeSettings: TimeSettings

switch (entry.entry?.case) {
case 'call':
openPanel(<TimelineCallDetails timestamp={entry.timeStamp as Timestamp} call={entry.entry.value} />, handlePanelClosed)
openPanel(<TimelineCallDetails timestamp={entry.timeStamp as Timestamp} event={entry} />, handlePanelClosed)
break
case 'log':
openPanel(<TimelineLogDetails event={entry} log={entry.entry.value} />, handlePanelClosed)
Expand All @@ -68,7 +68,7 @@ export const Timeline = ({ timeSettings, filters }: { timeSettings: TimeSettings
openPanel(<TimelineDeploymentUpdatedDetails event={entry} deployment={entry.entry.value} />, handlePanelClosed)
break
case 'ingress':
openPanel(<TimelineIngressDetails timestamp={entry.timeStamp as Timestamp} ingress={entry.entry.value} />, handlePanelClosed)
openPanel(<TimelineIngressDetails timestamp={entry.timeStamp as Timestamp} event={entry} />, handlePanelClosed)
break
default:
break
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
import type { Timestamp } from '@bufbuild/protobuf'
import { useContext, useEffect, useState } from 'react'
import { useContext } from 'react'
import { AttributeBadge } from '../../../components/AttributeBadge'
import { CloseButton } from '../../../components/CloseButton'
import { CodeBlock } from '../../../components/CodeBlock'
import type { CallEvent } from '../../../protos/xyz/block/ftl/v1/console/console_pb'
import type { CallEvent, Event } from '../../../protos/xyz/block/ftl/v1/console/console_pb'
import { SidePanelContext } from '../../../providers/side-panel-provider'
import { formatDuration } from '../../../utils/date.utils'
import { DeploymentCard } from '../../deployments/DeploymentCard'
import { RequestGraph } from '../../requests/RequestGraph'
import { TraceGraph } from '../../traces/TraceGraph'
import { verbRefString } from '../../verbs/verb.utils'
import { TimelineTimestamp } from './TimelineTimestamp'

export const TimelineCallDetails = ({ timestamp, call }: { timestamp?: Timestamp; call: CallEvent }) => {
export const TimelineCallDetails = ({ timestamp, event }: { timestamp?: Timestamp; event: Event }) => {
const { closePanel } = useContext(SidePanelContext)
const [selectedCall, setSelectedCall] = useState(call)

useEffect(() => {
setSelectedCall(call)
}, [call])

const call = event.entry.value as CallEvent
return (
<div className='p-4'>
<div className='flex items-center justify-between'>
Expand All @@ -35,28 +31,28 @@ export const TimelineCallDetails = ({ timestamp, call }: { timestamp?: Timestamp
<CloseButton onClick={closePanel} />
</div>

<div className='pt-4'>
<RequestGraph call={selectedCall} setSelectedCall={setSelectedCall} />
<div className='mt-4'>
<TraceGraph requestKey={call.requestKey} selectedEventId={event.id} />
</div>

<div className='text-sm pt-2'>Request</div>
<CodeBlock code={JSON.stringify(JSON.parse(selectedCall.request), null, 2)} language='json' />
<CodeBlock code={JSON.stringify(JSON.parse(call.request), null, 2)} language='json' />

{selectedCall.response !== 'null' && (
{call.response !== 'null' && (
<>
<div className='text-sm pt-2'>Response</div>
<CodeBlock code={JSON.stringify(JSON.parse(selectedCall.response), null, 2)} language='json' />
<CodeBlock code={JSON.stringify(JSON.parse(call.response), null, 2)} language='json' />
</>
)}

{selectedCall.error && (
{call.error && (
<>
<h3 className='pt-4'>Error</h3>
<CodeBlock code={selectedCall.error} language='text' />
{selectedCall.stack && (
<CodeBlock code={call.error} language='text' />
{call.stack && (
<>
<h3 className='pt-4'>Stack</h3>
<CodeBlock code={selectedCall.stack} language='text' />
<CodeBlock code={call.stack} language='text' />
</>
)}
</>
Expand All @@ -65,22 +61,22 @@ export const TimelineCallDetails = ({ timestamp, call }: { timestamp?: Timestamp
<DeploymentCard className='mt-4' deploymentKey={call.deploymentKey} />

<ul className='pt-4 space-y-2'>
{selectedCall.requestKey && (
{call.requestKey && (
<li>
<AttributeBadge name='Request' value={selectedCall.requestKey} />
<AttributeBadge name='Request' value={call.requestKey} />
</li>
)}
<li>
<AttributeBadge name='Duration' value={formatDuration(selectedCall.duration)} />
<AttributeBadge name='Duration' value={formatDuration(call.duration)} />
</li>
{selectedCall.destinationVerbRef && (
{call.destinationVerbRef && (
<li>
<AttributeBadge name='Destination' value={verbRefString(selectedCall.destinationVerbRef)} />
<AttributeBadge name='Destination' value={verbRefString(call.destinationVerbRef)} />
</li>
)}
{selectedCall.sourceVerbRef && (
{call.sourceVerbRef && (
<li>
<AttributeBadge name='Source' value={verbRefString(selectedCall.sourceVerbRef)} />
<AttributeBadge name='Source' value={verbRefString(call.sourceVerbRef)} />
</li>
)}
</ul>
Expand Down
Loading

0 comments on commit e4b9646

Please sign in to comment.