Skip to content

Commit

Permalink
Extract DG and RCS panels and show both DG results
Browse files Browse the repository at this point in the history
  • Loading branch information
markerikson committed Jul 8, 2024
1 parent afd7ccc commit cf3f739
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import React, { useContext, useState } from "react";
import { useImperativeCacheValue } from "suspense";

import { Button } from "replay-next/components/Button";
import Expandable from "replay-next/components/Expandable";
import Icon from "replay-next/components/Icon";
import Loader from "replay-next/components/Loader";
import { JsonViewer } from "replay-next/components/SyntaxHighlighter/JsonViewer";
import { useMostRecentLoadedPause } from "replay-next/src/hooks/useMostRecentLoadedPause";
import { ReplayClientContext } from "shared/client/ReplayClientContext";
import { DependencyGraphMode } from "shared/client/types";
import { seek } from "ui/actions/timeline";
import { useAppDispatch } from "ui/setup/hooks";
import { depGraphCache, reactComponentStackCache } from "ui/suspense/depGraphCache";
Expand All @@ -33,48 +35,82 @@ function JumpToDefinitionButton({ point }: { point?: TimeStampedPoint }) {
);
}

export function ReactComponentStack() {
const { point, time, pauseId } = useMostRecentLoadedPause() ?? {};
function ReactComponentStack({ point }: { point?: TimeStampedPoint }) {
const replayClient = useContext(ReplayClientContext);
const [currentPoint, setCurrentPoint] = useState<ExecutionPoint | null>(null);

const { status: depGraphStatus, value: depGraphValue } = useImperativeCacheValue(
depGraphCache,
replayClient,
currentPoint
);

const { status: reactStackStatus, value: reactStackValue } = useImperativeCacheValue(
reactComponentStackCache,
replayClient,
currentPoint
point ?? null
);

if (!pauseId || !point) {
return <div>Not paused at a point</div>;
let reactStackContent: React.ReactNode = undefined;

if (reactStackStatus === "rejected") {
reactStackContent = <div>Error loading dependencies</div>;
} else if (reactStackStatus === "pending") {
reactStackContent = <Loader />;
} else {
reactStackContent = (
<div className="m-1 flex grow flex-col border">
<h3 className="text-base font-bold">React Component Stack</h3>
{reactStackValue?.map((entry, index) => {
const jumpButton = entry.point ? <JumpToDefinitionButton point={entry} /> : null;
return (
<div key={index} className="m-1 flex flex-col">
<div title={entry.parentLocation.url}>
&lt;{entry.componentName}&gt; {jumpButton}
</div>
</div>
);
})}
</div>
);
}

return <div className="react-component-stack flex flex-col">{reactStackContent}</div>;
}

function DepGraphDisplay({
point,
mode,
title,
}: {
point: ExecutionPoint;
mode?: DependencyGraphMode;
title: string;
}) {
const replayClient = useContext(ReplayClientContext);

const { status: depGraphStatus, value: depGraphValue } = useImperativeCacheValue(
depGraphCache,
replayClient,
point,
mode ?? null
);

let depGraphContent: React.ReactNode = undefined;
let formattedDepGraphContent: React.ReactNode = undefined;
let reactStackContent: React.ReactNode = undefined;

if (depGraphStatus === "rejected") {
depGraphContent = <div>Error loading dependencies</div>;
} else if (depGraphStatus === "pending") {
depGraphContent = <Loader />;
} else {
const valueDescending = depGraphValue?.slice().reverse();

depGraphContent = (
<div className="m-1 grow border ">
<h3 className="text-sm font-bold">Dependency Graph JSON</h3>
<JsonViewer jsonText={JSON.stringify(depGraphValue, null, 2)} />
<Expandable header={<h4 className="inline text-sm font-bold">Dependency Graph JSON</h4>}>
<JsonViewer jsonText={JSON.stringify(valueDescending, null, 2)} />
</Expandable>
</div>
);

formattedDepGraphContent = (
<div className="m-1 grow border ">
<h3 className="text-sm font-bold">Dependency Graph Formatted</h3>
<h4 className="text-sm font-bold">Dependency Graph Formatted</h4>
<div className="m-1 flex flex-col">
{depGraphValue?.map((entry, index) => {
{valueDescending?.map((entry, index) => {
let jumpButton: React.ReactNode = undefined;

if (entry.point && entry.time) {
Expand All @@ -94,36 +130,38 @@ export function ReactComponentStack() {
);
}

if (reactStackStatus === "rejected") {
reactStackContent = <div>Error loading dependencies</div>;
} else if (reactStackStatus === "pending") {
reactStackContent = <Loader />;
} else {
reactStackContent = (
<div className="m-1 flex grow flex-col border">
<h3 className="text-sm font-bold">React Component Stack</h3>
{reactStackValue?.map((entry, index) => {
const jumpButton = entry.point ? <JumpToDefinitionButton point={entry} /> : null;
return (
<div key={index} className="m-1 flex flex-col">
<div title={entry.parentLocation.url}>
&lt;{entry.componentName}&gt; {jumpButton}
</div>
</div>
);
})}
</div>
);
return (
<div className="react-component-stack m-1 flex flex-col">
<h3 className="text-base font-bold">{title}</h3>
{formattedDepGraphContent}
{depGraphContent}
</div>
);
}

export function DepGraphPrototypePanel() {
const { point, time, pauseId } = useMostRecentLoadedPause() ?? {};
const replayClient = useContext(ReplayClientContext);
const [currentPoint, setCurrentPoint] = useState<ExecutionPoint | null>(null);

if (!pauseId || !point || !time) {
return <div>Not paused at a point</div>;
}

let timeStampedPoint: TimeStampedPoint = { point, time };

return (
<div className="react-component-stack flex flex-col">
<Button className="self-start" onClick={() => setCurrentPoint(point)}>
Load dependencies
</Button>
{reactStackContent}
{formattedDepGraphContent}
{depGraphContent}
<ReactComponentStack point={timeStampedPoint} />
<DepGraphDisplay point={point} title="Dep Graph (Regular)" />
<DepGraphDisplay
point={point}
mode={DependencyGraphMode.ReactParentRenders}
title="Dep Graph (React)"
/>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import NewFrames from "./Frames/NewFrames";
import FrameTimeline from "./FrameTimeline";
import LogpointsPane from "./LogpointsPane";
import NewScopes from "./NewScopes";
import { ReactComponentStack } from "./ReactComponentStack";
import { DepGraphPrototypePanel } from "./ReactComponentStack";

import { Accordion, AccordionPane } from "@recordreplay/accordion";

Expand Down Expand Up @@ -65,7 +65,7 @@ export default function SecondaryPanes() {
expanded={reactStackVisible}
onToggle={() => setReactStackVisible(!reactStackVisible)}
>
{currentPoint && <ReactComponentStack />}
{currentPoint && <DepGraphPrototypePanel />}
</AccordionPane>
<AccordionPane
header="Scopes"
Expand Down
66 changes: 56 additions & 10 deletions src/ui/suspense/depGraphCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,32 @@ import {
getSourceIdToDisplayForUrl,
getSourceToDisplayForUrl,
} from "replay-next/src/utils/sources";
import { DependencyChainStep, ReplayClientInterface } from "shared/client/types";
import {
DependencyChainStep,
DependencyGraphMode,
ReplayClientInterface,
} from "shared/client/types";
import { formatFunctionDetailsFromLocation } from "ui/actions/eventListeners/eventListenerUtils";
import { findFunctionOutlineForLocation } from "ui/actions/eventListeners/jumpToCode";

import { formattedPointStackCache } from "./frameCache";

export const depGraphCache: Cache<
[replayClient: ReplayClientInterface, point: ExecutionPoint | null],
[
replayClient: ReplayClientInterface,
point: ExecutionPoint | null,
mode: DependencyGraphMode | null
],
DependencyChainStep[] | null
> = createCache({
config: { immutable: true },
debugLabel: "depGraphCache",
getKey: ([replayClient, point]) => point ?? "null",
load: async ([replayClient, point]) => {
getKey: ([replayClient, point, mode]) => `${point ?? "null"}:${mode ?? "none"}`,
load: async ([replayClient, point, mode]) => {
if (!point) {
return null;
}
const dependencies = await replayClient.getDependencies(point);
const dependencies = await replayClient.getDependencies(point, mode ?? undefined);

console.log("Deps for point: ", point, dependencies);
return dependencies;
Expand All @@ -49,25 +57,63 @@ interface ReactComponentStackEntry extends TimeStampedPoint {
componentName: string;
}

export const REACT_DOM_SOURCE_URLS = [
// React 18 and earlier
"react-dom.",
// React 19
"react-dom-client.",
];

export const isReactUrl = (url?: string) =>
REACT_DOM_SOURCE_URLS.some(partial => url?.includes(partial));

export const reactComponentStackCache: Cache<
[replayClient: ReplayClientInterface, point: ExecutionPoint | null],
[replayClient: ReplayClientInterface, point: TimeStampedPoint | null],
ReactComponentStackEntry[] | null
> = createCache({
config: { immutable: true },
debugLabel: "reactComponentStackCache",
getKey: ([replayClient, point]) => point ?? "null",
getKey: ([replayClient, point]) => point?.point ?? "null",
load: async ([replayClient, point]) => {
const dependencies = await depGraphCache.readAsync(replayClient, point);
if (!point) {
return null;
}

const currentPointStack = await formattedPointStackCache.readAsync(
replayClient,
{
...point,
frameDepth: 2,
},
[]
);

console.log("Point stack for point: ", point, currentPointStack);

const precedingFrame = currentPointStack.allFrames[1];

if (!isReactUrl(precedingFrame?.url)) {
return null;
}

const originalDependencies = await depGraphCache.readAsync(replayClient, point.point, null);
const reactDependencies = await depGraphCache.readAsync(
replayClient,
point.point,
DependencyGraphMode.ReactParentRenders
);

console.log("Deps: ", { originalDependencies, reactDependencies });

if (!dependencies) {
if (!originalDependencies || !reactDependencies) {
return null;
}

const componentStack: ReactComponentStackEntry[] = [];

const sourcesById = await sourcesByIdCache.readAsync(replayClient);

const remainingDepEntries = dependencies.slice().reverse();
const remainingDepEntries = reactDependencies.slice().reverse();
while (remainingDepEntries.length) {
const depEntry = remainingDepEntries.shift()!;

Expand Down

0 comments on commit cf3f739

Please sign in to comment.