Skip to content
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

fix(notifications): added a debouncing mechanism #3327

Merged
merged 6 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions frontend/app/quality-assistant/ProcessTab/ProcessTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,8 @@ const ProcessTab = (): JSX.Element => {
</div>
<div>
<div
className={`${styles.first_line} ${
!filteredProcess.length ? styles.empty : ""
}`}
className={`${styles.first_line} ${!filteredProcess.length ? styles.empty : ""
}`}
>
<div className={styles.left}>
<Checkbox
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export const NotificationsButton = (): JSX.Element => {
const { supabase } = useSupabase();

useEffect(() => {
console.log("NotificationsButton");
const channel = supabase
.channel("notifications")
.on(
Expand Down
155 changes: 99 additions & 56 deletions frontend/lib/context/NotificationsProvider/notifications-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createContext, useEffect, useState } from "react";
import { createContext, useCallback, useEffect, useState } from "react";

import {
BulkNotification,
Expand All @@ -11,84 +11,126 @@ type NotificationsContextType = {
isVisible: boolean;
setIsVisible: React.Dispatch<React.SetStateAction<boolean>>;
bulkNotifications: BulkNotification[];
setBulkNotifications: React.Dispatch<
React.SetStateAction<BulkNotification[]>
>;
setBulkNotifications: React.Dispatch<React.SetStateAction<BulkNotification[]>>;
updateNotifications: () => Promise<void>;
unreadNotifications?: number;
setUnreadNotifications?: React.Dispatch<React.SetStateAction<number>>;
unreadNotifications: number;
setUnreadNotifications: React.Dispatch<React.SetStateAction<number>>;
};

export const NotificationsContext = createContext<
NotificationsContextType | undefined
>(undefined);
export const NotificationsContext = createContext<NotificationsContextType | undefined>(undefined);

export const NotificationsProvider = ({
children,
}: {
children: React.ReactNode;
}): JSX.Element => {
const [isVisible, setIsVisible] = useState(false);
const [unreadNotifications, setUnreadNotifications] = useState(0);
const [bulkNotifications, setBulkNotifications] = useState<
BulkNotification[]
>([]);
const [isVisible, setIsVisible] = useState<boolean>(false);
const [unreadNotifications, setUnreadNotifications] = useState<number>(0);
const [bulkNotifications, setBulkNotifications] = useState<BulkNotification[]>([]);
const { supabase } = useSupabase();

const updateNotifications = async () => {
const fetchNotifications = async (): Promise<NotificationType[]> => {
const { data, error } = await supabase.from("notifications").select();
if (error) {

return [];
}

// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unnecessary-condition
return data || [];
};

const processNotifications = (notifications: NotificationType[]): BulkNotification[] => {
const bulkMap: { [key: string]: NotificationType[] } = {};
notifications.forEach((notif) => {
if (!bulkMap[notif.bulk_id]) {
bulkMap[notif.bulk_id] = [];
}
bulkMap[notif.bulk_id].push(notif);
});

return Object.keys(bulkMap).map((bulkId) => ({
bulk_id: bulkId,
notifications: bulkMap[bulkId],
category: bulkMap[bulkId][0].category,
brain_id: bulkMap[bulkId][0].brain_id,
datetime: bulkMap[bulkId][0].datetime,
}));
};

const updateNotifications = async (): Promise<void> => {
try {
let notifs = (await supabase.from("notifications").select()).data;
if (notifs) {
notifs = notifs.sort(
(a: NotificationType, b: NotificationType) =>
new Date(b.datetime).getTime() - new Date(a.datetime).getTime()
);
const lastRetrieved = localStorage.getItem("lastRetrieved");
const now = Date.now();

if (lastRetrieved && now - parseInt(lastRetrieved) < 1000) {

return;
}

const bulkMap: { [key: string]: NotificationType[] } = {};
notifs?.forEach((notif: NotificationType) => {
if (!bulkMap[notif.bulk_id]) {
bulkMap[notif.bulk_id] = [];
}
bulkMap[notif.bulk_id].push(notif);
});

const bulkNotifs: BulkNotification[] = Object.keys(bulkMap).map(
(bulkId) => ({
bulk_id: bulkId,
notifications: bulkMap[bulkId],
category: bulkMap[bulkId][0].category,
brain_id: bulkMap[bulkId][0].brain_id,
datetime: bulkMap[bulkId][0].datetime,
})
localStorage.setItem("lastRetrieved", now.toString());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tu peux set lastRetrieves dans une const vu que c'est repris plus bas :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ici l'objectif c'est de faire en sorte que dans n'importe quelle endroit du code qui fait appel à updateNotifs ca soit commun. Il y a plusieurs endroits qui font appel à la fonction


const notifications = await fetchNotifications();
const sortedNotifications = notifications.sort(
(a, b) => new Date(b.datetime).getTime() - new Date(a.datetime).getTime()
);

const bulkNotifs = processNotifications(sortedNotifications);
setBulkNotifications(bulkNotifs);
setUnreadNotifications(
bulkNotifs.filter((bulk) => !bulk.notifications[0].read).length
);

const unreadCount = bulkNotifs.filter((bulk) => !bulk.notifications[0].read).length;
setUnreadNotifications(unreadCount);
} catch (error) {
console.error(error);
console.error("Error updating notifications:", error);
}
};

const debounce = <T extends (...args: unknown[]) => void>(func: T, delay: number): T & { cancel: () => void } => {
let timeoutId: NodeJS.Timeout; // Holds the timeout ID for the current debounce delay
let lastArgs: Parameters<T>; // Stores the last arguments passed to the debounced function

const debouncedFunction = (...args: Parameters<T>) => {
lastArgs = args; // Update the last arguments with the current call's arguments
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (timeoutId) {
clearTimeout(timeoutId); // Clear the existing timeout if it exists
}
// Set a new timeout to call the function after the specified delay
timeoutId = setTimeout(() => {
func(...lastArgs); // Call the function with the last arguments
}, delay);
};

debouncedFunction.cancel = () => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (timeoutId) {
clearTimeout(timeoutId); // Clear the timeout if the debounced function is canceled
}
};

return debouncedFunction as T & { cancel: () => void }; // Return the debounced function with a cancel method
};

const debouncedUpdateNotifications = useCallback(
// eslint-disable-next-line @typescript-eslint/no-misused-promises
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

est-ce qu'on peut faire en sorte de pas comment avec des disable d'eslint ? ca peut causer des soucis

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

J'ai mis car ca va sauter avec le KMS. C'est un hot fix :)

debounce(updateNotifications, 1000),
[updateNotifications]
);

useEffect(() => {
void (async () => {
for (const notifications of bulkNotifications) {
if (
notifications.notifications.every((notif) => notif.status !== "info")
) {
for (const notification of notifications.notifications) {
await supabase
.from("notifications")
.update({ read: true })
.eq("id", notification.id);
}
}
if (isVisible) {
const lastRetrieved = localStorage.getItem("lastRetrieved");
const now = Date.now();

if (!lastRetrieved || now - parseInt(lastRetrieved) >= 1000) {
void debouncedUpdateNotifications();
}
await updateNotifications();
})();
}, [isVisible]);

return () => {
debouncedUpdateNotifications.cancel();
};
}
}, [isVisible, debouncedUpdateNotifications]);

return (
<NotificationsContext.Provider
Expand All @@ -99,6 +141,7 @@ export const NotificationsProvider = ({
setBulkNotifications,
updateNotifications,
unreadNotifications,
setUnreadNotifications,
}}
>
{children}
Expand Down