Skip to content

Commit 60aea0b

Browse files
committed
Apache avro file viewer
1 parent 169ef7b commit 60aea0b

File tree

4 files changed

+75
-6
lines changed

4 files changed

+75
-6
lines changed

package.json

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,19 +48,20 @@
4848
},
4949
"dependencies": {
5050
"hightable": "0.12.1",
51-
"hyparquet": "1.9.0",
51+
"hyparquet": "1.9.1",
5252
"hyparquet-compressors": "1.0.0",
53+
"icebird": "0.1.8",
5354
"react": "18.3.1",
5455
"react-dom": "18.3.1"
5556
},
5657
"devDependencies": {
5758
"@eslint/js": "9.22.0",
5859
"@testing-library/react": "16.2.0",
5960
"@types/node": "22.13.10",
60-
"@types/react": "19.0.10",
61+
"@types/react": "19.0.12",
6162
"@types/react-dom": "19.0.4",
6263
"@vitejs/plugin-react": "4.3.4",
63-
"@vitest/coverage-v8": "3.0.8",
64+
"@vitest/coverage-v8": "3.0.9",
6465
"eslint": "9.22.0",
6566
"eslint-plugin-react": "7.37.4",
6667
"eslint-plugin-react-hooks": "5.2.0",
@@ -70,8 +71,8 @@
7071
"nodemon": "3.1.9",
7172
"npm-run-all": "4.1.5",
7273
"typescript": "5.8.2",
73-
"typescript-eslint": "8.26.0",
74-
"vite": "6.2.1",
75-
"vitest": "3.0.8"
74+
"typescript-eslint": "8.27.0",
75+
"vite": "6.2.2",
76+
"vitest": "3.0.9"
7677
}
7778
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { useEffect, useState } from 'react'
2+
import type { FileSource } from '../../lib/sources/types.js'
3+
import { parseFileSize } from '../../lib/utils.js'
4+
import styles from '../../styles/Json.module.css'
5+
import Json from '../Json.js'
6+
import { Spinner } from '../Layout.js'
7+
import ContentHeader, { ContentSize } from './ContentHeader.js'
8+
import { avroData, avroMetadata } from 'icebird'
9+
10+
interface ViewerProps {
11+
source: FileSource
12+
setError: (error: Error | undefined) => void
13+
}
14+
15+
/**
16+
* Apache Avro viewer component.
17+
*/
18+
export default function AvroView({ source, setError }: ViewerProps) {
19+
const [content, setContent] = useState<ContentSize>()
20+
const [json, setJson] = useState<unknown>()
21+
const [isLoading, setIsLoading] = useState(true)
22+
23+
const { resolveUrl, requestInit } = source
24+
25+
// Load avro content as json
26+
useEffect(() => {
27+
async function loadContent() {
28+
try {
29+
setIsLoading(true)
30+
const res = await fetch(resolveUrl, requestInit)
31+
if (res.status === 401) {
32+
const text = await res.text()
33+
setError(new Error(text))
34+
setContent(undefined)
35+
return
36+
}
37+
// Parse avro file
38+
const buffer = await res.arrayBuffer()
39+
const fileSize = parseFileSize(res.headers) ?? buffer.byteLength
40+
const reader = { view: new DataView(buffer), offset: 0 }
41+
const { metadata, syncMarker } = avroMetadata(reader)
42+
const json = avroData({ reader, metadata, syncMarker })
43+
setError(undefined)
44+
setContent({ fileSize })
45+
setJson(json)
46+
} catch (error) {
47+
setError(error as Error)
48+
} finally {
49+
setIsLoading(false)
50+
}
51+
}
52+
void loadContent()
53+
}, [resolveUrl, requestInit, setError])
54+
55+
const headers = content === undefined && <span>Loading...</span>
56+
57+
return <ContentHeader content={content} headers={headers}>
58+
<code className={styles.jsonView}>
59+
<Json json={json} />
60+
</code>
61+
62+
{isLoading && <div className='center'><Spinner /></div>}
63+
</ContentHeader>
64+
}

src/components/viewers/Viewer.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { FileSource } from '../../lib/sources/types.js'
22
import { imageTypes } from '../../lib/utils.js'
3+
import AvroView from './AvroView.js'
34
import ImageView from './ImageView.js'
45
import JsonView from './JsonView.js'
56
import MarkdownView from './MarkdownView.js'
@@ -39,6 +40,8 @@ export default function Viewer({
3940
)
4041
} else if (fileName.endsWith('.json')) {
4142
return <JsonView source={source} setError={setError} />
43+
} else if (fileName.endsWith('.avro')) {
44+
return <AvroView source={source} setError={setError} />
4245
} else if (imageTypes.some((type) => fileName.endsWith(type))) {
4346
return <ImageView source={source} setError={setError} />
4447
}

src/styles/Json.module.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
font-size: 10pt;
3131
margin-left: -12px;
3232
margin-right: 4px;
33+
padding: 2px 0;
3334
user-select: none;
3435
}
3536
.clickable:hover .drill {

0 commit comments

Comments
 (0)