[WEB-3207] chore: add state_id, priority and assignee_ids to create issue relation response (#6448)
This commit is contained in:
parent
586a320d86
commit
0b53912295
5 changed files with 129 additions and 83 deletions
|
|
@ -283,10 +283,26 @@ class IssueRelationSerializer(BaseSerializer):
|
||||||
)
|
)
|
||||||
name = serializers.CharField(source="related_issue.name", read_only=True)
|
name = serializers.CharField(source="related_issue.name", read_only=True)
|
||||||
relation_type = serializers.CharField(read_only=True)
|
relation_type = serializers.CharField(read_only=True)
|
||||||
|
state_id = serializers.UUIDField(source="related_issue.state.id", read_only=True)
|
||||||
|
priority = serializers.CharField(source="related_issue.priority", read_only=True)
|
||||||
|
assignee_ids = serializers.ListField(
|
||||||
|
child=serializers.PrimaryKeyRelatedField(queryset=User.objects.all()),
|
||||||
|
write_only=True,
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IssueRelation
|
model = IssueRelation
|
||||||
fields = ["id", "project_id", "sequence_id", "relation_type", "name"]
|
fields = [
|
||||||
|
"id",
|
||||||
|
"project_id",
|
||||||
|
"sequence_id",
|
||||||
|
"relation_type",
|
||||||
|
"name",
|
||||||
|
"state_id",
|
||||||
|
"priority",
|
||||||
|
"assignee_ids",
|
||||||
|
]
|
||||||
read_only_fields = ["workspace", "project"]
|
read_only_fields = ["workspace", "project"]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -298,10 +314,26 @@ class RelatedIssueSerializer(BaseSerializer):
|
||||||
sequence_id = serializers.IntegerField(source="issue.sequence_id", read_only=True)
|
sequence_id = serializers.IntegerField(source="issue.sequence_id", read_only=True)
|
||||||
name = serializers.CharField(source="issue.name", read_only=True)
|
name = serializers.CharField(source="issue.name", read_only=True)
|
||||||
relation_type = serializers.CharField(read_only=True)
|
relation_type = serializers.CharField(read_only=True)
|
||||||
|
state_id = serializers.UUIDField(source="issue.state.id", read_only=True)
|
||||||
|
priority = serializers.CharField(source="issue.priority", read_only=True)
|
||||||
|
assignee_ids = serializers.ListField(
|
||||||
|
child=serializers.PrimaryKeyRelatedField(queryset=User.objects.all()),
|
||||||
|
write_only=True,
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IssueRelation
|
model = IssueRelation
|
||||||
fields = ["id", "project_id", "sequence_id", "relation_type", "name"]
|
fields = [
|
||||||
|
"id",
|
||||||
|
"project_id",
|
||||||
|
"sequence_id",
|
||||||
|
"relation_type",
|
||||||
|
"name",
|
||||||
|
"state_id",
|
||||||
|
"priority",
|
||||||
|
"assignee_ids",
|
||||||
|
]
|
||||||
read_only_fields = ["workspace", "project"]
|
read_only_fields = ["workspace", "project"]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
"use client";
|
"use client";
|
||||||
import { FC, useState } from "react";
|
import { FC, useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
|
// plane imports
|
||||||
import { EIssueServiceType } from "@plane/constants";
|
import { EIssueServiceType } from "@plane/constants";
|
||||||
import { TIssue, TIssueRelationIdMap, TIssueServiceType } from "@plane/types";
|
import { TIssue, TIssueServiceType } from "@plane/types";
|
||||||
import { Collapsible } from "@plane/ui";
|
import { Collapsible } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { RelationIssueList } from "@/components/issues";
|
import { RelationIssueList } from "@/components/issues";
|
||||||
|
|
@ -11,6 +12,7 @@ import { CreateUpdateIssueModal } from "@/components/issues/issue-modal";
|
||||||
// hooks
|
// hooks
|
||||||
import { useIssueDetail } from "@/hooks/store";
|
import { useIssueDetail } from "@/hooks/store";
|
||||||
// Plane-web
|
// Plane-web
|
||||||
|
import { CreateUpdateEpicModal } from "@/plane-web/components/epics";
|
||||||
import { useTimeLineRelationOptions } from "@/plane-web/components/relations";
|
import { useTimeLineRelationOptions } from "@/plane-web/components/relations";
|
||||||
import { TIssueRelationTypes } from "@/plane-web/types";
|
import { TIssueRelationTypes } from "@/plane-web/types";
|
||||||
// helper
|
// helper
|
||||||
|
|
@ -62,6 +64,7 @@ export const RelationsCollapsibleContent: FC<Props> = observer((props) => {
|
||||||
|
|
||||||
// helper
|
// helper
|
||||||
const issueOperations = useRelationOperations();
|
const issueOperations = useRelationOperations();
|
||||||
|
const epicOperations = useRelationOperations(EIssueServiceType.EPICS);
|
||||||
|
|
||||||
// derived values
|
// derived values
|
||||||
const relations = getRelationsByIssueId(issueId);
|
const relations = getRelationsByIssueId(issueId);
|
||||||
|
|
@ -129,7 +132,6 @@ export const RelationsCollapsibleContent: FC<Props> = observer((props) => {
|
||||||
relationKey={relation.relationKey}
|
relationKey={relation.relationKey}
|
||||||
issueIds={relation.issueIds}
|
issueIds={relation.issueIds}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
issueOperations={issueOperations}
|
|
||||||
handleIssueCrudState={handleIssueCrudState}
|
handleIssueCrudState={handleIssueCrudState}
|
||||||
issueServiceType={issueServiceType}
|
issueServiceType={issueServiceType}
|
||||||
/>
|
/>
|
||||||
|
|
@ -146,24 +148,44 @@ export const RelationsCollapsibleContent: FC<Props> = observer((props) => {
|
||||||
toggleDeleteIssueModal(null);
|
toggleDeleteIssueModal(null);
|
||||||
}}
|
}}
|
||||||
data={issueCrudState?.delete?.issue as TIssue}
|
data={issueCrudState?.delete?.issue as TIssue}
|
||||||
onSubmit={async () =>
|
onSubmit={async () => {
|
||||||
await issueOperations.remove(workspaceSlug, projectId, issueCrudState?.delete?.issue?.id as string)
|
const deleteOperation = !!issueCrudState.delete.issue?.is_epic
|
||||||
}
|
? epicOperations.remove
|
||||||
|
: issueOperations.remove;
|
||||||
|
await deleteOperation(workspaceSlug, projectId, issueCrudState?.delete?.issue?.id as string);
|
||||||
|
}}
|
||||||
|
isEpic={!!issueCrudState.delete.issue?.is_epic}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{shouldRenderIssueUpdateModal && (
|
{shouldRenderIssueUpdateModal && (
|
||||||
<CreateUpdateIssueModal
|
<>
|
||||||
isOpen={issueCrudState?.update?.toggle}
|
{!!issueCrudState?.update?.issue?.is_epic ? (
|
||||||
onClose={() => {
|
<CreateUpdateEpicModal
|
||||||
handleIssueCrudState("update", null, null);
|
isOpen={issueCrudState?.update?.toggle}
|
||||||
toggleCreateIssueModal(false);
|
onClose={() => {
|
||||||
}}
|
handleIssueCrudState("update", null, null);
|
||||||
data={issueCrudState?.update?.issue ?? undefined}
|
toggleCreateIssueModal(false);
|
||||||
onSubmit={async (_issue: TIssue) => {
|
}}
|
||||||
await issueOperations.update(workspaceSlug, projectId, _issue.id, _issue);
|
data={issueCrudState?.update?.issue ?? undefined}
|
||||||
}}
|
onSubmit={async (_issue: TIssue) => {
|
||||||
/>
|
await epicOperations.update(workspaceSlug, projectId, _issue.id, _issue);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<CreateUpdateIssueModal
|
||||||
|
isOpen={issueCrudState?.update?.toggle}
|
||||||
|
onClose={() => {
|
||||||
|
handleIssueCrudState("update", null, null);
|
||||||
|
toggleCreateIssueModal(false);
|
||||||
|
}}
|
||||||
|
data={issueCrudState?.update?.issue ?? undefined}
|
||||||
|
onSubmit={async (_issue: TIssue) => {
|
||||||
|
await issueOperations.update(workspaceSlug, projectId, _issue.id, _issue);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ export const useRelationOperations = (
|
||||||
const { updateIssue, removeIssue } = useIssueDetail(issueServiceType);
|
const { updateIssue, removeIssue } = useIssueDetail(issueServiceType);
|
||||||
const { captureIssueEvent } = useEventTracker();
|
const { captureIssueEvent } = useEventTracker();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
// derived values
|
||||||
|
const entityName = issueServiceType === EIssueServiceType.ISSUES ? "Issue" : "Epic";
|
||||||
|
|
||||||
const issueOperations: TRelationIssueOperations = useMemo(
|
const issueOperations: TRelationIssueOperations = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
|
|
@ -32,7 +34,7 @@ export const useRelationOperations = (
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.SUCCESS,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
title: "Link Copied!",
|
title: "Link Copied!",
|
||||||
message: "Issue link copied to clipboard.",
|
message: `${entityName} link copied to clipboard.`,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
@ -51,7 +53,7 @@ export const useRelationOperations = (
|
||||||
setToast({
|
setToast({
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
type: TOAST_TYPE.SUCCESS,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
message: "Issue updated successfully",
|
message: `${entityName} updated successfully`,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
|
|
@ -66,7 +68,7 @@ export const useRelationOperations = (
|
||||||
setToast({
|
setToast({
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
message: "Issue update failed",
|
message: `${entityName} update failed`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@ import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||||
// plane web components
|
// plane web components
|
||||||
import { IssueIdentifier } from "@/plane-web/components/issues";
|
import { IssueIdentifier } from "@/plane-web/components/issues";
|
||||||
import { TIssueRelationTypes } from "@/plane-web/types";
|
import { TIssueRelationTypes } from "@/plane-web/types";
|
||||||
//
|
// local imports
|
||||||
import { TRelationIssueOperations } from "../issue-detail-widgets/relations/helper";
|
import { useRelationOperations } from "../issue-detail-widgets/relations/helper";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
|
|
@ -26,7 +26,6 @@ type Props = {
|
||||||
relationKey: TIssueRelationTypes;
|
relationKey: TIssueRelationTypes;
|
||||||
relationIssueId: string;
|
relationIssueId: string;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
issueOperations: TRelationIssueOperations;
|
|
||||||
handleIssueCrudState: (key: "update" | "delete", issueId: string, issue?: TIssue | null) => void;
|
handleIssueCrudState: (key: "update" | "delete", issueId: string, issue?: TIssue | null) => void;
|
||||||
issueServiceType?: TIssueServiceType;
|
issueServiceType?: TIssueServiceType;
|
||||||
};
|
};
|
||||||
|
|
@ -39,7 +38,6 @@ export const RelationIssueListItem: FC<Props> = observer((props) => {
|
||||||
relationKey,
|
relationKey,
|
||||||
relationIssueId,
|
relationIssueId,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
issueOperations,
|
|
||||||
handleIssueCrudState,
|
handleIssueCrudState,
|
||||||
issueServiceType = EIssueServiceType.ISSUES,
|
issueServiceType = EIssueServiceType.ISSUES,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
@ -57,6 +55,7 @@ export const RelationIssueListItem: FC<Props> = observer((props) => {
|
||||||
// derived values
|
// derived values
|
||||||
const issue = getIssueById(relationIssueId);
|
const issue = getIssueById(relationIssueId);
|
||||||
const { handleRedirection } = useIssuePeekOverviewRedirection(!!issue?.is_epic);
|
const { handleRedirection } = useIssuePeekOverviewRedirection(!!issue?.is_epic);
|
||||||
|
const issueOperations = useRelationOperations(!!issue?.is_epic ? EIssueServiceType.EPICS : EIssueServiceType.ISSUES);
|
||||||
const projectDetail = (issue && issue.project_id && project.getProjectById(issue.project_id)) || undefined;
|
const projectDetail = (issue && issue.project_id && project.getProjectById(issue.project_id)) || undefined;
|
||||||
const currentIssueStateDetail =
|
const currentIssueStateDetail =
|
||||||
(issue?.project_id && getProjectStates(issue?.project_id)?.find((state) => issue?.state_id == state.id)) ||
|
(issue?.project_id && getProjectStates(issue?.project_id)?.find((state) => issue?.state_id == state.id)) ||
|
||||||
|
|
@ -134,62 +133,58 @@ export const RelationIssueListItem: FC<Props> = observer((props) => {
|
||||||
<span className="w-full truncate text-sm text-custom-text-100">{issue.name}</span>
|
<span className="w-full truncate text-sm text-custom-text-100">{issue.name}</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
{!issue.is_epic && (
|
<div
|
||||||
<>
|
className="flex-shrink-0 text-sm"
|
||||||
<div
|
onClick={(e) => {
|
||||||
className="flex-shrink-0 text-sm"
|
e.preventDefault();
|
||||||
onClick={(e) => {
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
}}
|
||||||
e.stopPropagation();
|
>
|
||||||
}}
|
<RelationIssueProperty
|
||||||
>
|
workspaceSlug={workspaceSlug}
|
||||||
<RelationIssueProperty
|
issueId={relationIssueId}
|
||||||
workspaceSlug={workspaceSlug}
|
disabled={disabled}
|
||||||
issueId={relationIssueId}
|
issueOperations={issueOperations}
|
||||||
disabled={disabled}
|
issueServiceType={issueServiceType}
|
||||||
issueOperations={issueOperations}
|
/>
|
||||||
issueServiceType={issueServiceType}
|
</div>
|
||||||
/>
|
<div className="flex-shrink-0 text-sm">
|
||||||
</div>
|
<CustomMenu placement="bottom-end" ellipsis>
|
||||||
<div className="flex-shrink-0 text-sm">
|
{!disabled && (
|
||||||
<CustomMenu placement="bottom-end" ellipsis>
|
<CustomMenu.MenuItem onClick={handleEditIssue}>
|
||||||
{!disabled && (
|
<div className="flex items-center gap-2">
|
||||||
<CustomMenu.MenuItem onClick={handleEditIssue}>
|
<Pencil className="h-3.5 w-3.5" strokeWidth={2} />
|
||||||
<div className="flex items-center gap-2">
|
<span>Edit</span>
|
||||||
<Pencil className="h-3.5 w-3.5" strokeWidth={2} />
|
</div>
|
||||||
<span>Edit issue</span>
|
</CustomMenu.MenuItem>
|
||||||
</div>
|
)}
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<CustomMenu.MenuItem onClick={handleCopyIssueLink}>
|
<CustomMenu.MenuItem onClick={handleCopyIssueLink}>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<LinkIcon className="h-3.5 w-3.5" strokeWidth={2} />
|
<LinkIcon className="h-3.5 w-3.5" strokeWidth={2} />
|
||||||
<span>Copy issue link</span>
|
<span>Copy link</span>
|
||||||
</div>
|
</div>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
|
|
||||||
{!disabled && (
|
{!disabled && (
|
||||||
<CustomMenu.MenuItem onClick={handleRemoveRelation}>
|
<CustomMenu.MenuItem onClick={handleRemoveRelation}>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<X className="h-3.5 w-3.5" strokeWidth={2} />
|
<X className="h-3.5 w-3.5" strokeWidth={2} />
|
||||||
<span>Remove relation</span>
|
<span>Remove relation</span>
|
||||||
</div>
|
</div>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!disabled && (
|
{!disabled && (
|
||||||
<CustomMenu.MenuItem onClick={handleDeleteIssue}>
|
<CustomMenu.MenuItem onClick={handleDeleteIssue}>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Trash className="h-3.5 w-3.5" strokeWidth={2} />
|
<Trash className="h-3.5 w-3.5" strokeWidth={2} />
|
||||||
<span>Delete issue</span>
|
<span>Delete</span>
|
||||||
</div>
|
</div>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
)}
|
)}
|
||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
</div>
|
</div>
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</ControlLink>
|
</ControlLink>
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,6 @@ import { TIssue, TIssueServiceType } from "@plane/types";
|
||||||
import { RelationIssueListItem } from "@/components/issues/relations";
|
import { RelationIssueListItem } from "@/components/issues/relations";
|
||||||
// Plane-web
|
// Plane-web
|
||||||
import { TIssueRelationTypes } from "@/plane-web/types";
|
import { TIssueRelationTypes } from "@/plane-web/types";
|
||||||
//
|
|
||||||
import { TRelationIssueOperations } from "../issue-detail-widgets/relations/helper";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
|
|
@ -17,7 +15,6 @@ type Props = {
|
||||||
issueId: string;
|
issueId: string;
|
||||||
issueIds: string[];
|
issueIds: string[];
|
||||||
relationKey: TIssueRelationTypes;
|
relationKey: TIssueRelationTypes;
|
||||||
issueOperations: TRelationIssueOperations;
|
|
||||||
handleIssueCrudState: (key: "update" | "delete", issueId: string, issue?: TIssue | null) => void;
|
handleIssueCrudState: (key: "update" | "delete", issueId: string, issue?: TIssue | null) => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
issueServiceType?: TIssueServiceType;
|
issueServiceType?: TIssueServiceType;
|
||||||
|
|
@ -31,7 +28,6 @@ export const RelationIssueList: FC<Props> = observer((props) => {
|
||||||
issueIds,
|
issueIds,
|
||||||
relationKey,
|
relationKey,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
issueOperations,
|
|
||||||
handleIssueCrudState,
|
handleIssueCrudState,
|
||||||
issueServiceType = EIssueServiceType.ISSUES,
|
issueServiceType = EIssueServiceType.ISSUES,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
@ -50,7 +46,6 @@ export const RelationIssueList: FC<Props> = observer((props) => {
|
||||||
relationIssueId={relationIssueId}
|
relationIssueId={relationIssueId}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
handleIssueCrudState={handleIssueCrudState}
|
handleIssueCrudState={handleIssueCrudState}
|
||||||
issueOperations={issueOperations}
|
|
||||||
issueServiceType={issueServiceType}
|
issueServiceType={issueServiceType}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue