[WEB-3207] chore: add state_id, priority and assignee_ids to create issue relation response (#6448)

This commit is contained in:
Prateek Shourya 2025-01-23 14:16:06 +05:30 committed by GitHub
parent 586a320d86
commit 0b53912295
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 129 additions and 83 deletions

View file

@ -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"]

View file

@ -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,13 +148,31 @@ 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 && (
<>
{!!issueCrudState?.update?.issue?.is_epic ? (
<CreateUpdateEpicModal
isOpen={issueCrudState?.update?.toggle}
onClose={() => {
handleIssueCrudState("update", null, null);
toggleCreateIssueModal(false);
}}
data={issueCrudState?.update?.issue ?? undefined}
onSubmit={async (_issue: TIssue) => {
await epicOperations.update(workspaceSlug, projectId, _issue.id, _issue);
}}
/>
) : (
<CreateUpdateIssueModal <CreateUpdateIssueModal
isOpen={issueCrudState?.update?.toggle} isOpen={issueCrudState?.update?.toggle}
onClose={() => { onClose={() => {
@ -166,5 +186,7 @@ export const RelationsCollapsibleContent: FC<Props> = observer((props) => {
/> />
)} )}
</> </>
)}
</>
); );
}); });

View file

@ -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`,
}); });
} }
}, },

View file

@ -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,8 +133,6 @@ 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 <div
className="flex-shrink-0 text-sm" className="flex-shrink-0 text-sm"
onClick={(e) => { onClick={(e) => {
@ -157,7 +154,7 @@ export const RelationIssueListItem: FC<Props> = observer((props) => {
<CustomMenu.MenuItem onClick={handleEditIssue}> <CustomMenu.MenuItem onClick={handleEditIssue}>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Pencil className="h-3.5 w-3.5" strokeWidth={2} /> <Pencil className="h-3.5 w-3.5" strokeWidth={2} />
<span>Edit issue</span> <span>Edit</span>
</div> </div>
</CustomMenu.MenuItem> </CustomMenu.MenuItem>
)} )}
@ -165,7 +162,7 @@ export const RelationIssueListItem: FC<Props> = observer((props) => {
<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>
@ -182,14 +179,12 @@ export const RelationIssueListItem: FC<Props> = observer((props) => {
<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>

View file

@ -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}
/> />
))} ))}