[WEB-2479] fix: merge default and archived issue details endpoint. (#5882)

This commit is contained in:
Prateek Shourya
2024-10-24 14:40:50 +05:30
committed by GitHub
parent 2ecc379486
commit e4e83a947a
11 changed files with 86 additions and 48 deletions

View File

@@ -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(

View File

@@ -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
);

View File

@@ -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
);

View File

@@ -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);
}
}}

View File

@@ -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;

View File

@@ -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>
);

View File

@@ -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}

View File

@@ -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);

View File

@@ -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 });
}
}
};

View File

@@ -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);

View File

@@ -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) =>