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