@@ -31,6 +31,42 @@ import { docLinks } from '~/util/links'
31
31
32
32
export const handle = { crumb : 'Audit Log' }
33
33
34
+ /**
35
+ * Convert API response JSON from the camel-cased version we get out of the TS
36
+ * client back into snake-case, which is what we get from the API. This is truly
37
+ * stupid but I can't think of a better way.
38
+ */
39
+ function camelToSnakeJson ( o : Record < string , unknown > ) : Record < string , unknown > {
40
+ const result : Record < string , unknown > = { }
41
+
42
+ for ( const originalKey in o ) {
43
+ if ( ! Object . prototype . hasOwnProperty . call ( o , originalKey ) ) {
44
+ continue
45
+ }
46
+
47
+ const snakeKey = originalKey
48
+ . replace ( / [ A - Z ] / g, ( letter ) => `_${ letter . toLowerCase ( ) } ` )
49
+ . replace ( / ^ _ / , '' )
50
+ const value = o [ originalKey ]
51
+
52
+ if ( value !== null && typeof value === 'object' ) {
53
+ if ( Array . isArray ( value ) ) {
54
+ result [ snakeKey ] = value . map ( ( item ) =>
55
+ item !== null && typeof item === 'object' && ! Array . isArray ( item )
56
+ ? camelToSnakeJson ( item as Record < string , unknown > )
57
+ : item
58
+ )
59
+ } else {
60
+ result [ snakeKey ] = camelToSnakeJson ( value as Record < string , unknown > )
61
+ }
62
+ } else {
63
+ result [ snakeKey ] = value
64
+ }
65
+ }
66
+
67
+ return result
68
+ }
69
+
34
70
const Indent = ( { depth } : { depth : number } ) => (
35
71
< span className = "inline-block" style = { { width : `${ depth * 4 + 1 } ch` } } />
36
72
)
@@ -41,6 +77,8 @@ const Primitive = ({ value }: { value: null | boolean | number | string }) => (
41
77
</ span >
42
78
)
43
79
80
+ // TODO: avoid converting JSON to string and then parsing again. just need a better memo
81
+
44
82
// silly faux highlighting
45
83
// avoids unnecessary import of a library and all that overhead
46
84
const HighlightJSON = memo ( ( { jsonString } : { jsonString : string } ) => {
@@ -215,7 +253,10 @@ export default function SiloAuditLogsPage() {
215
253
{ rowVirtualizer . getVirtualItems ( ) . map ( ( virtualRow ) => {
216
254
const log = allItems [ virtualRow . index ]
217
255
const isExpanded = expandedItem === virtualRow . index . toString ( )
218
- const jsonString = JSON . stringify ( log , null , 2 )
256
+ // only bother doing all this computation if we're the expanded row
257
+ const jsonString = isExpanded
258
+ ? JSON . stringify ( camelToSnakeJson ( log ) , null , 2 )
259
+ : ''
219
260
220
261
const [ userId , siloId ] = match ( log . actor )
221
262
. with ( { kind : 'silo_user' } , ( actor ) => [ actor . siloUserId , actor . siloId ] )
@@ -232,7 +273,7 @@ export default function SiloAuditLogsPage() {
232
273
transform : `translateY(${ virtualRow . start } px)` ,
233
274
} }
234
275
>
235
- < button
276
+ < div
236
277
className = { cn (
237
278
'grid h-9 w-full cursor-pointer items-center gap-8 px-[var(--content-gutter)] text-left text-sans-md border-secondary' ,
238
279
isExpanded ? 'bg-raise' : 'hover:bg-raise' ,
@@ -243,7 +284,6 @@ export default function SiloAuditLogsPage() {
243
284
const newValue = isExpanded ? null : virtualRow . index . toString ( )
244
285
handleToggle ( newValue )
245
286
} }
246
- type = "button"
247
287
>
248
288
{ /* TODO: might be especially useful here to get the original UTC timestamp in a tooltip */ }
249
289
< div className = "overflow-hidden whitespace-nowrap text-mono-sm" >
@@ -292,7 +332,7 @@ export default function SiloAuditLogsPage() {
292
332
{ differenceInMilliseconds ( new Date ( log . timeCompleted ) , log . timeStarted ) }
293
333
ms
294
334
</ div >
295
- </ button >
335
+ </ div >
296
336
{ isExpanded && (
297
337
< div className = "h-72 border-t px-[var(--content-gutter)] py-3 border-secondary" >
298
338
< pre className = "h-full overflow-auto border-l pl-4 text-mono-code border-secondary" >
0 commit comments