Skip to content

Commit 6022080

Browse files
sangeethailangodheeru0198prateekshourya29
authored
[WEB-4338] fix: incorrect error code in project retrieve API (#7234)
* fix: project error message and status code * fix: incorrect member role check * fix: project error message and status code * fix: improve project permission checks and error handling in ProjectViewSet * feat: enhance project settings layout with better loading strategy and fix all flicker * fix: prevent rendering during project loading in ProjectAuthWrapper * refactor: adjust layout structure in ProjectDetailSettingsLayout and enhance access restriction logic in ProjectAccessRestriction * refactor: replace ProjectAccessRestriction component with updated version and enhance error handling - Deleted the old ProjectAccessRestriction component. - Introduced a new ProjectAccessRestriction component with improved error handling and user prompts for joining projects. - Updated translations for new error states in multiple languages. * fix: enhance error handling in IssueDetailsPage and remove JoinProject component --------- Co-authored-by: Dheeraj Kumar Ketireddy <[email protected]> Co-authored-by: Prateek Shourya <[email protected]>
1 parent 8db95d9 commit 6022080

File tree

43 files changed

+596
-503
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+596
-503
lines changed

apps/api/plane/app/views/project/base.py

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,43 @@
11
# Python imports
2-
import boto3
3-
from django.conf import settings
4-
from django.utils import timezone
52
import json
63

4+
import boto3
5+
76
# Django imports
8-
from django.db.models import Exists, F, OuterRef, Prefetch, Q, Subquery
7+
from django.conf import settings
98
from django.core.serializers.json import DjangoJSONEncoder
9+
from django.db.models import Exists, F, OuterRef, Prefetch, Q, Subquery
10+
from django.utils import timezone
1011

1112
# Third Party imports
12-
from rest_framework.response import Response
1313
from rest_framework import status
1414
from rest_framework.permissions import AllowAny
15+
from rest_framework.response import Response
1516

1617
# Module imports
17-
from plane.app.views.base import BaseViewSet, BaseAPIView
18+
from plane.app.permissions import ROLE, ProjectMemberPermission, allow_permission
1819
from plane.app.serializers import (
19-
ProjectSerializer,
20-
ProjectListSerializer,
2120
DeployBoardSerializer,
21+
ProjectListSerializer,
22+
ProjectSerializer,
2223
)
23-
24-
from plane.app.permissions import ProjectMemberPermission, allow_permission, ROLE
24+
from plane.app.views.base import BaseAPIView, BaseViewSet
25+
from plane.bgtasks.recent_visited_task import recent_visited_task
26+
from plane.bgtasks.webhook_task import model_activity, webhook_activity
2527
from plane.db.models import (
26-
UserFavorite,
27-
Intake,
2828
DeployBoard,
29+
Intake,
2930
IssueUserProperty,
3031
Project,
3132
ProjectIdentifier,
3233
ProjectMember,
34+
ProjectNetwork,
3335
State,
3436
DEFAULT_STATES,
3537
Workspace,
3638
WorkspaceMember,
3739
)
3840
from plane.utils.cache import cache_response
39-
from plane.bgtasks.webhook_task import model_activity, webhook_activity
40-
from plane.bgtasks.recent_visited_task import recent_visited_task
4141
from plane.utils.exception_logger import log_exception
4242
from plane.utils.host import base_host
4343

@@ -210,19 +210,25 @@ def list(self, request, slug):
210210

211211
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
212212
def retrieve(self, request, slug, pk):
213-
project = (
214-
self.get_queryset()
215-
.filter(
216-
project_projectmember__member=self.request.user,
217-
project_projectmember__is_active=True,
218-
)
219-
.filter(archived_at__isnull=True)
220-
.filter(pk=pk)
221-
).first()
213+
project = self.get_queryset().filter(archived_at__isnull=True).filter(pk=pk).first()
222214

223215
if project is None:
224216
return Response({"error": "Project does not exist"}, status=status.HTTP_404_NOT_FOUND)
225217

218+
member_ids = [str(project_member.member_id) for project_member in project.members_list]
219+
220+
if str(request.user.id) not in member_ids:
221+
if project.network == ProjectNetwork.SECRET.value:
222+
return Response(
223+
{"error": "You do not have permission"},
224+
status=status.HTTP_403_FORBIDDEN,
225+
)
226+
else:
227+
return Response(
228+
{"error": "You are not a member of this project"},
229+
status=status.HTTP_409_CONFLICT,
230+
)
231+
226232
recent_visited_task.delay(
227233
slug=slug,
228234
project_id=pk,

apps/api/plane/db/models/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
ProjectIdentifier,
5353
ProjectMember,
5454
ProjectMemberInvite,
55+
ProjectNetwork,
5556
ProjectPublicMember,
5657
)
5758
from .session import Session

apps/web/app/(all)/[workspaceSlug]/(projects)/browse/[workItem]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ function IssueDetailsPage({ params }: Route.ComponentProps) {
8282
return (
8383
<>
8484
<PageHead title={pageTitle} />
85-
{error ? (
85+
{error && !issueLoader ? (
8686
<EmptyState
8787
image={resolvedTheme === "dark" ? emptyIssueDark : emptyIssueLight}
8888
title={t("issue.empty_state.issue_detail.title")}

apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/layout.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { AppSidebarToggleButton } from "@/components/sidebar/sidebar-toggle-butt
1111
// hooks
1212
import { useAppTheme } from "@/hooks/store/use-app-theme";
1313
import { useProjectNavigationPreferences } from "@/hooks/use-navigation-preferences";
14+
import { ProjectAuthWrapper } from "@/plane-web/layouts/project-wrapper";
1415
// local imports
1516
import type { Route } from "./+types/layout";
1617

@@ -44,7 +45,9 @@ function ProjectLayout({ params }: Route.ComponentProps) {
4445
</Row>
4546
</div>
4647
)}
47-
<Outlet />
48+
<ProjectAuthWrapper workspaceSlug={workspaceSlug} projectId={projectId}>
49+
<Outlet />
50+
</ProjectAuthWrapper>
4851
</>
4952
);
5053
}

apps/web/app/(all)/[workspaceSlug]/(projects)/projects/(detail)/layout.tsx

Lines changed: 0 additions & 14 deletions
This file was deleted.

apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/page.tsx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { observer } from "mobx-react";
22
import { useTranslation } from "@plane/i18n";
33
// components
4-
import { LogoSpinner } from "@/components/common/logo-spinner";
54
import { PageHead } from "@/components/core/page-title";
65
import { ProfileForm } from "@/components/profile/form";
76
// hooks
@@ -12,13 +11,7 @@ function ProfileSettingsPage() {
1211
// store hooks
1312
const { data: currentUser, userProfile } = useUser();
1413

15-
if (!currentUser)
16-
return (
17-
<div className="grid h-full w-full place-items-center px-4 sm:px-0">
18-
<LogoSpinner />
19-
</div>
20-
);
21-
14+
if (!currentUser) return <></>;
2215
return (
2316
<>
2417
<PageHead title={`${t("profile.label")} - ${t("general_settings")}`} />

apps/web/app/(all)/[workspaceSlug]/(settings)/settings/account/preferences/page.tsx

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { observer } from "mobx-react";
22
// plane imports
33
import { useTranslation } from "@plane/i18n";
44
// components
5-
import { LogoSpinner } from "@/components/common/logo-spinner";
65
import { PageHead } from "@/components/core/page-title";
76
import { PreferencesList } from "@/components/preferences/list";
87
import { LanguageTimezone } from "@/components/profile/preferences/language-timezone";
@@ -16,30 +15,23 @@ function ProfileAppearancePage() {
1615
// hooks
1716
const { data: userProfile } = useUserProfile();
1817

18+
if (!userProfile) return <></>;
1919
return (
2020
<>
2121
<PageHead title={`${t("profile.label")} - ${t("preferences")}`} />
22-
{userProfile ? (
23-
<>
24-
<div className="flex flex-col gap-4 w-full">
25-
<div>
26-
<SettingsHeading
27-
title={t("account_settings.preferences.heading")}
28-
description={t("account_settings.preferences.description")}
29-
/>
30-
<PreferencesList />
31-
</div>
32-
<div>
33-
<ProfileSettingContentHeader title={t("language_and_time")} />
34-
<LanguageTimezone />
35-
</div>
36-
</div>
37-
</>
38-
) : (
39-
<div className="grid h-full w-full place-items-center px-4 sm:px-0">
40-
<LogoSpinner />
22+
<div className="flex flex-col gap-4 w-full">
23+
<div>
24+
<SettingsHeading
25+
title={t("account_settings.preferences.heading")}
26+
description={t("account_settings.preferences.description")}
27+
/>
28+
<PreferencesList />
4129
</div>
42-
)}
30+
<div>
31+
<ProfileSettingContentHeader title={t("language_and_time")} />
32+
<LanguageTimezone />
33+
</div>
34+
</div>
4335
</>
4436
);
4537
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { observer } from "mobx-react";
2+
import { usePathname } from "next/navigation";
3+
import { Outlet } from "react-router";
4+
// components
5+
import { getProjectActivePath } from "@/components/settings/helper";
6+
import { SettingsMobileNav } from "@/components/settings/mobile";
7+
import { ProjectSettingsSidebar } from "@/components/settings/project/sidebar";
8+
// plane web imports
9+
import { ProjectAuthWrapper } from "@/plane-web/layouts/project-wrapper";
10+
// types
11+
import type { Route } from "./+types/layout";
12+
13+
function ProjectDetailSettingsLayout({ params }: Route.ComponentProps) {
14+
const { workspaceSlug, projectId } = params;
15+
// router
16+
const pathname = usePathname();
17+
18+
return (
19+
<>
20+
<SettingsMobileNav hamburgerContent={ProjectSettingsSidebar} activePath={getProjectActivePath(pathname) || ""} />
21+
<div className="relative flex h-full w-full">
22+
<div className="hidden md:block">{projectId && <ProjectSettingsSidebar />}</div>
23+
<ProjectAuthWrapper workspaceSlug={workspaceSlug} projectId={projectId}>
24+
<div className="w-full h-full overflow-y-scroll md:pt-page-y">
25+
<Outlet />
26+
</div>
27+
</ProjectAuthWrapper>
28+
</div>
29+
</>
30+
);
31+
}
32+
33+
export default observer(ProjectDetailSettingsLayout);

apps/web/app/(all)/[workspaceSlug]/(settings)/settings/projects/[projectId]/page.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { useState } from "react";
22
import { observer } from "mobx-react";
3-
import useSWR from "swr";
43
// plane imports
54
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
65
// components
@@ -24,12 +23,8 @@ function ProjectSettingsPage({ params }: Route.ComponentProps) {
2423
// router
2524
const { workspaceSlug, projectId } = params;
2625
// store hooks
27-
const { currentProjectDetails, fetchProjectDetails } = useProject();
26+
const { currentProjectDetails } = useProject();
2827
const { allowPermissions } = useUserPermissions();
29-
30-
// api call to fetch project details
31-
// TODO: removed this API if not necessary
32-
const { isLoading } = useSWR(`PROJECT_DETAILS_${projectId}`, () => fetchProjectDetails(workspaceSlug, projectId));
3328
// derived values
3429
const isAdmin = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT, workspaceSlug, projectId);
3530

@@ -56,7 +51,7 @@ function ProjectSettingsPage({ params }: Route.ComponentProps) {
5651
)}
5752

5853
<div className={`w-full ${isAdmin ? "" : "opacity-60"}`}>
59-
{currentProjectDetails && !isLoading ? (
54+
{currentProjectDetails ? (
6055
<ProjectDetailsForm
6156
project={currentProjectDetails}
6257
workspaceSlug={workspaceSlug}
Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,17 @@
11
import { useEffect } from "react";
22
import { observer } from "mobx-react";
3-
import { usePathname } from "next/navigation";
43
import { Outlet } from "react-router";
5-
// components
6-
import { getProjectActivePath } from "@/components/settings/helper";
7-
import { SettingsMobileNav } from "@/components/settings/mobile";
8-
import { ProjectSettingsSidebar } from "@/components/settings/project/sidebar";
4+
// hooks
95
import { useProject } from "@/hooks/store/use-project";
106
import { useAppRouter } from "@/hooks/use-app-router";
11-
import { ProjectAuthWrapper } from "@/plane-web/layouts/project-wrapper";
7+
// types
128
import type { Route } from "./+types/layout";
139

1410
function ProjectSettingsLayout({ params }: Route.ComponentProps) {
11+
const { workspaceSlug, projectId } = params;
1512
// router
1613
const router = useAppRouter();
17-
const pathname = usePathname();
18-
const { workspaceSlug, projectId } = params;
14+
// store hooks
1915
const { joinedProjectIds } = useProject();
2016

2117
useEffect(() => {
@@ -25,19 +21,7 @@ function ProjectSettingsLayout({ params }: Route.ComponentProps) {
2521
}
2622
}, [joinedProjectIds, router, workspaceSlug, projectId]);
2723

28-
return (
29-
<>
30-
<SettingsMobileNav hamburgerContent={ProjectSettingsSidebar} activePath={getProjectActivePath(pathname) || ""} />
31-
<ProjectAuthWrapper workspaceSlug={workspaceSlug} projectId={projectId}>
32-
<div className="relative flex h-full w-full">
33-
<div className="hidden md:block">{projectId && <ProjectSettingsSidebar />}</div>
34-
<div className="w-full h-full overflow-y-scroll md:pt-page-y">
35-
<Outlet />
36-
</div>
37-
</div>
38-
</ProjectAuthWrapper>
39-
</>
40-
);
24+
return <Outlet />;
4125
}
4226

4327
export default observer(ProjectSettingsLayout);

0 commit comments

Comments
 (0)