From 9daf6d9c50122f126985eab5a1e6a972c3c63287 Mon Sep 17 00:00:00 2001 From: Jovan Cvetkovic Date: Sun, 26 Mar 2023 20:20:45 +0200 Subject: [PATCH] [FEATURE] Create global state object for async requests #491 Signed-off-by: Jovan Cvetkovic --- public/pages/Main/components/Callout.tsx | 14 +- public/store/DetectorsStore.tsx | 249 ++++++++++++----------- 2 files changed, 136 insertions(+), 127 deletions(-) diff --git a/public/pages/Main/components/Callout.tsx b/public/pages/Main/components/Callout.tsx index 7d2426f76..6eb3da5b9 100644 --- a/public/pages/Main/components/Callout.tsx +++ b/public/pages/Main/components/Callout.tsx @@ -13,8 +13,8 @@ import { EuiLoadingSpinner, } from '@elastic/eui'; -export type TCalloutColor = 'primary' | 'success' | 'warning' | 'danger' | undefined; -export type TCalloutIcon = 'iInCircle' | 'check' | 'help' | 'alert' | undefined; +export type TCalloutColor = 'primary' | 'success' | 'warning' | 'danger'; +export type TCalloutIcon = 'iInCircle' | 'check' | 'help' | 'alert'; interface ICalloutType { color: TCalloutColor; @@ -22,10 +22,9 @@ interface ICalloutType { } export interface ICalloutProps { - title: string; + title: string | JSX.Element; message?: string | JSX.Element; type?: ICalloutType | TCalloutColor; - closable?: boolean; loading?: boolean; closeHandler?: (callout?: ICalloutProps) => void; @@ -40,7 +39,7 @@ export const CallOut = ({ closeHandler, }: ICalloutProps) => { const toastTypes: { - [Key in TCalloutColor]?: TCalloutIcon; + [Key in TCalloutColor]: TCalloutIcon; } = { primary: 'iInCircle', success: 'check', @@ -48,7 +47,7 @@ export const CallOut = ({ danger: 'alert', }; - const resolveType = (type?: ICalloutType | TCalloutColor | undefined): ICalloutType => { + const resolveType = (type?: ICalloutType | TCalloutColor): ICalloutType => { if (type === undefined) { return { color: 'primary', @@ -66,8 +65,6 @@ export const CallOut = ({ } }; - const { color, iconType } = resolveType(type); - const closeCallout = () => closeHandler && closeHandler(undefined); const getTitle = (): JSX.Element => { @@ -88,6 +85,7 @@ export const CallOut = ({ ); }; + const { color, iconType } = resolveType(type); return ( <> void; + getState: () => IDetectorsState | undefined; + deleteState: () => void; + getPendingState: () => Promise<{ + detectorId?: string; + dashboardId?: string; + ok: boolean; + }>; + setCalloutHandler: (calloutHandler: (callout?: ICalloutProps) => void) => void; +} + export interface IDetectorsCache {} export interface IDetectorsState { @@ -51,7 +66,7 @@ export class DetectorsStore implements IDetectorsStore { /** * SavedObjectsService - * @property {SavedObjectsService} + * @property {ISavedObjectsService} * @readonly */ readonly savedObjectsService: ISavedObjectsService; @@ -61,7 +76,7 @@ export class DetectorsStore implements IDetectorsStore { * @property {RouteComponentProps['history']} * @readonly */ - private history: RouteComponentProps['history'] | undefined = undefined; + history: RouteComponentProps['history'] | undefined = undefined; /** * Keeps detector's data cached @@ -89,38 +104,98 @@ export class DetectorsStore implements IDetectorsStore { /** * Invalidates all detectors data */ - private invalidateCache = () => { + private invalidateCache = (): DetectorsStore => { this.cache = {}; return this; }; - public setState = async (state: IDetectorsState, history: RouteComponentProps['history']) => { + public setState = (state: IDetectorsState, history: RouteComponentProps['history']): void => { this.state = state; this.history = history; - this.showCallout({ - title: 'Attempting to create the detector.', - loading: true, - }); + this.showNotification('Attempting to create the detector.', undefined, 'primary', true); }; - public getState = () => (this.state ? this.state : undefined); + public getState = (): IDetectorsState | undefined => (this.state ? this.state : undefined); - public deleteState = () => { + public deleteState = (): void => { delete this.state; }; - mountToaster = (component: React.ReactElement) => (container: HTMLElement) => { + private showNotification = ( + title: string, + message?: string, + type?: TCalloutColor, + loading?: boolean, + btnText?: string, + btnHandler?: (e: any) => void + ): void => { + if (!type) type = 'primary'; + + const btn = btnText ? ( + { + btnHandler && btnHandler(e); + this.hideCallout(); + this.notifications?.toasts.remove(toast); + }} + size="s" + > + {btnText} + + ) : null; + + this.showCallout({ + type, + title, + message: ( + <> + {message} + {btn} + + ), + closeHandler: this.showCallout, + }); + + let toastGenerator; + switch (type) { + case 'danger': + toastGenerator = this.notifications?.toasts.addDanger; + break; + case 'warning': + toastGenerator = this.notifications?.toasts.addWarning; + break; + case 'success': + toastGenerator = this.notifications?.toasts.addSuccess; + break; + default: + toastGenerator = this.notifications?.toasts.addInfo; + break; + } + + const toast = toastGenerator.bind(this.notifications?.toasts)({ + title: title, + text: this.mountToaster( + <> + {message ?

{message}

: null} + {btn} + + ), + toastLifeTimeMs: 5000, + }); + }; + + private mountToaster = (component: React.ReactElement) => (container: HTMLElement) => { ReactDOM.render(component, container); return () => ReactDOM.unmountComponentAtNode(container); }; - viewDetectorConfiguration = () => { + private viewDetectorConfiguration = (): void => { const pendingState = DataStore.detectors.getState(); const detectorState = pendingState?.detectorState; this.history?.push({ pathname: `${ROUTES.DETECTORS_CREATE}`, - // @ts-ignore state: { detectorState }, }); DataStore.detectors.deleteState(); @@ -136,78 +211,41 @@ export class DetectorsStore implements IDetectorsStore { this.state?.pendingRequests )) as [ServerResponse, ServerResponse]; + let title: string = `Create detector failed.`; if (!mappingsResponse.ok) { - const error = { - title: `Create detector failed.`, - message: 'Double check the field mappings and try again.', - }; - this.showCallout({ - ...error, - closeHandler: this.showCallout, - }); - - const toast = this.notifications?.toasts.addDanger({ - title: error.title, - text: this.mountToaster( - <> -

{error.message}

- { - this.hideCallout(); - DataStore.detectors.viewDetectorConfiguration(); - this.notifications?.toasts.remove(toast); - }} - size="s" - > - Review detector configuration - - - ), - toastLifeTimeMs: 30000, - }); + const message = 'Double check the field mappings and try again.'; + + this.showNotification( + title, + message, + 'danger', + false, + 'Review detector configuration', + DataStore.detectors.viewDetectorConfiguration + ); return Promise.resolve({ ok: false, - error: error, + error: { title, message }, }); } if (!detectorResponse.ok) { - const error = { - title: `Create detector failed.`, - message: detectorResponse.error, - }; - - this.showCallout({ - ...error, - closeHandler: this.showCallout, - }); - - const toast = this.notifications?.toasts.addDanger({ - title: error.title, - text: this.mountToaster( - <> -

{error.message}

- { - this.hideCallout(); - DataStore.detectors.viewDetectorConfiguration(); - this.notifications?.toasts.remove(toast); - }} - size="s" - > - Review detector configuration - - - ), - toastLifeTimeMs: 30000, - }); + this.showNotification( + title, + detectorResponse.error, + 'danger', + false, + 'Review detector configuration', + DataStore.detectors.viewDetectorConfiguration + ); return Promise.resolve({ ok: false, - error: error, + error: { + title, + message: detectorResponse.error, + }, }); } @@ -236,32 +274,21 @@ export class DetectorsStore implements IDetectorsStore { } } - const success = { - title: `Detector created successfully: ${detectorResponse.response.detector.name}`, + const goToDetectorDetails = (e: any) => { + e.preventDefault(); + DataStore.detectors.deleteState(); + this.history?.push(`${ROUTES.DETECTOR_DETAILS}/${detectorId}`); }; - this.showCallout({ - type: 'success', - ...success, - closeHandler: this.showCallout, - }); - const toast = this.notifications?.toasts.addSuccess({ - title: success.title, - text: this.mountToaster( - { - this.hideCallout(); - DataStore.detectors.deleteState(); - this.history?.push(`${ROUTES.DETECTOR_DETAILS}/${detectorId}`); - this.notifications?.toasts.remove(toast); - }} - size="s" - > - View detector - - ), - toastLifeTimeMs: 30000, - }); + title = `Detector created successfully: ${detectorResponse.response.detector.name}`; + this.showNotification( + title, + undefined, + 'success', + false, + 'View detector', + goToDetectorDetails + ); return Promise.resolve({ detectorId: detectorId, @@ -273,22 +300,6 @@ export class DetectorsStore implements IDetectorsStore { return Promise.resolve({ ok: false }); }; - showNotification = ( - title: string, - message: string, - loading: boolean, - type: string = 'success', - btnText: string, - btnHandler: () => void - ) => { - this.showCallout({ - type, - title, - message, - closeHandler: this.showCallout, - }); - }; - private createDashboard = ( detectorName: string, logType: string, @@ -302,11 +313,11 @@ export class DetectorsStore implements IDetectorsStore { }); }; - showCallout = (callout?: ICalloutProps | undefined): void => {}; + private showCallout = (callout?: ICalloutProps | undefined): void => {}; - hideCallout = () => this.showCallout(undefined); + private hideCallout = (): void => this.showCallout(undefined); - public setCalloutHandler = (calloutHandler: (callout?: ICalloutProps) => void) => { + public setCalloutHandler = (calloutHandler: (callout?: ICalloutProps) => void): void => { this.showCallout = calloutHandler; }; }