[WEB-2189] fix: issue peek overview and issue detail unauthorised delete action (#5341)

* fix: issue peek overview and issue detail delete action

* chore: code refactor

* chore: code refactor
This commit is contained in:
Anmol Singh Bhatia 2024-08-09 19:09:25 +05:30 committed by GitHub
parent 421bf2abc7
commit 679b0b6465
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 53 additions and 31 deletions

View file

@ -8,7 +8,7 @@ import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui";
// constants // constants
import { PROJECT_ERROR_MESSAGES } from "@/constants/project"; import { PROJECT_ERROR_MESSAGES } from "@/constants/project";
// hooks // hooks
import { useIssues, useProject } from "@/hooks/store"; import { useIssues, useProject, useUser } from "@/hooks/store";
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;
@ -26,6 +26,7 @@ export const DeleteIssueModal: React.FC<Props> = (props) => {
// store hooks // store hooks
const { issueMap } = useIssues(); const { issueMap } = useIssues();
const { getProjectById } = useProject(); const { getProjectById } = useProject();
const { data: currentUser, canPerformProjectAdminActions } = useUser();
useEffect(() => { useEffect(() => {
setIsDeleting(false); setIsDeleting(false);
@ -36,6 +37,8 @@ export const DeleteIssueModal: React.FC<Props> = (props) => {
// derived values // derived values
const issue = data ? data : issueMap[dataId!]; const issue = data ? data : issueMap[dataId!];
const projectDetails = getProjectById(issue?.project_id); const projectDetails = getProjectById(issue?.project_id);
const isIssueCreator = issue?.created_by === currentUser?.id;
const authorized = isIssueCreator || canPerformProjectAdminActions;
const onClose = () => { const onClose = () => {
setIsDeleting(false); setIsDeleting(false);
@ -44,6 +47,16 @@ export const DeleteIssueModal: React.FC<Props> = (props) => {
const handleIssueDelete = async () => { const handleIssueDelete = async () => {
setIsDeleting(true); setIsDeleting(true);
if (!authorized) {
setToast({
title: PROJECT_ERROR_MESSAGES.permissionError.title,
type: TOAST_TYPE.ERROR,
message: PROJECT_ERROR_MESSAGES.permissionError.message,
});
onClose();
return;
}
if (onSubmit) if (onSubmit)
await onSubmit() await onSubmit()
.then(() => { .then(() => {

View file

@ -70,12 +70,13 @@ export const useRelationOperations = (): TRelationIssueOperations => {
}, },
remove: async (workspaceSlug: string, projectId: string, issueId: string) => { remove: async (workspaceSlug: string, projectId: string, issueId: string) => {
try { try {
await removeIssue(workspaceSlug, projectId, issueId); return removeIssue(workspaceSlug, projectId, issueId).then(() => {
captureIssueEvent({ captureIssueEvent({
eventName: ISSUE_DELETED, eventName: ISSUE_DELETED,
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" }, payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
path: pathname, path: pathname,
}); });
});
} catch (error) { } catch (error) {
captureIssueEvent({ captureIssueEvent({
eventName: ISSUE_DELETED, eventName: ISSUE_DELETED,

View file

@ -150,13 +150,14 @@ export const useSubIssueOperations = (): TSubIssueOperations => {
deleteSubIssue: async (workspaceSlug: string, projectId: string, parentIssueId: string, issueId: string) => { deleteSubIssue: async (workspaceSlug: string, projectId: string, parentIssueId: string, issueId: string) => {
try { try {
setSubIssueHelpers(parentIssueId, "issue_loader", issueId); setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
await deleteSubIssue(workspaceSlug, projectId, parentIssueId, issueId); return deleteSubIssue(workspaceSlug, projectId, parentIssueId, issueId).then(() => {
captureIssueEvent({ captureIssueEvent({
eventName: "Sub-issue deleted", eventName: "Sub-issue deleted",
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" }, payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
path: pathname, path: pathname,
}); });
setSubIssueHelpers(parentIssueId, "issue_loader", issueId); setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
});
} catch (error) { } catch (error) {
captureIssueEvent({ captureIssueEvent({
eventName: "Sub-issue removed", eventName: "Sub-issue removed",

View file

@ -78,14 +78,18 @@ export const IssueDetailQuickActions: FC<Props> = observer((props) => {
const handleDeleteIssue = async () => { const handleDeleteIssue = async () => {
try { try {
if (issue?.archived_at) await removeArchivedIssue(workspaceSlug, projectId, issueId); if (issue?.archived_at) {
else await removeIssue(workspaceSlug, projectId, issueId); return removeArchivedIssue(workspaceSlug, projectId, issueId).then(() => {
router.push(`/${workspaceSlug}/projects/${projectId}/issues`); router.push(`/${workspaceSlug}/projects/${projectId}/issues`);
captureIssueEvent({ captureIssueEvent({
eventName: ISSUE_DELETED, eventName: ISSUE_DELETED,
payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" }, payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" },
path: pathname, path: pathname,
}); });
});
} else {
return removeIssue(workspaceSlug, projectId, issueId);
}
} catch (error) { } catch (error) {
setToast({ setToast({
title: "Error!", title: "Error!",

View file

@ -34,6 +34,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
} = useIssues(EIssuesStoreType.ARCHIVED); } = useIssues(EIssuesStoreType.ARCHIVED);
const { const {
peekIssue, peekIssue,
setPeekIssue,
issue: { fetchIssue }, issue: { fetchIssue },
fetchActivities, fetchActivities,
} = useIssueDetail(); } = useIssueDetail();
@ -44,6 +45,11 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
const [loader, setLoader] = useState(true); const [loader, setLoader] = useState(true);
const [error, setError] = useState(false); const [error, setError] = useState(false);
const removeRoutePeekId = () => {
setPeekIssue(undefined);
if (embedIssue) embedRemoveCurrentNotification && embedRemoveCurrentNotification();
};
const issueOperations: TIssueOperations = useMemo( const issueOperations: TIssueOperations = useMemo(
() => ({ () => ({
fetch: async (workspaceSlug: string, projectId: string, issueId: string, loader = true) => { fetch: async (workspaceSlug: string, projectId: string, issueId: string, loader = true) => {
@ -95,17 +101,14 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
}, },
remove: async (workspaceSlug: string, projectId: string, issueId: string) => { remove: async (workspaceSlug: string, projectId: string, issueId: string) => {
try { try {
issues?.removeIssue(workspaceSlug, projectId, issueId); return issues?.removeIssue(workspaceSlug, projectId, issueId).then(() => {
setToast({
title: "Success!",
type: TOAST_TYPE.SUCCESS,
message: "Issue deleted successfully",
});
captureIssueEvent({ captureIssueEvent({
eventName: ISSUE_DELETED, eventName: ISSUE_DELETED,
payload: { id: issueId, state: "SUCCESS", element: "Issue peek-overview" }, payload: { id: issueId, state: "SUCCESS", element: "Issue peek-overview" },
path: pathname, path: pathname,
}); });
removeRoutePeekId();
});
} catch (error) { } catch (error) {
setToast({ setToast({
title: "Error!", title: "Error!",

View file

@ -131,7 +131,7 @@ export const IssueView: FC<IIssueView> = observer((props) => {
toggleDeleteIssueModal(null); toggleDeleteIssueModal(null);
}} }}
data={issue} data={issue}
onSubmit={() => issueOperations.remove(workspaceSlug, projectId, issueId).then(() => removeRoutePeekId())} onSubmit={async () => issueOperations.remove(workspaceSlug, projectId, issueId)}
/> />
)} )}