@@ -3,7 +3,12 @@ import { type MetaFunction } from "@remix-run/react";
33import { ServiceValidationError } from "~/v3/services/baseService.server" ;
44import { TypedAwait , typeddefer , useTypedLoaderData } from "remix-typedjson" ;
55import { requireUser } from "~/services/session.server" ;
6- import { EnvironmentParamSchema , v3ErrorsPath } from "~/utils/pathBuilder" ;
6+ import {
7+ EnvironmentParamSchema ,
8+ v3CreateBulkActionPath ,
9+ v3ErrorsPath ,
10+ v3RunsPath ,
11+ } from "~/utils/pathBuilder" ;
712import { findProjectBySlug } from "~/models/project.server" ;
813import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server" ;
914import {
@@ -21,17 +26,26 @@ import { Suspense, useMemo } from "react";
2126import { Spinner } from "~/components/primitives/Spinner" ;
2227import { Paragraph } from "~/components/primitives/Paragraph" ;
2328import { Callout } from "~/components/primitives/Callout" ;
24- import { Header2 , Header3 } from "~/components/primitives/Headers" ;
29+ import { Header1 , Header2 , Header3 } from "~/components/primitives/Headers" ;
2530import { formatDistanceToNow } from "date-fns" ;
2631import { formatNumberCompact } from "~/utils/numberFormatter" ;
2732import * as Property from "~/components/primitives/PropertyTable" ;
2833import { TaskRunsTable } from "~/components/runs/v3/TaskRunsTable" ;
29- import { DateTime } from "~/components/primitives/DateTime" ;
34+ import { DateTime , RelativeDateTime } from "~/components/primitives/DateTime" ;
3035import { ErrorId } from "@trigger.dev/core/v3/isomorphic" ;
3136import { Chart , type ChartConfig } from "~/components/primitives/charts/ChartCompound" ;
3237import { TimeFilter , timeFilterFromTo } from "~/components/runs/v3/SharedFilters" ;
3338import { useOptimisticLocation } from "~/hooks/useOptimisticLocation" ;
3439import { DirectionSchema , ListPagination } from "~/components/ListPagination" ;
40+ import { LinkButton } from "~/components/primitives/Buttons" ;
41+ import { ListCheckedIcon } from "~/assets/icons/ListCheckedIcon" ;
42+ import { useOrganization } from "~/hooks/useOrganizations" ;
43+ import { useProject } from "~/hooks/useProject" ;
44+ import { useEnvironment } from "~/hooks/useEnvironment" ;
45+ import { RunsIcon } from "~/assets/icons/RunsIcon" ;
46+ import { TaskRunListSearchFilters } from "~/components/runs/v3/RunFilters" ;
47+ import { useSearchParams } from "~/hooks/useSearchParam" ;
48+ import { CopyableText } from "~/components/primitives/CopyableText" ;
3549
3650export const meta : MetaFunction < typeof loader > = ( { data } ) => {
3751 return [
@@ -218,6 +232,11 @@ function ErrorGroupDetail({
218232 envParam : string ;
219233 fingerprint : string ;
220234} ) {
235+ const { value } = useSearchParams ( ) ;
236+ const organization = useOrganization ( ) ;
237+ const project = useProject ( ) ;
238+ const environment = useEnvironment ( ) ;
239+
221240 if ( ! errorGroup ) {
222241 return (
223242 < div className = "flex h-full items-center justify-center" >
@@ -231,37 +250,58 @@ function ErrorGroupDetail({
231250 ) ;
232251 }
233252
253+ const fromValue = value ( "from" ) ?? undefined ;
254+ const toValue = value ( "to" ) ?? undefined ;
255+
256+ const filters : TaskRunListSearchFilters = {
257+ period : value ( "period" ) ?? undefined ,
258+ from : fromValue ? parseInt ( fromValue , 10 ) : undefined ,
259+ to : toValue ? parseInt ( toValue , 10 ) : undefined ,
260+ rootOnly : false ,
261+ errorId : ErrorId . toFriendlyId ( fingerprint ) ,
262+ } ;
263+
234264 return (
235- < div className = "grid h-full grid-rows-[auto_16rem_1fr ] overflow-hidden" >
265+ < div className = "grid h-full grid-rows-[auto_12rem_1fr ] overflow-hidden" >
236266 { /* Error Summary */ }
237- < div className = "border-b border-grid-bright p-4" >
238- < Header2 className = "mb-4" > { errorGroup . errorMessage } </ Header2 >
239-
240- < div className = "mb-4" >
241- < TimeFilter defaultPeriod = "7d" labelName = "Occurred" />
267+ < div className = "flex flex-col gap-2 border-b border-grid-bright bg-background-bright p-4" >
268+ < div className = "flex flex-col gap-0.5" >
269+ < Header2 > { errorGroup . errorMessage } </ Header2 >
270+ < Header3 > { formatNumberCompact ( errorGroup . count ) } total occurrences</ Header3 >
242271 </ div >
243272
244- < div className = "grid grid-cols-3 gap-x-12 gap-y-1 " >
273+ < div className = "grid grid-cols-[auto_auto_auto_1fr] gap-x-12 gap-y-0.5 " >
245274 < Property . Table >
246275 < Property . Item >
247276 < Property . Label > ID</ Property . Label >
248277 < Property . Value >
249- < span className = "font-mono" > { ErrorId . toFriendlyId ( errorGroup . fingerprint ) } </ span >
278+ < CopyableText value = { ErrorId . toFriendlyId ( errorGroup . fingerprint ) } / >
250279 </ Property . Value >
251280 </ Property . Item >
252281 < Property . Item >
253282 < Property . Label > Task</ Property . Label >
254283 < Property . Value >
255- < span className = "font-mono" > { errorGroup . taskIdentifier } </ span >
284+ < CopyableText value = { errorGroup . taskIdentifier } / >
256285 </ Property . Value >
257286 </ Property . Item >
258287 </ Property . Table >
259288
260289 < Property . Table >
261290 < Property . Item >
262- < Property . Label > Total occurrences</ Property . Label >
263- < Property . Value > { formatNumberCompact ( errorGroup . count ) } </ Property . Value >
291+ < Property . Label > First seen</ Property . Label >
292+ < Property . Value >
293+ < DateTime date = { errorGroup . firstSeen } />
294+ </ Property . Value >
295+ </ Property . Item >
296+ < Property . Item >
297+ < Property . Label > Last seen</ Property . Label >
298+ < Property . Value >
299+ < RelativeDateTime date = { errorGroup . lastSeen } />
300+ </ Property . Value >
264301 </ Property . Item >
302+ </ Property . Table >
303+
304+ < Property . Table >
265305 { errorGroup . affectedVersions . length > 0 && (
266306 < Property . Item >
267307 < Property . Label > Affected versions</ Property . Label >
@@ -273,27 +313,15 @@ function ErrorGroupDetail({
273313 </ Property . Item >
274314 ) }
275315 </ Property . Table >
276-
277- < Property . Table >
278- < Property . Item >
279- < Property . Label > First seen</ Property . Label >
280- < Property . Value >
281- < DateTime date = { errorGroup . firstSeen } />
282- </ Property . Value >
283- </ Property . Item >
284- < Property . Item >
285- < Property . Label > Last seen</ Property . Label >
286- < Property . Value >
287- { formatDistanceToNow ( errorGroup . lastSeen , { addSuffix : true } ) }
288- </ Property . Value >
289- </ Property . Item >
290- </ Property . Table >
291316 </ div >
292317 </ div >
293318
294319 { /* Activity chart */ }
295- < div className = "flex flex-col overflow-hidden border-b border-grid-bright px-4 py-3" >
296- < Header3 className = "mb-2 shrink-0" > Activity</ Header3 >
320+ < div className = "flex flex-col gap-3 overflow-hidden border-b border-grid-bright px-4 py-3" >
321+ < div className = "flex items-center" >
322+ < TimeFilter defaultPeriod = "7d" labelName = "Occurred" />
323+ </ div >
324+
297325 < Suspense fallback = { < ActivityChartBlankState /> } >
298326 < TypedAwait resolve = { activity } errorElement = { < ActivityChartBlankState /> } >
299327 { ( result ) =>
@@ -311,7 +339,32 @@ function ErrorGroupDetail({
311339 < div className = "flex flex-col gap-1 overflow-y-hidden" >
312340 < div className = "flex items-center justify-between px-4" >
313341 < Header3 className = "mb-1 mt-2" > Runs</ Header3 >
314- { runList && < ListPagination list = { runList } /> }
342+ { runList && (
343+ < div className = "flex items-center gap-2" >
344+ < LinkButton
345+ variant = "secondary/small"
346+ to = { v3RunsPath ( organization , project , environment , filters ) }
347+ LeadingIcon = { RunsIcon }
348+ >
349+ View all runs
350+ </ LinkButton >
351+ < LinkButton
352+ variant = "secondary/small"
353+ to = { v3CreateBulkActionPath (
354+ organization ,
355+ project ,
356+ environment ,
357+ filters ,
358+ "filter" ,
359+ "replay"
360+ ) }
361+ LeadingIcon = { ListCheckedIcon }
362+ >
363+ Bulk replay…
364+ </ LinkButton >
365+ < ListPagination list = { runList } />
366+ </ div >
367+ ) }
315368 </ div >
316369 { runList ? (
317370 < TaskRunsTable
0 commit comments