import type { GrpcEvent, GrpcRequest } from '@yaakapp-internal/models'; import classNames from 'classnames'; import { format } from 'date-fns'; import type { CSSProperties } from 'react'; import { useEffect, useMemo, useRef, useState } from 'react'; import { useCopy } from '../hooks/useCopy'; import { useGrpcEvents } from '../hooks/useGrpcEvents'; import { usePinnedGrpcConnection } from '../hooks/usePinnedGrpcConnection'; import { useStateWithDeps } from '../hooks/useStateWithDeps'; import { AutoScroller } from './core/AutoScroller'; import { Banner } from './core/Banner'; import { Button } from './core/Button'; import { Icon } from './core/Icon'; import { IconButton } from './core/IconButton'; import { JsonAttributeTree } from './core/JsonAttributeTree'; import { KeyValueRow, KeyValueRows } from './core/KeyValueRow'; import { LoadingIcon } from './core/LoadingIcon'; import { Separator } from './core/Separator'; import { SplitLayout } from './core/SplitLayout'; import { HStack, VStack } from './core/Stacks'; import { EmptyStateText } from './EmptyStateText'; import { RecentGrpcConnectionsDropdown } from './RecentGrpcConnectionsDropdown'; interface Props { style?: CSSProperties; className?: string; activeRequest: GrpcRequest; methodType: | 'unary' | 'client_streaming' | 'server_streaming' | 'streaming' | 'no-schema' | 'no-method'; } export function GrpcConnectionMessagesPane({ style, methodType, activeRequest }: Props) { const [activeEventId, setActiveEventId] = useState(null); const [showLarge, setShowLarge] = useStateWithDeps(false, [activeRequest.id]); const [showingLarge, setShowingLarge] = useState(false); const { activeConnection, connections, setPinnedConnectionId } = usePinnedGrpcConnection(activeRequest); const events = useGrpcEvents(activeConnection?.id ?? null); const copy = useCopy(); const activeEvent = useMemo( () => events.find((m) => m.id === activeEventId) ?? null, [activeEventId, events], ); // Set active message to the first message received if unary useEffect(() => { if (events.length === 0 || activeEvent != null || methodType !== 'unary') { return; } setActiveEventId(events.find((m) => m.eventType === 'server_message')?.id ?? null); // eslint-disable-next-line react-hooks/exhaustive-deps }, [events.length]); return ( activeConnection && (
{events.length} Messages {activeConnection.state !== 'closed' && ( )}
{activeConnection.error} ) } render={(event) => ( { if (event.id === activeEventId) setActiveEventId(null); else setActiveEventId(event.id); }} /> )} />
) } secondSlot={ activeEvent && (() => (
{activeEvent.eventType === 'client_message' || activeEvent.eventType === 'server_message' ? ( <>
Message {activeEvent.eventType === 'client_message' ? 'Sent' : 'Received'}
copy(activeEvent.content)} />
{!showLarge && activeEvent.content.length > 1000 * 1000 ? ( Message previews larger than 1MB are hidden
) : ( )} ) : (
{activeEvent.content}
{activeEvent.error && (
{activeEvent.error}
)}
{Object.keys(activeEvent.metadata).length === 0 ? ( No {activeEvent.eventType === 'connection_end' ? 'trailers' : 'metadata'} ) : ( {Object.entries(activeEvent.metadata).map(([key, value]) => ( {value} ))} )}
)}
)) } /> ); } function EventRow({ onClick, isActive, event, }: { onClick?: () => void; isActive?: boolean; event: GrpcEvent; }) { const { eventType, status, createdAt, content, error } = event; const ref = useRef(null); return (
); }