fix: activity not coming for blocking/blocked, 'related to' and duplicate (#2189)

* fix: activity not coming for duplicate, relatesd to and for blocked/blocking

refactor: mutation logic to use relation id instead of issue id

* fix: mutation logic and changed keys to be aligned with api

* fix: build error
This commit is contained in:
Dakshesh Jain 2023-09-19 12:58:00 +05:30 committed by GitHub
parent 79bf7d4c0c
commit f6b92fc953
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 142 additions and 98 deletions

View File

@ -2,8 +2,9 @@ import { useRouter } from "next/router";
// icons // icons
import { Icon, Tooltip } from "components/ui"; import { Icon, Tooltip } from "components/ui";
import { CopyPlus } from "lucide-react";
import { Squares2X2Icon } from "@heroicons/react/24/outline"; import { Squares2X2Icon } from "@heroicons/react/24/outline";
import { BlockedIcon, BlockerIcon } from "components/icons"; import { BlockedIcon, BlockerIcon, RelatedIcon } from "components/icons";
// helpers // helpers
import { renderShortDateWithYearFormat } from "helpers/date-time.helper"; import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
import { capitalizeFirstLetter } from "helpers/string.helper"; import { capitalizeFirstLetter } from "helpers/string.helper";
@ -157,7 +158,7 @@ const activityDetails: {
}, },
icon: <BlockerIcon height="12" width="12" color="#6b7280" />, icon: <BlockerIcon height="12" width="12" color="#6b7280" />,
}, },
blocks: { blocked_by: {
message: (activity) => { message: (activity) => {
if (activity.old_value === "") if (activity.old_value === "")
return ( return (
@ -176,6 +177,44 @@ const activityDetails: {
}, },
icon: <BlockedIcon height="12" width="12" color="#6b7280" />, icon: <BlockedIcon height="12" width="12" color="#6b7280" />,
}, },
duplicate: {
message: (activity) => {
if (activity.old_value === "")
return (
<>
marked this issue as duplicate of{" "}
<span className="font-medium text-custom-text-100">{activity.new_value}</span>.
</>
);
else
return (
<>
removed this issue as a duplicate of{" "}
<span className="font-medium text-custom-text-100">{activity.old_value}</span>.
</>
);
},
icon: <CopyPlus size={12} color="#6b7280" />,
},
relates_to: {
message: (activity) => {
if (activity.old_value === "")
return (
<>
marked that this issue relates to{" "}
<span className="font-medium text-custom-text-100">{activity.new_value}</span>.
</>
);
else
return (
<>
removed the relation from{" "}
<span className="font-medium text-custom-text-100">{activity.old_value}</span>.
</>
);
},
icon: <RelatedIcon height="12" width="12" color="#6b7280" />,
},
cycles: { cycles: {
message: (activity, showIssue, workspaceSlug) => { message: (activity, showIssue, workspaceSlug) => {
if (activity.verb === "created") if (activity.verb === "created")

View File

@ -73,7 +73,7 @@ export const SidebarBlockedSelect: React.FC<Props> = ({
...selectedIssues.map((issue) => ({ ...selectedIssues.map((issue) => ({
issue: issueId as string, issue: issueId as string,
relation_type: "blocked_by" as const, relation_type: "blocked_by" as const,
related_issue_detail: issue.blocked_issue_detail, issue_detail: issue.blocked_issue_detail,
related_issue: issue.blocked_issue_detail.id, related_issue: issue.blocked_issue_detail.id,
})), })),
], ],
@ -111,17 +111,17 @@ export const SidebarBlockedSelect: React.FC<Props> = ({
{blockedByIssue && blockedByIssue.length > 0 {blockedByIssue && blockedByIssue.length > 0
? blockedByIssue.map((relation) => ( ? blockedByIssue.map((relation) => (
<div <div
key={relation.related_issue_detail?.id} key={relation?.id}
className="group flex cursor-pointer items-center gap-1 rounded-2xl border border-custom-border-200 px-1.5 py-0.5 text-xs text-red-500 duration-300 hover:border-red-500/20 hover:bg-red-500/20" className="group flex cursor-pointer items-center gap-1 rounded-2xl border border-custom-border-200 px-1.5 py-0.5 text-xs text-red-500 duration-300 hover:border-red-500/20 hover:bg-red-500/20"
> >
<a <a
href={`/${workspaceSlug}/projects/${relation.related_issue_detail?.project_detail.id}/issues/${relation.related_issue_detail?.id}`} href={`/${workspaceSlug}/projects/${relation.issue_detail?.project_detail.id}/issues/${relation.issue_detail?.id}`}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="flex items-center gap-1" className="flex items-center gap-1"
> >
<BlockedIcon height={10} width={10} /> <BlockedIcon height={10} width={10} />
{`${relation.related_issue_detail?.project_detail.identifier}-${relation.related_issue_detail?.sequence_id}`} {`${relation.issue_detail?.project_detail.identifier}-${relation.issue_detail?.sequence_id}`}
</a> </a>
<button <button
type="button" type="button"

View File

@ -81,6 +81,7 @@ export const SidebarBlockerSelect: React.FC<Props> = ({
related_issue_detail: issue.blocker_issue_detail, related_issue_detail: issue.blocker_issue_detail,
})), })),
], ],
relation: "blocking",
}) })
.then((response) => { .then((response) => {
submitChanges({ submitChanges({
@ -89,7 +90,7 @@ export const SidebarBlockerSelect: React.FC<Props> = ({
...(response ?? []).map((i: any) => ({ ...(response ?? []).map((i: any) => ({
id: i.id, id: i.id,
relation_type: i.relation_type, relation_type: i.relation_type,
issue_detail: i.related_issue_detail, issue_detail: i.issue_detail,
issue: i.related_issue, issue: i.related_issue,
})), })),
], ],
@ -118,7 +119,7 @@ export const SidebarBlockerSelect: React.FC<Props> = ({
{blockerIssue && blockerIssue.length > 0 {blockerIssue && blockerIssue.length > 0
? blockerIssue.map((relation) => ( ? blockerIssue.map((relation) => (
<div <div
key={relation.issue_detail?.id} key={relation.id}
className="group flex cursor-pointer items-center gap-1 rounded-2xl border border-custom-border-200 px-1.5 py-0.5 text-xs text-yellow-500 duration-300 hover:border-yellow-500/20 hover:bg-yellow-500/20" className="group flex cursor-pointer items-center gap-1 rounded-2xl border border-custom-border-200 px-1.5 py-0.5 text-xs text-yellow-500 duration-300 hover:border-yellow-500/20 hover:bg-yellow-500/20"
> >
<a <a
@ -134,9 +135,7 @@ export const SidebarBlockerSelect: React.FC<Props> = ({
type="button" type="button"
className="opacity-0 duration-300 group-hover:opacity-100" className="opacity-0 duration-300 group-hover:opacity-100"
onClick={() => { onClick={() => {
const updatedBlockers = blockerIssue.filter( const updatedBlockers = blockerIssue.filter((i) => i.id !== relation.id);
(i) => i.issue_detail?.id !== relation.issue_detail?.id
);
submitChanges({ submitChanges({
issue_relations: updatedBlockers, issue_relations: updatedBlockers,

View File

@ -18,7 +18,7 @@ import { BlockeIssueDetail, IIssue, ISearchIssueResponse } from "types";
type Props = { type Props = {
issueId?: string; issueId?: string;
submitChanges: (formData: Partial<IIssue>) => void; submitChanges: (formData?: Partial<IIssue>) => void;
watch: UseFormWatch<IIssue>; watch: UseFormWatch<IIssue>;
disabled?: boolean; disabled?: boolean;
}; };
@ -69,7 +69,7 @@ export const SidebarDuplicateSelect: React.FC<Props> = (props) => {
related_list: [ related_list: [
...selectedIssues.map((issue) => ({ ...selectedIssues.map((issue) => ({
issue: issueId as string, issue: issueId as string,
related_issue_detail: issue.blocker_issue_detail, issue_detail: issue.blocker_issue_detail,
related_issue: issue.blocker_issue_detail.id, related_issue: issue.blocker_issue_detail.id,
relation_type: "duplicate" as const, relation_type: "duplicate" as const,
})), })),
@ -90,7 +90,7 @@ export const SidebarDuplicateSelect: React.FC<Props> = (props) => {
?.filter((i) => i.relation_type === "duplicate") ?.filter((i) => i.relation_type === "duplicate")
.map((i) => ({ .map((i) => ({
...i, ...i,
related_issue_detail: i.issue_detail, issue_detail: i.issue_detail,
related_issue: i.issue_detail?.id, related_issue: i.issue_detail?.id,
})), })),
]; ];
@ -114,39 +114,35 @@ export const SidebarDuplicateSelect: React.FC<Props> = (props) => {
{duplicateIssuesRelation && duplicateIssuesRelation.length > 0 {duplicateIssuesRelation && duplicateIssuesRelation.length > 0
? duplicateIssuesRelation.map((relation) => ( ? duplicateIssuesRelation.map((relation) => (
<div <div
key={relation.related_issue_detail?.id} key={relation.issue_detail?.id}
className="group flex cursor-pointer items-center gap-1 rounded-2xl border border-custom-border-200 px-1.5 py-0.5 text-xs text-yellow-500 duration-300 hover:border-yellow-500/20 hover:bg-yellow-500/20" className="group flex cursor-pointer items-center gap-1 rounded-2xl border border-custom-border-200 px-1.5 py-0.5 text-xs text-yellow-500 duration-300 hover:border-yellow-500/20 hover:bg-yellow-500/20"
> >
<a <a
href={`/${workspaceSlug}/projects/${relation.related_issue_detail?.project_detail.id}/issues/${relation.related_issue_detail?.id}`} href={`/${workspaceSlug}/projects/${relation.issue_detail?.project_detail.id}/issues/${relation.issue_detail?.id}`}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="flex items-center gap-1" className="flex items-center gap-1"
> >
<BlockerIcon height={10} width={10} /> <BlockerIcon height={10} width={10} />
{`${relation.related_issue_detail?.project_detail.identifier}-${relation.related_issue_detail?.sequence_id}`} {`${relation.issue_detail?.project_detail.identifier}-${relation.issue_detail?.sequence_id}`}
</a> </a>
<button <button
type="button" type="button"
className="opacity-0 duration-300 group-hover:opacity-100" className="opacity-0 duration-300 group-hover:opacity-100"
onClick={() => { onClick={() => {
const updatedBlockers = duplicateIssuesRelation.filter(
(i) => i.related_issue_detail?.id !== relation.related_issue_detail?.id
);
submitChanges({
related_issues: updatedBlockers,
});
if (!user) return; if (!user) return;
issuesService.deleteIssueRelation( issuesService
.deleteIssueRelation(
workspaceSlug as string, workspaceSlug as string,
projectId as string, projectId as string,
issueId as string, issueId as string,
relation.id, relation.id,
user user
); )
.then(() => {
submitChanges();
});
}} }}
> >
<X className="h-2 w-2" /> <X className="h-2 w-2" />

View File

@ -18,7 +18,7 @@ import { BlockeIssueDetail, IIssue, ISearchIssueResponse } from "types";
type Props = { type Props = {
issueId?: string; issueId?: string;
submitChanges: (formData: Partial<IIssue>) => void; submitChanges: (formData?: Partial<IIssue>) => void;
watch: UseFormWatch<IIssue>; watch: UseFormWatch<IIssue>;
disabled?: boolean; disabled?: boolean;
}; };
@ -69,7 +69,7 @@ export const SidebarRelatesSelect: React.FC<Props> = (props) => {
related_list: [ related_list: [
...selectedIssues.map((issue) => ({ ...selectedIssues.map((issue) => ({
issue: issueId as string, issue: issueId as string,
related_issue_detail: issue.blocker_issue_detail, issue_detail: issue.blocker_issue_detail,
related_issue: issue.blocker_issue_detail.id, related_issue: issue.blocker_issue_detail.id,
relation_type: "relates_to" as const, relation_type: "relates_to" as const,
})), })),
@ -90,7 +90,7 @@ export const SidebarRelatesSelect: React.FC<Props> = (props) => {
?.filter((i) => i.relation_type === "relates_to") ?.filter((i) => i.relation_type === "relates_to")
.map((i) => ({ .map((i) => ({
...i, ...i,
related_issue_detail: i.issue_detail, issue_detail: i.issue_detail,
related_issue: i.issue_detail?.id, related_issue: i.issue_detail?.id,
})), })),
]; ];
@ -114,39 +114,35 @@ export const SidebarRelatesSelect: React.FC<Props> = (props) => {
{relatedToIssueRelation && relatedToIssueRelation.length > 0 {relatedToIssueRelation && relatedToIssueRelation.length > 0
? relatedToIssueRelation.map((relation) => ( ? relatedToIssueRelation.map((relation) => (
<div <div
key={relation.related_issue_detail?.id} key={relation.issue_detail?.id}
className="group flex cursor-pointer items-center gap-1 rounded-2xl border border-custom-border-200 px-1.5 py-0.5 text-xs text-yellow-500 duration-300 hover:border-yellow-500/20 hover:bg-yellow-500/20" className="group flex cursor-pointer items-center gap-1 rounded-2xl border border-custom-border-200 px-1.5 py-0.5 text-xs text-yellow-500 duration-300 hover:border-yellow-500/20 hover:bg-yellow-500/20"
> >
<a <a
href={`/${workspaceSlug}/projects/${relation.related_issue_detail?.project_detail.id}/issues/${relation.related_issue_detail?.id}`} href={`/${workspaceSlug}/projects/${relation.issue_detail?.project_detail.id}/issues/${relation.issue_detail?.id}`}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="flex items-center gap-1" className="flex items-center gap-1"
> >
<BlockerIcon height={10} width={10} /> <BlockerIcon height={10} width={10} />
{`${relation.related_issue_detail?.project_detail.identifier}-${relation.related_issue_detail?.sequence_id}`} {`${relation.issue_detail?.project_detail.identifier}-${relation.issue_detail?.sequence_id}`}
</a> </a>
<button <button
type="button" type="button"
className="opacity-0 duration-300 group-hover:opacity-100" className="opacity-0 duration-300 group-hover:opacity-100"
onClick={() => { onClick={() => {
const updatedBlockers = relatedToIssueRelation.filter(
(i) => i.related_issue_detail?.id !== relation.related_issue_detail?.id
);
submitChanges({
related_issues: updatedBlockers,
});
if (!user) return; if (!user) return;
issuesService.deleteIssueRelation( issuesService
.deleteIssueRelation(
workspaceSlug as string, workspaceSlug as string,
projectId as string, projectId as string,
issueId as string, issueId as string,
relation.id, relation.id,
user user
); )
.then(() => {
submitChanges();
});
}} }}
> >
<X className="h-2 w-2" /> <X className="h-2 w-2" />

View File

@ -509,17 +509,14 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
<SidebarDuplicateSelect <SidebarDuplicateSelect
issueId={issueId as string} issueId={issueId as string}
submitChanges={(data: any) => { submitChanges={(data: any) => {
mutate<IIssue>( if (!data) return mutate(ISSUE_DETAILS(issueId as string));
ISSUE_DETAILS(issueId as string), mutate<IIssue>(ISSUE_DETAILS(issueId as string), (prevData) => {
(prevData) => {
if (!prevData) return prevData; if (!prevData) return prevData;
return { return {
...prevData, ...prevData,
...data, ...data,
}; };
}, });
false
);
}} }}
watch={watchIssue} watch={watchIssue}
disabled={memberRole.isGuest || memberRole.isViewer || uneditable} disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
@ -529,17 +526,14 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
<SidebarRelatesSelect <SidebarRelatesSelect
issueId={issueId as string} issueId={issueId as string}
submitChanges={(data: any) => { submitChanges={(data: any) => {
mutate<IIssue>( if (!data) return mutate(ISSUE_DETAILS(issueId as string));
ISSUE_DETAILS(issueId as string), mutate<IIssue>(ISSUE_DETAILS(issueId as string), (prevData) => {
(prevData) => {
if (!prevData) return prevData; if (!prevData) return prevData;
return { return {
...prevData, ...prevData,
...data, ...data,
}; };
}, });
false
);
}} }}
watch={watchIssue} watch={watchIssue}
disabled={memberRole.isGuest || memberRole.isViewer || uneditable} disabled={memberRole.isGuest || memberRole.isViewer || uneditable}

View File

@ -1,9 +1,10 @@
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// icons // icons
import { CopyPlus } from "lucide-react";
import { Icon, Tooltip } from "components/ui"; import { Icon, Tooltip } from "components/ui";
import { Squares2X2Icon } from "@heroicons/react/24/outline"; import { Squares2X2Icon } from "@heroicons/react/24/outline";
import { BlockedIcon, BlockerIcon } from "components/icons"; import { BlockedIcon, BlockerIcon, RelatedIcon } from "components/icons";
// helpers // helpers
import { renderShortDateWithYearFormat } from "helpers/date-time.helper"; import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
import { capitalizeFirstLetter } from "helpers/string.helper"; import { capitalizeFirstLetter } from "helpers/string.helper";
@ -117,7 +118,7 @@ const activityDetails: {
icon: <BlockerIcon height="12" width="12" color="#6b7280" />, icon: <BlockerIcon height="12" width="12" color="#6b7280" />,
}, },
blocks: { blocked_by: {
message: (activity) => ( message: (activity) => (
<> <>
{activity.old_value === "" {activity.old_value === ""
@ -132,6 +133,36 @@ const activityDetails: {
icon: <BlockedIcon height="12" width="12" color="#6b7280" />, icon: <BlockedIcon height="12" width="12" color="#6b7280" />,
}, },
duplicate: {
message: (activity) => (
<>
{activity.old_value === ""
? "marked this issue as duplicate of "
: "removed this issue as a duplicate of "}
<span className="font-medium text-custom-text-100">
{activity.verb === "created" ? activity.new_value : activity.old_value}
</span>
.
</>
),
icon: <CopyPlus size={12} color="#6b7280" />,
},
relates_to: {
message: (activity) => (
<>
{activity.old_value === ""
? "marked that this issue relates to "
: "removed the relation from "}
<span className="font-medium text-custom-text-100">
{activity.old_value === "" ? activity.new_value : activity.old_value}
</span>
.
</>
),
icon: <RelatedIcon height="12" width="12" color="#6b7280" />,
},
cycles: { cycles: {
message: (activity) => ( message: (activity) => (
<> <>

View File

@ -351,25 +351,23 @@ export const IssuePropertiesDetail: React.FC<Props> = (props) => {
{blockedIssue && {blockedIssue &&
blockedIssue.map((issue) => ( blockedIssue.map((issue) => (
<div <div
key={issue.related_issue_detail?.id} key={issue.issue_detail?.id}
className="group inline-flex mr-1 cursor-pointer items-center gap-1 rounded-2xl border border-custom-border-200 px-1.5 py-0.5 text-xs text-red-500 duration-300 hover:border-red-500/20 hover:bg-red-500/20" className="group inline-flex mr-1 cursor-pointer items-center gap-1 rounded-2xl border border-custom-border-200 px-1.5 py-0.5 text-xs text-red-500 duration-300 hover:border-red-500/20 hover:bg-red-500/20"
> >
<a <a
href={`/${workspaceSlug}/projects/${issue.related_issue_detail?.project_detail.id}/issues/${issue.related_issue_detail?.id}`} href={`/${workspaceSlug}/projects/${issue.issue_detail?.project_detail.id}/issues/${issue.issue_detail?.id}`}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="flex items-center gap-1" className="flex items-center gap-1"
> >
<BlockedIcon height={10} width={10} /> <BlockedIcon height={10} width={10} />
{`${issue?.related_issue_detail?.project_detail?.identifier}-${issue?.related_issue_detail?.sequence_id}`} {`${issue?.issue_detail?.project_detail?.identifier}-${issue?.issue_detail?.sequence_id}`}
</a> </a>
<button <button
type="button" type="button"
className="duration-300" className="duration-300"
onClick={() => { onClick={() => {
const updatedBlocked = blockedIssue.filter( const updatedBlocked = blockedIssue.filter((i) => i?.id !== issue?.id);
(i) => i.related_issue_detail?.id !== issue.related_issue_detail?.id
);
if (!user) return; if (!user) return;

View File

@ -154,6 +154,7 @@ class ProjectIssuesServices extends APIService {
relation_type: "duplicate" | "relates_to" | "blocked_by"; relation_type: "duplicate" | "relates_to" | "blocked_by";
related_issue: string; related_issue: string;
}>; }>;
relation?: "blocking" | null;
} }
) { ) {
return this.post( return this.post(

20
web/types/issues.d.ts vendored
View File

@ -76,8 +76,10 @@ export type IssueRelationType = "duplicate" | "relates_to" | "blocked_by";
export interface IssueRelation { export interface IssueRelation {
id: string; id: string;
issue: string; issue: string;
related_issue: string; issue_detail: BlockeIssueDetail;
relation_type: IssueRelationType; relation_type: IssueRelationType;
related_issue: string;
relation: "blocking" | null;
} }
export interface IIssue { export interface IIssue {
@ -87,20 +89,8 @@ export interface IIssue {
assignees_list: string[]; assignees_list: string[];
attachment_count: number; attachment_count: number;
attachments: any[]; attachments: any[];
issue_relations: { issue_relations: IssueRelation[];
id: string; related_issues: IssueRelation[];
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; bridge_id?: string | null;
completed_at: Date; completed_at: Date;
created_at: string; created_at: string;