mirror of
https://github.com/Steffo99/sophon.git
synced 2024-12-23 07:14:21 +00:00
✨ Add types for the types of objects returned by the backend
This commit is contained in:
parent
0c1ce76f94
commit
bcf1b3f238
3 changed files with 61 additions and 256 deletions
|
@ -3,7 +3,7 @@ import * as ReactDOM from "react-dom"
|
|||
import {useLoginAxios} from "./LoginContext";
|
||||
import {useMemo} from "react";
|
||||
import {Box, Heading} from "@steffo/bluelib-react";
|
||||
import {ResearchGroupPanel} from "./ResearchGroupPanel";
|
||||
import {ResearchGroupPanel, ResearchGroupPanelProps} from "./ResearchGroupPanel";
|
||||
|
||||
|
||||
interface ResearchGroupListBoxProps {
|
||||
|
@ -13,6 +13,9 @@ interface ResearchGroupListBoxProps {
|
|||
|
||||
export function ResearchGroupListBox({}: ResearchGroupListBoxProps): JSX.Element {
|
||||
const api = useLoginAxios()
|
||||
const loading = React.useState<boolean>()
|
||||
|
||||
const data = React.useState<ResearchGroupPanelProps[]>([])
|
||||
|
||||
return (
|
||||
<Box>
|
||||
|
@ -20,9 +23,7 @@ export function ResearchGroupListBox({}: ResearchGroupListBoxProps): JSX.Element
|
|||
Research groups
|
||||
</Heading>
|
||||
<div>
|
||||
<ResearchGroupPanel/>
|
||||
<ResearchGroupPanel/>
|
||||
<ResearchGroupPanel/>
|
||||
{data.map(group => <ResearchGroupPanel {...group}/>)}
|
||||
</div>
|
||||
</Box>
|
||||
)
|
||||
|
|
|
@ -1,252 +0,0 @@
|
|||
import { useCallback, useEffect, useState } from "react"
|
||||
import {useLoginAxios} from "../components/LoginContext";
|
||||
|
||||
|
||||
/**
|
||||
* Error thrown when trying to access a backend view which doesn't exist or isn't allowed in the used hook.
|
||||
*/
|
||||
export class ViewNotAllowedError extends Error {
|
||||
view: string
|
||||
|
||||
constructor(view: string) {
|
||||
super()
|
||||
|
||||
this.view = view
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An hook which allows access to a full REST viewset (list, create, retrieve, edit, delete).
|
||||
*
|
||||
* @param resourcesPath - The path of the resource directory.
|
||||
* @param pkName - The name of the primary key attribute of the elements.
|
||||
* @param allowViews - An object with maps views to a boolean detailing if they're allowed in the viewset or not.
|
||||
*/
|
||||
export default function useBackendViewset(resourcesPath: string, pkName: string,
|
||||
{
|
||||
list: allowList = true,
|
||||
create: allowCreate = true,
|
||||
retrieve: allowRetrieve = true,
|
||||
edit: allowEdit = true,
|
||||
destroy: allowDestroy = true,
|
||||
command: allowCommand = false,
|
||||
action: allowAction = false,
|
||||
} = {},
|
||||
) {
|
||||
const api = useLoginAxios()
|
||||
|
||||
const [firstLoad, setFirstLoad] = useState<boolean>(false)
|
||||
const [resources, setResources] = useState<any[]>([])
|
||||
const [running, setRunning] = useState<boolean>(false)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
|
||||
const apiList = useCallback(
|
||||
async (abort: AbortSignal) => {
|
||||
if(!allowList) {
|
||||
throw new ViewNotAllowedError("list")
|
||||
}
|
||||
return api.get(`${resourcesPath}`, {signal: abort})
|
||||
},
|
||||
[api, allowList, resourcesPath],
|
||||
)
|
||||
|
||||
const apiRetrieve = useCallback(
|
||||
async (id: string, abort: AbortSignal) => {
|
||||
if(!allowRetrieve) {
|
||||
throw new ViewNotAllowedError("retrieve")
|
||||
}
|
||||
return api.get(`${resourcesPath}${id}/`, {signal: abort})
|
||||
},
|
||||
[api, allowRetrieve, resourcesPath],
|
||||
)
|
||||
|
||||
const apiCreate = useCallback(
|
||||
async (data: any, abort: AbortSignal) => {
|
||||
if(!allowCreate) {
|
||||
throw new ViewNotAllowedError("create")
|
||||
}
|
||||
return api.post(`${resourcesPath}`, data, {signal: abort})
|
||||
},
|
||||
[api, allowCreate, resourcesPath],
|
||||
)
|
||||
|
||||
const apiEdit = useCallback(
|
||||
async (id: string, data: any, abort: AbortSignal) => {
|
||||
if(!allowEdit) {
|
||||
throw new ViewNotAllowedError("edit")
|
||||
}
|
||||
return api.put(`${resourcesPath}${id}/`, data, {signal: abort})
|
||||
},
|
||||
[api, allowEdit, resourcesPath],
|
||||
)
|
||||
|
||||
const apiDestroy = useCallback(
|
||||
async (id: string, abort: AbortSignal) => {
|
||||
if(!allowDestroy) {
|
||||
throw new ViewNotAllowedError("destroy")
|
||||
}
|
||||
return api.delete(`${resourcesPath}${id}/`, {signal: abort})
|
||||
},
|
||||
[api, allowDestroy, resourcesPath],
|
||||
)
|
||||
|
||||
/*
|
||||
const apiCommand = useCallback(
|
||||
async (method, command, data, init) => {
|
||||
if(!allowCommand) {
|
||||
throw new ViewNotAllowedError("command")
|
||||
}
|
||||
return apiRequest(method, `${resourcesPath}${command}`, data, init)
|
||||
},
|
||||
[apiRequest, allowCommand, resourcesPath],
|
||||
)
|
||||
|
||||
const apiAction = useCallback(
|
||||
async (method, id, command, data, init) => {
|
||||
if(!allowAction) {
|
||||
throw new ViewNotAllowedError("action")
|
||||
}
|
||||
return apiRequest(method, `${resourcesPath}${id}/${command}`, data, init)
|
||||
},
|
||||
[apiRequest, allowAction, resourcesPath],
|
||||
)
|
||||
*/
|
||||
|
||||
const listResources = useCallback(
|
||||
async (abort: AbortSignal) => {
|
||||
let res
|
||||
try {
|
||||
res = await apiList(abort)
|
||||
}
|
||||
catch(e) {
|
||||
setError(e as Error)
|
||||
return
|
||||
}
|
||||
setError(null)
|
||||
setResources(res.data)
|
||||
},
|
||||
[apiList, setError, setResources],
|
||||
)
|
||||
|
||||
const retrieveResource = useCallback(
|
||||
async (pk: string, abort: AbortSignal) => {
|
||||
let res: any
|
||||
try {
|
||||
res = await apiRetrieve(pk, abort)
|
||||
}
|
||||
catch(e) {
|
||||
setError(e as Error)
|
||||
return
|
||||
}
|
||||
setError(null)
|
||||
|
||||
setResources(r => r.map(resource => {
|
||||
// @ts-ignore
|
||||
if(resource[pkName] === pk) {
|
||||
return res.data
|
||||
}
|
||||
return resource
|
||||
}))
|
||||
|
||||
return res
|
||||
},
|
||||
[apiRetrieve, setError, setResources, pkName],
|
||||
)
|
||||
|
||||
const createResource = useCallback(
|
||||
async (data: any, abort: AbortSignal) => {
|
||||
let res: any
|
||||
try {
|
||||
res = await apiCreate(data, abort)
|
||||
}
|
||||
catch(e) {
|
||||
setError(e as Error)
|
||||
return
|
||||
}
|
||||
setError(null)
|
||||
|
||||
setResources(r => [...r, res])
|
||||
return res
|
||||
},
|
||||
[apiCreate, setError, setResources],
|
||||
)
|
||||
|
||||
const editResource = useCallback(
|
||||
async (pk: string, data: any, abort: AbortSignal) => {
|
||||
let res: any
|
||||
try {
|
||||
res = await apiEdit(pk, data, abort)
|
||||
}
|
||||
catch(e) {
|
||||
setError(e as Error)
|
||||
return
|
||||
}
|
||||
setError(null)
|
||||
|
||||
setResources(r => r.map(resource => {
|
||||
if(resource[pkName] === pk) {
|
||||
return res
|
||||
}
|
||||
return resource
|
||||
}))
|
||||
return res
|
||||
},
|
||||
[apiEdit, setError, setResources, pkName],
|
||||
)
|
||||
|
||||
const destroyResource = useCallback(
|
||||
async (pk: string, abort: AbortSignal) => {
|
||||
try {
|
||||
await apiDestroy(pk, abort)
|
||||
}
|
||||
catch(e) {
|
||||
setError(e as Error)
|
||||
return
|
||||
}
|
||||
setError(null)
|
||||
|
||||
setResources(r => r.filter(resource => resource[pkName] !== pk))
|
||||
return null
|
||||
},
|
||||
[apiDestroy, setError, setResources, pkName],
|
||||
)
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if(allowList && !firstLoad && !running) {
|
||||
listResources().then(() => setFirstLoad(true))
|
||||
|
||||
}
|
||||
},
|
||||
[listResources, firstLoad, running, allowList],
|
||||
)
|
||||
|
||||
return {
|
||||
abort,
|
||||
resources,
|
||||
firstLoad,
|
||||
running,
|
||||
error,
|
||||
apiRequest,
|
||||
allowList,
|
||||
apiList,
|
||||
listResources,
|
||||
allowRetrieve,
|
||||
apiRetrieve,
|
||||
retrieveResource,
|
||||
allowCreate,
|
||||
apiCreate,
|
||||
createResource,
|
||||
allowEdit,
|
||||
apiEdit,
|
||||
editResource,
|
||||
allowDestroy,
|
||||
apiDestroy,
|
||||
destroyResource,
|
||||
apiCommand,
|
||||
apiAction,
|
||||
}
|
||||
}
|
56
frontend/src/types.ts
Normal file
56
frontend/src/types.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
export interface DRFList<T> {
|
||||
count: number,
|
||||
next: string | null,
|
||||
previous: string | null,
|
||||
results: T[]
|
||||
}
|
||||
|
||||
|
||||
export type ResearchGroupSlug = string
|
||||
|
||||
export interface ResearchGroup {
|
||||
owner: number,
|
||||
members: UserId[],
|
||||
name: string,
|
||||
description: string,
|
||||
access: "OPEN" | "MANUAL",
|
||||
slug: ResearchGroupSlug,
|
||||
}
|
||||
|
||||
|
||||
export type UserId = number
|
||||
|
||||
export interface User {
|
||||
id: UserId,
|
||||
username: string,
|
||||
first_name: string,
|
||||
last_name: string,
|
||||
email: string,
|
||||
}
|
||||
|
||||
|
||||
export type ResearchProjectSlug = string
|
||||
|
||||
export interface ResearchProject {
|
||||
visibility: "PUBLIC" | "INTERNAL" | "PRIVATE",
|
||||
slug: ResearchProjectSlug,
|
||||
name: string,
|
||||
description: string,
|
||||
group: ResearchGroupSlug,
|
||||
}
|
||||
|
||||
|
||||
export type NotebookSlug = string
|
||||
|
||||
export interface Notebook {
|
||||
locked_by: UserId,
|
||||
slug: NotebookSlug,
|
||||
legacy_notebook_url: string | null,
|
||||
jupyter_token: string,
|
||||
is_running: boolean,
|
||||
internet_access: true,
|
||||
container_image: string,
|
||||
project: ResearchProjectSlug,
|
||||
name: string,
|
||||
lab_url: string | null,
|
||||
}
|
Loading…
Reference in a new issue