Skip to content

Commit

Permalink
Error Handling (#752)
Browse files Browse the repository at this point in the history
* added the ErrorBoundary component

* added the rtkQueryErrorLogger middleware

* implemented logic of saving the RTK Query errors in the store

* added the ErrorHandler logic

* refactoring and added types

* updated the ErrorHandler component

* Update the ErrorBoundary

* added the test code

* updated the ErrorBoundary logic

* deleted the test code

* added the useApiErrorHandler hook

* updated the test code

* updated the useApiErrorHandler and deleted unused logic

* updated the ErrorBoundary

* refactoring

* deleted the test code

* added styles for the ErrorBoundary

* updated the useApiErrorHandler hook

* deleted the test code

* updated the useApiErrorHandler hook by creating the method for tracking errors directly

* added the ErrorHandler module and updated the error approach

* refactored the ErrorHandler logic and updated types for the Tag

* updated the ApiError class

* added export file

* updated the trackError method and updated the components by using it instead of throwing the error

* added the trackError instead of using error throwing

* added the trackError method instead of throwing error

* fixed types

* fixed issues

* fixed issues

* fixed issue

* updated the type

* updated the ErrorHandler by adding the handler prop and fixed statement

* Automatic frontend build

---------

Co-authored-by: ValeriaMaltseva <[email protected]>
  • Loading branch information
ValeriaMaltseva and ValeriaMaltseva authored Dec 11, 2024
1 parent 195de2c commit 205a4ed
Show file tree
Hide file tree
Showing 69 changed files with 2,918 additions and 107 deletions.
5 changes: 3 additions & 2 deletions assets/js/src/core/app/api/pimcore/tags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/

import { type ElementType } from 'types/element-type.d'
import trackError, { GeneralError } from '@Pimcore/modules/app/error-handler'

export type Tag = string | {
type: string
Expand Down Expand Up @@ -97,13 +98,13 @@ export const invalidatingTags = {
ELEMENT_NOTES_AND_EVENTS: (elementType: ElementType, id: number) => [getElementDetailTag(elementType, id), tagNames.NOTES_AND_EVENTS]
}

const getElementDetailTag = (elementType: ElementType, id: number): Tag => {
const getElementDetailTag = (elementType: ElementType, id: number): Tag | undefined => {
switch (elementType) {
case 'asset':
return { type: tagNames.ASSET_DETAIL, id }
case 'data-object':
return { type: tagNames.DATA_OBJECT_DETAIL, id }
}

throw new Error(`Unknown element type: ${elementType}`)
trackError(new GeneralError(`Unknown element type: ${elementType}`))
}
4 changes: 3 additions & 1 deletion assets/js/src/core/components/drag-and-drop/draggable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { useDraggable } from '@dnd-kit/core'
import { type DragAndDropInfo } from './context-provider'
import { uuid } from '@Pimcore/utils/uuid'
import { GlobalStyle } from './draggable.styles'
import trackError, { GeneralError } from '@Pimcore/modules/app/error-handler'

interface DraggableProps {
children: React.ReactNode
Expand All @@ -32,7 +33,8 @@ function Draggable (props: DraggableProps): React.JSX.Element {
const Child = Children.only(props.children)

if (!isValidElement(Child)) {
throw new Error('Children must be a valid react component')
trackError(new GeneralError('Children must be a valid react component'))
throw new Error('Invalid React child element.')
}

const Component = Child.type
Expand Down
7 changes: 5 additions & 2 deletions assets/js/src/core/components/drag-and-drop/droppable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { useStyle } from './droppable.styles'
import { DroppableContextProvider } from './droppable-context-provider'
import { uuid } from '@Pimcore/utils/uuid'
import cn from 'classnames'
import trackError, { GeneralError } from '@Pimcore/modules/app/error-handler'

export interface DroppableContentProps {
isDragActive: boolean
Expand All @@ -36,7 +37,7 @@ export interface DroppableProps {
onSort?: (info: DragAndDropInfo, dragId: UniqueIdentifier, dropId: UniqueIdentifier) => void
}

export const Droppable = (props: DroppableProps): React.JSX.Element => {
export const Droppable = (props: DroppableProps): React.JSX.Element | null => {
const { styles } = useStyle()
const context = useContext(DragAndDropInfoContext)
const [isValidContext, setIsValidContext] = useState(false)
Expand Down Expand Up @@ -86,7 +87,9 @@ export const Droppable = (props: DroppableProps): React.JSX.Element => {
const Child = Children.only(props.children)

if (!isValidElement(Child)) {
throw new Error('Children must be a valid react component')
trackError(new GeneralError('Children must be a valid react component'))

return null
}

const Component = Child.type
Expand Down
21 changes: 12 additions & 9 deletions assets/js/src/core/components/focal-point/focal-point.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@ import { restrictToParentElement } from '@dnd-kit/modifiers'
import { useAssetDraft } from '@Pimcore/modules/asset/hooks/use-asset-draft'
import { AssetContext } from '@Pimcore/modules/asset/asset-provider'
import { FocalPointContext } from '@Pimcore/components/focal-point/context/focal-point-context'
import trackError, { GeneralError } from '@Pimcore/modules/app/error-handler'

interface FocalPointProps {
activationConstraint?: PointerActivationConstraint
children: React.ReactNode
}

export const FocalPoint = ({ activationConstraint, children }: FocalPointProps): React.JSX.Element => {
export const FocalPoint = ({ activationConstraint, children }: FocalPointProps): React.JSX.Element | null => {
const Image = Children.only(children)
const { id } = useContext(AssetContext)
const focalPointContext = useContext(FocalPointContext)
Expand All @@ -44,7 +45,7 @@ export const FocalPoint = ({ activationConstraint, children }: FocalPointProps):
const { addImageSettings, removeImageSetting } = useAssetDraft(id)

if (focalPointContext === undefined) {
throw new Error('FocalPoint must be used within the FocalPointProvider')
trackError(new GeneralError('FocalPoint must be used within the FocalPointProvider'))
}

const {
Expand All @@ -54,13 +55,7 @@ export const FocalPoint = ({ activationConstraint, children }: FocalPointProps):
setIsActive,
disabled,
containerRef
} = focalPointContext

if (!isValidElement(Image)) {
throw new Error('Children must be a valid react component')
}

const ImageComponent = Image.type
} = focalPointContext!

const onLoad = (): void => {
if (
Expand All @@ -87,6 +82,14 @@ export const FocalPoint = ({ activationConstraint, children }: FocalPointProps):
}
}, [isActive])

if (!isValidElement(Image)) {
trackError(new GeneralError('Children must be a valid react component'))

return null
}

const ImageComponent = Image.type

return (
<DndContext
modifiers={ [restrictToParentElement] }
Expand Down
3 changes: 2 additions & 1 deletion assets/js/src/core/components/grid/columns/default-cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { useKeyboardNavigation } from '../keyboard-navigation/use-keyboard-navig
import { usePrevious } from '@Pimcore/utils/hooks/use-previous'
import { type ExtendedCellContext } from '../grid'
import { useDynamicTypeResolver } from '@Pimcore/modules/element/dynamic-types/resolver/hooks/use-dynamic-type-resolver'
import trackError, { GeneralError } from '@Pimcore/modules/app/error-handler'

export interface DefaultCellProps extends ExtendedCellContext {}

Expand Down Expand Up @@ -102,7 +103,7 @@ export const DefaultCell = ({ ...props }: DefaultCellProps): React.JSX.Element =
}

if (isEditable && table.options.meta?.onUpdateCellData === undefined) {
throw new Error('onUpdateCellData is required when using editable cells')
trackError(new GeneralError('onUpdateCellData is required when using editable cells'))
}

setIsInEditMode(true)
Expand Down
3 changes: 2 additions & 1 deletion assets/js/src/core/components/grid/grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { GridRow } from './grid-cell/grid-row'
import { SortButton, type SortDirection, SortDirections } from '../sort-button/sort-button'
import { DynamicTypeRegistryProvider } from '@Pimcore/modules/element/dynamic-types/registry/provider/dynamic-type-registry-provider'
import { type GridProps } from '@Pimcore/types/components/types'
import trackError, { GeneralError } from '@Pimcore/modules/app/error-handler'

declare module '@tanstack/react-table' {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand Down Expand Up @@ -174,7 +175,7 @@ export const Grid = ({ enableMultipleRowSelection = false, modifiedCells = [], s
for (const column of columns) {
if (column.meta?.autoWidth === true) {
if (autoWidthColumnFound) {
throw new Error('Only one column can have autoWidth set to true')
trackError(new GeneralError('Only one column can have autoWidth set to true'))
}
autoWidthColumnFound = true
}
Expand Down
13 changes: 7 additions & 6 deletions assets/js/src/core/components/login-form/login-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import { useMessage } from '@Pimcore/components/message/useMessage'
import { useTranslation } from 'react-i18next'
import { setUser } from '@Pimcore/modules/auth/user/user-slice'
import { Icon } from '../icon/icon'
import { type Credentials, useLoginMutation, type UserInformation } from '@Pimcore/modules/auth/authorization-api-slice.gen'
import { type Credentials, useLoginMutation } from '@Pimcore/modules/auth/authorization-api-slice.gen'
import trackError, { ApiError } from '@Pimcore/modules/app/error-handler'

export interface IAdditionalLogins {
key: string
Expand Down Expand Up @@ -49,19 +50,19 @@ export const LoginForm = ({ additionalLogins }: ILoginFormProps): React.JSX.Elem
const handleAuthentication = async (event: React.FormEvent<HTMLFormElement>): Promise<void> => {
const loginTask = login({ credentials: formState })

loginTask.catch((error) => {
throw new Error(error.message as string)
loginTask.catch((error: Error) => {
trackError(new ApiError(error))
})

try {
event.preventDefault()
const response = (await loginTask) as any
const response = (await loginTask)

if (response.error !== undefined) {
throw new Error(response.error.data.error as string)
trackError(new ApiError(response.error))
}

const userInformation = response.data as UserInformation
const userInformation = response.data!
dispatch(setUser(userInformation))
} catch (e: any) {
void messageApi.error({
Expand Down
5 changes: 3 additions & 2 deletions assets/js/src/core/components/sidebar/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import { useStyle } from './sidebar.styles'
import React, { isValidElement, useState } from 'react'
import { type ISidebarButton, type ISidebarEntry } from '@Pimcore/modules/element/sidebar/sidebar-manager'
import trackError, { GeneralError } from '@Pimcore/modules/app/error-handler'

interface SidebarProps {
entries: ISidebarEntry[]
Expand Down Expand Up @@ -89,11 +90,11 @@ export const Sidebar = ({ entries, buttons = [], sizing = 'default', highlights
const { component, key, ...props } = button

if (!isValidElement(component)) {
throw new Error('SidebarButton must be a valid react component')
trackError(new GeneralError('SidebarButton must be a valid react component'))
}

const SidebarButton = component.type
const sidebarButtonProps = component.props as any
const sidebarButtonProps = component.props

return (
<SidebarButton
Expand Down
20 changes: 17 additions & 3 deletions assets/js/src/core/modules/app/app-loader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,36 @@ import {
} from '../asset/editor/types/folder/tab-manager/tabs/list/toolbar/tools/mercure-api-slice.gen'
import { Content } from '@Pimcore/components/content/content'
import { GlobalStyles } from '@Pimcore/styles/global.styles'
import { useAlertModal } from '@Pimcore/components/modal/alert-modal/hooks/use-alert-modal'
import { ErrorModalService } from '@Pimcore/modules/app/error-handler/services/error-modal-service'
import trackError, { ApiError } from '@Pimcore/modules/app/error-handler'

export interface IAppLoaderProps {
children: React.ReactNode
}

export const AppLoader = (props: IAppLoaderProps): React.JSX.Element => {
const dispatch = useAppDispatch()
const [translations] = useTranslationGetCollectionMutation()
const { i18n } = useTranslation()

const [isLoading, setIsLoading] = useState(true)

const [translations] = useTranslationGetCollectionMutation()
const [fetchMercureCookie] = useMercureCreateCookieMutation()

const modal = useAlertModal()

// Register the modal instance to allow centralized error message display throughout the project
ErrorModalService.setModalInstance(modal)

async function initLoadUser (): Promise<any> {
const userFetcher = dispatch(api.endpoints.userGetCurrentInformation.initiate())
await fetchMercureCookie()

userFetcher
.then(({ data, isSuccess }) => {
.then(({ data, isSuccess, isError, error }) => {
isError && trackError(new ApiError(error))

if (isSuccess && data !== undefined) {
dispatch(setUser(data))
}
Expand All @@ -55,7 +67,9 @@ export const AppLoader = (props: IAppLoaderProps): React.JSX.Element => {
const settingsFetcher = dispatch(settingsApi.endpoints.systemSettingsGet.initiate())

settingsFetcher
.then(({ data, isSuccess }) => {
.then(({ data, isSuccess, isError, error }) => {
isError && trackError(new ApiError(error))

if (isSuccess && data !== undefined) {
dispatch(setSettings(data))
}
Expand Down
22 changes: 12 additions & 10 deletions assets/js/src/core/modules/app/app-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,22 @@ import { RouterProvider } from 'react-router-dom'
import { router } from '@Pimcore/app/router/router'
import { AppLoader } from '@Pimcore/modules/app/app-loader'
import { DateTimeConfig } from '@Pimcore/app/config/date-time'
import ErrorBoundary from '@Pimcore/modules/app/error-boundary/error-boundary'

export const AppView = (): React.JSX.Element => {
return (
<>

<GlobalProvider>
<AntApp>
<DateTimeConfig>
<AppLoader>
<RouterProvider router={ router } />
</AppLoader>
</DateTimeConfig>
</AntApp>
</GlobalProvider>
<ErrorBoundary>
<GlobalProvider>
<AntApp>
<DateTimeConfig>
<AppLoader>
<RouterProvider router={ router } />
</AppLoader>
</DateTimeConfig>
</AntApp>
</GlobalProvider>
</ErrorBoundary>
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import { injectable } from 'inversify'
import type React from 'react'
import trackError, { GeneralError } from '@Pimcore/modules/app/error-handler'

interface ComponentRegistryEntry<T> {
name: string
Expand All @@ -33,7 +34,7 @@ export class ComponentRegistry implements ComponentRegistryInterface {

register (component: ComponentRegistryEntry<any>): void {
if (this.has(component.name)) {
throw new Error(`Component with the name "${component.name}" already exists. Use the override method to override it`)
trackError(new GeneralError(`Component with the name "${component.name}" already exists. Use the override method to override it`))
}

this.registry[component.name] = component
Expand All @@ -45,7 +46,7 @@ export class ComponentRegistry implements ComponentRegistryInterface {

get<T>(name: string): ComponentRegistryEntry<T>['component'] {
if (!this.has(name)) {
throw new Error(`No component with the name "${name}" found`)
trackError(new GeneralError(`No component with the name "${name}" found`))
}

return this.registry[name].component
Expand All @@ -57,7 +58,7 @@ export class ComponentRegistry implements ComponentRegistryInterface {

override <T>(name: string, component: ComponentRegistryEntry<T>): void {
if (!this.has(name)) {
throw new Error(`No component named "${name}" found to override`)
trackError(new GeneralError(`No component named "${name}" found to override`))
}

this.registry[name] = component
Expand Down
Loading

0 comments on commit 205a4ed

Please sign in to comment.