mirror of
https://github.com/makeplane/plane.git
synced 2026-01-25 15:49:04 -06:00
[WEB-2479] fix: merge default and archived issue details endpoint. (#5882)
This commit is contained in:
@@ -478,7 +478,44 @@ class IssueViewSet(BaseViewSet):
|
||||
project = Project.objects.get(pk=project_id, workspace__slug=slug)
|
||||
|
||||
issue = (
|
||||
self.get_queryset()
|
||||
Issue.objects.filter(
|
||||
project_id=self.kwargs.get("project_id")
|
||||
)
|
||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||
.select_related("workspace", "project", "state", "parent")
|
||||
.prefetch_related("assignees", "labels", "issue_module__module")
|
||||
.annotate(
|
||||
cycle_id=Case(
|
||||
When(
|
||||
issue_cycle__cycle__deleted_at__isnull=True,
|
||||
then=F("issue_cycle__cycle_id"),
|
||||
),
|
||||
default=None,
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
.annotate(
|
||||
attachment_count=FileAsset.objects.filter(
|
||||
issue_id=OuterRef("id"),
|
||||
entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT,
|
||||
)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(
|
||||
parent=OuterRef("id")
|
||||
)
|
||||
.order_by()
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
.filter(pk=pk)
|
||||
.annotate(
|
||||
label_ids=Coalesce(
|
||||
|
||||
@@ -29,7 +29,7 @@ const ArchivedIssueDetailsPage = observer(() => {
|
||||
? `ARCHIVED_ISSUE_DETAIL_${workspaceSlug}_${projectId}_${archivedIssueId}`
|
||||
: null,
|
||||
workspaceSlug && projectId && archivedIssueId
|
||||
? () => fetchIssue(workspaceSlug.toString(), projectId.toString(), archivedIssueId.toString(), "ARCHIVED")
|
||||
? () => fetchIssue(workspaceSlug.toString(), projectId.toString(), archivedIssueId.toString())
|
||||
: null
|
||||
);
|
||||
|
||||
|
||||
@@ -13,9 +13,9 @@ import { ISSUE_DETAILS } from "@/constants/fetch-keys";
|
||||
// hooks
|
||||
import { useProject } from "@/hooks/store";
|
||||
// services
|
||||
import { IssueArchiveService } from "@/services/issue";
|
||||
import { IssueService } from "@/services/issue";
|
||||
|
||||
const issueArchiveService = new IssueArchiveService();
|
||||
const issueService = new IssueService();
|
||||
|
||||
export const ProjectArchivedIssueDetailsHeader = observer(() => {
|
||||
// router
|
||||
@@ -24,14 +24,9 @@ export const ProjectArchivedIssueDetailsHeader = observer(() => {
|
||||
const { currentProjectDetails, loader } = useProject();
|
||||
|
||||
const { data: issueDetails } = useSWR(
|
||||
workspaceSlug && projectId && archivedIssueId ? ISSUE_DETAILS(archivedIssueId as string) : null,
|
||||
workspaceSlug && projectId && archivedIssueId ? ISSUE_DETAILS(archivedIssueId.toString()) : null,
|
||||
workspaceSlug && projectId && archivedIssueId
|
||||
? () =>
|
||||
issueArchiveService.retrieveArchivedIssue(
|
||||
workspaceSlug as string,
|
||||
projectId as string,
|
||||
archivedIssueId as string
|
||||
)
|
||||
? () => issueService.retrieve(workspaceSlug.toString(), projectId.toString(), archivedIssueId.toString())
|
||||
: null
|
||||
);
|
||||
|
||||
|
||||
@@ -172,7 +172,12 @@ export const ActiveCycleStats: FC<ActiveCycleStatsProps> = observer((props) => {
|
||||
className="group flex cursor-pointer items-center justify-between gap-2 rounded-md hover:bg-custom-background-90 p-1"
|
||||
onClick={() => {
|
||||
if (issue.id) {
|
||||
setPeekIssue({ workspaceSlug, projectId, issueId: issue.id });
|
||||
setPeekIssue({
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
issueId: issue.id,
|
||||
isArchived: !!issue.archived_at,
|
||||
});
|
||||
handleFiltersUpdate("priority", ["urgent", "high"], true);
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -77,7 +77,13 @@ export const IssueBlock = observer((props: IssueBlockProps) => {
|
||||
issue.project_id &&
|
||||
issue.id &&
|
||||
!getIsIssuePeeked(issue.id) &&
|
||||
setPeekIssue({ workspaceSlug, projectId: issue.project_id, issueId: issue.id, nestingLevel: nestingLevel });
|
||||
setPeekIssue({
|
||||
workspaceSlug,
|
||||
projectId: issue.project_id,
|
||||
issueId: issue.id,
|
||||
nestingLevel: nestingLevel,
|
||||
isArchived: !!issue.archived_at,
|
||||
});
|
||||
|
||||
const issue = issuesMap[issueId];
|
||||
const subIssuesCount = issue?.sub_issues_count ?? 0;
|
||||
|
||||
@@ -34,7 +34,7 @@ export const ArchivedIssueLayoutRoot: React.FC = observer(() => {
|
||||
<div className="relative h-full w-full overflow-auto">
|
||||
<ArchivedIssueListLayout />
|
||||
</div>
|
||||
<IssuePeekOverview is_archived />
|
||||
<IssuePeekOverview />
|
||||
</Fragment>
|
||||
</IssuesStoreContext.Provider>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { FC, useEffect, useState, useMemo } from "react";
|
||||
import { FC, useEffect, useState, useMemo, useCallback } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { usePathname } from "next/navigation";
|
||||
// plane types
|
||||
@@ -21,12 +21,11 @@ import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/u
|
||||
interface IIssuePeekOverview {
|
||||
embedIssue?: boolean;
|
||||
embedRemoveCurrentNotification?: () => void;
|
||||
is_archived?: boolean;
|
||||
is_draft?: boolean;
|
||||
}
|
||||
|
||||
export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
||||
const { embedIssue = false, embedRemoveCurrentNotification, is_archived = false, is_draft = false } = props;
|
||||
const { embedIssue = false, embedRemoveCurrentNotification, is_draft = false } = props;
|
||||
// router
|
||||
const pathname = usePathname();
|
||||
// store hook
|
||||
@@ -47,26 +46,20 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
||||
// state
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
const removeRoutePeekId = () => {
|
||||
const removeRoutePeekId = useCallback(() => {
|
||||
setPeekIssue(undefined);
|
||||
if (embedIssue) embedRemoveCurrentNotification?.();
|
||||
};
|
||||
}, [embedIssue, embedRemoveCurrentNotification, setPeekIssue]);
|
||||
|
||||
const issueOperations: TIssueOperations = useMemo(
|
||||
() => ({
|
||||
fetch: async (workspaceSlug: string, projectId: string, issueId: string) => {
|
||||
try {
|
||||
setError(false);
|
||||
await fetchIssue(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
issueId,
|
||||
is_archived ? "ARCHIVED" : is_draft ? "DRAFT" : "DEFAULT"
|
||||
);
|
||||
setError(false);
|
||||
await fetchIssue(workspaceSlug, projectId, issueId, is_draft ? "DRAFT" : "DEFAULT");
|
||||
} catch (error) {
|
||||
setError(true);
|
||||
console.error("Error fetching the parent issue");
|
||||
console.error("Error fetching the parent issue", error);
|
||||
}
|
||||
},
|
||||
update: async (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => {
|
||||
@@ -109,7 +102,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
||||
});
|
||||
removeRoutePeekId();
|
||||
});
|
||||
} catch (error) {
|
||||
} catch {
|
||||
setToast({
|
||||
title: "Error!",
|
||||
type: TOAST_TYPE.ERROR,
|
||||
@@ -124,13 +117,14 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
||||
},
|
||||
archive: async (workspaceSlug: string, projectId: string, issueId: string) => {
|
||||
try {
|
||||
issues?.archiveIssue && (await issues.archiveIssue(workspaceSlug, projectId, issueId));
|
||||
if (!issues?.archiveIssue) return;
|
||||
await issues.archiveIssue(workspaceSlug, projectId, issueId);
|
||||
captureIssueEvent({
|
||||
eventName: ISSUE_ARCHIVED,
|
||||
payload: { id: issueId, state: "SUCCESS", element: "Issue peek-overview" },
|
||||
path: pathname,
|
||||
});
|
||||
} catch (error) {
|
||||
} catch {
|
||||
captureIssueEvent({
|
||||
eventName: ISSUE_ARCHIVED,
|
||||
payload: { id: issueId, state: "FAILED", element: "Issue peek-overview" },
|
||||
@@ -151,7 +145,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
||||
payload: { id: issueId, state: "SUCCESS", element: "Issue peek-overview" },
|
||||
path: pathname,
|
||||
});
|
||||
} catch (error) {
|
||||
} catch {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
@@ -177,7 +171,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
||||
},
|
||||
path: pathname,
|
||||
});
|
||||
} catch (error) {
|
||||
} catch {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
@@ -206,7 +200,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
||||
},
|
||||
path: pathname,
|
||||
});
|
||||
} catch (error) {
|
||||
} catch {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
@@ -248,7 +242,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
||||
},
|
||||
path: pathname,
|
||||
});
|
||||
} catch (error) {
|
||||
} catch {
|
||||
captureIssueEvent({
|
||||
eventName: ISSUE_UPDATED,
|
||||
payload: { state: "FAILED", element: "Issue peek-overview" },
|
||||
@@ -311,7 +305,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
||||
},
|
||||
path: pathname,
|
||||
});
|
||||
} catch (error) {
|
||||
} catch {
|
||||
captureIssueEvent({
|
||||
eventName: ISSUE_UPDATED,
|
||||
payload: { id: issueId, state: "FAILED", element: "Issue peek-overview" },
|
||||
@@ -324,7 +318,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
||||
}
|
||||
},
|
||||
}),
|
||||
[is_archived, is_draft, fetchIssue, issues, restoreIssue, captureIssueEvent, pathname]
|
||||
[fetchIssue, is_draft, issues, fetchActivities, captureIssueEvent, pathname, removeRoutePeekId, restoreIssue]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -350,7 +344,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
||||
issueId={peekIssue.issueId}
|
||||
isLoading={getIsFetchingIssueDetails(peekIssue.issueId)}
|
||||
isError={error}
|
||||
is_archived={is_archived}
|
||||
is_archived={!!peekIssue.isArchived}
|
||||
disabled={!isEditable}
|
||||
embedIssue={embedIssue}
|
||||
embedRemoveCurrentNotification={embedRemoveCurrentNotification}
|
||||
|
||||
@@ -66,7 +66,7 @@ export const IssueView: FC<IIssueView> = observer((props) => {
|
||||
// remove peek id
|
||||
const removeRoutePeekId = () => {
|
||||
setPeekIssue(undefined);
|
||||
if (embedIssue) embedRemoveCurrentNotification && embedRemoveCurrentNotification();
|
||||
if (embedIssue && embedRemoveCurrentNotification) embedRemoveCurrentNotification();
|
||||
};
|
||||
|
||||
const isLocalDBIssueDescription = getIsLocalDBIssueDescription(issueId);
|
||||
|
||||
@@ -22,9 +22,11 @@ const useIssuePeekOverviewRedirection = () => {
|
||||
if (workspaceSlug && project_id && id && !getIsIssuePeeked(id) && !tempId) {
|
||||
const issuePath = `/${workspaceSlug}/projects/${project_id}/${archived_at ? "archives/" : ""}issues/${id}`;
|
||||
|
||||
isMobile
|
||||
? router.push(issuePath)
|
||||
: setPeekIssue({ workspaceSlug, projectId: project_id, issueId: id, nestingLevel });
|
||||
if (isMobile) {
|
||||
router.push(issuePath);
|
||||
} else {
|
||||
setPeekIssue({ workspaceSlug, projectId: project_id, issueId: id, nestingLevel, isArchived: !!archived_at });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ export interface IIssueStoreActions {
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string,
|
||||
issueType?: "DEFAULT" | "DRAFT" | "ARCHIVED"
|
||||
issueStatus?: "DEFAULT" | "DRAFT",
|
||||
) => Promise<TIssue>;
|
||||
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
|
||||
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
|
||||
@@ -82,7 +82,7 @@ export class IssueStore implements IIssueStore {
|
||||
});
|
||||
|
||||
// actions
|
||||
fetchIssue = async (workspaceSlug: string, projectId: string, issueId: string, issueType = "DEFAULT") => {
|
||||
fetchIssue = async (workspaceSlug: string, projectId: string, issueId: string, issueStatus = "DEFAULT") => {
|
||||
const query = {
|
||||
expand: "issue_reactions,issue_attachments,issue_link,parent",
|
||||
};
|
||||
@@ -99,9 +99,7 @@ export class IssueStore implements IIssueStore {
|
||||
this.localDBIssueDescription = issueId;
|
||||
}
|
||||
|
||||
if (issueType === "ARCHIVED")
|
||||
issue = await this.issueArchiveService.retrieveArchivedIssue(workspaceSlug, projectId, issueId, query);
|
||||
else if (issueType === "DRAFT")
|
||||
if (issueStatus === "DRAFT")
|
||||
issue = await this.issueDraftService.getDraftIssueById(workspaceSlug, projectId, issueId, query);
|
||||
else issue = await this.issueService.retrieve(workspaceSlug, projectId, issueId, query);
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ export type TPeekIssue = {
|
||||
projectId: string;
|
||||
issueId: string;
|
||||
nestingLevel?: number;
|
||||
isArchived?: boolean;
|
||||
};
|
||||
|
||||
export type TIssueRelationModal = {
|
||||
@@ -251,8 +252,8 @@ export class IssueDetail implements IIssueDetail {
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string,
|
||||
issueType: "DEFAULT" | "ARCHIVED" | "DRAFT" = "DEFAULT"
|
||||
) => this.issue.fetchIssue(workspaceSlug, projectId, issueId, issueType);
|
||||
issueStatus: "DEFAULT" | "DRAFT" = "DEFAULT"
|
||||
) => this.issue.fetchIssue(workspaceSlug, projectId, issueId, issueStatus);
|
||||
updateIssue = async (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) =>
|
||||
this.issue.updateIssue(workspaceSlug, projectId, issueId, data);
|
||||
removeIssue = async (workspaceSlug: string, projectId: string, issueId: string) =>
|
||||
|
||||
Reference in New Issue
Block a user