From b814e4c9052c6928cb7240e4d7e5578b2c10a13a Mon Sep 17 00:00:00 2001 From: dakshesh14 Date: Wed, 13 Sep 2023 11:42:48 +0530 Subject: [PATCH 1/6] feat: issue linking --- .../issues/sidebar-select/blocked.tsx | 62 +++++-- .../issues/sidebar-select/blocker.tsx | 65 +++++-- .../issues/sidebar-select/duplicate.tsx | 174 ++++++++++++++++++ web/components/issues/sidebar-select/index.ts | 2 + .../issues/sidebar-select/relates-to.tsx | 174 ++++++++++++++++++ web/components/issues/sidebar.tsx | 72 +++++++- web/hooks/my-issues/use-my-issues-filter.tsx | 26 +-- .../projects/[projectId]/issues/[issueId].tsx | 4 +- web/services/issues.service.ts | 46 +++++ web/services/track-event.service.ts | 16 ++ web/types/issues.d.ts | 27 ++- 11 files changed, 614 insertions(+), 54 deletions(-) create mode 100644 web/components/issues/sidebar-select/duplicate.tsx create mode 100644 web/components/issues/sidebar-select/relates-to.tsx diff --git a/web/components/issues/sidebar-select/blocked.tsx b/web/components/issues/sidebar-select/blocked.tsx index 02cfd3b1691..21d5f3d71f2 100644 --- a/web/components/issues/sidebar-select/blocked.tsx +++ b/web/components/issues/sidebar-select/blocked.tsx @@ -1,11 +1,13 @@ import React, { useState } from "react"; import { useRouter } from "next/router"; - // react-hook-form import { UseFormWatch } from "react-hook-form"; // hooks import useToast from "hooks/use-toast"; +import useUser from "hooks/use-user"; +// services +import issuesService from "services/issues.service"; // components import { ExistingIssuesListModal } from "components/core"; // icons @@ -29,10 +31,11 @@ export const SidebarBlockedSelect: React.FC = ({ }) => { const [isBlockedModalOpen, setIsBlockedModalOpen] = useState(false); + const { user } = useUser(); const { setToastAlert } = useToast(); const router = useRouter(); - const { workspaceSlug } = router.query; + const { workspaceSlug, projectId } = router.query; const handleClose = () => { setIsBlockedModalOpen(false); @@ -62,15 +65,33 @@ export const SidebarBlockedSelect: React.FC = ({ }, })); - const newBlocked = [...watch("blocked_issues"), ...selectedIssues]; + if (!user) return; + + issuesService + .createIssueRelation(workspaceSlug as string, projectId as string, issueId as string, user, { + related_list: [ + ...selectedIssues.map((issue) => ({ + issue: issueId as string, + relation_type: "blocked_by" as const, + related_issue_detail: issue.blocked_issue_detail, + related_issue: issue.blocked_issue_detail.id, + })), + ], + }) + .then((response) => { + submitChanges({ + related_issues: [ + ...watch("related_issues")?.filter((i) => i.relation_type !== "blocked_by"), + ...response, + ], + }); + }); - submitChanges({ - blocked_issues: newBlocked, - blocks_list: newBlocked.map((i) => i.blocked_issue_detail?.id ?? ""), - }); handleClose(); }; + const blockedByIssue = watch("related_issues")?.filter((i) => i.relation_type === "blocked_by"); + return ( <> = ({
- {watch("blocked_issues") && watch("blocked_issues").length > 0 - ? watch("blocked_issues").map((issue) => ( + {blockedByIssue && blockedByIssue.length > 0 + ? blockedByIssue.map((relation) => (
- {`${issue.blocked_issue_detail?.project_detail.identifier}-${issue.blocked_issue_detail?.sequence_id}`} + {`${relation.related_issue_detail?.project_detail.identifier}-${relation.related_issue_detail?.sequence_id}`}
- {watch("blocker_issues") && watch("blocker_issues").length > 0 - ? watch("blocker_issues").map((issue) => ( + {blockerIssue && blockerIssue.length > 0 + ? blockerIssue.map((relation) => (
- {`${issue.blocker_issue_detail?.project_detail.identifier}-${issue.blocker_issue_detail?.sequence_id}`} + {`${relation.issue_detail?.project_detail.identifier}-${relation.issue_detail?.sequence_id}`} +
+ )) + : null} +
+ +
+
+ + ); +}; diff --git a/web/components/issues/sidebar-select/index.ts b/web/components/issues/sidebar-select/index.ts index 5035325fd98..8b083841eaf 100644 --- a/web/components/issues/sidebar-select/index.ts +++ b/web/components/issues/sidebar-select/index.ts @@ -8,3 +8,5 @@ export * from "./module"; export * from "./parent"; export * from "./priority"; export * from "./state"; +export * from "./duplicate"; +export * from "./relates-to"; diff --git a/web/components/issues/sidebar-select/relates-to.tsx b/web/components/issues/sidebar-select/relates-to.tsx new file mode 100644 index 00000000000..5afeb8f788a --- /dev/null +++ b/web/components/issues/sidebar-select/relates-to.tsx @@ -0,0 +1,174 @@ +import React, { useState } from "react"; + +import { useRouter } from "next/router"; +// react-hook-form +import { UseFormWatch } from "react-hook-form"; +// hooks +import useToast from "hooks/use-toast"; +import useUser from "hooks/use-user"; +// icons +import { LocateFixed } from "lucide-react"; +// components +import { ExistingIssuesListModal } from "components/core"; +// services +import issuesService from "services/issues.service"; +// icons +import { XMarkIcon } from "@heroicons/react/24/outline"; +import { BlockerIcon } from "components/icons"; +// types +import { BlockeIssueDetail, IIssue, ISearchIssueResponse, UserAuth } from "types"; + +type Props = { + issueId?: string; + submitChanges: (formData: Partial) => void; + watch: UseFormWatch; + disabled?: boolean; +}; + +export const SidebarRelatesSelect: React.FC = (props) => { + const { issueId, submitChanges, watch, disabled = false } = props; + + const [isRelatesToModalOpen, setIsRelatesToModalOpen] = useState(false); + + const { user } = useUser(); + const { setToastAlert } = useToast(); + + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + const handleClose = () => { + setIsRelatesToModalOpen(false); + }; + + const onSubmit = async (data: ISearchIssueResponse[]) => { + if (data.length === 0) { + setToastAlert({ + type: "error", + title: "Error!", + message: "Please select at least one issue.", + }); + + return; + } + + const selectedIssues: { blocker_issue_detail: BlockeIssueDetail }[] = data.map((i) => ({ + blocker_issue_detail: { + id: i.id, + name: i.name, + sequence_id: i.sequence_id, + project_detail: { + id: i.project_id, + identifier: i.project__identifier, + name: i.project__name, + }, + }, + })); + + if (!user) return; + + issuesService + .createIssueRelation(workspaceSlug as string, projectId as string, issueId as string, user, { + related_list: [ + ...selectedIssues.map((issue) => ({ + issue: issueId as string, + related_issue_detail: issue.blocker_issue_detail, + related_issue: issue.blocker_issue_detail.id, + relation_type: "relates_to" as const, + })), + ], + }) + .then((response) => { + submitChanges({ + related_issues: [...watch("related_issues"), ...response.related_list], + }); + }); + + handleClose(); + }; + + const relatedToIssueRelation = [ + ...(watch("related_issues")?.filter((i) => i.relation_type === "relates_to") ?? []), + ...(watch("issue_relations") ?? []) + ?.filter((i) => i.relation_type === "relates_to") + .map((i) => ({ + ...i, + related_issue_detail: i.issue_detail, + related_issue: i.issue_detail?.id, + })), + ]; + + return ( + <> + setIsRelatesToModalOpen(false)} + searchParams={{ blocker_blocked_by: true, issue_id: issueId }} + handleOnSubmit={onSubmit} + workspaceLevelToggle + /> +
+
+ +

Relates to

+
+
+
+ {relatedToIssueRelation && relatedToIssueRelation.length > 0 + ? relatedToIssueRelation.map((relation) => ( +
+ + + {`${relation.related_issue_detail?.project_detail.identifier}-${relation.related_issue_detail?.sequence_id}`} + + +
+ )) + : null} +
+ +
+
+ + ); +}; diff --git a/web/components/issues/sidebar.tsx b/web/components/issues/sidebar.tsx index a33d17705e0..1f48f730792 100644 --- a/web/components/issues/sidebar.tsx +++ b/web/components/issues/sidebar.tsx @@ -30,6 +30,8 @@ import { SidebarStateSelect, SidebarEstimateSelect, SidebarLabelSelect, + SidebarDuplicateSelect, + SidebarRelatesSelect, } from "components/issues"; // ui import { CustomDatePicker, Icon } from "components/ui"; @@ -76,6 +78,8 @@ type Props = { | "delete" | "all" | "subscribe" + | "duplicate" + | "relates_to" )[]; uneditable?: boolean; }; @@ -464,7 +468,19 @@ export const IssueDetailsSidebar: React.FC = ({ {(fieldsToShow.includes("all") || fieldsToShow.includes("blocker")) && ( { + mutate( + ISSUE_DETAILS(issueId as string), + (prevData) => { + if (!prevData) return prevData; + return { + ...prevData, + ...data, + }; + }, + false + ); + }} watch={watchIssue} disabled={memberRole.isGuest || memberRole.isViewer || uneditable} /> @@ -472,7 +488,59 @@ export const IssueDetailsSidebar: React.FC = ({ {(fieldsToShow.includes("all") || fieldsToShow.includes("blocked")) && ( { + mutate( + ISSUE_DETAILS(issueId as string), + (prevData) => { + if (!prevData) return prevData; + return { + ...prevData, + ...data, + }; + }, + false + ); + }} + watch={watchIssue} + disabled={memberRole.isGuest || memberRole.isViewer || uneditable} + /> + )} + {(fieldsToShow.includes("all") || fieldsToShow.includes("duplicate")) && ( + { + mutate( + ISSUE_DETAILS(issueId as string), + (prevData) => { + if (!prevData) return prevData; + return { + ...prevData, + ...data, + }; + }, + false + ); + }} + watch={watchIssue} + disabled={memberRole.isGuest || memberRole.isViewer || uneditable} + /> + )} + {(fieldsToShow.includes("all") || fieldsToShow.includes("relates_to")) && ( + { + mutate( + ISSUE_DETAILS(issueId as string), + (prevData) => { + if (!prevData) return prevData; + return { + ...prevData, + ...data, + }; + }, + false + ); + }} watch={watchIssue} disabled={memberRole.isGuest || memberRole.isViewer || uneditable} /> diff --git a/web/hooks/my-issues/use-my-issues-filter.tsx b/web/hooks/my-issues/use-my-issues-filter.tsx index 2de72918f2d..86bba49cc0b 100644 --- a/web/hooks/my-issues/use-my-issues-filter.tsx +++ b/web/hooks/my-issues/use-my-issues-filter.tsx @@ -174,19 +174,19 @@ const useMyIssuesFilters = (workspaceSlug: string | undefined) => { }, [myWorkspace, workspaceSlug]); const newProperties: Properties = { - assignee: myWorkspace?.view_props.properties.assignee ?? true, - start_date: myWorkspace?.view_props.properties.start_date ?? true, - due_date: myWorkspace?.view_props.properties.due_date ?? true, - key: myWorkspace?.view_props.properties.key ?? true, - labels: myWorkspace?.view_props.properties.labels ?? true, - priority: myWorkspace?.view_props.properties.priority ?? true, - state: myWorkspace?.view_props.properties.state ?? true, - sub_issue_count: myWorkspace?.view_props.properties.sub_issue_count ?? true, - attachment_count: myWorkspace?.view_props.properties.attachment_count ?? true, - link: myWorkspace?.view_props.properties.link ?? true, - estimate: myWorkspace?.view_props.properties.estimate ?? true, - created_on: myWorkspace?.view_props.properties.created_on ?? true, - updated_on: myWorkspace?.view_props.properties.updated_on ?? true, + assignee: myWorkspace?.view_props?.properties?.assignee ?? true, + start_date: myWorkspace?.view_props?.properties?.start_date ?? true, + due_date: myWorkspace?.view_props?.properties?.due_date ?? true, + key: myWorkspace?.view_props?.properties?.key ?? true, + labels: myWorkspace?.view_props?.properties?.labels ?? true, + priority: myWorkspace?.view_props?.properties?.priority ?? true, + state: myWorkspace?.view_props?.properties?.state ?? true, + sub_issue_count: myWorkspace?.view_props?.properties?.sub_issue_count ?? true, + attachment_count: myWorkspace?.view_props?.properties?.attachment_count ?? true, + link: myWorkspace?.view_props?.properties?.link ?? true, + estimate: myWorkspace?.view_props?.properties?.estimate ?? true, + created_on: myWorkspace?.view_props?.properties?.created_on ?? true, + updated_on: myWorkspace?.view_props?.properties?.updated_on ?? true, }; return { diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx index c40f936de9f..d686fc3573c 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx @@ -86,8 +86,8 @@ const IssueDetailsPage: NextPage = () => { ...formData, }; - delete payload.blocker_issues; - delete payload.blocked_issues; + delete payload.related_issues; + delete payload.issue_relations; await issuesService .patchIssue(workspaceSlug as string, projectId as string, issueId as string, payload, user) diff --git a/web/services/issues.service.ts b/web/services/issues.service.ts index af5d722e3bf..86b1bb4edc0 100644 --- a/web/services/issues.service.ts +++ b/web/services/issues.service.ts @@ -150,6 +150,52 @@ class ProjectIssuesServices extends APIService { }); } + async createIssueRelation( + workspaceSlug: string, + projectId: string, + issueId: string, + user: ICurrentUserResponse, + data: { + related_list: Array<{ + relation_type: "duplicate" | "relates_to" | "blocked_by"; + related_issue: string; + }>; + } + ) { + return this.post( + `/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-relation/`, + data + ) + .then((response) => { + if (trackEvent) + trackEventServices.trackIssueRelationEvent(response.data, "ISSUE_RELATION_CREATE", user); + return response?.data; + }) + .catch((error) => { + throw error?.response; + }); + } + + async deleteIssueRelation( + workspaceSlug: string, + projectId: string, + issueId: string, + relationId: string, + user: ICurrentUserResponse + ) { + return this.delete( + `/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-relation/${relationId}/` + ) + .then((response) => { + if (trackEvent) + trackEventServices.trackIssueRelationEvent(response.data, "ISSUE_RELATION_DELETE", user); + return response?.data; + }) + .catch((error) => { + throw error?.response; + }); + } + async createIssueProperties(workspaceSlug: string, projectId: string, data: any): Promise { return this.post( `/api/workspaces/${workspaceSlug}/projects/${projectId}/issue-properties/`, diff --git a/web/services/track-event.service.ts b/web/services/track-event.service.ts index c59242f5083..30a74798b50 100644 --- a/web/services/track-event.service.ts +++ b/web/services/track-event.service.ts @@ -328,6 +328,22 @@ class TrackEventServices extends APIService { }); } + async trackIssueRelationEvent( + data: any, + eventName: "ISSUE_RELATION_CREATE" | "ISSUE_RELATION_DELETE", + user: ICurrentUserResponse + ): Promise { + return this.request({ + url: "/api/track-event", + method: "POST", + data: { + eventName, + extra: data, + user: user, + }, + }); + } + async trackIssueMovedToCycleOrModuleEvent( data: any, eventName: diff --git a/web/types/issues.d.ts b/web/types/issues.d.ts index cdc0a391ab2..5c3fea37f13 100644 --- a/web/types/issues.d.ts +++ b/web/types/issues.d.ts @@ -66,6 +66,15 @@ export interface linkDetails { url: string; } +export type IssueRelationType = "duplicate" | "relates_to" | "blocked_by"; + +export interface IssueRelation { + id: string; + issue: string; + related_issue: string; + relation_type: IssueRelationType; +} + export interface IIssue { archived_at: string; assignees: string[]; @@ -73,10 +82,20 @@ export interface IIssue { assignees_list: string[]; attachment_count: number; attachments: any[]; - blocked_issues: { blocked_issue_detail?: BlockeIssueDetail }[]; - blocker_issues: { blocker_issue_detail?: BlockeIssueDetail }[]; - blockers_list: string[]; - blocks_list: string[]; + issue_relations: { + id: string; + issue: string; + issue_detail: BlockeIssueDetail; + relation_type: IssueRelationType; + related_issue: string; + }[]; + related_issues: { + id: string; + issue: string; + related_issue_detail: BlockeIssueDetail; + relation_type: IssueRelationType; + related_issue: string; + }[]; bridge_id?: string | null; completed_at: Date; created_at: string; From 51caac8c8fd4f879242a7c2468b5529999ea5538 Mon Sep 17 00:00:00 2001 From: dakshesh14 Date: Wed, 13 Sep 2023 16:36:47 +0530 Subject: [PATCH 2/6] fix: search params to filter out selected issue --- .../issues/sidebar-select/blocked.tsx | 2 +- .../issues/sidebar-select/blocker.tsx | 18 +++++++++++------- .../issues/sidebar-select/duplicate.tsx | 4 ++-- .../issues/sidebar-select/relates-to.tsx | 4 ++-- web/types/projects.d.ts | 2 +- 5 files changed, 17 insertions(+), 13 deletions(-) diff --git a/web/components/issues/sidebar-select/blocked.tsx b/web/components/issues/sidebar-select/blocked.tsx index 21d5f3d71f2..9554a83bae7 100644 --- a/web/components/issues/sidebar-select/blocked.tsx +++ b/web/components/issues/sidebar-select/blocked.tsx @@ -97,7 +97,7 @@ export const SidebarBlockedSelect: React.FC = ({ setIsBlockedModalOpen(false)} - searchParams={{ blocker_blocked_by: true, issue_id: issueId }} + searchParams={{ issue_relation: true, issue_id: issueId }} handleOnSubmit={onSubmit} workspaceLevelToggle /> diff --git a/web/components/issues/sidebar-select/blocker.tsx b/web/components/issues/sidebar-select/blocker.tsx index 41836c9e0bf..5b8fd374d2a 100644 --- a/web/components/issues/sidebar-select/blocker.tsx +++ b/web/components/issues/sidebar-select/blocker.tsx @@ -84,23 +84,27 @@ export const SidebarBlockerSelect: React.FC = ({ }) .then((response) => { submitChanges({ - issue_relations: [...blockerIssue, ...response], + issue_relations: [ + ...blockerIssue, + ...(response ?? []).map((i: any) => ({ + id: i.id, + relation_type: i.relation_type, + issue_detail: i.related_issue_detail, + issue: i.related_issue, + })), + ], }); }); handleClose(); }; - console.log(watch(), "watch"); - - console.log(blockerIssue); - return ( <> setIsBlockerModalOpen(false)} - searchParams={{ blocker_blocked_by: true, issue_id: issueId }} + searchParams={{ issue_relation: true, issue_id: issueId }} handleOnSubmit={onSubmit} workspaceLevelToggle /> @@ -143,7 +147,7 @@ export const SidebarBlockerSelect: React.FC = ({ issuesService.deleteIssueRelation( workspaceSlug as string, projectId as string, - issueId as string, + relation.issue_detail?.id as string, relation.id, user ); diff --git a/web/components/issues/sidebar-select/duplicate.tsx b/web/components/issues/sidebar-select/duplicate.tsx index 6787529547a..66343928fff 100644 --- a/web/components/issues/sidebar-select/duplicate.tsx +++ b/web/components/issues/sidebar-select/duplicate.tsx @@ -79,7 +79,7 @@ export const SidebarDuplicateSelect: React.FC = (props) => { }) .then((response) => { submitChanges({ - related_issues: [...watch("related_issues"), ...response.related_list], + related_issues: [...watch("related_issues"), ...(response ?? [])], }); }); @@ -102,7 +102,7 @@ export const SidebarDuplicateSelect: React.FC = (props) => { setIsDuplicateModalOpen(false)} - searchParams={{ blocker_blocked_by: true, issue_id: issueId }} + searchParams={{ issue_relation: true, issue_id: issueId }} handleOnSubmit={onSubmit} workspaceLevelToggle /> diff --git a/web/components/issues/sidebar-select/relates-to.tsx b/web/components/issues/sidebar-select/relates-to.tsx index 5afeb8f788a..7f39b5aaee5 100644 --- a/web/components/issues/sidebar-select/relates-to.tsx +++ b/web/components/issues/sidebar-select/relates-to.tsx @@ -79,7 +79,7 @@ export const SidebarRelatesSelect: React.FC = (props) => { }) .then((response) => { submitChanges({ - related_issues: [...watch("related_issues"), ...response.related_list], + related_issues: [...watch("related_issues"), ...(response ?? [])], }); }); @@ -102,7 +102,7 @@ export const SidebarRelatesSelect: React.FC = (props) => { setIsRelatesToModalOpen(false)} - searchParams={{ blocker_blocked_by: true, issue_id: issueId }} + searchParams={{ issue_relation: true, issue_id: issueId }} handleOnSubmit={onSubmit} workspaceLevelToggle /> diff --git a/web/types/projects.d.ts b/web/types/projects.d.ts index 590fe5023e2..08b772b87f9 100644 --- a/web/types/projects.d.ts +++ b/web/types/projects.d.ts @@ -137,7 +137,7 @@ export interface GithubRepositoriesResponse { export type TProjectIssuesSearchParams = { search: string; parent?: boolean; - blocker_blocked_by?: boolean; + issue_relation?: boolean; cycle?: boolean; module?: boolean; sub_issue?: boolean; From df8e29b28ec8a63fb7183b564ab40940f45cda8a Mon Sep 17 00:00:00 2001 From: dakshesh14 Date: Wed, 13 Sep 2023 17:58:17 +0530 Subject: [PATCH 3/6] style: changed icons --- web/components/icons/index.ts | 1 + web/components/icons/related-icon.tsx | 41 +++++++++++++++++++ .../issues/sidebar-select/duplicate.tsx | 12 +++--- .../issues/sidebar-select/relates-to.tsx | 12 +++--- 4 files changed, 52 insertions(+), 14 deletions(-) create mode 100644 web/components/icons/related-icon.tsx diff --git a/web/components/icons/index.ts b/web/components/icons/index.ts index d3be7f2a8a0..ab661a09249 100644 --- a/web/components/icons/index.ts +++ b/web/components/icons/index.ts @@ -83,3 +83,4 @@ export * from "./archive-icon"; export * from "./clock-icon"; export * from "./bell-icon"; export * from "./single-comment-icon"; +export * from "./related-icon"; diff --git a/web/components/icons/related-icon.tsx b/web/components/icons/related-icon.tsx new file mode 100644 index 00000000000..3abb4b1c3dc --- /dev/null +++ b/web/components/icons/related-icon.tsx @@ -0,0 +1,41 @@ +import React from "react"; + +import type { Props } from "./types"; + +export const RelatedIcon: React.FC = ({ + width = "24", + height = "24", + color = "rgb(var(--color-text-200))", + className, +}) => ( + + + + + +); diff --git a/web/components/issues/sidebar-select/duplicate.tsx b/web/components/issues/sidebar-select/duplicate.tsx index 66343928fff..d085dd87b3f 100644 --- a/web/components/issues/sidebar-select/duplicate.tsx +++ b/web/components/issues/sidebar-select/duplicate.tsx @@ -7,16 +7,14 @@ import { UseFormWatch } from "react-hook-form"; import useToast from "hooks/use-toast"; import useUser from "hooks/use-user"; // icons -import { Copy } from "lucide-react"; +import { X, CopyPlus } from "lucide-react"; // components +import { BlockerIcon } from "components/icons"; import { ExistingIssuesListModal } from "components/core"; // services import issuesService from "services/issues.service"; -// icons -import { XMarkIcon } from "@heroicons/react/24/outline"; -import { BlockerIcon } from "components/icons"; // types -import { BlockeIssueDetail, IIssue, ISearchIssueResponse, UserAuth } from "types"; +import { BlockeIssueDetail, IIssue, ISearchIssueResponse } from "types"; type Props = { issueId?: string; @@ -108,7 +106,7 @@ export const SidebarDuplicateSelect: React.FC = (props) => { />
- +

Duplicate

@@ -151,7 +149,7 @@ export const SidebarDuplicateSelect: React.FC = (props) => { ); }} > - +
)) diff --git a/web/components/issues/sidebar-select/relates-to.tsx b/web/components/issues/sidebar-select/relates-to.tsx index 7f39b5aaee5..fb878daee82 100644 --- a/web/components/issues/sidebar-select/relates-to.tsx +++ b/web/components/issues/sidebar-select/relates-to.tsx @@ -7,16 +7,14 @@ import { UseFormWatch } from "react-hook-form"; import useToast from "hooks/use-toast"; import useUser from "hooks/use-user"; // icons -import { LocateFixed } from "lucide-react"; +import { X } from "lucide-react"; +import { BlockerIcon, RelatedIcon } from "components/icons"; // components import { ExistingIssuesListModal } from "components/core"; // services import issuesService from "services/issues.service"; -// icons -import { XMarkIcon } from "@heroicons/react/24/outline"; -import { BlockerIcon } from "components/icons"; // types -import { BlockeIssueDetail, IIssue, ISearchIssueResponse, UserAuth } from "types"; +import { BlockeIssueDetail, IIssue, ISearchIssueResponse } from "types"; type Props = { issueId?: string; @@ -108,7 +106,7 @@ export const SidebarRelatesSelect: React.FC = (props) => { />
- +

Relates to

@@ -151,7 +149,7 @@ export const SidebarRelatesSelect: React.FC = (props) => { ); }} > - +
)) From 2a6aeaebab86b9c9e4c454e1b22ca70eda014ef9 Mon Sep 17 00:00:00 2001 From: dakshesh14 Date: Wed, 13 Sep 2023 18:49:01 +0530 Subject: [PATCH 4/6] fix: build error on web-view --- web/components/issues/confirm-form-close.tsx | 140 +++++++++++++ .../web-view/issue-properties-detail.tsx | 192 +++++++++++++----- web/components/web-view/select-blocked.tsx | 2 +- web/components/web-view/select-blocker.tsx | 2 +- 4 files changed, 283 insertions(+), 53 deletions(-) create mode 100644 web/components/issues/confirm-form-close.tsx diff --git a/web/components/issues/confirm-form-close.tsx b/web/components/issues/confirm-form-close.tsx new file mode 100644 index 00000000000..4444d5b1d36 --- /dev/null +++ b/web/components/issues/confirm-form-close.tsx @@ -0,0 +1,140 @@ +import React, { useState } from "react"; + +import { useRouter } from "next/router"; + +import { mutate } from "swr"; + +// headless ui +import { Dialog, Transition } from "@headlessui/react"; +// services +import viewsService from "services/views.service"; +// hooks +import useToast from "hooks/use-toast"; +// ui +import { DangerButton, SecondaryButton } from "components/ui"; +// icons +import { AlertTriangle } from "lucide-react"; +// types +import type { ICurrentUserResponse, IView } from "types"; +// fetch-keys +import { VIEWS_LIST } from "constants/fetch-keys"; + +type Props = { + isOpen: boolean; + handleClose: () => void; + onDiscard: () => void; + onSaveDraft: () => void; +}; + +export const DeleteViewModal: React.FC = (props) => { + const { isOpen, handleClose, onDiscard, onSaveDraft } = props; + + const [isDeleteLoading, setIsDeleteLoading] = useState(false); + + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + const { setToastAlert } = useToast(); + + const onClose = () => { + handleClose(); + setIsDeleteLoading(false); + }; + + const handleDeletion = async () => { + setIsDeleteLoading(true); + if (!workspaceSlug || !data || !projectId) return; + + await viewsService + .deleteView(workspaceSlug as string, projectId as string, data.id, user) + .then(() => { + mutate(VIEWS_LIST(projectId as string), (views) => + views?.filter((view) => view.id !== data.id) + ); + + handleClose(); + + setToastAlert({ + type: "success", + title: "Success!", + message: "View deleted successfully.", + }); + }) + .catch(() => { + setToastAlert({ + type: "error", + title: "Error!", + message: "View could not be deleted. Please try again.", + }); + }) + .finally(() => { + setIsDeleteLoading(false); + }); + }; + + return ( + + + +
+ + +
+
+ + +
+
+
+
+
+ + Delete View + +
+

+ Are you sure you want to delete view-{" "} + + {data?.name} + + ? All of the data related to the view will be permanently removed. This + action cannot be undone. +

+
+
+
+
+
+ Cancel + + {isDeleteLoading ? "Deleting..." : "Delete"} + +
+
+
+
+
+
+
+ ); +}; diff --git a/web/components/web-view/issue-properties-detail.tsx b/web/components/web-view/issue-properties-detail.tsx index 2fe0356f57c..089f8950f75 100644 --- a/web/components/web-view/issue-properties-detail.tsx +++ b/web/components/web-view/issue-properties-detail.tsx @@ -4,9 +4,21 @@ import React, { useState } from "react"; // next import { useRouter } from "next/router"; +// swr +import { mutate } from "swr"; + // react hook forms import { Control, Controller, useWatch } from "react-hook-form"; +// services +import issuesService from "services/issues.service"; + +// hooks +import useUser from "hooks/use-user"; + +// fetch keys +import { ISSUE_DETAILS } from "constants/fetch-keys"; + // icons import { BlockedIcon, BlockerIcon } from "components/icons"; import { ChevronDown, PlayIcon, User, X, CalendarDays, LayoutGrid, Users } from "lucide-react"; @@ -26,6 +38,7 @@ import { EstimateSelect, ParentSelect, BlockerSelect, + BlockedSelect, } from "components/web-view"; // types @@ -39,15 +52,16 @@ type Props = { export const IssuePropertiesDetail: React.FC = (props) => { const { control, submitChanges } = props; - const blockerIssue = useWatch({ - control, - name: "blocker_issues", - }); + const blockerIssue = + useWatch({ + control, + name: "issue_relations", + })?.filter((i) => i.relation_type === "blocked_by") || []; const blockedIssue = useWatch({ control, - name: "blocked_issues", - }); + name: "related_issues", + })?.filter((i) => i.relation_type === "blocked_by"); const startDate = useWatch({ control, @@ -55,12 +69,28 @@ export const IssuePropertiesDetail: React.FC = (props) => { }); const router = useRouter(); - const { workspaceSlug } = router.query; + const { workspaceSlug, projectId, issueId } = router.query; + + const { user } = useUser(); const [isViewAllOpen, setIsViewAllOpen] = useState(false); const { isEstimateActive } = useEstimateOption(); + const handleMutation = (data: any) => { + mutate( + ISSUE_DETAILS(issueId as string), + (prevData) => { + if (!prevData) return prevData; + return { + ...prevData, + ...data, + }; + }, + false + ); + }; + return (
@@ -188,51 +218,80 @@ export const IssuePropertiesDetail: React.FC = (props) => { Blocking
- ( - - submitChanges({ - blocker_issues: val, - blockers_list: val?.map((i: any) => i.blocker_issue_detail?.id ?? ""), - }) - } - /> - )} + { + if (!user || !workspaceSlug || !projectId || !issueId) return; + + issuesService + .createIssueRelation( + workspaceSlug as string, + projectId as string, + issueId as string, + user, + { + related_list: [ + ...val.map((issue: any) => ({ + issue: issue.blocker_issue_detail.id, + relation_type: "blocked_by" as const, + related_issue: issueId as string, + related_issue_detail: issue.blocker_issue_detail, + })), + ], + } + ) + .then((response) => { + handleMutation({ + issue_relations: [ + ...blockerIssue, + ...(response ?? []).map((i: any) => ({ + id: i.id, + relation_type: i.relation_type, + issue_detail: i.related_issue_detail, + issue: i.related_issue, + })), + ], + }); + }); + }} />
{blockerIssue && blockerIssue.map((issue) => (
- {`${issue.blocker_issue_detail?.project_detail.identifier}-${issue.blocker_issue_detail?.sequence_id}`} + {`${issue.issue_detail?.project_detail.identifier}-${issue.issue_detail?.sequence_id}`}
- ( - - submitChanges({ - blocked_issues: val, - blocks_list: val?.map((i: any) => i.blocker_issue_detail?.id ?? ""), - }) - } - /> - )} + { + if (!user || !workspaceSlug || !projectId || !issueId) return; + + issuesService + .createIssueRelation( + workspaceSlug as string, + projectId as string, + issueId as string, + user, + { + related_list: [ + ...val.map((issue: any) => ({ + issue: issue.blocked_issue_detail.id, + relation_type: "blocked_by" as const, + related_issue: issueId as string, + related_issue_detail: issue.blocked_issue_detail, + })), + ], + } + ) + .then((response) => { + handleMutation({ + related_issues: [ + ...blockedIssue, + ...(response ?? []).map((i: any) => ({ + id: i.id, + relation_type: i.relation_type, + issue_detail: i.related_issue_detail, + issue: i.related_issue, + })), + ], + }); + }); + }} />
{blockedIssue && blockedIssue.map((issue) => (
- {`${issue?.blocked_issue_detail?.project_detail?.identifier}-${issue?.blocked_issue_detail?.sequence_id}`} + {`${issue?.related_issue_detail?.project_detail?.identifier}-${issue?.related_issue_detail?.sequence_id}`}