import React, { FC, useMemo, ComponentProps, cloneElement } from "react"; import styled from "styled-components"; import { DebugEvent } from "@urql/core"; import { rem } from "polished"; import { useTimelineContext } from "../../../context"; import { TimelineEvent, TimelineEventGroup } from "./TimelineEvent"; import { TimelineAliveDuration, TimelineNetworkDuration, } from "./TimelineDuration"; export const TimelineRow: FC< { events: DebugEvent[] } & ComponentProps > = ({ events, ...props }) => { const { container, scale, setSelectedEvent, selectedEvent, filter, } = useTimelineContext(); const eventElements = useMemo( () => events .reduce<{ key: number; event: DebugEvent }[][]>((groups, event, i) => { if (!filter.source.includes(event.source)) { return groups; } const entry = { key: i, event }; if (groups.length === 0) { return [[entry]]; } const previousIndex = groups.length - 1; if ( scale(event.timestamp) - scale(groups[previousIndex][0].event.timestamp) < 5 ) { groups[previousIndex].push(entry); return groups; } groups.push([entry]); return groups; }, []) .map((group) => { if (group.length === 1) { const { event, key } = group[0]; return ( setSelectedEvent((s) => (s === event ? undefined : event)) } style={{ position: "absolute", left: scale(event.timestamp), transform: "translateX(-50%) translateY(-50%)", }} /> ); } const groupKey = group.reduce((p, c) => `${p} ${c.key}`, ""); return ( {group.map(({ event, key }) => ( setSelectedEvent((s) => (s === event ? undefined : event)) } /> ))} ); }), [events, scale] ); const durationElements = useMemo(() => { type ReduceState = { elements: JSX.Element[]; start: DebugEvent | undefined; }; // Network durations const reduceNetwork = ( p: ReduceState, e: DebugEvent ) => { const handleClick = () => setSelectedEvent((event) => (event === e ? undefined : e)); // Request started if (p.start === undefined && e.type === "fetchRequest") { p.start = e; return p; } // Safety condition - shouldn't occur if (!p.start) { return p; } // Response if (e.type === "fetchSuccess") { p.elements.push( ); p.start = undefined; return p; } if (e.type === "fetchError" || e.type === "teardown") { p.elements.push( ); p.start = undefined; return p; } return p; }; /** Semaphore */ let activeMutations = 0; // Alive durations const reduceAlive = ( p: ReduceState, e: DebugEvent ) => { // Semaphore addition if (e.operation.kind === "mutation" && e.type === "execution") { activeMutations++; } // First event to start timeline duration if (p.start === undefined && e.type !== "teardown") { p.start = e; return p; } if (p.start === undefined) { return p; } const isMutationResponse = e.operation.kind === "mutation" && (e.type === "update" || e.type === "error"); // Semaphore removal if (isMutationResponse) { activeMutations = Math.max(0, activeMutations - 1); } // End of timeline duration if ( e.type === "teardown" || (isMutationResponse && activeMutations === 0) ) { p.elements.push( ); p.start = undefined; return p; } return p; }; const reducedDurations = events.reduce<{ alive: ReduceState; network: ReduceState; }>( (p, e) => ({ network: reduceNetwork(p.network, e), alive: reduceAlive(p.alive, e), }), { alive: { start: undefined, elements: [] }, network: { start: undefined, elements: [] }, } ); const finalAliveDuration = reducedDurations.alive.start ? [ , ] : []; const finalNetworkDuration = reducedDurations.network.start ? [ setSelectedEvent(reducedDurations.network.start)} />, ] : []; return reducedDurations.alive.elements .concat(finalAliveDuration) .concat(reducedDurations.network.elements) .concat(finalNetworkDuration); }, [events, scale, container.clientWidth, setSelectedEvent]); return ( <> {durationElements .filter( (e) => e.props.style.right < container.clientWidth && e.props.style.left < container.clientWidth ) .map((e) => cloneElement(e, { ...e.props, style: { ...e.props.style, left: Math.max(0, e.props.style.left), right: Math.max(0, e.props.style.right), }, }) )} <> {eventElements.filter( (e) => e.props.style.left > -20 && e.props.style.left < container.clientWidth + 20 )} ); }; const Container = styled.div` position: relative; height: ${rem(20)}; margin-top: ${(p) => p.theme.space[5]}; `;