chore: archived issues restructure. (#3469)

* chore: archived issues restructure.

* fix issue detail functionalities

---------

Co-authored-by: rahulramesha <rahulramesham@gmail.com>
This commit is contained in:
Prateek Shourya 2024-01-25 18:09:01 +05:30 committed by GitHub
parent f8208b1b5e
commit 60b5589c48
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 72 additions and 133 deletions

View File

@ -1,43 +1,22 @@
import { useCallback, useEffect, useState, ReactElement } from "react";
import { useState, ReactElement } from "react";
import { useRouter } from "next/router";
import useSWR, { mutate } from "swr";
import { useForm } from "react-hook-form";
// services
import { IssueService, IssueArchiveService } from "services/issue";
import useSWR from "swr";
// hooks
import useToast from "hooks/use-toast";
import { useIssueDetail, useIssues, useProject } from "hooks/store";
// layouts
import { AppLayout } from "layouts/app-layout";
// components
// FIXME: have to replace this once the issue details page is ready --issue-detail--
// import { IssueDetailsSidebar, IssueMainContent } from "components/issues";
import { IssueDetailRoot } from "components/issues";
import { ProjectArchivedIssueDetailsHeader } from "components/headers";
// ui
import { ArchiveIcon, Loader } from "@plane/ui";
// icons
import { History } from "lucide-react";
// types
import { TIssue } from "@plane/types";
import { NextPageWithLayout } from "lib/types";
// fetch-keys
import { PROJECT_ISSUES_ACTIVITY, ISSUE_DETAILS } from "constants/fetch-keys";
import { useProject } from "hooks/store";
const defaultValues: Partial<TIssue> = {
name: "",
// description: "",
description_html: "",
estimate_point: null,
state_id: "",
priority: "low",
target_date: new Date().toString(),
cycle_id: null,
module_id: null,
};
// services
const issueService = new IssueService();
const issueArchiveService = new IssueArchiveService();
// constants
import { EIssuesStoreType } from "constants/issue";
const ArchivedIssueDetailsPage: NextPageWithLayout = () => {
// router
@ -46,84 +25,43 @@ const ArchivedIssueDetailsPage: NextPageWithLayout = () => {
// states
const [isRestoring, setIsRestoring] = useState(false);
// hooks
const {
fetchIssue,
issue: { getIssueById },
} = useIssueDetail();
const {
issues: { removeIssueFromArchived },
} = useIssues(EIssuesStoreType.ARCHIVED);
const { setToastAlert } = useToast();
const { getProjectById } = useProject();
const { data: issueDetails, mutate: mutateIssueDetails } = useSWR<TIssue | undefined>(
workspaceSlug && projectId && archivedIssueId ? ISSUE_DETAILS(archivedIssueId as string) : null,
const { isLoading } = useSWR(
workspaceSlug && projectId && archivedIssueId
? () =>
issueArchiveService.retrieveArchivedIssue(
workspaceSlug as string,
projectId as string,
archivedIssueId as string
)
? `ARCHIVED_ISSUE_DETAIL_${workspaceSlug}_${projectId}_${archivedIssueId}`
: null,
workspaceSlug && projectId && archivedIssueId
? () => fetchIssue(workspaceSlug.toString(), projectId.toString(), archivedIssueId.toString(), true)
: null
);
const { reset, control, watch } = useForm<TIssue>({
defaultValues,
});
const submitChanges = useCallback(
async (formData: Partial<TIssue>) => {
if (!workspaceSlug || !projectId || !archivedIssueId) return;
mutate<TIssue>(
ISSUE_DETAILS(archivedIssueId as string),
(prevData) => {
if (!prevData) return prevData;
return {
...prevData,
...formData,
};
},
false
);
const payload: Partial<TIssue> = {
...formData,
};
await issueService
.patchIssue(workspaceSlug as string, projectId as string, archivedIssueId as string, payload)
.then(() => {
mutateIssueDetails();
mutate(PROJECT_ISSUES_ACTIVITY(archivedIssueId as string));
})
.catch((e) => {
console.error(e);
});
},
[workspaceSlug, archivedIssueId, projectId, mutateIssueDetails]
);
useEffect(() => {
if (!issueDetails) return;
mutate(PROJECT_ISSUES_ACTIVITY(archivedIssueId as string));
reset({
...issueDetails,
});
}, [issueDetails, reset, archivedIssueId]);
const issue = getIssueById(archivedIssueId?.toString() || "") || undefined;
if (!issue) return <></>;
const handleUnArchive = async () => {
if (!workspaceSlug || !projectId || !archivedIssueId) return;
setIsRestoring(true);
await issueArchiveService
.unarchiveIssue(workspaceSlug as string, projectId as string, archivedIssueId as string)
await removeIssueFromArchived(workspaceSlug as string, projectId as string, archivedIssueId as string)
.then(() => {
setToastAlert({
type: "success",
title: "Success",
message:
issueDetails &&
`${getProjectById(issueDetails.project_id)?.identifier}-${
issueDetails?.sequence_id
} is restored successfully under the project ${getProjectById(issueDetails.project_id)?.name}`,
issue &&
`${getProjectById(issue.project_id)?.identifier}-${
issue?.sequence_id
} is restored successfully under the project ${getProjectById(issue.project_id)?.name}`,
});
router.push(`/${workspaceSlug}/projects/${projectId}/issues/${archivedIssueId}`);
})
@ -137,45 +75,11 @@ const ArchivedIssueDetailsPage: NextPageWithLayout = () => {
.finally(() => setIsRestoring(false));
};
const issueLoader = !issue || isLoading ? true : false;
return (
<>
{issueDetails && projectId ? (
<div className="flex h-full overflow-hidden">
<div className="h-full w-2/3 space-y-2 divide-y-2 divide-custom-border-300 overflow-y-auto p-5">
{issueDetails.archived_at && (
<div className="flex items-center justify-between gap-2 rounded-md border border-custom-border-200 bg-custom-background-90 px-2.5 py-2 text-sm text-custom-text-200">
<div className="flex items-center gap-2">
<ArchiveIcon className="h-3.5 w-3.5" />
<p>This issue has been archived by Plane.</p>
</div>
<button
className="flex items-center gap-2 rounded-md border border-custom-border-200 p-1.5 text-sm"
onClick={handleUnArchive}
disabled={isRestoring}
>
<History className="h-3.5 w-3.5" />
<span>{isRestoring ? "Restoring..." : "Restore Issue"}</span>
</button>
</div>
)}
{/* FIXME: have to replace this once the issue details page is ready --issue-detail-- */}
{/* <div className="pointer-events-none space-y-5 divide-y-2 divide-custom-border-200 opacity-60">
<IssueMainContent issueDetails={issueDetails} submitChanges={submitChanges} uneditable />
</div> */}
</div>
{/* FIXME: have to replace this once the issue details page is ready --issue-detail-- */}
{/* <div className="h-full w-1/3 space-y-5 overflow-hidden border-l border-custom-border-300 p-5">
<IssueDetailsSidebar
control={control}
issueDetail={issueDetails}
submitChanges={submitChanges}
watch={watch}
uneditable
/>
</div> */}
</div>
) : (
{issueLoader ? (
<Loader className="flex h-full gap-5 p-5">
<div className="basis-2/3 space-y-2">
<Loader.Item height="30px" width="40%" />
@ -190,6 +94,35 @@ const ArchivedIssueDetailsPage: NextPageWithLayout = () => {
<Loader.Item height="30px" />
</div>
</Loader>
) : (
<div className="flex h-full overflow-hidden">
<div className="h-full w-full space-y-2 divide-y-2 divide-custom-border-300 overflow-y-auto p-5">
{issue?.archived_at && (
<div className="flex items-center justify-between gap-2 rounded-md border border-custom-border-200 bg-custom-background-90 px-2.5 py-2 text-sm text-custom-text-200">
<div className="flex items-center gap-2">
<ArchiveIcon className="h-3.5 w-3.5" />
<p>This issue has been archived by Plane.</p>
</div>
<button
className="flex items-center gap-2 rounded-md border border-custom-border-200 p-1.5 text-sm"
onClick={handleUnArchive}
disabled={isRestoring}
>
<History className="h-3.5 w-3.5" />
<span>{isRestoring ? "Restoring..." : "Restore Issue"}</span>
</button>
</div>
)}
{workspaceSlug && projectId && archivedIssueId && (
<IssueDetailRoot
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
issueId={archivedIssueId.toString()}
is_archived
/>
)}
</div>
</div>
)}
</>
);

View File

@ -59,7 +59,6 @@ const IssueDetailsPage: NextPageWithLayout = observer(() => {
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
issueId={issueId.toString()}
is_archived={!!issue?.archived_at}
/>
)
)}

View File

@ -128,8 +128,8 @@ export class ArchivedIssues extends IssueHelperStore implements IArchivedIssues
try {
const response = await this.archivedIssueService.unarchiveIssue(workspaceSlug, projectId, issueId);
const issueIndex = this.issues[projectId].findIndex((_issueId) => _issueId === issueId);
if (issueIndex >= 0)
const issueIndex = this.issues[projectId]?.findIndex((_issueId) => _issueId === issueId);
if (issueIndex && issueIndex >= 0)
runInAction(() => {
this.issues[projectId].splice(issueIndex, 1);
});

View File

@ -1,13 +1,13 @@
import { makeObservable } from "mobx";
// services
import { IssueService } from "services/issue";
import { IssueArchiveService, IssueService } from "services/issue";
// types
import { IIssueDetail } from "./root.store";
import { TIssue } from "@plane/types";
export interface IIssueStoreActions {
// actions
fetchIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<TIssue>;
fetchIssue: (workspaceSlug: string, projectId: string, issueId: string, isArchived?: boolean) => Promise<TIssue>;
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<TIssue>;
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<TIssue>;
addIssueToCycle: (workspaceSlug: string, projectId: string, cycleId: string, issueIds: string[]) => Promise<TIssue>;
@ -31,6 +31,7 @@ export class IssueStore implements IIssueStore {
rootIssueDetailStore: IIssueDetail;
// services
issueService;
issueArchiveService;
constructor(rootStore: IIssueDetail) {
makeObservable(this, {});
@ -38,6 +39,7 @@ export class IssueStore implements IIssueStore {
this.rootIssueDetailStore = rootStore;
// services
this.issueService = new IssueService();
this.issueArchiveService = new IssueArchiveService();
}
// helper methods
@ -47,12 +49,17 @@ export class IssueStore implements IIssueStore {
};
// actions
fetchIssue = async (workspaceSlug: string, projectId: string, issueId: string) => {
fetchIssue = async (workspaceSlug: string, projectId: string, issueId: string, isArchived = false) => {
try {
const query = {
expand: "state,assignees,labels,parent",
};
const issue = (await this.issueService.retrieve(workspaceSlug, projectId, issueId, query)) as any;
let issue: any;
if (isArchived) issue = await this.issueArchiveService.retrieveArchivedIssue(workspaceSlug, projectId, issueId);
else issue = await this.issueService.retrieve(workspaceSlug, projectId, issueId, query);
if (!issue) throw new Error("Issue not found");
this.rootIssueDetailStore.rootIssueStore.issues.addIssue([issue]);

View File

@ -133,8 +133,8 @@ export class IssueDetail implements IIssueDetail {
toggleRelationModal = (value: TIssueRelationTypes | null) => (this.isRelationModalOpen = value);
// issue
fetchIssue = async (workspaceSlug: string, projectId: string, issueId: string) =>
this.issue.fetchIssue(workspaceSlug, projectId, issueId);
fetchIssue = async (workspaceSlug: string, projectId: string, issueId: string, isArchived = false) =>
this.issue.fetchIssue(workspaceSlug, projectId, issueId, isArchived);
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) =>