11import { type LoaderFunctionArgs } from "@remix-run/server-runtime" ;
2- import { type MetaFunction , Form , Link } from "@remix-run/react" ;
2+ import { type MetaFunction , Form , Link , Outlet } from "@remix-run/react" ;
33import { XMarkIcon } from "@heroicons/react/20/solid" ;
44import { ServiceValidationError } from "~/v3/services/baseService.server" ;
55import {
@@ -31,6 +31,18 @@ import { Badge } from "~/components/primitives/Badge";
3131import { Header1 , Header3 } from "~/components/primitives/Headers" ;
3232import { formatDistanceToNow } from "date-fns" ;
3333import { cn } from "~/utils/cn" ;
34+ import {
35+ CopyableTableCell ,
36+ Table ,
37+ TableBlankRow ,
38+ TableBody ,
39+ TableCell ,
40+ TableCellChevron ,
41+ TableCellMenu ,
42+ TableHeader ,
43+ TableHeaderCell ,
44+ TableRow ,
45+ } from "~/components/primitives/Table" ;
3446
3547export const meta : MetaFunction = ( ) => {
3648 return [
@@ -106,75 +118,81 @@ export default function Page() {
106118 useTypedLoaderData < typeof loader > ( ) ;
107119
108120 return (
109- < PageContainer >
110- < NavBar >
111- < PageTitle title = "Errors" />
112- </ NavBar >
121+ < >
122+ < PageContainer >
123+ < NavBar >
124+ < PageTitle title = "Errors" />
125+ </ NavBar >
113126
114- < PageBody scrollable = { false } >
115- < Suspense
116- fallback = {
117- < div className = "grid h-full max-h-full grid-rows-[2.5rem_auto] overflow-hidden" >
118- < div className = "border-b border-grid-bright" />
119- < div className = "my-2 flex items-center justify-center" >
120- < div className = "mx-auto flex items-center gap-2" >
121- < Spinner />
122- < Paragraph variant = "small" > Loading errors…</ Paragraph >
123- </ div >
124- </ div >
125- </ div >
126- }
127- >
128- < TypedAwait
129- resolve = { data }
130- errorElement = {
131- < div className = "grid h-full max-h-full grid-rows-[2.5rem_auto_1fr] overflow-hidden" >
132- < FiltersBar defaultPeriod = { defaultPeriod } retentionLimitDays = { retentionLimitDays } />
133- < div className = "flex items-center justify-center px-3 py-12" >
134- < Callout variant = "error" className = "max-w-fit" >
135- Unable to load errors. Please refresh the page or try again in a moment.
136- </ Callout >
127+ < PageBody scrollable = { false } >
128+ < Suspense
129+ fallback = {
130+ < div className = "grid h-full max-h-full grid-rows-[2.5rem_auto] overflow-hidden" >
131+ < div className = "border-b border-grid-bright" />
132+ < div className = "my-2 flex items-center justify-center" >
133+ < div className = "mx-auto flex items-center gap-2" >
134+ < Spinner />
135+ < Paragraph variant = "small" > Loading errors…</ Paragraph >
136+ </ div >
137137 </ div >
138138 </ div >
139139 }
140140 >
141- { ( result ) => {
142- // Check if result contains an error
143- if ( "error" in result ) {
141+ < TypedAwait
142+ resolve = { data }
143+ errorElement = {
144+ < div className = "grid h-full max-h-full grid-rows-[2.5rem_auto_1fr] overflow-hidden" >
145+ < FiltersBar
146+ defaultPeriod = { defaultPeriod }
147+ retentionLimitDays = { retentionLimitDays }
148+ />
149+ < div className = "flex items-center justify-center px-3 py-12" >
150+ < Callout variant = "error" className = "max-w-fit" >
151+ Unable to load errors. Please refresh the page or try again in a moment.
152+ </ Callout >
153+ </ div >
154+ </ div >
155+ }
156+ >
157+ { ( result ) => {
158+ // Check if result contains an error
159+ if ( "error" in result ) {
160+ return (
161+ < div className = "grid h-full max-h-full grid-rows-[2.5rem_auto_1fr] overflow-hidden" >
162+ < FiltersBar
163+ defaultPeriod = { defaultPeriod }
164+ retentionLimitDays = { retentionLimitDays }
165+ />
166+ < div className = "flex items-center justify-center px-3 py-12" >
167+ < Callout variant = "error" className = "max-w-fit" >
168+ { result . error }
169+ </ Callout >
170+ </ div >
171+ </ div >
172+ ) ;
173+ }
144174 return (
145- < div className = "grid h-full max-h-full grid-rows-[2.5rem_auto_1fr ] overflow-hidden" >
175+ < div className = "grid h-full max-h-full grid-rows-[2.5rem_1fr ] overflow-hidden" >
146176 < FiltersBar
177+ list = { result }
147178 defaultPeriod = { defaultPeriod }
148179 retentionLimitDays = { retentionLimitDays }
149180 />
150- < div className = "flex items-center justify-center px-3 py-12" >
151- < Callout variant = "error" className = "max-w-fit" >
152- { result . error }
153- </ Callout >
154- </ div >
181+ < ErrorsList
182+ errorGroups = { result . errorGroups }
183+ organizationSlug = { organizationSlug }
184+ projectParam = { projectParam }
185+ envParam = { envParam }
186+ />
155187 </ div >
156188 ) ;
157- }
158- return (
159- < div className = "grid h-full max-h-full grid-rows-[2.5rem_1fr] overflow-hidden" >
160- < FiltersBar
161- list = { result }
162- defaultPeriod = { defaultPeriod }
163- retentionLimitDays = { retentionLimitDays }
164- />
165- < ErrorsList
166- errorGroups = { result . errorGroups }
167- organizationSlug = { organizationSlug }
168- projectParam = { projectParam }
169- envParam = { envParam }
170- />
171- </ div >
172- ) ;
173- } }
174- </ TypedAwait >
175- </ Suspense >
176- </ PageBody >
177- </ PageContainer >
189+ } }
190+ </ TypedAwait >
191+ </ Suspense >
192+ </ PageBody >
193+ </ PageContainer >
194+ < Outlet />
195+ </ >
178196 ) ;
179197}
180198
@@ -260,8 +278,19 @@ function ErrorsList({
260278 }
261279
262280 return (
263- < div className = "overflow-y-auto" >
264- < div className = "divide-y divide-grid-dimmed" >
281+ < Table containerClassName = "max-h-full pb-[2.5rem]" >
282+ < TableHeader >
283+ < TableRow >
284+ < TableHeaderCell > ID</ TableHeaderCell >
285+ < TableHeaderCell > Error</ TableHeaderCell >
286+ < TableHeaderCell > Occurrences</ TableHeaderCell >
287+ < TableHeaderCell > Tasks</ TableHeaderCell >
288+ < TableHeaderCell > First seen</ TableHeaderCell >
289+ < TableHeaderCell > Last seen</ TableHeaderCell >
290+ < TableHeaderCell hiddenLabel > Go to page</ TableHeaderCell >
291+ </ TableRow >
292+ </ TableHeader >
293+ < TableBody >
265294 { errorGroups . map ( ( errorGroup ) => (
266295 < ErrorGroupRow
267296 key = { errorGroup . fingerprint }
@@ -271,8 +300,8 @@ function ErrorsList({
271300 envParam = { envParam }
272301 />
273302 ) ) }
274- </ div >
275- </ div >
303+ </ TableBody >
304+ </ Table >
276305 ) ;
277306}
278307
@@ -294,42 +323,25 @@ function ErrorGroupRow({
294323 { fingerprint : errorGroup . fingerprint }
295324 ) ;
296325
326+ const errorMessage = `${ errorGroup . errorType } : ${ errorGroup . errorMessage } ` ;
327+
297328 return (
298- < Link
299- to = { errorPath }
300- className = { cn (
301- "block px-4 py-3 transition hover:bg-charcoal-800" ,
302- "border-l-4 border-transparent hover:border-rose-500"
303- ) }
304- >
305- < div className = "flex items-start justify-between gap-4" >
306- < div className = "min-w-0 flex-1" >
307- < div className = "mb-1 flex items-center gap-2" >
308- < Badge variant = "default" > { errorGroup . errorType } </ Badge >
309- < Paragraph variant = "extra-small" className = "text-text-dimmed" >
310- { errorGroup . affectedTasks } task{ errorGroup . affectedTasks !== 1 ? "s" : "" }
311- </ Paragraph >
312- </ div >
313- < Paragraph className = "mb-2 truncate font-medium" > { errorGroup . errorMessage } </ Paragraph >
314- < div className = "flex items-center gap-3 text-xs text-text-dimmed" >
315- < span >
316- First seen: { formatDistanceToNow ( errorGroup . firstSeen , { addSuffix : true } ) }
317- </ span >
318- < span > •</ span >
319- < span > Last seen: { formatDistanceToNow ( errorGroup . lastSeen , { addSuffix : true } ) } </ span >
320- < span > •</ span >
321- < span > Sample: { errorGroup . sampleTaskIdentifier } </ span >
322- </ div >
323- </ div >
324- < div className = "flex flex-col items-end gap-1" >
325- < Badge variant = "outline-rounded" className = "font-mono" >
326- { errorGroup . count . toLocaleString ( ) }
327- </ Badge >
328- < Paragraph variant = "extra-small" className = "text-text-dimmed" >
329- occurrences
330- </ Paragraph >
331- </ div >
332- </ div >
333- </ Link >
329+ < TableRow >
330+ < CopyableTableCell to = { errorPath } value = { errorGroup . fingerprint } >
331+ { errorGroup . fingerprint . slice ( - 8 ) }
332+ </ CopyableTableCell >
333+ < CopyableTableCell to = { errorPath } className = "font-mono" value = { errorMessage } >
334+ { errorMessage }
335+ </ CopyableTableCell >
336+ < TableCell to = { errorPath } > { errorGroup . count . toLocaleString ( ) } </ TableCell >
337+ < TableCell to = { errorPath } > { errorGroup . affectedTasks } </ TableCell >
338+ < TableCell to = { errorPath } >
339+ { formatDistanceToNow ( errorGroup . firstSeen , { addSuffix : true } ) }
340+ </ TableCell >
341+ < TableCell to = { errorPath } >
342+ { formatDistanceToNow ( errorGroup . lastSeen , { addSuffix : true } ) }
343+ </ TableCell >
344+ < TableCellChevron to = { errorPath } isSticky />
345+ </ TableRow >
334346 ) ;
335347}
0 commit comments