mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-26 23:51:14 +02:00
fix: scope runs detail scrolling to the output table
This commit is contained in:
parent
1e801b3ae9
commit
cbf2334fb0
3 changed files with 76 additions and 85 deletions
|
|
@ -2,15 +2,9 @@
|
|||
|
||||
import { Check, Copy, Download } from "lucide-react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { JsonView } from "@/components/json-view";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { downloadCsv, rowsToCsv } from "@/lib/playground/csv";
|
||||
|
||||
|
|
@ -48,10 +42,11 @@ function ResultTable({ items }: { items: Record<string, unknown>[] }) {
|
|||
|
||||
const rows = items.slice(0, MAX_TABLE_ROWS);
|
||||
|
||||
// overscroll-contain keeps table scrolling from chaining to the page.
|
||||
return (
|
||||
<div className="overflow-x-auto rounded-md border border-border/60">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<div className="max-h-[480px] overflow-auto overscroll-contain rounded-md border border-border/60">
|
||||
<table className="w-full caption-bottom text-sm">
|
||||
<TableHeader className="sticky top-0 z-10 bg-background">
|
||||
<TableRow>
|
||||
{columns.map((col) => (
|
||||
<TableHead key={col} className="whitespace-nowrap">
|
||||
|
|
@ -72,7 +67,7 @@ function ResultTable({ items }: { items: Record<string, unknown>[] }) {
|
|||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -152,9 +147,9 @@ export function OutputViewer({ data, filenameBase }: { data: unknown; filenameBa
|
|||
)}
|
||||
</>
|
||||
) : (
|
||||
<pre className="max-h-[480px] overflow-auto rounded-md border border-border/60 bg-muted/20 p-3 text-xs">
|
||||
<code>{json}</code>
|
||||
</pre>
|
||||
<div className="max-h-[480px] overflow-auto overscroll-contain rounded-md border border-border/60 bg-muted/20 p-3">
|
||||
<JsonView src={data} collapsed={2} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { JsonView } from "@/components/json-view";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { useScraperRun } from "@/hooks/use-scraper-runs";
|
||||
import { OutputViewer } from "./output-viewer";
|
||||
|
|
@ -69,7 +70,7 @@ export function RunDetail({ workspaceId, runId }: { workspaceId: number; runId:
|
|||
{run.progress && run.progress.length > 0 && (
|
||||
<div>
|
||||
<h4 className="mb-1.5 text-xs font-medium text-muted-foreground">Progress</h4>
|
||||
<div className="max-h-48 space-y-1 overflow-y-auto rounded-md border border-border/60 bg-background p-3 font-mono text-xs text-muted-foreground">
|
||||
<div className="space-y-1 rounded-md border border-border/60 bg-background p-3 font-mono text-xs text-muted-foreground">
|
||||
{run.progress.map((event, i) => (
|
||||
<div key={i} className="truncate">
|
||||
{progressLine(event)}
|
||||
|
|
@ -81,9 +82,9 @@ export function RunDetail({ workspaceId, runId }: { workspaceId: number; runId:
|
|||
|
||||
<div>
|
||||
<h4 className="mb-1.5 text-xs font-medium text-muted-foreground">Input</h4>
|
||||
<pre className="max-h-64 overflow-auto rounded-md border border-border/60 bg-background p-3 text-xs">
|
||||
<code>{JSON.stringify(run.input ?? {}, null, 2)}</code>
|
||||
</pre>
|
||||
<div className="rounded-md border border-border/60 bg-background p-3">
|
||||
<JsonView src={run.input ?? {}} collapsed={2} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||
import { ChevronDown, ChevronRight, History, Info } from "lucide-react";
|
||||
import { Fragment, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
|
|
@ -13,14 +13,6 @@ import {
|
|||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { scrapersApiService } from "@/lib/apis/scrapers-api.service";
|
||||
import { formatRelativeDate } from "@/lib/format-date";
|
||||
import { PLAYGROUND_PLATFORMS } from "@/lib/playground/catalog";
|
||||
|
|
@ -33,6 +25,12 @@ import { RunStatusBadge } from "./run-status-badge";
|
|||
const PAGE_SIZE = 25;
|
||||
const ALL = "__all__";
|
||||
|
||||
// A grid (not a <table>) keeps an expanded row's detail out of a table-cell,
|
||||
// where overflow/max-height scroll containers get ignored. Tracks are fr/fixed
|
||||
// (never content-based auto) so separate per-row grids stay column-aligned.
|
||||
const ROW_GRID =
|
||||
"grid grid-cols-[2rem_minmax(8rem,2fr)_minmax(4rem,1fr)_minmax(5rem,1fr)_4rem_5rem_5rem_minmax(6rem,1fr)] items-center gap-2 px-3";
|
||||
|
||||
const CAPABILITY_OPTIONS = PLAYGROUND_PLATFORMS.flatMap((platform) =>
|
||||
platform.verbs.map((verb) => verb.name)
|
||||
);
|
||||
|
|
@ -122,63 +120,60 @@ export function RunsTable({ workspaceId }: { workspaceId: number }) {
|
|||
</div>
|
||||
) : (
|
||||
<div className="overflow-hidden rounded-md border border-border/60">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-8" />
|
||||
<TableHead>API</TableHead>
|
||||
<TableHead>Origin</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead className="text-right">Items</TableHead>
|
||||
<TableHead className="text-right">Duration</TableHead>
|
||||
<TableHead className="text-right">Cost</TableHead>
|
||||
<TableHead className="text-right">When</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{runs.map((run) => {
|
||||
const isOpen = expanded === run.id;
|
||||
return (
|
||||
<Fragment key={run.id}>
|
||||
<TableRow
|
||||
className="cursor-pointer"
|
||||
onClick={() => setExpanded(isOpen ? null : run.id)}
|
||||
>
|
||||
<TableCell>
|
||||
{isOpen ? (
|
||||
<ChevronDown className="h-4 w-4 text-muted-foreground" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="font-mono text-xs">{run.capability}</TableCell>
|
||||
<TableCell className="text-xs text-muted-foreground">{run.origin}</TableCell>
|
||||
<TableCell>
|
||||
<RunStatusBadge status={run.status} />
|
||||
</TableCell>
|
||||
<TableCell className="text-right tabular-nums">{run.item_count}</TableCell>
|
||||
<TableCell className="text-right tabular-nums text-muted-foreground">
|
||||
{formatDuration(run.duration_ms)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right tabular-nums text-muted-foreground">
|
||||
{formatCost(run.cost_micros)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right text-xs text-muted-foreground">
|
||||
{formatRelativeDate(run.created_at)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{isOpen && (
|
||||
<TableRow className="hover:bg-transparent">
|
||||
<TableCell colSpan={8} className="p-0">
|
||||
<RunDetail workspaceId={workspaceId} runId={run.id} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<div
|
||||
className={cn(
|
||||
ROW_GRID,
|
||||
"border-b border-border/60 bg-muted/30 py-2 text-xs font-medium text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
<span />
|
||||
<span>API</span>
|
||||
<span>Origin</span>
|
||||
<span>Status</span>
|
||||
<span className="text-right">Items</span>
|
||||
<span className="text-right">Duration</span>
|
||||
<span className="text-right">Cost</span>
|
||||
<span className="text-right">When</span>
|
||||
</div>
|
||||
{runs.map((run) => {
|
||||
const isOpen = expanded === run.id;
|
||||
return (
|
||||
<div key={run.id} className="border-b border-border/60 last:border-b-0">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setExpanded(isOpen ? null : run.id)}
|
||||
className={cn(
|
||||
ROW_GRID,
|
||||
"w-full py-2.5 text-left text-sm transition-colors hover:bg-muted/50"
|
||||
)}
|
||||
>
|
||||
{isOpen ? (
|
||||
<ChevronDown className="h-4 w-4 text-muted-foreground" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
<span className="min-w-0 truncate font-mono text-xs">{run.capability}</span>
|
||||
<span className="min-w-0 truncate text-xs text-muted-foreground">
|
||||
{run.origin}
|
||||
</span>
|
||||
<span>
|
||||
<RunStatusBadge status={run.status} />
|
||||
</span>
|
||||
<span className="text-right tabular-nums">{run.item_count}</span>
|
||||
<span className="text-right tabular-nums text-muted-foreground">
|
||||
{formatDuration(run.duration_ms)}
|
||||
</span>
|
||||
<span className="text-right tabular-nums text-muted-foreground">
|
||||
{formatCost(run.cost_micros)}
|
||||
</span>
|
||||
<span className="text-right text-xs text-muted-foreground">
|
||||
{formatRelativeDate(run.created_at)}
|
||||
</span>
|
||||
</button>
|
||||
{isOpen && <RunDetail workspaceId={workspaceId} runId={run.id} />}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue