Skip to content

Commit 1508eb6

Browse files
committed
Use packet display in logs details
Update packet display to highlight text
1 parent 255bec5 commit 1508eb6

File tree

4 files changed

+137
-92
lines changed

4 files changed

+137
-92
lines changed

apps/webapp/app/components/code/CodeBlock.tsx

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ type CodeBlockProps = {
6262

6363
/** Whether to show the open in modal button */
6464
showOpenInModal?: boolean;
65+
66+
/** Search term to highlight in the code */
67+
searchTerm?: string;
6568
};
6669

6770
const dimAmount = 0.5;
@@ -200,6 +203,7 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
200203
showChrome = false,
201204
fileName,
202205
rowTitle,
206+
searchTerm,
203207
...props
204208
}: CodeBlockProps,
205209
ref
@@ -338,6 +342,7 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
338342
className="px-2 py-3"
339343
preClassName="text-xs"
340344
isWrapped={isWrapped}
345+
searchTerm={searchTerm}
341346
/>
342347
) : (
343348
<div
@@ -449,6 +454,7 @@ type HighlightCodeProps = {
449454
className?: string;
450455
preClassName?: string;
451456
isWrapped: boolean;
457+
searchTerm?: string;
452458
};
453459

454460
function HighlightCode({
@@ -461,6 +467,7 @@ function HighlightCode({
461467
className,
462468
preClassName,
463469
isWrapped,
470+
searchTerm,
464471
}: HighlightCodeProps) {
465472
const [isLoaded, setIsLoaded] = useState(false);
466473

@@ -552,6 +559,47 @@ function HighlightCode({
552559
<div className="flex-1">
553560
{line.map((token, key) => {
554561
const tokenProps = getTokenProps({ token, key });
562+
563+
// Highlight search term matches in token
564+
let content: React.ReactNode = token.content;
565+
if (searchTerm && searchTerm.trim() !== "" && token.content) {
566+
const escapedSearch = searchTerm.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
567+
const regex = new RegExp(escapedSearch, "gi");
568+
569+
const parts: React.ReactNode[] = [];
570+
let lastIndex = 0;
571+
let match;
572+
let matchCount = 0;
573+
574+
while ((match = regex.exec(token.content)) !== null) {
575+
if (match.index > lastIndex) {
576+
parts.push(token.content.substring(lastIndex, match.index));
577+
}
578+
parts.push(
579+
<span
580+
key={`match-${matchCount}`}
581+
style={{
582+
backgroundColor: "#facc15",
583+
color: "#000000",
584+
fontWeight: "500",
585+
}}
586+
>
587+
{match[0]}
588+
</span>
589+
);
590+
lastIndex = regex.lastIndex;
591+
matchCount++;
592+
}
593+
594+
if (lastIndex < token.content.length) {
595+
parts.push(token.content.substring(lastIndex));
596+
}
597+
598+
if (parts.length > 0) {
599+
content = parts;
600+
}
601+
}
602+
555603
return (
556604
<span
557605
key={key}
@@ -560,7 +608,9 @@ function HighlightCode({
560608
color: tokenProps?.style?.color as string,
561609
...tokenProps.style,
562610
}}
563-
/>
611+
>
612+
{content}
613+
</span>
564614
);
565615
})}
566616
</div>

apps/webapp/app/components/logs/LogDetailView.tsx

Lines changed: 78 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { RunTag } from "~/components/runs/v3/RunTag";
3131
import { formatCurrencyAccurate } from "~/utils/numberFormatter";
3232
import type { TaskRunStatus } from "@trigger.dev/database";
3333
import { PacketDisplay } from "~/components/runs/v3/PacketDisplay";
34+
import type { ReactNode } from "react";
3435

3536
// Types for the run context endpoint response
3637
type RunContextData = {
@@ -64,6 +65,7 @@ type LogDetailViewProps = {
6465
// If we have the log entry from the list, we can display it immediately
6566
initialLog?: LogEntry;
6667
onClose: () => void;
68+
searchTerm?: string;
6769
};
6870

6971
type TabType = "details" | "run";
@@ -128,25 +130,61 @@ function getKindLabel(kind: string): string {
128130
}
129131
}
130132

131-
// Helper to unescape newlines in JSON strings for better readability
132-
function unescapeNewlines(obj: unknown): unknown {
133-
if (typeof obj === "string") {
134-
return obj.replace(/\\n/g, "\n");
135-
}
136-
if (Array.isArray(obj)) {
137-
return obj.map(unescapeNewlines);
133+
function formatStringJSON(str: string): string {
134+
return str
135+
.replace(/\\n/g, "\n") // Converts literal "\n" to newline
136+
.replace(/\\t/g, "\t"); // Converts literal "\t" to tab
137+
}
138+
139+
// Highlight search term in JSON string - returns React nodes with highlights
140+
function highlightJsonWithSearch(json: string, searchTerm: string | undefined): ReactNode {
141+
if (!searchTerm || searchTerm.trim() === "") {
142+
return json;
138143
}
139-
if (obj !== null && typeof obj === "object") {
140-
const result: Record<string, unknown> = {};
141-
for (const [key, value] of Object.entries(obj)) {
142-
result[key] = unescapeNewlines(value);
144+
145+
// Escape special regex characters in the search term
146+
const escapedSearch = searchTerm.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
147+
const regex = new RegExp(escapedSearch, "gi");
148+
149+
const parts: ReactNode[] = [];
150+
let lastIndex = 0;
151+
let match;
152+
let matchCount = 0;
153+
154+
while ((match = regex.exec(json)) !== null) {
155+
// Add text before match
156+
if (match.index > lastIndex) {
157+
parts.push(json.substring(lastIndex, match.index));
143158
}
144-
return result;
159+
// Add highlighted match with inline styles
160+
parts.push(
161+
<span
162+
key={`match-${matchCount}`}
163+
style={{
164+
backgroundColor: "#facc15",
165+
color: "#000000",
166+
fontWeight: "500",
167+
borderRadius: "0.25rem",
168+
padding: "0 0.125rem",
169+
}}
170+
>
171+
{match[0]}
172+
</span>
173+
);
174+
lastIndex = regex.lastIndex;
175+
matchCount++;
145176
}
146-
return obj;
177+
178+
// Add remaining text
179+
if (lastIndex < json.length) {
180+
parts.push(json.substring(lastIndex));
181+
}
182+
183+
return parts.length > 0 ? parts : json;
147184
}
148185

149-
export function LogDetailView({ logId, initialLog, onClose }: LogDetailViewProps) {
186+
187+
export function LogDetailView({ logId, initialLog, onClose, searchTerm }: LogDetailViewProps) {
150188
const organization = useOrganization();
151189
const project = useProject();
152190
const environment = useEnvironment();
@@ -262,7 +300,7 @@ export function LogDetailView({ logId, initialLog, onClose }: LogDetailViewProps
262300
{/* Content */}
263301
<div className="flex-1 overflow-y-auto p-4">
264302
{activeTab === "details" && (
265-
<DetailsTab log={log} runPath={runPath} />
303+
<DetailsTab log={log} runPath={runPath} searchTerm={searchTerm} />
266304
)}
267305
{activeTab === "run" && (
268306
<RunTab log={log} runPath={runPath} />
@@ -272,89 +310,39 @@ export function LogDetailView({ logId, initialLog, onClose }: LogDetailViewProps
272310
);
273311
}
274312

275-
function DetailsTab({ log, runPath }: { log: LogEntry; runPath: string }) {
276-
// Extract metadata and attributes - handle both parsed and raw string forms
313+
function DetailsTab({ log, runPath, searchTerm }: { log: LogEntry; runPath: string; searchTerm?: string }) {
277314
const logWithExtras = log as LogEntry & {
278315
metadata?: Record<string, unknown>;
279-
rawMetadata?: string;
280316
attributes?: Record<string, unknown>;
281-
rawAttributes?: string;
282317
};
283318

284-
const rawMetadata = logWithExtras.rawMetadata;
285-
const rawAttributes = logWithExtras.rawAttributes;
286319

287320
let metadata: Record<string, unknown> | null = null;
288321
let beautifiedMetadata: string | null = null;
322+
let beautifiedAttributes: string | null = null;
323+
289324
if (logWithExtras.metadata) {
290-
metadata = logWithExtras.metadata;
291-
const unescaped = unescapeNewlines(metadata);
292-
beautifiedMetadata = JSON.stringify(unescaped, null, 2);
293-
} else if (rawMetadata) {
294-
try {
295-
metadata = JSON.parse(rawMetadata) as Record<string, unknown>;
296-
const unescaped = unescapeNewlines(metadata);
297-
beautifiedMetadata = JSON.stringify(unescaped, null, 2);
298-
} catch {
299-
// Ignore parse errors
300-
}
325+
beautifiedMetadata = JSON.stringify(logWithExtras.metadata, null, 2);
326+
beautifiedMetadata = formatStringJSON(beautifiedMetadata);
301327
}
302328

303-
let attributes: Record<string, unknown> | null = null;
304-
let beautifiedAttributes: string | null = null;
305329
if (logWithExtras.attributes) {
306-
attributes = logWithExtras.attributes;
307-
const unescaped = unescapeNewlines(attributes);
308-
beautifiedAttributes = JSON.stringify(unescaped, null, 2);
309-
} else if (rawAttributes) {
310-
try {
311-
attributes = JSON.parse(rawAttributes) as Record<string, unknown>;
312-
const unescaped = unescapeNewlines(attributes);
313-
beautifiedAttributes = JSON.stringify(unescaped, null, 2);
314-
} catch {
315-
// Ignore parse errors
316-
}
330+
beautifiedAttributes = JSON.stringify(logWithExtras.attributes, null, 2);
331+
beautifiedAttributes = formatStringJSON(beautifiedAttributes);
317332
}
318333

319-
const errorInfo = metadata?.error as { message?: string; attributes?: Record<string, unknown> } | undefined;
320-
321-
// Check if we should show metadata/attributes sections
322-
const showMetadata = rawMetadata && rawMetadata !== "{}";
323-
const showAttributes = rawAttributes && rawAttributes !== "{}";
334+
const showMetadata = beautifiedMetadata && beautifiedMetadata !== "{}";
335+
const showAttributes = beautifiedAttributes && beautifiedAttributes !== "{}";
324336

325337
return (
326338
<>
327-
{/* Error Details - show prominently for error status */}
328-
{errorInfo && (
329-
<div className="mb-6">
330-
<Header3 className="mb-2 text-error">Error Details</Header3>
331-
<div className="rounded-md border border-error/30 bg-error/5 p-3">
332-
{errorInfo.message && (
333-
<pre className="mb-3 whitespace-pre-wrap break-words font-mono text-sm text-error">
334-
{errorInfo.message}
335-
</pre>
336-
)}
337-
{errorInfo.attributes && Object.keys(errorInfo.attributes).length > 0 && (
338-
<div className="border-t border-error/20 pt-3">
339-
<Paragraph variant="extra-small" className="mb-2 text-text-dimmed">
340-
Error Attributes
341-
</Paragraph>
342-
<pre className="whitespace-pre-wrap break-words font-mono text-xs text-text-bright">
343-
{JSON.stringify(errorInfo.attributes, null, 2)}
344-
</pre>
345-
</div>
346-
)}
347-
</div>
348-
</div>
349-
)}
350-
351339
{/* Message */}
352340
<div className="mb-6">
353341
<Header3 className="mb-2">Message</Header3>
354342
<div className="rounded-md border border-grid-dimmed bg-charcoal-850 p-3">
355-
<pre className="whitespace-pre-wrap break-words font-mono text-sm text-text-bright">
356-
{log.message}
357-
</pre>
343+
<div className="whitespace-pre-wrap break-words font-mono text-sm text-text-bright">
344+
{highlightJsonWithSearch(log.message, searchTerm)}
345+
</div>
358346
</div>
359347
</div>
360348

@@ -363,11 +351,7 @@ function DetailsTab({ log, runPath }: { log: LogEntry; runPath: string }) {
363351
<Header3 className="mb-2">Run</Header3>
364352
<div className="flex items-center gap-3">
365353
<span className="font-mono text-sm text-text-bright">{log.runId}</span>
366-
<Link
367-
to={runPath}
368-
target="_blank"
369-
rel="noopener noreferrer"
370-
>
354+
<Link to={runPath} target="_blank" rel="noopener noreferrer">
371355
<Button variant="tertiary/small" LeadingIcon={ArrowTopRightOnSquareIcon}>
372356
View in Run
373357
</Button>
@@ -431,14 +415,24 @@ function DetailsTab({ log, runPath }: { log: LogEntry; runPath: string }) {
431415
{/* Metadata - only available in full log detail */}
432416
{showMetadata && beautifiedMetadata && (
433417
<div className="mb-6">
434-
<PacketDisplay data={beautifiedMetadata} dataType="application/json" title="Metadata" />
418+
<PacketDisplay
419+
data={beautifiedMetadata}
420+
dataType="application/json"
421+
title="Metadata"
422+
searchTerm={searchTerm}
423+
/>
435424
</div>
436425
)}
437426

438427
{/* Attributes - only available in full log detail */}
439428
{showAttributes && beautifiedAttributes && (
440429
<div className="mb-6">
441-
<PacketDisplay data={beautifiedAttributes} dataType="application/json" title="Attributes" />
430+
<PacketDisplay
431+
data={beautifiedAttributes}
432+
dataType="application/json"
433+
title="Attributes"
434+
searchTerm={searchTerm}
435+
/>
442436
</div>
443437
)}
444438
</>

apps/webapp/app/components/runs/v3/PacketDisplay.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ export function PacketDisplay({
1111
data,
1212
dataType,
1313
title,
14+
searchTerm,
1415
}: {
1516
data: string;
1617
dataType: string;
1718
title: string;
19+
searchTerm?: string;
1820
}) {
1921
switch (dataType) {
2022
case "application/store": {
@@ -51,6 +53,7 @@ export function PacketDisplay({
5153
maxLines={20}
5254
showLineNumbers={false}
5355
showTextWrapping
56+
searchTerm={searchTerm}
5457
/>
5558
);
5659
}
@@ -63,6 +66,7 @@ export function PacketDisplay({
6366
maxLines={20}
6467
showLineNumbers={false}
6568
showTextWrapping
69+
searchTerm={searchTerm}
6670
/>
6771
);
6872
}

0 commit comments

Comments
 (0)