fix: new project issues response (#303)

This commit is contained in:
Aaryan Khandelwal 2023-02-18 21:19:04 +05:30 committed by GitHub
parent 393638c700
commit 77c319c748
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 105 additions and 141 deletions

View File

@ -43,10 +43,13 @@ export const BoardHeader: React.FC<Props> = ({
let assignees: any;
if (selectedGroup === "assignees") {
assignees = groupTitle.split(",");
assignees = assignees
.map((a: string) => members?.find((m) => m.member.id === a)?.member.first_name)
.join(", ");
assignees = groupTitle && groupTitle !== "" ? groupTitle.split(",") : [];
assignees =
assignees.length > 0
? assignees
.map((a: string) => members?.find((m) => m.member.id === a)?.member.first_name)
.join(", ")
: "No assignee";
}
return (

View File

@ -12,10 +12,10 @@ import {
DraggingStyle,
NotDraggingStyle,
} from "react-beautiful-dnd";
// constants
import { TrashIcon } from "@heroicons/react/24/outline";
// services
import issuesService from "services/issues.service";
// hooks
import useToast from "hooks/use-toast";
// components
import {
ViewAssigneeSelect,
@ -25,11 +25,12 @@ import {
} from "components/issues/view-select";
// ui
import { CustomMenu } from "components/ui";
// helpers
import { copyTextToClipboard } from "helpers/string.helper";
// types
import {
CycleIssueResponse,
IIssue,
IssueResponse,
ModuleIssueResponse,
NestedKeyOf,
Properties,
@ -37,8 +38,6 @@ import {
} from "types";
// fetch-keys
import { CYCLE_ISSUES, MODULE_ISSUES, PROJECT_ISSUES_LIST } from "constants/fetch-keys";
import { copyTextToClipboard } from "helpers/string.helper";
import useToast from "hooks/use-toast";
type Props = {
type?: string;
@ -71,7 +70,9 @@ export const SingleBoardIssue: React.FC<Props> = ({
}) => {
const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
const { setToastAlert } = useToast();
const partialUpdateIssue = useCallback(
(formData: Partial<IIssue>) => {
if (!workspaceSlug || !projectId) return;
@ -118,15 +119,15 @@ export const SingleBoardIssue: React.FC<Props> = ({
false
);
mutate<IssueResponse>(
mutate<IIssue[]>(
PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string),
(prevData) => ({
...(prevData as IssueResponse),
results: (prevData?.results ?? []).map((p) => {
(prevData) =>
(prevData ?? []).map((p) => {
if (p.id === issue.id) return { ...p, ...formData };
return p;
}),
}),
false
);

View File

@ -18,7 +18,7 @@ import { Button } from "components/ui";
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
import { LayerDiagonalIcon } from "components/icons";
// types
import { IIssue, IssueResponse } from "types";
import { IIssue } from "types";
// fetch keys
import { PROJECT_ISSUES_LIST } from "constants/fetch-keys";
@ -62,8 +62,8 @@ export const BulkDeleteIssuesModal: React.FC<Props> = ({ isOpen, setIsOpen }) =>
const filteredIssues: IIssue[] =
query === ""
? issues?.results ?? []
: issues?.results.filter(
? issues ?? []
: issues?.filter(
(issue) =>
issue.name.toLowerCase().includes(query.toLowerCase()) ||
`${issue.project_detail.identifier}-${issue.sequence_id}`
@ -101,17 +101,9 @@ export const BulkDeleteIssuesModal: React.FC<Props> = ({ isOpen, setIsOpen }) =>
message: res.message,
});
mutate<IssueResponse>(
mutate<IIssue[]>(
PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string),
(prevData) => ({
...(prevData as IssueResponse),
count: (prevData?.results ?? []).filter(
(p) => !data.delete_issue_ids.some((id) => p.id === id)
).length,
results: (prevData?.results ?? []).filter(
(p) => !data.delete_issue_ids.some((id) => p.id === id)
),
}),
(prevData) => (prevData ?? []).filter((p) => !data.delete_issue_ids.includes(p.id)),
false
);
handleClose();

View File

@ -103,7 +103,7 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="relative mx-auto max-w-2xl transform divide-y divide-gray-500 divide-opacity-10 rounded-xl bg-white bg-opacity-80 shadow-2xl ring-1 ring-black ring-opacity-5 backdrop-blur backdrop-filter transition-all">
<Dialog.Panel className="relative mx-auto max-w-2xl transform divide-y divide-gray-500 divide-opacity-10 rounded-xl bg-white shadow-2xl ring-1 ring-black ring-opacity-5 transition-all">
<form>
<Controller
control={control}

View File

@ -22,7 +22,7 @@ import { TrashIcon } from "@heroicons/react/24/outline";
// helpers
import { getStatesList } from "helpers/state.helper";
// types
import { CycleIssueResponse, IIssue, IssueResponse, ModuleIssueResponse, UserAuth } from "types";
import { CycleIssueResponse, IIssue, ModuleIssueResponse, UserAuth } from "types";
// fetch-keys
import {
CYCLE_ISSUES,
@ -159,12 +159,12 @@ export const IssuesView: React.FC<Props> = ({
false
);
mutate<IssueResponse>(
mutate<IIssue[]>(
PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string),
(prevData) => {
if (!prevData) return prevData;
const updatedIssues = prevData.results.map((issue) => {
const updatedIssues = prevData.map((issue) => {
if (issue.id === draggedItem.id)
return {
...draggedItem,
@ -174,10 +174,7 @@ export const IssuesView: React.FC<Props> = ({
return issue;
});
return {
...prevData,
results: updatedIssues,
};
return updatedIssues;
},
false
);
@ -248,12 +245,12 @@ export const IssuesView: React.FC<Props> = ({
false
);
mutate<IssueResponse>(
mutate<IIssue[]>(
PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string),
(prevData) => {
if (!prevData) return prevData;
const updatedIssues = prevData.results.map((issue) => {
const updatedIssues = prevData.map((issue) => {
if (issue.id === draggedItem.id)
return {
...draggedItem,
@ -264,10 +261,7 @@ export const IssuesView: React.FC<Props> = ({
return issue;
});
return {
...prevData,
results: updatedIssues,
};
return updatedIssues;
},
false
);

View File

@ -7,6 +7,8 @@ import { mutate } from "swr";
// services
import issuesService from "services/issues.service";
// hooks
import useToast from "hooks/use-toast";
// components
import {
ViewAssigneeSelect,
@ -16,19 +18,12 @@ import {
} from "components/issues/view-select";
// ui
import { CustomMenu } from "components/ui";
// types
import {
CycleIssueResponse,
IIssue,
IssueResponse,
ModuleIssueResponse,
Properties,
UserAuth,
} from "types";
// fetch-keys
import { CYCLE_ISSUES, MODULE_ISSUES, PROJECT_ISSUES_LIST, STATE_LIST } from "constants/fetch-keys";
// helpers
import { copyTextToClipboard } from "helpers/string.helper";
import useToast from "hooks/use-toast";
// types
import { CycleIssueResponse, IIssue, ModuleIssueResponse, Properties, UserAuth } from "types";
// fetch-keys
import { CYCLE_ISSUES, MODULE_ISSUES, PROJECT_ISSUES_LIST } from "constants/fetch-keys";
type Props = {
type?: string;
@ -98,15 +93,15 @@ export const SingleListIssue: React.FC<Props> = ({
false
);
mutate<IssueResponse>(
mutate<IIssue[]>(
PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string),
(prevData) => ({
...(prevData as IssueResponse),
results: (prevData?.results ?? []).map((p) => {
(prevData) =>
(prevData ?? []).map((p) => {
if (p.id === issue.id) return { ...p, ...formData };
return p;
}),
}),
false
);

View File

@ -55,10 +55,13 @@ export const SingleList: React.FC<Props> = ({
let assignees: any;
if (selectedGroup === "assignees") {
assignees = groupTitle.split(",");
assignees = assignees
.map((a: string) => members?.find((m) => m.member.id === a)?.member.first_name)
.join(", ");
assignees = groupTitle && groupTitle !== "" ? groupTitle.split(",") : [];
assignees =
assignees.length > 0
? assignees
.map((a: string) => members?.find((m) => m.member.id === a)?.member.first_name)
.join(", ")
: "No assignee";
}
return (

View File

@ -15,7 +15,7 @@ import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
// ui
import { Button } from "components/ui";
// types
import type { CycleIssueResponse, IIssue, IssueResponse, ModuleIssueResponse } from "types";
import type { CycleIssueResponse, IIssue, ModuleIssueResponse } from "types";
// fetch-keys
import { CYCLE_ISSUES, PROJECT_ISSUES_LIST, MODULE_ISSUES, USER_ISSUE } from "constants/fetch-keys";
@ -77,13 +77,9 @@ export const DeleteIssueModal: React.FC<Props> = ({ isOpen, handleClose, data })
false
);
mutate<IssueResponse>(
mutate<IIssue[]>(
PROJECT_ISSUES_LIST(workspaceSlug as string, projectId),
(prevData) => ({
...(prevData as IssueResponse),
results: prevData?.results.filter((i) => i.id !== data.id) ?? [],
count: (prevData?.count as number) - 1,
}),
(prevData) => (prevData ?? []).filter((i) => i.id !== data.id),
false
);

View File

@ -16,7 +16,7 @@ import useToast from "hooks/use-toast";
// components
import { IssueForm } from "components/issues";
// types
import type { IIssue, IssueResponse } from "types";
import type { IIssue } from "types";
// fetch keys
import {
PROJECT_ISSUES_DETAILS,
@ -91,15 +91,13 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
false
);
} else
mutate<IssueResponse>(
mutate<IIssue[]>(
PROJECT_ISSUES_LIST(workspaceSlug as string, activeProject ?? ""),
(prevData) => ({
...(prevData as IssueResponse),
results: (prevData?.results ?? []).map((issue) => {
if (issue.id === res.id) return { ...issue, sprints: cycleId };
return issue;
(prevData) =>
(prevData ?? []).map((i) => {
if (i.id === res.id) return { ...i, sprints: cycleId };
return i;
}),
}),
false
);
})
@ -126,7 +124,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
await issuesService
.createIssues(workspaceSlug as string, activeProject ?? "", payload)
.then((res) => {
mutate<IssueResponse>(PROJECT_ISSUES_LIST(workspaceSlug as string, activeProject ?? ""));
mutate(PROJECT_ISSUES_LIST(workspaceSlug as string, activeProject ?? ""));
if (payload.cycle && payload.cycle !== "") addIssueToCycle(res.id, payload.cycle);
if (payload.module && payload.module !== "") addIssueToModule(res.id, payload.module);
@ -159,15 +157,13 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
if (isUpdatingSingleIssue) {
mutate<IIssue>(PROJECT_ISSUES_DETAILS, (prevData) => ({ ...prevData, ...res }), false);
} else {
mutate<IssueResponse>(
mutate<IIssue[]>(
PROJECT_ISSUES_LIST(workspaceSlug as string, activeProject ?? ""),
(prevData) => ({
...(prevData as IssueResponse),
results: (prevData?.results ?? []).map((issue) => {
if (issue.id === res.id) return { ...issue, ...res };
return issue;
}),
})
(prevData) =>
(prevData ?? []).map((i) => {
if (i.id === res.id) return { ...i, ...res };
return i;
})
);
}
@ -232,7 +228,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
>
<Dialog.Panel className="relative transform rounded-lg bg-white p-5 text-left shadow-xl transition-all sm:w-full sm:max-w-2xl">
<IssueForm
issues={issues?.results ?? []}
issues={issues ?? []}
handleFormSubmit={handleFormSubmit}
initialData={prePopulateData}
createMore={createMore}

View File

@ -127,14 +127,14 @@ export const SidebarBlockedSelect: React.FC<Props> = ({
>
<Link
href={`/${workspaceSlug}/projects/${projectId}/issues/${
issues?.results.find((i) => i.id === issue)?.id
issues?.find((i) => i.id === issue)?.id
}`}
>
<a className="flex items-center gap-1">
<BlockedIcon height={10} width={10} />
{`${
issues?.results.find((i) => i.id === issue)?.project_detail?.identifier
}-${issues?.results.find((i) => i.id === issue)?.sequence_id}`}
{`${issues?.find((i) => i.id === issue)?.project_detail?.identifier}-${
issues?.find((i) => i.id === issue)?.sequence_id
}`}
</a>
</Link>
<span className="opacity-0 duration-300 group-hover:opacity-100">
@ -243,8 +243,8 @@ export const SidebarBlockedSelect: React.FC<Props> = ({
/>
<span className="flex-shrink-0 text-xs text-gray-500">
{
issues?.results.find((i) => i.id === issue.id)
?.project_detail?.identifier
issues?.find((i) => i.id === issue.id)?.project_detail
?.identifier
}
-{issue.sequence_id}
</span>

View File

@ -119,14 +119,14 @@ export const SidebarBlockerSelect: React.FC<Props> = ({
>
<Link
href={`/${workspaceSlug}/projects/${projectId}/issues/${
issues?.results.find((i) => i.id === issue)?.id
issues?.find((i) => i.id === issue)?.id
}`}
>
<a className="flex items-center gap-1">
<BlockerIcon height={10} width={10} />
{`${
issues?.results.find((i) => i.id === issue)?.project_detail?.identifier
}-${issues?.results.find((i) => i.id === issue)?.sequence_id}`}
{`${issues?.find((i) => i.id === issue)?.project_detail?.identifier}-${
issues?.find((i) => i.id === issue)?.sequence_id
}`}
</a>
</Link>
<span
@ -244,8 +244,8 @@ export const SidebarBlockerSelect: React.FC<Props> = ({
/>
<span className="flex-shrink-0 text-xs text-gray-500">
{
issues?.results.find((i) => i.id === issue.id)
?.project_detail?.identifier
issues?.find((i) => i.id === issue.id)?.project_detail
?.identifier
}
-{issue.sequence_id}
</span>

View File

@ -84,9 +84,9 @@ export const SidebarParentSelect: React.FC<Props> = ({
disabled={isNotAllowed}
>
{watch("parent") && watch("parent") !== ""
? `${
issues?.results.find((i) => i.id === watch("parent"))?.project_detail?.identifier
}-${issues?.results.find((i) => i.id === watch("parent"))?.sequence_id}`
? `${issues?.find((i) => i.id === watch("parent"))?.project_detail?.identifier}-${
issues?.find((i) => i.id === watch("parent"))?.sequence_id
}`
: "Select issue"}
</button>
</div>

View File

@ -222,7 +222,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
control={control}
submitChanges={submitChanges}
issuesList={
issues?.results.filter(
issues?.filter(
(i) =>
i.id !== issueDetail?.id &&
i.id !== issueDetail?.parent &&
@ -250,13 +250,13 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
/>
<SidebarBlockerSelect
submitChanges={submitChanges}
issuesList={issues?.results.filter((i) => i.id !== issueDetail?.id) ?? []}
issuesList={issues?.filter((i) => i.id !== issueDetail?.id) ?? []}
watch={watchIssue}
userAuth={userAuth}
/>
<SidebarBlockedSelect
submitChanges={submitChanges}
issuesList={issues?.results.filter((i) => i.id !== issueDetail?.id) ?? []}
issuesList={issues?.filter((i) => i.id !== issueDetail?.id) ?? []}
watch={watchIssue}
userAuth={userAuth}
/>

View File

@ -19,7 +19,7 @@ import { ChevronRightIcon, PlusIcon, XMarkIcon } from "@heroicons/react/24/outli
// helpers
import { orderArrayBy } from "helpers/array.helper";
// types
import { IIssue, IssueResponse, UserAuth } from "types";
import { IIssue, UserAuth } from "types";
// fetch-keys
import { PROJECT_ISSUES_LIST, SUB_ISSUES } from "constants/fetch-keys";
@ -68,7 +68,7 @@ export const SubIssuesList: FC<Props> = ({ parentIssue, userAuth }) => {
let newSubIssues = [...(prevData as IIssue[])];
data.issues.forEach((issueId: string) => {
const issue = issues?.results.find((i) => i.id === issueId);
const issue = issues?.find((i) => i.id === issueId);
if (issue) newSubIssues.push(issue);
});
@ -80,11 +80,10 @@ export const SubIssuesList: FC<Props> = ({ parentIssue, userAuth }) => {
false
);
mutate<IssueResponse>(
mutate<IIssue[]>(
PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string),
(prevData) => ({
...(prevData as IssueResponse),
results: (prevData?.results ?? []).map((p) => {
(prevData) =>
(prevData ?? []).map((p) => {
if (data.issues.includes(p.id))
return {
...p,
@ -93,7 +92,6 @@ export const SubIssuesList: FC<Props> = ({ parentIssue, userAuth }) => {
return p;
}),
}),
false
);
@ -118,11 +116,10 @@ export const SubIssuesList: FC<Props> = ({ parentIssue, userAuth }) => {
.then((res) => {
mutate(SUB_ISSUES(parentIssue.id ?? ""));
mutate<IssueResponse>(
mutate<IIssue[]>(
PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string),
(prevData) => ({
...(prevData as IssueResponse),
results: (prevData?.results ?? []).map((p) => {
(prevData) =>
(prevData ?? []).map((p) => {
if (p.id === res.id)
return {
...p,
@ -131,7 +128,6 @@ export const SubIssuesList: FC<Props> = ({ parentIssue, userAuth }) => {
return p;
}),
}),
false
);
})
@ -160,7 +156,7 @@ export const SubIssuesList: FC<Props> = ({ parentIssue, userAuth }) => {
isOpen={subIssuesListModal}
handleClose={() => setSubIssuesListModal(false)}
issues={
issues?.results.filter(
issues?.filter(
(i) =>
(i.parent === "" || i.parent === null) &&
i.id !== parentIssue?.id &&

View File

@ -8,6 +8,8 @@ import { useRouter } from "next/router";
import useSWR from "swr";
// services
import cyclesService from "services/cycles.service";
// hooks
import useToast from "hooks/use-toast";
// ui
import { Button, CustomMenu } from "components/ui";
// icons
@ -17,12 +19,11 @@ import { CyclesIcon } from "components/icons";
// helpers
import { renderShortNumericDateFormat } from "helpers/date-time.helper";
import { groupBy } from "helpers/array.helper";
import { copyTextToClipboard } from "helpers/string.helper";
// types
import { CycleIssueResponse, ICycle } from "types";
// fetch-keys
import { CYCLE_ISSUES } from "constants/fetch-keys";
import { copyTextToClipboard } from "helpers/string.helper";
import useToast from "hooks/use-toast";
type TSingleStatProps = {
cycle: ICycle;

View File

@ -74,7 +74,7 @@ export const DeleteStateModal: React.FC<Props> = ({ isOpen, onClose, data }) =>
});
};
const groupedIssues = groupBy(issues?.results ?? [], "state");
const groupedIssues = groupBy(issues ?? [], "state");
useEffect(() => {
if (data) setIssuesWithThisStateExist(!!groupedIssues[data.id]);

View File

@ -118,7 +118,7 @@ const SingleCycle: React.FC<UserAuth> = (props) => {
<ExistingIssuesListModal
isOpen={cycleIssuesListModal}
handleClose={() => setCycleIssuesListModal(false)}
issues={issues?.results.filter((i) => !i.issue_cycle) ?? []}
issues={issues?.filter((i) => !i.issue_cycle) ?? []}
handleOnSubmit={handleAddIssuesToCycle}
/>
<AppLayout

View File

@ -55,9 +55,7 @@ const ProjectIssues: NextPage<UserAuth> = (props) => {
}
right={
<div className="flex items-center gap-2">
<IssuesFilterView
issues={projectIssues?.results.filter((p) => p.parent === null) ?? []}
/>
<IssuesFilterView issues={projectIssues?.filter((p) => p.parent === null) ?? []} />
<HeaderButton
Icon={PlusIcon}
label="Add Issue"
@ -72,9 +70,9 @@ const ProjectIssues: NextPage<UserAuth> = (props) => {
}
>
{projectIssues ? (
projectIssues.count > 0 ? (
projectIssues.length > 0 ? (
<IssuesView
issues={projectIssues?.results.filter((p) => p.parent === null) ?? []}
issues={projectIssues?.filter((p) => p.parent === null) ?? []}
userAuth={props}
/>
) : (

View File

@ -113,7 +113,7 @@ const SingleModule: React.FC<UserAuth> = (props) => {
<ExistingIssuesListModal
isOpen={moduleIssuesListModal}
handleClose={() => setModuleIssuesListModal(false)}
issues={issues?.results.filter((i) => !i.issue_module) ?? []}
issues={issues?.filter((i) => !i.issue_module) ?? []}
handleOnSubmit={handleAddIssuesToModule}
/>
<AppLayout

View File

@ -1,7 +1,7 @@
// services
import APIService from "services/api.service";
// type
import type { IIssue, IIssueActivity, IIssueComment, IssueResponse } from "types";
import type { IIssue, IIssueActivity, IIssueComment } from "types";
const { NEXT_PUBLIC_API_BASE_URL } = process.env;
@ -18,7 +18,7 @@ class ProjectIssuesServices extends APIService {
});
}
async getIssues(workspaceSlug: string, projectId: string): Promise<IssueResponse> {
async getIssues(workspaceSlug: string, projectId: string): Promise<IIssue[]> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/`)
.then((response) => response?.data)
.catch((error) => {

View File

@ -1,16 +1,5 @@
import type { IState, IUser, IProject, ICycle, IModule, IUserLite } from "./";
export interface IssueResponse {
next_cursor: string;
prev_cursor: string;
next_page_results: boolean;
prev_page_results: boolean;
count: number;
total_pages: number;
extra_stats: null;
results: IIssue[];
}
export interface IIssueCycle {
id: string;
cycle_detail: ICycle;