Skip to content

Commit

Permalink
[WEB-1437] feat: notifications mention filter (#5040)
Browse files Browse the repository at this point in the history
* chore: implemented mentions on the notification

* chore: mention notification filter

* chore: handled mentions refetch and total count on header and sidebar menu option

* chore: seperated notifications empty state

* chore: updated sidebar menu option notification vaidation

* chore: handled notificaition sidebar total notifications count

---------

Co-authored-by: gurusainath <gurusainath007@gmail.com>
  • Loading branch information
NarayanBavisetti and gurusainath authored Jul 5, 2024
1 parent 837f09e commit 54a5e5e
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 55 deletions.
28 changes: 26 additions & 2 deletions apiserver/plane/app/views/notification/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def list(self, request, slug):
archived = request.GET.get("archived", "false")
read = request.GET.get("read", None)
type = request.GET.get("type", "all")
mentioned = request.GET.get("mentioned", False)
q_filters = Q()

inbox_issue = Issue.objects.filter(
Expand Down Expand Up @@ -86,6 +87,13 @@ def list(self, request, slug):
if read == "true":
notifications = notifications.filter(read_at__isnull=False)

if mentioned:
notifications = notifications.filter(sender__icontains="mentioned")
else:
notifications = notifications.exclude(
sender__icontains="mentioned"
)

type = type.split(",")
# Subscribed issues
if "subscribed" in type:
Expand Down Expand Up @@ -210,19 +218,35 @@ def unarchive(self, request, slug, pk):
class UnreadNotificationEndpoint(BaseAPIView):
def get(self, request, slug):
# Watching Issues Count
unread_notifications_count = Notification.objects.filter(
unread_notifications_count = (
Notification.objects.filter(
workspace__slug=slug,
receiver_id=request.user.id,
read_at__isnull=True,
archived_at__isnull=True,
snoozed_till__isnull=True,
)
.exclude(sender__icontains="mentioned")
.count()
)

mention_notifications_count = Notification.objects.filter(
workspace__slug=slug,
receiver_id=request.user.id,
read_at__isnull=True,
archived_at__isnull=True,
snoozed_till__isnull=True,
sender__icontains="mentioned",
).count()

return Response(
{
"total_unread_notifications_count": int(
unread_notifications_count
)
),
"mention_unread_notifications_count": int(
mention_notifications_count
),
},
status=status.HTTP_200_OK,
)
Expand Down
2 changes: 2 additions & 0 deletions packages/types/src/workspace-notifications.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export type TNotificationPaginatedInfoQueryParams = {
type?: string | undefined;
snoozed?: boolean;
archived?: boolean;
mentioned?: boolean;
read?: boolean;
per_page?: number;
cursor?: string;
Expand All @@ -86,6 +87,7 @@ export type TNotificationPaginatedInfo = {
// notification count
export type TUnreadNotificationsCount = {
total_unread_notifications_count: number;
mention_unread_notifications_count: number;
};

export type TCurrentSelectedNotification = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,20 @@ export const NotificationAppSidebarOption: FC<TNotificationAppSidebarOption> = o
workspaceSlug ? () => getUnreadNotificationsCount(workspaceSlug) : null
);

if (unreadNotificationsCount.total_unread_notifications_count <= 0) return <></>;
// derived values
const isMentionsEnabled = unreadNotificationsCount.mention_unread_notifications_count > 0 ? true : false;
const totalNotifications = isMentionsEnabled
? unreadNotificationsCount.mention_unread_notifications_count
: unreadNotificationsCount.total_unread_notifications_count;

if (totalNotifications <= 0) return <></>;

if (isSidebarCollapsed)
return <div className="absolute right-3.5 top-2 h-2 w-2 rounded-full bg-custom-primary-300" />;

return (
<div className="ml-auto px-2.5 py-0.5 bg-custom-primary-100/20 text-custom-primary-100 text-xs font-semibold rounded-xl">
{getNumberCount(unreadNotificationsCount.total_unread_notifications_count)}
<div className="text-[8px] ml-auto bg-custom-primary-100 text-white p-1 py-0.5 rounded-full">
{`${isMentionsEnabled ? `@` : ``}${getNumberCount(totalNotifications)}`}
</div>
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"use client";

import { FC } from "react";
import { observer } from "mobx-react";
// components
import { EmptyState } from "@/components/empty-state";
// constants
import { EmptyStateType } from "@/constants/empty-state";
import { ENotificationTab } from "@/constants/notification";

export const NotificationEmptyState: FC = observer(() => {
// derived values
const currentTabEmptyState = ENotificationTab.ALL
? EmptyStateType.NOTIFICATION_ALL_EMPTY_STATE
: EmptyStateType.NOTIFICATION_MENTIONS_EMPTY_STATE;

return <EmptyState type={currentTabEmptyState} layout="screen-simple" />;
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,18 @@
import { FC } from "react";
import { observer } from "mobx-react";
import { Bell } from "lucide-react";
import { Breadcrumbs, Tooltip } from "@plane/ui";
import { Breadcrumbs } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common";
import { SidebarHamburgerToggle } from "@/components/core";
import { NotificationSidebarHeaderOptions } from "@/components/workspace-notifications";
// helpers
import { getNumberCount } from "@/helpers/string.helper";
// hooks
import { usePlatformOS } from "@/hooks/use-platform-os";

type TNotificationSidebarHeader = {
workspaceSlug: string;
notificationsCount: number;
};

export const NotificationSidebarHeader: FC<TNotificationSidebarHeader> = observer((props) => {
const { workspaceSlug, notificationsCount } = props;
// hooks
const { isMobile } = usePlatformOS();
const { workspaceSlug } = props;

if (!workspaceSlug) return <></>;
return (
Expand All @@ -35,20 +28,7 @@ export const NotificationSidebarHeader: FC<TNotificationSidebarHeader> = observe
type="text"
link={
<BreadcrumbLink
label={
<div className="flex items-center gap-2">
<div className="font-medium">Notifications</div>
<Tooltip
isMobile={isMobile}
tooltipContent={`There are ${notificationsCount} ${notificationsCount > 1 ? "notifications" : "notification"} in this workspace`}
position="bottom"
>
<div className="px-2.5 py-0.5 bg-custom-primary-100/20 text-custom-primary-100 text-xs font-semibold rounded-xl">
{getNumberCount(notificationsCount)}
</div>
</Tooltip>
</div>
}
label="Notifications"
icon={<Bell className="h-4 w-4 text-custom-text-300" />}
disableTooltip
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from "./loader";
export * from "./empty-state";

export * from "./root";

Expand Down
65 changes: 48 additions & 17 deletions web/core/components/workspace-notifications/sidebar/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,73 @@ import { FC } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// components
import { EmptyState } from "@/components/empty-state";
import {
NotificationsLoader,
NotificationEmptyState,
NotificationSidebarHeader,
AppliedFilters,
NotificationsLoader,
NotificationCardListRoot,
} from "@/components/workspace-notifications";
// constants
import { EmptyStateType } from "@/constants/empty-state";
import { ENotificationTab } from "@/constants/notification";
import { NOTIFICATION_TABS } from "@/constants/notification";
// helpers
import { cn } from "@/helpers/common.helper";
import { getNumberCount } from "@/helpers/string.helper";
// hooks
import { useWorkspace, useWorkspaceNotifications } from "@/hooks/store";

export const NotificationsSidebar: FC = observer(() => {
const { workspaceSlug } = useParams();
// hooks
const { getWorkspaceBySlug } = useWorkspace();
const { unreadNotificationsCount, loader, notificationIdsByWorkspaceId } = useWorkspaceNotifications();
const {
unreadNotificationsCount,
loader,
notificationIdsByWorkspaceId,
currentNotificationTab,
setCurrentNotificationTab,
} = useWorkspaceNotifications();
// derived values
const workspace = workspaceSlug ? getWorkspaceBySlug(workspaceSlug.toString()) : undefined;
const notificationIds = workspace ? notificationIdsByWorkspaceId(workspace.id) : undefined;

// derived values
const currentTabEmptyState = ENotificationTab.ALL
? EmptyStateType.NOTIFICATION_ALL_EMPTY_STATE
: EmptyStateType.NOTIFICATION_MENTIONS_EMPTY_STATE;
const totalNotificationCount = unreadNotificationsCount.total_unread_notifications_count;

if (!workspaceSlug || !workspace) return <></>;
return (
<div className="relative w-full h-full overflow-hidden flex flex-col">
<div className="border-b border-custom-border-200">
<NotificationSidebarHeader
workspaceSlug={workspaceSlug.toString()}
notificationsCount={totalNotificationCount}
/>
<NotificationSidebarHeader workspaceSlug={workspaceSlug.toString()} />
</div>

<div className="flex-shrink-0 w-full h-[46px] border-b border-custom-border-200 px-5 relative flex items-center gap-2">
{NOTIFICATION_TABS.map((tab) => (
<div
key={tab.value}
className="h-full px-3 relative flex flex-col cursor-pointer"
onClick={() => currentNotificationTab != tab.value && setCurrentNotificationTab(tab.value)}
>
<div
className={cn(
`relative h-full flex justify-center items-center gap-1 text-sm transition-all`,
currentNotificationTab === tab.value
? "text-custom-primary-100"
: "text-custom-text-100 hover:text-custom-text-200"
)}
>
<div className="font-medium">{tab.label}</div>
<div
className={cn(
`rounded-full text-xs px-1.5 py-0.5`,
currentNotificationTab === tab.value ? `bg-custom-primary-100/20` : `bg-custom-background-80/50`
)}
>
{getNumberCount(tab.count(unreadNotificationsCount))}
</div>
</div>
{currentNotificationTab === tab.value && (
<div className="border absolute bottom-0 right-0 left-0 rounded-t-md border-custom-primary-100" />
)}
</div>
))}
</div>

{/* applied filters */}
Expand All @@ -49,7 +80,7 @@ export const NotificationsSidebar: FC = observer(() => {

{/* rendering notifications */}
{loader === "init-loader" ? (
<div className="relative w-full h-full overflow-hidden p-5">
<div className="relative w-full h-full overflow-hidden">
<NotificationsLoader />
</div>
) : (
Expand All @@ -60,7 +91,7 @@ export const NotificationsSidebar: FC = observer(() => {
</div>
) : (
<div className="relative w-full h-full flex justify-center items-center">
<EmptyState type={currentTabEmptyState} layout="screen-simple" />
<NotificationEmptyState />
</div>
)}
</>
Expand Down
13 changes: 9 additions & 4 deletions web/core/constants/notification.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { TUnreadNotificationsCount } from "@plane/types";

export enum ENotificationTab {
ALL = "all",
MENTIONS = "mentions",
Expand Down Expand Up @@ -29,11 +31,14 @@ export const NOTIFICATION_TABS = [
{
label: "All",
value: ENotificationTab.ALL,
count: (unReadNotification: TUnreadNotificationsCount) => unReadNotification?.total_unread_notifications_count || 0,
},
{
label: "Mentions",
value: ENotificationTab.MENTIONS,
count: (unReadNotification: TUnreadNotificationsCount) =>
unReadNotification?.mention_unread_notifications_count || 0,
},
// {
// label: "Mentions",
// value: ENotificationTab.MENTIONS,
// },
];

export const FILTER_TYPE_OPTIONS = [
Expand Down
31 changes: 25 additions & 6 deletions web/core/store/notifications/workspace-notifications.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export class WorkspaceNotificationStore implements IWorkspaceNotificationStore {
loader: TNotificationLoader = undefined;
unreadNotificationsCount: TUnreadNotificationsCount = {
total_unread_notifications_count: 0,
mention_unread_notifications_count: 0,
};
notifications: Record<string, INotification> = {};
currentNotificationTab: TNotificationTab = ENotificationTab.ALL;
Expand Down Expand Up @@ -186,6 +187,8 @@ export class WorkspaceNotificationStore implements IWorkspaceNotificationStore {
// NOTE: This validation is required to show all the read and unread notifications in a single place it may change in future.
queryParams.read = this.filters.read === true ? false : undefined;

if (this.currentNotificationTab === ENotificationTab.MENTIONS) queryParams.mentioned = true;

return queryParams;
};

Expand Down Expand Up @@ -242,6 +245,12 @@ export class WorkspaceNotificationStore implements IWorkspaceNotificationStore {
*/
setCurrentNotificationTab = (tab: TNotificationTab): void => {
set(this, "currentNotificationTab", tab);

const { workspaceSlug } = this.store.router;
if (!workspaceSlug) return;

set(this, "notifications", {});
this.getNotifications(workspaceSlug, ENotificationLoader.INIT_LOADER, ENotificationQueryParamType.INIT);
};

/**
Expand All @@ -258,12 +267,22 @@ export class WorkspaceNotificationStore implements IWorkspaceNotificationStore {
* @param { "increment" | "decrement" } type
* @returns { void }
*/
setUnreadNotificationsCount = (type: "increment" | "decrement"): void =>
runInAction(() => {
update(this.unreadNotificationsCount, "total_unread_notifications_count", (count: 0) =>
type === "increment" ? count + 1 : count - 1
);
});
setUnreadNotificationsCount = (type: "increment" | "decrement"): void => {
switch (this.currentNotificationTab) {
case ENotificationTab.ALL:
update(this.unreadNotificationsCount, "total_unread_notifications_count", (count: 0) =>
type === "increment" ? count + 1 : count - 1
);
break;
case ENotificationTab.MENTIONS:
update(this.unreadNotificationsCount, "mention_unread_notifications_count", (count: 0) =>
type === "increment" ? count + 1 : count - 1
);
break;
default:
break;
}
};

/**
* @description get unread notifications count
Expand Down

0 comments on commit 54a5e5e

Please sign in to comment.