@@ -12,6 +12,7 @@ import cn from 'classnames'
12
12
import { differenceInMilliseconds } from 'date-fns'
13
13
import { memo , useCallback , useMemo , useRef , useState } from 'react'
14
14
import { match } from 'ts-pattern'
15
+ import { JsonValue } from 'type-fest'
15
16
16
17
import { api } from '@oxide/api'
17
18
import { Logs16Icon , Logs24Icon } from '@oxide/design-system/icons/react'
@@ -77,78 +78,63 @@ const Primitive = ({ value }: { value: null | boolean | number | string }) => (
77
78
</ span >
78
79
)
79
80
80
- // TODO: avoid converting JSON to string and then parsing again. just need a better memo
81
-
82
- // silly faux highlighting
83
- // avoids unnecessary import of a library and all that overhead
84
- const HighlightJSON = memo ( ( { jsonString } : { jsonString : string } ) => {
85
- const renderValue = (
86
- value : null | boolean | number | string | object ,
87
- depth = 0
88
- ) : React . ReactNode => {
89
- if (
90
- value === null ||
91
- typeof value === 'boolean' ||
92
- typeof value === 'number' ||
93
- typeof value === 'string'
94
- ) {
95
- return < Primitive value = { value } />
96
- }
97
-
98
- if ( Array . isArray ( value ) ) {
99
- if ( value . length === 0 ) return < span className = "text-quaternary" > []</ span >
81
+ // memo is important to avoid re-renders if the value hasn't changed. value
82
+ // passed in must be referentially stable, which should generally be the case
83
+ // with API responses
84
+ const HighlightJSON = memo ( ( { json, depth = 0 } : { json : JsonValue ; depth ?: number } ) => {
85
+ if ( json === undefined ) return null
86
+
87
+ if (
88
+ json === null ||
89
+ typeof json === 'boolean' ||
90
+ typeof json === 'number' ||
91
+ typeof json === 'string'
92
+ ) {
93
+ return < Primitive value = { json } />
94
+ }
100
95
101
- return (
102
- < >
103
- < span className = "text-quaternary" > [</ span >
104
- { '\n' }
105
- { value . map ( ( item , index ) => (
106
- < span key = { index } >
107
- < Indent depth = { depth + 1 } />
108
- { renderValue ( item , depth + 1 ) }
109
- { index < value . length - 1 && < span className = "text-quaternary" > ,</ span > }
110
- { '\n' }
111
- </ span >
112
- ) ) }
113
- < Indent depth = { depth } />
114
- < span className = "text-quaternary" > ]</ span >
115
- </ >
116
- )
117
- }
96
+ if ( Array . isArray ( json ) ) {
97
+ if ( json . length === 0 ) return < span className = "text-quaternary" > []</ span >
98
+
99
+ return (
100
+ < >
101
+ < span className = "text-quaternary" > [</ span >
102
+ { '\n' }
103
+ { json . map ( ( item , index ) => (
104
+ < span key = { index } >
105
+ < Indent depth = { depth + 1 } />
106
+ < HighlightJSON json = { item } depth = { depth + 1 } />
107
+ { index < json . length - 1 && < span className = "text-quaternary" > ,</ span > }
108
+ { '\n' }
109
+ </ span >
110
+ ) ) }
111
+ < Indent depth = { depth } />
112
+ < span className = "text-quaternary" > ]</ span >
113
+ </ >
114
+ )
115
+ }
118
116
119
- if ( typeof value === 'object' ) {
120
- const entries = Object . entries ( value )
121
- if ( entries . length === 0 ) return < span className = "text-quaternary" > { '{}' } </ span >
117
+ const entries = Object . entries ( json )
118
+ if ( entries . length === 0 ) return < span className = "text-quaternary" > { '{}' } </ span >
122
119
123
- return (
124
- < >
125
- < span className = "text-quaternary" > { '{' } </ span >
120
+ return (
121
+ < >
122
+ < span className = "text-quaternary" > { '{' } </ span >
123
+ { '\n' }
124
+ { entries . map ( ( [ key , val ] , index ) => (
125
+ < span key = { key } >
126
+ < Indent depth = { depth + 1 } />
127
+ < span className = "text-default" > { key } </ span >
128
+ < span className = "text-quaternary" > : </ span >
129
+ < HighlightJSON json = { val } depth = { depth + 1 } />
130
+ { index < entries . length - 1 && < span className = "text-quaternary" > ,</ span > }
126
131
{ '\n' }
127
- { entries . map ( ( [ key , val ] , index ) => (
128
- < span key = { key } >
129
- < Indent depth = { depth + 1 } />
130
- < span className = "text-default" > { key } </ span >
131
- < span className = "text-quaternary" > : </ span >
132
- { renderValue ( val , depth + 1 ) }
133
- { index < entries . length - 1 && < span className = "text-quaternary" > ,</ span > }
134
- { '\n' }
135
- </ span >
136
- ) ) }
137
- < Indent depth = { depth } />
138
- < span className = "text-quaternary" > { '}' } </ span >
139
- </ >
140
- )
141
- }
142
-
143
- return String ( value )
144
- }
145
-
146
- try {
147
- const parsed = JSON . parse ( jsonString )
148
- return < > { renderValue ( parsed ) } </ >
149
- } catch {
150
- return < > { jsonString } </ >
151
- }
132
+ </ span >
133
+ ) ) }
134
+ < Indent depth = { depth } />
135
+ < span className = "text-quaternary" > { '}' } </ span >
136
+ </ >
137
+ )
152
138
} )
153
139
154
140
// todo
@@ -254,9 +240,7 @@ export default function SiloAuditLogsPage() {
254
240
const log = allItems [ virtualRow . index ]
255
241
const isExpanded = expandedItem === virtualRow . index . toString ( )
256
242
// only bother doing all this computation if we're the expanded row
257
- const jsonString = isExpanded
258
- ? JSON . stringify ( camelToSnakeJson ( log ) , null , 2 )
259
- : ''
243
+ const json = isExpanded ? camelToSnakeJson ( log ) : undefined
260
244
261
245
const [ userId , siloId ] = match ( log . actor )
262
246
. with ( { kind : 'silo_user' } , ( actor ) => [ actor . siloUserId , actor . siloId ] )
@@ -336,7 +320,7 @@ export default function SiloAuditLogsPage() {
336
320
{ isExpanded && (
337
321
< div className = "h-72 border-t px-[var(--content-gutter)] py-3 border-secondary" >
338
322
< pre className = "h-full overflow-auto border-l pl-4 text-mono-code border-secondary" >
339
- < HighlightJSON jsonString = { jsonString } />
323
+ < HighlightJSON json = { json as JsonValue } />
340
324
</ pre >
341
325
</ div >
342
326
) }
0 commit comments