Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the ability to replay requests from the details view #197

Merged
merged 31 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
d90ce86
add initial useReplay hook
keturiosakys Aug 16, 2024
81901a4
add replay button
keturiosakys Aug 16, 2024
90c9bef
update replay hook
keturiosakys Aug 16, 2024
b026144
update count badge minimum width
keturiosakys Aug 16, 2024
e794c21
update key value table headers so the count badges always align
keturiosakys Aug 16, 2024
ffcbe24
formatting
keturiosakys Aug 16, 2024
3f55371
better filter maybe
keturiosakys Aug 20, 2024
611fd4e
add useReplayRequest hook
keturiosakys Aug 26, 2024
21fd335
add useMostRecentRequest hook
keturiosakys Aug 26, 2024
90aee49
add useShouldReplay hook
keturiosakys Aug 26, 2024
a326232
revert pagination to use callback hooks so they get re-rendered when …
keturiosakys Aug 26, 2024
6e3feb7
pass in the traceId
keturiosakys Aug 26, 2024
e6a59de
add a "View latest response" and fix the replay button
keturiosakys Aug 26, 2024
28c4b3e
organize imports
keturiosakys Aug 26, 2024
36604a0
add an error callback
keturiosakys Aug 26, 2024
b8bead3
handle json or text
keturiosakys Aug 27, 2024
696fadf
make body optional so that it can be undefined
keturiosakys Aug 27, 2024
6c8cc68
mock callbacks
keturiosakys Aug 27, 2024
e30fe99
revert optional body because things blow up
keturiosakys Aug 27, 2024
f2939fa
add keyboard shortcut to replay
keturiosakys Aug 27, 2024
7fabfb3
make traces optional
keturiosakys Aug 27, 2024
456900f
fix types and use hooks from useRequestor state
keturiosakys Aug 27, 2024
e93edca
Merge branch 'main' into replay
keturiosakys Aug 27, 2024
f2d0d91
fix formatting
keturiosakys Aug 27, 2024
3172bca
use block statements
keturiosakys Aug 27, 2024
52bb6d6
fix up lints
keturiosakys Aug 27, 2024
a41d130
uncomment replay documentation
keturiosakys Aug 27, 2024
fd59b1c
Remove double ?? from http summary component
brettimus Aug 27, 2024
341e9ec
Fix margin on Incoming Request
brettimus Aug 27, 2024
7ef2d6c
Handle squishing better
brettimus Aug 27, 2024
1144d4e
Merge branch 'main' into replay
brettimus Aug 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion studio/src/components/CountBadge.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
export const CountBadge = ({ count }: { count: number }) => {
return (
<span className="text-gray-400 font-normal bg-muted-foreground/20 rounded px-1.5 inline-block ml-2">
<span
className="text-gray-400
font-normal
bg-muted-foreground/20
rounded
px-1.5
inline-block
min-w-5
"
>
{count}
</span>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export function Otel({

return (
<RequestDetailsPageContentV2
traceId={traceId}
spans={spans}
orphanLogs={orphanLogs}
pagination={pagination}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,17 @@ import {
ResizablePanelGroup,
} from "@/components/ui/resizable";
import type { MizuOrphanLog } from "@/queries";
import type { OtelSpan } from "@/queries/traces-otel";
import { cn } from "@/utils";
import { type OtelSpan, useOtelTraces } from "@/queries/traces-otel";
import { cn, isMac } from "@/utils";
import { useRef } from "react";
import { useHotkeys } from "react-hotkeys-hook";
import { Link } from "react-router-dom";
import { EmptyState } from "../EmptyState";
import {
useMostRecentRequest,
useReplayRequest,
useShouldReplay,
} from "../hooks";
import { TraceDetailsTimeline, TraceDetailsV2 } from "../v2";
import { HttpSummary, SummaryV2 } from "../v2/SummaryV2";
import type { getVendorInfo } from "../v2/vendorify-traces";
Expand All @@ -31,10 +39,12 @@ export type Waterfall = Array<SpanWithVendorInfo | MizuOrphanLog>;
const EMPTY_LIST: Array<MizuOrphanLog> = [];

export function RequestDetailsPageContentV2({
traceId,
pagination,
spans,
orphanLogs = EMPTY_LIST,
}: {
traceId: string;
spans: Array<OtelSpan>;
orphanLogs?: Array<MizuOrphanLog>;
pagination?: {
Expand All @@ -44,8 +54,29 @@ export function RequestDetailsPageContentV2({
handleNextTrace: () => void;
};
}) {
const currentTrace = {
traceId,
spans,
};
const { data: traces } = useOtelTraces();

const { isMostRecentTrace, traceId: mostRecentTraceId } =
useMostRecentRequest(currentTrace, traces);

const { rootSpan, waterfall } = useRequestWaterfall(spans, orphanLogs);

const shouldReplay = useShouldReplay(currentTrace);

const { replay, isReplaying } = useReplayRequest({ span: rootSpan?.span });

const replayRef = useRef<HTMLButtonElement>(null);

useHotkeys(["mod+enter"], () => {
if (replayRef.current && shouldReplay) {
replayRef.current.click();
}
});

if (!rootSpan) {
return <EmptyState />;
}
Expand Down Expand Up @@ -76,48 +107,97 @@ export function RequestDetailsPageContentV2({
<HttpSummary trace={rootSpan.span} />
</div>
</div>
{pagination && (
<div className="flex gap-2">
<div className="flex gap-2 items-center">
{!isMostRecentTrace && (
<Link
className="text-blue-600 pr-4"
to={`/requests/otel/${mostRecentTraceId}`}
>
View most recent response ↗
brettimus marked this conversation as resolved.
Show resolved Hide resolved
</Link>
)}
{shouldReplay && (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="secondary"
size="icon"
disabled={pagination.currentIndex === 0}
onClick={pagination.handlePrevTrace}
variant="default"
disabled={isReplaying}
onClick={replay}
ref={replayRef}
>
<ChevronUpIcon className="w-4 h-4" />
Replay
</Button>
</TooltipTrigger>
<TooltipContent
side="left"
className="bg-slate-950 text-white"
sideOffset={15}
className={cn(
"bg-slate-900 ",
"text-muted-foreground",
"px-2",
"py-1.5",
"gap-1.5",
"text-sm",
"flex",
"items-center",
)}
align="center"
>
Prev <KeyboardShortcutKey>K</KeyboardShortcutKey>
<p>Replay request</p>
<div className="flex gap-1">
<KeyboardShortcutKey>
{isMac ? "⌘" : "Ctrl"}
</KeyboardShortcutKey>{" "}
<KeyboardShortcutKey>Enter</KeyboardShortcutKey>
</div>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="secondary"
size="icon"
disabled={pagination.currentIndex === pagination.maxIndex}
onClick={pagination.handleNextTrace}
)}
{pagination && (
<div className="flex gap-2">
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="secondary"
size="icon"
disabled={pagination.currentIndex === 0}
onClick={pagination.handlePrevTrace}
>
<ChevronUpIcon className="w-4 h-4" />
</Button>
</TooltipTrigger>
<TooltipContent
side="left"
className="bg-slate-900 text-white px-2 py-1.5 gap-1.5"
align="center"
>
<ChevronDownIcon className="w-4 h-4" />
</Button>
</TooltipTrigger>
<TooltipContent
side="bottom"
className="bg-slate-950 text-white"
align="center"
>
Next <KeyboardShortcutKey>J</KeyboardShortcutKey>
</TooltipContent>
</Tooltip>
</div>
)}
<p>Prev</p>
<KeyboardShortcutKey>K</KeyboardShortcutKey>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="secondary"
size="icon"
disabled={pagination.currentIndex === pagination.maxIndex}
onClick={pagination.handleNextTrace}
>
<ChevronDownIcon className="w-4 h-4" />
</Button>
</TooltipTrigger>
<TooltipContent
side="bottom"
className="bg-slate-900 text-white px-2 py-1.5 gap-1.5"
align="center"
>
<p>Next</p>
<KeyboardShortcutKey>J</KeyboardShortcutKey>
</TooltipContent>
</Tooltip>
</div>
)}
</div>
</div>
<div className={cn("grid grid-rows-[auto_1fr] gap-4")}>
<SummaryV2 requestSpan={rootSpan.span} />
Expand Down
3 changes: 3 additions & 0 deletions studio/src/pages/RequestDetailsPage/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
export { useEscapeToList } from "./useNavigateToList";
export { usePagination } from "./usePagination";
export { useReplayRequest } from "./useReplayRequest";
export { useMostRecentRequest } from "./useMostRecentRequest";
export { useShouldReplay } from "./useShouldReplay";
57 changes: 57 additions & 0 deletions studio/src/pages/RequestDetailsPage/hooks/useMostRecentRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { OtelTrace } from "@/queries";
import { getRequestPath, isFpxRequestSpan } from "../v2/otel-helpers";

export function useMostRecentRequest(
currentTrace: OtelTrace,
traces?: OtelTrace[] | null,
) {
if (!traces) {
console.debug("Traces are null");
return {
isMostRecentTrace: true,
traceId: currentTrace.traceId,
};
}

const currentTraceIndex = traces.findIndex(
(trace) => trace.traceId === currentTrace.traceId,
);

if (currentTraceIndex === -1) {
console.debug("Current trace not found in traces array");
return {
isMostRecentTrace: true,
traceId: currentTrace.traceId,
};
}

const currentRequestSpan = currentTrace.spans.find(isFpxRequestSpan);

if (!currentRequestSpan) {
console.debug("No request span found in current trace");
return {
isMostRecentTrace: true,
traceId: currentTrace.traceId,
};
}

const currentPath = getRequestPath(currentRequestSpan);

const mostRecentTrace = traces
.slice(0, currentTraceIndex + 1)
.find((trace) => {
const requestSpan = trace.spans.find(isFpxRequestSpan);

if (requestSpan) {
const tracePath = getRequestPath(requestSpan);
return tracePath === currentPath;
}

return false;
});

return {
isMostRecentTrace: mostRecentTrace?.traceId === currentTrace.traceId,
traceId: mostRecentTrace?.traceId || currentTrace.traceId,
};
}
11 changes: 5 additions & 6 deletions studio/src/pages/RequestDetailsPage/hooks/usePagination.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useHandler } from "@fiberplane/hooks";
import { useCallback } from "react";
import { useHotkeys } from "react-hotkeys-hook";
import { useNavigate } from "react-router-dom";

Expand All @@ -16,7 +16,7 @@ export function usePagination({
const navigate = useNavigate();
const currentIndex = findIndex(traceId) || 0;

const handleNextTrace = useHandler(() => {
const handleNextTrace = useCallback(() => {
if (currentIndex === undefined) {
return;
}
Expand All @@ -27,20 +27,19 @@ export function usePagination({

const route = getTraceRoute(currentIndex + 1);
navigate(route);
});
}, [currentIndex, navigate, getTraceRoute, maxIndex]);

const handlePrevTrace = useHandler(() => {
const handlePrevTrace = useCallback(() => {
if (currentIndex === undefined) {
return;
}

if (currentIndex === 0) {
return;
}

const route = getTraceRoute(currentIndex - 1);
navigate(route);
});
}, [currentIndex, navigate, getTraceRoute]);

useHotkeys(["J"], () => {
handleNextTrace();
Expand Down
Loading
Loading