Skip to content

Commit

Permalink
Error improvements (#137)
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelcolvin committed Feb 7, 2024
1 parent 13a1d46 commit 99dc253
Show file tree
Hide file tree
Showing 11 changed files with 126 additions and 39 deletions.
6 changes: 6 additions & 0 deletions src/npm-fastui-bootstrap/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,5 +137,11 @@ export const classNameGenerator: ClassNameGenerator = ({
}
case 'Code':
return 'rounded'
case 'Error':
if (props.statusCode === 502) {
return 'm-3 text-muted'
} else {
return 'alert alert-danger m-3'
}
}
}
11 changes: 9 additions & 2 deletions src/npm-fastui-prebuilt/src/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ $link-color: #0d6efd; // bootstrap primary

@import 'bootstrap/scss/bootstrap';

html, body, #root {
html,
body,
#root {
height: 100%;
}

Expand Down Expand Up @@ -33,7 +35,12 @@ body {
backdrop-filter: blur(8px);
}

h1, h2, h3, h4, h5, h6 {
h1,
h2,
h3,
h4,
h5,
h6 {
scroll-margin-top: 60px;
}

Expand Down
15 changes: 13 additions & 2 deletions src/npm-fastui-prebuilt/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
import path from 'path'

import react from '@vitejs/plugin-react-swc'
import { defineConfig } from 'vite'
import { defineConfig, HttpProxy } from 'vite'

export default () => {
const serverConfig = {
host: true,
port: 3000,
proxy: {
'/api': 'http://localhost:8000',
'/api': {
target: 'http://localhost:8000',
configure: (proxy: HttpProxy.Server) => {
proxy.on('error', (err, _, res) => {
const { code } = err as any
if (code === 'ECONNREFUSED') {
res.writeHead(502, { 'content-type': 'text/plain' })
res.end('vite-proxy: Proxy connection refused')
}
})
},
},
},
}

Expand Down
5 changes: 2 additions & 3 deletions src/npm-fastui/src/components/Custom.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { FC, useContext } from 'react'
import { FC } from 'react'

import type { Custom } from '../models'

import { ErrorContext } from '../hooks/error'
import { DisplayError } from '../hooks/error'

import { JsonComp } from './Json'

export const CustomComp: FC<Custom> = (props) => {
const { data, subType, library } = props
const { DisplayError } = useContext(ErrorContext)

const description = [`The custom component "${subType}"`]
if (library) {
Expand Down
24 changes: 24 additions & 0 deletions src/npm-fastui/src/components/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { FC } from 'react'

import type { Error } from '../models'

import { useClassName } from '../hooks/className'

export const ErrorComp: FC<Error> = (props) => {
const { title, description, statusCode, children } = props
return (
<>
<div className={useClassName(props)} role="alert">
{statusCode === 502 ? (
<>Backend server down.</>
) : (
<>
<h4>{title}</h4>
{description}
</>
)}
</div>
{children}
</>
)
}
10 changes: 6 additions & 4 deletions src/npm-fastui/src/components/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useContext, FC } from 'react'
import { FC } from 'react'

import type { FastProps, Display, Text, ServerLoad, PageTitle, FireEvent } from '../models'

import { ErrorContext } from '../hooks/error'
import { DisplayError } from '../hooks/error'
import { useCustomRender } from '../hooks/config'
import { unreachable } from '../tools'

Expand Down Expand Up @@ -37,6 +37,7 @@ import { ImageComp } from './image'
import { IframeComp } from './Iframe'
import { VideoComp } from './video'
import { FireEventComp } from './FireEvent'
import { ErrorComp } from './error'
import { CustomComp } from './Custom'

// TODO some better way to export components
Expand Down Expand Up @@ -70,6 +71,7 @@ export {
IframeComp,
VideoComp,
FireEventComp,
ErrorComp,
CustomComp,
LinkRender,
}
Expand All @@ -85,8 +87,6 @@ export const AnyCompList: FC<{ propsList: FastProps[] }> = ({ propsList }) => (
)

export const AnyComp: FC<FastProps> = (props) => {
const { DisplayError } = useContext(ErrorContext)

const CustomRenderComp = useCustomRender(props)
if (CustomRenderComp) {
return <CustomRenderComp />
Expand Down Expand Up @@ -155,6 +155,8 @@ export const AnyComp: FC<FastProps> = (props) => {
return <VideoComp {...props} />
case 'FireEvent':
return <FireEventComp {...props} />
case 'Error':
return <ErrorComp {...props} />
case 'Custom':
return <CustomComp {...props} />
default:
Expand Down
43 changes: 25 additions & 18 deletions src/npm-fastui/src/hooks/error.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,49 @@
import { createContext, FC, ReactNode, useCallback, useContext, useState } from 'react'

import type { Error as ErrorProps } from '../models'

import { ErrorComp } from '../components'

import { useCustomRender } from './config'

interface ErrorDetails {
title: string
description: string
statusCode?: number
}

interface ErrorDisplayProps extends ErrorDetails {
children?: ReactNode
}

export type ErrorDisplayType = FC<ErrorDisplayProps>

interface ErrorContextType {
error: ErrorDetails | null
setError: (error: ErrorDetails | null) => void
DisplayError: ErrorDisplayType
}

const DefaultErrorDisplay: ErrorDisplayType = ({ title, description, children }) => (
<>
<div className="alert alert-danger m-3" role="alert">
<h4>{title}</h4>
{description}
</div>
{children}
</>
)
export const DisplayError: FC<ErrorDisplayProps> = ({ title, description, statusCode, children }) => {
const props: ErrorProps = {
title,
description,
statusCode,
children,
type: 'Error',
}
const CustomRenderComp = useCustomRender(props)
if (CustomRenderComp) {
return <CustomRenderComp />
} else {
return <ErrorComp {...props} />
}
}

export const ErrorContext = createContext<ErrorContextType>({
error: null,
setError: () => null,
DisplayError: DefaultErrorDisplay,
})

const MaybeError: FC<{ children: ReactNode }> = ({ children }) => {
const { error, DisplayError } = useContext(ErrorContext)
const { error } = useContext(ErrorContext)
if (error) {
return <DisplayError {...error}>{children}</DisplayError>
} else {
Expand All @@ -43,11 +52,10 @@ const MaybeError: FC<{ children: ReactNode }> = ({ children }) => {
}

interface Props {
DisplayError?: ErrorDisplayType
children: ReactNode
}

export const ErrorContextProvider: FC<Props> = ({ DisplayError, children }) => {
export const ErrorContextProvider: FC<Props> = ({ children }) => {
const [error, setErrorState] = useState<ErrorDetails | null>(null)

const setError = useCallback(
Expand All @@ -59,10 +67,9 @@ export const ErrorContextProvider: FC<Props> = ({ DisplayError, children }) => {
},
[setErrorState],
)
const contextValue: ErrorContextType = { error, setError, DisplayError: DisplayError ?? DefaultErrorDisplay }

return (
<ErrorContext.Provider value={contextValue}>
<ErrorContext.Provider value={{ error, setError }}>
<MaybeError>{children}</MaybeError>
</ErrorContext.Provider>
)
Expand Down
16 changes: 7 additions & 9 deletions src/npm-fastui/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { FC, ReactNode } from 'react'

import type { ErrorDisplayType } from './hooks/error'
import type { FastProps } from './models'

import { LocationProvider } from './hooks/locationContext'
Expand All @@ -26,25 +25,24 @@ export interface FastUIProps {
Spinner?: FC
NotFound?: FC<{ url: string }>
Transition?: FC<{ children: ReactNode; transitioning: boolean }>
DisplayError?: ErrorDisplayType
classNameGenerator?: ClassNameGenerator
customRender?: CustomRender
// defaults to `process.env.NODE_ENV === 'development'
devMode?: boolean
}

export function FastUI(props: FastUIProps) {
const { classNameGenerator, DisplayError, devMode, ...rest } = props
const { classNameGenerator, devMode, ...rest } = props
return (
<ErrorContextProvider DisplayError={DisplayError}>
<LocationProvider>
<ClassNameContext.Provider value={classNameGenerator ?? null}>
<ClassNameContext.Provider value={classNameGenerator ?? null}>
<ErrorContextProvider>
<LocationProvider>
<ConfigContext.Provider value={rest}>
<DevReload enabled={devMode} />
<FastUIController />
</ConfigContext.Provider>
</ClassNameContext.Provider>
</LocationProvider>
</ErrorContextProvider>
</LocationProvider>
</ErrorContextProvider>
</ClassNameContext.Provider>
)
}
9 changes: 9 additions & 0 deletions src/npm-fastui/src/models.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export type FastProps =
| Iframe
| Video
| FireEvent
| Error
| Custom
| Table
| Pagination
Expand Down Expand Up @@ -243,6 +244,14 @@ export interface FireEvent {
message?: string
type: 'FireEvent'
}
export interface Error {
title: string
description: string
statusCode?: number
className?: ClassName
type: 'Error'
children?: ReactNode
}
export interface Custom {
data: JsonData
subType: string
Expand Down
7 changes: 6 additions & 1 deletion src/npm-fastui/src/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ export function useRequest(): (args: RequestArgs) => Promise<[number, any]> {
try {
return await request(args)
} catch (e) {
setError({ title: 'Request Error', description: (e as any)?.message })
const title = 'Request Error'
if (e instanceof RequestError) {
setError({ title, description: e.message, statusCode: e.status })
} else {
setError({ title, description: (e as any)?.message })
}
throw e
}
},
Expand Down
19 changes: 19 additions & 0 deletions src/python-fastui/fastui/components/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
'Image',
'Iframe',
'FireEvent',
'Error',
'Custom',
'Table',
'Pagination',
Expand Down Expand Up @@ -271,6 +272,23 @@ class FireEvent(_p.BaseModel, extra='forbid'):
type: _t.Literal['FireEvent'] = 'FireEvent'


class Error(_p.BaseModel, extra='forbid'):
title: str
description: str
status_code: _t.Union[int, None] = _p.Field(None, serialization_alias='statusCode')
class_name: _class_name.ClassNameField = None
type: _t.Literal['Error'] = 'Error'

@classmethod
def __get_pydantic_json_schema__(
cls, core_schema: _core_schema.CoreSchema, handler: _p.GetJsonSchemaHandler
) -> _t.Any:
# add `children` to the schema so it can be used in the client
json_schema = handler(core_schema)
json_schema['properties']['children'] = {'tsType': 'ReactNode'}
return json_schema


class Custom(_p.BaseModel, extra='forbid'):
data: _types.JsonData
sub_type: str = _p.Field(serialization_alias='subType')
Expand Down Expand Up @@ -301,6 +319,7 @@ class Custom(_p.BaseModel, extra='forbid'):
Iframe,
Video,
FireEvent,
Error,
Custom,
Table,
Pagination,
Expand Down

0 comments on commit 99dc253

Please sign in to comment.