@@ -19,7 +19,7 @@ import { $replica } from "~/db.server";
1919import { logsClickhouseClient , clickhouseClient } from "~/services/clickhouseInstance.server" ;
2020import { NavBar , PageTitle } from "~/components/primitives/PageHeader" ;
2121import { PageBody , PageContainer } from "~/components/layout/AppLayout" ;
22- import { Suspense } from "react" ;
22+ import { Suspense , useMemo } from "react" ;
2323import { Spinner } from "~/components/primitives/Spinner" ;
2424import { Paragraph } from "~/components/primitives/Paragraph" ;
2525import { Callout } from "~/components/primitives/Callout" ;
@@ -28,18 +28,9 @@ import { formatDistanceToNow } from "date-fns";
2828import { formatNumberCompact } from "~/utils/numberFormatter" ;
2929import * as Property from "~/components/primitives/PropertyTable" ;
3030import { TaskRunsTable } from "~/components/runs/v3/TaskRunsTable" ;
31- import { DateTime , formatDateTime } from "~/components/primitives/DateTime" ;
31+ import { DateTime } from "~/components/primitives/DateTime" ;
3232import { ErrorId } from "@trigger.dev/core/v3/isomorphic" ;
33- import {
34- Bar ,
35- BarChart ,
36- ReferenceLine ,
37- ResponsiveContainer ,
38- Tooltip ,
39- YAxis ,
40- type TooltipProps ,
41- } from "recharts" ;
42- import TooltipPortal from "~/components/primitives/TooltipPortal" ;
33+ import { Chart , type ChartConfig } from "~/components/primitives/charts/ChartCompound" ;
4334
4435export const meta : MetaFunction < typeof loader > = ( { data } ) => {
4536 return [
@@ -217,12 +208,12 @@ function ErrorGroupDetail({
217208 }
218209
219210 return (
220- < div className = "grid h-full grid-rows-[auto_auto_1fr ] overflow-hidden" >
211+ < div className = "grid h-full grid-rows-[auto_16rem_1fr ] overflow-hidden" >
221212 { /* Error Summary */ }
222213 < div className = "border-b border-grid-bright p-4" >
223214 < Header2 className = "mb-4" > { errorGroup . errorMessage } </ Header2 >
224215
225- < div className = "grid grid-cols-2 gap-x-12 gap-y-1" >
216+ < div className = "grid grid-cols-3 gap-x-12 gap-y-1" >
226217 < Property . Table >
227218 < Property . Item >
228219 < Property . Label > ID</ Property . Label >
@@ -243,6 +234,9 @@ function ErrorGroupDetail({
243234 < Property . Label > Occurrences</ Property . Label >
244235 < Property . Value > { formatNumberCompact ( errorGroup . count ) } </ Property . Value >
245236 </ Property . Item >
237+ </ Property . Table >
238+
239+ < Property . Table >
246240 < Property . Item >
247241 < Property . Label > First seen</ Property . Label >
248242 < Property . Value >
@@ -271,13 +265,10 @@ function ErrorGroupDetail({
271265 </ div >
272266
273267 { /* Activity over past 7 days by hour */ }
274- < div className = "border-b border-grid-bright px-4 py-3" >
275- < Header3 className = "mb-2" > Activity (past 7 days)</ Header3 >
268+ < div className = "flex flex-col overflow-hidden border-b border-grid-bright px-4 py-3" >
269+ < Header3 className = "mb-2 shrink-0 " > Activity (past 7 days)</ Header3 >
276270 < Suspense fallback = { < ActivityChartBlankState /> } >
277- < TypedAwait
278- resolve = { hourlyActivity }
279- errorElement = { < ActivityChartBlankState /> }
280- >
271+ < TypedAwait resolve = { hourlyActivity } errorElement = { < ActivityChartBlankState /> } >
281272 { ( activity ) =>
282273 activity . length > 0 ? (
283274 < ActivityChart activity = { activity } />
@@ -291,7 +282,7 @@ function ErrorGroupDetail({
291282
292283 { /* Runs Table */ }
293284 < div className = "flex flex-col gap-1 overflow-y-hidden" >
294- < Header3 className = "mt-2 mb-1 px-4" > Recent runs</ Header3 >
285+ < Header3 className = "mb-1 mt-2 px-4" > Recent runs</ Header3 >
295286 { runList ? (
296287 < TaskRunsTable
297288 total = { runList . runs . length }
@@ -318,69 +309,92 @@ function ErrorGroupDetail({
318309 ) ;
319310}
320311
321- function ActivityChart ( { activity } : { activity : ErrorGroupHourlyActivity } ) {
322- const maxCount = Math . max ( ...activity . map ( ( d ) => d . count ) ) ;
312+ const activityChartConfig : ChartConfig = {
313+ count : {
314+ label : "Occurrences" ,
315+ color : "#EC003F" ,
316+ } ,
317+ } ;
323318
324- return (
325- < div className = "flex items-start gap-2" >
326- < div className = "h-16 flex-1 rounded-sm" >
327- < ResponsiveContainer width = "100%" height = "100%" >
328- < BarChart data = { activity } margin = { { top : 0 , right : 0 , left : 0 , bottom : 0 } } >
329- < YAxis domain = { [ 0 , maxCount || 1 ] } hide />
330- < Tooltip
331- cursor = { { fill : "transparent" } }
332- content = { < ActivityChartTooltip /> }
333- allowEscapeViewBox = { { x : true , y : true } }
334- wrapperStyle = { { zIndex : 1000 } }
335- animationDuration = { 0 }
336- />
337- < Bar dataKey = "count" fill = "#EC003F" strokeWidth = { 0 } isAnimationActive = { false } />
338- < ReferenceLine y = { 0 } stroke = "#B5B8C0" strokeWidth = { 1 } />
339- { maxCount > 0 && (
340- < ReferenceLine
341- y = { maxCount }
342- stroke = "#B5B8C0"
343- strokeDasharray = "3 2"
344- strokeWidth = { 1 }
345- />
346- ) }
347- </ BarChart >
348- </ ResponsiveContainer >
349- </ div >
350- < span className = "text-xxs tabular-nums text-text-dimmed" >
351- { formatNumberCompact ( maxCount ) }
352- </ span >
353- </ div >
319+ function ActivityChart ( { activity } : { activity : ErrorGroupHourlyActivity } ) {
320+ const data = useMemo (
321+ ( ) =>
322+ activity . map ( ( d ) => ( {
323+ ...d ,
324+ __timestamp : d . date instanceof Date ? d . date . getTime ( ) : new Date ( d . date ) . getTime ( ) ,
325+ } ) ) ,
326+ [ activity ]
354327 ) ;
355- }
356328
357- const ActivityChartTooltip = ( { active, payload } : TooltipProps < number , string > ) => {
358- if ( active && payload && payload . length > 0 ) {
359- const entry = payload [ 0 ] . payload as { date : Date ; count : number } ;
360- const date = entry . date instanceof Date ? entry . date : new Date ( entry . date ) ;
361- const formattedDate = formatDateTime ( date , "UTC" , [ ] , false , true ) ;
329+ const midnightTicks = useMemo ( ( ) => {
330+ const ticks : number [ ] = [ ] ;
331+ for ( const d of data ) {
332+ const date = new Date ( d . __timestamp ) ;
333+ if ( date . getHours ( ) === 0 && date . getMinutes ( ) === 0 ) {
334+ ticks . push ( d . __timestamp ) ;
335+ }
336+ }
337+ return ticks ;
338+ } , [ data ] ) ;
362339
363- return (
364- < TooltipPortal active = { active } >
365- < div className = "rounded-sm border border-grid-bright bg-background-dimmed px-3 py-2" >
366- < Header3 className = "border-b border-b-charcoal-650 pb-2" > { formattedDate } </ Header3 >
367- < div className = "mt-2 text-xs text-text-bright" >
368- < span className = "tabular-nums" > { entry . count } </ span > { " " }
369- < span className = "text-text-dimmed" >
370- { entry . count === 1 ? "occurrence" : "occurrences" }
371- </ span >
372- </ div >
373- </ div >
374- </ TooltipPortal >
375- ) ;
376- }
340+ const xAxisFormatter = useMemo ( ( ) => {
341+ return ( value : number ) => {
342+ const date = new Date ( value ) ;
343+ return date . toLocaleDateString ( "en-US" , {
344+ month : "short" ,
345+ day : "numeric" ,
346+ hour : "2-digit" ,
347+ minute : "2-digit" ,
348+ hour12 : false ,
349+ } ) ;
350+ } ;
351+ } , [ ] ) ;
377352
378- return null ;
379- } ;
353+ const tooltipLabelFormatter = useMemo ( ( ) => {
354+ return ( _label : string , payload : Array < { payload ?: Record < string , unknown > } > ) => {
355+ const timestamp = payload [ 0 ] ?. payload ?. __timestamp as number | undefined ;
356+ if ( timestamp ) {
357+ const date = new Date ( timestamp ) ;
358+ return date . toLocaleString ( "en-US" , {
359+ month : "short" ,
360+ day : "numeric" ,
361+ year : "numeric" ,
362+ hour : "2-digit" ,
363+ minute : "2-digit" ,
364+ hour12 : false ,
365+ } ) ;
366+ }
367+ return _label ;
368+ } ;
369+ } , [ ] ) ;
370+
371+ return (
372+ < Chart . Root
373+ config = { activityChartConfig }
374+ data = { data }
375+ dataKey = "__timestamp"
376+ series = { [ "count" ] }
377+ fillContainer
378+ >
379+ < Chart . Bar
380+ xAxisProps = { {
381+ tickFormatter : xAxisFormatter ,
382+ ticks : midnightTicks ,
383+ height : 40 ,
384+ } }
385+ yAxisProps = { {
386+ width : 30 ,
387+ tickMargin : 4 ,
388+ } }
389+ tooltipLabelFormatter = { tooltipLabelFormatter }
390+ />
391+ </ Chart . Root >
392+ ) ;
393+ }
380394
381395function ActivityChartBlankState ( ) {
382396 return (
383- < div className = "flex h-16 w-full items-end gap-px rounded-sm" >
397+ < div className = "flex min-h-0 flex-1 items-end gap-px rounded-sm" >
384398 { [ ...Array ( 42 ) ] . map ( ( _ , i ) => (
385399 < div key = { i } className = "h-full flex-1 bg-charcoal-850" />
386400 ) ) }
0 commit comments