From d0e2ce10871c55e7b51e9d3294fb3a07dda93023 Mon Sep 17 00:00:00 2001 From: Sangeetha Date: Sun, 25 May 2025 02:05:04 +0530 Subject: [PATCH] [WEB-3955] fix: handling search issues listing same issues (#3209) * fix: epic relation search listing same epic * chore: seperate query params for search-issues on convertion * chore: existing work item modal params updated * chore: use exclude * chore: remove seperate issue_id filter * chore: remove Q filter --------- Co-authored-by: Anmol Singh Bhatia --- apiserver/plane/app/views/search/issue.py | 31 ++++++++++++++++--- .../types/src/project/project-extended.d.ts | 1 + .../components/issues/issue-modal/base.tsx | 1 + .../components/default-properties.tsx | 4 +++ .../components/issues/issue-modal/form.tsx | 3 ++ .../issues/parent-issues-list-modal.tsx | 7 +++-- 6 files changed, 40 insertions(+), 7 deletions(-) diff --git a/apiserver/plane/app/views/search/issue.py b/apiserver/plane/app/views/search/issue.py index 6311eb1994..f27174e474 100644 --- a/apiserver/plane/app/views/search/issue.py +++ b/apiserver/plane/app/views/search/issue.py @@ -8,6 +8,7 @@ from django.db.models import Q, QuerySet from rest_framework import status from rest_framework.response import Response from rest_framework.request import Request + # Module imports from .base import BaseAPIView from plane.db.models import Issue, ProjectMember, IssueRelation @@ -51,7 +52,8 @@ class IssueSearchEndpoint(BaseAPIView): """ Filter issues excluding related issues """ - issue = Issue.issue_objects.filter(pk=issue_id).first() + issue = Issue.objects.filter(pk=issue_id).first() + related_issue_ids = ( IssueRelation.objects.filter(Q(related_issue=issue) | Q(issue=issue)) .values_list("issue_id", "related_issue_id") @@ -61,7 +63,7 @@ class IssueSearchEndpoint(BaseAPIView): related_issue_ids = [item for sublist in related_issue_ids for item in sublist] if issue: - issues = issues.exclude(pk__in=related_issue_ids, pk=issue_id) + issues = issues.exclude(pk__in=related_issue_ids) return issues @@ -101,6 +103,16 @@ class IssueSearchEndpoint(BaseAPIView): issues = issues.filter(target_date__isnull=True) return issues + def filter_issues_and_epics_by_excluding_given_issue_id( + self, query, issue_id: str, issues_and_epics: QuerySet + ) -> QuerySet: + """ + Filter issues and epics by excluding the given issue + """ + issues = search_issues(query, issues_and_epics) + + return issues.exclude(pk=issue_id) + def get(self, request: Request, slug: str, project_id: UUID): query = request.query_params.get("search", False) workspace_search = request.query_params.get("workspace_search", "false") @@ -112,6 +124,7 @@ class IssueSearchEndpoint(BaseAPIView): sub_issue = request.query_params.get("sub_issue", "false") target_date = request.query_params.get("target_date", True) issue_id = request.query_params.get("issue_id", False) + convert = request.query_params.get("convert", False) issues = Issue.issue_objects.filter( workspace__slug=slug, @@ -139,12 +152,14 @@ class IssueSearchEndpoint(BaseAPIView): # Filter issues and epics by project if workspace_search == "false": issues = self.filter_issues_by_project(project_id, issues) - issues_and_epics = self.filter_issues_by_project(project_id, issues_and_epics) + issues_and_epics = self.filter_issues_by_project( + project_id, issues_and_epics + ) # Filter issues and epics by query if epic == "true": issues = self.search_issues_by_query(query, issues_and_epics) - + if parent == "true": issues = self.search_issues_by_query(query, issues_and_epics) @@ -159,7 +174,9 @@ class IssueSearchEndpoint(BaseAPIView): issues = self.search_issues_and_excluding_parent(issues_and_epics, issue_id) if issue_relation == "true" and issue_id: - issues = self.filter_issues_excluding_related_issues(issue_id, issues_and_epics) + issues = self.filter_issues_excluding_related_issues( + issue_id, issues_and_epics + ) if sub_issue == "true" and issue_id: issues = self.filter_root_issues_only(issue_id, issues) @@ -173,6 +190,10 @@ class IssueSearchEndpoint(BaseAPIView): if target_date == "none": issues = self.filter_issues_without_target_date(issues) + if convert == "true" and issue_id: + issues = self.filter_issues_and_epics_by_excluding_given_issue_id( + query, issue_id, issues_and_epics + ) if ProjectMember.objects.filter( project_id=project_id, member=self.request.user, is_active=True, role=5 ).exists(): diff --git a/packages/types/src/project/project-extended.d.ts b/packages/types/src/project/project-extended.d.ts index 11692ba901..f21c2e68f9 100644 --- a/packages/types/src/project/project-extended.d.ts +++ b/packages/types/src/project/project-extended.d.ts @@ -1,3 +1,4 @@ export type TProjectIssuesSearchParams = { customer_request_id?: string; + convert?: boolean; }; diff --git a/web/core/components/issues/issue-modal/base.tsx b/web/core/components/issues/issue-modal/base.tsx index 7576712631..0a28f2e637 100644 --- a/web/core/components/issues/issue-modal/base.tsx +++ b/web/core/components/issues/issue-modal/base.tsx @@ -399,6 +399,7 @@ export const CreateUpdateIssueModalBase: React.FC = observer(( handleDuplicateIssueModal: handleDuplicateIssueModal, isProjectSelectionDisabled: isProjectSelectionDisabled, storeType: storeType, + convertToWorkItem: isConversionOperation, }; return ( diff --git a/web/core/components/issues/issue-modal/components/default-properties.tsx b/web/core/components/issues/issue-modal/components/default-properties.tsx index 5696743c72..21b2b7d180 100644 --- a/web/core/components/issues/issue-modal/components/default-properties.tsx +++ b/web/core/components/issues/issue-modal/components/default-properties.tsx @@ -45,6 +45,7 @@ type TIssueDefaultPropertiesProps = { handleFormChange: () => void; setLabelModal: React.Dispatch>; setSelectedParentIssue: (issue: ISearchIssueResponse) => void; + convertToWorkItem?: boolean; }; export const IssueDefaultProperties: React.FC = observer((props) => { @@ -61,6 +62,7 @@ export const IssueDefaultProperties: React.FC = ob handleFormChange, setLabelModal, setSelectedParentIssue, + convertToWorkItem = false, } = props; // states const [parentIssueListModalOpen, setParentIssueListModalOpen] = useState(false); @@ -334,6 +336,8 @@ export const IssueDefaultProperties: React.FC = ob }} projectId={projectId ?? undefined} issueId={isDraft ? undefined : id} + searchEpic + convertToWorkItem={convertToWorkItem} /> )} /> diff --git a/web/core/components/issues/issue-modal/form.tsx b/web/core/components/issues/issue-modal/form.tsx index ca37cbf24a..5862a971db 100644 --- a/web/core/components/issues/issue-modal/form.tsx +++ b/web/core/components/issues/issue-modal/form.tsx @@ -64,6 +64,7 @@ export interface IssueFormProps { handleDraftAndClose?: () => void; isProjectSelectionDisabled?: boolean; storeType: EIssuesStoreType; + convertToWorkItem?: boolean; } export const IssueFormRoot: FC = observer((props) => { @@ -90,6 +91,7 @@ export const IssueFormRoot: FC = observer((props) => { handleDraftAndClose, isProjectSelectionDisabled = false, storeType, + convertToWorkItem = false, } = props; // states @@ -505,6 +507,7 @@ export const IssueFormRoot: FC = observer((props) => { handleFormChange={handleFormChange} setLabelModal={setLabelModal} setSelectedParentIssue={setSelectedParentIssue} + convertToWorkItem={convertToWorkItem} />
diff --git a/web/core/components/issues/parent-issues-list-modal.tsx b/web/core/components/issues/parent-issues-list-modal.tsx index 67ad44f883..46c98075e2 100644 --- a/web/core/components/issues/parent-issues-list-modal.tsx +++ b/web/core/components/issues/parent-issues-list-modal.tsx @@ -33,6 +33,7 @@ type Props = { projectId: string | undefined; issueId?: string; searchEpic?: boolean; + convertToWorkItem?: boolean; }; // services @@ -46,6 +47,7 @@ export const ParentIssuesListModal: React.FC = ({ projectId, issueId, searchEpic = false, + convertToWorkItem = false, }) => { // i18n const { t } = useTranslation(); @@ -78,14 +80,15 @@ export const ParentIssuesListModal: React.FC = ({ parent: searchEpic ? undefined : true, issue_id: issueId, workspace_search: false, - epic: searchEpic ? true : undefined, + epic: searchEpic && !convertToWorkItem ? true : undefined, + convert: convertToWorkItem ? true : undefined, }) .then((res) => setIssues(res)) .finally(() => { setIsSearching(false); setIsLoading(false); }); - }, [debouncedSearchTerm, isOpen, issueId, projectId, workspaceSlug]); + }, [debouncedSearchTerm, isOpen, issueId, projectId, workspaceSlug, convertToWorkItem, searchEpic]); return ( <>