From b7b2806a14acf1b99e539d08495a80f20169fb0f Mon Sep 17 00:00:00 2001 From: Milho001 <l.milhomemfrancochristino@uu.nl> Date: Wed, 30 Aug 2023 14:40:12 +0000 Subject: [PATCH] feat(alerts): add alert banners with a few default alerts --- apps/web/src/app/app.tsx | 2 + libs/config/tailwind.config.js | 2 +- .../WebSocketHandler.tsx | 2 - .../socket/broker/brokerAlerts.tsx | 91 +++++++++++++++++++ .../lib/data-access/socket/broker/index.tsx | 45 ++++++++- 5 files changed, 135 insertions(+), 7 deletions(-) create mode 100644 libs/shared/lib/data-access/socket/broker/brokerAlerts.tsx diff --git a/apps/web/src/app/app.tsx b/apps/web/src/app/app.tsx index 2759ac280..814d865fc 100644 --- a/apps/web/src/app/app.tsx +++ b/apps/web/src/app/app.tsx @@ -28,6 +28,7 @@ import { logout } from '@graphpolaris/shared/lib/data-access/store/authSlice'; import { SchemaFromBackend } from '@graphpolaris/shared/lib/schema'; import { LinkPredictionInstance, setMLResult, allMLTypes } from '@graphpolaris/shared/lib/data-access/store/mlSlice'; import { Resizable } from '@graphpolaris/shared/lib/components/Resizable'; +import { BrokerAlerts } from '@graphpolaris/shared/lib/data-access/socket/broker/brokerAlerts'; export interface App {} @@ -105,6 +106,7 @@ export function App(props: App) { return ( <div className="h-screen w-screen"> + <BrokerAlerts /> <div className={'h-screen w-screen ' + (!auth.authorized ? 'blur-sm pointer-events-none ' : '')}> <div className="flex flex-col h-screen max-h-screen relative"> <aside className="h-[4rem]"> diff --git a/libs/config/tailwind.config.js b/libs/config/tailwind.config.js index fa3bfc85e..59520b7a0 100644 --- a/libs/config/tailwind.config.js +++ b/libs/config/tailwind.config.js @@ -41,7 +41,7 @@ export default { 'base-100': '#F7F9FA', // 'base-200': '#F9FBFC', info: '#99622E', - success: '#2DC177', + success: '#35a06a', warning: '#eab308', error: '#dc2626', diff --git a/libs/shared/lib/data-access/socket/backend-message-receiver/WebSocketHandler.tsx b/libs/shared/lib/data-access/socket/backend-message-receiver/WebSocketHandler.tsx index 64b41b9df..d61e843a2 100644 --- a/libs/shared/lib/data-access/socket/backend-message-receiver/WebSocketHandler.tsx +++ b/libs/shared/lib/data-access/socket/backend-message-receiver/WebSocketHandler.tsx @@ -67,8 +67,6 @@ export class WebSocketHandler implements BackendMessageReceiver { */ public onWebSocketMessage = (event: MessageEvent<any>) => { let data = JSON.parse(event.data); - console.debug('WS message: ', data); - Broker.instance().publish(data.value, data.type); }; diff --git a/libs/shared/lib/data-access/socket/broker/brokerAlerts.tsx b/libs/shared/lib/data-access/socket/broker/brokerAlerts.tsx new file mode 100644 index 000000000..a2b96066b --- /dev/null +++ b/libs/shared/lib/data-access/socket/broker/brokerAlerts.tsx @@ -0,0 +1,91 @@ +import React, { ReactNode, useEffect, useState } from 'react'; +import Broker from '@graphpolaris/shared/lib/data-access/socket/broker'; +import { useImmer } from 'use-immer'; +import styles from './brokerAlerts.module.scss'; +import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; + +type Message = { + message: ReactNode; + className: string; +}; + +export const BrokerAlerts = (props: { timer?: number }) => { + const [messages, setMessages] = useImmer<{ data: any; routingKey: string; message: Message; showing: boolean }[]>([]); + const timer = props.timer || 1200; + + useEffect(() => { + Broker.instance().subscribeDefault((data: any, routingKey: string) => { + let message: Message | undefined = undefined; + + // Use the logic below to define which broker messages should be shown as alerts. + if (routingKey === 'schema_result') { + message = { + message: ( + <> + <CheckCircleOutlineIcon /> Schema graph updated + </> + ), + className: 'alert-success', + }; + } else if (routingKey === 'query_result') { + message = { + message: ( + <> + <CheckCircleOutlineIcon /> Query Executed! + </> + ), + className: 'alert-success', + }; + } + + // Queue the message to be shown for props.time time, then fade it out matching the animation of animate-closemenu fade out time + if (message) { + setMessages((draft) => { + draft.unshift({ data, routingKey, message: message as Message, showing: true }); + return draft; + }); + setTimeout(() => { + setMessages((draft) => { + draft[draft.length - 1].showing = false; + // draft.pop(); + return draft; + }); + + setTimeout(() => { + setMessages((draft) => { + draft.pop(); + return draft; + }); + }, 300); // matches the animation of animate-closemenu + }, timer); + } + }); + return () => { + Broker.instance().unSubscribeDefault(); + }; + }, []); + + return ( + <> + {messages && + messages.map((m, i) => { + return ( + <div + key={i} + className={ + `absolute bottom-5 right-5 w-fit min-w-[20rem] alert z-50 ` + + (m.showing ? 'animate-openmenu ' : 'animate-closemenu ') + + (m.message?.className || '') + } + style={{ + transform: `translateY(-${i * 5}rem)`, + }} + > + <span className="flex flex-row content-center gap-3 text-white">{m.message.message}</span> + {/* {!message && (m.data?.status ? m.data.status : m.routingKey)} */} + </div> + ); + })} + </> + ); +}; diff --git a/libs/shared/lib/data-access/socket/broker/index.tsx b/libs/shared/lib/data-access/socket/broker/index.tsx index 098413fd9..cd47d1921 100644 --- a/libs/shared/lib/data-access/socket/broker/index.tsx +++ b/libs/shared/lib/data-access/socket/broker/index.tsx @@ -20,6 +20,7 @@ export default class Broker { private static singletonInstance: Broker; private listeners: Record<string, Record<string, Function>> = {}; + private catchAllListener: Function | undefined; /** mostRecentMessages is a dictionary with <routingkey, messageObject>. It stores the most recent message for that routingkey. */ private mostRecentMessages: Record<string, unknown> = {}; @@ -38,15 +39,34 @@ export default class Broker { public publish(jsonObject: unknown, routingKey: string): void { this.mostRecentMessages[routingKey] = jsonObject; - if (this.listeners[routingKey] && Object.keys(this.listeners[routingKey]).length != 0) + if (this.listeners[routingKey] && Object.keys(this.listeners[routingKey]).length != 0) { + if (this.catchAllListener) { + this.catchAllListener(jsonObject, routingKey); + } Object.values(this.listeners[routingKey]).forEach((listener) => listener(jsonObject, routingKey)); - // If there are no listeners, log the message - else console.debug( - `no listeners for message with routing key %c${routingKey}`, + `message processed with routing key %c${routingKey}`, 'font-weight:bold; color: blue; background-color: white;', jsonObject ); + } + // If there are no listeners, log the message + else { + if (this.catchAllListener) { + this.catchAllListener(jsonObject, routingKey); + console.debug( + `catch all used for message with routing key %c${routingKey}`, + 'font-weight:bold; color: blue; background-color: white;', + jsonObject + ); + } else { + console.debug( + `no listeners for message with routing key %c${routingKey}`, + 'font-weight:bold; color: blue; background-color: white;', + jsonObject + ); + } + } } /** @@ -74,6 +94,16 @@ export default class Broker { return key; } + /** + * Subscribe a listener to messages with the specified routingKey. + * @param {Function} newListener The listener to subscribe. + * @param {string} routingKey The routingkey to subscribe to. + * @param {boolean} consumeMostRecentMessage If true and there is a message for this routingkey available, notify the new listener. Default true. + */ + public subscribeDefault(newListener: Function): void { + this.catchAllListener = newListener; + } + /** * Unsubscribes a listener from messages with specified routingkey. * @param {string} routingKey The routing key to unsubscribe from @@ -85,6 +115,13 @@ export default class Broker { } } + /** + * Unsubscribes the catch all listener from messages + */ + public unSubscribeDefault(): void { + this.catchAllListener = undefined; + } + /** * Unsubscribes all listeners from messages with specified routingkey. * @param {string} routingKey The routing key to unsubscribe from -- GitLab