From d47efaa0f0f752693759eae9363f7b5a1ab9a863 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Mon, 25 Sep 2023 16:51:59 +0530 Subject: [PATCH 001/427] dev: remove auto filter endpoint --- apiserver/plane/api/urls.py | 6 ---- apiserver/plane/api/views/__init__.py | 2 +- apiserver/plane/api/views/view.py | 45 -------------------------- apiserver/plane/utils/issue_filters.py | 4 --- 4 files changed, 1 insertion(+), 56 deletions(-) diff --git a/apiserver/plane/api/urls.py b/apiserver/plane/api/urls.py index c10c4a745..771c131ed 100644 --- a/apiserver/plane/api/urls.py +++ b/apiserver/plane/api/urls.py @@ -105,7 +105,6 @@ from plane.api.views import ( GlobalViewViewSet, GlobalViewIssuesViewSet, IssueViewViewSet, - ViewIssuesEndpoint, IssueViewFavoriteViewSet, ## End Views # Cycles @@ -649,11 +648,6 @@ urlpatterns = [ ), name="project-view", ), - path( - "workspaces//projects//views//issues/", - ViewIssuesEndpoint.as_view(), - name="project-view-issues", - ), path( "workspaces//views/", GlobalViewViewSet.as_view( diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index c03d6d5b7..7e89c37f1 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -56,7 +56,7 @@ from .workspace import ( LeaveWorkspaceEndpoint, ) from .state import StateViewSet -from .view import GlobalViewViewSet, GlobalViewIssuesViewSet, IssueViewViewSet, ViewIssuesEndpoint, IssueViewFavoriteViewSet +from .view import GlobalViewViewSet, GlobalViewIssuesViewSet, IssueViewViewSet, IssueViewFavoriteViewSet from .cycle import ( CycleViewSet, CycleIssueViewSet, diff --git a/apiserver/plane/api/views/view.py b/apiserver/plane/api/views/view.py index b6f1d7c4b..bf47d1e4b 100644 --- a/apiserver/plane/api/views/view.py +++ b/apiserver/plane/api/views/view.py @@ -243,51 +243,6 @@ class IssueViewViewSet(BaseViewSet): ) -class ViewIssuesEndpoint(BaseAPIView): - permission_classes = [ - ProjectEntityPermission, - ] - - def get(self, request, slug, project_id, view_id): - try: - view = IssueView.objects.get(pk=view_id) - queries = view.query - - filters = issue_filters(request.query_params, "GET") - - issues = ( - Issue.issue_objects.filter( - **queries, project_id=project_id, workspace__slug=slug - ) - .filter(**filters) - .select_related("project") - .select_related("workspace") - .select_related("state") - .select_related("parent") - .prefetch_related("assignees") - .prefetch_related("labels") - .prefetch_related( - Prefetch( - "issue_reactions", - queryset=IssueReaction.objects.select_related("actor"), - ) - ) - ) - - serializer = IssueLiteSerializer(issues, many=True) - return Response(serializer.data, status=status.HTTP_200_OK) - except IssueView.DoesNotExist: - return Response( - {"error": "Issue View does not exist"}, status=status.HTTP_404_NOT_FOUND - ) - except Exception as e: - capture_exception(e) - return Response( - {"error": "Something went wrong please try again later"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - class IssueViewFavoriteViewSet(BaseViewSet): serializer_class = IssueViewFavoriteSerializer model = IssueViewFavorite diff --git a/apiserver/plane/utils/issue_filters.py b/apiserver/plane/utils/issue_filters.py index 3a869113c..eec528916 100644 --- a/apiserver/plane/utils/issue_filters.py +++ b/apiserver/plane/utils/issue_filters.py @@ -1,7 +1,3 @@ -from django.utils.timezone import make_aware -from django.utils.dateparse import parse_datetime - - def filter_state(params, filter, method): if method == "GET": states = params.get("state").split(",") From 608ba9d5cbefeb6d60e6c16bbdb96dc5de69e67d Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Tue, 26 Sep 2023 16:33:49 +0530 Subject: [PATCH 002/427] chore: udpate date filters to support dynamic options --- apiserver/plane/utils/issue_filters.py | 250 +++++++++++++++++++++---- 1 file changed, 212 insertions(+), 38 deletions(-) diff --git a/apiserver/plane/utils/issue_filters.py b/apiserver/plane/utils/issue_filters.py index eec528916..09b55d96f 100644 --- a/apiserver/plane/utils/issue_filters.py +++ b/apiserver/plane/utils/issue_filters.py @@ -1,3 +1,38 @@ +import re +from datetime import timedelta +from django.utils import timezone + +# The date from pattern +pattern = re.compile(r"\d+_(weeks|months)$") + + +# Get the 2_weeks, 3_months +def date_filter(filter, duration, subsequent, term, date_filter, offset): + now = timezone.now().date() + if term == "months": + if subsequent == "after": + if offset == "fromnow": + filter[f"{date_filter}__gte"] = now + timedelta(days=duration * 30) + else: + filter[f"{date_filter}__gte"] = now - timedelta(days=duration * 30) + else: + if offset == "fromnow": + filter[f"{date_filter}__lte"] = now + timedelta(days=duration * 30) + else: + filter[f"{date_filter}__lte"] = now - timedelta(days=duration * 30) + if term == "weeks": + if subsequent == "after": + if offset == "fromnow": + filter[f"{date_filter}__gte"] = now + timedelta(weeks=duration) + else: + filter[f"{date_filter}__gte"] = now - timedelta(weeks=duration) + else: + if offset == "fromnow": + filter[f"{date_filter}__lte"] = now + timedelta(days=duration) + else: + filter[f"{date_filter}__lte"] = now - timedelta(days=duration) + + def filter_state(params, filter, method): if method == "GET": states = params.get("state").split(",") @@ -95,18 +130,46 @@ def filter_created_at(params, filter, method): if len(created_ats) and "" not in created_ats: for query in created_ats: created_at_query = query.split(";") - if len(created_at_query) == 2 and "after" in created_at_query: - filter["created_at__date__gte"] = created_at_query[0] - else: - filter["created_at__date__lte"] = created_at_query[0] + if len(created_at_query) >= 2: + match = pattern.match(created_at_query[0]) + if match: + if len(created_at_query) == 3: + digit, term = created_at_query[0].split("_") + date_filter( + filter=filter, + duration=digit, + subsequent=created_at_query[1], + term=term, + date_filter="created_at__date", + offset=created_at_query[2], + ) + else: + if "after" in created_at_query: + filter["created_at__date__gte"] = created_at_query[0] + else: + filter["created_at__date__lte"] = created_at_query[0] else: if params.get("created_at", None) and len(params.get("created_at")): for query in params.get("created_at"): created_at_query = query.split(";") - if len(created_at_query) == 2 and "after" in created_at_query: - filter["created_at__date__gte"] = created_at_query[0] - else: - filter["created_at__date__lte"] = created_at_query[0] + if len(created_at_query) == 2: + match = pattern.match(created_at_query[0]) + if match: + if len(created_at_query) == 3: + digit, term = created_at_query[0].split("_") + date_filter( + filter=filter, + duration=digit, + subsequent=created_at_query[1], + term=term, + date_filter="created_at__date", + offset=created_at_query[2], + ) + else: + if "after" in created_at_query: + filter["created_at__date__gte"] = created_at_query[0] + else: + filter["created_at__date__lte"] = created_at_query[0] return filter @@ -116,18 +179,46 @@ def filter_updated_at(params, filter, method): if len(updated_ats) and "" not in updated_ats: for query in updated_ats: updated_at_query = query.split(";") - if len(updated_at_query) == 2 and "after" in updated_at_query: - filter["updated_at__date__gte"] = updated_at_query[0] - else: - filter["updated_at__date__lte"] = updated_at_query[0] + if len(updated_at_query) == 2: + match = pattern.match(updated_at_query[0]) + if match: + if len(updated_at_query) == 3: + digit, term = updated_at_query[0].split("_") + date_filter( + filter=filter, + duration=digit, + subsequent=updated_at_query[1], + term=term, + date_filter="updated_at__date", + offset=updated_at_query[2], + ) + else: + if "after" in updated_at_query: + filter["updated_at__date__gte"] = updated_at_query[0] + else: + filter["updated_at__date__lte"] = updated_at_query[0] else: if params.get("updated_at", None) and len(params.get("updated_at")): for query in params.get("updated_at"): updated_at_query = query.split(";") - if len(updated_at_query) == 2 and "after" in updated_at_query: - filter["updated_at__date__gte"] = updated_at_query[0] - else: - filter["updated_at__date__lte"] = updated_at_query[0] + if len(updated_at_query) == 2: + match = pattern.match(updated_at_query[0]) + if match: + if len(updated_at_query) == 3: + digit, term = updated_at_query[0].split("_") + date_filter( + filter=filter, + duration=digit, + subsequent=updated_at_query[1], + term=term, + date_filter="updated_at__date", + offset=updated_at_query[2], + ) + else: + if "after" in updated_at_query: + filter["updated_at__date__gte"] = updated_at_query[0] + else: + filter["updated_at__date__lte"] = updated_at_query[0] return filter @@ -137,18 +228,46 @@ def filter_start_date(params, filter, method): if len(start_dates) and "" not in start_dates: for query in start_dates: start_date_query = query.split(";") - if len(start_date_query) == 2 and "after" in start_date_query: - filter["start_date__gte"] = start_date_query[0] - else: - filter["start_date__lte"] = start_date_query[0] + if len(start_date_query) >= 2: + match = pattern.match(start_date_query[0]) + if match: + if len(start_date_query) == 3: + digit, term = start_date_query[0].split("_") + date_filter( + filter=filter, + duration=float(digit), + subsequent=start_date_query[1], + term=term, + date_filter="start_date", + offset=start_date_query[2], + ) + else: + if "after" in start_date_query: + filter["start_date__gte"] = start_date_query[0] + else: + filter["start_date__lte"] = start_date_query[0] else: if params.get("start_date", None) and len(params.get("start_date")): for query in params.get("start_date"): start_date_query = query.split(";") - if len(start_date_query) == 2 and "after" in start_date_query: - filter["start_date__gte"] = start_date_query[0] - else: - filter["start_date__lte"] = start_date_query[0] + if len(start_date_query) == 2: + match = pattern.match(start_date_query[0]) + if match: + if len(start_date_query) == 3: + digit, term = start_date_query[0].split("_") + date_filter( + filter=filter, + duration=float(digit), + subsequent=start_date_query[1], + term=term, + date_filter="start_date", + offset=start_date_query[2], + ) + else: + if "after" in start_date_query: + filter["start_date__gte"] = start_date_query[0] + else: + filter["start_date__lte"] = start_date_query[0] return filter @@ -158,18 +277,46 @@ def filter_target_date(params, filter, method): if len(target_dates) and "" not in target_dates: for query in target_dates: target_date_query = query.split(";") - if len(target_date_query) == 2 and "after" in target_date_query: - filter["target_date__gt"] = target_date_query[0] - else: - filter["target_date__lt"] = target_date_query[0] + if len(target_date_query) == 2: + match = pattern.match(target_date_query[0]) + if match: + if len(target_date_query) == 3: + digit, term = target_date_query[0].split("_") + date_filter( + filter=filter, + duration=digit, + subsequent=target_date_query[1], + term=term, + date_filter="target_date", + offset=target_date_query[2], + ) + else: + if "after" in target_date_query: + filter["target_date__gt"] = target_date_query[0] + else: + filter["target_date__lt"] = target_date_query[0] else: if params.get("target_date", None) and len(params.get("target_date")): for query in params.get("target_date"): target_date_query = query.split(";") - if len(target_date_query) == 2 and "after" in target_date_query: - filter["target_date__gt"] = target_date_query[0] - else: - filter["target_date__lt"] = target_date_query[0] + if len(target_date_query) == 2: + match = pattern.match(target_date_query[0]) + if match: + if len(target_date_query) == 3: + digit, term = target_date_query[0].split("_") + date_filter( + filter=filter, + duration=digit, + subsequent=target_date_query[1], + term=term, + date_filter="target_date", + offset=target_date_query[2], + ) + else: + if "after" in target_date_query: + filter["target_date__gt"] = target_date_query[0] + else: + filter["target_date__lt"] = target_date_query[0] return filter @@ -180,18 +327,45 @@ def filter_completed_at(params, filter, method): if len(completed_ats) and "" not in completed_ats: for query in completed_ats: completed_at_query = query.split(";") - if len(completed_at_query) == 2 and "after" in completed_at_query: - filter["completed_at__date__gte"] = completed_at_query[0] + if len(completed_at_query) == 2: + match = pattern.match(completed_at_query[0]) + if match: + if len(completed_at_query) == 3: + digit, term = completed_at_query[0].split("_") + date_filter( + filter=filter, + duration=digit, + subsequent=completed_at_query[1], + term=term, + date_filter="completed_at__date", + offset=completed_at_query[2], + ) else: - filter["completed_at__lte"] = completed_at_query[0] + if "after" in completed_at_query: + filter["completed_at__date__gte"] = completed_at_query[0] + else: + filter["completed_at__date__lte"] = completed_at_query[0] else: if params.get("completed_at", None) and len(params.get("completed_at")): for query in params.get("completed_at"): completed_at_query = query.split(";") - if len(completed_at_query) == 2 and "after" in completed_at_query: - filter["completed_at__date__gte"] = completed_at_query[0] + if len(completed_at_query) == 2: + match = pattern.match(completed_at_query[0]) + if match: + digit, term = match.group(1), match.group(2) + date_filter( + filter=filter, + duration=digit, + subsequent=completed_at_query[1], + term=term, + date_filter="completed_at__date", + offset=completed_at_query[2], + ) else: - filter["completed_at__lte"] = completed_at_query[0] + if "after" in completed_at_query: + filter["completed_at__date__gte"] = completed_at_query[0] + else: + filter["completed_at__date__lte"] = completed_at_query[0] return filter From 42d39194592f3f1cce21089fb06312b3beb86833 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Wed, 27 Sep 2023 14:33:41 +0530 Subject: [PATCH 003/427] dev: refactor date filters to a single function --- apiserver/plane/api/views/issue.py | 4 - apiserver/plane/utils/issue_filters.py | 238 ++++--------------------- 2 files changed, 37 insertions(+), 205 deletions(-) diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index 003a8ae32..280d929af 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -24,8 +24,6 @@ from django.core.serializers.json import DjangoJSONEncoder from django.utils.decorators import method_decorator from django.views.decorators.gzip import gzip_page from django.db import IntegrityError -from django.conf import settings -from django.db import IntegrityError # Third Party imports from rest_framework.response import Response @@ -58,7 +56,6 @@ from plane.api.serializers import ( IssuePublicSerializer, ) from plane.api.permissions import ( - WorkspaceEntityPermission, ProjectEntityPermission, WorkSpaceAdminPermission, ProjectMemberPermission, @@ -86,7 +83,6 @@ from plane.db.models import ( from plane.bgtasks.issue_activites_task import issue_activity from plane.utils.grouper import group_results from plane.utils.issue_filters import issue_filters -from plane.bgtasks.export_task import issue_export_task class IssueViewSet(BaseViewSet): diff --git a/apiserver/plane/utils/issue_filters.py b/apiserver/plane/utils/issue_filters.py index 09b55d96f..edcfa2930 100644 --- a/apiserver/plane/utils/issue_filters.py +++ b/apiserver/plane/utils/issue_filters.py @@ -7,7 +7,7 @@ pattern = re.compile(r"\d+_(weeks|months)$") # Get the 2_weeks, 3_months -def date_filter(filter, duration, subsequent, term, date_filter, offset): +def string_date_filter(filter, duration, subsequent, term, date_filter, offset): now = timezone.now().date() if term == "months": if subsequent == "after": @@ -33,6 +33,32 @@ def date_filter(filter, duration, subsequent, term, date_filter, offset): filter[f"{date_filter}__lte"] = now - timedelta(days=duration) +def date_filter(filter, date_term, queries): + """ + Handle all date filters + """ + for query in queries: + date_query = query.split(";") + if len(date_query) >= 2: + match = pattern.match(date_query[0]) + if match: + if len(date_query) == 3: + digit, term = date_query[0].split("_") + string_date_filter( + filter=filter, + duration=int(digit), + subsequent=date_query[1], + term=term, + date_filter="created_at__date", + offset=date_query[2], + ) + else: + if "after" in date_query: + filter[f"{date_term}__gte"] = date_query[0] + else: + filter[f"{date_term}__lte"] = date_query[0] + + def filter_state(params, filter, method): if method == "GET": states = params.get("state").split(",") @@ -128,48 +154,10 @@ def filter_created_at(params, filter, method): if method == "GET": created_ats = params.get("created_at").split(",") if len(created_ats) and "" not in created_ats: - for query in created_ats: - created_at_query = query.split(";") - if len(created_at_query) >= 2: - match = pattern.match(created_at_query[0]) - if match: - if len(created_at_query) == 3: - digit, term = created_at_query[0].split("_") - date_filter( - filter=filter, - duration=digit, - subsequent=created_at_query[1], - term=term, - date_filter="created_at__date", - offset=created_at_query[2], - ) - else: - if "after" in created_at_query: - filter["created_at__date__gte"] = created_at_query[0] - else: - filter["created_at__date__lte"] = created_at_query[0] + date_filter(filter=filter, date_term="created_at__date", queries=created_ats) else: if params.get("created_at", None) and len(params.get("created_at")): - for query in params.get("created_at"): - created_at_query = query.split(";") - if len(created_at_query) == 2: - match = pattern.match(created_at_query[0]) - if match: - if len(created_at_query) == 3: - digit, term = created_at_query[0].split("_") - date_filter( - filter=filter, - duration=digit, - subsequent=created_at_query[1], - term=term, - date_filter="created_at__date", - offset=created_at_query[2], - ) - else: - if "after" in created_at_query: - filter["created_at__date__gte"] = created_at_query[0] - else: - filter["created_at__date__lte"] = created_at_query[0] + date_filter(filter=filter, date_term="created_at__date", queries=params.get("created_at", [])) return filter @@ -177,48 +165,10 @@ def filter_updated_at(params, filter, method): if method == "GET": updated_ats = params.get("updated_at").split(",") if len(updated_ats) and "" not in updated_ats: - for query in updated_ats: - updated_at_query = query.split(";") - if len(updated_at_query) == 2: - match = pattern.match(updated_at_query[0]) - if match: - if len(updated_at_query) == 3: - digit, term = updated_at_query[0].split("_") - date_filter( - filter=filter, - duration=digit, - subsequent=updated_at_query[1], - term=term, - date_filter="updated_at__date", - offset=updated_at_query[2], - ) - else: - if "after" in updated_at_query: - filter["updated_at__date__gte"] = updated_at_query[0] - else: - filter["updated_at__date__lte"] = updated_at_query[0] + date_filter(filter=filter, date_term="created_at__date", queries=updated_ats) else: if params.get("updated_at", None) and len(params.get("updated_at")): - for query in params.get("updated_at"): - updated_at_query = query.split(";") - if len(updated_at_query) == 2: - match = pattern.match(updated_at_query[0]) - if match: - if len(updated_at_query) == 3: - digit, term = updated_at_query[0].split("_") - date_filter( - filter=filter, - duration=digit, - subsequent=updated_at_query[1], - term=term, - date_filter="updated_at__date", - offset=updated_at_query[2], - ) - else: - if "after" in updated_at_query: - filter["updated_at__date__gte"] = updated_at_query[0] - else: - filter["updated_at__date__lte"] = updated_at_query[0] + date_filter(filter=filter, date_term="created_at__date", queries=params.get("updated_at", [])) return filter @@ -226,48 +176,10 @@ def filter_start_date(params, filter, method): if method == "GET": start_dates = params.get("start_date").split(",") if len(start_dates) and "" not in start_dates: - for query in start_dates: - start_date_query = query.split(";") - if len(start_date_query) >= 2: - match = pattern.match(start_date_query[0]) - if match: - if len(start_date_query) == 3: - digit, term = start_date_query[0].split("_") - date_filter( - filter=filter, - duration=float(digit), - subsequent=start_date_query[1], - term=term, - date_filter="start_date", - offset=start_date_query[2], - ) - else: - if "after" in start_date_query: - filter["start_date__gte"] = start_date_query[0] - else: - filter["start_date__lte"] = start_date_query[0] + date_filter(filter=filter, date_term="start_date", queries=start_dates) else: if params.get("start_date", None) and len(params.get("start_date")): - for query in params.get("start_date"): - start_date_query = query.split(";") - if len(start_date_query) == 2: - match = pattern.match(start_date_query[0]) - if match: - if len(start_date_query) == 3: - digit, term = start_date_query[0].split("_") - date_filter( - filter=filter, - duration=float(digit), - subsequent=start_date_query[1], - term=term, - date_filter="start_date", - offset=start_date_query[2], - ) - else: - if "after" in start_date_query: - filter["start_date__gte"] = start_date_query[0] - else: - filter["start_date__lte"] = start_date_query[0] + date_filter(filter=filter, date_term="start_date", queries=params.get("start_date", None)) return filter @@ -275,49 +187,10 @@ def filter_target_date(params, filter, method): if method == "GET": target_dates = params.get("target_date").split(",") if len(target_dates) and "" not in target_dates: - for query in target_dates: - target_date_query = query.split(";") - if len(target_date_query) == 2: - match = pattern.match(target_date_query[0]) - if match: - if len(target_date_query) == 3: - digit, term = target_date_query[0].split("_") - date_filter( - filter=filter, - duration=digit, - subsequent=target_date_query[1], - term=term, - date_filter="target_date", - offset=target_date_query[2], - ) - else: - if "after" in target_date_query: - filter["target_date__gt"] = target_date_query[0] - else: - filter["target_date__lt"] = target_date_query[0] + date_filter(filter=filter, date_term="target_date", queries=target_dates) else: if params.get("target_date", None) and len(params.get("target_date")): - for query in params.get("target_date"): - target_date_query = query.split(";") - if len(target_date_query) == 2: - match = pattern.match(target_date_query[0]) - if match: - if len(target_date_query) == 3: - digit, term = target_date_query[0].split("_") - date_filter( - filter=filter, - duration=digit, - subsequent=target_date_query[1], - term=term, - date_filter="target_date", - offset=target_date_query[2], - ) - else: - if "after" in target_date_query: - filter["target_date__gt"] = target_date_query[0] - else: - filter["target_date__lt"] = target_date_query[0] - + date_filter(filter=filter, date_term="target_date", queries=params.get("target_date", [])) return filter @@ -325,47 +198,10 @@ def filter_completed_at(params, filter, method): if method == "GET": completed_ats = params.get("completed_at").split(",") if len(completed_ats) and "" not in completed_ats: - for query in completed_ats: - completed_at_query = query.split(";") - if len(completed_at_query) == 2: - match = pattern.match(completed_at_query[0]) - if match: - if len(completed_at_query) == 3: - digit, term = completed_at_query[0].split("_") - date_filter( - filter=filter, - duration=digit, - subsequent=completed_at_query[1], - term=term, - date_filter="completed_at__date", - offset=completed_at_query[2], - ) - else: - if "after" in completed_at_query: - filter["completed_at__date__gte"] = completed_at_query[0] - else: - filter["completed_at__date__lte"] = completed_at_query[0] + date_filter(filter=filter, date_term="completed_at__date", queries=completed_ats) else: if params.get("completed_at", None) and len(params.get("completed_at")): - for query in params.get("completed_at"): - completed_at_query = query.split(";") - if len(completed_at_query) == 2: - match = pattern.match(completed_at_query[0]) - if match: - digit, term = match.group(1), match.group(2) - date_filter( - filter=filter, - duration=digit, - subsequent=completed_at_query[1], - term=term, - date_filter="completed_at__date", - offset=completed_at_query[2], - ) - else: - if "after" in completed_at_query: - filter["completed_at__date__gte"] = completed_at_query[0] - else: - filter["completed_at__date__lte"] = completed_at_query[0] + date_filter(filter=filter, date_term="completed_at__date", queries=params.get("completed_at", [])) return filter From 4a380ae2422b491c2351ffc61b202eb3d798ee30 Mon Sep 17 00:00:00 2001 From: Manish Gupta Date: Mon, 9 Oct 2023 15:09:27 +0530 Subject: [PATCH 004/427] sync CE Master to EE Develop --- .github/workflows/create-sync-pr.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/create-sync-pr.yml b/.github/workflows/create-sync-pr.yml index 28e47a0d6..d93aec13e 100644 --- a/.github/workflows/create-sync-pr.yml +++ b/.github/workflows/create-sync-pr.yml @@ -2,6 +2,8 @@ name: Create PR in Plane EE Repository to sync the changes on: pull_request: + branches: + - master types: - closed From 58ea4d6ec9c53134fe4b5a20bd5d5630ed389300 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Wed, 11 Oct 2023 12:05:53 +0530 Subject: [PATCH 005/427] chore: integrate popper js (#2398) * chore: react-popper-js added * chore: integrate popper js in issue properties dropdown * chore: integrate popper js in custom menu component * chore: integrate popper js in custom select component * chore: integrate popper js in custom search select component * chore: popper js placement type added * chore: popper js placement type added --- .../automation/auto-archive-automation.tsx | 1 - web/components/core/theme/theme-switch.tsx | 1 - .../core/views/board-view/single-board.tsx | 8 +- .../core/views/board-view/single-issue.tsx | 4 +- .../core/views/list-view/single-list.tsx | 1 - .../spreadsheet-view/spreadsheet-view.tsx | 10 +- .../integration/github/select-repository.tsx | 1 - .../integration/jira/give-details.tsx | 1 - web/components/issues/main-content.tsx | 2 +- .../issues/peek-overview/header.tsx | 1 - web/components/issues/select/estimate.tsx | 1 - .../issues/sidebar-select/cycle.tsx | 1 - .../issues/sidebar-select/estimate.tsx | 7 +- .../issues/sidebar-select/module.tsx | 1 - .../issues/sidebar-select/priority.tsx | 5 +- .../issues/sidebar-select/state.tsx | 5 +- web/components/issues/sub-issues/root.tsx | 2 - .../issues/view-select/assignee.tsx | 2 - .../issues/view-select/estimate.tsx | 2 - web/components/issues/view-select/label.tsx | 2 - .../issues/view-select/priority.tsx | 7 +- .../modules/sidebar-select/select-lead.tsx | 1 - .../modules/sidebar-select/select-members.tsx | 1 - .../notifications/notification-card.tsx | 7 +- web/components/onboarding/user-details.tsx | 2 - web/components/pages/single-page-block.tsx | 4 +- .../project/create-project-modal.tsx | 1 - web/components/project/label-select.tsx | 72 +++---- web/components/project/member-select.tsx | 1 - web/components/project/members-select.tsx | 66 ++++--- web/components/project/priority-select.tsx | 156 ++++++++------- .../project/send-project-invitation-modal.tsx | 5 +- web/components/states/state-select.tsx | 67 ++++--- web/components/ui/dropdowns/custom-menu.tsx | 187 +++++++++--------- .../ui/dropdowns/custom-search-select.tsx | 91 +++++---- web/components/ui/dropdowns/custom-select.tsx | 137 +++++++------ web/components/ui/dropdowns/types.d.ts | 7 +- web/package.json | 2 + .../[workspaceSlug]/me/profile/index.tsx | 3 - .../projects/[projectId]/pages/[pageId].tsx | 7 +- .../projects/[projectId]/settings/members.tsx | 5 +- .../[workspaceSlug]/settings/members.tsx | 5 +- yarn.lock | 9 +- 43 files changed, 449 insertions(+), 452 deletions(-) diff --git a/web/components/automation/auto-archive-automation.tsx b/web/components/automation/auto-archive-automation.tsx index d0c9a1be6..5aed7e852 100644 --- a/web/components/automation/auto-archive-automation.tsx +++ b/web/components/automation/auto-archive-automation.tsx @@ -74,7 +74,6 @@ export const AutoArchiveAutomation: React.FC = ({ handleChange({ archive_in: val }); }} input - verticalPosition="bottom" width="w-full" disabled={disabled} > diff --git a/web/components/core/theme/theme-switch.tsx b/web/components/core/theme/theme-switch.tsx index 687998c25..56d07fbad 100644 --- a/web/components/core/theme/theme-switch.tsx +++ b/web/components/core/theme/theme-switch.tsx @@ -100,7 +100,6 @@ export const ThemeSwitch: React.FC = observer( }} input width="w-full" - position="right" > {THEMES_OBJ.map(({ value, label, type, icon }) => ( diff --git a/web/components/core/views/board-view/single-board.tsx b/web/components/core/views/board-view/single-board.tsx index 6d583e772..1325f8cab 100644 --- a/web/components/core/views/board-view/single-board.tsx +++ b/web/components/core/views/board-view/single-board.tsx @@ -255,15 +255,11 @@ export const SingleBoard: React.FC = (props) => { !isDraftIssuesPage && ( +
Add Issue - +
} - position="left" noBorder > = ({ {type && !isNotAllowed && ( setIsMenuActive(!isMenuActive)} > - + } > = (props) => { } - position="right" noBorder > setIsCreateIssueFormOpen(true)}> diff --git a/web/components/core/views/spreadsheet-view/spreadsheet-view.tsx b/web/components/core/views/spreadsheet-view/spreadsheet-view.tsx index 0d9214a36..3a5108410 100644 --- a/web/components/core/views/spreadsheet-view/spreadsheet-view.tsx +++ b/web/components/core/views/spreadsheet-view/spreadsheet-view.tsx @@ -255,7 +255,6 @@ export const SpreadsheetView: React.FC = ({ = ({ +
New Issue - +
} - position="left" - verticalPosition="top" optionsClassName="left-5 !w-36" noBorder > diff --git a/web/components/integration/github/select-repository.tsx b/web/components/integration/github/select-repository.tsx index b46942e6d..ddc5e3a8a 100644 --- a/web/components/integration/github/select-repository.tsx +++ b/web/components/integration/github/select-repository.tsx @@ -92,7 +92,6 @@ export const SelectRepository: React.FC = ({ )} } - position="right" optionsClassName="w-full" /> ); diff --git a/web/components/integration/jira/give-details.tsx b/web/components/integration/jira/give-details.tsx index 3fc5d9641..fa04c3404 100644 --- a/web/components/integration/jira/give-details.tsx +++ b/web/components/integration/jira/give-details.tsx @@ -143,7 +143,6 @@ export const JiraGetImportDetail: React.FC = () => { )} } - verticalPosition="top" > {projects && projects.length > 0 ? ( projects.map((project) => ( diff --git a/web/components/issues/main-content.tsx b/web/components/issues/main-content.tsx index c4c9a780a..810f8fc95 100644 --- a/web/components/issues/main-content.tsx +++ b/web/components/issues/main-content.tsx @@ -158,7 +158,7 @@ export const IssueMainContent: React.FC = ({ - + {siblingIssuesList ? ( siblingIssuesList.length > 0 ? ( <> diff --git a/web/components/issues/peek-overview/header.tsx b/web/components/issues/peek-overview/header.tsx index 266b2edb8..b607e4089 100644 --- a/web/components/issues/peek-overview/header.tsx +++ b/web/components/issues/peek-overview/header.tsx @@ -93,7 +93,6 @@ export const PeekOverviewHeader: React.FC = ({ m.key === mode)?.icon ?? ""} /> } - position="left" > {peekModes.map((mode) => ( diff --git a/web/components/issues/select/estimate.tsx b/web/components/issues/select/estimate.tsx index 0d910013b..7ac86f6b9 100644 --- a/web/components/issues/select/estimate.tsx +++ b/web/components/issues/select/estimate.tsx @@ -33,7 +33,6 @@ export const IssueEstimateSelect: React.FC = ({ value, onChange }) => { } onChange={onChange} - position="right" width="w-full min-w-[8rem]" noChevron > diff --git a/web/components/issues/sidebar-select/cycle.tsx b/web/components/issues/sidebar-select/cycle.tsx index 8fbfcf707..a8dffabce 100644 --- a/web/components/issues/sidebar-select/cycle.tsx +++ b/web/components/issues/sidebar-select/cycle.tsx @@ -91,7 +91,6 @@ export const SidebarCycleSelect: React.FC = ({ : handleCycleChange(incompleteCycles?.find((c) => c.id === value) as ICycle); }} width="w-full" - position="right" maxHeight="rg" disabled={disabled} > diff --git a/web/components/issues/sidebar-select/estimate.tsx b/web/components/issues/sidebar-select/estimate.tsx index 7ebdfe2b9..75c168853 100644 --- a/web/components/issues/sidebar-select/estimate.tsx +++ b/web/components/issues/sidebar-select/estimate.tsx @@ -20,17 +20,14 @@ export const SidebarEstimateSelect: React.FC = ({ value, onChange, disabl +
{estimatePoints?.find((e) => e.key === value)?.value ?? "No estimate"} - +
} onChange={onChange} disabled={disabled} diff --git a/web/components/issues/sidebar-select/module.tsx b/web/components/issues/sidebar-select/module.tsx index c9599bdf4..edb95b983 100644 --- a/web/components/issues/sidebar-select/module.tsx +++ b/web/components/issues/sidebar-select/module.tsx @@ -87,7 +87,6 @@ export const SidebarModuleSelect: React.FC = ({ : handleModuleChange(modules?.find((m) => m.id === value) as IModule); }} width="w-full" - position="right" maxHeight="rg" disabled={disabled} > diff --git a/web/components/issues/sidebar-select/priority.tsx b/web/components/issues/sidebar-select/priority.tsx index 543f79707..512aba5c6 100644 --- a/web/components/issues/sidebar-select/priority.tsx +++ b/web/components/issues/sidebar-select/priority.tsx @@ -18,8 +18,7 @@ type Props = { export const SidebarPrioritySelect: React.FC = ({ value, onChange, disabled = false }) => ( = ({ value, onChange, disabl {value ?? "None"} - + } value={value} onChange={onChange} diff --git a/web/components/issues/sidebar-select/state.tsx b/web/components/issues/sidebar-select/state.tsx index cf2cfe3b2..7ff4f13b8 100644 --- a/web/components/issues/sidebar-select/state.tsx +++ b/web/components/issues/sidebar-select/state.tsx @@ -39,7 +39,7 @@ export const SidebarStateSelect: React.FC = ({ value, onChange, disabled return ( +
{selectedState ? (
@@ -53,12 +53,11 @@ export const SidebarStateSelect: React.FC = ({ value, onChange, disabled ) : ( "None" )} - +
} value={value} onChange={onChange} optionsClassName="w-min" - position="left" disabled={disabled} > {states ? ( diff --git a/web/components/issues/sub-issues/root.tsx b/web/components/issues/sub-issues/root.tsx index 4b29b97c9..a2c6c81a4 100644 --- a/web/components/issues/sub-issues/root.tsx +++ b/web/components/issues/sub-issues/root.tsx @@ -247,7 +247,6 @@ export const SubIssuesRoot: React.FC = ({ parentIssue, user }) = } buttonClassName="whitespace-nowrap" - position="left" noBorder noChevron > @@ -283,7 +282,6 @@ export const SubIssuesRoot: React.FC = ({ parentIssue, user }) = } buttonClassName="whitespace-nowrap" - position="left" noBorder noChevron > diff --git a/web/components/issues/view-select/assignee.tsx b/web/components/issues/view-select/assignee.tsx index 1026f240d..807d343e2 100644 --- a/web/components/issues/view-select/assignee.tsx +++ b/web/components/issues/view-select/assignee.tsx @@ -113,10 +113,8 @@ export const ViewAssigneeSelect: React.FC = ({ {...(customButton ? { customButton: assigneeLabel } : { label: assigneeLabel })} multiple noChevron - position={position} disabled={isNotAllowed} onOpen={() => setFetchAssignees(true)} - selfPositioned={selfPositioned} width="w-full min-w-[12rem]" /> ); diff --git a/web/components/issues/view-select/estimate.tsx b/web/components/issues/view-select/estimate.tsx index bef060e77..0edf5e33f 100644 --- a/web/components/issues/view-select/estimate.tsx +++ b/web/components/issues/view-select/estimate.tsx @@ -74,8 +74,6 @@ export const ViewEstimateSelect: React.FC = ({ maxHeight="md" noChevron disabled={isNotAllowed} - position={position} - selfPositioned={selfPositioned} width="w-full min-w-[8rem]" > diff --git a/web/components/issues/view-select/label.tsx b/web/components/issues/view-select/label.tsx index 3a75763b2..ae3fe1f50 100644 --- a/web/components/issues/view-select/label.tsx +++ b/web/components/issues/view-select/label.tsx @@ -146,9 +146,7 @@ export const ViewLabelSelect: React.FC = ({ {...(customButton ? { customButton: labelsLabel } : { label: labelsLabel })} multiple noChevron - position={position} disabled={isNotAllowed} - selfPositioned={selfPositioned} footerOption={footerOption} width="w-full min-w-[12rem]" /> diff --git a/web/components/issues/view-select/priority.tsx b/web/components/issues/view-select/priority.tsx index 1bdbf3428..f19b682fc 100644 --- a/web/components/issues/view-select/priority.tsx +++ b/web/components/issues/view-select/priority.tsx @@ -59,8 +59,7 @@ export const ViewPrioritySelect: React.FC = ({ }} maxHeight="md" customButton={ - +
} noChevron disabled={isNotAllowed} - position={position} - selfPositioned={selfPositioned} > {PRIORITIES?.map((priority) => ( diff --git a/web/components/modules/sidebar-select/select-lead.tsx b/web/components/modules/sidebar-select/select-lead.tsx index e00a5db25..46e08af0b 100644 --- a/web/components/modules/sidebar-select/select-lead.tsx +++ b/web/components/modules/sidebar-select/select-lead.tsx @@ -65,7 +65,6 @@ export const SidebarLeadSelect: React.FC = ({ value, onChange }) => { } options={options} maxHeight="md" - position="right" onChange={onChange} /> diff --git a/web/components/modules/sidebar-select/select-members.tsx b/web/components/modules/sidebar-select/select-members.tsx index 185f70cec..7aa1e8e5e 100644 --- a/web/components/modules/sidebar-select/select-members.tsx +++ b/web/components/modules/sidebar-select/select-members.tsx @@ -64,7 +64,6 @@ export const SidebarMembersSelect: React.FC = ({ value, onChange }) => { options={options} onChange={onChange} maxHeight="md" - position="right" multiple /> diff --git a/web/components/notifications/notification-card.tsx b/web/components/notifications/notification-card.tsx index 29fe579a8..acbd1c0c9 100644 --- a/web/components/notifications/notification-card.tsx +++ b/web/components/notifications/notification-card.tsx @@ -214,12 +214,9 @@ export const NotificationCard: React.FC = (props) => { e.stopPropagation(); }} customButton={ - + } optionsClassName="!z-20" > diff --git a/web/components/onboarding/user-details.tsx b/web/components/onboarding/user-details.tsx index 2911bbc80..8ac8b2070 100644 --- a/web/components/onboarding/user-details.tsx +++ b/web/components/onboarding/user-details.tsx @@ -167,7 +167,6 @@ export const UserDetails: React.FC = ({ user }) => { } input width="w-full" - verticalPosition="top" > {USER_ROLES.map((item) => ( @@ -197,7 +196,6 @@ export const UserDetails: React.FC = ({ user }) => { } options={timeZoneOptions} onChange={onChange} - verticalPosition="top" optionsClassName="w-full" input /> diff --git a/web/components/pages/single-page-block.tsx b/web/components/pages/single-page-block.tsx index e4c1d94ac..18fba83bc 100644 --- a/web/components/pages/single-page-block.tsx +++ b/web/components/pages/single-page-block.tsx @@ -384,12 +384,12 @@ export const SinglePageBlock: React.FC = ({ setIsMenuActive(!isMenuActive)} > - + } > {block.issue ? ( diff --git a/web/components/project/create-project-modal.tsx b/web/components/project/create-project-modal.tsx index 5593d8c7c..e0afea1ce 100644 --- a/web/components/project/create-project-modal.tsx +++ b/web/components/project/create-project-modal.tsx @@ -418,7 +418,6 @@ export const CreateProjectModal: React.FC = ({ )} } - verticalPosition="top" noChevron /> ); diff --git a/web/components/project/label-select.tsx b/web/components/project/label-select.tsx index c155dea14..a34d778f9 100644 --- a/web/components/project/label-select.tsx +++ b/web/components/project/label-select.tsx @@ -1,13 +1,13 @@ -import React, { useRef, useState } from "react"; +import React, { useState } from "react"; import useSWR from "swr"; import { useRouter } from "next/router"; +// react-popper +import { usePopper } from "react-popper"; // services import issuesService from "services/issues.service"; -// hooks -import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown"; // headless ui import { Combobox } from "@headlessui/react"; // component @@ -19,6 +19,7 @@ import { PlusIcon } from "lucide-react"; // types import { Tooltip } from "components/ui"; import { ICurrentUserResponse, IIssueLabels } from "types"; +import { Placement } from "@popperjs/core"; // constants import { PROJECT_ISSUE_LABELS } from "constants/fetch-keys"; @@ -31,6 +32,7 @@ type Props = { buttonClassName?: string; optionsClassName?: string; maxRender?: number; + placement?: Placement; hideDropdownArrow?: boolean; disabled?: boolean; user: ICurrentUserResponse | undefined; @@ -45,21 +47,25 @@ export const LabelSelect: React.FC = ({ buttonClassName = "", optionsClassName = "", maxRender = 2, + placement, hideDropdownArrow = false, disabled = false, user, }) => { const [query, setQuery] = useState(""); - const [isOpen, setIsOpen] = useState(false); const [fetchStates, setFetchStates] = useState(false); + const [referenceElement, setReferenceElement] = useState(null); + const [popperElement, setPopperElement] = useState(null); + const [labelModal, setLabelModal] = useState(false); const router = useRouter(); const { workspaceSlug } = router.query; - const dropdownBtn = useRef(null); - const dropdownOptions = useRef(null); + const { styles, attributes } = usePopper(referenceElement, popperElement, { + placement: placement ?? "bottom-start", + }); const { data: issueLabels } = useSWR( projectId && fetchStates ? PROJECT_ISSUE_LABELS(projectId) : null, @@ -131,8 +137,6 @@ export const LabelSelect: React.FC = ({ ); - useDynamicDropdownPosition(isOpen, () => setIsOpen(false), dropdownBtn, dropdownOptions); - const footerOption = ( -
- +
@@ -234,8 +240,8 @@ export const LabelSelect: React.FC = ({ )}
{footerOption} - -
+
+ ); }} diff --git a/web/components/project/member-select.tsx b/web/components/project/member-select.tsx index 4fcb04268..0d29e8e8d 100644 --- a/web/components/project/member-select.tsx +++ b/web/components/project/member-select.tsx @@ -77,7 +77,6 @@ export const MemberSelect: React.FC = ({ value, onChange, isDisabled = fa ] } maxHeight="md" - position="right" width="w-full" onChange={onChange} disabled={isDisabled} diff --git a/web/components/project/members-select.tsx b/web/components/project/members-select.tsx index 57523df8d..42dd89ef4 100644 --- a/web/components/project/members-select.tsx +++ b/web/components/project/members-select.tsx @@ -1,9 +1,10 @@ -import React, { useRef, useState } from "react"; +import React, { useState } from "react"; import { useRouter } from "next/router"; +// react-popper +import { usePopper } from "react-popper"; // hooks -import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown"; import useProjectMembers from "hooks/use-project-members"; import useWorkspaceMembers from "hooks/use-workspace-members"; // headless ui @@ -15,6 +16,7 @@ import { ChevronDownIcon } from "@heroicons/react/20/solid"; import { CheckIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline"; // types import { IUser } from "types"; +import { Placement } from "@popperjs/core"; type Props = { value: string | string[]; @@ -25,6 +27,7 @@ type Props = { className?: string; buttonClassName?: string; optionsClassName?: string; + placement?: Placement; hideDropdownArrow?: boolean; disabled?: boolean; }; @@ -38,18 +41,22 @@ export const MembersSelect: React.FC = ({ className = "", buttonClassName = "", optionsClassName = "", + placement, hideDropdownArrow = false, disabled = false, }) => { const [query, setQuery] = useState(""); - const [isOpen, setIsOpen] = useState(false); const [fetchStates, setFetchStates] = useState(false); + const [referenceElement, setReferenceElement] = useState(null); + const [popperElement, setPopperElement] = useState(null); + const router = useRouter(); const { workspaceSlug } = router.query; - const dropdownBtn = useRef(null); - const dropdownOptions = useRef(null); + const { styles, attributes } = usePopper(referenceElement, popperElement, { + placement: placement ?? "bottom-start", + }); const { members } = useProjectMembers( workspaceSlug?.toString(), @@ -105,8 +112,6 @@ export const MembersSelect: React.FC = ({ ); - useDynamicDropdownPosition(isOpen, () => setIsOpen(false), dropdownBtn, dropdownOptions); - return ( = ({ multiple > {({ open }: { open: boolean }) => { - if (open) { - if (!isOpen) setIsOpen(true); - setFetchStates(true); - } else if (isOpen) setIsOpen(false); + if (open) setFetchStates(true); return ( <> - - {label} - {!hideDropdownArrow && !disabled && ( -