[WEB-3251] fix: add to projects list API (#6550)

This commit is contained in:
Prateek Shourya 2025-02-05 15:18:02 +05:30 committed by GitHub
parent 9bd70cdb4e
commit 89d1926727
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 48 additions and 41 deletions

View file

@ -91,7 +91,6 @@ class ProjectLiteSerializer(BaseSerializer):
class ProjectListSerializer(DynamicBaseSerializer): class ProjectListSerializer(DynamicBaseSerializer):
is_favorite = serializers.BooleanField(read_only=True) is_favorite = serializers.BooleanField(read_only=True)
is_member = serializers.BooleanField(read_only=True)
sort_order = serializers.FloatField(read_only=True) sort_order = serializers.FloatField(read_only=True)
member_role = serializers.IntegerField(read_only=True) member_role = serializers.IntegerField(read_only=True)
anchor = serializers.CharField(read_only=True) anchor = serializers.CharField(read_only=True)
@ -120,7 +119,6 @@ class ProjectDetailSerializer(BaseSerializer):
default_assignee = UserLiteSerializer(read_only=True) default_assignee = UserLiteSerializer(read_only=True)
project_lead = UserLiteSerializer(read_only=True) project_lead = UserLiteSerializer(read_only=True)
is_favorite = serializers.BooleanField(read_only=True) is_favorite = serializers.BooleanField(read_only=True)
is_member = serializers.BooleanField(read_only=True)
sort_order = serializers.FloatField(read_only=True) sort_order = serializers.FloatField(read_only=True)
member_role = serializers.IntegerField(read_only=True) member_role = serializers.IntegerField(read_only=True)
anchor = serializers.CharField(read_only=True) anchor = serializers.CharField(read_only=True)

View file

@ -70,16 +70,6 @@ class ProjectViewSet(BaseViewSet):
) )
) )
) )
.annotate(
is_member=Exists(
ProjectMember.objects.filter(
member=self.request.user,
project_id=OuterRef("pk"),
workspace__slug=self.kwargs.get("slug"),
is_active=True,
)
)
)
.annotate( .annotate(
member_role=ProjectMember.objects.filter( member_role=ProjectMember.objects.filter(
project_id=OuterRef("pk"), project_id=OuterRef("pk"),
@ -164,14 +154,11 @@ class ProjectViewSet(BaseViewSet):
"workspace", "workspace__owner", "default_assignee", "project_lead" "workspace", "workspace__owner", "default_assignee", "project_lead"
) )
.annotate( .annotate(
is_member=Exists( member_role=ProjectMember.objects.filter(
ProjectMember.objects.filter( project_id=OuterRef("pk"),
member=self.request.user, member_id=self.request.user.id,
project_id=OuterRef("pk"), is_active=True,
workspace__slug=self.kwargs.get("slug"), ).values("role")
is_active=True,
)
)
) )
.annotate(inbox_view=F("intake_view")) .annotate(inbox_view=F("intake_view"))
.annotate(sort_order=Subquery(sort_order)) .annotate(sort_order=Subquery(sort_order))
@ -182,7 +169,7 @@ class ProjectViewSet(BaseViewSet):
"identifier", "identifier",
"sort_order", "sort_order",
"logo_props", "logo_props",
"is_member", "member_role",
"archived_at", "archived_at",
"workspace", "workspace",
"cycle_view", "cycle_view",

View file

@ -16,7 +16,7 @@ export interface IPartialProject {
identifier: string; identifier: string;
sort_order: number | null; sort_order: number | null;
logo_props: TLogoProps; logo_props: TLogoProps;
is_member: boolean; member_role: TUserPermissions | null;
archived_at: string | null; archived_at: string | null;
workspace: IWorkspace | string; workspace: IWorkspace | string;
cycle_view: boolean; cycle_view: boolean;
@ -50,7 +50,6 @@ export interface IProject extends IPartialProject {
is_favorite?: boolean; is_favorite?: boolean;
is_issue_type_enabled?: boolean; is_issue_type_enabled?: boolean;
is_time_tracking_enabled?: boolean; is_time_tracking_enabled?: boolean;
member_role?: TUserPermissions | null;
members?: string[]; members?: string[];
network?: number; network?: number;
timezone?: string; timezone?: string;

View file

@ -1,3 +1,25 @@
import Root from "@/components/project/root"; "use client";
export const ProjectPageRoot = () => <Root />; import { observer } from "mobx-react";
import { useParams } from "next/navigation";
import useSWR from "swr";
// components
import Root from "@/components/project/root";
// hooks
import { useProject, useWorkspace } from "@/hooks/store";
export const ProjectPageRoot = observer(() => {
// router
const { workspaceSlug } = useParams();
// store
const { currentWorkspace } = useWorkspace();
const { fetchProjects } = useProject();
// fetching workspace projects
useSWR(
workspaceSlug && currentWorkspace ? `WORKSPACE_PROJECTS_${workspaceSlug}` : null,
workspaceSlug && currentWorkspace ? () => fetchProjects(workspaceSlug.toString()) : null,
{ revalidateIfStale: false, revalidateOnFocus: false }
);
return <Root />;
});

View file

@ -63,8 +63,9 @@ export const ProjectCard: React.FC<Props> = observer((props) => {
EUserPermissionsLevel.WORKSPACE EUserPermissionsLevel.WORKSPACE
); );
// auth // auth
const isOwner = project.member_role === EUserPermissions.ADMIN; const isMemberOfProject = !!project.member_role;
const isMember = project.member_role === EUserPermissions.MEMBER; const hasAdminRole = project.member_role === EUserPermissions.ADMIN;
const hasMemberRole = project.member_role === EUserPermissions.MEMBER;
// archive // archive
const isArchived = !!project.archived_at; const isArchived = !!project.archived_at;
@ -119,21 +120,21 @@ export const ProjectCard: React.FC<Props> = observer((props) => {
action: () => router.push(`/${workspaceSlug}/projects/${project.id}/settings`, {}, { showProgressBar: false }), action: () => router.push(`/${workspaceSlug}/projects/${project.id}/settings`, {}, { showProgressBar: false }),
title: "Settings", title: "Settings",
icon: Settings, icon: Settings,
shouldRender: !isArchived && (isOwner || isMember), shouldRender: !isArchived && (hasAdminRole || hasMemberRole),
}, },
{ {
key: "join", key: "join",
action: () => setJoinProjectModal(true), action: () => setJoinProjectModal(true),
title: "Join", title: "Join",
icon: UserPlus, icon: UserPlus,
shouldRender: !project.is_member && !isArchived, shouldRender: !isMemberOfProject && !isArchived,
}, },
{ {
key: "open-new-tab", key: "open-new-tab",
action: handleOpenInNewTab, action: handleOpenInNewTab,
title: "Open in new tab", title: "Open in new tab",
icon: ExternalLink, icon: ExternalLink,
shouldRender: project.is_member && !isArchived, shouldRender: !isMemberOfProject && !isArchived,
}, },
{ {
key: "copy-link", key: "copy-link",
@ -147,14 +148,14 @@ export const ProjectCard: React.FC<Props> = observer((props) => {
action: () => setRestoreProject(true), action: () => setRestoreProject(true),
title: "Restore", title: "Restore",
icon: ArchiveRestoreIcon, icon: ArchiveRestoreIcon,
shouldRender: isArchived && isOwner, shouldRender: isArchived && hasAdminRole,
}, },
{ {
key: "delete", key: "delete",
action: () => setDeleteProjectModal(true), action: () => setDeleteProjectModal(true),
title: "Delete", title: "Delete",
icon: Trash2, icon: Trash2,
shouldRender: isArchived && isOwner, shouldRender: isArchived && hasAdminRole,
}, },
]; ];
@ -189,13 +190,13 @@ export const ProjectCard: React.FC<Props> = observer((props) => {
ref={projectCardRef} ref={projectCardRef}
href={`/${workspaceSlug}/projects/${project.id}/issues`} href={`/${workspaceSlug}/projects/${project.id}/issues`}
onClick={(e) => { onClick={(e) => {
if (!project.is_member || isArchived) { if (!isMemberOfProject || isArchived) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
if (!isArchived) setJoinProjectModal(true); if (!isArchived) setJoinProjectModal(true);
} }
}} }}
data-prevent-nprogress={!project.is_member || isArchived} data-prevent-nprogress={!isMemberOfProject || isArchived}
className="flex flex-col rounded border border-custom-border-200 bg-custom-background-100" className="flex flex-col rounded border border-custom-border-200 bg-custom-background-100"
> >
<ContextMenu parentRef={projectCardRef} items={MENU_ITEMS} /> <ContextMenu parentRef={projectCardRef} items={MENU_ITEMS} />
@ -297,7 +298,7 @@ export const ProjectCard: React.FC<Props> = observer((props) => {
{isArchived && <div className="text-xs text-custom-text-400 font-medium">Archived</div>} {isArchived && <div className="text-xs text-custom-text-400 font-medium">Archived</div>}
</div> </div>
{isArchived ? ( {isArchived ? (
isOwner && ( hasAdminRole && (
<div className="flex items-center justify-center gap-2"> <div className="flex items-center justify-center gap-2">
<div <div
className="flex items-center justify-center text-xs text-custom-text-400 font-medium hover:text-custom-text-200" className="flex items-center justify-center text-xs text-custom-text-400 font-medium hover:text-custom-text-200"
@ -326,8 +327,8 @@ export const ProjectCard: React.FC<Props> = observer((props) => {
) )
) : ( ) : (
<> <>
{project.is_member && {isMemberOfProject &&
(isOwner || isMember ? ( (hasAdminRole || hasMemberRole ? (
<Link <Link
className="flex items-center justify-center rounded p-1 text-custom-text-400 hover:bg-custom-background-80 hover:text-custom-text-200" className="flex items-center justify-center rounded p-1 text-custom-text-400 hover:bg-custom-background-80 hover:text-custom-text-200"
onClick={(e) => { onClick={(e) => {
@ -343,7 +344,7 @@ export const ProjectCard: React.FC<Props> = observer((props) => {
Joined Joined
</span> </span>
))} ))}
{!project.is_member && ( {!isMemberOfProject && (
<div className="flex items-center"> <div className="flex items-center">
<Button <Button
variant="link-primary" variant="link-primary"

View file

@ -215,7 +215,7 @@ export class ProjectStore implements IProjectStore {
projects = sortBy(projects, "sort_order"); projects = sortBy(projects, "sort_order");
const projectIds = projects const projectIds = projects
.filter((project) => project.workspace === currentWorkspace.id && project.is_member && !project.archived_at) .filter((project) => project.workspace === currentWorkspace.id && !!project.member_role && !project.archived_at)
.map((project) => project.id); .map((project) => project.id);
return projectIds; return projectIds;
} }
@ -233,7 +233,7 @@ export class ProjectStore implements IProjectStore {
const projectIds = projects const projectIds = projects
.filter( .filter(
(project) => (project) =>
project.workspace === currentWorkspace.id && project.is_member && project.is_favorite && !project.archived_at project.workspace === currentWorkspace.id && !!project.member_role && project.is_favorite && !project.archived_at
) )
.map((project) => project.id); .map((project) => project.id);
return projectIds; return projectIds;

View file

@ -87,7 +87,7 @@ export const shouldFilterProject = (
}); });
} }
}); });
if (displayFilters.my_projects && !project.is_member) fallsInFilters = false; if (displayFilters.my_projects && !project.member_role) fallsInFilters = false;
if (displayFilters.archived_projects && !project.archived_at) fallsInFilters = false; if (displayFilters.archived_projects && !project.archived_at) fallsInFilters = false;
if (project.archived_at) fallsInFilters = displayFilters.archived_projects ? fallsInFilters : false; if (project.archived_at) fallsInFilters = displayFilters.archived_projects ? fallsInFilters : false;