-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/40 add error handling via notifications #125
Merged
zsoltker
merged 15 commits into
main
from
feature/40-add-error-handling-via-notifications
Nov 14, 2024
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
97c2675
implement toasters / notifications
zsoltker 240a27f
implement custom ErrorBoundary (part1)
zsoltker 52dea0f
error notification working version 1 -- small refinements needed
zsoltker 351441c
error notification working version 2 -- small refinements needed
zsoltker cca0669
Merge branch 'refs/heads/main' into feature/40-add-error-handling-via…
zsoltker 17df050
error notification working version 3 -- small refinements needed
zsoltker 8ca0e4e
error notification working version 4-- small refinements needed
zsoltker 6dc1c1b
error notification working version 5 (error context provider)
zsoltker aa2bc19
error notification working version 6
zsoltker 27644a4
fix comments
zsoltker b7c2f0a
Add unique IDs to messages for supporting removal of multiple messages.
zsoltker 6577c19
Fix toaster progress colors & clean
zsoltker b8780e2
test for notifications, error boundary and error context provider
zsoltker 41eb60f
reason !!! npm error Could not resolve dependency:
zsoltker 09ce387
fix typo
zsoltker File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { notify } from '../../components/notification/Notification'; | ||
import { NotificationMessageType } from '../../types/notification.model'; | ||
|
||
export abstract class BusinessException extends Error { | ||
messageType: NotificationMessageType; | ||
|
||
protected constructor(message: string, messageType?: NotificationMessageType) { | ||
super(message); | ||
this.messageType = messageType ? messageType : 'ERROR'; | ||
} | ||
|
||
handleNotification(publish?: () => void): void { | ||
notify({ | ||
messageType: this.messageType, | ||
message: this.message, | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import React, { useEffect } from 'react'; | ||
|
||
import { BusinessException } from '../../common/exceptions/BusinessException'; | ||
import { notify } from '../notification/Notification'; | ||
|
||
import { useErrorContext } from './ErrorContext'; | ||
|
||
type ErrorBoundaryProps = { | ||
children: React.ReactNode; | ||
}; | ||
|
||
const ErrorBoundary: React.FC<ErrorBoundaryProps> = ({ children }) => { | ||
const { publish } = useErrorContext(); // Use the error context | ||
|
||
const handleGlobalError = (event: ErrorEvent) => { | ||
event.preventDefault(); | ||
handleException(event.error); | ||
}; | ||
|
||
const handleUnhandledRejection = (event: PromiseRejectionEvent) => { | ||
event.preventDefault(); | ||
handleException(event.reason); | ||
}; | ||
|
||
const handleException = (error: Error | string) => { | ||
if (error instanceof BusinessException) { | ||
error.handleNotification(() => { | ||
publish(error); // Add the error to the context | ||
}); | ||
} else { | ||
const errorMessage = getErrorMessage(error); | ||
showNotification(errorMessage); | ||
} | ||
}; | ||
|
||
const getErrorMessage = (error: string | { message?: string }): string => { | ||
let errorMessage = ''; | ||
if (typeof error === 'string') { | ||
errorMessage = error; | ||
} else if (typeof error === 'object') { | ||
errorMessage = error.message || 'Unknown error'; | ||
} else { | ||
errorMessage = 'Unknown error'; | ||
} | ||
return errorMessage; | ||
}; | ||
|
||
const showNotification = (errorMessage: string) => { | ||
notify({ | ||
messageType: 'ERROR', | ||
message: errorMessage, | ||
}); | ||
}; | ||
|
||
useEffect(() => { | ||
window.addEventListener('error', handleGlobalError); | ||
window.addEventListener('unhandledrejection', handleUnhandledRejection); | ||
|
||
return () => { | ||
window.removeEventListener('error', handleGlobalError); | ||
window.removeEventListener('unhandledrejection', handleUnhandledRejection); | ||
}; | ||
}, []); | ||
|
||
return <>{children}</>; | ||
}; | ||
|
||
export default ErrorBoundary; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import React, { createContext, ReactNode, useContext, useState } from 'react'; | ||
import { v4 } from 'uuid'; | ||
|
||
import { BusinessException } from '../../common/exceptions/BusinessException'; | ||
|
||
// Define the shape of the context | ||
interface ErrorContextType { | ||
errorMessages: ErrorMessage[]; | ||
publish: (exception: BusinessException) => void; | ||
removeMessage: (messageId: string) => void; | ||
} | ||
|
||
// Create context with a default value of `undefined` | ||
const ErrorContext = createContext<ErrorContextType | undefined>(undefined); | ||
|
||
// Custom hook to access ErrorContext | ||
export const useErrorContext = (): ErrorContextType => { | ||
const context = useContext(ErrorContext); | ||
if (!context) { | ||
throw new Error('useErrorContext must be used within an ErrorProvider'); | ||
} | ||
return context; | ||
}; | ||
|
||
// Define the props type for ErrorProvider | ||
interface ErrorProviderProps { | ||
children: ReactNode; | ||
} | ||
|
||
type ErrorMessage = { | ||
messageId: string, | ||
exception: BusinessException, | ||
} | ||
|
||
// ErrorProvider component | ||
export const ErrorProvider: React.FC<ErrorProviderProps> = ({ children }) => { | ||
const [errorMessages, setErrorMessages] = useState<ErrorMessage[]>([]); | ||
|
||
const publish = (exception: BusinessException) => { | ||
const messageWithId = { | ||
messageId: v4(), | ||
exception: exception, | ||
}; | ||
setErrorMessages((prevList) => [...prevList, messageWithId]); | ||
}; | ||
|
||
const removeMessage = (messageId: string) => { | ||
setErrorMessages((prevList) => prevList.filter((msg) => msg.messageId !== messageId)); | ||
}; | ||
|
||
return ( | ||
<ErrorContext.Provider value={{ errorMessages, publish, removeMessage }}> | ||
{children} | ||
</ErrorContext.Provider> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
:root { | ||
--toastify-color-progressbar: #ddd; | ||
|
||
--toastify-color-progress-info: var(--toastify-color-progressbar); | ||
--toastify-color-progress-success: var(--toastify-color-progressbar); | ||
--toastify-color-progress-warning: var(--toastify-color-progressbar); | ||
--toastify-color-progress-error: var(--toastify-color-progressbar); | ||
--toastify-color-progress-light: var(--toastify-color-progressbar); | ||
--toastify-color-progress-dark: var(--toastify-color-progressbar); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { toast } from 'react-toastify'; | ||
import { ToastOptions } from 'react-toastify/dist/types'; | ||
|
||
import { Notification } from '../../types/notification.model'; | ||
|
||
export const notify = (notification: Notification) => { | ||
|
||
function getAutoCloseOption(toastOptions?: ToastOptions): number | false { | ||
return toastOptions && toastOptions.autoClose ? toastOptions.autoClose : 15000; | ||
} | ||
|
||
const toastOptions = { | ||
...notification.options, | ||
autoClose: getAutoCloseOption(notification.options), | ||
} | ||
|
||
switch (notification.messageType) { | ||
case 'SUCCESS': | ||
toast.success(notification.message, { | ||
...toastOptions, | ||
}); | ||
break; | ||
case 'ERROR': | ||
toast.error(notification.message, { | ||
...toastOptions, | ||
autoClose: false | ||
}); | ||
break; | ||
case 'WARNING': | ||
toast.warn(notification.message, { | ||
...toastOptions, | ||
}); | ||
break; | ||
case 'INFO': | ||
toast.info(notification.message, { | ||
...toastOptions, | ||
}); | ||
break; | ||
default: | ||
toast(notification.message, { | ||
...toastOptions, | ||
}); | ||
break | ||
} | ||
}; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
/* test coverage not required */ | ||
export type ExceptionType = 'BusinessException'; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/* test coverage not required */ | ||
import { ToastContent, ToastOptions } from 'react-toastify/dist/types'; | ||
|
||
export type NotificationMessageType = | ||
| 'SUCCESS' | ||
| 'ERROR' | ||
| 'WARNING' | ||
| 'INFO' | ||
|
||
export interface Notification { | ||
messageType: NotificationMessageType, | ||
message: ToastContent; | ||
options?: ToastOptions; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import '@testing-library/jest-dom'; | ||
import { BusinessException } from '../../../src/common/exceptions/BusinessException'; | ||
import { notify } from '../../../src/components/notification/Notification'; | ||
import { NotificationMessageType } from '../../../src/types/notification.model'; | ||
|
||
// Mock the `notify` function | ||
jest.mock('../../../src/components/notification/Notification', () => ({ | ||
notify: jest.fn(), | ||
})); | ||
|
||
// Define a concrete subclass for testing | ||
class DummyException extends BusinessException { | ||
constructor(message: string, messageType?: NotificationMessageType) { | ||
super(message, messageType); | ||
this.name = 'DummyException'; | ||
} | ||
} | ||
|
||
describe('BusinessException', () => { | ||
it('should call notify with correct arguments in handleNotification', () => { | ||
const message = 'Test error message'; | ||
const messageType: NotificationMessageType = 'ERROR'; | ||
const error = new DummyException(message, messageType); | ||
|
||
// Call handleNotification | ||
error.handleNotification(); | ||
|
||
// Verify that notify was called with the correct parameters | ||
expect(notify).toHaveBeenCalledWith({ | ||
messageType, | ||
message, | ||
}); | ||
}); | ||
|
||
it('should use default messageType if not provided', () => { | ||
const message = 'Another test error message'; | ||
const error = new DummyException(message); | ||
|
||
// Call handleNotification | ||
error.handleNotification(); | ||
|
||
// Verify that notify was called with the default messageType 'ERROR' | ||
expect(notify).toHaveBeenCalledWith({ | ||
messageType: 'ERROR', | ||
message, | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This comment was marked as resolved.
Sorry, something went wrong.