Skip to content

Commit

Permalink
feat: add JWT authentication and projects API endpoints; update packa…
Browse files Browse the repository at this point in the history
…ge.json and refactor Overview component
  • Loading branch information
amirrr committed Feb 12, 2025
1 parent c8af336 commit 424cf57
Show file tree
Hide file tree
Showing 10 changed files with 651 additions and 1,433 deletions.
1,461 changes: 43 additions & 1,418 deletions client/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"react-day-picker": "^8.10.1",
"react-dom": "^18.2.0",
"react-hook-form": "^7.54.1",
"react-papaparse": "^4.4.0",
"react-resizable-panels": "^2.1.7",
"react-router-dom": "^6.23.0",
"reactflow": "^11.10.3",
Expand Down
10 changes: 1 addition & 9 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import Table from './components/View/TableView/Table'
import Home from './pages/Home/Home'
import ProjectView from './pages/ProjectView/ProjectView'
import Project from './components/View/DataGrid/Project'
import MainPage from './components/View/DataGrid/MainPage'
import Overview from './pages/Dashboard/Overview'

function App() {
Expand All @@ -15,14 +14,7 @@ function App() {
<Route path='/table' element={<Table />} />
<Route path='/projects/:project_id' element={<ProjectView />} />
<Route path='/dashboard' element={<Overview />} />
<Route
path='/grid/:project_id'
element={
<MainPage breadcrumbs={[]} sidebarOpen={false}>
<Project />
</MainPage>
}
/>
<Route path='/grid/:project_id' element={<Project />} />
</Routes>
</>
)
Expand Down
178 changes: 176 additions & 2 deletions client/src/components/View/DataGrid/Project.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,32 @@ import Contenteditable from '../../../pages/ProjectView/Contenteditable'
import { debounce } from 'lodash'
import api from '../../../service/api'
import { toast } from 'sonner'
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog'
import { FolderDown } from 'lucide-react'
import { MultiSelect } from '@/components/ui/multi-select'
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form'
import { useForm } from 'react-hook-form'
import { Separator } from '@/components/ui/separator'
import { Input } from '@/components/ui/input'
import { usePapaParse } from 'react-papaparse'
import { flattenObject, nestFlatKeys } from '../TableView/hooks/data-handler'

type ProjectDetails = {
name: string
Expand Down Expand Up @@ -90,7 +116,6 @@ const Project: React.FC = () => {
},
})
const response_data = response.data
console.log('projectResults:', response_data.results)
setProjectResults(response_data.results)
} catch (error) {
console.error('Error:', error)
Expand Down Expand Up @@ -187,6 +212,85 @@ const Project: React.FC = () => {
}
}

const form = useForm()
const [selectableHeaders, setSelectableHeaders] = useState<string[]>([])

useEffect(() => {
const allKeys = new Set<string>()
flattenObject(projectResults).forEach((obj) => {
Object.keys(obj).forEach((key) => allKeys.add(key))
})
setSelectableHeaders(Array.from(allKeys))
}, [projectResults])

const { jsonToCSV, readString } = usePapaParse()

const handleJsonToCSV = () => {
const selectedColumns = form.watch('frameworks') || []
const data = flattenObject(projectResults)
if (!data.length) return

const finalCols = selectableHeaders.reduce((acc, col) => {
acc.push(col)
if (selectedColumns.includes(col)) acc.push(`${col}_truth`)
return acc
}, [] as string[])

const finalData = data.map((row) =>
finalCols.reduce(
(acc, col) => {
acc[col] = row[col] ?? (col.endsWith('_truth') ? 'Fill in ground truth' : 'NaN')
return acc
},
{} as Record<string, unknown>,
),
)

const csvString = jsonToCSV(finalData)
const blob = new Blob([csvString], { type: 'text/csv;charset=utf-8;' })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.setAttribute('download', 'truth_template.csv')
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}

// State and handlers for reading user’s CSV
const [truthFile, setTruthFile] = useState<File | null>(null)

const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (!e.target.files?.length) {
setTruthFile(null)
return
}
setTruthFile(e.target.files[0])
}

const handleLoadCSV = () => {
if (!truthFile) return

// Read file contents
const reader = new FileReader()
reader.onload = (e) => {
if (!e.target?.result) return

const csvString = e.target.result as string
readString(csvString, {
header: true,
complete: (results) => {
results.data.forEach((value: unknown) => {
const row = value as Record<string, unknown>
const nestedRow = nestFlatKeys(row)
console.log(nestedRow)
})
},
})
}
reader.readAsText(truthFile)
}

return (
<main className={`${loading ? 'blur-sm' : ''}`}>
<div className='navbar bg-base-100 flex flex-col sm:flex-row'>
Expand All @@ -205,7 +309,77 @@ const Project: React.FC = () => {
<div className='navbar-center text-center'>
<div className='flex flex-row justify-center '>{project.id}</div>
</div>
<div className='md:navbar-end z-10 max-sm:pt-4'></div>
<div className='md:navbar-end z-10 max-md:pt-4'>
<Dialog>
<DialogTrigger asChild>
<Button>
<FolderDown />
&nbsp;Load truth
</Button>
</DialogTrigger>
<DialogContent className='sm:max-w-[600px]'>
<DialogHeader>
<DialogTitle>Create truth input</DialogTitle>
<DialogDescription>
Select the columns you want to input a ground truth for and then export the CSV
file. After editing, upload your modified ground truth to obtain an accuracy score
on your features.
</DialogDescription>
</DialogHeader>

<Form {...form}>
<form onSubmit={form.handleSubmit(() => {})} className='space-y-8'>
<FormField
control={form.control}
name='frameworks'
render={({ field }) => (
<FormItem>
<FormLabel>Columns</FormLabel>
<FormControl>
<MultiSelect
options={selectableHeaders.map((feature) => ({
label: feature.replace(/\s/g, ' → ').toLowerCase(),
value: feature,
}))}
onValueChange={field.onChange}
defaultValue={field.value}
placeholder='Select columns'
variant='inverted'
maxCount={3}
/>
</FormControl>
<FormDescription>
Choose the columns/features you are interested in.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<DialogFooter>
<Button type='submit' onClick={handleJsonToCSV}>
Export CSV Base Template
</Button>
</DialogFooter>
</form>
</Form>

<Separator />

<div className='space-y-2'>
<div className='font-semibold'>Import Ground Truth</div>
<p className='text-sm text-muted-foreground'>
Upload your modified ground truth CSV to score your features.
</p>
<Input type='file' accept='.csv' onChange={handleFileChange} />
<DialogFooter>
<Button type='submit' onClick={handleLoadCSV}>
Load CSV Ground Truth
</Button>
</DialogFooter>
</div>
</DialogContent>
</Dialog>
</div>
</div>
<hr></hr>

Expand Down
26 changes: 26 additions & 0 deletions client/src/components/View/TableView/hooks/data-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -460,3 +460,29 @@ export function flattenObject(

return [obj]
}

export function nestFlatKeys(flatObj: Record<string, unknown>) {
const nested: Record<string, unknown> = {}

for (const [key, val] of Object.entries(flatObj)) {
const segments = key.split(' ')
let curr = nested

for (let i = 0; i < segments.length; i++) {
const segment = segments[i].endsWith('_truth')
? segments[i].slice(0, -6) // remove "_truth"
: segments[i]

if (i === segments.length - 1) {
curr[segment] = val
} else {
if (curr[segment] == null) {
curr[segment] = {}
}
curr = curr[segment] as Record<string, unknown>
}
}
}

return nested
}
Loading

0 comments on commit 424cf57

Please sign in to comment.