diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index d7722a46f0..3eb98e5177 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -35,6 +35,8 @@ jobs: - name: 🔎 Type check run: pnpm run typecheck + env: + NODE_OPTIONS: --max-old-space-size=8192 - name: 🔎 Check exports run: pnpm run check-exports diff --git a/apps/webapp/app/components/primitives/Buttons.tsx b/apps/webapp/app/components/primitives/Buttons.tsx index 67ba3c0924..c2845f1040 100644 --- a/apps/webapp/app/components/primitives/Buttons.tsx +++ b/apps/webapp/app/components/primitives/Buttons.tsx @@ -331,7 +331,7 @@ export const Button = forwardRef( type LinkPropsType = Pick< LinkProps, "to" | "target" | "onClick" | "onMouseDown" | "onMouseEnter" | "onMouseLeave" | "download" -> & { disabled?: boolean } & React.ComponentProps; +> & { disabled?: boolean; replace?: boolean } & React.ComponentProps; export const LinkButton = ({ to, onClick, @@ -340,6 +340,7 @@ export const LinkButton = ({ onMouseLeave, download, disabled = false, + replace, ...props }: LinkPropsType) => { const innerRef = useRef(null); @@ -387,6 +388,7 @@ export const LinkButton = ({ ["trace"]>["events"][0]; type RunsListNavigation = { - runs: Array<{ friendlyId: string }>; + runs: Array<{ friendlyId: string; spanId: string }>; pagination: { next?: string; previous?: string }; - prevPageLastRun?: { friendlyId: string; cursor: string }; - nextPageFirstRun?: { friendlyId: string; cursor: string }; + prevPageLastRun?: { friendlyId: string; spanId: string; cursor: string }; + nextPageFirstRun?: { friendlyId: string; spanId: string; cursor: string }; }; async function getRunsListFromTableState({ @@ -204,6 +204,7 @@ async function getRunsListFromTableState({ if (prevPageResult.runs.length > 0) { runsList.prevPageLastRun = { friendlyId: prevPageResult.runs[0].friendlyId, + spanId: prevPageResult.runs[0].spanId, cursor: currentPageResult.pagination.previous, }; } @@ -222,6 +223,7 @@ async function getRunsListFromTableState({ if (nextPageResult.runs.length > 0) { runsList.nextPageFirstRun = { friendlyId: nextPageResult.runs[0].friendlyId, + spanId: nextPageResult.runs[0].spanId, cursor: currentPageResult.pagination.next, }; } @@ -296,7 +298,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { type LoaderData = SerializeFrom; export default function Page() { - const { run, trace, resizable, maximumLiveReloadingSetting, runsList } = useLoaderData(); + const { run, trace, maximumLiveReloadingSetting, runsList } = useLoaderData(); const organization = useOrganization(); const project = useProject(); const environment = useEnvironment(); @@ -308,8 +310,10 @@ export default function Page() { const tableState = decodeURIComponent(value("tableState") ?? ""); const tableStateSearchParams = new URLSearchParams(tableState); const filters = getRunFiltersFromSearchParams(tableStateSearchParams); + const tabParam = value("tab") ?? undefined; + const spanParam = value("span") ?? undefined; - const [previousRunPath, nextRunPath] = useAdjacentRunPaths({organization, project, environment, tableState, run, runsList}); + const [previousRunPath, nextRunPath] = useAdjacentRunPaths({organization, project, environment, tableState, run, runsList, tabParam, useSpan: !!spanParam}); return ( <> @@ -320,7 +324,7 @@ export default function Page() { text: "Runs", }} title={<> - + {tableState && (
@@ -940,7 +944,6 @@ function TimelineView({ scale, rootSpanStatus, rootStartedAt, - parentRef, timelineScrollRef, virtualizer, events, @@ -1127,7 +1130,8 @@ function TimelineView({ "-ml-[0.5px] h-[0.5625rem] w-px rounded-none", eventBackgroundClassName(node.data) )} - layoutId={`${node.id}-${event.name}`} + layoutId={node.data.isPartial ? `${node.id}-${event.name}` : undefined} + animate={!node.data.isPartial ? false : undefined} /> )} @@ -1145,7 +1149,8 @@ function TimelineView({ "-ml-[0.1562rem] size-[0.3125rem] rounded-full border bg-background-bright", eventBorderClassName(node.data) )} - layoutId={`${node.id}-${event.name}`} + layoutId={node.data.isPartial ? `${node.id}-${event.name}` : undefined} + animate={!node.data.isPartial ? false : undefined} /> )} @@ -1164,7 +1169,8 @@ function TimelineView({ > ) : null} @@ -1201,7 +1207,8 @@ function TimelineView({ "-ml-0.5 size-3 rounded-full border-2 border-background-bright", eventBackgroundClassName(node.data) )} - layoutId={node.id} + layoutId={node.data.isPartial ? node.id : undefined} + animate={!node.data.isPartial ? false : undefined} /> )} @@ -1444,7 +1451,8 @@ function SpanWithDuration({ fadeLeft ? "rounded-r-sm bg-gradient-to-r from-black/50 to-transparent" : "rounded-sm" )} style={{ backgroundSize: "20px 100%", backgroundRepeat: "no-repeat" }} - layoutId={node.id} + layoutId={node.data.isPartial ? node.id : undefined} + animate={!node.data.isPartial ? false : undefined} > {node.data.isPartial && (
{formatDurationMilliseconds(props.durationMs, { style: "short", @@ -1543,12 +1553,11 @@ function KeyboardShortcuts({ expandAllBelowDepth, collapseAllBelowDepth, toggleExpandLevel, - setShowDurations, }: { expandAllBelowDepth: (depth: number) => void; collapseAllBelowDepth: (depth: number) => void; toggleExpandLevel: (depth: number) => void; - setShowDurations: (show: (show: boolean) => boolean) => void; + setShowDurations?: (show: (show: boolean) => boolean) => void; }) { return ( <> @@ -1666,63 +1675,77 @@ function useAdjacentRunPaths({ tableState, run, runsList, + tabParam, + useSpan }: { organization: { slug: string }; project: { slug: string }; environment: { slug: string }; tableState: string; - run: { friendlyId: string }; + run: { friendlyId: string, spanId: string }; runsList: RunsListNavigation | null; + tabParam?: string; + useSpan?: boolean; }): [string | null, string | null] { - return useMemo(() => { - if (!runsList || runsList.runs.length === 0) { - return [null, null]; - } - - const currentIndex = runsList.runs.findIndex((r) => r.friendlyId === run.friendlyId); - - if (currentIndex === -1) { - return [null, null]; - } + if (!runsList || runsList.runs.length === 0) { + return [null, null]; + } - // Determine previous run: use prevPageLastRun if at first position, otherwise use previous run in list - let previousRun: { friendlyId: string } | null = null; - const previousRunTableState = new URLSearchParams(tableState); - if (currentIndex > 0) { - previousRun = runsList.runs[currentIndex - 1]; - } else if (runsList.prevPageLastRun) { - previousRun = runsList.prevPageLastRun; - // Update tableState with the new cursor for the previous page - previousRunTableState.set("cursor", runsList.prevPageLastRun.cursor); - previousRunTableState.set("direction", "backward"); - } + const currentIndex = runsList.runs.findIndex((r) => r.friendlyId === run.friendlyId); + + if (currentIndex === -1) { + return [null, null]; + } - // Determine next run: use nextPageFirstRun if at last position, otherwise use next run in list - let nextRun: { friendlyId: string } | null = null; - const nextRunTableState = new URLSearchParams(tableState); - if (currentIndex < runsList.runs.length - 1) { - nextRun = runsList.runs[currentIndex + 1]; - } else if (runsList.nextPageFirstRun) { - nextRun = runsList.nextPageFirstRun; - // Update tableState with the new cursor for the next page - nextRunTableState.set("cursor", runsList.nextPageFirstRun.cursor); - nextRunTableState.set("direction", "forward"); - } + // Determine previous run: use prevPageLastRun if at first position, otherwise use previous run in list + let previousRun: { friendlyId: string; spanId: string } | null = null; + const previousRunTableState = new URLSearchParams(tableState); + if (currentIndex > 0) { + previousRun = runsList.runs[currentIndex - 1]; + } else if (runsList.prevPageLastRun) { + previousRun = runsList.prevPageLastRun; + // Update tableState with the new cursor for the previous page + previousRunTableState.set("cursor", runsList.prevPageLastRun.cursor); + previousRunTableState.set("direction", "backward"); + } - const previousURLSearchParams = new URLSearchParams(); - previousURLSearchParams.set("tableState", previousRunTableState.toString()); - const previousRunPath = previousRun - ? v3RunPath(organization, project, environment, previousRun, previousURLSearchParams) - : null; + // Determine next run: use nextPageFirstRun if at last position, otherwise use next run in list + let nextRun: { friendlyId: string; spanId: string } | null = null; + const nextRunTableState = new URLSearchParams(tableState); + if (currentIndex < runsList.runs.length - 1) { + nextRun = runsList.runs[currentIndex + 1]; + } else if (runsList.nextPageFirstRun) { + nextRun = runsList.nextPageFirstRun; + // Update tableState with the new cursor for the next page + nextRunTableState.set("cursor", runsList.nextPageFirstRun.cursor); + nextRunTableState.set("direction", "forward"); + } - const nextURLSearchParams = new URLSearchParams(); - nextURLSearchParams.set("tableState", nextRunTableState.toString()); - const nextRunPath = nextRun - ? v3RunPath(organization, project, environment, nextRun, nextURLSearchParams) - : null; + const previousURLSearchParams = new URLSearchParams(); + previousURLSearchParams.set("tableState", previousRunTableState.toString()); + if (previousRun && useSpan) { + previousURLSearchParams.set("span", previousRun.spanId); + } + if (tabParam && useSpan) { + previousURLSearchParams.set("tab", tabParam); + } + const previousRunPath = previousRun + ? v3RunPath(organization, project, environment, previousRun, previousURLSearchParams) + : null; + + const nextURLSearchParams = new URLSearchParams(); + nextURLSearchParams.set("tableState", nextRunTableState.toString()); + if (nextRun && useSpan) { + nextURLSearchParams.set("span", nextRun.spanId); + } + if (tabParam && useSpan) { + nextURLSearchParams.set("tab", tabParam); + } + const nextRunPath = nextRun + ? v3RunPath(organization, project, environment, nextRun, nextURLSearchParams) + : null; - return [previousRunPath, nextRunPath]; - }, [organization, project, environment, tableState, run.friendlyId, runsList]); + return [previousRunPath, nextRunPath]; } @@ -1741,6 +1764,7 @@ function PreviousRunButton({ to }: { to: string | null }) { shortcut={{ key: "[" }} tooltip="Previous Run" disabled={!to} + replace />
); @@ -1761,6 +1785,7 @@ function NextRunButton({ to }: { to: string | null }) { shortcut={{ key: "]" }} tooltip="Next Run" disabled={!to} + replace />
); diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs._index/route.tsx index 8c41f0ceac..8f80124707 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs._index/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs._index/route.tsx @@ -55,6 +55,7 @@ import { v3CreateBulkActionPath, v3ProjectPath, v3TestPath, + v3TestTaskPath, } from "~/utils/pathBuilder"; import { ListPagination } from "../../components/ListPagination"; import { CreateBulkActionInspector } from "../resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.bulkaction"; @@ -235,7 +236,13 @@ function RunsList({ list.possibleTasks.length === 0 ? ( ) : ( - + t.slug === list.filters.tasks[0]) + : undefined + } + /> ) ) : (
@@ -339,7 +346,7 @@ function CreateFirstTaskInstructions() { ); } -function RunTaskInstructions() { +function RunTaskInstructions({ task }: { task?: { slug: string } }) { const organization = useOrganization(); const project = useProject(); const environment = useEnvironment(); @@ -352,7 +359,11 @@ function RunTaskInstructions() { Perform a test run with a payload directly from the dashboard.