diff --git a/apiserver/plane/api/views/inbox.py b/apiserver/plane/api/views/inbox.py index 24eac569d68..f7e18dd76ff 100644 --- a/apiserver/plane/api/views/inbox.py +++ b/apiserver/plane/api/views/inbox.py @@ -285,7 +285,7 @@ def patch(self, request, slug, project_id, issue_id): ) # Only project admins and members can edit inbox issue attributes - if project_member.role > 5: + if project_member.role > 15: serializer = InboxIssueSerializer( inbox_issue, data=request.data, partial=True ) diff --git a/apiserver/plane/app/views/inbox/base.py b/apiserver/plane/app/views/inbox/base.py index 3bd5332dc27..4a32d993034 100644 --- a/apiserver/plane/app/views/inbox/base.py +++ b/apiserver/plane/app/views/inbox/base.py @@ -323,7 +323,7 @@ def create(self, request, slug, project_id): serializer.errors, status=status.HTTP_400_BAD_REQUEST ) - @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) + @allow_permission(allowed_roles=[ROLE.ADMIN], creator=True, model=Issue) def partial_update(self, request, slug, project_id, pk): inbox_id = Inbox.objects.filter( workspace__slug=slug, project_id=project_id @@ -418,7 +418,7 @@ def partial_update(self, request, slug, project_id, pk): ) # Only project admins and members can edit inbox issue attributes - if project_member.role > 5: + if project_member.role > 15: serializer = InboxIssueSerializer( inbox_issue, data=request.data, partial=True ) diff --git a/web/core/components/inbox/content/inbox-issue-header.tsx b/web/core/components/inbox/content/inbox-issue-header.tsx index efd0e8ee1e3..19a39e5e92c 100644 --- a/web/core/components/inbox/content/inbox-issue-header.tsx +++ b/web/core/components/inbox/content/inbox-issue-header.tsx @@ -89,6 +89,12 @@ export const InboxIssueActionsHeader: FC = observer((p const canDelete = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT, workspaceSlug, projectId) || issue?.created_by === currentUser?.id; + const isProjectAdmin = allowPermissions( + [EUserPermissions.ADMIN], + EUserPermissionsLevel.PROJECT, + workspaceSlug, + projectId + ); const isAcceptedOrDeclined = inboxIssue?.status ? [-1, 1, 2].includes(inboxIssue.status) : undefined; // days left for snooze const numberOfDaysLeft = findHowManyDaysLeft(inboxIssue?.snoozed_till); @@ -199,6 +205,17 @@ export const InboxIssueActionsHeader: FC = observer((p [handleInboxIssueNavigation] ); + const handleActionWithPermission = (isAdmin: boolean, action: () => void, errorMessage: string) => { + if (isAdmin) action(); + else { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Permission denied", + message: errorMessage, + }); + } + }; + useEffect(() => { if (!isNotificationEmbed) document.addEventListener("keydown", onKeyDown); return () => { @@ -293,7 +310,13 @@ export const InboxIssueActionsHeader: FC = observer((p size="sm" prependIcon={} className="text-green-500 border-0.5 border-green-500 bg-green-500/20 focus:bg-green-500/20 focus:text-green-500 hover:bg-green-500/40 bg-opacity-20" - onClick={() => setAcceptIssueModal(true)} + onClick={() => + handleActionWithPermission( + isProjectAdmin, + () => setAcceptIssueModal(true), + "Only project admins can accept issues" + ) + } > Accept @@ -307,7 +330,13 @@ export const InboxIssueActionsHeader: FC = observer((p size="sm" prependIcon={} className="text-red-500 border-0.5 border-red-500 bg-red-500/20 focus:bg-red-500/20 focus:text-red-500 hover:bg-red-500/40 bg-opacity-20" - onClick={() => setDeclineIssueModal(true)} + onClick={() => + handleActionWithPermission( + isProjectAdmin, + () => setDeclineIssueModal(true), + "Only project admins can deny issues" + ) + } > Decline @@ -341,7 +370,15 @@ export const InboxIssueActionsHeader: FC = observer((p {isAllowed && ( {canMarkAsAccepted && ( - + + handleActionWithPermission( + isProjectAdmin, + handleIssueSnoozeAction, + "Only project admins can snooze/Un-snooze issues" + ) + } + >
{inboxIssue?.snoozed_till && numberOfDaysLeft && numberOfDaysLeft > 0 @@ -351,7 +388,15 @@ export const InboxIssueActionsHeader: FC = observer((p )} {canMarkAsDuplicate && ( - setSelectDuplicateIssue(true)}> + + handleActionWithPermission( + isProjectAdmin, + () => setSelectDuplicateIssue(true), + "Only project admins can mark issues as duplicate" + ) + } + >
Mark as duplicate @@ -401,6 +446,8 @@ export const InboxIssueActionsHeader: FC = observer((p setIsMobileSidebar={setIsMobileSidebar} isNotificationEmbed={isNotificationEmbed} embedRemoveCurrentNotification={embedRemoveCurrentNotification} + isProjectAdmin={isProjectAdmin} + handleActionWithPermission={handleActionWithPermission} />
diff --git a/web/core/components/inbox/content/inbox-issue-mobile-header.tsx b/web/core/components/inbox/content/inbox-issue-mobile-header.tsx index e87573e9be0..7a66d0976f8 100644 --- a/web/core/components/inbox/content/inbox-issue-mobile-header.tsx +++ b/web/core/components/inbox/content/inbox-issue-mobile-header.tsx @@ -47,6 +47,8 @@ type Props = { setIsMobileSidebar: (value: boolean) => void; isNotificationEmbed: boolean; embedRemoveCurrentNotification?: () => void; + isProjectAdmin: boolean; + handleActionWithPermission: (isAdmin: boolean, action: () => void, errorMessage: string) => void; }; export const InboxIssueActionsMobileHeader: React.FC = observer((props) => { @@ -70,6 +72,8 @@ export const InboxIssueActionsMobileHeader: React.FC = observer((props) = setIsMobileSidebar, isNotificationEmbed, embedRemoveCurrentNotification, + isProjectAdmin, + handleActionWithPermission, } = props; const router = useAppRouter(); const issue = inboxIssue?.issue; @@ -139,7 +143,15 @@ export const InboxIssueActionsMobileHeader: React.FC = observer((props) =
)} {canMarkAsAccepted && !isAcceptedOrDeclined && ( - + + handleActionWithPermission( + isProjectAdmin, + handleIssueSnoozeAction, + "Only project admins can snooze/Un-snooze issues" + ) + } + >
{inboxIssue?.snoozed_till && numberOfDaysLeft && numberOfDaysLeft > 0 ? "Un-snooze" : "Snooze"} @@ -147,7 +159,15 @@ export const InboxIssueActionsMobileHeader: React.FC = observer((props) = )} {canMarkAsDuplicate && !isAcceptedOrDeclined && ( - setSelectDuplicateIssue(true)}> + + handleActionWithPermission( + isProjectAdmin, + () => setSelectDuplicateIssue(true), + "Only project admins can mark issues as duplicate" + ) + } + >
Mark as duplicate @@ -155,7 +175,15 @@ export const InboxIssueActionsMobileHeader: React.FC = observer((props) = )} {canMarkAsAccepted && ( - setAcceptIssueModal(true)}> + + handleActionWithPermission( + isProjectAdmin, + () => setAcceptIssueModal(true), + "Only project admins can accept issues" + ) + } + >
Accept @@ -163,7 +191,15 @@ export const InboxIssueActionsMobileHeader: React.FC = observer((props) = )} {canMarkAsDeclined && ( - setDeclineIssueModal(true)}> + + handleActionWithPermission( + isProjectAdmin, + () => setDeclineIssueModal(true), + "Only project admins can deny issues" + ) + } + >
Decline diff --git a/web/core/components/inbox/content/root.tsx b/web/core/components/inbox/content/root.tsx index 852be8a80b8..504b1d593a7 100644 --- a/web/core/components/inbox/content/root.tsx +++ b/web/core/components/inbox/content/root.tsx @@ -62,10 +62,10 @@ export const InboxContentRoot: FC = observer((props) => { } ); - const isEditable = allowPermissions( - [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST], - EUserPermissionsLevel.PROJECT - ); + const isEditable = + allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.PROJECT) || + inboxIssue.created_by === currentUser?.id; + const isGuest = projectPermissionsByWorkspaceSlugAndProjectId(workspaceSlug, projectId) === EUserPermissions.GUEST; const isOwner = inboxIssue?.issue.created_by === currentUser?.id; const readOnly = !isOwner && isGuest; diff --git a/web/core/store/inbox/project-inbox.store.ts b/web/core/store/inbox/project-inbox.store.ts index bf0a485762a..2897825397b 100644 --- a/web/core/store/inbox/project-inbox.store.ts +++ b/web/core/store/inbox/project-inbox.store.ts @@ -423,7 +423,7 @@ export class ProjectInboxStore implements IProjectInboxStore { if (inboxIssue && issueId) { runInAction(() => { - set(this.inboxIssues, [issueId], new InboxIssueStore(workspaceSlug, projectId, inboxIssue, this.store)); + this.createOrUpdateInboxIssue([inboxIssue], workspaceSlug, projectId); set(this, "loader", undefined); }); await Promise.all([