Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions apiserver/plane/app/serializers/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class Meta:


class WorkspaceMemberMeSerializer(BaseSerializer):
draft_issue_count = serializers.IntegerField(read_only=True)
class Meta:
model = WorkspaceMember
fields = "__all__"
Expand Down
29 changes: 25 additions & 4 deletions apiserver/plane/app/views/workspace/member.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
CharField,
Count,
Q,
OuterRef,
Subquery,
IntegerField,
)
from django.db.models.functions import Coalesce
from django.db.models.functions import Cast

# Third party modules
Expand Down Expand Up @@ -34,6 +38,7 @@
User,
Workspace,
WorkspaceMember,
DraftIssue,
)
from plane.utils.cache import cache_response, invalidate_cache

Expand Down Expand Up @@ -283,10 +288,26 @@ def post(self, request, slug):

class WorkspaceMemberUserEndpoint(BaseAPIView):
def get(self, request, slug):
workspace_member = WorkspaceMember.objects.get(
member=request.user,
workspace__slug=slug,
is_active=True,
draft_issue_count = (
DraftIssue.objects.filter(
created_by=request.user,
workspace_id=OuterRef("workspace_id"),
)
.values("workspace_id")
.annotate(count=Count("id"))
.values("count")
)

workspace_member = (
WorkspaceMember.objects.filter(
member=request.user, workspace__slug=slug, is_active=True
)
.annotate(
draft_issue_count=Coalesce(
Subquery(draft_issue_count, output_field=IntegerField()), 0
)
)
.first()
Comment on lines +291 to +310
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Simplify the query by removing unnecessary Subquery and OuterRef

Since you are fetching a single WorkspaceMember object for the current user and workspace, using a Subquery and OuterRef adds unnecessary complexity. You can simplify the code by directly querying the count of DraftIssue objects and assigning it to the WorkspaceMember instance.

Apply this diff to simplify the code:

 def get(self, request, slug):
-    draft_issue_count = (
-        DraftIssue.objects.filter(
-            created_by=request.user,
-            workspace_id=OuterRef("workspace_id"),
-        )
-        .values("workspace_id")
-        .annotate(count=Count("id"))
-        .values("count")
-    )
-
-    workspace_member = (
-        WorkspaceMember.objects.filter(
-            member=request.user, workspace__slug=slug, is_active=True
-        )
-        .annotate(
-            draft_issue_count=Coalesce(
-                Subquery(draft_issue_count, output_field=IntegerField()), 0
-            )
-        )
-        .first()
-    )
+    # Retrieve the workspace member
+    workspace_member = WorkspaceMember.objects.get(
+        member=request.user, workspace__slug=slug, is_active=True
+    )
+
+    # Count the draft issues
+    draft_issue_count = DraftIssue.objects.filter(
+        created_by=request.user,
+        workspace__slug=slug,
+    ).count()
+
+    # Assign the count to the instance
+    workspace_member.draft_issue_count = draft_issue_count
+
     serializer = WorkspaceMemberMeSerializer(workspace_member)
     return Response(serializer.data, status=status.HTTP_200_OK)

Note: Ensure that the WorkspaceMemberMeSerializer is updated to handle the draft_issue_count attribute appropriately. You might need to modify the serializer to include this field if it's not already defined.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
draft_issue_count = (
DraftIssue.objects.filter(
created_by=request.user,
workspace_id=OuterRef("workspace_id"),
)
.values("workspace_id")
.annotate(count=Count("id"))
.values("count")
)
workspace_member = (
WorkspaceMember.objects.filter(
member=request.user, workspace__slug=slug, is_active=True
)
.annotate(
draft_issue_count=Coalesce(
Subquery(draft_issue_count, output_field=IntegerField()), 0
)
)
.first()
# Retrieve the workspace member
workspace_member = WorkspaceMember.objects.get(
member=request.user, workspace__slug=slug, is_active=True
)
# Count the draft issues
draft_issue_count = DraftIssue.objects.filter(
created_by=request.user,
workspace__slug=slug,
).count()
# Assign the count to the instance
workspace_member.draft_issue_count = draft_issue_count
serializer = WorkspaceMemberMeSerializer(workspace_member)
return Response(serializer.data, status=status.HTTP_200_OK)

)
serializer = WorkspaceMemberMeSerializer(workspace_member)
return Response(serializer.data, status=status.HTTP_200_OK)
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/workspace.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export interface IWorkspaceMemberMe {
updated_by: string;
view_props: IWorkspaceViewProps;
workspace: string;
draft_issue_count: number;
}

export interface ILastActiveWorkspaceDetails {
Expand Down
9 changes: 7 additions & 2 deletions web/app/[workspaceSlug]/(projects)/sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { FC, useEffect, useRef } from "react";
import isEmpty from "lodash/isEmpty";
import { observer } from "mobx-react";
// plane helpers
import { useOutsideClickDetector } from "@plane/helpers";
Expand All @@ -16,15 +17,17 @@ import { SidebarFavoritesMenu } from "@/components/workspace/sidebar/favorites/f
import { cn } from "@/helpers/common.helper";
// hooks
import { useAppTheme, useUserPermissions } from "@/hooks/store";
// plane web components
import { useFavorite } from "@/hooks/store/use-favorite";
import useSize from "@/hooks/use-window-size";
// plane web components
import { SidebarAppSwitcher } from "@/plane-web/components/sidebar";
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";

export const AppSidebar: FC = observer(() => {
// store hooks
const { allowPermissions } = useUserPermissions();
const { toggleSidebar, sidebarCollapsed } = useAppTheme();
const { groupedFavorites } = useFavorite();
const windowSize = useSize();
// refs
const ref = useRef<HTMLDivElement>(null);
Expand All @@ -48,6 +51,8 @@ export const AppSidebar: FC = observer(() => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [windowSize]);

const isFavoriteEmpty = isEmpty(groupedFavorites);

return (
<div
className={cn(
Expand Down Expand Up @@ -91,7 +96,7 @@ export const AppSidebar: FC = observer(() => {
"opacity-0": !sidebarCollapsed,
})}
/>
{canPerformWorkspaceMemberActions && <SidebarFavoritesMenu />}
{canPerformWorkspaceMemberActions && !isFavoriteEmpty && <SidebarFavoritesMenu />}

<SidebarProjectsList />
</div>
Expand Down
12 changes: 8 additions & 4 deletions web/core/components/workspace/sidebar/user-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const SidebarUserMenu = observer(() => {
const { captureEvent } = useEventTracker();
const { isMobile } = usePlatformOS();
const { data: currentUser } = useUser();
const { allowPermissions } = useUserPermissions();
const { allowPermissions, workspaceUserInfo } = useUserPermissions();
// router params
const { workspaceSlug } = useParams();
// pathname
Expand All @@ -50,14 +50,17 @@ export const SidebarUserMenu = observer(() => {
/>
);

const draftIssueCount = workspaceUserInfo[workspaceSlug.toString()]?.draft_issue_count;

return (
<div
className={cn("flex flex-col gap-0.5", {
"space-y-0": sidebarCollapsed,
})}
>
{SIDEBAR_USER_MENU_ITEMS.map(
(link) =>
{SIDEBAR_USER_MENU_ITEMS.map((link) => {
if (link.key === "drafts" && draftIssueCount === 0) return null;
return (
allowPermissions(link.access, EUserPermissionsLevel.WORKSPACE, workspaceSlug.toString()) && (
<Tooltip
key={link.key}
Expand All @@ -81,7 +84,8 @@ export const SidebarUserMenu = observer(() => {
</Link>
</Tooltip>
)
)}
);
})}
</div>
);
});
13 changes: 13 additions & 0 deletions web/core/store/issue/workspace-draft/issue.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,13 @@ export class WorkspaceDraftIssues implements IWorkspaceDraftIssues {
});
}

private updateWorkspaceUserDraftIssueCount(workspaceSlug: string, increment: number) {
const workspaceUserInfo = this.issueStore.rootStore.user.permission.workspaceUserInfo;
const currentCount = workspaceUserInfo[workspaceSlug]?.draft_issue_count ?? 0;

set(workspaceUserInfo, [workspaceSlug, "draft_issue_count"], currentCount + increment);
}

Comment on lines +142 to +148
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure draft_issue_count does not become negative

The updateWorkspaceUserDraftIssueCount method updates draft_issue_count by adding an increment, which could be negative. To prevent potential issues with negative counts, consider ensuring that draft_issue_count does not fall below zero.

Apply this diff to enforce a non-negative draft_issue_count:

private updateWorkspaceUserDraftIssueCount(workspaceSlug: string, increment: number) {
  const workspaceUserInfo = this.issueStore.rootStore.user.permission.workspaceUserInfo;
  const currentCount = workspaceUserInfo[workspaceSlug]?.draft_issue_count ?? 0;

+ const updatedCount = Math.max(0, currentCount + increment);
+ set(workspaceUserInfo, [workspaceSlug, "draft_issue_count"], updatedCount);
- set(workspaceUserInfo, [workspaceSlug, "draft_issue_count"], currentCount + increment);
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private updateWorkspaceUserDraftIssueCount(workspaceSlug: string, increment: number) {
const workspaceUserInfo = this.issueStore.rootStore.user.permission.workspaceUserInfo;
const currentCount = workspaceUserInfo[workspaceSlug]?.draft_issue_count ?? 0;
set(workspaceUserInfo, [workspaceSlug, "draft_issue_count"], currentCount + increment);
}
private updateWorkspaceUserDraftIssueCount(workspaceSlug: string, increment: number) {
const workspaceUserInfo = this.issueStore.rootStore.user.permission.workspaceUserInfo;
const currentCount = workspaceUserInfo[workspaceSlug]?.draft_issue_count ?? 0;
const updatedCount = Math.max(0, currentCount + increment);
set(workspaceUserInfo, [workspaceSlug, "draft_issue_count"], updatedCount);
}

// computed
get issueIds() {
const workspaceSlug = this.issueStore.workspaceSlug;
Expand Down Expand Up @@ -259,6 +266,8 @@ export class WorkspaceDraftIssues implements IWorkspaceDraftIssues {
total_count: this.paginationInfo.total_count + 1,
});
}
// Update draft issue count in workspaceUserInfo
this.updateWorkspaceUserDraftIssueCount(workspaceSlug, 1);
});
}

Expand Down Expand Up @@ -310,6 +319,8 @@ export class WorkspaceDraftIssues implements IWorkspaceDraftIssues {
total_count: this.paginationInfo.total_count - 1,
});
}
// Update draft issue count in workspaceUserInfo
this.updateWorkspaceUserDraftIssueCount(workspaceSlug, -1);
});

this.loader = undefined;
Expand Down Expand Up @@ -337,6 +348,8 @@ export class WorkspaceDraftIssues implements IWorkspaceDraftIssues {
total_count: this.paginationInfo.total_count - 1,
});
}
// Update draft issue count in workspaceUserInfo
this.updateWorkspaceUserDraftIssueCount(workspaceSlug, -1);
});

this.loader = undefined;
Expand Down