From 999bf64e193ad9e1f68ddb860c5f70895417e1c5 Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Mon, 21 Oct 2024 17:31:55 +0530 Subject: [PATCH] [WEB-2479] fix: merge default and archived issue details endpoint. --- apiserver/plane/app/views/issue/base.py | 39 +++++++++++++++++- .../(detail)/[archivedIssueId]/page.tsx | 2 +- .../archives/issues/(detail)/header.tsx | 13 ++---- .../cycles/active-cycle/cycle-stats.tsx | 7 +++- .../issues/issue-layouts/list/block.tsx | 8 +++- .../roots/archived-issue-layout-root.tsx | 2 +- .../components/issues/peek-overview/root.tsx | 40 ++++++++----------- .../components/issues/peek-overview/view.tsx | 2 +- .../use-issue-peek-overview-redirection.tsx | 8 ++-- .../store/issue/issue-details/issue.store.ts | 8 ++-- .../store/issue/issue-details/root.store.ts | 5 ++- 11 files changed, 86 insertions(+), 48 deletions(-) diff --git a/apiserver/plane/app/views/issue/base.py b/apiserver/plane/app/views/issue/base.py index 0488a5471d8..5f401a0b196 100644 --- a/apiserver/plane/app/views/issue/base.py +++ b/apiserver/plane/app/views/issue/base.py @@ -482,7 +482,44 @@ def retrieve(self, request, slug, project_id, pk=None): 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( diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/[archivedIssueId]/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/[archivedIssueId]/page.tsx index 8573795b5a2..80ecbbf67f1 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/[archivedIssueId]/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/[archivedIssueId]/page.tsx @@ -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 ); diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/header.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/header.tsx index bb0c63d7024..b9c684b0eb3 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/header.tsx @@ -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 ); diff --git a/web/core/components/cycles/active-cycle/cycle-stats.tsx b/web/core/components/cycles/active-cycle/cycle-stats.tsx index 8e1d8234939..6fcdaebd539 100644 --- a/web/core/components/cycles/active-cycle/cycle-stats.tsx +++ b/web/core/components/cycles/active-cycle/cycle-stats.tsx @@ -172,7 +172,12 @@ export const ActiveCycleStats: FC = 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); } }} diff --git a/web/core/components/issues/issue-layouts/list/block.tsx b/web/core/components/issues/issue-layouts/list/block.tsx index 59e7715c3d1..0b0700b0f0b 100644 --- a/web/core/components/issues/issue-layouts/list/block.tsx +++ b/web/core/components/issues/issue-layouts/list/block.tsx @@ -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; diff --git a/web/core/components/issues/issue-layouts/roots/archived-issue-layout-root.tsx b/web/core/components/issues/issue-layouts/roots/archived-issue-layout-root.tsx index 322c0005460..5a5663900c4 100644 --- a/web/core/components/issues/issue-layouts/roots/archived-issue-layout-root.tsx +++ b/web/core/components/issues/issue-layouts/roots/archived-issue-layout-root.tsx @@ -34,7 +34,7 @@ export const ArchivedIssueLayoutRoot: React.FC = observer(() => {
- + ); diff --git a/web/core/components/issues/peek-overview/root.tsx b/web/core/components/issues/peek-overview/root.tsx index 49f178287bf..70eb51d81b8 100644 --- a/web/core/components/issues/peek-overview/root.tsx +++ b/web/core/components/issues/peek-overview/root.tsx @@ -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 = 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 = 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) => { @@ -109,7 +102,7 @@ export const IssuePeekOverview: FC = observer((props) => { }); removeRoutePeekId(); }); - } catch (error) { + } catch { setToast({ title: "Error!", type: TOAST_TYPE.ERROR, @@ -124,13 +117,14 @@ export const IssuePeekOverview: FC = 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 = 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 = observer((props) => { }, path: pathname, }); - } catch (error) { + } catch { setToast({ type: TOAST_TYPE.ERROR, title: "Error!", @@ -206,7 +200,7 @@ export const IssuePeekOverview: FC = observer((props) => { }, path: pathname, }); - } catch (error) { + } catch { setToast({ type: TOAST_TYPE.ERROR, title: "Error!", @@ -248,7 +242,7 @@ export const IssuePeekOverview: FC = 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 = 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 = 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 = 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} diff --git a/web/core/components/issues/peek-overview/view.tsx b/web/core/components/issues/peek-overview/view.tsx index 959592fcc6e..8a9a7f2e205 100644 --- a/web/core/components/issues/peek-overview/view.tsx +++ b/web/core/components/issues/peek-overview/view.tsx @@ -66,7 +66,7 @@ export const IssueView: FC = observer((props) => { // remove peek id const removeRoutePeekId = () => { setPeekIssue(undefined); - if (embedIssue) embedRemoveCurrentNotification && embedRemoveCurrentNotification(); + if (embedIssue && embedRemoveCurrentNotification) embedRemoveCurrentNotification(); }; const isLocalDBIssueDescription = getIsLocalDBIssueDescription(issueId); diff --git a/web/core/hooks/use-issue-peek-overview-redirection.tsx b/web/core/hooks/use-issue-peek-overview-redirection.tsx index 89e3dc24140..1433e6ffb9b 100644 --- a/web/core/hooks/use-issue-peek-overview-redirection.tsx +++ b/web/core/hooks/use-issue-peek-overview-redirection.tsx @@ -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 }); + } } }; diff --git a/web/core/store/issue/issue-details/issue.store.ts b/web/core/store/issue/issue-details/issue.store.ts index 13a11d2bb5e..db0ccc39af2 100644 --- a/web/core/store/issue/issue-details/issue.store.ts +++ b/web/core/store/issue/issue-details/issue.store.ts @@ -15,7 +15,7 @@ export interface IIssueStoreActions { workspaceSlug: string, projectId: string, issueId: string, - issueType?: "DEFAULT" | "DRAFT" | "ARCHIVED" + issueStatus?: "DEFAULT" | "DRAFT", ) => Promise; updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => Promise; removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise; @@ -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); diff --git a/web/core/store/issue/issue-details/root.store.ts b/web/core/store/issue/issue-details/root.store.ts index 872df8cce4b..2e92c05d625 100644 --- a/web/core/store/issue/issue-details/root.store.ts +++ b/web/core/store/issue/issue-details/root.store.ts @@ -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) => this.issue.updateIssue(workspaceSlug, projectId, issueId, data); removeIssue = async (workspaceSlug: string, projectId: string, issueId: string) =>