From 91cb15c2e34f9553d1b8bd9100bd2a8768d15e0e Mon Sep 17 00:00:00 2001 From: rahulramesha <71900764+rahulramesha@users.noreply.github.com> Date: Wed, 6 Dec 2023 01:02:18 +0530 Subject: [PATCH] fix: bugs related to issues (#2995) * hide properties in list and kanban with 0 or nil values * module and cycle mutation from peek overlay * fix peek over view title change while switching * fix create issue fetching * fix build errors by mutating the values as well --- .../issue-layouts/kanban/properties.tsx | 6 +- .../issues/issue-layouts/list/properties.tsx | 6 +- .../issue-peek-overview/issue-detail.tsx | 10 +- .../issues/issue-peek-overview/properties.tsx | 26 ++--- web/components/issues/modal.tsx | 2 +- .../issues/sidebar-select/cycle.tsx | 104 +++++++++++------ .../issues/sidebar-select/module.tsx | 110 +++++++++++------- web/components/profile/profile-issues.tsx | 2 +- web/store/issues/profile/issue.store.ts | 2 + .../project-issues/cycle/issue.store.ts | 21 +++- .../project-issues/module/issue.store.ts | 21 +++- 11 files changed, 193 insertions(+), 117 deletions(-) diff --git a/web/components/issues/issue-layouts/kanban/properties.tsx b/web/components/issues/issue-layouts/kanban/properties.tsx index 929311f31..8eec09d8e 100644 --- a/web/components/issues/issue-layouts/kanban/properties.tsx +++ b/web/components/issues/issue-layouts/kanban/properties.tsx @@ -164,7 +164,7 @@ export const KanBanProperties: React.FC = observer((props) => {/* extra render properties */} {/* sub-issues */} - {displayProperties && displayProperties?.sub_issue_count && ( + {displayProperties && displayProperties?.sub_issue_count && !!issue?.sub_issues_count && (
@@ -174,7 +174,7 @@ export const KanBanProperties: React.FC = observer((props) => )} {/* attachments */} - {displayProperties && displayProperties?.attachment_count && ( + {displayProperties && displayProperties?.attachment_count && !!issue?.attachment_count && (
@@ -184,7 +184,7 @@ export const KanBanProperties: React.FC = observer((props) => )} {/* link */} - {displayProperties && displayProperties?.link && ( + {displayProperties && displayProperties?.link && !!issue?.link_count && (
diff --git a/web/components/issues/issue-layouts/list/properties.tsx b/web/components/issues/issue-layouts/list/properties.tsx index a1ce45313..4ebb0c04d 100644 --- a/web/components/issues/issue-layouts/list/properties.tsx +++ b/web/components/issues/issue-layouts/list/properties.tsx @@ -135,7 +135,7 @@ export const ListProperties: FC = observer((props) => { {/* extra render properties */} {/* sub-issues */} - {displayProperties && displayProperties?.sub_issue_count && ( + {displayProperties && displayProperties?.sub_issue_count && !!issue?.sub_issues_count && (
@@ -145,7 +145,7 @@ export const ListProperties: FC = observer((props) => { )} {/* attachments */} - {displayProperties && displayProperties?.attachment_count && ( + {displayProperties && displayProperties?.attachment_count && !!issue?.attachment_count && (
@@ -155,7 +155,7 @@ export const ListProperties: FC = observer((props) => { )} {/* link */} - {displayProperties && displayProperties?.link && ( + {displayProperties && displayProperties?.link && !!issue?.link_count && (
diff --git a/web/components/issues/issue-peek-overview/issue-detail.tsx b/web/components/issues/issue-peek-overview/issue-detail.tsx index 3e90c8b8d..e582ee3b2 100644 --- a/web/components/issues/issue-peek-overview/issue-detail.tsx +++ b/web/components/issues/issue-peek-overview/issue-detail.tsx @@ -60,8 +60,8 @@ export const PeekOverviewIssueDetails: FC = (props) = formState: { errors }, } = useForm({ defaultValues: { - name: "", - description_html: "", + name: issue.name, + description_html: issue.description_html, }, }); @@ -78,7 +78,7 @@ export const PeekOverviewIssueDetails: FC = (props) = [issue, issueUpdate] ); - const [localTitleValue, setLocalTitleValue] = useState(""); + const [localTitleValue, setLocalTitleValue] = useState(issue.name); const issueTitleCurrentValue = watch("name"); useEffect(() => { if (localTitleValue === "" && issueTitleCurrentValue !== "") { @@ -86,6 +86,10 @@ export const PeekOverviewIssueDetails: FC = (props) = } }, [issueTitleCurrentValue, localTitleValue]); + useEffect(() => { + setLocalTitleValue(issue.name); + }, [issue.name]); + const debouncedFormSave = debounce(async () => { handleSubmit(handleDescriptionFormSubmit)().finally(() => setIsSubmitting("submitted")); }, 1500); diff --git a/web/components/issues/issue-peek-overview/properties.tsx b/web/components/issues/issue-peek-overview/properties.tsx index 9ccba48b4..05cb05d7c 100644 --- a/web/components/issues/issue-peek-overview/properties.tsx +++ b/web/components/issues/issue-peek-overview/properties.tsx @@ -47,12 +47,13 @@ export const PeekOverviewProperties: FC = observer((pro const { user: { currentProjectRole }, - cycleIssues: { addIssueToCycle }, + cycleIssues: cycleIssueStore, moduleIssues: { addIssueToModule }, + issueDetail: { fetchPeekIssueDetails }, } = useMobxStore(); const router = useRouter(); - const { workspaceSlug, projectId } = router.query; + const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string }; const { setToastAlert } = useToast(); @@ -77,17 +78,6 @@ export const PeekOverviewProperties: FC = observer((pro const handleParent = (_parent: string) => { issueUpdate({ ...issue, parent: _parent }); }; - const handleAddIssueToCycle = async (cycleId: string) => { - if (!workspaceSlug || !issue || !cycleId) return; - - addIssueToCycle(workspaceSlug.toString(), cycleId, [issue.id]); - }; - - const handleAddIssueToModule = async (moduleId: string) => { - if (!workspaceSlug || !issue || !moduleId) return; - - addIssueToModule(workspaceSlug.toString(), moduleId, [issue.id]); - }; const handleLabels = (formData: Partial) => { issueUpdate({ ...issue, ...formData }); }; @@ -147,6 +137,12 @@ export const PeekOverviewProperties: FC = observer((pro }); }; + const handleCycleOrModuleChange = async () => { + if (!workspaceSlug || !projectId) return; + + await fetchPeekIssueDetails(workspaceSlug, projectId, issue.id); + }; + const handleEditLink = (link: ILinkDetails) => { setSelectedLinkToUpdate(link); setLinkModal(true); @@ -309,8 +305,8 @@ export const PeekOverviewProperties: FC = observer((pro
@@ -323,8 +319,8 @@ export const PeekOverviewProperties: FC = observer((pro
diff --git a/web/components/issues/modal.tsx b/web/components/issues/modal.tsx index 65ae1b12f..3d5780c06 100644 --- a/web/components/issues/modal.tsx +++ b/web/components/issues/modal.tsx @@ -240,7 +240,7 @@ export const CreateUpdateIssueModal: React.FC = observer((prop if (handleSubmit) { await handleSubmit(res); } else { - if (viewId) currentIssueStore.fetchIssues(workspaceSlug, dataIdToUpdate, "mutation", viewId); + currentIssueStore.fetchIssues(workspaceSlug, dataIdToUpdate, "mutation", viewId); if (payload.cycle && payload.cycle !== "") await addIssueToCycle(res, payload.cycle); if (payload.module && payload.module !== "") await addIssueToModule(res, payload.module); diff --git a/web/components/issues/sidebar-select/cycle.tsx b/web/components/issues/sidebar-select/cycle.tsx index 03b3f5376..e5708fa0f 100644 --- a/web/components/issues/sidebar-select/cycle.tsx +++ b/web/components/issues/sidebar-select/cycle.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useState } from "react"; import { useRouter } from "next/router"; import useSWR, { mutate } from "swr"; // mobx store @@ -6,7 +6,7 @@ import { useMobxStore } from "lib/mobx/store-provider"; // services import { CycleService } from "services/cycle.service"; // ui -import { ContrastIcon, CustomSearchSelect, Tooltip } from "@plane/ui"; +import { ContrastIcon, CustomSearchSelect, Spinner, Tooltip } from "@plane/ui"; // types import { IIssue } from "types"; // fetch-keys @@ -14,23 +14,26 @@ import { CYCLE_ISSUES, INCOMPLETE_CYCLES_LIST, ISSUE_DETAILS } from "constants/f type Props = { issueDetail: IIssue | undefined; - handleCycleChange: (cycleId: string) => void; + handleCycleChange?: (cycleId: string) => void; disabled?: boolean; + handleIssueUpdate?: () => void; }; // services const cycleService = new CycleService(); export const SidebarCycleSelect: React.FC = (props) => { - const { issueDetail, handleCycleChange, disabled = false } = props; + const { issueDetail, disabled = false, handleIssueUpdate, handleCycleChange } = props; // router const router = useRouter(); const { workspaceSlug, projectId } = router.query; // mobx store const { - cycleIssues: { removeIssueFromCycle }, + cycleIssues: { removeIssueFromCycle, addIssueToCycle }, } = useMobxStore(); + const [isUpdating, setIsUpdating] = useState(false); + const { data: incompleteCycles } = useSWR( workspaceSlug && projectId ? INCOMPLETE_CYCLES_LIST(projectId as string) : null, workspaceSlug && projectId @@ -38,17 +41,35 @@ export const SidebarCycleSelect: React.FC = (props) => { : null ); + const handleCycleStoreChange = async (cycleId: string) => { + if (!workspaceSlug || !issueDetail || !cycleId) return; + + setIsUpdating(true); + await addIssueToCycle(workspaceSlug.toString(), cycleId, [issueDetail.id], false, projectId?.toString()) + .then(async () => { + handleIssueUpdate && (await handleIssueUpdate()); + }) + .finally(() => { + setIsUpdating(false); + }); + }; + const handleRemoveIssueFromCycle = (bridgeId: string, cycleId: string) => { if (!workspaceSlug || !projectId || !issueDetail) return; + setIsUpdating(true); removeIssueFromCycle(workspaceSlug.toString(), projectId.toString(), cycleId, issueDetail.id, bridgeId) - .then(() => { + .then(async () => { + handleIssueUpdate && (await handleIssueUpdate()); mutate(ISSUE_DETAILS(issueDetail.id)); mutate(CYCLE_ISSUES(cycleId)); }) .catch((e) => { console.log(e); + }) + .finally(() => { + setIsUpdating(false); }); }; @@ -67,39 +88,46 @@ export const SidebarCycleSelect: React.FC = (props) => { const issueCycle = issueDetail?.issue_cycle; + const disableSelect = disabled || isUpdating; + return ( - { - value === issueCycle?.cycle_detail.id - ? handleRemoveIssueFromCycle(issueCycle?.id ?? "", issueCycle?.cycle ?? "") - : handleCycleChange(value); - }} - options={options} - customButton={ -
- - - -
- } - width="max-w-[10rem]" - noChevron - disabled={disabled} - /> + + {issueCycle && } + {issueCycle ? issueCycle.cycle_detail.name : "No cycle"} + + +
+
+ } + width="max-w-[10rem]" + noChevron + disabled={disableSelect} + /> + {isUpdating && } +
); }; diff --git a/web/components/issues/sidebar-select/module.tsx b/web/components/issues/sidebar-select/module.tsx index 9300c53bd..026a0d30f 100644 --- a/web/components/issues/sidebar-select/module.tsx +++ b/web/components/issues/sidebar-select/module.tsx @@ -1,11 +1,11 @@ -import React from "react"; +import React, { useState } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import { mutate } from "swr"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; // ui -import { CustomSearchSelect, DiceIcon, Tooltip } from "@plane/ui"; +import { CustomSearchSelect, DiceIcon, Spinner, Tooltip } from "@plane/ui"; // types import { IIssue } from "types"; // fetch-keys @@ -13,32 +13,53 @@ import { ISSUE_DETAILS, MODULE_ISSUES } from "constants/fetch-keys"; type Props = { issueDetail: IIssue | undefined; - handleModuleChange: (moduleId: string) => void; + handleModuleChange?: (moduleId: string) => void; disabled?: boolean; + handleIssueUpdate?: () => void; }; export const SidebarModuleSelect: React.FC = observer((props) => { - const { issueDetail, handleModuleChange, disabled = false } = props; + const { issueDetail, disabled = false, handleIssueUpdate, handleModuleChange } = props; // router const router = useRouter(); const { workspaceSlug, projectId } = router.query; // mobx store const { module: { projectModules }, - moduleIssues: { removeIssueFromModule }, + moduleIssues: { removeIssueFromModule, addIssueToModule }, } = useMobxStore(); + const [isUpdating, setIsUpdating] = useState(false); + + const handleModuleStoreChange = async (moduleId: string) => { + if (!workspaceSlug || !issueDetail || !moduleId) return; + + setIsUpdating(true); + await addIssueToModule(workspaceSlug.toString(), moduleId, [issueDetail.id], false, projectId?.toString()) + .then(async () => { + handleIssueUpdate && (await handleIssueUpdate()); + }) + .finally(() => { + setIsUpdating(false); + }); + }; + const handleRemoveIssueFromModule = (bridgeId: string, moduleId: string) => { if (!workspaceSlug || !projectId || !issueDetail) return; + setIsUpdating(true); removeIssueFromModule(workspaceSlug.toString(), projectId.toString(), moduleId, issueDetail.id, bridgeId) - .then(() => { + .then(async () => { + handleIssueUpdate && (await handleIssueUpdate()); mutate(ISSUE_DETAILS(issueDetail.id)); mutate(MODULE_ISSUES(moduleId)); }) .catch((e) => { console.log(e); + }) + .finally(() => { + setIsUpdating(false); }); }; @@ -57,44 +78,51 @@ export const SidebarModuleSelect: React.FC = observer((props) => { const issueModule = issueDetail?.issue_module; + const disableSelect = disabled || isUpdating; + return ( - { - value === issueModule?.module_detail.id - ? handleRemoveIssueFromModule(issueModule?.id ?? "", issueModule?.module ?? "") - : handleModuleChange(value); - }} - options={options} - customButton={ -
- m.id === issueModule?.module)?.name ?? "No module"}`} - > - - -
- } - width="max-w-[10rem]" - noChevron - disabled={disabled} - /> + +
+
+ } + width="max-w-[10rem]" + noChevron + disabled={disableSelect} + /> + {isUpdating && } +
); }); diff --git a/web/components/profile/profile-issues.tsx b/web/components/profile/profile-issues.tsx index cfb8cdbe6..35d9cf7b3 100644 --- a/web/components/profile/profile-issues.tsx +++ b/web/components/profile/profile-issues.tsx @@ -34,7 +34,7 @@ export const ProfileIssuesPage = observer((props: IProfileIssuesPage) => { async () => { if (workspaceSlug && userId) { await fetchFilters(workspaceSlug); - await fetchIssues(workspaceSlug, userId, getIssues ? "mutation" : "init-loader", type); + await fetchIssues(workspaceSlug, userId, getIssues ? "mutation" : "init-loader", undefined, type); } } ); diff --git a/web/store/issues/profile/issue.store.ts b/web/store/issues/profile/issue.store.ts index eb02796c6..1ef0b34c8 100644 --- a/web/store/issues/profile/issue.store.ts +++ b/web/store/issues/profile/issue.store.ts @@ -28,6 +28,7 @@ export interface IProfileIssuesStore { workspaceSlug: string, userId: string, loadType: TLoader, + id?: string | undefined, type?: "assigned" | "created" | "subscribed" ) => Promise; createIssue: (workspaceSlug: string, userId: string, data: Partial) => Promise; @@ -150,6 +151,7 @@ export class ProfileIssuesStore extends IssueBaseStore implements IProfileIssues workspaceSlug: string, userId: string, loadType: TLoader = "init-loader", + id?: string | undefined, type?: "assigned" | "created" | "subscribed" ) => { try { diff --git a/web/store/issues/project-issues/cycle/issue.store.ts b/web/store/issues/project-issues/cycle/issue.store.ts index a951914a1..ffcf850b8 100644 --- a/web/store/issues/project-issues/cycle/issue.store.ts +++ b/web/store/issues/project-issues/cycle/issue.store.ts @@ -53,7 +53,8 @@ export interface ICycleIssuesStore { workspaceSlug: string, cycleId: string, issueIds: string[], - fetchAfterAddition?: boolean + fetchAfterAddition?: boolean, + projectId?: string ) => Promise; removeIssueFromCycle: ( workspaceSlug: string, @@ -314,19 +315,27 @@ export class CycleIssuesStore extends IssueBaseStore implements ICycleIssuesStor } }; - addIssueToCycle = async (workspaceSlug: string, cycleId: string, issueIds: string[], fetchAfterAddition = true) => { - if (!this.currentProjectId) return; + addIssueToCycle = async ( + workspaceSlug: string, + cycleId: string, + issueIds: string[], + fetchAfterAddition = true, + projectId?: string + ) => { + if (!this.currentProjectId && !projectId) return; + + const projectIdToUpdate: string = this.currentProjectId || projectId || ""; try { - const issueToCycle = await this.issueService.addIssueToCycle(workspaceSlug, this.currentProjectId, cycleId, { + const issueToCycle = await this.issueService.addIssueToCycle(workspaceSlug, projectIdToUpdate, cycleId, { issues: issueIds, }); - if (fetchAfterAddition) this.fetchIssues(workspaceSlug, this.currentProjectId, "mutation", cycleId); + if (fetchAfterAddition) this.fetchIssues(workspaceSlug, projectIdToUpdate, "mutation", cycleId); return issueToCycle; } catch (error) { - this.fetchIssues(workspaceSlug, this.currentProjectId, "mutation", cycleId); + this.fetchIssues(workspaceSlug, projectIdToUpdate, "mutation", cycleId); throw error; } }; diff --git a/web/store/issues/project-issues/module/issue.store.ts b/web/store/issues/project-issues/module/issue.store.ts index feaba7f56..89a678422 100644 --- a/web/store/issues/project-issues/module/issue.store.ts +++ b/web/store/issues/project-issues/module/issue.store.ts @@ -53,7 +53,8 @@ export interface IModuleIssuesStore { workspaceSlug: string, moduleId: string, issueIds: string[], - fetchAfterAddition?: boolean + fetchAfterAddition?: boolean, + projectId?: string ) => Promise; removeIssueFromModule: ( workspaceSlug: string, @@ -306,19 +307,27 @@ export class ModuleIssuesStore extends IssueBaseStore implements IModuleIssuesSt } }; - addIssueToModule = async (workspaceSlug: string, moduleId: string, issueIds: string[], fetchAfterAddition = true) => { - if (!this.currentProjectId) return; + addIssueToModule = async ( + workspaceSlug: string, + moduleId: string, + issueIds: string[], + fetchAfterAddition = true, + projectId?: string + ) => { + if (!this.currentProjectId && !projectId) return; + + const projectIdToUpdate: string = this.currentProjectId || projectId || ""; try { - const issueToModule = await this.moduleService.addIssuesToModule(workspaceSlug, this.currentProjectId, moduleId, { + const issueToModule = await this.moduleService.addIssuesToModule(workspaceSlug, projectIdToUpdate, moduleId, { issues: issueIds, }); - if (fetchAfterAddition) this.fetchIssues(workspaceSlug, this.currentProjectId, "mutation", moduleId); + if (fetchAfterAddition) this.fetchIssues(workspaceSlug, projectIdToUpdate, "mutation", moduleId); return issueToModule; } catch (error) { - this.fetchIssues(workspaceSlug, this.currentProjectId, "mutation", moduleId); + this.fetchIssues(workspaceSlug, projectIdToUpdate, "mutation", moduleId); throw error; } };