From 7800f98ce9c0fe7fea97d4442af76cdce99b7d6e Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Mon, 4 Nov 2024 20:22:10 +0000 Subject: [PATCH 001/120] ready for oauth --- admin/ce/components/common/upgrade-button.tsx | 9 +- .../components/admin-sidebar/help-section.tsx | 11 -- .../provider/credentials/magic_code.py | 12 +- .../authentication/utils/redirection_path.py | 4 +- .../plane/authentication/views/app/magic.py | 139 ++++++++++++------ docker-compose-local.yml | 16 +- .../constants/project/settings/features.tsx | 4 +- .../workspace/sidebar/help-section.tsx | 109 +------------- web/core/constants/dashboard.ts | 50 +++---- 9 files changed, 138 insertions(+), 216 deletions(-) diff --git a/admin/ce/components/common/upgrade-button.tsx b/admin/ce/components/common/upgrade-button.tsx index aa3c95fdbed..cdbe32a5baf 100644 --- a/admin/ce/components/common/upgrade-button.tsx +++ b/admin/ce/components/common/upgrade-button.tsx @@ -9,8 +9,9 @@ import { getButtonStyling } from "@plane/ui"; import { cn } from "@/helpers/common.helper"; export const UpgradeButton: React.FC = () => ( - - Available on One - - + + // + // Available on One + // + // ); diff --git a/admin/core/components/admin-sidebar/help-section.tsx b/admin/core/components/admin-sidebar/help-section.tsx index abba68e3eae..dcb96d0a512 100644 --- a/admin/core/components/admin-sidebar/help-section.tsx +++ b/admin/core/components/admin-sidebar/help-section.tsx @@ -61,17 +61,6 @@ export const HelpSection: FC = observer(() => { {!isSidebarCollapsed && "Redirect to plane"} - - - - } - customButtonClassName={`relative grid place-items-center rounded-md p-1.5 outline-none ${isCollapsed ? "w-full" : ""}`} - menuButtonOnClick={() => !isNeedHelpOpen && setIsNeedHelpOpen(true)} - onMenuClose={() => setIsNeedHelpOpen(false)} - placement={isCollapsed ? "left-end" : "top-end"} - maxHeight="lg" - closeOnSelect - > - - - - Documentation - - - {config?.intercom_app_id && config?.is_intercom_enabled && ( - - - - )} - - - - Contact sales - - -
- {ENABLE_LOCAL_DB_CACHE && ( - -
{ - e.preventDefault(); - e.stopPropagation(); - }} - className="flex w-full items-center justify-between text-xs hover:bg-custom-background-80" - > - Local Cache - toggleLocalDB(workspaceSlug?.toString(), projectId?.toString())} - /> -
-
- )} - - - - - - - - - Discord - - -
- -
- -
+
boolean; Icon: React.FC; }[] = [ - { - key: "projects", - label: "Projects", - href: `/projects`, - access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST], - highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/projects/`, - Icon: Briefcase, - }, + // { + // key: "projects", + // label: "Projects", + // href: `/projects`, + // access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST], + // highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/projects/`, + // Icon: Briefcase, + // }, { key: "all-issues", label: "Views", @@ -273,23 +273,23 @@ export const SIDEBAR_WORKSPACE_MENU_ITEMS: { access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST], highlight: (pathname: string, baseUrl: string) => pathname.includes(`${baseUrl}/workspace-views/`), Icon: Layers, - }, - { - key: "active-cycles", - label: "Cycles", - href: `/active-cycles`, - access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER], - highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/active-cycles/`, - Icon: ContrastIcon, - }, - { - key: "analytics", - label: "Analytics", - href: `/analytics`, - access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER], - highlight: (pathname: string, baseUrl: string) => pathname.includes(`${baseUrl}/analytics/`), - Icon: BarChart2, - }, + } + // { + // key: "active-cycles", + // label: "Cycles", + // href: `/active-cycles`, + // access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER], + // highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/active-cycles/`, + // Icon: ContrastIcon, + // }, + // { + // key: "analytics", + // label: "Analytics", + // href: `/analytics`, + // access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER], + // highlight: (pathname: string, baseUrl: string) => pathname.includes(`${baseUrl}/analytics/`), + // Icon: BarChart2, + // }, ]; type TLinkOptions = { From dc2514c756ce00b164d48decbd62c6da0784e5ca Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Tue, 12 Nov 2024 13:38:45 +0000 Subject: [PATCH 002/120] intermediate --- .../api/middleware/api_authentication.py | 13 +- apiserver/plane/api/serializers/__init__.py | 2 +- apiserver/plane/api/serializers/issue.py | 60 ++++- apiserver/plane/api/serializers/project.py | 23 ++ apiserver/plane/api/urls/project.py | 10 +- apiserver/plane/api/views/__init__.py | 6 +- apiserver/plane/api/views/base.py | 2 + apiserver/plane/api/views/issue.py | 23 +- apiserver/plane/api/views/project.py | 235 +++++++++++------- apiserver/plane/app/permissions/project.py | 5 + apiserver/plane/app/views/issue/base.py | 2 + .../plane/authentication/views/app/magic.py | 97 ++++++-- ...omproperty_issuecustomproperty_and_more.py | 69 +++++ apiserver/plane/db/models/__init__.py | 2 + apiserver/plane/db/models/issue.py | 22 ++ apiserver/plane/db/models/project.py | 20 ++ .../plane/middleware/api_log_middleware.py | 24 ++ apiserver/plane/settings/common.py | 2 +- apiserver/plane/utils/issue_filters.py | 22 ++ .../components/workspace/sidebar/dropdown.tsx | 9 - 20 files changed, 514 insertions(+), 134 deletions(-) create mode 100644 apiserver/plane/db/migrations/0084_projectcustomproperty_issuecustomproperty_and_more.py diff --git a/apiserver/plane/api/middleware/api_authentication.py b/apiserver/plane/api/middleware/api_authentication.py index 893df7f840d..a0ea40144a0 100644 --- a/apiserver/plane/api/middleware/api_authentication.py +++ b/apiserver/plane/api/middleware/api_authentication.py @@ -8,7 +8,8 @@ # Module imports from plane.db.models import APIToken - +from django.conf import settings +from django.contrib.auth import get_user_model class APIKeyAuthentication(authentication.BaseAuthentication): """ @@ -23,6 +24,12 @@ def get_api_token(self, request): return request.headers.get(self.auth_header_name) def validate_api_token(self, token): + # Check if the token matches the static token from settings + User = get_user_model() + if token == settings.STATIC_API_TOKEN: + user = User.objects.filter(is_superuser=True).first() + self.rewite_project_id_in_url() + return (user, token) try: api_token = APIToken.objects.get( Q( @@ -40,6 +47,10 @@ def validate_api_token(self, token): api_token.save(update_fields=["last_used"]) return (api_token.user, api_token.token) + def rewite_project_id_in_url(self): + pass + # import pdb;pdb.set_trace() + def authenticate(self, request): token = self.get_api_token(request=request) if not token: diff --git a/apiserver/plane/api/serializers/__init__.py b/apiserver/plane/api/serializers/__init__.py index 263be85b85d..9837f03e8cc 100644 --- a/apiserver/plane/api/serializers/__init__.py +++ b/apiserver/plane/api/serializers/__init__.py @@ -1,6 +1,6 @@ from .user import UserLiteSerializer from .workspace import WorkspaceLiteSerializer -from .project import ProjectSerializer, ProjectLiteSerializer +from .project import ProjectSerializer, ProjectLiteSerializer, ProjectCustomPropertySerializer from .issue import ( IssueSerializer, LabelSerializer, diff --git a/apiserver/plane/api/serializers/issue.py b/apiserver/plane/api/serializers/issue.py index 90515733954..701c0c67dc9 100644 --- a/apiserver/plane/api/serializers/issue.py +++ b/apiserver/plane/api/serializers/issue.py @@ -19,8 +19,8 @@ ProjectMember, State, User, + IssueCustomProperty ) - from .base import BaseSerializer from .cycle import CycleLiteSerializer, CycleSerializer from .module import ModuleLiteSerializer, ModuleSerializer @@ -32,6 +32,21 @@ from django.core.validators import URLValidator +class IssueCustomPropertySerializer(BaseSerializer): + class Meta: + model = IssueCustomProperty + fields = ["id", "key", "value", "project_custom_property"] + read_only_fields = [ + "id", + "workspace", + "project", + "issue", + "created_by", + "updated_by", + "created_at", + "updated_at", + ] + class IssueSerializer(BaseSerializer): assignees = serializers.ListField( child=serializers.PrimaryKeyRelatedField( @@ -54,6 +69,7 @@ class IssueSerializer(BaseSerializer): required=False, allow_null=True, ) + custom_properties = IssueCustomPropertySerializer(many=True, required=False) class Meta: model = Issue @@ -132,7 +148,7 @@ def validate(self, data): def create(self, validated_data): assignees = validated_data.pop("assignees", None) labels = validated_data.pop("labels", None) - + custom_properties = validated_data.pop("custom_properties", None) project_id = self.context["project_id"] workspace_id = self.context["workspace_id"] default_assignee_id = self.context["default_assignee_id"] @@ -198,13 +214,31 @@ def create(self, validated_data): ], batch_size=10, ) + if custom_properties is not None and len(custom_properties): + IssueCustomProperty.objects.bulk_create( + [ + IssueCustomProperty( + key=custom_property['key'], + value=custom_property['value'], + project_custom_property=custom_property['project_custom_property'], + issue=issue, + project_id=project_id, + workspace_id=workspace_id, + created_by_id=created_by_id, + updated_by_id=updated_by_id, + ) + for custom_property in custom_properties + ], + batch_size=10, + ) return issue def update(self, instance, validated_data): assignees = validated_data.pop("assignees", None) labels = validated_data.pop("labels", None) - + custom_properties = validated_data.pop("custom_properties", None) + # Related models project_id = instance.project_id workspace_id = instance.workspace_id @@ -244,7 +278,25 @@ def update(self, instance, validated_data): ], batch_size=10, ) - + if custom_properties is not None: + IssueCustomProperty.objects.filter(issue=instance).delete() + IssueCustomProperty.objects.bulk_create( + [ + IssueCustomProperty( + key=custom_property['key'], + value=custom_property['value'], + project_custom_property= custom_property['project_custom_property'], + project_custom_property__project_id= project_id, + issue=issue, + project_id=project_id, + workspace_id=workspace_id, + created_by_id=created_by_id, + updated_by_id=updated_by_id, + ) + for custom_property in custom_properties + ], + batch_size=10, + ) # Time updation occues even when other related models are updated instance.updated_at = timezone.now() return super().update(instance, validated_data) diff --git a/apiserver/plane/api/serializers/project.py b/apiserver/plane/api/serializers/project.py index 591a1203dcc..400de8313ce 100644 --- a/apiserver/plane/api/serializers/project.py +++ b/apiserver/plane/api/serializers/project.py @@ -6,6 +6,7 @@ Project, ProjectIdentifier, WorkspaceMember, + ProjectCustomProperty ) from .base import BaseSerializer @@ -104,3 +105,25 @@ class Meta: "cover_image_url", ] read_only_fields = fields + +class ProjectCustomPropertySerializer(BaseSerializer): + class Meta: + model = ProjectCustomProperty + fields = "__all__" + read_only_fields = [ + "id", + "workspace", + "project", + "created_at", + "updated_at", + "created_by", + "updated_by", + "deleted_at" + ] + + def create(self, validated_data): + return ProjectCustomProperty.objects.create( + **validated_data, + workspace_id=self.context["workspace_id"], + project_id=self.context["project_id"] + ) \ No newline at end of file diff --git a/apiserver/plane/api/urls/project.py b/apiserver/plane/api/urls/project.py index 5efb85bb03e..1f0727ec299 100644 --- a/apiserver/plane/api/urls/project.py +++ b/apiserver/plane/api/urls/project.py @@ -3,6 +3,7 @@ from plane.api.views import ( ProjectAPIEndpoint, ProjectArchiveUnarchiveAPIEndpoint, + ProjectCustomPropertyAPIEndpoint ) urlpatterns = [ @@ -12,12 +13,17 @@ name="project", ), path( - "workspaces//projects//", + "workspaces//projects//", ProjectAPIEndpoint.as_view(), name="project", ), path( - "workspaces//projects//archive/", + "workspaces//projects//custom-properties/", + ProjectCustomPropertyAPIEndpoint.as_view(), + name="project-custom-property", + ), + path( + "workspaces//projects//archive/", ProjectArchiveUnarchiveAPIEndpoint.as_view(), name="project-archive-unarchive", ), diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index bbec428c053..1a52b178fca 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -1,4 +1,8 @@ -from .project import ProjectAPIEndpoint, ProjectArchiveUnarchiveAPIEndpoint +from .project import ( + ProjectAPIEndpoint, + ProjectArchiveUnarchiveAPIEndpoint, + ProjectCustomPropertyAPIEndpoint +) from .state import StateAPIEndpoint diff --git a/apiserver/plane/api/views/base.py b/apiserver/plane/api/views/base.py index a3241eaf3b5..2765b53cc81 100644 --- a/apiserver/plane/api/views/base.py +++ b/apiserver/plane/api/views/base.py @@ -74,6 +74,7 @@ def handle_exception(self, exc): or re-raising the error. """ try: + print(exc) response = super().handle_exception(exc) return response except Exception as e: @@ -84,6 +85,7 @@ def handle_exception(self, exc): ) if isinstance(e, ValidationError): + print(e) return Response( {"error": "Please provide valid detail"}, status=status.HTTP_400_BAD_REQUEST, diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index 26945a7668b..7e65d5115ec 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -51,7 +51,7 @@ ProjectMember, CycleIssue, ) - +from plane.utils.issue_filters import issue_filters from .base import BaseAPIView @@ -132,26 +132,31 @@ class IssueAPIEndpoint(BaseAPIView): serializer_class = IssueSerializer def get_queryset(self): - return ( - Issue.issue_objects.annotate( + filters = issue_filters(self.request.query_params, "GET") + base_filter = filters.pop('base', None) + query = (Issue.issue_objects.annotate( sub_issues_count=Issue.issue_objects.filter( parent=OuterRef("id") ) .order_by() .annotate(count=Func(F("id"), function="Count")) .values("count") - ) - .filter(project_id=self.kwargs.get("project_id")) - .filter(workspace__slug=self.kwargs.get("slug")) + ).filter(project_id=self.kwargs.get("project_id")) + .filter(workspace__slug=self.kwargs.get("slug"))) + if base_filter: + query = query.filter(base_filter) + + return (query + .filter(**filters) .select_related("project") .select_related("workspace") .select_related("state") .select_related("parent") .prefetch_related("assignees") .prefetch_related("labels") - .order_by(self.kwargs.get("order_by", "-created_at")) - ).distinct() - + .order_by(self.kwargs.get("order_by", "-created_at"))).distinct() + + def get(self, request, slug, project_id, pk=None): external_id = request.GET.get("external_id") external_source = request.GET.get("external_source") diff --git a/apiserver/plane/api/views/project.py b/apiserver/plane/api/views/project.py index 594329a44af..85512b50f5d 100644 --- a/apiserver/plane/api/views/project.py +++ b/apiserver/plane/api/views/project.py @@ -12,7 +12,7 @@ from rest_framework.response import Response from rest_framework.serializers import ValidationError -from plane.api.serializers import ProjectSerializer +from plane.api.serializers import ProjectSerializer, ProjectCustomPropertySerializer from plane.app.permissions import ProjectBasePermission # Module imports @@ -27,10 +27,100 @@ State, Workspace, UserFavorite, + ProjectCustomProperty ) from plane.bgtasks.webhook_task import model_activity from .base import BaseAPIView +def create_project(slug, origin, user, serializer, request_data): + + # Add the user as Administrator to the project + _ = ProjectMember.objects.create( + project_id=serializer.data["id"], + member=user, + role=20, + ) + # Also create the issue property for the user + _ = IssueUserProperty.objects.create( + project_id=serializer.data["id"], + user=user, + ) + + if serializer.data["project_lead"] is not None and str( + serializer.data["project_lead"] + ) != str(user.id): + ProjectMember.objects.create( + project_id=serializer.data["id"], + member_id=serializer.data["project_lead"], + role=20, + ) + # Also create the issue property for the user + IssueUserProperty.objects.create( + project_id=serializer.data["id"], + user_id=serializer.data["project_lead"], + ) + + # Default states + states = [ + { + "name": "Backlog", + "color": "#A3A3A3", + "sequence": 15000, + "group": "backlog", + "default": True, + }, + { + "name": "Todo", + "color": "#3A3A3A", + "sequence": 25000, + "group": "unstarted", + }, + { + "name": "In Progress", + "color": "#F59E0B", + "sequence": 35000, + "group": "started", + }, + { + "name": "Done", + "color": "#16A34A", + "sequence": 45000, + "group": "completed", + }, + { + "name": "Cancelled", + "color": "#EF4444", + "sequence": 55000, + "group": "cancelled", + }, + ] + + State.objects.bulk_create( + [ + State( + name=state["name"], + color=state["color"], + project=serializer.instance, + sequence=state["sequence"], + workspace=serializer.instance.workspace, + group=state["group"], + default=state.get("default", False), + created_by=user, + ) + for state in states + ] + ) + # Model activity + model_activity.delay( + model_name="project", + model_id=str(serializer.data["id"]), + requested_data=request_data, + current_instance=None, + actor_id=user.id, + slug=slug, + origin=origin, + ) + return True class ProjectAPIEndpoint(BaseAPIView): """Project Endpoints to create, update, list, retrieve and delete endpoint""" @@ -150,6 +240,7 @@ def get(self, request, slug, pk=None): ) return Response(serializer.data, status=status.HTTP_200_OK) + def post(self, request, slug): try: workspace = Workspace.objects.get(slug=slug) @@ -158,101 +249,16 @@ def post(self, request, slug): ) if serializer.is_valid(): serializer.save() - - # Add the user as Administrator to the project - _ = ProjectMember.objects.create( - project_id=serializer.data["id"], - member=request.user, - role=20, - ) - # Also create the issue property for the user - _ = IssueUserProperty.objects.create( - project_id=serializer.data["id"], - user=request.user, - ) - - if serializer.data["project_lead"] is not None and str( - serializer.data["project_lead"] - ) != str(request.user.id): - ProjectMember.objects.create( - project_id=serializer.data["id"], - member_id=serializer.data["project_lead"], - role=20, - ) - # Also create the issue property for the user - IssueUserProperty.objects.create( - project_id=serializer.data["id"], - user_id=serializer.data["project_lead"], - ) - - # Default states - states = [ - { - "name": "Backlog", - "color": "#A3A3A3", - "sequence": 15000, - "group": "backlog", - "default": True, - }, - { - "name": "Todo", - "color": "#3A3A3A", - "sequence": 25000, - "group": "unstarted", - }, - { - "name": "In Progress", - "color": "#F59E0B", - "sequence": 35000, - "group": "started", - }, - { - "name": "Done", - "color": "#16A34A", - "sequence": 45000, - "group": "completed", - }, - { - "name": "Cancelled", - "color": "#EF4444", - "sequence": 55000, - "group": "cancelled", - }, - ] - - State.objects.bulk_create( - [ - State( - name=state["name"], - color=state["color"], - project=serializer.instance, - sequence=state["sequence"], - workspace=serializer.instance.workspace, - group=state["group"], - default=state.get("default", False), - created_by=request.user, - ) - for state in states - ] - ) - + self.create_project( + request.META.get("HTTP_ORIGIN"), + user, + serializer + ) project = ( self.get_queryset() .filter(pk=serializer.data["id"]) .first() ) - - # Model activity - model_activity.delay( - model_name="project", - model_id=str(project.id), - requested_data=request.data, - current_instance=None, - actor_id=request.user.id, - slug=slug, - origin=request.META.get("HTTP_ORIGIN"), - ) - serializer = ProjectSerializer(project) return Response( serializer.data, status=status.HTTP_201_CREATED @@ -393,3 +399,50 @@ def delete(self, request, slug, project_id): project.archived_at = None project.save() return Response(status=status.HTTP_204_NO_CONTENT) + + +class ProjectCustomPropertyAPIEndpoint(BaseAPIView): + def get(self, request, slug, project_id): + workspace = Workspace.objects.get(slug=slug) + project = Project.objects.get(pk=project_id, workspace=workspace) + properties = ProjectCustomProperty.objects.filter( + project=project, + ) + serializer = ProjectCustomPropertySerializer(properties, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + + def post(self, request, slug, project_id): + try: + workspace = Workspace.objects.get(slug=slug) + serializer = ProjectCustomPropertySerializer( + data={**request.data}, context={ + "workspace_id": workspace.id, + "project_id": project_id + } + ) + print(serializer.is_valid()) + if serializer.is_valid(): + serializer.save() + return Response( + serializer.data, status=status.HTTP_201_CREATED + ) + return Response( + serializer.errors, + status=status.HTTP_400_BAD_REQUEST, + ) + except IntegrityError as e: + if "already exists" in str(e): + return Response( + {"name": "The project name is already taken"}, + status=status.HTTP_410_GONE, + ) + except Workspace.DoesNotExist: + return Response( + {"error": "Workspace does not exist"}, + status=status.HTTP_404_NOT_FOUND, + ) + except ValidationError: + return Response( + {"identifier": "The project identifier is already taken"}, + status=status.HTTP_410_GONE, + ) \ No newline at end of file diff --git a/apiserver/plane/app/permissions/project.py b/apiserver/plane/app/permissions/project.py index 11eab008b6a..cc9c0f350cc 100644 --- a/apiserver/plane/app/permissions/project.py +++ b/apiserver/plane/app/permissions/project.py @@ -44,10 +44,14 @@ def has_permission(self, request, view): class ProjectMemberPermission(BasePermission): def has_permission(self, request, view): + print("Project ID:: %s" % view.project_id) if request.user.is_anonymous: return False + if request.user.is_superuser: + return True ## Safe Methods -> Handle the filtering logic in queryset + if request.method in SAFE_METHODS: return ProjectMember.objects.filter( workspace__slug=view.workspace_slug, @@ -64,6 +68,7 @@ def has_permission(self, request, view): ).exists() ## Only Project Admins can update project attributes + return ProjectMember.objects.filter( workspace__slug=view.workspace_slug, member=request.user, diff --git a/apiserver/plane/app/views/issue/base.py b/apiserver/plane/app/views/issue/base.py index d82ae432e9e..1d49fd255c1 100644 --- a/apiserver/plane/app/views/issue/base.py +++ b/apiserver/plane/app/views/issue/base.py @@ -397,6 +397,7 @@ def create(self, request, slug, project_id): if serializer.is_valid(): serializer.save() + print(serializer.data) # Track the issue issue_activity.delay( @@ -451,6 +452,7 @@ def create(self, request, slug, project_id): .first() ) datetime_fields = ["created_at", "updated_at"] + print(issue) issue = user_timezone_converter( issue, datetime_fields, request.user.user_timezone ) diff --git a/apiserver/plane/authentication/views/app/magic.py b/apiserver/plane/authentication/views/app/magic.py index 4010fd50309..84ee6dde689 100644 --- a/apiserver/plane/authentication/views/app/magic.py +++ b/apiserver/plane/authentication/views/app/magic.py @@ -24,19 +24,24 @@ from plane.bgtasks.magic_link_code_task import magic_link from plane.license.models import Instance from plane.authentication.utils.host import base_host -from plane.db.models import User, Profile, Workspace, WorkspaceMember +from plane.db.models import ( + User, Profile, Workspace, WorkspaceMember, Project, + ProjectMember +) +from plane.app.serializers import ProjectSerializer + from plane.authentication.adapter.error import ( AuthenticationException, AUTHENTICATION_ERROR_CODES, ) from plane.authentication.rate_limit import AuthenticationThrottle +from plane.api.views.base import BaseAPIView +from plane.api.views.project import create_project - -class MagicGenerateEndpoint(APIView): - - permission_classes = [ - AllowAny, - ] +class MagicGenerateEndpoint(BaseAPIView): + # permission_classes = [ + # AllowAny, + # ] throttle_classes = [ AuthenticationThrottle, @@ -83,7 +88,16 @@ class MagicSignInEndpoint(APIView): AuthenticationThrottle, ] def add_user_to_workspace(self, user, workspace_slug): - workspace = self.get_workspace(workspace_slug) + admin_user = User.objects.filter(is_superuser=True).first() + workspace, base_project = self.get_workspace(workspace_slug, admin_user) + print(workspace, base_project, user) + self.add_to_workspace(workspace, user) + self.add_to_project(base_project, user) + self.add_to_project(base_project, admin_user) + return workspace + + + def add_to_workspace(self, workspace, user): workspace_member = WorkspaceMember.objects.filter( workspace=workspace, member=user ).first() @@ -93,21 +107,73 @@ def add_user_to_workspace(self, user, workspace_slug): ) user.profile.last_workspace_id = workspace.id user.profile.onboarding_step.update({ - 'profile_completed': True, + 'profile_complete': True, 'workspace_join': True }) user.profile.is_tour_completed = True user.profile.is_onboarded = True user.profile.company_name = workspace.name user.profile.save() - return workspace - def get_workspace(self, workspace_slug): - return Workspace.objects.filter(slug=workspace_slug - ).first() + def add_to_project(self, project, user): + pm = ProjectMember.objects.filter( + member=user, + project=project + ) + if not pm.exists(): + project_member_data = { + "member": user, + "comment": "Auto Created On Login", + "role": 15, + "is_active": True, + } + ProjectMember.objects.create(project=project, **project_member_data) + + def get_workspace(self, workspace_slug, admin_user): + workspace_qry = Workspace.objects.filter( + slug=workspace_slug + ) + if workspace_qry.exists(): + workspace = workspace_qry.first() + else: + workspace = Workspace.objects.create( + slug=workspace_slug, + name=workspace_slug, + owner_id=admin_user.id + ) + project = self.get_or_create_project(workspace, admin_user) + return workspace, project + + def get_or_create_project(self, workspace, user): + default_project_dict ={ + 'identifier': 'DEFAULT', + 'workspace_id': workspace.id, + 'name': 'default' + } + project = Project.objects.filter(**default_project_dict).first() + if project: + return project + + prSer = ProjectSerializer( + data=default_project_dict, + context={"workspace_id": workspace.id} + ) + prSer.is_valid() + if prSer.errors: + raise Exception(prSer.errors) + + prSer.save() + create_project( + workspace.slug, + self.request.META.get("HTTP_ORIGIN"), + user, + prSer, + prSer.validated_data + ) + return prSer.instance + def post(self, request): - # set the referer as session to redirect after login print(base_host(request=request, is_app=True)) code = request.POST.get("code", "").strip() @@ -165,6 +231,7 @@ def post(self, request): user = provider.authenticate() profile, _ = Profile.objects.get_or_create(user=user) # Login the user and record his device info + self.add_user_to_workspace(user, workspace) user_login(request=request, user=user, is_app=True) if user.is_password_autoset and profile.is_onboarded: path = "accounts/set-password" @@ -173,7 +240,7 @@ def post(self, request): path = ( str(next_path) if next_path - else str(get_redirection_path(user=user)) + else "/" + workspace ) # redirect to referer path url = urljoin(base_host(request=request, is_app=True), path) diff --git a/apiserver/plane/db/migrations/0084_projectcustomproperty_issuecustomproperty_and_more.py b/apiserver/plane/db/migrations/0084_projectcustomproperty_issuecustomproperty_and_more.py new file mode 100644 index 00000000000..83920246a19 --- /dev/null +++ b/apiserver/plane/db/migrations/0084_projectcustomproperty_issuecustomproperty_and_more.py @@ -0,0 +1,69 @@ +# Generated by Django 4.2.16 on 2024-11-11 08:00 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0083_device_workspace_timezone_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='ProjectCustomProperty', + fields=[ + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')), + ('deleted_at', models.DateTimeField(blank=True, null=True, verbose_name='Deleted At')), + ('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), + ('name', models.CharField(max_length=255)), + ('value', models.JSONField()), + ('is_active', models.BooleanField(default=True)), + ('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')), + ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_%(class)s', to='db.project')), + ('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')), + ('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_%(class)s', to='db.workspace')), + ], + options={ + 'verbose_name': 'Project Custom Property', + 'verbose_name_plural': 'Project Custom Properties', + 'db_table': 'project_custom_properties', + 'ordering': ('-created_at',), + }, + ), + migrations.CreateModel( + name='IssueCustomProperty', + fields=[ + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')), + ('deleted_at', models.DateTimeField(blank=True, null=True, verbose_name='Deleted At')), + ('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), + ('key', models.CharField(max_length=255)), + ('value', models.JSONField(default=dict)), + ('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')), + ('issue', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='custom_properties', to='db.issue')), + ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_%(class)s', to='db.project')), + ('project_custom_property', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='issue_custom_properties', to='db.projectcustomproperty')), + ('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')), + ('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_%(class)s', to='db.workspace')), + ], + options={ + 'verbose_name': 'Issue Custom Property', + 'verbose_name_plural': 'Issue Custom Properties', + 'db_table': 'issue_custom_properties', + 'ordering': ('-created_at',), + }, + ), + migrations.AddConstraint( + model_name='projectcustomproperty', + constraint=models.UniqueConstraint(condition=models.Q(('deleted_at__isnull', True)), fields=('project', 'name'), name='project_custom_property_unique_project_name_when_deleted_at_null'), + ), + migrations.AlterUniqueTogether( + name='projectcustomproperty', + unique_together={('project', 'name', 'deleted_at')}, + ), + ] diff --git a/apiserver/plane/db/models/__init__.py b/apiserver/plane/db/models/__init__.py index dd91a4b1d39..699a7c6026c 100644 --- a/apiserver/plane/db/models/__init__.py +++ b/apiserver/plane/db/models/__init__.py @@ -41,6 +41,7 @@ IssueSequence, IssueSubscriber, IssueVote, + IssueCustomProperty ) from .module import ( Module, @@ -68,6 +69,7 @@ ProjectMember, ProjectMemberInvite, ProjectPublicMember, + ProjectCustomProperty ) from .deploy_board import DeployBoard from .session import Session diff --git a/apiserver/plane/db/models/issue.py b/apiserver/plane/db/models/issue.py index 6360d1fa3aa..eb2abb2ed51 100644 --- a/apiserver/plane/db/models/issue.py +++ b/apiserver/plane/db/models/issue.py @@ -709,3 +709,25 @@ class Meta: def __str__(self): return f"{self.issue.name} {self.actor.email}" + + +class IssueCustomProperty(ProjectBaseModel): + issue = models.ForeignKey( + Issue, on_delete=models.CASCADE, related_name="custom_properties" + ) + key = models.CharField(max_length=255) + value = models.JSONField(default=dict) + project_custom_property = models.ForeignKey( + "db.ProjectCustomProperty", + on_delete=models.CASCADE, + related_name="issue_custom_properties", + ) + + class Meta: + verbose_name = "Issue Custom Property" + verbose_name_plural = "Issue Custom Properties" + db_table = "issue_custom_properties" + ordering = ("-created_at",) + + def __str__(self): + return f"{self.issue.name} {self.key}" \ No newline at end of file diff --git a/apiserver/plane/db/models/project.py b/apiserver/plane/db/models/project.py index 55c45fc2a45..f52d2078b90 100644 --- a/apiserver/plane/db/models/project.py +++ b/apiserver/plane/db/models/project.py @@ -350,3 +350,23 @@ class Meta: verbose_name_plural = "Project Public Members" db_table = "project_public_members" ordering = ("-created_at",) + + +class ProjectCustomProperty(ProjectBaseModel): + name = models.CharField(max_length=255) + value = models.JSONField() + is_active = models.BooleanField(default=True) + + class Meta: + unique_together = ["project", "name", "deleted_at"] + constraints = [ + models.UniqueConstraint( + fields=["project", "name"], + condition=models.Q(deleted_at__isnull=True), + name="project_custom_property_unique_project_name_when_deleted_at_null", + ) + ] + verbose_name = "Project Custom Property" + verbose_name_plural = "Project Custom Properties" + db_table = "project_custom_properties" + ordering = ("-created_at",) \ No newline at end of file diff --git a/apiserver/plane/middleware/api_log_middleware.py b/apiserver/plane/middleware/api_log_middleware.py index 96c62c2fd9d..2b7b5a781fc 100644 --- a/apiserver/plane/middleware/api_log_middleware.py +++ b/apiserver/plane/middleware/api_log_middleware.py @@ -1,4 +1,5 @@ from plane.db.models import APIActivityLog +from django.urls import resolve class APITokenLogMiddleware: @@ -8,9 +9,32 @@ def __init__(self, get_response): def __call__(self, request): request_body = request.body response = self.get_response(request) + rewriten_path = self.project_rewiter(request) + request.path = rewriten_path + request.path_info = rewriten_path + resolver_match = resolve(request.path_info) + request.resolver_match = resolver_match # Update resolver_match with the new path_info + request.kwargs = resolver_match.kwargs + # print(request.__dict__) self.process_request(request, response, request_body) return response + def project_rewiter(self, request): + # Modify `kwargs` as needed + path_split = request.path.split('/') + print(len(path_split)) + if len(path_split) <= 6: + return request.path + if request.path.split('/')[6] == 'DEFAULT': + path_parts = request.path.split('/') + path_parts[6] = 'dab178af-a6bb-4bfb-a0a8-ae8fd702b587' + import pdb;pdb.set_trace() + return '/'.join(path_parts) + + return request.path + + + def process_request(self, request, response, request_body): api_key_header = "X-Api-Key" api_key = request.headers.get(api_key_header) diff --git a/apiserver/plane/settings/common.py b/apiserver/plane/settings/common.py index 1b878e6b8d6..a411e17b08b 100644 --- a/apiserver/plane/settings/common.py +++ b/apiserver/plane/settings/common.py @@ -381,7 +381,7 @@ ADMIN_BASE_URL = os.environ.get("ADMIN_BASE_URL", None) SPACE_BASE_URL = os.environ.get("SPACE_BASE_URL", None) APP_BASE_URL = os.environ.get("APP_BASE_URL") - +STATIC_API_TOKEN = os.environ.get("STATIC_API_TOKEN", "TEST_API_TOKEN") HARD_DELETE_AFTER_DAYS = int(os.environ.get("HARD_DELETE_AFTER_DAYS", 60)) # Instance Changelog URL diff --git a/apiserver/plane/utils/issue_filters.py b/apiserver/plane/utils/issue_filters.py index b82ba1e8c75..2826dece7b6 100644 --- a/apiserver/plane/utils/issue_filters.py +++ b/apiserver/plane/utils/issue_filters.py @@ -3,6 +3,7 @@ from datetime import timedelta from django.utils import timezone +from django.db.models import Q # The date from pattern pattern = re.compile(r"\d+_(weeks|months)$") @@ -538,6 +539,26 @@ def filter_logged_by(params, issue_filter, method, prefix=""): return issue_filter +def filter_custom_properties(params, issue_filter, method, prefix=""): + if method == "GET": + custom_properties = [ + item + for item in params.get("custom_properties").split(",") + if item != "null" + ] + query = Q() + for row in custom_properties: + key, value = row.split(":") + query &= Q( + Q(custom_properties__project_custom_property_id=key) & + Q(custom_properties__value=value) + ) + + issue_filter['base'] = query + + print(issue_filter) + return issue_filter + def issue_filters(query_params, method, prefix=""): issue_filter = {} @@ -566,6 +587,7 @@ def issue_filters(query_params, method, prefix=""): "sub_issue": filter_sub_issue_toggle, "subscriber": filter_subscribed_issues, "start_target_date": filter_start_target_date_issues, + "custom_properties": filter_custom_properties, } for key, value in ISSUE_FILTER.items(): diff --git a/web/core/components/workspace/sidebar/dropdown.tsx b/web/core/components/workspace/sidebar/dropdown.tsx index a60a7d7e1b1..15d2a079df7 100644 --- a/web/core/components/workspace/sidebar/dropdown.tsx +++ b/web/core/components/workspace/sidebar/dropdown.tsx @@ -205,15 +205,6 @@ export const SidebarDropdown = observer(() => { )}
- - - - Create workspace - - {userLinks(workspaceSlug?.toString() ?? "").map( (link, index) => allowPermissions(link.access, EUserPermissionsLevel.WORKSPACE) && ( From 06433d45f90adf7df6f3887ab8467ac75e53f608 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 14 Nov 2024 10:18:09 +0000 Subject: [PATCH 003/120] issue type and custom fields created --- apiserver/plane/api/serializers/__init__.py | 3 +- apiserver/plane/api/serializers/issue.py | 6 +- apiserver/plane/api/serializers/issue_type.py | 44 ++++ apiserver/plane/api/serializers/project.py | 24 +- apiserver/plane/api/urls/__init__.py | 4 + apiserver/plane/api/urls/issue.py | 3 +- apiserver/plane/api/urls/issue_type.py | 25 ++ apiserver/plane/api/urls/project.py | 8 +- apiserver/plane/api/views/__init__.py | 6 +- apiserver/plane/api/views/issue.py | 6 +- apiserver/plane/api/views/issue_type.py | 241 ++++++++++++++++++ apiserver/plane/api/views/project.py | 52 +--- apiserver/plane/app/permissions/project.py | 7 + ...operty_project_custom_property_and_more.py | 22 ++ ...y_delete_projectcustomproperty_and_more.py | 48 ++++ ...issuecustomproperty_issue_type_and_more.py | 24 ++ ...88_alter_issuecustomproperty_issue_type.py | 19 ++ ...ustomproperty_issue_type_custom_proerty.py | 18 ++ ...stomproperty_issue_type_custom_property.py | 18 ++ apiserver/plane/db/models/__init__.py | 5 +- apiserver/plane/db/models/base.py | 3 +- apiserver/plane/db/models/issue.py | 14 +- apiserver/plane/db/models/issue_type.py | 24 ++ apiserver/plane/db/models/project.py | 20 -- .../plane/middleware/api_log_middleware.py | 28 +- docker-compose-local.yml | 2 + 26 files changed, 544 insertions(+), 130 deletions(-) create mode 100644 apiserver/plane/api/serializers/issue_type.py create mode 100644 apiserver/plane/api/urls/issue_type.py create mode 100644 apiserver/plane/api/views/issue_type.py create mode 100644 apiserver/plane/db/migrations/0085_remove_issuecustomproperty_project_custom_property_and_more.py create mode 100644 apiserver/plane/db/migrations/0086_issuetypecustomproperty_delete_projectcustomproperty_and_more.py create mode 100644 apiserver/plane/db/migrations/0087_issuecustomproperty_issue_type_and_more.py create mode 100644 apiserver/plane/db/migrations/0088_alter_issuecustomproperty_issue_type.py create mode 100644 apiserver/plane/db/migrations/0089_rename_issue_type_issuecustomproperty_issue_type_custom_proerty.py create mode 100644 apiserver/plane/db/migrations/0090_rename_issue_type_custom_proerty_issuecustomproperty_issue_type_custom_property.py diff --git a/apiserver/plane/api/serializers/__init__.py b/apiserver/plane/api/serializers/__init__.py index 9837f03e8cc..a1e9fe01ec2 100644 --- a/apiserver/plane/api/serializers/__init__.py +++ b/apiserver/plane/api/serializers/__init__.py @@ -1,6 +1,6 @@ from .user import UserLiteSerializer from .workspace import WorkspaceLiteSerializer -from .project import ProjectSerializer, ProjectLiteSerializer, ProjectCustomPropertySerializer +from .project import ProjectSerializer, ProjectLiteSerializer from .issue import ( IssueSerializer, LabelSerializer, @@ -11,6 +11,7 @@ IssueExpandSerializer, IssueLiteSerializer, ) +from .issue_type import IssueTypeSerializer, IssueTypeCustomPropertySerializer from .state import StateLiteSerializer, StateSerializer from .cycle import CycleSerializer, CycleIssueSerializer, CycleLiteSerializer from .module import ( diff --git a/apiserver/plane/api/serializers/issue.py b/apiserver/plane/api/serializers/issue.py index 701c0c67dc9..07c223e2202 100644 --- a/apiserver/plane/api/serializers/issue.py +++ b/apiserver/plane/api/serializers/issue.py @@ -35,11 +35,9 @@ class IssueCustomPropertySerializer(BaseSerializer): class Meta: model = IssueCustomProperty - fields = ["id", "key", "value", "project_custom_property"] + fields = ["key", "value", "issue_type_custom_property"] read_only_fields = [ "id", - "workspace", - "project", "issue", "created_by", "updated_by", @@ -220,7 +218,7 @@ def create(self, validated_data): IssueCustomProperty( key=custom_property['key'], value=custom_property['value'], - project_custom_property=custom_property['project_custom_property'], + issue_type_custom_property=custom_property['issue_type_custom_property'], issue=issue, project_id=project_id, workspace_id=workspace_id, diff --git a/apiserver/plane/api/serializers/issue_type.py b/apiserver/plane/api/serializers/issue_type.py new file mode 100644 index 00000000000..1094f45a483 --- /dev/null +++ b/apiserver/plane/api/serializers/issue_type.py @@ -0,0 +1,44 @@ +from rest_framework import serializers + +from plane.db.models import IssueType, IssueTypeCustomProperty + +from .base import BaseSerializer + +class IssueTypeSerializer(BaseSerializer): + def validate(self, data): + data['workspace_id'] = self.context["workspace_id"] + return data + class Meta: + model = IssueType + read_only_fields = [ + "id", + "workspace", + "created_by", + "updated_by", + "created_at", + "updated_at", + ] + exclude = [ + "created_by", + "updated_by", + ] + +class IssueTypeCustomPropertySerializer(BaseSerializer): + class Meta: + model = IssueTypeCustomProperty + fields = "__all__" + read_only_fields = [ + "id", + "issue_type", + "created_at", + "updated_at", + "created_by", + "updated_by", + "deleted_at" + ] + + def create(self, validated_data): + return IssueTypeCustomProperty.objects.create( + **validated_data, + issue_type_id=self.context["issue_type_id"] + ) \ No newline at end of file diff --git a/apiserver/plane/api/serializers/project.py b/apiserver/plane/api/serializers/project.py index 400de8313ce..da90d62a367 100644 --- a/apiserver/plane/api/serializers/project.py +++ b/apiserver/plane/api/serializers/project.py @@ -6,10 +6,10 @@ Project, ProjectIdentifier, WorkspaceMember, - ProjectCustomProperty ) from .base import BaseSerializer +from .issue_type import IssueTypeSerializer class ProjectSerializer(BaseSerializer): @@ -31,7 +31,6 @@ class Meta: "workspace", "created_at", "updated_at", - "created_by", "updated_by", "deleted_at", "cover_image_url", @@ -106,24 +105,3 @@ class Meta: ] read_only_fields = fields -class ProjectCustomPropertySerializer(BaseSerializer): - class Meta: - model = ProjectCustomProperty - fields = "__all__" - read_only_fields = [ - "id", - "workspace", - "project", - "created_at", - "updated_at", - "created_by", - "updated_by", - "deleted_at" - ] - - def create(self, validated_data): - return ProjectCustomProperty.objects.create( - **validated_data, - workspace_id=self.context["workspace_id"], - project_id=self.context["project_id"] - ) \ No newline at end of file diff --git a/apiserver/plane/api/urls/__init__.py b/apiserver/plane/api/urls/__init__.py index efa84bce038..ee14d99f662 100644 --- a/apiserver/plane/api/urls/__init__.py +++ b/apiserver/plane/api/urls/__init__.py @@ -1,10 +1,12 @@ from .project import urlpatterns as project_patterns from .state import urlpatterns as state_patterns from .issue import urlpatterns as issue_patterns +from .issue_type import urlpatterns as issue_type_patterns from .cycle import urlpatterns as cycle_patterns from .module import urlpatterns as module_patterns from .inbox import urlpatterns as inbox_patterns from .member import urlpatterns as member_patterns +from plane.app.urls.search import urlpatterns as search_patters urlpatterns = [ *project_patterns, @@ -14,4 +16,6 @@ *module_patterns, *inbox_patterns, *member_patterns, + *issue_type_patterns, + *search_patters ] diff --git a/apiserver/plane/api/urls/issue.py b/apiserver/plane/api/urls/issue.py index e9bf030a2a2..e2f6c8e3fb4 100644 --- a/apiserver/plane/api/urls/issue.py +++ b/apiserver/plane/api/urls/issue.py @@ -8,6 +8,7 @@ IssueActivityAPIEndpoint, WorkspaceIssueAPIEndpoint, IssueAttachmentEndpoint, + IssueTypeAPIEndpoint ) urlpatterns = [ @@ -70,5 +71,5 @@ "workspaces//projects//issues//issue-attachments/", IssueAttachmentEndpoint.as_view(), name="attachment", - ), + ) ] diff --git a/apiserver/plane/api/urls/issue_type.py b/apiserver/plane/api/urls/issue_type.py new file mode 100644 index 00000000000..1c2511511bf --- /dev/null +++ b/apiserver/plane/api/urls/issue_type.py @@ -0,0 +1,25 @@ +from django.urls import path + +from plane.api.views import ( + IssueTypeAPIEndpoint, + IssueTypeCustomPropertyAPIEndpoint +) + +urlpatterns = [ + path( + "workspaces//issue-type/", + IssueTypeAPIEndpoint.as_view(), + name="issue-type", + ), + path( + "workspaces//issue-type//", + IssueTypeAPIEndpoint.as_view(), + name="issue-type", + ), + path( + "workspaces//issue-type//custom-properties/", + IssueTypeCustomPropertyAPIEndpoint.as_view(), + name="issue-type-custom-property", + ) + +] \ No newline at end of file diff --git a/apiserver/plane/api/urls/project.py b/apiserver/plane/api/urls/project.py index 1f0727ec299..381c0ac0050 100644 --- a/apiserver/plane/api/urls/project.py +++ b/apiserver/plane/api/urls/project.py @@ -2,8 +2,7 @@ from plane.api.views import ( ProjectAPIEndpoint, - ProjectArchiveUnarchiveAPIEndpoint, - ProjectCustomPropertyAPIEndpoint + ProjectArchiveUnarchiveAPIEndpoint ) urlpatterns = [ @@ -17,11 +16,6 @@ ProjectAPIEndpoint.as_view(), name="project", ), - path( - "workspaces//projects//custom-properties/", - ProjectCustomPropertyAPIEndpoint.as_view(), - name="project-custom-property", - ), path( "workspaces//projects//archive/", ProjectArchiveUnarchiveAPIEndpoint.as_view(), diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index 1a52b178fca..59eb132dfb9 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -1,7 +1,6 @@ from .project import ( ProjectAPIEndpoint, - ProjectArchiveUnarchiveAPIEndpoint, - ProjectCustomPropertyAPIEndpoint + ProjectArchiveUnarchiveAPIEndpoint ) from .state import StateAPIEndpoint @@ -13,8 +12,9 @@ IssueLinkAPIEndpoint, IssueCommentAPIEndpoint, IssueActivityAPIEndpoint, - IssueAttachmentEndpoint, + IssueAttachmentEndpoint ) +from .issue_type import IssueTypeAPIEndpoint,IssueTypeCustomPropertyAPIEndpoint from .cycle import ( CycleAPIEndpoint, diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index 7e65d5115ec..7ac94b8b306 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -33,6 +33,7 @@ IssueLinkSerializer, IssueSerializer, LabelSerializer, + IssueTypeSerializer ) from plane.app.permissions import ( ProjectEntityPermission, @@ -50,6 +51,7 @@ Project, ProjectMember, CycleIssue, + IssueType ) from plane.utils.issue_filters import issue_filters from .base import BaseAPIView @@ -306,7 +308,6 @@ def get(self, request, slug, project_id, pk=None): def post(self, request, slug, project_id): project = Project.objects.get(pk=project_id) - serializer = IssueSerializer( data=request.data, context={ @@ -340,7 +341,6 @@ def post(self, request, slug, project_id): }, status=status.HTTP_409_CONFLICT, ) - serializer.save() # Refetch the issue issue = Issue.objects.filter( @@ -1149,3 +1149,5 @@ def get(self, request, slug, project_id, issue_id): ) serializer = IssueAttachmentSerializer(issue_attachments, many=True) return Response(serializer.data, status=status.HTTP_200_OK) + + diff --git a/apiserver/plane/api/views/issue_type.py b/apiserver/plane/api/views/issue_type.py new file mode 100644 index 00000000000..d83dab4f313 --- /dev/null +++ b/apiserver/plane/api/views/issue_type.py @@ -0,0 +1,241 @@ +import json + +from django.core.serializers.json import DjangoJSONEncoder + +# Django imports +from django.db import IntegrityError +from django.db.models import ( + Case, + CharField, + Exists, + F, + Func, + Max, + OuterRef, + Q, + Value, + When, + Subquery, +) +from django.utils import timezone + +# Third party imports +from rest_framework import status +from rest_framework.response import Response +from rest_framework.parsers import MultiPartParser, FormParser + +# Module imports +from plane.api.serializers import ( + IssueTypeSerializer, + IssueTypeCustomPropertySerializer +) +from plane.app.permissions import ( + ProjectLitePermission, +) +from plane.bgtasks.issue_activities_task import issue_activity +from plane.db.models import ( + Workspace, + IssueType, + IssueTypeCustomProperty +) +from .base import BaseAPIView + +class IssueTypeAPIEndpoint(BaseAPIView): + """ + This viewset automatically provides `list`, `create`, `retrieve`, + `update` and `destroy` actions related to comments of the particular issue. + + """ + + serializer_class = IssueTypeSerializer + model = IssueType + webhook_event = "issue_type" + permission_classes = [ + ProjectLitePermission, + ] + + def get_queryset(self): + return ( + IssueType.objects.filter( + workspace__slug=self.kwargs.get("slug") + ) + .select_related("workspace") + .order_by(self.kwargs.get("order_by", "-created_at")) + .distinct() + ) + + def get(self, request, slug, pk=None): + if pk: + issue_type = self.get_queryset().get(pk=pk) + serializer = IssueTypeSerializer( + issue_type, + fields=self.fields, + expand=self.expand, + ) + import pdb;pdb.set_trace() + return Response(serializer.data, status=status.HTTP_200_OK) + return self.paginate( + request=request, + queryset=(self.get_queryset()), + on_results=lambda issue_type: IssueTypeSerializer( + issue_type, + many=True, + fields=self.fields, + expand=self.expand, + ).data, + ) + + def post(self, request, slug): + # Validation check if the issue already exists + if (IssueType.objects.filter( + name=request.data.get('name'), + workspace__slug=slug + ).exists() + ): + issue_type = IssueType.objects.filter( + name=request.data.get('name'), + workspace__slug=slug + ).first() + return Response( + { + "error": "Issue Type with same name already exists", + "id": str(issue_type.id), + }, + status=status.HTTP_409_CONFLICT, + ) + workspace = Workspace.objects.get(slug=slug) + serializer = IssueTypeSerializer( + data=request.data, + context={'workspace_id': workspace.id} + ) + if serializer.is_valid(): + serializer.save() + issue_type = IssueType.objects.get( + pk=serializer.data.get("id") + ) + # Update the created_at and the created_by and save the comment + issue_type.created_at = request.data.get( + "created_at", timezone.now() + ) + issue_type.created_by_id = request.data.get( + "created_by", request.user.id + ) + issue_type.save(update_fields=["created_at", "created_by"]) + + # issue_activity.delay( + # type="type.activity.created", + # requested_data=json.dumps( + # serializer.data, cls=DjangoJSONEncoder + # ), + # current_instance=None, + # epoch=int(timezone.now().timestamp()), + # ) + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def patch(self, request, slug, project_id, issue_id, pk): + issue_type = IssueType.objects.get( + workspace__slug=slug, + pk=pk, + ) + requested_data = json.dumps(self.request.data, cls=DjangoJSONEncoder) + current_instance = json.dumps( + IssueTypeSerializer(issue_comment).data, + cls=DjangoJSONEncoder, + ) + + # Validation check if the issue already exists + if ( + IssueType.objects.filter( + workspace__slug=slug, + name=request.data.get('name') + ).exists() + ): + return Response( + { + "error": "Issue Comment with the same name already exists", + "id": str(issue_type.id), + }, + status=status.HTTP_409_CONFLICT, + ) + + serializer = IssueTypeSerializer( + issue_comment, data=request.data, partial=True + ) + if serializer.is_valid(): + serializer.save() + # issue_activity.delay( + # type="type.activity.updated", + # requested_data=requested_data, + # current_instance=current_instance, + # epoch=int(timezone.now().timestamp()), + # ) + return Response(serializer.data, status=status.HTTP_200_OK) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + # def delete(self, request, slug, project_id, issue_id, pk): + # issue_comment = IssueComment.objects.get( + # workspace__slug=slug, + # project_id=project_id, + # issue_id=issue_id, + # pk=pk, + # ) + # current_instance = json.dumps( + # IssueCommentSerializer(issue_comment).data, + # cls=DjangoJSONEncoder, + # ) + # issue_comment.delete() + # issue_activity.delay( + # type="comment.activity.deleted", + # requested_data=json.dumps({"comment_id": str(pk)}), + # actor_id=str(request.user.id), + # issue_id=str(issue_id), + # project_id=str(project_id), + # current_instance=current_instance, + # epoch=int(timezone.now().timestamp()), + # ) + # return Response(status=status.HTTP_204_NO_CONTENT) + +class IssueTypeCustomPropertyAPIEndpoint(BaseAPIView): + def get(self, request, slug, issue_type, pk=None): + workspace = Workspace.objects.get(slug=slug) + properties = IssueTypeCustomProperty.objects.filter( + issue_type_id=issue_type + ) + if pk: + property = properties.get(pk=pk) + serializer = IssueTypeCustomPropertySerializer( + property, many=False + ) + return Response(serializer.data, status=status.HTTP_200_OK) + serializer = IssueTypeCustomPropertySerializer(properties, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + + def post(self, request, slug, issue_type): + try: + serializer = IssueTypeCustomPropertySerializer( + data={**request.data}, context={ + "issue_type_id": issue_type + } + ) + print(serializer.is_valid()) + if serializer.is_valid(): + serializer.save() + return Response( + serializer.data, status=status.HTTP_201_CREATED + ) + return Response( + serializer.errors, + status=status.HTTP_400_BAD_REQUEST, + ) + except IntegrityError as e: + if "already exists" in str(e): + return Response( + {"name": "The Property Name is already taken"}, + status=status.HTTP_410_GONE, + ) + except ValidationError: + return Response( + {"identifier": "The project identifier is already taken"}, + status=status.HTTP_410_GONE, + ) \ No newline at end of file diff --git a/apiserver/plane/api/views/project.py b/apiserver/plane/api/views/project.py index 85512b50f5d..fe397e00c41 100644 --- a/apiserver/plane/api/views/project.py +++ b/apiserver/plane/api/views/project.py @@ -12,7 +12,7 @@ from rest_framework.response import Response from rest_framework.serializers import ValidationError -from plane.api.serializers import ProjectSerializer, ProjectCustomPropertySerializer +from plane.api.serializers import ProjectSerializer from plane.app.permissions import ProjectBasePermission # Module imports @@ -26,8 +26,7 @@ ProjectMember, State, Workspace, - UserFavorite, - ProjectCustomProperty + UserFavorite ) from plane.bgtasks.webhook_task import model_activity from .base import BaseAPIView @@ -399,50 +398,3 @@ def delete(self, request, slug, project_id): project.archived_at = None project.save() return Response(status=status.HTTP_204_NO_CONTENT) - - -class ProjectCustomPropertyAPIEndpoint(BaseAPIView): - def get(self, request, slug, project_id): - workspace = Workspace.objects.get(slug=slug) - project = Project.objects.get(pk=project_id, workspace=workspace) - properties = ProjectCustomProperty.objects.filter( - project=project, - ) - serializer = ProjectCustomPropertySerializer(properties, many=True) - return Response(serializer.data, status=status.HTTP_200_OK) - - def post(self, request, slug, project_id): - try: - workspace = Workspace.objects.get(slug=slug) - serializer = ProjectCustomPropertySerializer( - data={**request.data}, context={ - "workspace_id": workspace.id, - "project_id": project_id - } - ) - print(serializer.is_valid()) - if serializer.is_valid(): - serializer.save() - return Response( - serializer.data, status=status.HTTP_201_CREATED - ) - return Response( - serializer.errors, - status=status.HTTP_400_BAD_REQUEST, - ) - except IntegrityError as e: - if "already exists" in str(e): - return Response( - {"name": "The project name is already taken"}, - status=status.HTTP_410_GONE, - ) - except Workspace.DoesNotExist: - return Response( - {"error": "Workspace does not exist"}, - status=status.HTTP_404_NOT_FOUND, - ) - except ValidationError: - return Response( - {"identifier": "The project identifier is already taken"}, - status=status.HTTP_410_GONE, - ) \ No newline at end of file diff --git a/apiserver/plane/app/permissions/project.py b/apiserver/plane/app/permissions/project.py index cc9c0f350cc..522c1bfde43 100644 --- a/apiserver/plane/app/permissions/project.py +++ b/apiserver/plane/app/permissions/project.py @@ -15,6 +15,8 @@ def has_permission(self, request, view): if request.user.is_anonymous: return False + if request.user.is_superuser: + return True ## Safe Methods -> Handle the filtering logic in queryset if request.method in SAFE_METHODS: return WorkspaceMember.objects.filter( @@ -83,6 +85,8 @@ def has_permission(self, request, view): if request.user.is_anonymous: return False + if request.user.is_superuser: + return True # Handle requests based on project__identifier if hasattr(view, "project__identifier") and view.project__identifier: if request.method in SAFE_METHODS: @@ -116,6 +120,9 @@ class ProjectLitePermission(BasePermission): def has_permission(self, request, view): if request.user.is_anonymous: return False + + if request.user.is_superuser: + return True return ProjectMember.objects.filter( workspace__slug=view.workspace_slug, diff --git a/apiserver/plane/db/migrations/0085_remove_issuecustomproperty_project_custom_property_and_more.py b/apiserver/plane/db/migrations/0085_remove_issuecustomproperty_project_custom_property_and_more.py new file mode 100644 index 00000000000..040a6944f7f --- /dev/null +++ b/apiserver/plane/db/migrations/0085_remove_issuecustomproperty_project_custom_property_and_more.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.16 on 2024-11-13 11:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0084_projectcustomproperty_issuecustomproperty_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='issuecustomproperty', + name='project_custom_property', + ), + migrations.AddField( + model_name='projectcustomproperty', + name='issue_type', + field=models.ManyToManyField(related_name='custom_propery_issue_type', to='db.issuetype'), + ), + ] diff --git a/apiserver/plane/db/migrations/0086_issuetypecustomproperty_delete_projectcustomproperty_and_more.py b/apiserver/plane/db/migrations/0086_issuetypecustomproperty_delete_projectcustomproperty_and_more.py new file mode 100644 index 00000000000..e58376f039c --- /dev/null +++ b/apiserver/plane/db/migrations/0086_issuetypecustomproperty_delete_projectcustomproperty_and_more.py @@ -0,0 +1,48 @@ +# Generated by Django 4.2.16 on 2024-11-14 07:53 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0085_remove_issuecustomproperty_project_custom_property_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='IssueTypeCustomProperty', + fields=[ + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')), + ('deleted_at', models.DateTimeField(blank=True, null=True, verbose_name='Deleted At')), + ('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), + ('name', models.CharField(max_length=255)), + ('value', models.JSONField()), + ('is_active', models.BooleanField(default=True)), + ('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')), + ('issue_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='custom_propery_issue_type', to='db.issuetype')), + ('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')), + ], + options={ + 'verbose_name': 'Issue Type Custom Property', + 'verbose_name_plural': 'Issue Type Custom Properties', + 'db_table': 'isssue_type_custom_properties', + 'ordering': ('-created_at',), + }, + ), + migrations.DeleteModel( + name='ProjectCustomProperty', + ), + migrations.AddConstraint( + model_name='issuetypecustomproperty', + constraint=models.UniqueConstraint(condition=models.Q(('deleted_at__isnull', True)), fields=('issue_type', 'name'), name='issue_type_custom_property_unique_type_name_when_deleted_at_null'), + ), + migrations.AlterUniqueTogether( + name='issuetypecustomproperty', + unique_together={('issue_type', 'name', 'deleted_at')}, + ), + ] diff --git a/apiserver/plane/db/migrations/0087_issuecustomproperty_issue_type_and_more.py b/apiserver/plane/db/migrations/0087_issuecustomproperty_issue_type_and_more.py new file mode 100644 index 00000000000..2351c48f201 --- /dev/null +++ b/apiserver/plane/db/migrations/0087_issuecustomproperty_issue_type_and_more.py @@ -0,0 +1,24 @@ +# Generated by Django 4.2.16 on 2024-11-14 09:56 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0086_issuetypecustomproperty_delete_projectcustomproperty_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='issuecustomproperty', + name='issue_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='custom_issue_type', to='db.issuetype'), + ), + migrations.AlterField( + model_name='issuecustomproperty', + name='value', + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/apiserver/plane/db/migrations/0088_alter_issuecustomproperty_issue_type.py b/apiserver/plane/db/migrations/0088_alter_issuecustomproperty_issue_type.py new file mode 100644 index 00000000000..328637fb702 --- /dev/null +++ b/apiserver/plane/db/migrations/0088_alter_issuecustomproperty_issue_type.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.16 on 2024-11-14 10:04 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0087_issuecustomproperty_issue_type_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='issuecustomproperty', + name='issue_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='issue_type_custom_property', to='db.issuetypecustomproperty'), + ), + ] diff --git a/apiserver/plane/db/migrations/0089_rename_issue_type_issuecustomproperty_issue_type_custom_proerty.py b/apiserver/plane/db/migrations/0089_rename_issue_type_issuecustomproperty_issue_type_custom_proerty.py new file mode 100644 index 00000000000..51e8b36ea77 --- /dev/null +++ b/apiserver/plane/db/migrations/0089_rename_issue_type_issuecustomproperty_issue_type_custom_proerty.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.16 on 2024-11-14 10:06 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0088_alter_issuecustomproperty_issue_type'), + ] + + operations = [ + migrations.RenameField( + model_name='issuecustomproperty', + old_name='issue_type', + new_name='issue_type_custom_proerty', + ), + ] diff --git a/apiserver/plane/db/migrations/0090_rename_issue_type_custom_proerty_issuecustomproperty_issue_type_custom_property.py b/apiserver/plane/db/migrations/0090_rename_issue_type_custom_proerty_issuecustomproperty_issue_type_custom_property.py new file mode 100644 index 00000000000..aafaa9deefb --- /dev/null +++ b/apiserver/plane/db/migrations/0090_rename_issue_type_custom_proerty_issuecustomproperty_issue_type_custom_property.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.16 on 2024-11-14 10:08 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0089_rename_issue_type_issuecustomproperty_issue_type_custom_proerty'), + ] + + operations = [ + migrations.RenameField( + model_name='issuecustomproperty', + old_name='issue_type_custom_proerty', + new_name='issue_type_custom_property', + ), + ] diff --git a/apiserver/plane/db/models/__init__.py b/apiserver/plane/db/models/__init__.py index 699a7c6026c..8b2a050a0a1 100644 --- a/apiserver/plane/db/models/__init__.py +++ b/apiserver/plane/db/models/__init__.py @@ -68,8 +68,7 @@ ProjectIdentifier, ProjectMember, ProjectMemberInvite, - ProjectPublicMember, - ProjectCustomProperty + ProjectPublicMember ) from .deploy_board import DeployBoard from .session import Session @@ -113,7 +112,7 @@ from .favorite import UserFavorite -from .issue_type import IssueType +from .issue_type import IssueType, IssueTypeCustomProperty from .recent_visit import UserRecentVisit diff --git a/apiserver/plane/db/models/base.py b/apiserver/plane/db/models/base.py index 63c08afa49a..7900a52e947 100644 --- a/apiserver/plane/db/models/base.py +++ b/apiserver/plane/db/models/base.py @@ -33,7 +33,8 @@ def save(self, *args, **kwargs): # Check if the model is being created or updated if self._state.adding: # If created only set created_by value: set updated_by to None - self.created_by = user + if self.created_by is None: + self.created_by = user self.updated_by = None # If updated only set updated_by value don't touch created_by self.updated_by = user diff --git a/apiserver/plane/db/models/issue.py b/apiserver/plane/db/models/issue.py index eb2abb2ed51..d154a4fc881 100644 --- a/apiserver/plane/db/models/issue.py +++ b/apiserver/plane/db/models/issue.py @@ -716,13 +716,15 @@ class IssueCustomProperty(ProjectBaseModel): Issue, on_delete=models.CASCADE, related_name="custom_properties" ) key = models.CharField(max_length=255) - value = models.JSONField(default=dict) - project_custom_property = models.ForeignKey( - "db.ProjectCustomProperty", - on_delete=models.CASCADE, - related_name="issue_custom_properties", + value = models.CharField(max_length=255, null=True, blank=True) + issue_type_custom_property = models.ForeignKey( + "db.IssueTypeCustomProperty", + on_delete=models.SET_NULL, + related_name="issue_type_custom_property", + null=True, + blank=True, ) - + class Meta: verbose_name = "Issue Custom Property" verbose_name_plural = "Issue Custom Properties" diff --git a/apiserver/plane/db/models/issue_type.py b/apiserver/plane/db/models/issue_type.py index e6e409c84db..619fb8cfd0d 100644 --- a/apiserver/plane/db/models/issue_type.py +++ b/apiserver/plane/db/models/issue_type.py @@ -57,3 +57,27 @@ class Meta: def __str__(self): return f"{self.project} - {self.issue_type}" + + +class IssueTypeCustomProperty(BaseModel): + name = models.CharField(max_length=255) + value = models.JSONField() + is_active = models.BooleanField(default=True) + issue_type = models.ForeignKey( + "db.IssueType", + on_delete=models.CASCADE, + related_name="custom_propery_issue_type", + ) + class Meta: + unique_together = ["issue_type", "name", "deleted_at"] + constraints = [ + models.UniqueConstraint( + fields=["issue_type", "name"], + condition=models.Q(deleted_at__isnull=True), + name="issue_type_custom_property_unique_type_name_when_deleted_at_null", + ) + ] + verbose_name = "Issue Type Custom Property" + verbose_name_plural = "Issue Type Custom Properties" + db_table = "isssue_type_custom_properties" + ordering = ("-created_at",) \ No newline at end of file diff --git a/apiserver/plane/db/models/project.py b/apiserver/plane/db/models/project.py index f52d2078b90..55c45fc2a45 100644 --- a/apiserver/plane/db/models/project.py +++ b/apiserver/plane/db/models/project.py @@ -350,23 +350,3 @@ class Meta: verbose_name_plural = "Project Public Members" db_table = "project_public_members" ordering = ("-created_at",) - - -class ProjectCustomProperty(ProjectBaseModel): - name = models.CharField(max_length=255) - value = models.JSONField() - is_active = models.BooleanField(default=True) - - class Meta: - unique_together = ["project", "name", "deleted_at"] - constraints = [ - models.UniqueConstraint( - fields=["project", "name"], - condition=models.Q(deleted_at__isnull=True), - name="project_custom_property_unique_project_name_when_deleted_at_null", - ) - ] - verbose_name = "Project Custom Property" - verbose_name_plural = "Project Custom Properties" - db_table = "project_custom_properties" - ordering = ("-created_at",) \ No newline at end of file diff --git a/apiserver/plane/middleware/api_log_middleware.py b/apiserver/plane/middleware/api_log_middleware.py index 2b7b5a781fc..95fa572e9ae 100644 --- a/apiserver/plane/middleware/api_log_middleware.py +++ b/apiserver/plane/middleware/api_log_middleware.py @@ -19,17 +19,27 @@ def __call__(self, request): self.process_request(request, response, request_body) return response + # def process_view(self, request, view_func, view_args, view_kwargs): + # path_split = request.path.split('/') + # print(len(path_split)) + # if len(path_split) <= 6: + # return request.path + # if request.path.split('/')[6] == 'DEFAULT': + # path_parts = request.path.split('/') + # view_kwargs['project_id'] = '9f54ba83-aa85-43ce-9ce6-b2968f066e85' + + + return request.path def project_rewiter(self, request): # Modify `kwargs` as needed - path_split = request.path.split('/') - print(len(path_split)) - if len(path_split) <= 6: - return request.path - if request.path.split('/')[6] == 'DEFAULT': - path_parts = request.path.split('/') - path_parts[6] = 'dab178af-a6bb-4bfb-a0a8-ae8fd702b587' - import pdb;pdb.set_trace() - return '/'.join(path_parts) + # path_split = request.path.split('/') + # print(len(path_split)) + # if len(path_split) <= 6: + # return request.path + # if request.path.split('/')[6] == 'DEFAULT': + # path_parts = request.path.split('/') + # path_parts[6] = 'dab178af-a6bb-4bfb-a0a8-ae8fd702b587' + # return '/'.join(path_parts) return request.path diff --git a/docker-compose-local.yml b/docker-compose-local.yml index fb8f81cc9b5..067aaead89e 100644 --- a/docker-compose-local.yml +++ b/docker-compose-local.yml @@ -110,6 +110,8 @@ services: depends_on: - plane-db - plane-redis + stdin_open: true + tty: true worker: build: From a01a77c18f00a8e390c2c420d68c346156ef1091 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Tue, 10 Dec 2024 06:49:37 +0000 Subject: [PATCH 004/120] dev complete --- apiserver/plane/api/serializers/attachment.py | 0 apiserver/plane/api/serializers/issue.py | 67 ++++- apiserver/plane/api/urls/__init__.py | 2 +- apiserver/plane/api/urls/issue.py | 31 +- apiserver/plane/api/urls/search.py | 13 + apiserver/plane/api/views/__init__.py | 3 +- apiserver/plane/api/views/attachment.py | 174 ++++++++++++ apiserver/plane/api/views/base.py | 21 +- apiserver/plane/api/views/issue.py | 8 +- apiserver/plane/api/views/issue_type.py | 1 - apiserver/plane/api/views/member.py | 66 +++-- apiserver/plane/api/views/search.py | 265 ++++++++++++++++++ .../plane/authentication/adapter/base.py | 6 +- .../provider/credentials/magic_code.py | 12 +- .../plane/authentication/views/app/magic.py | 28 +- apiserver/plane/db/models/base.py | 4 + apiserver/plane/settings/common.py | 2 +- apiserver/plane/settings/storage.py | 7 + 18 files changed, 642 insertions(+), 68 deletions(-) create mode 100644 apiserver/plane/api/serializers/attachment.py create mode 100644 apiserver/plane/api/urls/search.py create mode 100644 apiserver/plane/api/views/attachment.py create mode 100644 apiserver/plane/api/views/search.py diff --git a/apiserver/plane/api/serializers/attachment.py b/apiserver/plane/api/serializers/attachment.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/apiserver/plane/api/serializers/issue.py b/apiserver/plane/api/serializers/issue.py index 07c223e2202..31cf4412bab 100644 --- a/apiserver/plane/api/serializers/issue.py +++ b/apiserver/plane/api/serializers/issue.py @@ -1,6 +1,7 @@ # Django imports from django.utils import timezone from lxml import html +import uuid # Third party imports from rest_framework import serializers @@ -32,6 +33,13 @@ from django.core.validators import URLValidator +def is_uuid(value): + try: + uuid_obj = uuid.UUID(str(value)) # Convert to string in case it's not already + return True + except (ValueError, TypeError): + return False + class IssueCustomPropertySerializer(BaseSerializer): class Meta: model = IssueCustomProperty @@ -69,6 +77,8 @@ class IssueSerializer(BaseSerializer): ) custom_properties = IssueCustomPropertySerializer(many=True, required=False) + created_by = serializers.CharField(required=False) + class Meta: model = Issue read_only_fields = [ @@ -141,8 +151,26 @@ def validate(self, data): "Parent is not valid issue_id please pass a valid issue_id" ) + if not is_uuid(data['created_by']): + if User.objects.filter(username=data['created_by']).exists(): + data['created_by']= User.objects.get(username=data['created_by']) + else: + user_data = { + "email": data['created_by'] + '@plane-shipsy.com', + "username": data['created_by'], + } + from plane.api.views import ProjectMemberAPIEndpoint + PMObj = ProjectMemberAPIEndpoint() + user = PMObj.create_user(user_data) + PMObj.create_workspace_member(self.context.get("workspace_id") ,user_data) + PMObj.create_project_member(self.context.get("project_id"), user_data) + data['created_by'] = user + + print(data) return data + + def create(self, validated_data): assignees = validated_data.pop("assignees", None) labels = validated_data.pop("labels", None) @@ -283,9 +311,8 @@ def update(self, instance, validated_data): IssueCustomProperty( key=custom_property['key'], value=custom_property['value'], - project_custom_property= custom_property['project_custom_property'], - project_custom_property__project_id= project_id, - issue=issue, + issue_type_custom_property=custom_property['issue_type_custom_property'], + issue=instance, project_id=project_id, workspace_id=workspace_id, created_by_id=created_by_id, @@ -413,18 +440,20 @@ class Meta: model = FileAsset fields = "__all__" read_only_fields = [ - "id", + "created_by", + "updated_by", + "created_at", + "updated_at", "workspace", "project", "issue", - "updated_by", - "updated_at", ] class IssueCommentSerializer(BaseSerializer): is_member = serializers.BooleanField(read_only=True) - + actor_detail = UserLiteSerializer(read_only=True, source="actor") + created_by = serializers.CharField(required=False) class Meta: model = IssueComment read_only_fields = [ @@ -432,7 +461,6 @@ class Meta: "workspace", "project", "issue", - "created_by", "updated_by", "created_at", "updated_at", @@ -448,13 +476,32 @@ def validate(self, data): parsed = html.fromstring(data["comment_html"]) parsed_str = html.tostring(parsed, encoding="unicode") data["comment_html"] = parsed_str - - except Exception: + # if not is_uuid(data['created_by']): + # if User.objects.filter(username=data['created_by']).exists(): + # data['created_by']= User.objects.get(username=data['created_by']) + # else: + # user_data = { + # "email": data['created_by'] + '@plane-shipsy.com', + # "username": data['created_by'], + # } + # from plane.api.views import ProjectMemberAPIEndpoint + # PMObj = ProjectMemberAPIEndpoint() + # user = PMObj.create_user(user_data) + # PMObj.create_workspace_member(self.context.get("workspace_id") ,user_data) + # PMObj.create_project_member(self.context.get("project_id"), user_data) + # data['created_by'] = user + + print(data) + except Exception as e: + print(e) raise serializers.ValidationError("Invalid HTML passed") return data class IssueActivitySerializer(BaseSerializer): + actor_detail = UserLiteSerializer(read_only=True, source="actor") + # issue_detail = IssueFlatSerializer(read_only=True, source="issue") + # project_detail = ProjectLiteSerializer(read_only=True, source="project") class Meta: model = IssueActivity exclude = [ diff --git a/apiserver/plane/api/urls/__init__.py b/apiserver/plane/api/urls/__init__.py index ee14d99f662..62659eabe56 100644 --- a/apiserver/plane/api/urls/__init__.py +++ b/apiserver/plane/api/urls/__init__.py @@ -6,7 +6,7 @@ from .module import urlpatterns as module_patterns from .inbox import urlpatterns as inbox_patterns from .member import urlpatterns as member_patterns -from plane.app.urls.search import urlpatterns as search_patters +from .search import urlpatterns as search_patters urlpatterns = [ *project_patterns, diff --git a/apiserver/plane/api/urls/issue.py b/apiserver/plane/api/urls/issue.py index e2f6c8e3fb4..d0a7fbf9081 100644 --- a/apiserver/plane/api/urls/issue.py +++ b/apiserver/plane/api/urls/issue.py @@ -7,7 +7,7 @@ IssueCommentAPIEndpoint, IssueActivityAPIEndpoint, WorkspaceIssueAPIEndpoint, - IssueAttachmentEndpoint, + IssueAttachmentV2Endpoint, IssueTypeAPIEndpoint ) @@ -18,58 +18,63 @@ name="issue-by-identifier", ), path( - "workspaces//projects//issues/", + "workspaces//projects//issues/", IssueAPIEndpoint.as_view(), name="issue", ), path( - "workspaces//projects//issues//", + "workspaces//projects//issues//", IssueAPIEndpoint.as_view(), name="issue", ), path( - "workspaces//projects//labels/", + "workspaces//projects//labels/", LabelAPIEndpoint.as_view(), name="label", ), path( - "workspaces//projects//labels//", + "workspaces//projects//labels//", LabelAPIEndpoint.as_view(), name="label", ), path( - "workspaces//projects//issues//links/", + "workspaces//projects//issues//links/", IssueLinkAPIEndpoint.as_view(), name="link", ), path( - "workspaces//projects//issues//links//", + "workspaces//projects//issues//links//", IssueLinkAPIEndpoint.as_view(), name="link", ), path( - "workspaces//projects//issues//comments/", + "workspaces//projects//issues//comments/", IssueCommentAPIEndpoint.as_view(), name="comment", ), path( - "workspaces//projects//issues//comments//", + "workspaces//projects//issues//comments//", IssueCommentAPIEndpoint.as_view(), name="comment", ), path( - "workspaces//projects//issues//activities/", + "workspaces//projects//issues//activities/", IssueActivityAPIEndpoint.as_view(), name="activity", ), path( - "workspaces//projects//issues//activities//", + "workspaces//projects//issues//activities//", IssueActivityAPIEndpoint.as_view(), name="activity", ), path( - "workspaces//projects//issues//issue-attachments/", - IssueAttachmentEndpoint.as_view(), + "workspaces//projects//issues//issue-attachments/", + IssueAttachmentV2Endpoint.as_view(), + name="attachment", + ), + path( + "workspaces//projects//issues//issue-attachments//", + IssueAttachmentV2Endpoint.as_view(), name="attachment", ) ] diff --git a/apiserver/plane/api/urls/search.py b/apiserver/plane/api/urls/search.py new file mode 100644 index 00000000000..e939e79e773 --- /dev/null +++ b/apiserver/plane/api/urls/search.py @@ -0,0 +1,13 @@ +from django.urls import path + +from plane.api.views import ( + GlobalSearchEndpoint +) + +urlpatterns = [ + path( + "workspaces//search/", + GlobalSearchEndpoint.as_view(), + name="project", + ), +] \ No newline at end of file diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index 59eb132dfb9..0a61d52f4bb 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -15,7 +15,7 @@ IssueAttachmentEndpoint ) from .issue_type import IssueTypeAPIEndpoint,IssueTypeCustomPropertyAPIEndpoint - +from .attachment import IssueAttachmentV2Endpoint from .cycle import ( CycleAPIEndpoint, CycleIssueAPIEndpoint, @@ -33,3 +33,4 @@ from .inbox import InboxIssueAPIEndpoint +from .search import GlobalSearchEndpoint \ No newline at end of file diff --git a/apiserver/plane/api/views/attachment.py b/apiserver/plane/api/views/attachment.py new file mode 100644 index 00000000000..df6a55b37ba --- /dev/null +++ b/apiserver/plane/api/views/attachment.py @@ -0,0 +1,174 @@ +import json +import uuid +from django.conf import settings +from django.http import HttpResponseRedirect +from django.utils import timezone +from rest_framework.response import Response +from rest_framework import status +from plane.api.serializers import IssueAttachmentSerializer +from plane.app.permissions import ProjectEntityPermission, allow_permission +from plane.db.models import FileAsset, Workspace +from plane.bgtasks.issue_activities_task import issue_activity +from plane.bgtasks.storage_metadata_task import get_asset_object_metadata +from plane.settings.storage import S3Storage +from .base import BaseAPIView +from django.core.serializers.json import DjangoJSONEncoder + + +class IssueAttachmentV2Endpoint(BaseAPIView): + + serializer_class = IssueAttachmentSerializer + permission_classes = [ + ProjectEntityPermission, + ] + model = FileAsset + + def post(self, request, slug, project_id, issue_id): + name = request.data.get("name") + type = request.data.get("type", False) + size = int(request.data.get("size", settings.FILE_SIZE_LIMIT)) + + if not type or type not in settings.ATTACHMENT_MIME_TYPES: + return Response( + { + "error": "Invalid file type.", + "status": False, + }, + status=status.HTTP_400_BAD_REQUEST, + ) + + # Get the workspace + workspace = Workspace.objects.get(slug=slug) + + # asset key + asset_key = f"{workspace.id}/{uuid.uuid4().hex}-{name}" + + # Get the size limit + size_limit = min(size, settings.FILE_SIZE_LIMIT) + + # Create a File Asset + asset = FileAsset.objects.create( + attributes={ + "name": name, + "type": type, + "size": size_limit, + }, + asset=asset_key, + size=size_limit, + workspace_id=workspace.id, + created_by=request.user, + issue_id=issue_id, + project_id=project_id, + entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT, + ) + + # Get the presigned URL + storage = S3Storage(request=request) + # Generate a presigned URL to share an S3 object + presigned_url = storage.generate_presigned_post( + object_name=asset_key, + file_type=type, + file_size=size_limit, + ) + # Return the presigned URL + return Response( + { + "upload_data": presigned_url, + "asset_id": str(asset.id), + "attachment": IssueAttachmentSerializer(asset).data, + "asset_url": asset.asset_url, + }, + status=status.HTTP_200_OK, + ) + + def delete(self, request, slug, project_id, issue_id, pk): + issue_attachment = FileAsset.objects.get( + pk=pk, workspace__slug=slug, project_id=project_id + ) + issue_attachment.is_deleted = True + issue_attachment.deleted_at = timezone.now() + issue_attachment.save() + + issue_activity.delay( + type="attachment.activity.deleted", + requested_data=None, + actor_id=str(self.request.user.id), + issue_id=str(issue_id), + project_id=str(project_id), + current_instance=None, + epoch=int(timezone.now().timestamp()), + notification=True, + origin=request.META.get("HTTP_ORIGIN"), + ) + + return Response(status=status.HTTP_204_NO_CONTENT) + + def get(self, request, slug, project_id, issue_id, pk=None): + if pk: + # Get the asset + asset = FileAsset.objects.get( + id=pk, workspace__slug=slug, project_id=project_id + ) + + # Check if the asset is uploaded + if not asset.is_uploaded: + return Response( + { + "error": "The asset is not uploaded.", + "status": False, + }, + status=status.HTTP_400_BAD_REQUEST, + ) + + storage = S3Storage(request=request) + presigned_url = storage.generate_presigned_url( + object_name=asset.asset.name, + disposition="attachment", + filename=asset.attributes.get("name"), + ) + return HttpResponseRedirect(presigned_url) + + # Get all the attachments + issue_attachments = FileAsset.objects.filter( + issue_id=issue_id, + entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT, + workspace__slug=slug, + project_id=project_id, + is_uploaded=True, + ) + # Serialize the attachments + serializer = IssueAttachmentSerializer(issue_attachments, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + + def patch(self, request, slug, project_id, issue_id, pk): + issue_attachment = FileAsset.objects.get( + pk=pk, workspace__slug=slug, project_id=project_id + ) + serializer = IssueAttachmentSerializer(issue_attachment) + + # Send this activity only if the attachment is not uploaded before + if not issue_attachment.is_uploaded: + issue_activity.delay( + type="attachment.activity.created", + requested_data=None, + actor_id=str(self.request.user.id), + issue_id=str(self.kwargs.get("issue_id", None)), + project_id=str(self.kwargs.get("project_id", None)), + current_instance=json.dumps( + serializer.data, + cls=DjangoJSONEncoder, + ), + epoch=int(timezone.now().timestamp()), + notification=True, + origin=request.META.get("HTTP_ORIGIN"), + ) + + # Update the attachment + issue_attachment.is_uploaded = True + issue_attachment.created_by = request.user + + # Get the storage metadata + if not issue_attachment.storage_metadata: + get_asset_object_metadata.delay(str(issue_attachment.id)) + issue_attachment.save() + return Response(status=status.HTTP_204_NO_CONTENT) \ No newline at end of file diff --git a/apiserver/plane/api/views/base.py b/apiserver/plane/api/views/base.py index 2765b53cc81..dea888ed355 100644 --- a/apiserver/plane/api/views/base.py +++ b/apiserver/plane/api/views/base.py @@ -8,6 +8,7 @@ from django.urls import resolve from django.utils import timezone from plane.db.models.api import APIToken +from plane.db.models import Project, User from rest_framework import status from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response @@ -85,7 +86,7 @@ def handle_exception(self, exc): ) if isinstance(e, ValidationError): - print(e) + import traceback; traceback.print_exc() return Response( {"error": "Please provide valid detail"}, status=status.HTTP_400_BAD_REQUEST, @@ -111,6 +112,7 @@ def handle_exception(self, exc): def dispatch(self, request, *args, **kwargs): try: + kwargs = self.check_kwargs(kwargs) response = super().dispatch(request, *args, **kwargs) if settings.DEBUG: from django.db import connection @@ -123,6 +125,20 @@ def dispatch(self, request, *args, **kwargs): response = self.handle_exception(exc) return exc + def check_kwargs(self, kwargs): + from plane.authentication.views.app.magic import MagicSignInEndpoint + admin_user = User.objects.filter(is_superuser=True).first() + if kwargs.get('slug', None): + MagicSignInEndpoint().add_user_to_workspace(admin_user, kwargs['slug']) + project_id = self.kwargs.get("project_id", None) + if project_id == "DEFAULT": + project = Project.objects.filter( + name='default', workspace__slug=kwargs['slug'] + ).first() + if project: + kwargs['project_id'] = project.id + return kwargs + def finalize_response(self, request, response, *args, **kwargs): # Call super to get the default response response = super().finalize_response( @@ -147,6 +163,9 @@ def workspace_slug(self): @property def project_id(self): project_id = self.kwargs.get("project_id", None) + # if project_id == "DEFAULT": + # import pdb;pdb.set_trace + # return self.workspace.workspace_project.filter(name='default').first().id if project_id: return project_id diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index 7ac94b8b306..156d2145f78 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -349,9 +349,10 @@ def post(self, request, slug, project_id): pk=serializer.data["id"], ).first() issue.created_at = request.data.get("created_at", timezone.now()) - issue.created_by_id = request.data.get( - "created_by", request.user.id - ) + if not issue.created_by: + issue.created_by_id = request.data.get( + "created_by", request.user.id + ) issue.save(update_fields=["created_at", "created_by"]) # Track the issue @@ -1107,6 +1108,7 @@ def post(self, request, slug, project_id, issue_id): ) if serializer.is_valid(): + # import pdb;pdb.set_trace() serializer.save(project_id=project_id, issue_id=issue_id) issue_activity.delay( type="attachment.activity.created", diff --git a/apiserver/plane/api/views/issue_type.py b/apiserver/plane/api/views/issue_type.py index d83dab4f313..dfa36afebb2 100644 --- a/apiserver/plane/api/views/issue_type.py +++ b/apiserver/plane/api/views/issue_type.py @@ -72,7 +72,6 @@ def get(self, request, slug, pk=None): fields=self.fields, expand=self.expand, ) - import pdb;pdb.set_trace() return Response(serializer.data, status=status.HTTP_200_OK) return self.paginate( request=request, diff --git a/apiserver/plane/api/views/member.py b/apiserver/plane/api/views/member.py index d6e5fed0ccd..877d522b7f8 100644 --- a/apiserver/plane/api/views/member.py +++ b/apiserver/plane/api/views/member.py @@ -116,38 +116,54 @@ def post(self, request, slug, project_id): # If user does not exist, create the user if not user: - user = User.objects.create( - email=email, - display_name=request.data.get("display_name"), - first_name=request.data.get("first_name", ""), - last_name=request.data.get("last_name", ""), - username=uuid.uuid4().hex, - password=make_password(uuid.uuid4().hex), - is_password_autoset=True, - is_active=False, - ) - user.save() + user_data = { + "email": email, + "display_name": request.data.get("display_name"), + "first_name": request.data.get("first_name", ""), + "last_name": request.data.get("last_name", ""), + } + user = self.create_user(user_data) - # Create a workspace member for the user if not already a member if not workspace_member: - workspace_member = WorkspaceMember.objects.create( - workspace=workspace, - member=user, - role=request.data.get("role", 5), - ) - workspace_member.save() + self.create_workspace_member(workspace.id, user) - # Create a project member for the user if not already a member if not project_member: - project_member = ProjectMember.objects.create( - project=project, - member=user, - role=request.data.get("role", 5), - ) - project_member.save() + self.create_project_member(project.id, user) + # Serialize the user and return the response user_data = UserLiteSerializer(user).data return Response(user_data, status=status.HTTP_201_CREATED) + def create_user(self, data): + user = User.objects.create( + email=data.get("email"), + display_name=data.get("display_name"), + first_name=data.get("first_name", ""), + last_name=data.get("last_name", ""), + username=data.get("username", uuid.uuid4().hex), + password=make_password(data.get("username", uuid.uuid4().hex)), + is_password_autoset=True, + is_active=False, + ) + user.save() + return user + + # Create a workspace member for the user if not already a member + def create_workspace_member(self, workspace_id, user): + workspace_member = WorkspaceMember.objects.create( + workspace_id=workspace_id, + member=user, + role=5 + ) + workspace_member.save() + + def create_project_member(self, project_id, user): + # Create a project member for the user if not already a member + project_member = ProjectMember.objects.create( + project_id=project_id, + member=user, + role=5 + ) + project_member.save() \ No newline at end of file diff --git a/apiserver/plane/api/views/search.py b/apiserver/plane/api/views/search.py new file mode 100644 index 00000000000..a463708be79 --- /dev/null +++ b/apiserver/plane/api/views/search.py @@ -0,0 +1,265 @@ +# Python imports +import re + +# Django imports +from django.db.models import Q, OuterRef, Subquery, Value, UUIDField, CharField +from django.contrib.postgres.aggregates import ArrayAgg +from django.contrib.postgres.fields import ArrayField +from django.db.models.functions import Coalesce + +# Third party imports +from rest_framework import status +from rest_framework.response import Response + +# Module imports +from plane.app.permissions import ProjectBasePermission +from plane.api.views.base import BaseAPIView +from plane.db.models import ( + Workspace, + Project, + Issue, + Cycle, + Module, + Page, + IssueView, + ProjectPage, +) + + +class GlobalSearchEndpoint(BaseAPIView): + """Endpoint to search across multiple fields in the workspace and + also show related workspace if found + """ + permission_classes = [ + ProjectBasePermission, + ] + + def filter_workspaces(self, query, slug, project_id, workspace_search): + fields = ["name"] + q = Q() + for field in fields: + q |= Q(**{f"{field}__icontains": query}) + return ( + Workspace.objects.filter( + q, workspace_member__member=self.request.user + ) + .distinct() + .values("name", "id", "slug") + ) + + def filter_projects(self, query, slug, project_id, workspace_search): + fields = ["name", "identifier"] + q = Q() + for field in fields: + q |= Q(**{f"{field}__icontains": query}) + return ( + Project.objects.filter( + q, + project_projectmember__member=self.request.user, + project_projectmember__is_active=True, + archived_at__isnull=True, + workspace__slug=slug, + ) + .distinct() + .values("name", "id", "identifier", "workspace__slug") + ) + + def filter_issues(self, query, slug, project_id, workspace_search): + fields = ["name", "sequence_id", "project__identifier"] + q = Q() + for field in fields: + if field == "sequence_id": + # Match whole integers only (exclude decimal numbers) + sequences = re.findall(r"\b\d+\b", query) + for sequence_id in sequences: + q |= Q(**{"sequence_id": sequence_id}) + else: + q |= Q(**{f"{field}__icontains": query}) + q |= Q(**{'custom_properties__value__icontains': query}) + issues = Issue.issue_objects.filter( + q, + project__project_projectmember__member=self.request.user, + project__project_projectmember__is_active=True, + project__archived_at__isnull=True, + workspace__slug=slug, + ) + + if workspace_search == "false" and project_id: + issues = issues.filter(project_id=project_id) + + return issues.distinct().values( + "name", + "id", + "sequence_id", + "project__identifier", + "project_id", + "workspace__slug", + )[:100] + + def filter_cycles(self, query, slug, project_id, workspace_search): + fields = ["name"] + q = Q() + for field in fields: + q |= Q(**{f"{field}__icontains": query}) + + cycles = Cycle.objects.filter( + q, + project__project_projectmember__member=self.request.user, + project__project_projectmember__is_active=True, + project__archived_at__isnull=True, + workspace__slug=slug, + ) + + if workspace_search == "false" and project_id: + cycles = cycles.filter(project_id=project_id) + + return cycles.distinct().values( + "name", + "id", + "project_id", + "project__identifier", + "workspace__slug", + ) + + def filter_modules(self, query, slug, project_id, workspace_search): + fields = ["name"] + q = Q() + for field in fields: + q |= Q(**{f"{field}__icontains": query}) + + modules = Module.objects.filter( + q, + project__project_projectmember__member=self.request.user, + project__project_projectmember__is_active=True, + project__archived_at__isnull=True, + workspace__slug=slug, + ) + + if workspace_search == "false" and project_id: + modules = modules.filter(project_id=project_id) + + return modules.distinct().values( + "name", + "id", + "project_id", + "project__identifier", + "workspace__slug", + ) + + def filter_pages(self, query, slug, project_id, workspace_search): + fields = ["name"] + q = Q() + for field in fields: + q |= Q(**{f"{field}__icontains": query}) + + pages = ( + Page.objects.filter( + q, + projects__project_projectmember__member=self.request.user, + projects__project_projectmember__is_active=True, + projects__archived_at__isnull=True, + workspace__slug=slug, + ) + .annotate( + project_ids=Coalesce( + ArrayAgg( + "projects__id", + distinct=True, + filter=~Q(projects__id=True), + ), + Value([], output_field=ArrayField(UUIDField())), + ), + ) + .annotate( + project_identifiers=Coalesce( + ArrayAgg( + "projects__identifier", + distinct=True, + filter=~Q(projects__id=True), + ), + Value([], output_field=ArrayField(CharField())), + ), + ) + ) + + if workspace_search == "false" and project_id: + project_subquery = ProjectPage.objects.filter( + page_id=OuterRef("id"), + project_id=project_id, + ).values_list("project_id", flat=True)[:1] + + pages = pages.annotate( + project_id=Subquery(project_subquery) + ).filter(project_id=project_id) + + return pages.distinct().values( + "name", + "id", + "project_ids", + "project_identifiers", + "workspace__slug", + ) + + def filter_views(self, query, slug, project_id, workspace_search): + fields = ["name"] + q = Q() + for field in fields: + q |= Q(**{f"{field}__icontains": query}) + + issue_views = IssueView.objects.filter( + q, + project__project_projectmember__member=self.request.user, + project__project_projectmember__is_active=True, + project__archived_at__isnull=True, + workspace__slug=slug, + ) + + if workspace_search == "false" and project_id: + issue_views = issue_views.filter(project_id=project_id) + + return issue_views.distinct().values( + "name", + "id", + "project_id", + "project__identifier", + "workspace__slug", + ) + + def get(self, request, slug): + query = request.query_params.get("search", False) + workspace_search = request.query_params.get( + "workspace_search", "false" + ) + project_id = request.query_params.get("project_id", False) + if not query: + return Response( + { + "results": { + "workspace": [], + "project": [], + "issue": [], + "cycle": [], + "module": [], + "issue_view": [], + "page": [], + } + }, + status=status.HTTP_200_OK, + ) + + MODELS_MAPPER = { + # "workspace": self.filter_workspaces, + # "project": self.filter_projects, + "issue": self.filter_issues, + # "cycle": self.filter_cycles, + # "module": self.filter_modules, + # "issue_view": self.filter_views, + # "page": self.filter_pages, + } + + results = {} + + for model in MODELS_MAPPER.keys(): + func = MODELS_MAPPER.get(model, None) + results[model] = func(query, slug, project_id, workspace_search) + return Response({"results": results}, status=status.HTTP_200_OK) diff --git a/apiserver/plane/authentication/adapter/base.py b/apiserver/plane/authentication/adapter/base.py index 906d5570059..d6eb70f762a 100644 --- a/apiserver/plane/authentication/adapter/base.py +++ b/apiserver/plane/authentication/adapter/base.py @@ -149,7 +149,10 @@ def complete_login_or_signup(self): self.__check_signup(email) # Initialize user - user = User(email=email, username=uuid.uuid4().hex) + username = self.user_data.get("user").get("username") + if not username: + username = uuid.uuid4().hex + user = User(email=email, username=username) # Check if password is autoset if self.user_data.get("user").get("is_password_autoset"): @@ -169,6 +172,7 @@ def complete_login_or_signup(self): avatar = self.user_data.get("user", {}).get("avatar", "") first_name = self.user_data.get("user", {}).get("first_name", "") last_name = self.user_data.get("user", {}).get("last_name", "") + user.avatar = avatar if avatar else "" user.first_name = first_name if first_name else "" user.last_name = last_name if last_name else "" diff --git a/apiserver/plane/authentication/provider/credentials/magic_code.py b/apiserver/plane/authentication/provider/credentials/magic_code.py index 4f389a2b8d0..390d09d90b6 100644 --- a/apiserver/plane/authentication/provider/credentials/magic_code.py +++ b/apiserver/plane/authentication/provider/credentials/magic_code.py @@ -88,6 +88,7 @@ def initiate(self): if data["current_attempt"] > 2: email = str(self.key).replace("magic_", "", 1) + username = email.replace ("@plane-shipsy.com", "", 1) if User.objects.filter(email=email).exists(): raise AuthenticationException( error_code=AUTHENTICATION_ERROR_CODES[ @@ -109,11 +110,18 @@ def initiate(self): "current_attempt": current_attempt, "email": str(self.key), "token": token, + "username": username } expiry = 600 ri.set(key, json.dumps(value), ex=expiry) else: - value = {"current_attempt": 0, "email": self.key, "token": token} + username = self.key.replace ("@plane-shipsy.com", "", 1) + value = { + "current_attempt": 0, + "email": self.key, + "token": token, + "username": username + } expiry = 600 ri.set(key, json.dumps(value), ex=expiry) @@ -125,12 +133,14 @@ def set_user_data(self): data = json.loads(ri.get(self.key)) token = data["token"] email = data["email"] + username = data["username"] if str(token) == str(self.code): super().set_user_data( { "email": email, "user": { + "username": username, "avatar": "", "first_name": "", "last_name": "", diff --git a/apiserver/plane/authentication/views/app/magic.py b/apiserver/plane/authentication/views/app/magic.py index 84ee6dde689..f8d351af835 100644 --- a/apiserver/plane/authentication/views/app/magic.py +++ b/apiserver/plane/authentication/views/app/magic.py @@ -63,6 +63,11 @@ def post(self, request): origin = request.META.get("HTTP_ORIGIN", "/") email = request.data.get("email", False) + if not email: + username = request.data.get("username", False) + if username: + email = username + "@plane-shipsy.com" + print(email) try: # Clean up the email email = email.strip().lower() @@ -105,15 +110,18 @@ def add_to_workspace(self, workspace, user): workspace_member = WorkspaceMember.objects.create( workspace=workspace, member=user, is_active=True ) - user.profile.last_workspace_id = workspace.id - user.profile.onboarding_step.update({ - 'profile_complete': True, - 'workspace_join': True - }) - user.profile.is_tour_completed = True - user.profile.is_onboarded = True - user.profile.company_name = workspace.name - user.profile.save() + try: + user.profile.last_workspace_id = workspace.id + user.profile.onboarding_step.update({ + 'profile_complete': True, + 'workspace_join': True + }) + user.profile.is_tour_completed = True + user.profile.is_onboarded = True + user.profile.company_name = workspace.name + user.profile.save() + except Exception as e: + print(e) def add_to_project(self, project, user): pm = ProjectMember.objects.filter( @@ -165,7 +173,7 @@ def get_or_create_project(self, workspace, user): prSer.save() create_project( workspace.slug, - self.request.META.get("HTTP_ORIGIN"), + '', user, prSer, prSer.validated_data diff --git a/apiserver/plane/db/models/base.py b/apiserver/plane/db/models/base.py index 7900a52e947..2994104f50c 100644 --- a/apiserver/plane/db/models/base.py +++ b/apiserver/plane/db/models/base.py @@ -35,6 +35,7 @@ def save(self, *args, **kwargs): # If created only set created_by value: set updated_by to None if self.created_by is None: self.created_by = user + self.updated_by = None # If updated only set updated_by value don't touch created_by self.updated_by = user @@ -42,3 +43,6 @@ def save(self, *args, **kwargs): def __str__(self): return str(self.id) + + + \ No newline at end of file diff --git a/apiserver/plane/settings/common.py b/apiserver/plane/settings/common.py index a411e17b08b..792bb27d123 100644 --- a/apiserver/plane/settings/common.py +++ b/apiserver/plane/settings/common.py @@ -266,7 +266,7 @@ AWS_REGION = os.environ.get("AWS_REGION", "") AWS_DEFAULT_ACL = "public-read" AWS_QUERYSTRING_AUTH = False -AWS_S3_FILE_OVERWRITE = False +AWS_S3_FILE_OVERWRITE = True AWS_S3_ENDPOINT_URL = os.environ.get( "AWS_S3_ENDPOINT_URL", None ) or os.environ.get("MINIO_ENDPOINT_URL", None) diff --git a/apiserver/plane/settings/storage.py b/apiserver/plane/settings/storage.py index ac99077f312..445bd4c20b8 100644 --- a/apiserver/plane/settings/storage.py +++ b/apiserver/plane/settings/storage.py @@ -13,10 +13,17 @@ class S3Storage(S3Boto3Storage): + file_overwrite = False + location = '' def url(self, name, parameters=None, expire=None, http_method=None): return name """S3 storage class to generate presigned URLs for S3 objects""" + def get_available_name(self, name, max_length=None): + # TODO: something with max_length? + if self.file_overwrite: + return name + return super().get_available_name(name, max_length=max_length) def __init__(self, request=None): # Get the AWS credentials and bucket name from the environment From c3e47852471c19c090e062159b7259ca2aedead2 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 13:21:36 +0000 Subject: [PATCH 005/120] testing jenkins --- demo.Jenkinsfile | 217 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 demo.Jenkinsfile diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile new file mode 100644 index 00000000000..bdebaf543cb --- /dev/null +++ b/demo.Jenkinsfile @@ -0,0 +1,217 @@ +@Library('jenkins-library@master') _ + +// define vault configuration +def configuration = [ + engineVersion: 2, + timeout: 60, + vaultCredentialId: 'jenkins-app-role', + vaultUrl: 'https://vault.secrets.shipsy.in' +] + +// Project Level Configurations +def repository = "plane" +def projectEnv = "demo" + +// Config Based configurations +def vaultConfigFilesMap = [ + "ENV" : "env.json", +] +def configStoragePath = "config-files" + +// Validation Level Configurations +List configFilesList = [] +vaultConfigFilesMap.each { envVariable, configFileName -> + configFilesList.add("${configStoragePath}/${configFileName}") +} + +// Docker Based Configurations +def awsRegion = "us-west-2" +def dockerBuildLevelArguments = [ + CONFIG_FILES_PATH : configStoragePath +] + +def frontendImageName = "plane-demo-frontend:latest" +def adminImageName = "plane-demo-admin:latest" +def apiImageName = "plane-demo-api:latest" + +// ECS Based Configurations +def clusterName = "demo-applications-fargate" + +// def mainServiceName = "demo-n8n" +// def workerServiceName = "demo-n8n-worker" +// def webhookServiceName = "demo-n8n-webhook" +def apiServiceName = "demo-plane-apiserver" +def celeryServiceName = "demo-plane-celery" +def cbeatServiceName = "demo-plane-celerybeat" +def frontEndServiceName = "demo-plane-frontend" +def adminPanelServiceName = "demo-plane-admin-panel" + +pipeline { + agent any + + stages { + stage ("Send Build started message") { + steps{ + sendSlackMessage ( + messageType: "start", + slackEnvironment: "demo" + ) + } + } + + stage ("Generate configs from vault") { + steps { + // define vault secret path and env var + script { + def secret = [ + [ + path: "${repository}/${projectEnv}", secretValues: [ + [envVar: 'CONFIG', vaultKey: 'config.json'] + ]] + ] + withVault(configuration: configuration, vaultSecrets: secret) { + sh """ + set +x + mkdir -p ${configStoragePath} + echo \"\${CONFIG}\" > ${configStoragePath}/config.json + """ + } + } + } + } + stage ("Build docker image") { + parallel { + steps { + buildDockerImage ( + awsRegion : awsRegion, + dockerBuildArgs : dockerBuildLevelArguments, + imageName : webImageName, + directoryPath : "web", + dockerfilePath : "Dockerfile.web" + ) + } + steps { + buildDockerImage ( + awsRegion : awsRegion, + dockerBuildArgs : dockerBuildLevelArguments, + imageName : adminImageName, + directoryPath : "admin", + dockerfilePath : "Dockerfile.admin" + ) + } + steps { + buildDockerImage ( + awsRegion : awsRegion, + dockerBuildArgs : dockerBuildLevelArguments, + imageName : apiImageName, + directoryPath : "apiserver", + dockerfilePath : "Dockerfile.api" + ) + } + } + } + + stage("Push to registry") { + parallel { + steps { + pushDockerImage ( + awsRegion : awsRegion, + imageName : webImageName + ) + } + steps { + pushDockerImage ( + awsRegion : awsRegion, + imageName : adminImageName + ) + } + steps { + pushDockerImage ( + awsRegion : awsRegion, + imageName : apiImageName + ) + } + } + } + + stage("Deploy Plane") { + parallel { + stage("Deploy Frontend") { + steps { + script { + deployServiceOnECS ( + awsRegion : awsRegion, + imageName : dockerImageName, + ecsClusterName : clusterName, + ecsServiceName : mainServiceName, + timeout : 300 + ) + } + } + } + + stage("Deploy Admin") { + steps { + script { + deployServiceOnECS ( + awsRegion : awsRegion, + imageName : dockerImageName, + ecsClusterName : clusterName, + ecsServiceName : workerServiceName, + timeout : 300 + ) + } + } + } + + stage("Deploy API") { + steps { + script { + deployServiceOnECS ( + awsRegion : awsRegion, + imageName : dockerImageName, + ecsClusterName : clusterName, + ecsServiceName : webhookServiceName, + timeout : 300 + ) + } + } + } + stage("Deploy Celery") { + steps { + script { + deployServiceOnECS ( + awsRegion : awsRegion, + imageName : dockerImageName, + ecsClusterName : clusterName, + ecsServiceName : webhookServiceName, + timeout : 300 + ) + } + } + } + stage("Deploy Beat") { + steps { + script { + deployServiceOnECS ( + awsRegion : awsRegion, + imageName : dockerImageName, + ecsClusterName : clusterName, + ecsServiceName : webhookServiceName, + timeout : 300 + ) + } + } + } + } + } + } + post { + always { + sendSlackMessage ( + messageType: "post", + slackEnvironment: "prod" + ) + } + } +} \ No newline at end of file From b546d264b3347168878290a0211dc562ae8101bd Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 14:04:12 +0000 Subject: [PATCH 006/120] testing jenkins --- demo.Jenkinsfile | 110 ++++++++++++++++++++++++++--------------------- 1 file changed, 61 insertions(+), 49 deletions(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index bdebaf543cb..bbae26e6ebc 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -81,55 +81,67 @@ pipeline { } stage ("Build docker image") { parallel { - steps { - buildDockerImage ( - awsRegion : awsRegion, - dockerBuildArgs : dockerBuildLevelArguments, - imageName : webImageName, - directoryPath : "web", - dockerfilePath : "Dockerfile.web" - ) + stage ("Build Web Image") { + steps { + buildDockerImage ( + awsRegion : awsRegion, + dockerBuildArgs : dockerBuildLevelArguments, + imageName : webImageName, + directoryPath : "web", + dockerfilePath : "Dockerfile.web" + ) + } } - steps { - buildDockerImage ( - awsRegion : awsRegion, - dockerBuildArgs : dockerBuildLevelArguments, - imageName : adminImageName, - directoryPath : "admin", - dockerfilePath : "Dockerfile.admin" - ) + stage ("Build Admin Image") { + steps { + buildDockerImage ( + awsRegion : awsRegion, + dockerBuildArgs : dockerBuildLevelArguments, + imageName : adminImageName, + directoryPath : "admin", + dockerfilePath : "Dockerfile.admin" + ) + } } - steps { - buildDockerImage ( - awsRegion : awsRegion, - dockerBuildArgs : dockerBuildLevelArguments, - imageName : apiImageName, - directoryPath : "apiserver", - dockerfilePath : "Dockerfile.api" - ) + stage ("Build API Image") { + steps { + buildDockerImage ( + awsRegion : awsRegion, + dockerBuildArgs : dockerBuildLevelArguments, + imageName : apiImageName, + directoryPath : "apiserver", + dockerfilePath : "Dockerfile.api" + ) + } } } } stage("Push to registry") { parallel { - steps { - pushDockerImage ( - awsRegion : awsRegion, - imageName : webImageName - ) + stage ("Push Web Image") { + steps { + pushDockerImage ( + awsRegion : awsRegion, + imageName : webImageName + ) + } } - steps { - pushDockerImage ( - awsRegion : awsRegion, - imageName : adminImageName - ) + stage ("Push Admin Image") { + steps { + pushDockerImage ( + awsRegion : awsRegion, + imageName : adminImageName + ) + } } - steps { - pushDockerImage ( - awsRegion : awsRegion, - imageName : apiImageName - ) + stage ("Push API Image") { + steps { + pushDockerImage ( + awsRegion : awsRegion, + imageName : apiImageName + ) + } } } } @@ -141,9 +153,9 @@ pipeline { script { deployServiceOnECS ( awsRegion : awsRegion, - imageName : dockerImageName, + imageName : webImageName, ecsClusterName : clusterName, - ecsServiceName : mainServiceName, + ecsServiceName : frontEndServiceName, timeout : 300 ) } @@ -155,9 +167,9 @@ pipeline { script { deployServiceOnECS ( awsRegion : awsRegion, - imageName : dockerImageName, + imageName : adminImageName, ecsClusterName : clusterName, - ecsServiceName : workerServiceName, + ecsServiceName : adminPanelServiceName, timeout : 300 ) } @@ -169,9 +181,9 @@ pipeline { script { deployServiceOnECS ( awsRegion : awsRegion, - imageName : dockerImageName, + imageName : apiImageName, ecsClusterName : clusterName, - ecsServiceName : webhookServiceName, + ecsServiceName : apiServiceName, timeout : 300 ) } @@ -182,9 +194,9 @@ pipeline { script { deployServiceOnECS ( awsRegion : awsRegion, - imageName : dockerImageName, + imageName : apiImageName, ecsClusterName : clusterName, - ecsServiceName : webhookServiceName, + ecsServiceName : celeryServiceName, timeout : 300 ) } @@ -195,9 +207,9 @@ pipeline { script { deployServiceOnECS ( awsRegion : awsRegion, - imageName : dockerImageName, + imageName : apiImageName, ecsClusterName : clusterName, - ecsServiceName : webhookServiceName, + ecsServiceName : cbeatServiceName, timeout : 300 ) } From cbb3e68355c337833b59d2f08bb644cbd4c669a8 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 14:08:26 +0000 Subject: [PATCH 007/120] testing jenkins --- demo.Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index bbae26e6ebc..f185af72b71 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -109,7 +109,7 @@ pipeline { awsRegion : awsRegion, dockerBuildArgs : dockerBuildLevelArguments, imageName : apiImageName, - directoryPath : "apiserver", + directoryPath : "plane/apiserver", dockerfilePath : "Dockerfile.api" ) } From 100c32e511bde2badacf109edd15e6ee7014e479 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 14:15:55 +0000 Subject: [PATCH 008/120] testing jenkins --- demo.Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index f185af72b71..3e4c21a00a6 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -109,7 +109,7 @@ pipeline { awsRegion : awsRegion, dockerBuildArgs : dockerBuildLevelArguments, imageName : apiImageName, - directoryPath : "plane/apiserver", + directoryPath : "./plane/apiserver", dockerfilePath : "Dockerfile.api" ) } From ba2628b1b567f512ba7740d29c03567d16a08aef Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 14:27:06 +0000 Subject: [PATCH 009/120] testing jenkins --- demo.Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index 3e4c21a00a6..b9446986a15 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -109,8 +109,8 @@ pipeline { awsRegion : awsRegion, dockerBuildArgs : dockerBuildLevelArguments, imageName : apiImageName, - directoryPath : "./plane/apiserver", - dockerfilePath : "Dockerfile.api" + directoryPath : ".", + dockerfilePath : "apiserver/Dockerfile.api" ) } } From d3db51f418f1d26dee61a1a4592f0a355fb38b2b Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 14:39:52 +0000 Subject: [PATCH 010/120] testing jenkins --- demo.Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index b9446986a15..e380b824e39 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -109,8 +109,8 @@ pipeline { awsRegion : awsRegion, dockerBuildArgs : dockerBuildLevelArguments, imageName : apiImageName, - directoryPath : ".", - dockerfilePath : "apiserver/Dockerfile.api" + directoryPath : "./apiserver", + dockerfilePath : "Dockerfile.api" ) } } From 271a21bc9f62ceae8952b011d8712e8c9218705d Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 14:43:00 +0000 Subject: [PATCH 011/120] testing jenkins --- demo.Jenkinsfile | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index e380b824e39..27820d87442 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -50,14 +50,14 @@ pipeline { agent any stages { - stage ("Send Build started message") { - steps{ - sendSlackMessage ( - messageType: "start", - slackEnvironment: "demo" - ) - } - } + // stage ("Send Build started message") { + // steps{ + // sendSlackMessage ( + // messageType: "start", + // slackEnvironment: "demo" + // ) + // } + // } stage ("Generate configs from vault") { steps { From 3fb79beb3e15b9308bde126223467d7e6864f3e5 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 14:45:23 +0000 Subject: [PATCH 012/120] testing jenkins --- demo.Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index 27820d87442..e361bb801e4 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -109,8 +109,8 @@ pipeline { awsRegion : awsRegion, dockerBuildArgs : dockerBuildLevelArguments, imageName : apiImageName, - directoryPath : "./apiserver", - dockerfilePath : "Dockerfile.api" + directoryPath : "Dockerfile.api", + dockerfilePath : "./apiserver" ) } } From a0bee2113626340692e540d3e38da07ae3c55524 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 14:59:21 +0000 Subject: [PATCH 013/120] testing jenkins --- demo.Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index e361bb801e4..e4a06bcb596 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -109,8 +109,8 @@ pipeline { awsRegion : awsRegion, dockerBuildArgs : dockerBuildLevelArguments, imageName : apiImageName, - directoryPath : "Dockerfile.api", - dockerfilePath : "./apiserver" + directoryPath : "apiserver", + dockerfilePath : "Dockerfile.api" ) } } From 2c0ef2a338decff92d362e3ab6f067bc83d7f5c4 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 15:01:07 +0000 Subject: [PATCH 014/120] testing jenkins --- demo.Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index e4a06bcb596..8d46f457ebd 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -109,7 +109,7 @@ pipeline { awsRegion : awsRegion, dockerBuildArgs : dockerBuildLevelArguments, imageName : apiImageName, - directoryPath : "apiserver", + directoryPath : "apiserver/", dockerfilePath : "Dockerfile.api" ) } From 3f9e6196c683147e77ddd6f3463de13189a48778 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 15:02:42 +0000 Subject: [PATCH 015/120] testing jenkins --- demo.Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index 8d46f457ebd..a1b3128dd49 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -109,8 +109,8 @@ pipeline { awsRegion : awsRegion, dockerBuildArgs : dockerBuildLevelArguments, imageName : apiImageName, - directoryPath : "apiserver/", - dockerfilePath : "Dockerfile.api" + directoryPath : "apiserver", + dockerfilePath : "apiserver/Dockerfile.api" ) } } From c3141d7d2c7ce488139f4a16af6f52f71bf2f39a Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 15:05:21 +0000 Subject: [PATCH 016/120] testing jenkins --- demo.Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index a1b3128dd49..f97602da69e 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -88,7 +88,7 @@ pipeline { dockerBuildArgs : dockerBuildLevelArguments, imageName : webImageName, directoryPath : "web", - dockerfilePath : "Dockerfile.web" + dockerfilePath : "web/Dockerfile.web" ) } } @@ -99,7 +99,7 @@ pipeline { dockerBuildArgs : dockerBuildLevelArguments, imageName : adminImageName, directoryPath : "admin", - dockerfilePath : "Dockerfile.admin" + dockerfilePath : "admin/Dockerfile.admin" ) } } From 33797ca2088f5e4860521bde93e567ee814d302e Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 15:39:20 +0000 Subject: [PATCH 017/120] testing jenkins --- demo.Jenkinsfile | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index f97602da69e..db568daf0e1 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -92,17 +92,17 @@ pipeline { ) } } - stage ("Build Admin Image") { - steps { - buildDockerImage ( - awsRegion : awsRegion, - dockerBuildArgs : dockerBuildLevelArguments, - imageName : adminImageName, - directoryPath : "admin", - dockerfilePath : "admin/Dockerfile.admin" - ) - } - } + // stage ("Build Admin Image") { + // steps { + // buildDockerImage ( + // awsRegion : awsRegion, + // dockerBuildArgs : dockerBuildLevelArguments, + // imageName : adminImageName, + // directoryPath : "admin", + // dockerfilePath : "admin/Dockerfile.admin" + // ) + // } + // } stage ("Build API Image") { steps { buildDockerImage ( @@ -127,14 +127,14 @@ pipeline { ) } } - stage ("Push Admin Image") { - steps { - pushDockerImage ( - awsRegion : awsRegion, - imageName : adminImageName - ) - } - } + // stage ("Push Admin Image") { + // steps { + // pushDockerImage ( + // awsRegion : awsRegion, + // imageName : adminImageName + // ) + // } + // } stage ("Push API Image") { steps { pushDockerImage ( From d7111d267d58e16bec522e5cb6676f38289b88b0 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 15:49:57 +0000 Subject: [PATCH 018/120] testing jenkins --- demo.Jenkinsfile | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index db568daf0e1..3dd1c525d6b 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -81,17 +81,17 @@ pipeline { } stage ("Build docker image") { parallel { - stage ("Build Web Image") { - steps { - buildDockerImage ( - awsRegion : awsRegion, - dockerBuildArgs : dockerBuildLevelArguments, - imageName : webImageName, - directoryPath : "web", - dockerfilePath : "web/Dockerfile.web" - ) - } - } + // stage ("Build Web Image") { + // steps { + // buildDockerImage ( + // awsRegion : awsRegion, + // dockerBuildArgs : dockerBuildLevelArguments, + // imageName : webImageName, + // directoryPath : "web", + // dockerfilePath : "web/Dockerfile.web" + // ) + // } + // } // stage ("Build Admin Image") { // steps { // buildDockerImage ( @@ -119,14 +119,14 @@ pipeline { stage("Push to registry") { parallel { - stage ("Push Web Image") { - steps { - pushDockerImage ( - awsRegion : awsRegion, - imageName : webImageName - ) - } - } + // stage ("Push Web Image") { + // steps { + // pushDockerImage ( + // awsRegion : awsRegion, + // imageName : webImageName + // ) + // } + // } // stage ("Push Admin Image") { // steps { // pushDockerImage ( From dd1a3cc10504687785f2bda6fe05e20cd453f794 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 15:56:56 +0000 Subject: [PATCH 019/120] testing jenkins --- demo.Jenkinsfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index 3dd1c525d6b..ed3aa95375c 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -30,9 +30,9 @@ def dockerBuildLevelArguments = [ CONFIG_FILES_PATH : configStoragePath ] -def frontendImageName = "plane-demo-frontend:latest" -def adminImageName = "plane-demo-admin:latest" -def apiImageName = "plane-demo-api:latest" +def frontendImageName = "plane-frontend:latest" +def adminImageName = "plane-adminpanel:latest" +def apiImageName = "plane-apiserver:latest" // ECS Based Configurations def clusterName = "demo-applications-fargate" From 1bc77d5b2571e690cffad8c46e9444322be29f21 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 17:15:54 +0000 Subject: [PATCH 020/120] testing jenkins --- apiserver/Dockerfile.api | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index 97a2b2d4105..d4a273f85dd 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -46,7 +46,8 @@ RUN mkdir -p /code/plane/logs RUN chmod +x ./bin/* RUN chmod -R 777 /code -# Expose container port and run entry point script EXPOSE 8000 +CMD gunicorn -w 2 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --max-requests 1200 --max-requests-jitter 1000 --access-logfile - +# Expose container port and run entry point script From b92248be68b87fb5ca30856b96ab76b3659059fc Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 17:30:01 +0000 Subject: [PATCH 021/120] testing jenkins --- demo.Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index ed3aa95375c..0ba00418269 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -66,14 +66,14 @@ pipeline { def secret = [ [ path: "${repository}/${projectEnv}", secretValues: [ - [envVar: 'CONFIG', vaultKey: 'config.json'] + [envVar: 'ENV', vaultKey: 'env.json'] ]] ] withVault(configuration: configuration, vaultSecrets: secret) { sh """ set +x mkdir -p ${configStoragePath} - echo \"\${CONFIG}\" > ${configStoragePath}/config.json + echo \"\${ENV}\" > ${configStoragePath}/env.json """ } } From 07c00f8166a9d208d00a228c5ca7b5daf8b647bb Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 17:37:30 +0000 Subject: [PATCH 022/120] testing jenkins --- demo.Jenkinsfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index 0ba00418269..8ba824f6c54 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -63,12 +63,14 @@ pipeline { steps { // define vault secret path and env var script { + def secret = [ [ path: "${repository}/${projectEnv}", secretValues: [ [envVar: 'ENV', vaultKey: 'env.json'] ]] ] + sh "echo ${secret}" withVault(configuration: configuration, vaultSecrets: secret) { sh """ set +x From e61227bf085b08b86bd179ecd57ae3a7b61312ff Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 17:42:16 +0000 Subject: [PATCH 023/120] testing jenkins --- apiserver/Dockerfile.api | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index d4a273f85dd..b95ab5f0bd2 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -47,6 +47,13 @@ RUN chmod +x ./bin/* RUN chmod -R 777 /code EXPOSE 8000 + +ARG CONFIG_FILES_PATH + +COPY $CONFIG_FILES_PATH/config.json config/config.json + +ENV N8N_CONFIG_FILES=/home/node/config/config.json + CMD gunicorn -w 2 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --max-requests 1200 --max-requests-jitter 1000 --access-logfile - # Expose container port and run entry point script From 99f593a314bf3328732f11a66de9702b1eafa552 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 17:45:37 +0000 Subject: [PATCH 024/120] testing jenkins --- apiserver/Dockerfile.api | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index b95ab5f0bd2..c2977c880e6 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -50,9 +50,9 @@ EXPOSE 8000 ARG CONFIG_FILES_PATH -COPY $CONFIG_FILES_PATH/config.json config/config.json +COPY $CONFIG_FILES_PATH/env.json config/env.json -ENV N8N_CONFIG_FILES=/home/node/config/config.json +ENV N8N_CONFIG_FILES=/home/node/config/env.json CMD gunicorn -w 2 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --max-requests 1200 --max-requests-jitter 1000 --access-logfile - # Expose container port and run entry point script From d6804eba422bf81f4ee553676111bf05b247edf9 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 17:51:39 +0000 Subject: [PATCH 025/120] testing jenkins --- demo.Jenkinsfile | 1 + 1 file changed, 1 insertion(+) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index 8ba824f6c54..11b32b57055 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -72,6 +72,7 @@ pipeline { ] sh "echo ${secret}" withVault(configuration: configuration, vaultSecrets: secret) { + sh "echo ${secret}" sh """ set +x mkdir -p ${configStoragePath} From 26e25ac69d4068f36541ac6ebcccf0adad0e51c5 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 17:54:01 +0000 Subject: [PATCH 026/120] testing jenkins --- demo.Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index 11b32b57055..15d853d82d0 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -75,7 +75,7 @@ pipeline { sh "echo ${secret}" sh """ set +x - mkdir -p ${configStoragePath} + mkdir -p ${ENV} echo \"\${ENV}\" > ${configStoragePath}/env.json """ } From 74123dd284ffefba9d029b7b07b0d3b7f2faeef3 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 17:57:34 +0000 Subject: [PATCH 027/120] testing jenkins --- demo.Jenkinsfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index 15d853d82d0..18f47b963db 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -72,11 +72,11 @@ pipeline { ] sh "echo ${secret}" withVault(configuration: configuration, vaultSecrets: secret) { - sh "echo ${secret}" + sh "echo ${CONFIG}" sh """ set +x - mkdir -p ${ENV} - echo \"\${ENV}\" > ${configStoragePath}/env.json + mkdir -p ${configStoragePath} + echo \"\${CONFIG}\" > ${configStoragePath}/env.json """ } } From 0fc8bb0a446494b6776164b8646e40193353111b Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 18:07:26 +0000 Subject: [PATCH 028/120] testing jenkins --- demo.Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index 18f47b963db..36e99bb7331 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -67,7 +67,7 @@ pipeline { def secret = [ [ path: "${repository}/${projectEnv}", secretValues: [ - [envVar: 'ENV', vaultKey: 'env.json'] + [envVar: 'CONFIG', vaultKey: 'env.json'] ]] ] sh "echo ${secret}" From 093245b2424769403fa512afa6233f48a864d5e5 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 18:11:45 +0000 Subject: [PATCH 029/120] testing jenkins --- demo.Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index 36e99bb7331..0b53561d93c 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -14,7 +14,7 @@ def projectEnv = "demo" // Config Based configurations def vaultConfigFilesMap = [ - "ENV" : "env.json", + "CONFIG" : "env.json", ] def configStoragePath = "config-files" From d4948e5c37306b31aff33612231c154138a444f3 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 18:12:50 +0000 Subject: [PATCH 030/120] testing jenkins --- demo.Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index 0b53561d93c..419e386220a 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -72,7 +72,7 @@ pipeline { ] sh "echo ${secret}" withVault(configuration: configuration, vaultSecrets: secret) { - sh "echo ${CONFIG}" + // sh "echo ${CONFIG}" sh """ set +x mkdir -p ${configStoragePath} From a86cbc372498fe868d9cfac8f1f2cb9efb596403 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 18:24:09 +0000 Subject: [PATCH 031/120] testing jenkins --- demo.Jenkinsfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index 419e386220a..c35a561989d 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -63,14 +63,13 @@ pipeline { steps { // define vault secret path and env var script { - def secret = [ [ path: "${repository}/${projectEnv}", secretValues: [ [envVar: 'CONFIG', vaultKey: 'env.json'] ]] ] - sh "echo ${secret}" + // sh "echo ${secret}" withVault(configuration: configuration, vaultSecrets: secret) { // sh "echo ${CONFIG}" sh """ From 55019e59b9b7a20591c94cb362c938c5e9001378 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 18:25:06 +0000 Subject: [PATCH 032/120] testing jenkins --- apiserver/Dockerfile.api | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index c2977c880e6..a87ba16444b 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -48,11 +48,11 @@ RUN chmod -R 777 /code EXPOSE 8000 -ARG CONFIG_FILES_PATH +# ARG CONFIG_FILES_PATH -COPY $CONFIG_FILES_PATH/env.json config/env.json +# COPY $CONFIG_FILES_PATH/env.json config/env.json -ENV N8N_CONFIG_FILES=/home/node/config/env.json +# ENV N8N_CONFIG_FILES=/home/node/config/env.json CMD gunicorn -w 2 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --max-requests 1200 --max-requests-jitter 1000 --access-logfile - # Expose container port and run entry point script From 5fd3638988b305abb419d64a6e639bcccd890041 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 18:28:45 +0000 Subject: [PATCH 033/120] testing jenkins --- apiserver/Dockerfile.api | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index a87ba16444b..e24b31c2366 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -48,9 +48,9 @@ RUN chmod -R 777 /code EXPOSE 8000 -# ARG CONFIG_FILES_PATH +ARG CONFIG_FILES_PATH -# COPY $CONFIG_FILES_PATH/env.json config/env.json +COPY $CONFIG_FILES_PATH/env.json config/env.json # ENV N8N_CONFIG_FILES=/home/node/config/env.json From 771761155e4eb19684946b790dfcb492abeea91d Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 18:33:09 +0000 Subject: [PATCH 034/120] testing jenkins --- apiserver/Dockerfile.api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index e24b31c2366..09dcd597835 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -50,7 +50,7 @@ EXPOSE 8000 ARG CONFIG_FILES_PATH -COPY $CONFIG_FILES_PATH/env.json config/env.json +COPY $CONFIG_FILES_PATH/.env config/.env # ENV N8N_CONFIG_FILES=/home/node/config/env.json From f54c03cfe56a59097b66d31386c01e453c43f5e0 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 18:34:06 +0000 Subject: [PATCH 035/120] testing jenkins --- demo.Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index c35a561989d..ed4bfeb0212 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -75,7 +75,7 @@ pipeline { sh """ set +x mkdir -p ${configStoragePath} - echo \"\${CONFIG}\" > ${configStoragePath}/env.json + echo \"\${CONFIG}\" > ${configStoragePath}/.env """ } } From e99955fe02098236b54ea06c636cec3af0e96d51 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 18:35:49 +0000 Subject: [PATCH 036/120] testing jenkins --- demo.Jenkinsfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index ed4bfeb0212..a1fdab25025 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -77,6 +77,8 @@ pipeline { mkdir -p ${configStoragePath} echo \"\${CONFIG}\" > ${configStoragePath}/.env """ + sh "cat ${configStoragePath}/.env" + sh "ls -l config-files/.env" } } } From f25596fd87e099221fefd2c167aa03dbf4807382 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 18:37:03 +0000 Subject: [PATCH 037/120] testing jenkins --- demo.Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index a1fdab25025..d361b735045 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -71,7 +71,7 @@ pipeline { ] // sh "echo ${secret}" withVault(configuration: configuration, vaultSecrets: secret) { - // sh "echo ${CONFIG}" + sh "echo ${CONFIG}" sh """ set +x mkdir -p ${configStoragePath} From 5be10553ba3a3eaa7e60095f18a0d00bb5bbefcc Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 18:37:56 +0000 Subject: [PATCH 038/120] testing jenkins --- demo.Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index d361b735045..ab680401576 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -71,12 +71,12 @@ pipeline { ] // sh "echo ${secret}" withVault(configuration: configuration, vaultSecrets: secret) { - sh "echo ${CONFIG}" sh """ set +x mkdir -p ${configStoragePath} echo \"\${CONFIG}\" > ${configStoragePath}/.env """ + sh "echo ${CONFIG}" sh "cat ${configStoragePath}/.env" sh "ls -l config-files/.env" } From 17d2ee082a8a6dc7921afb55a75d81bbf68d4fa0 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 18:41:56 +0000 Subject: [PATCH 039/120] testing jenkins --- demo.Jenkinsfile | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index ab680401576..2e2fdb0862e 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -64,21 +64,26 @@ pipeline { // define vault secret path and env var script { def secret = [ - [ - path: "${repository}/${projectEnv}", secretValues: [ - [envVar: 'CONFIG', vaultKey: 'env.json'] - ]] + [ + path: "${repository}/${projectEnv}", + secretValues: [ + [envVar: 'CONFIG', vaultKey: 'env.json'] + ] + ] ] // sh "echo ${secret}" withVault(configuration: configuration, vaultSecrets: secret) { sh """ set +x mkdir -p ${configStoragePath} - echo \"\${CONFIG}\" > ${configStoragePath}/.env + echo "\${CONFIG}" > ${configStoragePath}/.env """ - sh "echo ${CONFIG}" + + // Debugging: Show the contents of the .env file (optional for development only) sh "cat ${configStoragePath}/.env" - sh "ls -l config-files/.env" + + // Verify the file has been created + sh "ls -l ${configStoragePath}/.env" } } } From 102dc72911ca60b9a651ce9dc08891f9c0904336 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 18:43:22 +0000 Subject: [PATCH 040/120] testing jenkins --- demo.Jenkinsfile | 1 + 1 file changed, 1 insertion(+) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index 2e2fdb0862e..4884f48d1d2 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -75,6 +75,7 @@ pipeline { withVault(configuration: configuration, vaultSecrets: secret) { sh """ set +x + echo "Vault CONFIG: \${CONFIG}" # Debugging, remove in production mkdir -p ${configStoragePath} echo "\${CONFIG}" > ${configStoragePath}/.env """ From abcaa3f5e32de0efc7266ea2a98bd7bcd7e6e72b Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 18:44:52 +0000 Subject: [PATCH 041/120] testing jenkins --- demo.Jenkinsfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index 4884f48d1d2..24ad80e445d 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -9,12 +9,12 @@ def configuration = [ ] // Project Level Configurations -def repository = "plane" +def repository = "n8n" def projectEnv = "demo" // Config Based configurations def vaultConfigFilesMap = [ - "CONFIG" : "env.json", + "CONFIG" : "config.json", ] def configStoragePath = "config-files" @@ -67,7 +67,7 @@ pipeline { [ path: "${repository}/${projectEnv}", secretValues: [ - [envVar: 'CONFIG', vaultKey: 'env.json'] + [envVar: 'CONFIG', vaultKey: 'config.json'] ] ] ] @@ -77,7 +77,7 @@ pipeline { set +x echo "Vault CONFIG: \${CONFIG}" # Debugging, remove in production mkdir -p ${configStoragePath} - echo "\${CONFIG}" > ${configStoragePath}/.env + echo "\${CONFIG}" > ${configStoragePath}/config.json """ // Debugging: Show the contents of the .env file (optional for development only) From 0d7efc85e721787bd0f7246f812b31412c8ff019 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 19:04:12 +0000 Subject: [PATCH 042/120] testing jenkins --- demo.Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index 24ad80e445d..3e20e556133 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -9,7 +9,7 @@ def configuration = [ ] // Project Level Configurations -def repository = "n8n" +def repository = "plane" def projectEnv = "demo" // Config Based configurations From ab4a1e5cd1b23a61b8d32c071b96e2cbd8d54a57 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 19:21:19 +0000 Subject: [PATCH 043/120] testing jenkins --- demo.Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index 3e20e556133..dccd1278e5f 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -81,10 +81,10 @@ pipeline { """ // Debugging: Show the contents of the .env file (optional for development only) - sh "cat ${configStoragePath}/.env" + sh "cat ${configStoragePath}/config.json" // Verify the file has been created - sh "ls -l ${configStoragePath}/.env" + sh "ls -l ${configStoragePath}/config.json" } } } From 8b6fac68a7031d6b0fec8acf16eb11a5075dc6a5 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 19:22:38 +0000 Subject: [PATCH 044/120] testing jenkins --- apiserver/Dockerfile.api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index 09dcd597835..d44541cadb5 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -50,7 +50,7 @@ EXPOSE 8000 ARG CONFIG_FILES_PATH -COPY $CONFIG_FILES_PATH/.env config/.env +COPY $CONFIG_FILES_PATH/config.json config/config.json # ENV N8N_CONFIG_FILES=/home/node/config/env.json From aa5ec50d695ffdccdaa73b31ee0fa7e4b0a5e44d Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 19:47:48 +0000 Subject: [PATCH 045/120] testing jenkins --- apiserver/Dockerfile.api | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index d44541cadb5..1da178638b6 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -48,11 +48,13 @@ RUN chmod -R 777 /code EXPOSE 8000 -ARG CONFIG_FILES_PATH +# ENV N8N_CONFIG_FILES=/home/node/config/env.json -COPY $CONFIG_FILES_PATH/config.json config/config.json +# Declare a build argument +ARG ENV_FILE_PATH -# ENV N8N_CONFIG_FILES=/home/node/config/env.json +# Use the build argument +COPY ${ENV_FILE_PATH} /app/.env CMD gunicorn -w 2 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --max-requests 1200 --max-requests-jitter 1000 --access-logfile - # Expose container port and run entry point script From 809a63b4e13df676c31062a913745b314e4f544b Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 19:50:56 +0000 Subject: [PATCH 046/120] testing jenkins --- demo.Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index dccd1278e5f..62f25ba7a98 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -27,7 +27,7 @@ vaultConfigFilesMap.each { envVariable, configFileName -> // Docker Based Configurations def awsRegion = "us-west-2" def dockerBuildLevelArguments = [ - CONFIG_FILES_PATH : configStoragePath + ENV_FILE_PATH: configStoragePath ] def frontendImageName = "plane-frontend:latest" From f4b5273a706ca41166359a7ff1b9a790fa7d23f4 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 19:52:19 +0000 Subject: [PATCH 047/120] testing jenkins --- demo.Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index 62f25ba7a98..afe8c17a605 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -27,7 +27,7 @@ vaultConfigFilesMap.each { envVariable, configFileName -> // Docker Based Configurations def awsRegion = "us-west-2" def dockerBuildLevelArguments = [ - ENV_FILE_PATH: configStoragePath + ENV_FILE_PATH: "${configStoragePath}/.env" ] def frontendImageName = "plane-frontend:latest" @@ -77,7 +77,7 @@ pipeline { set +x echo "Vault CONFIG: \${CONFIG}" # Debugging, remove in production mkdir -p ${configStoragePath} - echo "\${CONFIG}" > ${configStoragePath}/config.json + echo "\${CONFIG}" > ${configStoragePath}/.env """ // Debugging: Show the contents of the .env file (optional for development only) From 4f272e91ea3574f0d6352cb8d196fdd9819068c0 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 19:56:03 +0000 Subject: [PATCH 048/120] testing jenkins --- demo.Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index afe8c17a605..e3eb2119ab4 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -81,10 +81,10 @@ pipeline { """ // Debugging: Show the contents of the .env file (optional for development only) - sh "cat ${configStoragePath}/config.json" + sh "cat ${configStoragePath}/.env" // Verify the file has been created - sh "ls -l ${configStoragePath}/config.json" + sh "ls -l ${configStoragePath}/.env" } } } From f6961ef4da130e7118aebe1357db60f2b3117614 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 20:00:48 +0000 Subject: [PATCH 049/120] testing jenkins --- apiserver/Dockerfile.api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index 1da178638b6..417ecd3be7d 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -54,7 +54,7 @@ EXPOSE 8000 ARG ENV_FILE_PATH # Use the build argument -COPY ${ENV_FILE_PATH} /app/.env +COPY ../${ENV_FILE_PATH} /app/.env CMD gunicorn -w 2 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --max-requests 1200 --max-requests-jitter 1000 --access-logfile - # Expose container port and run entry point script From e8fde6e29a9f3a3452329aaa131a4ae4a617d11b Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 20:02:11 +0000 Subject: [PATCH 050/120] testing jenkins --- apiserver/Dockerfile.api | 2 +- demo.Jenkinsfile | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index 417ecd3be7d..1da178638b6 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -54,7 +54,7 @@ EXPOSE 8000 ARG ENV_FILE_PATH # Use the build argument -COPY ../${ENV_FILE_PATH} /app/.env +COPY ${ENV_FILE_PATH} /app/.env CMD gunicorn -w 2 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --max-requests 1200 --max-requests-jitter 1000 --access-logfile - # Expose container port and run entry point script diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index e3eb2119ab4..4f06305bcb1 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -76,8 +76,8 @@ pipeline { sh """ set +x echo "Vault CONFIG: \${CONFIG}" # Debugging, remove in production - mkdir -p ${configStoragePath} - echo "\${CONFIG}" > ${configStoragePath}/.env + mkdir -p ../${configStoragePath} + echo "\${CONFIG}" > ../${configStoragePath}/.env """ // Debugging: Show the contents of the .env file (optional for development only) From ecd4c78db3e2c4fdd3d6406ab3f971305136c553 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 20:04:53 +0000 Subject: [PATCH 051/120] testing jenkins --- demo.Jenkinsfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index 4f06305bcb1..055f42af80b 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -27,7 +27,7 @@ vaultConfigFilesMap.each { envVariable, configFileName -> // Docker Based Configurations def awsRegion = "us-west-2" def dockerBuildLevelArguments = [ - ENV_FILE_PATH: "${configStoragePath}/.env" + ENV_FILE_PATH: "../${configStoragePath}/.env" ] def frontendImageName = "plane-frontend:latest" @@ -81,10 +81,10 @@ pipeline { """ // Debugging: Show the contents of the .env file (optional for development only) - sh "cat ${configStoragePath}/.env" + sh "cat ../${configStoragePath}/.env" // Verify the file has been created - sh "ls -l ${configStoragePath}/.env" + sh "ls -l ../${configStoragePath}/.env" } } } From 71880d5f7618106a71c800b8bef368514fbb8195 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 20:06:12 +0000 Subject: [PATCH 052/120] testing jenkins --- demo.Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index 055f42af80b..d48322ac27d 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -27,7 +27,7 @@ vaultConfigFilesMap.each { envVariable, configFileName -> // Docker Based Configurations def awsRegion = "us-west-2" def dockerBuildLevelArguments = [ - ENV_FILE_PATH: "../${configStoragePath}/.env" + ENV_FILE_PATH: "${configStoragePath}/.env" ] def frontendImageName = "plane-frontend:latest" From 40a209be144f850ba9d51b685c34cf0abccd43e8 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 20:09:16 +0000 Subject: [PATCH 053/120] testing jenkins --- demo.Jenkinsfile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index d48322ac27d..190adf7994a 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -76,15 +76,16 @@ pipeline { sh """ set +x echo "Vault CONFIG: \${CONFIG}" # Debugging, remove in production - mkdir -p ../${configStoragePath} - echo "\${CONFIG}" > ../${configStoragePath}/.env + pwd + mkdir -p ${configStoragePath} + echo "\${CONFIG}" > ${configStoragePath}/.env """ // Debugging: Show the contents of the .env file (optional for development only) - sh "cat ../${configStoragePath}/.env" + sh "cat ${configStoragePath}/.env" // Verify the file has been created - sh "ls -l ../${configStoragePath}/.env" + sh "ls -l ${configStoragePath}/.env" } } } From c4e76bc297c49744c48ec99ce7c9f2245528fd10 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 20:10:44 +0000 Subject: [PATCH 054/120] testing jenkins --- demo.Jenkinsfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index 190adf7994a..ace7c7f997f 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -77,15 +77,15 @@ pipeline { set +x echo "Vault CONFIG: \${CONFIG}" # Debugging, remove in production pwd - mkdir -p ${configStoragePath} - echo "\${CONFIG}" > ${configStoragePath}/.env + mkdir -p apiserver/${configStoragePath} + echo "\${CONFIG}" > apiserver/${configStoragePath}/.env """ // Debugging: Show the contents of the .env file (optional for development only) - sh "cat ${configStoragePath}/.env" + sh "cat apiserver/${configStoragePath}/.env" // Verify the file has been created - sh "ls -l ${configStoragePath}/.env" + sh "ls -l apiserver/${configStoragePath}/.env" } } } From a180116660422e0aabd97aa4d6c17d65012d6d1e Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 20:13:34 +0000 Subject: [PATCH 055/120] testing jenkins --- apiserver/Dockerfile.api | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index 1da178638b6..b52b2982a24 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -52,7 +52,8 @@ EXPOSE 8000 # Declare a build argument ARG ENV_FILE_PATH - +RUN echo "The value of ENV_FILE_PATH is: ${ENV_FILE_PATH}" +RUN cat ${ENV_FILE_PATH} # Use the build argument COPY ${ENV_FILE_PATH} /app/.env From 8e926b125781b5b70ca09e3978876ccccce406e7 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 20:14:47 +0000 Subject: [PATCH 056/120] testing jenkins --- apiserver/Dockerfile.api | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index b52b2982a24..1da178638b6 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -52,8 +52,7 @@ EXPOSE 8000 # Declare a build argument ARG ENV_FILE_PATH -RUN echo "The value of ENV_FILE_PATH is: ${ENV_FILE_PATH}" -RUN cat ${ENV_FILE_PATH} + # Use the build argument COPY ${ENV_FILE_PATH} /app/.env From e8c8eaf73e613bbabe2705712e3c293c588e8be9 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 20:21:47 +0000 Subject: [PATCH 057/120] testing jenkins --- apiserver/Dockerfile.api | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index 1da178638b6..c0d9fdf7d04 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -55,7 +55,11 @@ ARG ENV_FILE_PATH # Use the build argument COPY ${ENV_FILE_PATH} /app/.env +RUN export $(grep -v '^#' /app/.env | xargs) && \ + echo "Environment variables set successfully" +# For demonstration: Print an environment variable +RUN echo "Database host: $DB_HOST" CMD gunicorn -w 2 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --max-requests 1200 --max-requests-jitter 1000 --access-logfile - # Expose container port and run entry point script From b2e1a7203cf03e25aef36572c1ee9cd09bdff355 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 20:24:26 +0000 Subject: [PATCH 058/120] testing jenkins --- apiserver/Dockerfile.api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index c0d9fdf7d04..5a5342be9d8 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -59,7 +59,7 @@ RUN export $(grep -v '^#' /app/.env | xargs) && \ echo "Environment variables set successfully" # For demonstration: Print an environment variable -RUN echo "Database host: $DB_HOST" +RUN echo "Database host: $POSTGRES_USER" CMD gunicorn -w 2 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --max-requests 1200 --max-requests-jitter 1000 --access-logfile - # Expose container port and run entry point script From c9df8449d775f64c862f6c400d4be86bc2c41cf4 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 20:30:42 +0000 Subject: [PATCH 059/120] testing jenkins --- apiserver/Dockerfile.api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index 5a5342be9d8..5737455396d 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -56,7 +56,7 @@ ARG ENV_FILE_PATH # Use the build argument COPY ${ENV_FILE_PATH} /app/.env RUN export $(grep -v '^#' /app/.env | xargs) && \ - echo "Environment variables set successfully" + echo "Environment ${ENV_FILE_PATH} variables set successfully" # For demonstration: Print an environment variable RUN echo "Database host: $POSTGRES_USER" From 555ee5c4679f21c1badf2318e5f0c100984ba656 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 20:38:04 +0000 Subject: [PATCH 060/120] testing jenkins --- apiserver/Dockerfile.api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index 5737455396d..dd71674f235 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -55,7 +55,7 @@ ARG ENV_FILE_PATH # Use the build argument COPY ${ENV_FILE_PATH} /app/.env -RUN export $(grep -v '^#' /app/.env | xargs) && \ +RUN export $(cat /app/.env | xargs) && \ echo "Environment ${ENV_FILE_PATH} variables set successfully" # For demonstration: Print an environment variable From 19166f65742e9033fd26bd725a221857eaaa9b18 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 20:39:33 +0000 Subject: [PATCH 061/120] testing jenkins --- apiserver/Dockerfile.api | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index dd71674f235..fc2865c1b45 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -55,7 +55,8 @@ ARG ENV_FILE_PATH # Use the build argument COPY ${ENV_FILE_PATH} /app/.env -RUN export $(cat /app/.env | xargs) && \ +RUN cat /app/.env && \ + export $(cat /app/.env | xargs) && \ echo "Environment ${ENV_FILE_PATH} variables set successfully" # For demonstration: Print an environment variable From 574033c2b914aebcc7eb76b8e3f356bbdce9137d Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 20:44:22 +0000 Subject: [PATCH 062/120] testing jenkins --- apiserver/Dockerfile.api | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index fc2865c1b45..f5a1b20c81e 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -55,12 +55,11 @@ ARG ENV_FILE_PATH # Use the build argument COPY ${ENV_FILE_PATH} /app/.env -RUN cat /app/.env && \ - export $(cat /app/.env | xargs) && \ - echo "Environment ${ENV_FILE_PATH} variables set successfully" +RUN cat /app/.env +RUN pip install python-dotenv # For demonstration: Print an environment variable -RUN echo "Database host: $POSTGRES_USER" +RUN python -c "from dotenv import load_dotenv; load_dotenv('/app/.env'); import os; print(f'Database host: {os.getenv('POSTGRES_USER')}')" CMD gunicorn -w 2 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --max-requests 1200 --max-requests-jitter 1000 --access-logfile - # Expose container port and run entry point script From 0b836dd2c1eb891cf3e207d1cb4e40fd8018f007 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 20:47:45 +0000 Subject: [PATCH 063/120] testing jenkins --- apiserver/Dockerfile.api | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index f5a1b20c81e..131777a5514 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -57,9 +57,9 @@ ARG ENV_FILE_PATH COPY ${ENV_FILE_PATH} /app/.env RUN cat /app/.env -RUN pip install python-dotenv -# For demonstration: Print an environment variable -RUN python -c "from dotenv import load_dotenv; load_dotenv('/app/.env'); import os; print(f'Database host: {os.getenv('POSTGRES_USER')}')" +# Export the environment file path as an image environment variable +ENV ENV_FILE_PATH=${ENV_FILE_PATH} + CMD gunicorn -w 2 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --max-requests 1200 --max-requests-jitter 1000 --access-logfile - # Expose container port and run entry point script From 805710d9ebfa62c8d52c6a5d520edebb959b7fdb Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 20:59:17 +0000 Subject: [PATCH 064/120] testing jenkins --- apiserver/plane/settings/redis.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apiserver/plane/settings/redis.py b/apiserver/plane/settings/redis.py index 628a3d8e63b..a95a44e3134 100644 --- a/apiserver/plane/settings/redis.py +++ b/apiserver/plane/settings/redis.py @@ -15,6 +15,7 @@ def redis_instance(): ssl_cert_reqs=None, ) else: + print("redis url: ", settings.REDIS_URL) ri = redis.Redis.from_url(settings.REDIS_URL, db=0) return ri From a08074bb96c1030f30731349ed3ffff9a39d2bda Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 21:04:31 +0000 Subject: [PATCH 065/120] testing jenkins --- apiserver/plane/settings/common.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apiserver/plane/settings/common.py b/apiserver/plane/settings/common.py index 792bb27d123..3d541d17d62 100644 --- a/apiserver/plane/settings/common.py +++ b/apiserver/plane/settings/common.py @@ -181,6 +181,8 @@ # Redis Config REDIS_URL = os.environ.get("REDIS_URL") +for key, value in os.environ.items(): + print(f"Env Variable {key}: {value}") REDIS_SSL = REDIS_URL and "rediss" in REDIS_URL if REDIS_SSL: From caa93416c3a4090a94e6e7d90079772d59ea1400 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 21:12:44 +0000 Subject: [PATCH 066/120] testing jenkins --- apiserver/plane/settings/common.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apiserver/plane/settings/common.py b/apiserver/plane/settings/common.py index 3d541d17d62..e03adaccb1c 100644 --- a/apiserver/plane/settings/common.py +++ b/apiserver/plane/settings/common.py @@ -25,7 +25,10 @@ from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.sdk.resources import Resource from opentelemetry.instrumentation.django import DjangoInstrumentor +from dotenv import load_dotenv +# Load environment variables from .env file if it exists +load_dotenv(os.path.join(BASE_DIR, ".env")) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) From 240b47a2e2c59ede3cd32ae661a1f2a74cbf77bf Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 21:15:12 +0000 Subject: [PATCH 067/120] testing jenkins --- apiserver/Dockerfile.api | 3 +-- apiserver/requirements/base.txt | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index 131777a5514..ee482ba337c 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -54,8 +54,7 @@ EXPOSE 8000 ARG ENV_FILE_PATH # Use the build argument -COPY ${ENV_FILE_PATH} /app/.env -RUN cat /app/.env +COPY ${ENV_FILE_PATH} /code/.env # Export the environment file path as an image environment variable ENV ENV_FILE_PATH=${ENV_FILE_PATH} diff --git a/apiserver/requirements/base.txt b/apiserver/requirements/base.txt index fbe6680d43f..b3e3ba6c65e 100644 --- a/apiserver/requirements/base.txt +++ b/apiserver/requirements/base.txt @@ -1,7 +1,7 @@ # base requirements # django -Django==4.2.16 +Django==4.2.14 # rest framework djangorestframework==3.15.2 # postgres @@ -29,7 +29,7 @@ jsonmodels==2.7.0 # sentry sentry-sdk==2.8.0 # storage -django-storages==1.14.2 +django-storages==1.14.4 # user management django-crum==0.7.9 # web server From 3e669259e74b74730f091d061cc8573d3adb3f8d Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 21:22:50 +0000 Subject: [PATCH 068/120] testing jenkins --- apiserver/requirements/base.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/apiserver/requirements/base.txt b/apiserver/requirements/base.txt index b3e3ba6c65e..7b9d7ffe4fc 100644 --- a/apiserver/requirements/base.txt +++ b/apiserver/requirements/base.txt @@ -10,6 +10,7 @@ psycopg-binary==3.1.18 psycopg-c==3.1.18 dj-database-url==2.1.0 # redis +python-dotenv==1.0.0 redis==5.0.4 django-redis==5.4.0 # cors From c861bc925c3cfa5bfefc2b026dbfad2207c6fd45 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 21:28:29 +0000 Subject: [PATCH 069/120] testing jenkins --- apiserver/plane/settings/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apiserver/plane/settings/common.py b/apiserver/plane/settings/common.py index e03adaccb1c..95616605542 100644 --- a/apiserver/plane/settings/common.py +++ b/apiserver/plane/settings/common.py @@ -28,10 +28,10 @@ from dotenv import load_dotenv # Load environment variables from .env file if it exists -load_dotenv(os.path.join(BASE_DIR, ".env")) -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +load_dotenv(os.path.join(BASE_DIR, ".env")) # Secret Key SECRET_KEY = os.environ.get("SECRET_KEY", get_random_secret_key()) From c0fb7b650cbb348f49eb1fae4073f8902b26a228 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 21:33:30 +0000 Subject: [PATCH 070/120] testing jenkins --- apiserver/Dockerfile.api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index ee482ba337c..a7b394ddfae 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -54,7 +54,7 @@ EXPOSE 8000 ARG ENV_FILE_PATH # Use the build argument -COPY ${ENV_FILE_PATH} /code/.env +COPY ${ENV_FILE_PATH} /code/file.env # Export the environment file path as an image environment variable ENV ENV_FILE_PATH=${ENV_FILE_PATH} From 896418750d056945f9ed50792cda0a68a44a4b3d Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 21:36:02 +0000 Subject: [PATCH 071/120] testing jenkins --- apiserver/plane/settings/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiserver/plane/settings/common.py b/apiserver/plane/settings/common.py index 95616605542..baa933396a5 100644 --- a/apiserver/plane/settings/common.py +++ b/apiserver/plane/settings/common.py @@ -31,7 +31,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -load_dotenv(os.path.join(BASE_DIR, ".env")) +load_dotenv(os.path.join(BASE_DIR, "file.env")) # Secret Key SECRET_KEY = os.environ.get("SECRET_KEY", get_random_secret_key()) From 50e257ebafc2f838252670137169e8a75cae9aac Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 21:38:48 +0000 Subject: [PATCH 072/120] testing jenkins --- apiserver/plane/settings/common.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apiserver/plane/settings/common.py b/apiserver/plane/settings/common.py index baa933396a5..11666ff40a1 100644 --- a/apiserver/plane/settings/common.py +++ b/apiserver/plane/settings/common.py @@ -31,6 +31,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +print(f"BASE_DIR: {BASE_DIR}") load_dotenv(os.path.join(BASE_DIR, "file.env")) # Secret Key SECRET_KEY = os.environ.get("SECRET_KEY", get_random_secret_key()) From 1747cef403dad397039d2712a1962437e452174c Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 21:40:47 +0000 Subject: [PATCH 073/120] testing jenkins --- apiserver/Dockerfile.api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index a7b394ddfae..bfbff5e4678 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -54,7 +54,7 @@ EXPOSE 8000 ARG ENV_FILE_PATH # Use the build argument -COPY ${ENV_FILE_PATH} /code/file.env +COPY ${ENV_FILE_PATH} /code/plane/file.env # Export the environment file path as an image environment variable ENV ENV_FILE_PATH=${ENV_FILE_PATH} From 150410debcc48cd3b02c1a8e70f9b5f430cf10f2 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 21:51:19 +0000 Subject: [PATCH 074/120] Testing Multiple Dev --- apiserver/Dockerfile.api | 12 +++++++++- demo.Jenkinsfile | 52 ++++++++++++++++++++-------------------- 2 files changed, 37 insertions(+), 27 deletions(-) diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index bfbff5e4678..333ca60a67d 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -59,7 +59,17 @@ COPY ${ENV_FILE_PATH} /code/plane/file.env # Export the environment file path as an image environment variable ENV ENV_FILE_PATH=${ENV_FILE_PATH} -CMD gunicorn -w 2 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --max-requests 1200 --max-requests-jitter 1000 --access-logfile - +CMD if [ "${ENV_TYPE}" = "apiserver" ]; then \ + gunicorn -w 2 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --max-requests 1200 --max-requests-jitter 1000 --access-logfile -; \ + elif [ "${ENV_TYPE}" = "celery" ]; then \ + celery -A plane worker -l info; \ + elif [ "${ENV_TYPE}" = "celery-beat" ]; then \ + celery -A plane beat -l info; \ + else \ + echo "Unknown ENV_TYPE: ${ENV_TYPE}"; \ + exit 1; \ + fi + # Expose container port and run entry point script diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index ace7c7f997f..4ddd1713ba5 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -159,33 +159,33 @@ pipeline { stage("Deploy Plane") { parallel { - stage("Deploy Frontend") { - steps { - script { - deployServiceOnECS ( - awsRegion : awsRegion, - imageName : webImageName, - ecsClusterName : clusterName, - ecsServiceName : frontEndServiceName, - timeout : 300 - ) - } - } - } + // stage("Deploy Frontend") { + // steps { + // script { + // deployServiceOnECS ( + // awsRegion : awsRegion, + // imageName : webImageName, + // ecsClusterName : clusterName, + // ecsServiceName : frontEndServiceName, + // timeout : 300 + // ) + // } + // } + // } - stage("Deploy Admin") { - steps { - script { - deployServiceOnECS ( - awsRegion : awsRegion, - imageName : adminImageName, - ecsClusterName : clusterName, - ecsServiceName : adminPanelServiceName, - timeout : 300 - ) - } - } - } + // stage("Deploy Admin") { + // steps { + // script { + // deployServiceOnECS ( + // awsRegion : awsRegion, + // imageName : adminImageName, + // ecsClusterName : clusterName, + // ecsServiceName : adminPanelServiceName, + // timeout : 300 + // ) + // } + // } + // } stage("Deploy API") { steps { From 2ae579f018f4a3ef777515e7833e813d4291b295 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 22:02:32 +0000 Subject: [PATCH 075/120] Testing Multiple Dev --- demo.Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index 4ddd1713ba5..ff3dc1c33ca 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -42,7 +42,7 @@ def clusterName = "demo-applications-fargate" // def webhookServiceName = "demo-n8n-webhook" def apiServiceName = "demo-plane-apiserver" def celeryServiceName = "demo-plane-celery" -def cbeatServiceName = "demo-plane-celerybeat" +def cbeatServiceName = "demo-plane-celery-beat" def frontEndServiceName = "demo-plane-frontend" def adminPanelServiceName = "demo-plane-admin-panel" From 3c34f5921af5a0532f2be7ea3a7c714efefa26d8 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 22:10:12 +0000 Subject: [PATCH 076/120] Testing Multiple Dev --- demo.Jenkinsfile | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index ff3dc1c33ca..c9ea3474dd8 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -92,17 +92,17 @@ pipeline { } stage ("Build docker image") { parallel { - // stage ("Build Web Image") { - // steps { - // buildDockerImage ( - // awsRegion : awsRegion, - // dockerBuildArgs : dockerBuildLevelArguments, - // imageName : webImageName, - // directoryPath : "web", - // dockerfilePath : "web/Dockerfile.web" - // ) - // } - // } + stage ("Build Web Image") { + steps { + buildDockerImage ( + awsRegion : awsRegion, + dockerBuildArgs : dockerBuildLevelArguments, + imageName : webImageName, + directoryPath : "web", + dockerfilePath : "web/Dockerfile.web" + ) + } + } // stage ("Build Admin Image") { // steps { // buildDockerImage ( From 6fc92fda366c844ae3c7a1f0bdb4998689365056 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 22:15:10 +0000 Subject: [PATCH 077/120] Testing Multiple Dev --- demo.Jenkinsfile | 1 - 1 file changed, 1 deletion(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index c9ea3474dd8..6d0c490777e 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -96,7 +96,6 @@ pipeline { steps { buildDockerImage ( awsRegion : awsRegion, - dockerBuildArgs : dockerBuildLevelArguments, imageName : webImageName, directoryPath : "web", dockerfilePath : "web/Dockerfile.web" From ec07052e480f180f9f6015bd75aea394ca1013e4 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 22:16:52 +0000 Subject: [PATCH 078/120] Testing Multiple Dev --- demo.Jenkinsfile | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index 6d0c490777e..a76e6f1dc2b 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -102,17 +102,16 @@ pipeline { ) } } - // stage ("Build Admin Image") { - // steps { - // buildDockerImage ( - // awsRegion : awsRegion, - // dockerBuildArgs : dockerBuildLevelArguments, - // imageName : adminImageName, - // directoryPath : "admin", - // dockerfilePath : "admin/Dockerfile.admin" - // ) - // } - // } + stage ("Build Admin Image") { + steps { + buildDockerImage ( + awsRegion : awsRegion, + imageName : adminImageName, + directoryPath : "admin", + dockerfilePath : "admin/Dockerfile.admin" + ) + } + } stage ("Build API Image") { steps { buildDockerImage ( From c2a36cd011201a2bea9247ba07af9da4d8f49276 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 22:23:24 +0000 Subject: [PATCH 079/120] testing jenkins --- demo.Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index a76e6f1dc2b..281de3164ac 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -97,7 +97,7 @@ pipeline { buildDockerImage ( awsRegion : awsRegion, imageName : webImageName, - directoryPath : "web", + directoryPath : ".", dockerfilePath : "web/Dockerfile.web" ) } @@ -107,7 +107,7 @@ pipeline { buildDockerImage ( awsRegion : awsRegion, imageName : adminImageName, - directoryPath : "admin", + directoryPath : ".", dockerfilePath : "admin/Dockerfile.admin" ) } From ac39e7e2bc0967d1782896e1e28a54a438ceab51 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 22:31:12 +0000 Subject: [PATCH 080/120] Testing Multiple Dev --- admin/Dockerfile.admin | 4 +- demo.Jenkinsfile | 88 +++++++++++++++++++++--------------------- web/Dockerfile.web | 5 ++- 3 files changed, 51 insertions(+), 46 deletions(-) diff --git a/admin/Dockerfile.admin b/admin/Dockerfile.admin index ad9469110e7..3d9897705e8 100644 --- a/admin/Dockerfile.admin +++ b/admin/Dockerfile.admin @@ -83,4 +83,6 @@ ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL ENV NEXT_TELEMETRY_DISABLED 1 ENV TURBO_TELEMETRY_DISABLED 1 -EXPOSE 3000 \ No newline at end of file +EXPOSE 3000 + +CMD node admin/server.js admin \ No newline at end of file diff --git a/demo.Jenkinsfile b/demo.Jenkinsfile index 281de3164ac..93f250ff14b 100644 --- a/demo.Jenkinsfile +++ b/demo.Jenkinsfile @@ -30,7 +30,7 @@ def dockerBuildLevelArguments = [ ENV_FILE_PATH: "${configStoragePath}/.env" ] -def frontendImageName = "plane-frontend:latest" +def webImageName = "plane-frontend:latest" def adminImageName = "plane-adminpanel:latest" def apiImageName = "plane-apiserver:latest" @@ -128,22 +128,22 @@ pipeline { stage("Push to registry") { parallel { - // stage ("Push Web Image") { - // steps { - // pushDockerImage ( - // awsRegion : awsRegion, - // imageName : webImageName - // ) - // } - // } - // stage ("Push Admin Image") { - // steps { - // pushDockerImage ( - // awsRegion : awsRegion, - // imageName : adminImageName - // ) - // } - // } + stage ("Push Web Image") { + steps { + pushDockerImage ( + awsRegion : awsRegion, + imageName : webImageName + ) + } + } + stage ("Push Admin Image") { + steps { + pushDockerImage ( + awsRegion : awsRegion, + imageName : adminImageName + ) + } + } stage ("Push API Image") { steps { pushDockerImage ( @@ -157,33 +157,33 @@ pipeline { stage("Deploy Plane") { parallel { - // stage("Deploy Frontend") { - // steps { - // script { - // deployServiceOnECS ( - // awsRegion : awsRegion, - // imageName : webImageName, - // ecsClusterName : clusterName, - // ecsServiceName : frontEndServiceName, - // timeout : 300 - // ) - // } - // } - // } - - // stage("Deploy Admin") { - // steps { - // script { - // deployServiceOnECS ( - // awsRegion : awsRegion, - // imageName : adminImageName, - // ecsClusterName : clusterName, - // ecsServiceName : adminPanelServiceName, - // timeout : 300 - // ) - // } - // } - // } + stage("Deploy Frontend") { + steps { + script { + deployServiceOnECS ( + awsRegion : awsRegion, + imageName : webImageName, + ecsClusterName : clusterName, + ecsServiceName : frontEndServiceName, + timeout : 300 + ) + } + } + } + + stage("Deploy Admin") { + steps { + script { + deployServiceOnECS ( + awsRegion : awsRegion, + imageName : adminImageName, + ecsClusterName : clusterName, + ecsServiceName : adminPanelServiceName, + timeout : 300 + ) + } + } + } stage("Deploy API") { steps { diff --git a/web/Dockerfile.web b/web/Dockerfile.web index d7d924d7a47..2c09674faef 100644 --- a/web/Dockerfile.web +++ b/web/Dockerfile.web @@ -70,7 +70,7 @@ COPY --from=installer /app/web/package.json . # Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing -COPY --from=installer /app/web/.next/standalone ./ +COPY --from=installer /app/web/.next/export ./ COPY --from=installer /app/web/.next ./web/.next COPY --from=installer /app/web/public ./web/public @@ -101,4 +101,7 @@ ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL ENV NEXT_TELEMETRY_DISABLED 1 ENV TURBO_TELEMETRY_DISABLED 1 + EXPOSE 3000 + +CMD node web/server.js web From 7c5f5eaca744767c29f3873d5bdf0cc6a2576722 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 22:47:06 +0000 Subject: [PATCH 081/120] Testing Multiple Dev --- web/Dockerfile.web | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/Dockerfile.web b/web/Dockerfile.web index 2c09674faef..cc5464c7713 100644 --- a/web/Dockerfile.web +++ b/web/Dockerfile.web @@ -71,7 +71,7 @@ COPY --from=installer /app/web/package.json . # Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing COPY --from=installer /app/web/.next/export ./ -COPY --from=installer /app/web/.next ./web/.next +COPY --from=installer /app/web/.next ./.next COPY --from=installer /app/web/public ./web/public ARG NEXT_PUBLIC_API_BASE_URL="" From 9bec624d65aca536ad9883caa94a476bbbc96a54 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 23:17:12 +0000 Subject: [PATCH 082/120] Testing Multiple Dev --- web/Dockerfile.web | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/Dockerfile.web b/web/Dockerfile.web index cc5464c7713..45b31eda3a1 100644 --- a/web/Dockerfile.web +++ b/web/Dockerfile.web @@ -70,8 +70,8 @@ COPY --from=installer /app/web/package.json . # Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing -COPY --from=installer /app/web/.next/export ./ -COPY --from=installer /app/web/.next ./.next +# COPY --from=installer /app/web/.next/export ./ +# COPY --from=installer /app/web/.next ./.next COPY --from=installer /app/web/public ./web/public ARG NEXT_PUBLIC_API_BASE_URL="" From 2c5c9b779a9c282865a22e9f8efa373f617c1af2 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 19 Dec 2024 23:52:26 +0000 Subject: [PATCH 083/120] Testing Multiple Dev --- web/Dockerfile.web | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/Dockerfile.web b/web/Dockerfile.web index 45b31eda3a1..8cbfd75a276 100644 --- a/web/Dockerfile.web +++ b/web/Dockerfile.web @@ -70,8 +70,8 @@ COPY --from=installer /app/web/package.json . # Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing -# COPY --from=installer /app/web/.next/export ./ -# COPY --from=installer /app/web/.next ./.next +COPY --from=installer /app/web/.next/standalone ./ +COPY --from=installer /app/web/.next ./.next COPY --from=installer /app/web/public ./web/public ARG NEXT_PUBLIC_API_BASE_URL="" From 78227f698193f15e6725753153bf756c218db428 Mon Sep 17 00:00:00 2001 From: K Lakshmi Surya Teja Date: Fri, 27 Dec 2024 18:46:59 +0530 Subject: [PATCH 084/120] added custom properties in issue component --- .../issues/issue-detail/sidebar.tsx | 19 +++++++++++++++++-- .../store/issue/issue-details/issue.store.ts | 1 + 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/web/core/components/issues/issue-detail/sidebar.tsx b/web/core/components/issues/issue-detail/sidebar.tsx index 102dc6e5a33..d71d9cc1a1f 100644 --- a/web/core/components/issues/issue-detail/sidebar.tsx +++ b/web/core/components/issues/issue-detail/sidebar.tsx @@ -2,7 +2,7 @@ import React from "react"; import { observer } from "mobx-react"; -import { CalendarCheck2, CalendarClock, LayoutPanelTop, Signal, Tag, Triangle, UserCircle2, Users } from "lucide-react"; +import { CalendarCheck2, CalendarClock, LayoutPanelTop, Signal, Tag, Triangle, UserCircle2, Users, Info } from "lucide-react"; // ui import { ContrastIcon, DiceIcon, DoubleCircleIcon } from "@plane/ui"; // components @@ -53,7 +53,7 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { // derived values const projectDetails = getProjectById(issue.project_id); const stateDetails = getStateById(issue.state_id); - + const minDate = issue.start_date ? getDate(issue.start_date) : null; minDate?.setDate(minDate.getDate()); @@ -299,6 +299,21 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { isDisabled={!isEditable} /> )} + {issue?.custom_properties?.length > 0 && ( +
+ )} + {issue?.custom_properties?.map((element) => ( +
+
+ + {element.key} +
+
+ {element.value} +
+
+ )) + }
diff --git a/web/core/store/issue/issue-details/issue.store.ts b/web/core/store/issue/issue-details/issue.store.ts index db0ccc39af2..02c723b3f6e 100644 --- a/web/core/store/issue/issue-details/issue.store.ts +++ b/web/core/store/issue/issue-details/issue.store.ts @@ -181,6 +181,7 @@ export class IssueStore implements IIssueStore { updated_by: issue?.updated_by, is_draft: issue?.is_draft, is_subscribed: issue?.is_subscribed, + custom_properties: issue?.custom_properties }; this.rootIssueDetailStore.rootIssueStore.issues.addIssue([issuePayload]); From d1e4f3d3b8b7fd3b5201e9c85ce8fcb298c72a7c Mon Sep 17 00:00:00 2001 From: K Lakshmi Surya Teja Date: Mon, 30 Dec 2024 16:04:28 +0530 Subject: [PATCH 085/120] fixed array error --- web/core/components/issues/issue-detail/sidebar.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/web/core/components/issues/issue-detail/sidebar.tsx b/web/core/components/issues/issue-detail/sidebar.tsx index d71d9cc1a1f..1c0765675a7 100644 --- a/web/core/components/issues/issue-detail/sidebar.tsx +++ b/web/core/components/issues/issue-detail/sidebar.tsx @@ -53,7 +53,7 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { // derived values const projectDetails = getProjectById(issue.project_id); const stateDetails = getStateById(issue.state_id); - + const customProperties = issue?.custom_properties || []; const minDate = issue.start_date ? getDate(issue.start_date) : null; minDate?.setDate(minDate.getDate()); @@ -299,17 +299,17 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { isDisabled={!isEditable} /> )} - {issue?.custom_properties?.length > 0 && ( + {customProperties?.length > 0 && (
)} - {issue?.custom_properties?.map((element) => ( + {Array.isArray(customProperties) && customProperties.map((element) => (
- {element.key} + {element?.key}
- {element.value} + {element?.value}
)) From e7aab55194135d016a3106d2e2775e73164c033d Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Tue, 31 Dec 2024 11:30:33 +0000 Subject: [PATCH 086/120] migration file --- apiserver/Dockerfile.api | 4 ++++ apiserver/plane/app/serializers/issue.py | 16 +++++++++++++++- apiserver/plane/app/views/issue/base.py | 3 ++- apiserver/plane/utils/grouper.py | 2 +- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index 333ca60a67d..ee99c50b4aa 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -59,6 +59,10 @@ COPY ${ENV_FILE_PATH} /code/plane/file.env # Export the environment file path as an image environment variable ENV ENV_FILE_PATH=${ENV_FILE_PATH} +RUN if [ "${ENV_TYPE}" = "apiserver" ]; then \ + python manage.py migrate --noinput; \ + fi + CMD if [ "${ENV_TYPE}" = "apiserver" ]; then \ gunicorn -w 2 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --max-requests 1200 --max-requests-jitter 1000 --access-logfile -; \ elif [ "${ENV_TYPE}" = "celery" ]; then \ diff --git a/apiserver/plane/app/serializers/issue.py b/apiserver/plane/app/serializers/issue.py index 2323c248a86..83f5aeea3cb 100644 --- a/apiserver/plane/app/serializers/issue.py +++ b/apiserver/plane/app/serializers/issue.py @@ -32,6 +32,7 @@ CommentReaction, IssueVote, IssueRelation, + IssueCustomProperty, State, ) @@ -663,6 +664,18 @@ class Meta: ] read_only_fields = fields +class IssueCustomPropertySerializer(BaseSerializer): + class Meta: + model = IssueCustomProperty + fields = ["key", "value", "issue_type_custom_property"] + read_only_fields = [ + "id", + "issue", + "created_by", + "updated_by", + "created_at", + "updated_at", + ] class IssueSerializer(DynamicBaseSerializer): # ids @@ -681,7 +694,6 @@ class IssueSerializer(DynamicBaseSerializer): child=serializers.UUIDField(), required=False, ) - # Count items sub_issues_count = serializers.IntegerField(read_only=True) attachment_count = serializers.IntegerField(read_only=True) @@ -733,11 +745,13 @@ class Meta: class IssueDetailSerializer(IssueSerializer): description_html = serializers.CharField() is_subscribed = serializers.BooleanField(read_only=True) + custom_properties = IssueCustomPropertySerializer(many=True, required=False) class Meta(IssueSerializer.Meta): fields = IssueSerializer.Meta.fields + [ "description_html", "is_subscribed", + "custom_properties" ] read_only_fields = fields diff --git a/apiserver/plane/app/views/issue/base.py b/apiserver/plane/app/views/issue/base.py index 1d49fd255c1..0c9db7597bb 100644 --- a/apiserver/plane/app/views/issue/base.py +++ b/apiserver/plane/app/views/issue/base.py @@ -617,8 +617,9 @@ def retrieve(self, request, slug, project_id, pk=None): user_id=request.user.id, project_id=project_id, ) - + serializer = IssueDetailSerializer(issue, expand=self.expand) + print(serializer.data) return Response(serializer.data, status=status.HTTP_200_OK) @allow_permission( diff --git a/apiserver/plane/utils/grouper.py b/apiserver/plane/utils/grouper.py index 38ac74a1647..4cf97d56566 100644 --- a/apiserver/plane/utils/grouper.py +++ b/apiserver/plane/utils/grouper.py @@ -93,7 +93,7 @@ def issue_on_results(issues, group_by, sub_group_by): "link_count", "is_draft", "archived_at", - "state__group", + "state__group" ] if group_by in FIELD_MAPPER: From cd0382899721c1e0c874d866c9141cff994707a7 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Tue, 31 Dec 2024 11:51:42 +0000 Subject: [PATCH 087/120] migration file --- packages/types/src/issues/issue.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/types/src/issues/issue.d.ts b/packages/types/src/issues/issue.d.ts index ae4a98d63f0..024ab5f6070 100644 --- a/packages/types/src/issues/issue.d.ts +++ b/packages/types/src/issues/issue.d.ts @@ -58,6 +58,7 @@ export type TIssue = TBaseIssue & { issue_link?: TIssueLink[]; issue_relation?: IssueRelation[]; issue_related?: IssueRelation[]; + custom_properties?: Record; // tempId is used for optimistic updates. It is not a part of the API response. tempId?: string; // sourceIssueId is used to store the original issue id when creating a copy of an issue. Used in cloning property values. It is not a part of the API response. From fb53aafe1ca725fede507a9b9d600f5945810df5 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 24 Feb 2025 21:29:38 +0530 Subject: [PATCH 088/120] web dev complete --- web/Dockerfile.web | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/Dockerfile.web b/web/Dockerfile.web index 8cbfd75a276..eb67cbbc486 100644 --- a/web/Dockerfile.web +++ b/web/Dockerfile.web @@ -71,7 +71,7 @@ COPY --from=installer /app/web/package.json . # Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing COPY --from=installer /app/web/.next/standalone ./ -COPY --from=installer /app/web/.next ./.next +COPY --from=installer /app/web/.next ./web/.next COPY --from=installer /app/web/public ./web/public ARG NEXT_PUBLIC_API_BASE_URL="" From 7157b8512c18baf47b3743c26827d35651c12bd2 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Tue, 25 Feb 2025 08:38:16 +0000 Subject: [PATCH 089/120] dev changes --- aio/nginx.conf | 3 + apiserver/plane/api/serializers/issue.py | 69 ++-- apiserver/plane/api/serializers/webhook.py | 135 ++++++++ apiserver/plane/api/urls/__init__.py | 4 +- apiserver/plane/api/urls/issue.py | 1 - apiserver/plane/api/urls/state.py | 4 +- apiserver/plane/api/urls/webhook.py | 31 ++ apiserver/plane/api/views/__init__.py | 7 +- apiserver/plane/api/views/base.py | 3 +- apiserver/plane/api/views/issue.py | 7 +- apiserver/plane/api/views/member.py | 37 +- apiserver/plane/api/views/search.py | 11 +- apiserver/plane/api/views/state.py | 1 + apiserver/plane/api/views/webhook.py | 129 +++++++ apiserver/plane/app/serializers/issue.py | 6 + apiserver/plane/app/urls/issue.py | 6 + apiserver/plane/app/urls/views.py | 9 + apiserver/plane/app/views/__init__.py | 1 + apiserver/plane/app/views/base.py | 3 +- apiserver/plane/app/views/issue/base.py | 130 ++++++- apiserver/plane/app/views/view/base.py | 54 ++- .../provider/credentials/magic_code.py | 14 +- .../plane/authentication/views/app/magic.py | 12 +- ...e_customer_code_issue_hub_code_and_more.py | 43 +++ apiserver/plane/db/models/issue.py | 6 + apiserver/plane/settings/common.py | 2 +- apiserver/plane/settings/storage.py | 5 +- apiserver/plane/utils/constants.py | 13 + apiserver/plane/utils/grouper.py | 8 +- apiserver/plane/utils/issue_filters.py | 68 +++- deploy/selfhost/install.sh | 2 +- docker-compose-local.yml | 2 + docker-compose.yml | 319 +++++++++--------- packages/types/src/view-props.d.ts | 3 + .../components/issues/filters/selection.tsx | 2 +- space/next.config.js | 2 +- web/app/[workspaceSlug]/(projects)/header.tsx | 21 -- web/app/workspace-invitations/page.tsx | 1 - .../components/issues/custom-properties.tsx | 35 ++ web/core/components/issues/index.ts | 1 + .../issues/issue-detail/sidebar.tsx | 34 +- .../applied-filters/additional-properties.tsx | 32 ++ .../filters/applied-filters/filters-list.tsx | 9 + .../filters/applied-filters/index.ts | 1 + .../header/filters/additional-properties.tsx | 80 +++++ .../header/filters/custom-properties.tsx | 129 +++++++ .../header/filters/filters-selection.tsx | 29 +- .../filters/header/filters/index.ts | 1 + .../issues/peek-overview/properties.tsx | 25 +- .../workspace/sidebar/help-section.tsx | 10 +- .../workspace/sidebar/projects-list-item.tsx | 30 -- web/core/constants/analytics.ts | 4 + web/core/constants/issue.ts | 70 ++++ web/core/services/workspace.service.ts | 21 ++ .../helpers/issue-filter-helper.store.ts | 13 + .../store/issue/issue-details/issue.store.ts | 8 +- web/next.config.js | 6 +- 57 files changed, 1380 insertions(+), 332 deletions(-) create mode 100644 apiserver/plane/api/serializers/webhook.py create mode 100644 apiserver/plane/api/urls/webhook.py create mode 100644 apiserver/plane/api/views/webhook.py create mode 100644 apiserver/plane/db/migrations/0091_issue_customer_code_issue_hub_code_and_more.py create mode 100644 web/core/components/issues/custom-properties.tsx create mode 100644 web/core/components/issues/issue-layouts/filters/applied-filters/additional-properties.tsx create mode 100644 web/core/components/issues/issue-layouts/filters/header/filters/additional-properties.tsx create mode 100644 web/core/components/issues/issue-layouts/filters/header/filters/custom-properties.tsx diff --git a/aio/nginx.conf b/aio/nginx.conf index 78ae00d28ce..ac485460e8b 100644 --- a/aio/nginx.conf +++ b/aio/nginx.conf @@ -3,6 +3,9 @@ events { http { sendfile on; + chunked_transfer_encoding on; + client_body_buffer_size 10M; + client_max_body_size 50M; server { listen 80; diff --git a/apiserver/plane/api/serializers/issue.py b/apiserver/plane/api/serializers/issue.py index 31cf4412bab..a865f303ba2 100644 --- a/apiserver/plane/api/serializers/issue.py +++ b/apiserver/plane/api/serializers/issue.py @@ -92,7 +92,7 @@ class Meta: "description", "description_stripped", ] - + def validate(self, data): if ( data.get("start_date", None) is not None @@ -150,21 +150,23 @@ def validate(self, data): raise serializers.ValidationError( "Parent is not valid issue_id please pass a valid issue_id" ) - - if not is_uuid(data['created_by']): - if User.objects.filter(username=data['created_by']).exists(): - data['created_by']= User.objects.get(username=data['created_by']) - else: - user_data = { + if self.instance is None: + if not is_uuid(data.get('created_by')): + if User.objects.filter(username=data['created_by']).exists(): + data['created_by'] = User.objects.get(username=data['created_by']) + else: + user_data = { "email": data['created_by'] + '@plane-shipsy.com', "username": data['created_by'], - } - from plane.api.views import ProjectMemberAPIEndpoint - PMObj = ProjectMemberAPIEndpoint() - user = PMObj.create_user(user_data) - PMObj.create_workspace_member(self.context.get("workspace_id") ,user_data) - PMObj.create_project_member(self.context.get("project_id"), user_data) - data['created_by'] = user + "role": 5, + "display_name": data['created_by'] + } + from plane.api.views import ProjectMemberAPIEndpoint + PMObj = ProjectMemberAPIEndpoint() + user = PMObj.create_user(user_data) + PMObj.create_workspace_member(self.context.get("workspace_id"), user_data) + PMObj.create_project_member(self.context.get("project_id"), user_data) + data['created_by'] = user print(data) return data @@ -463,7 +465,7 @@ class Meta: "issue", "updated_by", "created_at", - "updated_at", + "updated_at" ] exclude = [ "comment_stripped", @@ -472,29 +474,32 @@ class Meta: def validate(self, data): try: - if data.get("comment_html", None) is not None: + if data.get("comment_html", None) is not None: + print("Am Here") + parsed = html.fromstring(data["comment_html"]) parsed_str = html.tostring(parsed, encoding="unicode") + print(html.tostring(parsed, encoding="unicode")) data["comment_html"] = parsed_str - # if not is_uuid(data['created_by']): - # if User.objects.filter(username=data['created_by']).exists(): - # data['created_by']= User.objects.get(username=data['created_by']) - # else: - # user_data = { - # "email": data['created_by'] + '@plane-shipsy.com', - # "username": data['created_by'], - # } - # from plane.api.views import ProjectMemberAPIEndpoint - # PMObj = ProjectMemberAPIEndpoint() - # user = PMObj.create_user(user_data) - # PMObj.create_workspace_member(self.context.get("workspace_id") ,user_data) - # PMObj.create_project_member(self.context.get("project_id"), user_data) - # data['created_by'] = user + ## if not data.get('created_by', None): + ## if User.objects.filter(username=data['created_by']).exists(): + ## data['created_by'] = User.objects.get(username=data['created_by']) + ## else: +## user_data = { + ## "email": data['created_by'] + '@plane-shipsy.com', + ## "username": data['created_by'], + ## } + ## from plane.api.views import ProjectMemberAPIEndpoint + ## PMObj = ProjectMemberAPIEndpoint() + ## user = PMObj.create_user(user_data) + ## PMObj.create_workspace_member(self.context.get("workspace_id") ,user_data) + ## PMObj.create_project_member(self.context.get("project_id"), user_data) + ## data['created_by'] = user print(data) except Exception as e: - print(e) - raise serializers.ValidationError("Invalid HTML passed") + print(e.__cause__) + raise serializers.ValidationError(e) return data diff --git a/apiserver/plane/api/serializers/webhook.py b/apiserver/plane/api/serializers/webhook.py new file mode 100644 index 00000000000..918d1705c1a --- /dev/null +++ b/apiserver/plane/api/serializers/webhook.py @@ -0,0 +1,135 @@ +# Python imports +import socket +import ipaddress +from urllib.parse import urlparse + +# Third party imports +from rest_framework import serializers + +# Module imports +from .base import DynamicBaseSerializer +from plane.db.models import Webhook, WebhookLog +from plane.db.models.webhook import validate_domain, validate_schema + + +class WebhookSerializer(DynamicBaseSerializer): + url = serializers.URLField(validators=[validate_schema, validate_domain]) + + def create(self, validated_data): + url = validated_data.get("url", None) + + # Extract the hostname from the URL + hostname = urlparse(url).hostname + if not hostname: + raise serializers.ValidationError( + {"url": "Invalid URL: No hostname found."} + ) + + # Resolve the hostname to IP addresses + try: + ip_addresses = socket.getaddrinfo(hostname, None) + except socket.gaierror: + raise serializers.ValidationError( + {"url": "Hostname could not be resolved."} + ) + + if not ip_addresses: + raise serializers.ValidationError( + {"url": "No IP addresses found for the hostname."} + ) + + for addr in ip_addresses: + ip = ipaddress.ip_address(addr[4][0]) + if ip.is_loopback: + raise serializers.ValidationError( + {"url": "URL resolves to a blocked IP address."} + ) + + # Additional validation for multiple request domains and their subdomains + request = self.context.get("request") + disallowed_domains = [ + "plane.so", + ] # Add your disallowed domains here + if request: + request_host = request.get_host().split(":")[ + 0 + ] # Remove port if present + disallowed_domains.append(request_host) + + # Check if hostname is a subdomain or exact match of any disallowed domain + if any( + hostname == domain or hostname.endswith("." + domain) + for domain in disallowed_domains + ): + raise serializers.ValidationError( + {"url": "URL domain or its subdomain is not allowed."} + ) + + return Webhook.objects.create(**validated_data) + + def update(self, instance, validated_data): + url = validated_data.get("url", None) + if url: + # Extract the hostname from the URL + hostname = urlparse(url).hostname + if not hostname: + raise serializers.ValidationError( + {"url": "Invalid URL: No hostname found."} + ) + + # Resolve the hostname to IP addresses + try: + ip_addresses = socket.getaddrinfo(hostname, None) + except socket.gaierror: + raise serializers.ValidationError( + {"url": "Hostname could not be resolved."} + ) + + if not ip_addresses: + raise serializers.ValidationError( + {"url": "No IP addresses found for the hostname."} + ) + + for addr in ip_addresses: + ip = ipaddress.ip_address(addr[4][0]) + if ip.is_loopback: + raise serializers.ValidationError( + {"url": "URL resolves to a blocked IP address."} + ) + + # Additional validation for multiple request domains and their subdomains + request = self.context.get("request") + disallowed_domains = [ + "plane.so", + ] # Add your disallowed domains here + if request: + request_host = request.get_host().split(":")[ + 0 + ] # Remove port if present + disallowed_domains.append(request_host) + + # Check if hostname is a subdomain or exact match of any disallowed domain + if any( + hostname == domain or hostname.endswith("." + domain) + for domain in disallowed_domains + ): + raise serializers.ValidationError( + {"url": "URL domain or its subdomain is not allowed."} + ) + + return super().update(instance, validated_data) + + class Meta: + model = Webhook + fields = "__all__" + read_only_fields = [ + "workspace", + "secret_key", + ] + + +class WebhookLogSerializer(DynamicBaseSerializer): + class Meta: + model = WebhookLog + fields = "__all__" + read_only_fields = ["workspace", "webhook"] diff --git a/apiserver/plane/api/urls/__init__.py b/apiserver/plane/api/urls/__init__.py index 62659eabe56..603ee239f2a 100644 --- a/apiserver/plane/api/urls/__init__.py +++ b/apiserver/plane/api/urls/__init__.py @@ -7,6 +7,7 @@ from .inbox import urlpatterns as inbox_patterns from .member import urlpatterns as member_patterns from .search import urlpatterns as search_patters +from .webhook import urlpatterns as webhook_patters urlpatterns = [ *project_patterns, @@ -17,5 +18,6 @@ *inbox_patterns, *member_patterns, *issue_type_patterns, - *search_patters + *search_patters, + *webhook_patters ] diff --git a/apiserver/plane/api/urls/issue.py b/apiserver/plane/api/urls/issue.py index d0a7fbf9081..afaec678f00 100644 --- a/apiserver/plane/api/urls/issue.py +++ b/apiserver/plane/api/urls/issue.py @@ -1,5 +1,4 @@ from django.urls import path - from plane.api.views import ( IssueAPIEndpoint, LabelAPIEndpoint, diff --git a/apiserver/plane/api/urls/state.py b/apiserver/plane/api/urls/state.py index b03f386e648..e18380fbc78 100644 --- a/apiserver/plane/api/urls/state.py +++ b/apiserver/plane/api/urls/state.py @@ -4,12 +4,12 @@ urlpatterns = [ path( - "workspaces//projects//states/", + "workspaces//projects//states/", StateAPIEndpoint.as_view(), name="states", ), path( - "workspaces//projects//states//", + "workspaces//projects//states//", StateAPIEndpoint.as_view(), name="states", ), diff --git a/apiserver/plane/api/urls/webhook.py b/apiserver/plane/api/urls/webhook.py new file mode 100644 index 00000000000..74a8da75965 --- /dev/null +++ b/apiserver/plane/api/urls/webhook.py @@ -0,0 +1,31 @@ +from django.urls import path + +from plane.api.views import ( + WebhookEndpoint, + WebhookLogsEndpoint, + WebhookSecretRegenerateEndpoint, +) + + +urlpatterns = [ + path( + "workspaces//webhooks/", + WebhookEndpoint.as_view(), + name="webhooks", + ), + path( + "workspaces//webhooks//", + WebhookEndpoint.as_view(), + name="webhooks", + ), + path( + "workspaces//webhooks//regenerate/", + WebhookSecretRegenerateEndpoint.as_view(), + name="webhooks", + ), + path( + "workspaces//webhook-logs//", + WebhookLogsEndpoint.as_view(), + name="webhooks", + ), +] diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index 0a61d52f4bb..d6b77e99ca2 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -33,4 +33,9 @@ from .inbox import InboxIssueAPIEndpoint -from .search import GlobalSearchEndpoint \ No newline at end of file +from .search import GlobalSearchEndpoint + +from .webhook import ( + WebhookEndpoint, WebhookLogsEndpoint, + WebhookSecretRegenerateEndpoint +) \ No newline at end of file diff --git a/apiserver/plane/api/views/base.py b/apiserver/plane/api/views/base.py index dea888ed355..6dbf4d52f13 100644 --- a/apiserver/plane/api/views/base.py +++ b/apiserver/plane/api/views/base.py @@ -116,7 +116,6 @@ def dispatch(self, request, *args, **kwargs): response = super().dispatch(request, *args, **kwargs) if settings.DEBUG: from django.db import connection - print( f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}" ) @@ -133,7 +132,7 @@ def check_kwargs(self, kwargs): project_id = self.kwargs.get("project_id", None) if project_id == "DEFAULT": project = Project.objects.filter( - name='default', workspace__slug=kwargs['slug'] + name='TICKET', workspace__slug=kwargs['slug'] ).first() if project: kwargs['project_id'] = project.id diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index 156d2145f78..227685acae9 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -118,7 +118,6 @@ def get( status=status.HTTP_200_OK, ) - class IssueAPIEndpoint(BaseAPIView): """ This viewset automatically provides `list`, `create`, `retrieve`, @@ -148,6 +147,7 @@ def get_queryset(self): if base_filter: query = query.filter(base_filter) + print(query) return (query .filter(**filters) .select_related("project") @@ -158,7 +158,7 @@ def get_queryset(self): .prefetch_related("labels") .order_by(self.kwargs.get("order_by", "-created_at"))).distinct() - + def get(self, request, slug, project_id, pk=None): external_id = request.GET.get("external_id") external_source = request.GET.get("external_source") @@ -925,7 +925,9 @@ def post(self, request, slug, project_id, issue_id): status=status.HTTP_409_CONFLICT, ) + serializer = IssueCommentSerializer(data=request.data) + if serializer.is_valid(): serializer.save( project_id=project_id, @@ -1108,7 +1110,6 @@ def post(self, request, slug, project_id, issue_id): ) if serializer.is_valid(): - # import pdb;pdb.set_trace() serializer.save(project_id=project_id, issue_id=issue_id) issue_activity.delay( type="attachment.activity.created", diff --git a/apiserver/plane/api/views/member.py b/apiserver/plane/api/views/member.py index 877d522b7f8..aee6cbc6af3 100644 --- a/apiserver/plane/api/views/member.py +++ b/apiserver/plane/api/views/member.py @@ -19,6 +19,7 @@ Project, WorkspaceMember, ProjectMember, + Profile ) from plane.app.permissions import ( @@ -63,17 +64,15 @@ def post(self, request, slug, project_id): # ------------------- Validation ------------------- if ( request.data.get("email") is None - or request.data.get("display_name") is None ): return Response( { - "error": "Expected email, display_name, workspace_slug, project_id, one or more of the fields are missing." + "error": "Expected email, workspace_slug, project_id, one or more of the fields are missing." }, status=status.HTTP_400_BAD_REQUEST, ) email = request.data.get("email") - try: validate_email(email) except ValidationError: @@ -92,7 +91,7 @@ def post(self, request, slug, project_id): ) # Check if user exists - user = User.objects.filter(email=email).first() + user = User.objects.filter(email=email.lower()).first() workspace_member = None project_member = None @@ -115,20 +114,32 @@ def post(self, request, slug, project_id): ) # If user does not exist, create the user - if not user: - user_data = { + user_data = { "email": email, "display_name": request.data.get("display_name"), "first_name": request.data.get("first_name", ""), "last_name": request.data.get("last_name", ""), + "role": request.data.get("role", 15), } + if not user: user = self.create_user(user_data) + profile, _ = Profile.objects.get_or_create(user=user) + profile.last_workspace_id = workspace.id + profile.onboarding_step.update({ + 'profile_complete': True, + 'workspace_join': True + }) + profile.is_tour_completed = True + profile.is_onboarded = True + profile.company_name = workspace.name + profile.save() if not workspace_member: - self.create_workspace_member(workspace.id, user) + self.create_workspace_member(workspace.id, user, role=user_data.get("role")) + if not project_member: - self.create_project_member(project.id, user) + self.create_project_member(project.id, user, role=user_data.get("role")) # Serialize the user and return the response @@ -144,26 +155,26 @@ def create_user(self, data): last_name=data.get("last_name", ""), username=data.get("username", uuid.uuid4().hex), password=make_password(data.get("username", uuid.uuid4().hex)), - is_password_autoset=True, + is_password_autoset=False, is_active=False, ) user.save() return user # Create a workspace member for the user if not already a member - def create_workspace_member(self, workspace_id, user): + def create_workspace_member(self, workspace_id, user, role=15): workspace_member = WorkspaceMember.objects.create( workspace_id=workspace_id, member=user, - role=5 + role=role ) workspace_member.save() - def create_project_member(self, project_id, user): + def create_project_member(self, project_id, user, role=15): # Create a project member for the user if not already a member project_member = ProjectMember.objects.create( project_id=project_id, member=user, - role=5 + role=role ) project_member.save() \ No newline at end of file diff --git a/apiserver/plane/api/views/search.py b/apiserver/plane/api/views/search.py index a463708be79..b514937f3e2 100644 --- a/apiserver/plane/api/views/search.py +++ b/apiserver/plane/api/views/search.py @@ -64,7 +64,7 @@ def filter_projects(self, query, slug, project_id, workspace_search): .values("name", "id", "identifier", "workspace__slug") ) - def filter_issues(self, query, slug, project_id, workspace_search): + def filter_issues(self, query, slug, project_id, workspace_search, created_by_username=None): fields = ["name", "sequence_id", "project__identifier"] q = Q() for field in fields: @@ -81,8 +81,10 @@ def filter_issues(self, query, slug, project_id, workspace_search): project__project_projectmember__member=self.request.user, project__project_projectmember__is_active=True, project__archived_at__isnull=True, - workspace__slug=slug, + workspace__slug=slug ) + if created_by_username: + issues = issues.filter(created_by__username=created_by_username) if workspace_search == "false" and project_id: issues = issues.filter(project_id=project_id) @@ -93,7 +95,7 @@ def filter_issues(self, query, slug, project_id, workspace_search): "sequence_id", "project__identifier", "project_id", - "workspace__slug", + "workspace__slug" )[:100] def filter_cycles(self, query, slug, project_id, workspace_search): @@ -230,6 +232,7 @@ def get(self, request, slug): workspace_search = request.query_params.get( "workspace_search", "false" ) + created_by_username = request.query_params.get("created_by_username", False) project_id = request.query_params.get("project_id", False) if not query: return Response( @@ -261,5 +264,5 @@ def get(self, request, slug): for model in MODELS_MAPPER.keys(): func = MODELS_MAPPER.get(model, None) - results[model] = func(query, slug, project_id, workspace_search) + results[model] = func(query, slug, project_id, workspace_search, created_by_username=created_by_username) return Response({"results": results}, status=status.HTTP_200_OK) diff --git a/apiserver/plane/api/views/state.py b/apiserver/plane/api/views/state.py index dd239754cb8..c39e6f34ebb 100644 --- a/apiserver/plane/api/views/state.py +++ b/apiserver/plane/api/views/state.py @@ -21,6 +21,7 @@ class StateAPIEndpoint(BaseAPIView): ] def get_queryset(self): + print(self.kwargs.get("project_id")) return ( State.objects.filter(workspace__slug=self.kwargs.get("slug")) .filter(project_id=self.kwargs.get("project_id")) diff --git a/apiserver/plane/api/views/webhook.py b/apiserver/plane/api/views/webhook.py new file mode 100644 index 00000000000..0e281a03a33 --- /dev/null +++ b/apiserver/plane/api/views/webhook.py @@ -0,0 +1,129 @@ +# Django imports +from django.db import IntegrityError + +# Third party imports +from rest_framework import status +from rest_framework.response import Response + +# Module imports +from plane.db.models import Webhook, WebhookLog, Workspace +from plane.db.models.webhook import generate_token +from .base import BaseAPIView +from plane.app.permissions import allow_permission, ROLE +from plane.app.serializers import WebhookSerializer, WebhookLogSerializer + + +class WebhookEndpoint(BaseAPIView): + + def post(self, request, slug): + workspace = Workspace.objects.get(slug=slug) + try: + serializer = WebhookSerializer( + data=request.data, context={"request": request} + ) + if serializer.is_valid(): + serializer.save(workspace_id=workspace.id) + return Response( + serializer.data, status=status.HTTP_201_CREATED + ) + return Response( + serializer.errors, status=status.HTTP_400_BAD_REQUEST + ) + except IntegrityError as e: + if "already exists" in str(e): + return Response( + {"error": "URL already exists for the workspace"}, + status=status.HTTP_410_GONE, + ) + raise IntegrityError + + def get(self, request, slug, pk=None): + if pk is None: + webhooks = Webhook.objects.filter(workspace__slug=slug) + serializer = WebhookSerializer( + webhooks, + fields=( + "id", + "url", + "is_active", + "created_at", + "updated_at", + "project", + "issue", + "cycle", + "module", + "issue_comment", + ), + many=True, + ) + return Response(serializer.data, status=status.HTTP_200_OK) + else: + webhook = Webhook.objects.get(workspace__slug=slug, pk=pk) + serializer = WebhookSerializer( + webhook, + fields=( + "id", + "url", + "is_active", + "created_at", + "updated_at", + "project", + "issue", + "cycle", + "module", + "issue_comment", + ), + ) + return Response(serializer.data, status=status.HTTP_200_OK) + + def patch(self, request, slug, pk): + webhook = Webhook.objects.get(workspace__slug=slug, pk=pk) + serializer = WebhookSerializer( + webhook, + data=request.data, + context={request: request}, + partial=True, + fields=( + "id", + "url", + "is_active", + "created_at", + "updated_at", + "project", + "issue", + "cycle", + "module", + "issue_comment", + ), + ) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data, status=status.HTTP_200_OK) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def delete(self, request, slug, pk): + webhook = Webhook.objects.get(pk=pk, workspace__slug=slug) + webhook.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + + +class WebhookSecretRegenerateEndpoint(BaseAPIView): + + @allow_permission(allowed_roles=[ROLE.ADMIN], level="WORKSPACE") + def post(self, request, slug, pk): + webhook = Webhook.objects.get(workspace__slug=slug, pk=pk) + webhook.secret_key = generate_token() + webhook.save() + serializer = WebhookSerializer(webhook) + return Response(serializer.data, status=status.HTTP_200_OK) + + +class WebhookLogsEndpoint(BaseAPIView): + + @allow_permission(allowed_roles=[ROLE.ADMIN], level="WORKSPACE") + def get(self, request, slug, webhook_id): + webhook_logs = WebhookLog.objects.filter( + workspace__slug=slug, webhook_id=webhook_id + ) + serializer = WebhookLogSerializer(webhook_logs, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) diff --git a/apiserver/plane/app/serializers/issue.py b/apiserver/plane/app/serializers/issue.py index 83f5aeea3cb..2bc4afea4ee 100644 --- a/apiserver/plane/app/serializers/issue.py +++ b/apiserver/plane/app/serializers/issue.py @@ -708,6 +708,12 @@ class Meta: "sort_order", "completed_at", "estimate_point", + "hub_code", + "customer_code", + "reference_number", + "trip_reference_number", + "vendor_code", + "worker_code", "priority", "start_date", "target_date", diff --git a/apiserver/plane/app/urls/issue.py b/apiserver/plane/app/urls/issue.py index e8ad4408dfb..47b34954f06 100644 --- a/apiserver/plane/app/urls/issue.py +++ b/apiserver/plane/app/urls/issue.py @@ -24,6 +24,7 @@ IssueDetailEndpoint, IssueAttachmentV2Endpoint, IssueBulkUpdateDateEndpoint, + SearchAPIEndpoint ) urlpatterns = [ @@ -320,4 +321,9 @@ IssueBulkUpdateDateEndpoint.as_view(), name="project-issue-dates", ), + path( + "workspaces//projects//search", + SearchAPIEndpoint.as_view(), + name="key-codes", + ), ] diff --git a/apiserver/plane/app/urls/views.py b/apiserver/plane/app/urls/views.py index a2f8e2ac88b..985da5d3dd0 100644 --- a/apiserver/plane/app/urls/views.py +++ b/apiserver/plane/app/urls/views.py @@ -63,6 +63,15 @@ ), name="global-view-issues", ), + path( + "workspaces//custom-properties/", + WorkspaceViewIssuesViewSet.as_view( + { + "get": "listCustomProperty", + } + ), + name="global-view-issues", + ), path( "workspaces//projects//user-favorite-views/", IssueViewFavoriteViewSet.as_view( diff --git a/apiserver/plane/app/views/__init__.py b/apiserver/plane/app/views/__init__.py index 1f94cb7ac9a..e6ca8f8634f 100644 --- a/apiserver/plane/app/views/__init__.py +++ b/apiserver/plane/app/views/__init__.py @@ -132,6 +132,7 @@ IssuePaginatedViewSet, IssueDetailEndpoint, IssueBulkUpdateDateEndpoint, + SearchAPIEndpoint ) from .issue.activity import ( diff --git a/apiserver/plane/app/views/base.py b/apiserver/plane/app/views/base.py index 45488b64e9a..10f5c205135 100644 --- a/apiserver/plane/app/views/base.py +++ b/apiserver/plane/app/views/base.py @@ -120,7 +120,8 @@ def dispatch(self, request, *args, **kwargs): if settings.DEBUG: from django.db import connection - + if len(connection.queries) > 20: + print(connection.queries) print( f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}" ) diff --git a/apiserver/plane/app/views/issue/base.py b/apiserver/plane/app/views/issue/base.py index 0c9db7597bb..8fa4c8197d8 100644 --- a/apiserver/plane/app/views/issue/base.py +++ b/apiserver/plane/app/views/issue/base.py @@ -5,6 +5,7 @@ from django.contrib.postgres.aggregates import ArrayAgg from django.contrib.postgres.fields import ArrayField from django.core.serializers.json import DjangoJSONEncoder +from rest_framework.pagination import PageNumberPagination from django.db.models import ( Exists, F, @@ -34,6 +35,7 @@ IssueDetailSerializer, IssueUserPropertySerializer, IssueSerializer, + BaseSerializer, ) from plane.bgtasks.issue_activities_task import issue_activity from plane.db.models import ( @@ -46,6 +48,7 @@ Project, ProjectMember, CycleIssue, + IssueCustomProperty, ) from plane.utils.grouper import ( issue_group_values, @@ -54,6 +57,7 @@ ) from plane.utils.issue_filters import issue_filters from plane.utils.order_queryset import order_issue_queryset +from plane.utils.constants import ALLOWED_CUSTOM_PROPERTY_WORKSPACE_MAP from plane.utils.paginator import ( GroupedOffsetPaginator, SubGroupedOffsetPaginator, @@ -64,6 +68,24 @@ from plane.utils.global_paginator import paginate from plane.bgtasks.webhook_task import model_activity +from plane.app.permissions import ( + ProjectEntityPermission, + ProjectLitePermission, + ProjectMemberPermission, +) + +class IssueCustomPropertySerializer(BaseSerializer): + class Meta: + model = IssueCustomProperty + fields = ["key", "value", "issue_type_custom_property"] + read_only_fields = [ + "id", + "issue", + "created_by", + "updated_by", + "created_at", + "updated_at", + ] class IssueListEndpoint(BaseAPIView): @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) @@ -123,6 +145,7 @@ def get(self, request, slug, project_id): order_by_param = request.GET.get("order_by", "-created_at") issue_queryset = queryset.filter(**filters) + # Issue queryset issue_queryset, _ = order_issue_queryset( issue_queryset=issue_queryset, @@ -209,7 +232,8 @@ def get_serializer_class(self): "workspace__id", ] - def get_queryset(self): + def get_queryset(self, filters={}): + custom_properties = filters.get("custom_properties", {}) return ( Issue.issue_objects.filter( project_id=self.kwargs.get("project_id") @@ -247,6 +271,18 @@ def get_queryset(self): .annotate(count=Func(F("id"), function="Count")) .values("count") ) + .filter( + *[ + Q( + id__in=IssueCustomProperty.objects.filter( + issue_id=OuterRef("id"), + key=group_key, + value__in=values + ).values("issue_id") + ) + for group_key, values in custom_properties.items() + ] + ) ).distinct() @method_decorator(gzip_page) @@ -260,9 +296,11 @@ def list(self, request, slug, project_id): project = Project.objects.get(pk=project_id, workspace__slug=slug) filters = issue_filters(request.query_params, "GET") + filtersWithoutCustomProperties = filters.copy() + filtersWithoutCustomProperties.pop('custom_properties', None) order_by_param = request.GET.get("order_by", "-created_at") - issue_queryset = self.get_queryset().filter(**filters, **extra_filters) + issue_queryset = self.get_queryset(filters).filter(**filtersWithoutCustomProperties, **extra_filters) # Custom ordering for priority and state # Issue queryset @@ -428,6 +466,12 @@ def create(self, request, slug, project_id): "sort_order", "completed_at", "estimate_point", + "hub_code", + "customer_code", + "reference_number", + "trip_reference_number", + "vendor_code", + "worker_code", "priority", "start_date", "target_date", @@ -1078,6 +1122,38 @@ def get(self, request, slug, project_id): ).data, ) +# class HubViewSet(BaseAPIView): +# @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) +# def list(self, request, slug, project_id): +# hubs = Hub.objects.filter(workspace__slug=slug, project_id=project_id) + +# # Extract query parameters +# hub_code = request.GET.get("hub_code", "").strip() +# reference_number = request.GET.get("reference_number", "").strip() +# trip_reference_number = request.GET.get("trip_reference_number", "").strip() +# vendor_code = request.GET.get("vendor_code", "").strip() +# worker_code = request.GET.get("worker_code", "").strip() + +# # Apply filtering dynamically +# filter_params = {} +# if hub_code: +# filter_params["hub_code__icontains"] = hub_code +# if reference_number: +# filter_params["reference_number__icontains"] = reference_number +# if trip_reference_number: +# filter_params["trip_reference_number__icontains"] = trip_reference_number +# if vendor_code: +# filter_params["vendor_code__icontains"] = vendor_code +# if worker_code: +# filter_params["worker_code__icontains"] = worker_code + +# hubs = hubs.filter(**filter_params) + +# # Serialize the results +# hub_data = HubSerializer(hubs, many=True).data +# return Response(hub_data, status=status.HTTP_200_OK) + + class IssueBulkUpdateDateEndpoint(BaseAPIView): @@ -1170,3 +1246,53 @@ def post(self, request, slug, project_id): {"message": "Issues updated successfully"}, status=status.HTTP_200_OK, ) + +class SearchAPIEndpoint(BaseAPIView): + """ + API to fetch hub codes for a specific workspace and project. + """ + model = Issue + webhook_event = "issue" + permission_classes = [ProjectEntityPermission] + def get(self, request, slug, project_id): + """ + Fetch unique values for hub_code, worker_code, reference_number, trip_reference_number, + customer_code, or vendor_code based on the requested query parameters. + """ + allowed_fields = ["hub_code", "worker_code", "reference_number", "trip_reference_number", "customer_code", "vendor_code"] + + field = request.GET.get("field") # Get the single field value + + if not field: # Ensure that 'field' is provided in the request + return Response( + {"error": "Missing 'field' parameter. Allowed values: " + ", ".join(allowed_fields)}, + status=status.HTTP_400_BAD_REQUEST + ) + + if field not in allowed_fields: # Validate the field + return Response( + {"error": f"Invalid field. Allowed values: {', '.join(allowed_fields)}"}, + status=status.HTTP_400_BAD_REQUEST + ) + + # Fetch values dynamically based on the requested field + values = Issue.objects.filter( + workspace__slug=slug, project_id=project_id + ).values_list(field, flat=True) + + unique_values = list(set(filter(None, values))) # Remove duplicates and nulls + + paginator = PageNumberPagination() + paginator.page_size = int(request.GET.get("limit", 10)) # Default limit = 10 + paginated_values = paginator.paginate_queryset(unique_values, request) + + # Custom pagination response + response_data = { + "total_results": len(unique_values), + "page": paginator.page.number, + "limit": paginator.page.paginator.per_page, + "total_pages": paginator.page.paginator.num_pages, + "data": paginated_values + } + + return Response(response_data, status=status.HTTP_200_OK) diff --git a/apiserver/plane/app/views/view/base.py b/apiserver/plane/app/views/view/base.py index e999dc32926..f82199cae23 100644 --- a/apiserver/plane/app/views/view/base.py +++ b/apiserver/plane/app/views/view/base.py @@ -1,5 +1,6 @@ # Django imports from django.contrib.postgres.aggregates import ArrayAgg +from django.db.models import Count from django.contrib.postgres.fields import ArrayField from django.db.models import ( Exists, @@ -27,6 +28,7 @@ ) from plane.app.serializers import ( IssueViewSerializer, + BaseSerializer, ) from plane.db.models import ( Issue, @@ -38,6 +40,7 @@ ProjectMember, Project, CycleIssue, + IssueCustomProperty ) from plane.utils.grouper import ( issue_group_values, @@ -45,6 +48,7 @@ issue_queryset_grouper, ) from plane.utils.issue_filters import issue_filters +from plane.utils.constants import ALLOWED_CUSTOM_PROPERTY_WORKSPACE_MAP from plane.utils.order_queryset import order_issue_queryset from plane.utils.paginator import ( GroupedOffsetPaginator, @@ -56,6 +60,18 @@ UserFavorite, ) +class IssueCustomPropertySerializer(BaseSerializer): + class Meta: + model = IssueCustomProperty + fields = ["key", "value", "issue_type_custom_property"] + read_only_fields = [ + "id", + "issue", + "created_by", + "updated_by", + "created_at", + "updated_at", + ] class WorkspaceViewViewSet(BaseViewSet): serializer_class = IssueViewSerializer @@ -190,7 +206,8 @@ def destroy(self, request, slug, pk): class WorkspaceViewIssuesViewSet(BaseViewSet): - def get_queryset(self): + def get_queryset(self, filters): + custom_properties = filters.get("custom_properties", {}) return ( Issue.issue_objects.annotate( sub_issues_count=Issue.issue_objects.filter( @@ -274,6 +291,18 @@ def get_queryset(self): Value([], output_field=ArrayField(UUIDField())), ), ) + .filter( + *[ + Q( + id__in=IssueCustomProperty.objects.filter( + issue_id=OuterRef("id"), + key=group_key, + value__in=values + ).values("issue_id") + ) + for group_key, values in custom_properties.items() + ] + ) ) @method_decorator(gzip_page) @@ -281,13 +310,32 @@ def get_queryset(self): allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE", ) + + def listCustomProperty(self, request, slug): + customPropertyAllowedKeys = ALLOWED_CUSTOM_PROPERTY_WORKSPACE_MAP.get(slug, []) + custom_properties = IssueCustomProperty.objects.filter(key__in=customPropertyAllowedKeys) + serializer = IssueCustomPropertySerializer(custom_properties, many=True) + + groupedCustomProperties = {} + for item in serializer.data: + key = item.get("key") + value = item.get("value") + if value is None or key not in customPropertyAllowedKeys: + continue + groupedCustomProperties.setdefault(key, set()).add(value) + + groupedUniqueCustomProperties = {key: list(values) for key, values in groupedCustomProperties.items()} + return Response(groupedUniqueCustomProperties, status=status.HTTP_200_OK) + def list(self, request, slug): filters = issue_filters(request.query_params, "GET") + filtersWithoutCustomProperties = filters.copy() + filtersWithoutCustomProperties.pop('custom_properties', None) order_by_param = request.GET.get("order_by", "-created_at") issue_queryset = ( - self.get_queryset() - .filter(**filters) + self.get_queryset(filters) + .filter(**filtersWithoutCustomProperties) .annotate( cycle_id=Subquery( CycleIssue.objects.filter( diff --git a/apiserver/plane/authentication/provider/credentials/magic_code.py b/apiserver/plane/authentication/provider/credentials/magic_code.py index 390d09d90b6..a531ca57043 100644 --- a/apiserver/plane/authentication/provider/credentials/magic_code.py +++ b/apiserver/plane/authentication/provider/credentials/magic_code.py @@ -83,12 +83,10 @@ def initiate(self): # Check if the key already exists in python if ri.exists(key): data = json.loads(ri.get(key)) - current_attempt = data["current_attempt"] + 1 - + email = str(self.key).replace("magic_", "", 1) + username = email.replace("@plane-shipsy.com", "", 1) if data["current_attempt"] > 2: - email = str(self.key).replace("magic_", "", 1) - username = email.replace ("@plane-shipsy.com", "", 1) if User.objects.filter(email=email).exists(): raise AuthenticationException( error_code=AUTHENTICATION_ERROR_CODES[ @@ -112,10 +110,11 @@ def initiate(self): "token": token, "username": username } - expiry = 600 + expiry = 30 + print(key, value) ri.set(key, json.dumps(value), ex=expiry) else: - username = self.key.replace ("@plane-shipsy.com", "", 1) + username = self.key.replace("@plane-shipsy.com", "", 1) value = { "current_attempt": 0, "email": self.key, @@ -129,6 +128,9 @@ def initiate(self): def set_user_data(self): ri = redis_instance() + keys = ri.keys('*') + print("All keys in redis:", keys) + print("Key to be checked:", self.key) if ri.exists(self.key): data = json.loads(ri.get(self.key)) token = data["token"] diff --git a/apiserver/plane/authentication/views/app/magic.py b/apiserver/plane/authentication/views/app/magic.py index f8d351af835..8002e34acf1 100644 --- a/apiserver/plane/authentication/views/app/magic.py +++ b/apiserver/plane/authentication/views/app/magic.py @@ -85,7 +85,7 @@ def post(self, request): ) -class MagicSignInEndpoint(APIView): +class MagicSignInEndpoint(BaseAPIView): permission_classes = [ AllowAny, ] @@ -95,7 +95,6 @@ class MagicSignInEndpoint(APIView): def add_user_to_workspace(self, user, workspace_slug): admin_user = User.objects.filter(is_superuser=True).first() workspace, base_project = self.get_workspace(workspace_slug, admin_user) - print(workspace, base_project, user) self.add_to_workspace(workspace, user) self.add_to_project(base_project, user) self.add_to_project(base_project, admin_user) @@ -108,7 +107,8 @@ def add_to_workspace(self, workspace, user): ).first() if not workspace_member: workspace_member = WorkspaceMember.objects.create( - workspace=workspace, member=user, is_active=True + workspace=workspace, member=user, is_active=True, + role=15 ) try: user.profile.last_workspace_id = workspace.id @@ -118,7 +118,9 @@ def add_to_workspace(self, workspace, user): }) user.profile.is_tour_completed = True user.profile.is_onboarded = True + user.is_password_autoset = True user.profile.company_name = workspace.name + user.save() user.profile.save() except Exception as e: print(e) @@ -154,9 +156,9 @@ def get_workspace(self, workspace_slug, admin_user): def get_or_create_project(self, workspace, user): default_project_dict ={ - 'identifier': 'DEFAULT', + 'identifier': 'TICKET', 'workspace_id': workspace.id, - 'name': 'default' + 'name': 'TICKET' } project = Project.objects.filter(**default_project_dict).first() if project: diff --git a/apiserver/plane/db/migrations/0091_issue_customer_code_issue_hub_code_and_more.py b/apiserver/plane/db/migrations/0091_issue_customer_code_issue_hub_code_and_more.py new file mode 100644 index 00000000000..d3d42273346 --- /dev/null +++ b/apiserver/plane/db/migrations/0091_issue_customer_code_issue_hub_code_and_more.py @@ -0,0 +1,43 @@ +# Generated by Django 4.2.14 on 2025-02-18 10:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0090_rename_issue_type_custom_proerty_issuecustomproperty_issue_type_custom_property'), + ] + + operations = [ + migrations.AddField( + model_name='issue', + name='customer_code', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='issue', + name='hub_code', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='issue', + name='reference_number', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='issue', + name='trip_reference_number', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='issue', + name='vendor_code', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='issue', + name='worker_code', + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/apiserver/plane/db/models/issue.py b/apiserver/plane/db/models/issue.py index d154a4fc881..2bb3e371001 100644 --- a/apiserver/plane/db/models/issue.py +++ b/apiserver/plane/db/models/issue.py @@ -133,6 +133,12 @@ class Issue(ProjectBaseModel): null=True, blank=True, ) + hub_code = models.CharField(max_length=255, blank=True, null=True) + vendor_code = models.CharField(max_length=255, blank=True, null=True) + customer_code = models.CharField(max_length=255, blank=True, null=True) + worker_code = models.CharField(max_length=255, blank=True, null=True) + reference_number = models.CharField(max_length=255, blank=True, null=True) + trip_reference_number = models.CharField(max_length=255, blank=True, null=True) name = models.CharField(max_length=255, verbose_name="Issue Name") description = models.JSONField(blank=True, default=dict) description_html = models.TextField(blank=True, default="

") diff --git a/apiserver/plane/settings/common.py b/apiserver/plane/settings/common.py index 11666ff40a1..e0975d37f9c 100644 --- a/apiserver/plane/settings/common.py +++ b/apiserver/plane/settings/common.py @@ -378,7 +378,7 @@ # CSRF cookies CSRF_COOKIE_SECURE = secure_origins -CSRF_COOKIE_HTTPONLY = True +CSRF_COOKIE_HTTPONLY = False CSRF_TRUSTED_ORIGINS = cors_allowed_origins CSRF_COOKIE_DOMAIN = os.environ.get("COOKIE_DOMAIN", None) CSRF_FAILURE_VIEW = "plane.authentication.views.common.csrf_failure" diff --git a/apiserver/plane/settings/storage.py b/apiserver/plane/settings/storage.py index 445bd4c20b8..72057c79ac9 100644 --- a/apiserver/plane/settings/storage.py +++ b/apiserver/plane/settings/storage.py @@ -38,7 +38,6 @@ def __init__(self, request=None): self.aws_s3_endpoint_url = os.environ.get( "AWS_S3_ENDPOINT_URL" ) or os.environ.get("MINIO_ENDPOINT_URL") - if os.environ.get("USE_MINIO") == "1": # Create an S3 client for MinIO self.s3_client = boto3.client( @@ -47,7 +46,7 @@ def __init__(self, request=None): aws_secret_access_key=self.aws_secret_access_key, region_name=self.aws_region, endpoint_url=( - f"{request.scheme}://{request.get_host()}" + f"https://{request.get_host()}" if request else self.aws_s3_endpoint_url ), @@ -89,6 +88,7 @@ def generate_presigned_post( # Generate the presigned POST URL try: + # Generate a presigned URL for the S3 object response = self.s3_client.generate_presigned_post( Bucket=self.aws_storage_bucket_name, @@ -97,6 +97,7 @@ def generate_presigned_post( Conditions=conditions, ExpiresIn=expiration, ) + # Handle errors except ClientError as e: print(f"Error generating presigned POST URL: {e}") diff --git a/apiserver/plane/utils/constants.py b/apiserver/plane/utils/constants.py index a4d1e1ea662..36f411eaba7 100644 --- a/apiserver/plane/utils/constants.py +++ b/apiserver/plane/utils/constants.py @@ -33,3 +33,16 @@ "signup", "config", ] + +ALLOWED_CUSTOM_PROPERTY_WORKSPACE_MAP = { + "heineken": [ + "Hub Code", + "Customer Name", + "Vendor Code", + "Vendor Name", + "Customer Code", + "Consignment Number", + "Trip Number", + "Hub Name" + ] +} \ No newline at end of file diff --git a/apiserver/plane/utils/grouper.py b/apiserver/plane/utils/grouper.py index 4cf97d56566..f064036eb8e 100644 --- a/apiserver/plane/utils/grouper.py +++ b/apiserver/plane/utils/grouper.py @@ -93,7 +93,13 @@ def issue_on_results(issues, group_by, sub_group_by): "link_count", "is_draft", "archived_at", - "state__group" + "state__group", + "hub_code", + "vendor_code", + "customer_code", + "worker_code", + "reference_number", + "trip_reference_number" ] if group_by in FIELD_MAPPER: diff --git a/apiserver/plane/utils/issue_filters.py b/apiserver/plane/utils/issue_filters.py index 2826dece7b6..d981c343745 100644 --- a/apiserver/plane/utils/issue_filters.py +++ b/apiserver/plane/utils/issue_filters.py @@ -268,7 +268,9 @@ def filter_created_by(params, issue_filter, method, prefix=""): ] if "None" in created_bys: issue_filter[f"{prefix}created_by__isnull"] = True + print(created_bys) created_bys = filter_valid_uuids(created_bys) + if len(created_bys) and "" not in created_bys: issue_filter[f"{prefix}created_by__in"] = created_bys else: @@ -281,6 +283,25 @@ def filter_created_by(params, issue_filter, method, prefix=""): return issue_filter +def filter_created_by_username(params, issue_filter, method, prefix=""): + if method == "GET": + created_bys = [ + item + for item in params.get("created_by_username").split(",") + if item != "null" + ] + if len(created_bys) and "" not in created_bys: + issue_filter[f"{prefix}created_by__username__in"] = created_bys + # else: + # if ( + # params.get("created_by", None) + # and len(params.get("created_by")) + # and params.get("created_by") != "null" + # ): + # issue_filter[f"{prefix}created_by__in"] = params.get("created_by") + return issue_filter + + def filter_name(params, issue_filter, method, prefix=""): if params.get("name", "") != "": issue_filter[f"{prefix}name__icontains"] = params.get("name") @@ -546,19 +567,47 @@ def filter_custom_properties(params, issue_filter, method, prefix=""): for item in params.get("custom_properties").split(",") if item != "null" ] - query = Q() - for row in custom_properties: - key, value = row.split(":") - query &= Q( - Q(custom_properties__project_custom_property_id=key) & - Q(custom_properties__value=value) - ) + + # query = Q() + # for row in custom_properties: + # key, value = row.split(":") + # query &= Q( + # Q(custom_properties__project_custom_property_id=key) & + # Q(custom_properties__value=value) + # ) - issue_filter['base'] = query + # issue_filter['base'] = query + groupedCustomProperties = {} + for row in custom_properties: + key, value = row.split(':') + if key not in groupedCustomProperties: + groupedCustomProperties[key] = [] + groupedCustomProperties[key].append(value) + + issue_filter['custom_properties'] = groupedCustomProperties + print(issue_filter) return issue_filter +def filter_character_fields(params, issue_filter, method, prefix=""): + character_fields = [ + "hub_code", + "worker_code", + "vendor_code", + "trip_reference_number", + "reference_number", + "customer_code" + ] + + for field in character_fields: + value = params.get(field, "").strip() # Remove spaces to avoid blank entries + if value: + issue_filter[f"{prefix}{field}__iexact"] = value # Case-insensitive filtering + + return issue_filter + + def issue_filters(query_params, method, prefix=""): issue_filter = {} @@ -572,6 +621,7 @@ def issue_filters(query_params, method, prefix=""): "assignees": filter_assignees, "mentions": filter_mentions, "created_by": filter_created_by, + "created_by_username": filter_created_by_username, "logged_by": filter_logged_by, "name": filter_name, "created_at": filter_created_at, @@ -594,4 +644,6 @@ def issue_filters(query_params, method, prefix=""): if key in query_params: func = value func(query_params, issue_filter, method, prefix) + + filter_character_fields(query_params, issue_filter, method, prefix) return issue_filter diff --git a/deploy/selfhost/install.sh b/deploy/selfhost/install.sh index 08cd4d9169e..185a1a05600 100755 --- a/deploy/selfhost/install.sh +++ b/deploy/selfhost/install.sh @@ -182,7 +182,7 @@ function buildYourOwnImage(){ local PLANE_TEMP_CODE_DIR=~/tmp/plane rm -rf $PLANE_TEMP_CODE_DIR mkdir -p $PLANE_TEMP_CODE_DIR - REPO=https://github.com/makeplane/plane.git + REPO=https://github.com/shipsy/plane.git git clone "$REPO" "$PLANE_TEMP_CODE_DIR" --branch "$BRANCH" --single-branch --depth 1 cp "$PLANE_TEMP_CODE_DIR/deploy/selfhost/build.yml" "$PLANE_TEMP_CODE_DIR/build.yml" diff --git a/docker-compose-local.yml b/docker-compose-local.yml index 067aaead89e..473fc8a4fc9 100644 --- a/docker-compose-local.yml +++ b/docker-compose-local.yml @@ -20,6 +20,8 @@ services: environment: MINIO_ROOT_USER: ${AWS_ACCESS_KEY_ID} MINIO_ROOT_PASSWORD: ${AWS_SECRET_ACCESS_KEY} + ports: + - 9090:9090 plane-db: image: postgres:15.7-alpine diff --git a/docker-compose.yml b/docker-compose.yml index 861fa9dd89f..a15bec98aa1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,174 +7,177 @@ services: args: DOCKER_BUILDKIT: 1 restart: always - command: node web/server.js web - depends_on: - - api + volumes: + - ./web:/app/web + - ./output:/app/out + command: tail -f #node web/server.js web + # depends_on: + # - api - admin: - container_name: admin - build: - context: . - dockerfile: ./admin/Dockerfile.admin - args: - DOCKER_BUILDKIT: 1 - restart: always - command: node admin/server.js admin - depends_on: - - api - - web +# admin: +# container_name: admin +# build: +# context: . +# dockerfile: ./admin/Dockerfile.admin +# args: +# DOCKER_BUILDKIT: 1 +# restart: always +# command: node admin/server.js admin +# depends_on: +# - api +# - web - space: - container_name: space - build: - context: . - dockerfile: ./space/Dockerfile.space - args: - DOCKER_BUILDKIT: 1 - restart: always - command: node space/server.js space - depends_on: - - api - - web +# space: +# container_name: space +# build: +# context: . +# dockerfile: ./space/Dockerfile.space +# args: +# DOCKER_BUILDKIT: 1 +# restart: always +# command: node space/server.js space +# depends_on: +# - api +# - web - api: - container_name: api - build: - context: ./apiserver - dockerfile: Dockerfile.api - args: - DOCKER_BUILDKIT: 1 - restart: always - command: ./bin/docker-entrypoint-api.sh - env_file: - - ./apiserver/.env - depends_on: - - plane-db - - plane-redis +# api: +# container_name: api +# build: +# context: ./apiserver +# dockerfile: Dockerfile.api +# args: +# DOCKER_BUILDKIT: 1 +# restart: always +# command: ./bin/docker-entrypoint-api.sh +# env_file: +# - ./apiserver/.env +# depends_on: +# - plane-db +# - plane-redis - worker: - container_name: bgworker - build: - context: ./apiserver - dockerfile: Dockerfile.api - args: - DOCKER_BUILDKIT: 1 - restart: always - command: ./bin/docker-entrypoint-worker.sh - env_file: - - ./apiserver/.env - depends_on: - - api - - plane-db - - plane-redis +# worker: +# container_name: bgworker +# build: +# context: ./apiserver +# dockerfile: Dockerfile.api +# args: +# DOCKER_BUILDKIT: 1 +# restart: always +# command: ./bin/docker-entrypoint-worker.sh +# env_file: +# - ./apiserver/.env +# depends_on: +# - api +# - plane-db +# - plane-redis - beat-worker: - container_name: beatworker - build: - context: ./apiserver - dockerfile: Dockerfile.api - args: - DOCKER_BUILDKIT: 1 - restart: always - command: ./bin/docker-entrypoint-beat.sh - env_file: - - ./apiserver/.env - depends_on: - - api - - plane-db - - plane-redis +# beat-worker: +# container_name: beatworker +# build: +# context: ./apiserver +# dockerfile: Dockerfile.api +# args: +# DOCKER_BUILDKIT: 1 +# restart: always +# command: ./bin/docker-entrypoint-beat.sh +# env_file: +# - ./apiserver/.env +# depends_on: +# - api +# - plane-db +# - plane-redis - migrator: - container_name: plane-migrator - build: - context: ./apiserver - dockerfile: Dockerfile.api - args: - DOCKER_BUILDKIT: 1 - restart: no - command: ./bin/docker-entrypoint-migrator.sh - env_file: - - ./apiserver/.env - depends_on: - - plane-db - - plane-redis +# migrator: +# container_name: plane-migrator +# build: +# context: ./apiserver +# dockerfile: Dockerfile.api +# args: +# DOCKER_BUILDKIT: 1 +# restart: no +# command: ./bin/docker-entrypoint-migrator.sh +# env_file: +# - ./apiserver/.env +# depends_on: +# - plane-db +# - plane-redis - live: - container_name: plane-live - build: - context: . - dockerfile: ./live/Dockerfile.live - args: - DOCKER_BUILDKIT: 1 - restart: always - command: node live/dist/server.js +# live: +# container_name: plane-live +# build: +# context: . +# dockerfile: ./live/Dockerfile.live +# args: +# DOCKER_BUILDKIT: 1 +# restart: always +# command: node live/dist/server.js - plane-db: - container_name: plane-db - image: postgres:15.7-alpine - restart: always - command: postgres -c 'max_connections=1000' - volumes: - - pgdata:/var/lib/postgresql/data - env_file: - - .env - environment: - POSTGRES_USER: ${POSTGRES_USER} - POSTGRES_DB: ${POSTGRES_DB} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - PGDATA: /var/lib/postgresql/data +# plane-db: +# container_name: plane-db +# image: postgres:15.7-alpine +# restart: always +# command: postgres -c 'max_connections=1000' +# volumes: +# - pgdata:/var/lib/postgresql/data +# env_file: +# - .env +# environment: +# POSTGRES_USER: ${POSTGRES_USER} +# POSTGRES_DB: ${POSTGRES_DB} +# POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} +# PGDATA: /var/lib/postgresql/data - plane-redis: - container_name: plane-redis - image: valkey/valkey:7.2.5-alpine - restart: always - volumes: - - redisdata:/data +# plane-redis: +# container_name: plane-redis +# image: valkey/valkey:7.2.5-alpine +# restart: always +# volumes: +# - redisdata:/data - plane-mq: - container_name: plane-mq - image: rabbitmq:3.13.6-management-alpine - restart: always - env_file: - - .env - environment: - RABBITMQ_DEFAULT_USER: ${RABBITMQ_USER} - RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASSWORD} - RABBITMQ_DEFAULT_VHOST: ${RABBITMQ_VHOST} - volumes: - - rabbitmq_data:/var/lib/rabbitmq +# plane-mq: +# container_name: plane-mq +# image: rabbitmq:3.13.6-management-alpine +# restart: always +# env_file: +# - .env +# environment: +# RABBITMQ_DEFAULT_USER: ${RABBITMQ_USER} +# RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASSWORD} +# RABBITMQ_DEFAULT_VHOST: ${RABBITMQ_VHOST} +# volumes: +# - rabbitmq_data:/var/lib/rabbitmq - plane-minio: - container_name: plane-minio - image: minio/minio - restart: always - command: server /export --console-address ":9090" - volumes: - - uploads:/export - environment: - MINIO_ROOT_USER: ${AWS_ACCESS_KEY_ID} - MINIO_ROOT_PASSWORD: ${AWS_SECRET_ACCESS_KEY} +# plane-minio: +# container_name: plane-minio +# image: minio/minio +# restart: always +# command: server /export --console-address ":9090" +# volumes: +# - uploads:/export +# environment: +# MINIO_ROOT_USER: ${AWS_ACCESS_KEY_ID} +# MINIO_ROOT_PASSWORD: ${AWS_SECRET_ACCESS_KEY} - # Comment this if you already have a reverse proxy running - proxy: - container_name: proxy - build: - context: ./nginx - dockerfile: Dockerfile - restart: always - ports: - - ${NGINX_PORT}:80 - environment: - FILE_SIZE_LIMIT: ${FILE_SIZE_LIMIT:-5242880} - BUCKET_NAME: ${AWS_S3_BUCKET_NAME:-uploads} - depends_on: - - web - - api - - space - - admin +# # Comment this if you already have a reverse proxy running +# proxy: +# container_name: proxy +# build: +# context: ./nginx +# dockerfile: Dockerfile +# restart: always +# ports: +# - ${NGINX_PORT}:80 +# environment: +# FILE_SIZE_LIMIT: ${FILE_SIZE_LIMIT:-5242880} +# BUCKET_NAME: ${AWS_S3_BUCKET_NAME:-uploads} +# depends_on: +# - web +# - api +# - space +# - admin -volumes: - pgdata: - redisdata: - uploads: - rabbitmq_data: +# volumes: +# pgdata: +# redisdata: +# uploads: +# rabbitmq_data: diff --git a/packages/types/src/view-props.d.ts b/packages/types/src/view-props.d.ts index 0c543241eef..9c79296da4f 100644 --- a/packages/types/src/view-props.d.ts +++ b/packages/types/src/view-props.d.ts @@ -77,6 +77,7 @@ export type TIssueParams = | "show_empty_groups" | "cursor" | "per_page" + | "hub_code" | "issue_type" | "layout" | "expand"; @@ -98,6 +99,8 @@ export interface IIssueFilterOptions { subscriber?: string[] | null; target_date?: string[] | null; issue_type?: string[] | null; + hub_code?: string[] | null; + custom_properties?: string[] | null; } export interface IIssueDisplayFilterOptions { diff --git a/space/core/components/issues/filters/selection.tsx b/space/core/components/issues/filters/selection.tsx index 2b5d54074c7..b8bd632feac 100644 --- a/space/core/components/issues/filters/selection.tsx +++ b/space/core/components/issues/filters/selection.tsx @@ -6,7 +6,7 @@ import { Search, X } from "lucide-react"; // types import { IIssueFilterOptions, TIssueFilterKeys } from "@/types/issue"; // components -import { FilterPriority, FilterState } from "."; +import { FilterPriority, FilterState, FilterCustomProperty } from "."; type Props = { filters: IIssueFilterOptions; diff --git a/space/next.config.js b/space/next.config.js index d18ce805f4d..de3afceb17c 100644 --- a/space/next.config.js +++ b/space/next.config.js @@ -13,7 +13,7 @@ const nextConfig = { return [ { source: "/", - headers: [{ key: "X-Frame-Options", value: "SAMEORIGIN" }], // clickjacking protection + headers: [{ key: "X-Frame-Options", value: "ALLOWALL" }], // clickjacking protection }, ]; }, diff --git a/web/app/[workspaceSlug]/(projects)/header.tsx b/web/app/[workspaceSlug]/(projects)/header.tsx index a7a19afe6b0..6607f3a9383 100644 --- a/web/app/[workspaceSlug]/(projects)/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/header.tsx @@ -33,27 +33,6 @@ export const WorkspaceDashboardHeader = () => { - - - captureEvent(GITHUB_REDIRECTED, { - element: "navbar", - }) - } - className="flex flex-shrink-0 items-center gap-1.5 rounded bg-custom-background-80 px-3 py-1.5" - href="https://github.com/makeplane/plane" - target="_blank" - rel="noopener noreferrer" - > - GitHub Logo - Star us on GitHub - - ); diff --git a/web/app/workspace-invitations/page.tsx b/web/app/workspace-invitations/page.tsx index a6829019892..3e0aafb7202 100644 --- a/web/app/workspace-invitations/page.tsx +++ b/web/app/workspace-invitations/page.tsx @@ -107,7 +107,6 @@ const WorkspaceInvitationPage = observer(() => { ) : ( )} - = ({ customProperties }) => { + if (!Array.isArray(customProperties) || customProperties.length === 0) { + return null; + } + + return ( +
+
+ {customProperties.map((element) => ( +
+
+ + {element.key} +
+
+ {element.value} +
+
+ ))} +
+ ); +}; diff --git a/web/core/components/issues/index.ts b/web/core/components/issues/index.ts index 483d53da918..42114945798 100644 --- a/web/core/components/issues/index.ts +++ b/web/core/components/issues/index.ts @@ -12,6 +12,7 @@ export * from "./issue-update-status"; export * from "./create-issue-toast-action-items"; export * from "./relations"; export * from "./issue-detail-widgets"; +export * from "./custom-properties"; // issue details export * from "./issue-detail"; diff --git a/web/core/components/issues/issue-detail/sidebar.tsx b/web/core/components/issues/issue-detail/sidebar.tsx index 1c0765675a7..1caee007e7d 100644 --- a/web/core/components/issues/issue-detail/sidebar.tsx +++ b/web/core/components/issues/issue-detail/sidebar.tsx @@ -14,7 +14,7 @@ import { StateDropdown, } from "@/components/dropdowns"; import { ButtonAvatars } from "@/components/dropdowns/member/avatar"; -import { IssueCycleSelect, IssueLabel, IssueModuleSelect, IssueParentSelect } from "@/components/issues"; +import { IssueCycleSelect, IssueLabel, IssueModuleSelect, IssueParentSelect, CustomProperties} from "@/components/issues"; // helpers import { cn } from "@/helpers/common.helper"; import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper"; @@ -54,6 +54,8 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { const projectDetails = getProjectById(issue.project_id); const stateDetails = getStateById(issue.state_id); const customProperties = issue?.custom_properties || []; + const fields = [ { key: "hub_code", label: "Hub Code" }, { key: "vendor_code", label: "Vendor Code" }, { key: "customer_code", label: "Customer Code" }, + { key: "worker_code", label: "Worker Code" }, { key: "reference_number", label: "Reference No" }, { key: "trip_reference_number", label: "Trip Ref No" }]; const minDate = issue.start_date ? getDate(issue.start_date) : null; minDate?.setDate(minDate.getDate()); @@ -283,6 +285,18 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { + {fields.map(({ key, label }) => + issue?.[key] ? ( +
+
+ + {label} +
+
{issue[key]}
+
+ ) : null + )} + = observer((props) => { isDisabled={!isEditable} /> )} - {customProperties?.length > 0 && ( -
- )} - {Array.isArray(customProperties) && customProperties.map((element) => ( -
-
- - {element?.key} -
-
- {element?.value} -
-
- )) - } + diff --git a/web/core/components/issues/issue-layouts/filters/applied-filters/additional-properties.tsx b/web/core/components/issues/issue-layouts/filters/applied-filters/additional-properties.tsx new file mode 100644 index 00000000000..3e4139df851 --- /dev/null +++ b/web/core/components/issues/issue-layouts/filters/applied-filters/additional-properties.tsx @@ -0,0 +1,32 @@ +"use client"; + +import { observer } from "mobx-react"; + +// icons +import { X } from "lucide-react"; + +type Props = { + handleRemove: (val: string) => void; + values: string[]; +}; + +export const AppliedAdditionalPropertiesFilters: React.FC = observer((props) => { + const { handleRemove, values } = props; + + return ( + <> + {values.map((element) => ( +
+ {element} + +
+ ))} + + ); +}); diff --git a/web/core/components/issues/issue-layouts/filters/applied-filters/filters-list.tsx b/web/core/components/issues/issue-layouts/filters/applied-filters/filters-list.tsx index 268358efea2..64acb2ccac6 100644 --- a/web/core/components/issues/issue-layouts/filters/applied-filters/filters-list.tsx +++ b/web/core/components/issues/issue-layouts/filters/applied-filters/filters-list.tsx @@ -14,6 +14,7 @@ import { AppliedProjectFilters, AppliedStateFilters, AppliedStateGroupFilters, + AppliedAdditionalPropertiesFilters } from "@/components/issues"; // constants // helpers @@ -36,6 +37,7 @@ type Props = { const membersFilters = ["assignees", "mentions", "created_by", "subscriber"]; const dateFilters = ["start_date", "target_date"]; +const additionalFilters = ["hub_code", "customer_code", "reference_number", "trip_reference_number", "vendor_code", "worker_code"]; export const AppliedFiltersList: React.FC = observer((props) => { const { @@ -134,6 +136,13 @@ export const AppliedFiltersList: React.FC = observer((props) => { values={value} /> )} + {additionalFilters?.includes(filterKey) && ( + handleRemoveFilter(filterKey, val)} + values={value} + /> + )} {isEditingAllowed && ( + )} + + ) : ( +

No Matches Found

+ )} + + )} + + ); +}); \ No newline at end of file diff --git a/web/core/components/issues/issue-layouts/filters/header/filters/custom-properties.tsx b/web/core/components/issues/issue-layouts/filters/header/filters/custom-properties.tsx new file mode 100644 index 00000000000..772cc5cc136 --- /dev/null +++ b/web/core/components/issues/issue-layouts/filters/header/filters/custom-properties.tsx @@ -0,0 +1,129 @@ +"use client"; + +import React, { useMemo, useState } from "react"; +// import sortBy from "lodash/sortBy"; +import { observer } from "mobx-react"; +import { useParams } from "next/navigation"; +import { IState } from "@plane/types"; +import { WorkspaceService } from "@/services/workspace.service"; +import { API_BASE_URL } from "@/helpers/common.helper"; +// import { Loader, StateGroupIcon } from "@plane/ui"; +import { FilterHeader, FilterOption } from "@/components/issues"; + +type Props = { + appliedFilters: string[] | null; + handleUpdate: (val: string) => void; + searchQuery: string; + states: IState[] | undefined; +}; + +export const FilterCustomProperty: React.FC = observer((props) => { + const { workspaceSlug } = useParams(); + const workspaceService = new WorkspaceService(API_BASE_URL); + const { appliedFilters, handleUpdate, searchQuery } = props; + + const [mainPreviewEnabled, setMainPreviewEnabled] = useState(true); + const [groupPreviewEnabled, setGroupPreviewEnabled] = useState>({}); + const [renderMoreGroupItems, setRenderMoreGroupItems] = useState>({}); + const [groupedProperties, setGroupedProperties] = useState>({}); + + const appliedFiltersCount = appliedFilters?.length ?? 0; + + const handleViewToggle = (groupKey) => { + setRenderMoreGroupItems((prev) => { + return { + ...prev, + [groupKey]: !prev[groupKey] + }; + }); + }; + + React.useEffect(() => { + const fetchCustomProperties = async (): Promise => { + try { + const data = await workspaceService.getIssuesCustomProperties(workspaceSlug); + setGroupedProperties(data); + Object?.keys(data)?.forEach((groupKey) => { + setGroupPreviewEnabled((prev) => ({ + ...prev, + [groupKey]: true + })); + }); + } catch (error) { + console.error("Error fetching custom properties:", error); + } + }; + + fetchCustomProperties(); + }, [workspaceSlug]); + + const filteredGroupOptions = useMemo(() => { + return Object.keys(groupedProperties).reduce((acc, groupKey) => { + const properties = groupedProperties[groupKey]; + + const filteredValues = properties + .filter((property) => property?.toLowerCase()?.includes(searchQuery.toLowerCase())) + .map((property) => property); + + if (filteredValues?.length > 0) { + acc[groupKey] = filteredValues; + } + + return acc; + }, {}); + }, [searchQuery, groupedProperties]); + + const toggleGroupPreview = (groupKey: string) => { + setGroupPreviewEnabled((prev) => ({ + ...prev, + [groupKey]: !prev[groupKey], + })); + }; + + return ( + <> + 0 ? ` (${appliedFiltersCount})` : ""}`} + isPreviewEnabled={mainPreviewEnabled} + handleIsPreviewEnabled={() => setMainPreviewEnabled(!mainPreviewEnabled)} + /> + {mainPreviewEnabled && ( +
+ {Object.keys(filteredGroupOptions).map((groupKey) => { + const properties = filteredGroupOptions[groupKey]; + return ( +
+ toggleGroupPreview(groupKey)} + /> + {groupPreviewEnabled[groupKey] && ( +
+ {properties + .slice(0, renderMoreGroupItems[groupKey] ? properties.length : 5) + .map((property) => ( + handleUpdate(`${groupKey}:${property}`)} + title={property} + /> + ))} + {properties.length > 5 ? : null} +
+ )} +
+ ); + })} +
+ )} + + ); +}); diff --git a/web/core/components/issues/issue-layouts/filters/header/filters/filters-selection.tsx b/web/core/components/issues/issue-layouts/filters/header/filters/filters-selection.tsx index 0bf180aefa7..3c6c2d43663 100644 --- a/web/core/components/issues/issue-layouts/filters/header/filters/filters-selection.tsx +++ b/web/core/components/issues/issue-layouts/filters/header/filters/filters-selection.tsx @@ -16,16 +16,18 @@ import { FilterState, FilterStateGroup, FilterTargetDate, + FilterAdditionalProperties, FilterCycle, FilterModule, FilterIssueGrouping, } from "@/components/issues"; // constants -import { ILayoutDisplayFiltersOptions } from "@/constants/issue"; +import { ILayoutDisplayFiltersOptions, ISSUE_ADDITIONAL_PROPERTIES } from "@/constants/issue"; // hooks import { usePlatformOS } from "@/hooks/use-platform-os"; // plane web components import { FilterIssueTypes } from "@/plane-web/components/issues"; +import { FilterCustomProperty } from "./custom-properties"; type Props = { filters: IIssueFilterOptions; @@ -242,6 +244,31 @@ export const FilterSelection: React.FC = observer((props) => { /> )} + + {ISSUE_ADDITIONAL_PROPERTIES.map((prop) => ( +
+ handleFiltersUpdate(prop.key, val)} + searchQuery={filtersSearchQuery} + /> +
+ ))} + + {/* custom_properties */} + {isFilterEnabled("custom_properties") && ( +
+ handleFiltersUpdate("custom_properties", val)} + // handleUpdate={(val) => handleFiltersUpdate("custom_properties", null, val)} + searchQuery={filtersSearchQuery} + /> +
+ )} ); diff --git a/web/core/components/issues/issue-layouts/filters/header/filters/index.ts b/web/core/components/issues/issue-layouts/filters/header/filters/index.ts index ab5756bf4d7..495020f9244 100644 --- a/web/core/components/issues/issue-layouts/filters/header/filters/index.ts +++ b/web/core/components/issues/issue-layouts/filters/header/filters/index.ts @@ -11,3 +11,4 @@ export * from "./state"; export * from "./cycle"; export * from "./module"; export * from "./target-date"; +export * from "./additional-properties"; diff --git a/web/core/components/issues/peek-overview/properties.tsx b/web/core/components/issues/peek-overview/properties.tsx index fcc706cbb86..e995aa909cb 100644 --- a/web/core/components/issues/peek-overview/properties.tsx +++ b/web/core/components/issues/peek-overview/properties.tsx @@ -2,7 +2,7 @@ import { FC } from "react"; import { observer } from "mobx-react"; -import { Signal, Tag, Triangle, LayoutPanelTop, CalendarClock, CalendarCheck2, Users, UserCircle2 } from "lucide-react"; +import { Signal, Tag, Triangle, LayoutPanelTop, CalendarClock, CalendarCheck2, Users, UserCircle2, Info } from "lucide-react"; // hooks // ui icons import { DiceIcon, DoubleCircleIcon, ContrastIcon } from "@plane/ui"; @@ -21,6 +21,7 @@ import { IssueParentSelect, IssueLabel, TIssueOperations, + CustomProperties } from "@/components/issues"; // helpers import { cn } from "@/helpers/common.helper"; @@ -29,7 +30,7 @@ import { shouldHighlightIssueDueDate } from "@/helpers/issue.helper"; import { useIssueDetail, useMember, useProject, useProjectState } from "@/hooks/store"; // plane web components import { IssueAdditionalPropertyValuesUpdate } from "@/plane-web/components/issue-types/values"; -import { IssueWorklogProperty } from "@/plane-web/components/issues"; +import { IssueWorklogProperty} from "@/plane-web/components/issues"; interface IPeekOverviewProperties { workspaceSlug: string; @@ -53,9 +54,11 @@ export const PeekOverviewProperties: FC = observer((pro if (!issue) return <>; const createdByDetails = getUserDetails(issue?.created_by); const projectDetails = getProjectById(issue.project_id); + const customProperties = issue?.custom_properties || []; const isEstimateEnabled = projectDetails?.estimate; const stateDetails = getStateById(issue.state_id); - + const fields = [ { key: "hub_code", label: "Hub Code" }, { key: "vendor_code", label: "Vendor Code" }, { key: "customer_code", label: "Customer Code" }, + { key: "worker_code", label: "Worker Code" }, { key: "reference_number", label: "Reference No" }, { key: "trip_reference_number", label: "Trip Ref No" }]; const minDate = getDate(issue.start_date); minDate?.setDate(minDate.getDate()); @@ -283,6 +286,22 @@ export const PeekOverviewProperties: FC = observer((pro +
+ {fields.map(({ key, label }) => + issue?.[key] ? ( +
+
+ + {label} +
+
{issue[key]}
+
+ ) : null + )} +
+ + + = observer( >
- -
-
- {fields.map(({ key, label }) => - issue?.[key] ? ( + {ISSUE_ADDITIONAL_PROPERTIES.map((prop) => + issue[prop.key] ? (
- {label} + {prop.title}
-
{issue[key]}
+
{issue[prop.key]}
) : null )} From db341b28d106a23956c9ffb40f591553124e8dbc Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Tue, 25 Feb 2025 09:52:33 +0000 Subject: [PATCH 091/120] dev issues --- packages/types/src/issues/issue.d.ts | 6 ++++++ .../components/issues/peek-overview/properties.tsx | 12 +++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/types/src/issues/issue.d.ts b/packages/types/src/issues/issue.d.ts index 024ab5f6070..108c1034830 100644 --- a/packages/types/src/issues/issue.d.ts +++ b/packages/types/src/issues/issue.d.ts @@ -59,6 +59,12 @@ export type TIssue = TBaseIssue & { issue_relation?: IssueRelation[]; issue_related?: IssueRelation[]; custom_properties?: Record; + hub_code?: string; + worker_code?: string; + customer_code?: string; + trip_reference_number?: string; + reference_number?: string; + vendor_code?: string; // tempId is used for optimistic updates. It is not a part of the API response. tempId?: string; // sourceIssueId is used to store the original issue id when creating a copy of an issue. Used in cloning property values. It is not a part of the API response. diff --git a/web/core/components/issues/peek-overview/properties.tsx b/web/core/components/issues/peek-overview/properties.tsx index e995aa909cb..cde6f597672 100644 --- a/web/core/components/issues/peek-overview/properties.tsx +++ b/web/core/components/issues/peek-overview/properties.tsx @@ -31,6 +31,7 @@ import { useIssueDetail, useMember, useProject, useProjectState } from "@/hooks/ // plane web components import { IssueAdditionalPropertyValuesUpdate } from "@/plane-web/components/issue-types/values"; import { IssueWorklogProperty} from "@/plane-web/components/issues"; +import { ISSUE_ADDITIONAL_PROPERTIES } from "@/constants/issue"; interface IPeekOverviewProperties { workspaceSlug: string; @@ -40,6 +41,7 @@ interface IPeekOverviewProperties { issueOperations: TIssueOperations; } + export const PeekOverviewProperties: FC = observer((props) => { const { workspaceSlug, projectId, issueId, issueOperations, disabled } = props; // store hooks @@ -287,14 +289,14 @@ export const PeekOverviewProperties: FC = observer((pro
- {fields.map(({ key, label }) => - issue?.[key] ? ( -
+ {ISSUE_ADDITIONAL_PROPERTIES.map((prop) => + issue[prop.key] ? ( +
- {label} + {prop?.title}
-
{issue[key]}
+
{issue[prop.key]}
) : null )} From 08a42fdb4c3ad1d9ccf18fe6d5be09d0f7ca5ca2 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Tue, 25 Feb 2025 11:36:51 +0000 Subject: [PATCH 092/120] dev issues --- web/core/components/issues/filters.tsx | 2 ++ web/core/constants/issue.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/web/core/components/issues/filters.tsx b/web/core/components/issues/filters.tsx index 4b80ba12f16..ff496d0e7fc 100644 --- a/web/core/components/issues/filters.tsx +++ b/web/core/components/issues/filters.tsx @@ -119,6 +119,8 @@ const HeaderFilters = observer(({ currentProjectDetails, projectId, workspaceSlu states={projectStates} cycleViewDisabled={!currentProjectDetails?.cycle_view} moduleViewDisabled={!currentProjectDetails?.module_view} + projectId={projectId} + workspaceSlug={workspaceSlug} /> diff --git a/web/core/constants/issue.ts b/web/core/constants/issue.ts index d4ccadfd571..c8691b56bdc 100644 --- a/web/core/constants/issue.ts +++ b/web/core/constants/issue.ts @@ -554,7 +554,7 @@ export const groupReactionEmojis = (reactions: any) => { export const ISSUE_ADDITIONAL_PROPERTIES: { key: TIssueAdditionalProperties; - title: string; + title: any; }[] = [ { key: "hub_code", title: "Hub Code" }, { key: "customer_code", title: "Customer Code" }, From 5c50c21a6b0aaaa0b578a9a051a18ef1c48fd442 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Tue, 25 Feb 2025 12:05:47 +0000 Subject: [PATCH 093/120] dev issues --- packages/types/src/issues/issue.d.ts | 12 ++++++------ packages/types/src/view-props.d.ts | 5 +++++ web/core/components/issues/issue-detail/sidebar.tsx | 2 -- .../components/issues/peek-overview/properties.tsx | 2 -- web/core/constants/issue.ts | 2 +- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/types/src/issues/issue.d.ts b/packages/types/src/issues/issue.d.ts index 108c1034830..7b1024e941b 100644 --- a/packages/types/src/issues/issue.d.ts +++ b/packages/types/src/issues/issue.d.ts @@ -59,12 +59,12 @@ export type TIssue = TBaseIssue & { issue_relation?: IssueRelation[]; issue_related?: IssueRelation[]; custom_properties?: Record; - hub_code?: string; - worker_code?: string; - customer_code?: string; - trip_reference_number?: string; - reference_number?: string; - vendor_code?: string; + hub_code?: string[] | null; + worker_code?: string[] | null; + customer_code?: string[] | null; + trip_reference_number?: string[] | null; + reference_number?: string[] | null; + vendor_code?: string[] | null; // tempId is used for optimistic updates. It is not a part of the API response. tempId?: string; // sourceIssueId is used to store the original issue id when creating a copy of an issue. Used in cloning property values. It is not a part of the API response. diff --git a/packages/types/src/view-props.d.ts b/packages/types/src/view-props.d.ts index 9c79296da4f..fc0561ff565 100644 --- a/packages/types/src/view-props.d.ts +++ b/packages/types/src/view-props.d.ts @@ -100,6 +100,11 @@ export interface IIssueFilterOptions { target_date?: string[] | null; issue_type?: string[] | null; hub_code?: string[] | null; + customer_code?: string[] | null; + worker_code?: string[] | null; + trip_reference_number?: string[] | null; + reference_number?: string[] | null; + vendor_code?: string[] | null; custom_properties?: string[] | null; } diff --git a/web/core/components/issues/issue-detail/sidebar.tsx b/web/core/components/issues/issue-detail/sidebar.tsx index c5ce1632c08..67c90a9b106 100644 --- a/web/core/components/issues/issue-detail/sidebar.tsx +++ b/web/core/components/issues/issue-detail/sidebar.tsx @@ -55,8 +55,6 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { const projectDetails = getProjectById(issue.project_id); const stateDetails = getStateById(issue.state_id); const customProperties = issue?.custom_properties || []; - const fields = [ { key: "hub_code", label: "Hub Code" }, { key: "vendor_code", label: "Vendor Code" }, { key: "customer_code", label: "Customer Code" }, - { key: "worker_code", label: "Worker Code" }, { key: "reference_number", label: "Reference No" }, { key: "trip_reference_number", label: "Trip Ref No" }]; const minDate = issue.start_date ? getDate(issue.start_date) : null; minDate?.setDate(minDate.getDate()); diff --git a/web/core/components/issues/peek-overview/properties.tsx b/web/core/components/issues/peek-overview/properties.tsx index cde6f597672..1675ac3e410 100644 --- a/web/core/components/issues/peek-overview/properties.tsx +++ b/web/core/components/issues/peek-overview/properties.tsx @@ -59,8 +59,6 @@ export const PeekOverviewProperties: FC = observer((pro const customProperties = issue?.custom_properties || []; const isEstimateEnabled = projectDetails?.estimate; const stateDetails = getStateById(issue.state_id); - const fields = [ { key: "hub_code", label: "Hub Code" }, { key: "vendor_code", label: "Vendor Code" }, { key: "customer_code", label: "Customer Code" }, - { key: "worker_code", label: "Worker Code" }, { key: "reference_number", label: "Reference No" }, { key: "trip_reference_number", label: "Trip Ref No" }]; const minDate = getDate(issue.start_date); minDate?.setDate(minDate.getDate()); diff --git a/web/core/constants/issue.ts b/web/core/constants/issue.ts index c8691b56bdc..d4ccadfd571 100644 --- a/web/core/constants/issue.ts +++ b/web/core/constants/issue.ts @@ -554,7 +554,7 @@ export const groupReactionEmojis = (reactions: any) => { export const ISSUE_ADDITIONAL_PROPERTIES: { key: TIssueAdditionalProperties; - title: any; + title: string; }[] = [ { key: "hub_code", title: "Hub Code" }, { key: "customer_code", title: "Customer Code" }, From daa3d2e83238fe588b423a1211ce87d8efc0a0ca Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Tue, 25 Feb 2025 12:30:48 +0000 Subject: [PATCH 094/120] dev issues --- web/core/components/issues/filters.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/web/core/components/issues/filters.tsx b/web/core/components/issues/filters.tsx index ff496d0e7fc..4b80ba12f16 100644 --- a/web/core/components/issues/filters.tsx +++ b/web/core/components/issues/filters.tsx @@ -119,8 +119,6 @@ const HeaderFilters = observer(({ currentProjectDetails, projectId, workspaceSlu states={projectStates} cycleViewDisabled={!currentProjectDetails?.cycle_view} moduleViewDisabled={!currentProjectDetails?.module_view} - projectId={projectId} - workspaceSlug={workspaceSlug} /> From 08bd96abe0521a49715f1995f8a0632701fc21bd Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Wed, 26 Feb 2025 06:11:35 +0000 Subject: [PATCH 095/120] fixed --- .../issues/issue-detail/sidebar.tsx | 6 ++-- .../applied-filters/additional-properties.tsx | 1 + .../header/filters/additional-properties.tsx | 18 ++++++------ .../header/filters/custom-properties.tsx | 29 ++++++++++++++----- .../header/filters/filters-selection.tsx | 6 ++-- .../issues/peek-overview/properties.tsx | 4 +-- web/core/constants/analytics.ts | 6 +--- web/core/constants/issue.ts | 3 +- web/core/services/workspace.service.ts | 2 +- 9 files changed, 42 insertions(+), 33 deletions(-) diff --git a/web/core/components/issues/issue-detail/sidebar.tsx b/web/core/components/issues/issue-detail/sidebar.tsx index 67c90a9b106..b80ac7c44cc 100644 --- a/web/core/components/issues/issue-detail/sidebar.tsx +++ b/web/core/components/issues/issue-detail/sidebar.tsx @@ -284,9 +284,9 @@ export const IssueDetailsSidebar: React.FC = observer((props) => {
- {ISSUE_ADDITIONAL_PROPERTIES.map((prop) => + {ISSUE_ADDITIONAL_PROPERTIES.map((prop: any) => issue[prop.key] ? ( -
+
{prop.title} @@ -313,7 +313,7 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { /> )}
diff --git a/web/core/components/issues/issue-layouts/filters/applied-filters/additional-properties.tsx b/web/core/components/issues/issue-layouts/filters/applied-filters/additional-properties.tsx index 3e4139df851..c90a3533b16 100644 --- a/web/core/components/issues/issue-layouts/filters/applied-filters/additional-properties.tsx +++ b/web/core/components/issues/issue-layouts/filters/applied-filters/additional-properties.tsx @@ -8,6 +8,7 @@ import { X } from "lucide-react"; type Props = { handleRemove: (val: string) => void; values: string[]; + editable: boolean | undefined; }; export const AppliedAdditionalPropertiesFilters: React.FC = observer((props) => { diff --git a/web/core/components/issues/issue-layouts/filters/header/filters/additional-properties.tsx b/web/core/components/issues/issue-layouts/filters/header/filters/additional-properties.tsx index 79a84d5479a..62d19bb871b 100644 --- a/web/core/components/issues/issue-layouts/filters/header/filters/additional-properties.tsx +++ b/web/core/components/issues/issue-layouts/filters/header/filters/additional-properties.tsx @@ -8,10 +8,10 @@ import { WorkspaceService } from "@/services/workspace.service"; import { API_BASE_URL } from "@/helpers/common.helper"; type Props = { - appliedFilters: Record; - handleUpdate: (field: string, val: string) => void; - additionalPropertyTitle: string; - additionalPropertyKey: string; + appliedFilters: string[] | null; + handleUpdate: (val: string) => void; + additionalPropertyTitle: string | undefined; + additionalPropertyKey: string | undefined; searchQuery: string; }; @@ -27,8 +27,8 @@ export const FilterAdditionalProperties: React.FC = observer((props) => { useEffect(() => { const fetchOptions = async () => { try { - const response = await workspaceService.getIssueAdditionalProperties(workspaceSlug, projectId, additionalPropertyKey); - const formattedOptions = response.data.map((item: string) => ({ key: item, value: item })) || []; + const response = await workspaceService.getIssueAdditionalProperties(workspaceSlug.toString(), projectId.toString(), props.additionalPropertyKey); + const formattedOptions = response.data.map((item: string) => ({ label: item, value: item })) || []; setOptions(formattedOptions); } catch (error) { console.error(`Error fetching ${additionalPropertyKey} options:`, error); @@ -39,7 +39,7 @@ export const FilterAdditionalProperties: React.FC = observer((props) => { }, [workspaceSlug, projectId, additionalPropertyKey]); const filteredOptions = searchQuery - ? options.filter(({ key }) => key.toLowerCase().includes(searchQuery.toLowerCase())) + ? options.filter((label: any) => label.toLowerCase().includes(searchQuery.toLowerCase())) : options; return ( @@ -53,12 +53,12 @@ export const FilterAdditionalProperties: React.FC = observer((props) => {
{filteredOptions.length > 0 ? ( <> - {filteredOptions.slice(0, visibleOptions).map(({ key, value }) => ( + {filteredOptions.slice(0, visibleOptions).map(({ label, value }) => ( handleUpdate(value)} - title={key} + title={label} /> ))} {filteredOptions.length > 5 && ( diff --git a/web/core/components/issues/issue-layouts/filters/header/filters/custom-properties.tsx b/web/core/components/issues/issue-layouts/filters/header/filters/custom-properties.tsx index 772cc5cc136..e67eaaac70d 100644 --- a/web/core/components/issues/issue-layouts/filters/header/filters/custom-properties.tsx +++ b/web/core/components/issues/issue-layouts/filters/header/filters/custom-properties.tsx @@ -14,7 +14,6 @@ type Props = { appliedFilters: string[] | null; handleUpdate: (val: string) => void; searchQuery: string; - states: IState[] | undefined; }; export const FilterCustomProperty: React.FC = observer((props) => { @@ -29,7 +28,7 @@ export const FilterCustomProperty: React.FC = observer((props) => { const appliedFiltersCount = appliedFilters?.length ?? 0; - const handleViewToggle = (groupKey) => { + const handleViewToggle = (groupKey: string) => { setRenderMoreGroupItems((prev) => { return { ...prev, @@ -41,24 +40,38 @@ export const FilterCustomProperty: React.FC = observer((props) => { React.useEffect(() => { const fetchCustomProperties = async (): Promise => { try { - const data = await workspaceService.getIssuesCustomProperties(workspaceSlug); - setGroupedProperties(data); - Object?.keys(data)?.forEach((groupKey) => { + const data = await workspaceService.getIssuesCustomProperties(workspaceSlug.toString()); + + if (Array.isArray(data)) { + // Convert array to an object with group keys + const groupedData: Record = data.reduce((acc, item) => { + const key = item.group || "default"; // Adjust based on API response + acc[key] = acc[key] ? [...acc[key], item] : [item]; + return acc; + }, {} as Record); + + setGroupedProperties(groupedData); + } else { + setGroupedProperties(data); + } + + Object.keys(data).forEach((groupKey) => { setGroupPreviewEnabled((prev) => ({ ...prev, - [groupKey]: true + [groupKey]: true, })); }); } catch (error) { console.error("Error fetching custom properties:", error); } }; + fetchCustomProperties(); }, [workspaceSlug]); const filteredGroupOptions = useMemo(() => { - return Object.keys(groupedProperties).reduce((acc, groupKey) => { + return Object.keys(groupedProperties).reduce>((acc, groupKey) => { const properties = groupedProperties[groupKey]; const filteredValues = properties @@ -105,7 +118,7 @@ export const FilterCustomProperty: React.FC = observer((props) => { .map((property) => ( handleUpdate(`${groupKey}:${property}`)} title={property} /> diff --git a/web/core/components/issues/issue-layouts/filters/header/filters/filters-selection.tsx b/web/core/components/issues/issue-layouts/filters/header/filters/filters-selection.tsx index 3c6c2d43663..71ace44f61a 100644 --- a/web/core/components/issues/issue-layouts/filters/header/filters/filters-selection.tsx +++ b/web/core/components/issues/issue-layouts/filters/header/filters/filters-selection.tsx @@ -245,14 +245,14 @@ export const FilterSelection: React.FC = observer((props) => {
)} - {ISSUE_ADDITIONAL_PROPERTIES.map((prop) => ( + {ISSUE_ADDITIONAL_PROPERTIES.map((prop: any) => (
handleFiltersUpdate(prop.key, val)} + handleUpdate={(val) => handleFiltersUpdate(prop.key as keyof IIssueFilterOptions, val)} searchQuery={filtersSearchQuery} />
diff --git a/web/core/components/issues/peek-overview/properties.tsx b/web/core/components/issues/peek-overview/properties.tsx index 1675ac3e410..333222791f5 100644 --- a/web/core/components/issues/peek-overview/properties.tsx +++ b/web/core/components/issues/peek-overview/properties.tsx @@ -287,7 +287,7 @@ export const PeekOverviewProperties: FC = observer((pro
- {ISSUE_ADDITIONAL_PROPERTIES.map((prop) => + {ISSUE_ADDITIONAL_PROPERTIES.map((prop: any) => issue[prop.key] ? (
@@ -300,7 +300,7 @@ export const PeekOverviewProperties: FC = observer((pro )}
- + { }; export const ISSUE_ADDITIONAL_PROPERTIES: { - key: TIssueAdditionalProperties; + key: keyof TIssue; title: string; }[] = [ { key: "hub_code", title: "Hub Code" }, diff --git a/web/core/services/workspace.service.ts b/web/core/services/workspace.service.ts index 7c97d05b227..20a68c64abf 100644 --- a/web/core/services/workspace.service.ts +++ b/web/core/services/workspace.service.ts @@ -289,7 +289,7 @@ export class WorkspaceService extends APIService { }); } - async getIssueAdditionalProperties(workspaceSlug: string, projectId: string , field: any): Promise[]> { + async getIssueAdditionalProperties(workspaceSlug: string, projectId: string , field: any): Promise { return this.get( `/api/workspaces/${workspaceSlug}/projects/${projectId}/search?field=${field}`, ) From 4ffe507e3d9b504ca34d75828a0293b94fb0fcb9 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Wed, 26 Feb 2025 06:25:34 +0000 Subject: [PATCH 096/120] fix --- packages/types/src/issues/issue.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/types/src/issues/issue.d.ts b/packages/types/src/issues/issue.d.ts index 7b1024e941b..287a64eb1d9 100644 --- a/packages/types/src/issues/issue.d.ts +++ b/packages/types/src/issues/issue.d.ts @@ -69,6 +69,7 @@ export type TIssue = TBaseIssue & { tempId?: string; // sourceIssueId is used to store the original issue id when creating a copy of an issue. Used in cloning property values. It is not a part of the API response. sourceIssueId?: string; + [key: string]: any; }; export type TIssueMap = { From 2007e99df195ad1153c7bc64e4257ae4b0c69d24 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Wed, 26 Feb 2025 07:39:34 +0000 Subject: [PATCH 097/120] build fix --- web/core/constants/issue.ts | 37 +------------------------------------ 1 file changed, 1 insertion(+), 36 deletions(-) diff --git a/web/core/constants/issue.ts b/web/core/constants/issue.ts index 55480d21683..257e08cb84d 100644 --- a/web/core/constants/issue.ts +++ b/web/core/constants/issue.ts @@ -9,6 +9,7 @@ import { TIssueOrderByOptions, TIssuePriorities, TIssueGroupingFilters, + TIssue } from "@plane/types"; export const DRAG_ALLOWED_GROUPS: TIssueGroupByOptions[] = [ @@ -244,12 +245,6 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: { "start_date", "target_date", "issue_type", - "hub_code", - "customer_code", - "worker_code", - "vendor_code", - "trip_reference_number", - "reference_number", ], display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS, display_filters: { @@ -262,12 +257,6 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: { "labels", "assignees", "created_by", - "hub_code", - "customer_code", - "worker_code", - "vendor_code", - "trip_reference_number", - "reference_number", null, ], order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"], @@ -348,12 +337,6 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: { "project", "start_date", "target_date", - "hub_code", - "customer_code", - "worker_code", - "vendor_code", - "trip_reference_number", - "reference_number", ], display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS, display_filters: { @@ -411,12 +394,6 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: { "start_date", "target_date", "issue_type", - "hub_code", - "customer_code", - "worker_code", - "vendor_code", - "trip_reference_number", - "reference_number", ], display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS, display_filters: { @@ -442,12 +419,6 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: { "labels", "start_date", "issue_type", - "hub_code", - "customer_code", - "worker_code", - "vendor_code", - "trip_reference_number", - "reference_number", ], display_properties: ["key", "issue_type"], display_filters: { @@ -502,12 +473,6 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: { "start_date", "target_date", "issue_type", - "hub_code", - "customer_code", - "worker_code", - "vendor_code", - "trip_reference_number", - "reference_number", ], display_properties: ["key", "issue_type"], display_filters: { From 5ad130fa6983b6f76af9e8a107793648a7dbedfd Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Wed, 26 Feb 2025 10:11:13 +0000 Subject: [PATCH 098/120] minor fixes --- .../components/issues/custom-properties.tsx | 2 -- .../issues/issue-detail/sidebar.tsx | 27 +++++++++-------- .../issues/peek-overview/properties.tsx | 30 +++++++++---------- web/core/constants/issue.ts | 2 +- web/core/hooks/use-group-dragndrop.ts | 4 +-- 5 files changed, 32 insertions(+), 33 deletions(-) diff --git a/web/core/components/issues/custom-properties.tsx b/web/core/components/issues/custom-properties.tsx index ebe4c1d71db..4c6d74717e3 100644 --- a/web/core/components/issues/custom-properties.tsx +++ b/web/core/components/issues/custom-properties.tsx @@ -1,5 +1,4 @@ import React from "react"; -import { Info } from "lucide-react"; type CustomProperty = { key: string; @@ -22,7 +21,6 @@ export const CustomProperties: React.FC = ({ customProper {customProperties.map((element) => (
- {element.key}
diff --git a/web/core/components/issues/issue-detail/sidebar.tsx b/web/core/components/issues/issue-detail/sidebar.tsx index b80ac7c44cc..ad5b18bd839 100644 --- a/web/core/components/issues/issue-detail/sidebar.tsx +++ b/web/core/components/issues/issue-detail/sidebar.tsx @@ -283,18 +283,6 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { />
- - {ISSUE_ADDITIONAL_PROPERTIES.map((prop: any) => - issue[prop.key] ? ( -
-
- - {prop.title} -
-
{issue[prop.key]}
-
- ) : null - )} = observer((props) => { isDisabled={!isEditable} /> )} + + {ISSUE_ADDITIONAL_PROPERTIES.map((prop: any) => + issue[prop.key] ? ( +
+
+ + {prop?.title} +
+
+ {issue[prop.key]} +
+
+ ) : null + )} + diff --git a/web/core/components/issues/peek-overview/properties.tsx b/web/core/components/issues/peek-overview/properties.tsx index 333222791f5..daf1929351a 100644 --- a/web/core/components/issues/peek-overview/properties.tsx +++ b/web/core/components/issues/peek-overview/properties.tsx @@ -286,22 +286,6 @@ export const PeekOverviewProperties: FC = observer((pro
-
- {ISSUE_ADDITIONAL_PROPERTIES.map((prop: any) => - issue[prop.key] ? ( -
-
- - {prop?.title} -
-
{issue[prop.key]}
-
- ) : null - )} -
- - - = observer((pro isDisabled={disabled} /> )} + + {ISSUE_ADDITIONAL_PROPERTIES.map((prop: any) => + issue[prop.key] ? ( +
+
+ + {prop?.title} +
+
{issue[prop.key]}
+
+ ) : null + )} + + ); diff --git a/web/core/constants/issue.ts b/web/core/constants/issue.ts index 257e08cb84d..00647d91d12 100644 --- a/web/core/constants/issue.ts +++ b/web/core/constants/issue.ts @@ -524,6 +524,6 @@ export const ISSUE_ADDITIONAL_PROPERTIES: { { key: "customer_code", title: "Customer Code" }, { key: "worker_code", title: "Worker Code" }, { key: "vendor_code", title: "Vendor Code" }, - { key: "trip_reference_number", title: "Trip Reference Number" }, + { key: "trip_reference_number", title: "Trip Ref Number" }, { key: "reference_number", title: "Reference Number" } ]; \ No newline at end of file diff --git a/web/core/hooks/use-group-dragndrop.ts b/web/core/hooks/use-group-dragndrop.ts index 08695d341d2..0a1c3ef697a 100644 --- a/web/core/hooks/use-group-dragndrop.ts +++ b/web/core/hooks/use-group-dragndrop.ts @@ -61,8 +61,8 @@ export const useGroupIssuesDragNDrop = ( const moduleKey = ISSUE_FILTER_DEFAULT_DATA["module"]; const cycleKey = ISSUE_FILTER_DEFAULT_DATA["cycle"]; - const isModuleChanged = Object.keys(data).includes(moduleKey); - const isCycleChanged = Object.keys(data).includes(cycleKey); + const isModuleChanged = Object.keys(data).includes(moduleKey.toString()); + const isCycleChanged = Object.keys(data).includes(cycleKey.toString()); if (isCycleChanged && workspaceSlug) { if (data[cycleKey]) { From bed3c3367e5417bf8101cd82f58a9179fb620227 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Wed, 26 Feb 2025 10:26:57 +0000 Subject: [PATCH 099/120] added custom properties in filters --- web/core/constants/issue.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/core/constants/issue.ts b/web/core/constants/issue.ts index 00647d91d12..eddd6417ba0 100644 --- a/web/core/constants/issue.ts +++ b/web/core/constants/issue.ts @@ -368,7 +368,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: { "vendor_code", "trip_reference_number", "reference_number", - // "custom_properties" + "custom_properties" ], display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS, display_filters: { @@ -449,6 +449,7 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: { "vendor_code", "trip_reference_number", "reference_number", + "custom_properties" ], display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS, display_filters: { From a0882d18cbe099f4075e8d46600c4a47d28b3dd7 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Wed, 26 Feb 2025 10:35:48 +0000 Subject: [PATCH 100/120] added properties --- packages/types/src/view-props.d.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/types/src/view-props.d.ts b/packages/types/src/view-props.d.ts index fc0561ff565..2ae3ff29952 100644 --- a/packages/types/src/view-props.d.ts +++ b/packages/types/src/view-props.d.ts @@ -80,7 +80,14 @@ export type TIssueParams = | "hub_code" | "issue_type" | "layout" - | "expand"; + | "expand" + | "customer_code" + | "worker_code" + | "trip_reference_number" + | "reference_number" + | "vendor_code" + | "custom_properties" + | "hub_code"; export type TCalendarLayouts = "month" | "week"; From 7809f0ee425dfe0c6335918305758f041498decd Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 27 Feb 2025 11:03:55 +0000 Subject: [PATCH 101/120] celery and flower --- apiserver/Dockerfile.api | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index ee99c50b4aa..ef6e3266221 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -63,10 +63,11 @@ RUN if [ "${ENV_TYPE}" = "apiserver" ]; then \ python manage.py migrate --noinput; \ fi +COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf \ CMD if [ "${ENV_TYPE}" = "apiserver" ]; then \ gunicorn -w 2 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --max-requests 1200 --max-requests-jitter 1000 --access-logfile -; \ elif [ "${ENV_TYPE}" = "celery" ]; then \ - celery -A plane worker -l info; \ + supervisord -c /etc/supervisor/conf.d/supervisord.conf \ elif [ "${ENV_TYPE}" = "celery-beat" ]; then \ celery -A plane beat -l info; \ else \ From 475fdcbcf5355931d5cc9c7264bc390645028f51 Mon Sep 17 00:00:00 2001 From: Naman-SIngla-Shipsy Date: Mon, 3 Mar 2025 03:09:03 +0000 Subject: [PATCH 102/120] Health check fix --- apiserver/Dockerfile.api | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index ee99c50b4aa..f703b2131f6 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -27,13 +27,13 @@ RUN apk add --no-cache --virtual .build-deps \ "postgresql-dev" \ "libc-dev" \ "linux-headers" \ - && \ - pip install -r requirements.txt --compile --no-cache-dir \ - && \ - apk del .build-deps + && pip install -r requirements.txt --compile --no-cache-dir \ + && apk del .build-deps +# Install Flower for Celery Monitoring +RUN pip install flower -# Add in Django deps and generate Django's static files +# Copy application files COPY manage.py manage.py COPY plane plane/ COPY templates templates/ @@ -46,9 +46,8 @@ RUN mkdir -p /code/plane/logs RUN chmod +x ./bin/* RUN chmod -R 777 /code -EXPOSE 8000 - -# ENV N8N_CONFIG_FILES=/home/node/config/env.json +# Expose ports for API and Flower UI +EXPOSE 8000 5555 # Declare a build argument ARG ENV_FILE_PATH @@ -62,10 +61,11 @@ ENV ENV_FILE_PATH=${ENV_FILE_PATH} RUN if [ "${ENV_TYPE}" = "apiserver" ]; then \ python manage.py migrate --noinput; \ fi - + CMD if [ "${ENV_TYPE}" = "apiserver" ]; then \ gunicorn -w 2 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:8000 --max-requests 1200 --max-requests-jitter 1000 --access-logfile -; \ elif [ "${ENV_TYPE}" = "celery" ]; then \ + celery -A plane flower --port=5555 --address=0.0.0.0 --url_prefix=flower & \ celery -A plane worker -l info; \ elif [ "${ENV_TYPE}" = "celery-beat" ]; then \ celery -A plane beat -l info; \ @@ -73,7 +73,3 @@ CMD if [ "${ENV_TYPE}" = "apiserver" ]; then \ echo "Unknown ENV_TYPE: ${ENV_TYPE}"; \ exit 1; \ fi - -# Expose container port and run entry point script - - From 217204ba8b9b7735d1be2fd08842b40347e62117 Mon Sep 17 00:00:00 2001 From: Naman-SIngla-Shipsy Date: Mon, 3 Mar 2025 05:28:44 +0000 Subject: [PATCH 103/120] env var fix --- apiserver/Dockerfile.api | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index f703b2131f6..5eaa699d01b 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -33,6 +33,8 @@ RUN apk add --no-cache --virtual .build-deps \ # Install Flower for Celery Monitoring RUN pip install flower +ENV FLOWER_UNAUTHENTICATED_API=true + # Copy application files COPY manage.py manage.py COPY plane plane/ From af3d837f0aa2de8ca4f87479d7e4d5f2695083c9 Mon Sep 17 00:00:00 2001 From: Naman-SIngla-Shipsy Date: Tue, 4 Mar 2025 03:51:43 +0000 Subject: [PATCH 104/120] dockerfile fix --- apiserver/Dockerfile.api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index 5eaa699d01b..c55b670df5a 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -70,7 +70,7 @@ CMD if [ "${ENV_TYPE}" = "apiserver" ]; then \ celery -A plane flower --port=5555 --address=0.0.0.0 --url_prefix=flower & \ celery -A plane worker -l info; \ elif [ "${ENV_TYPE}" = "celery-beat" ]; then \ - celery -A plane beat -l info; \ + (celery -A plane flower --port=5555 --address=0.0.0.0 --url-prefix=flower &) && celery -A plane beat -l info; \ else \ echo "Unknown ENV_TYPE: ${ENV_TYPE}"; \ exit 1; \ From 316984e63187a8e0578ab29994f899a92eced53a Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Wed, 12 Mar 2025 10:27:42 +0000 Subject: [PATCH 105/120] changes till 12th march --- .../api/middleware/api_authentication.py | 13 ++- apiserver/plane/api/serializers/issue.py | 25 ++---- apiserver/plane/api/views/base.py | 38 ++++++++- apiserver/plane/api/views/issue.py | 33 +++++--- apiserver/plane/app/permissions/__init__.py | 1 + apiserver/plane/app/permissions/project.py | 36 +++++++++ apiserver/plane/app/urls/issue.py | 10 ++- apiserver/plane/app/views/__init__.py | 3 +- apiserver/plane/app/views/issue/base.py | 80 +++++++++---------- .../plane/bgtasks/issue_activities_task.py | 11 ++- apiserver/plane/bgtasks/webhook_task.py | 4 +- apiserver/plane/db/models/issue.py | 2 +- apiserver/plane/settings/common.py | 6 +- apiserver/supervisord.conf | 16 ++++ .../templates/emails/exports/analytics.html | 9 ++- web/app/provider.tsx | 23 +++++- web/next.config.js | 2 +- 17 files changed, 223 insertions(+), 89 deletions(-) create mode 100644 apiserver/supervisord.conf diff --git a/apiserver/plane/api/middleware/api_authentication.py b/apiserver/plane/api/middleware/api_authentication.py index a0ea40144a0..d7c74a8f85c 100644 --- a/apiserver/plane/api/middleware/api_authentication.py +++ b/apiserver/plane/api/middleware/api_authentication.py @@ -19,15 +19,19 @@ class APIKeyAuthentication(authentication.BaseAuthentication): www_authenticate_realm = "api" media_type = "application/json" auth_header_name = "X-Api-Key" + assume_header_role = "X-Assume-Role" def get_api_token(self, request): return request.headers.get(self.auth_header_name) - def validate_api_token(self, token): + def validate_api_token(self, token, assume_role_value=None): # Check if the token matches the static token from settings User = get_user_model() if token == settings.STATIC_API_TOKEN: - user = User.objects.filter(is_superuser=True).first() + if assume_role_value: + user = User.objects.filter(username=assume_role_value).first() + else: + user = User.objects.filter(is_superuser=True).first() self.rewite_project_id_in_url() return (user, token) try: @@ -57,5 +61,8 @@ def authenticate(self, request): return None # Validate the API token - user, token = self.validate_api_token(token) + assume_role_value = request.headers.get(self.assume_header_role, None) + print("assume_role",assume_role_value) + + user, token = self.validate_api_token(token, assume_role_value) return user, token diff --git a/apiserver/plane/api/serializers/issue.py b/apiserver/plane/api/serializers/issue.py index a865f303ba2..5aeaaf6208f 100644 --- a/apiserver/plane/api/serializers/issue.py +++ b/apiserver/plane/api/serializers/issue.py @@ -61,6 +61,12 @@ class IssueSerializer(BaseSerializer): write_only=True, required=False, ) + state_id = serializers.PrimaryKeyRelatedField( + source="state", + queryset=State.objects.all(), + required=False, + allow_null=True, + ) labels = serializers.ListField( child=serializers.PrimaryKeyRelatedField( @@ -164,10 +170,9 @@ def validate(self, data): from plane.api.views import ProjectMemberAPIEndpoint PMObj = ProjectMemberAPIEndpoint() user = PMObj.create_user(user_data) - PMObj.create_workspace_member(self.context.get("workspace_id"), user_data) - PMObj.create_project_member(self.context.get("project_id"), user_data) + PMObj.create_workspace_member(self.context.get("workspace_id"), user,5) + PMObj.create_project_member(self.context.get("project_id"), user,5) data['created_by'] = user - print(data) return data @@ -481,20 +486,6 @@ def validate(self, data): parsed_str = html.tostring(parsed, encoding="unicode") print(html.tostring(parsed, encoding="unicode")) data["comment_html"] = parsed_str - ## if not data.get('created_by', None): - ## if User.objects.filter(username=data['created_by']).exists(): - ## data['created_by'] = User.objects.get(username=data['created_by']) - ## else: -## user_data = { - ## "email": data['created_by'] + '@plane-shipsy.com', - ## "username": data['created_by'], - ## } - ## from plane.api.views import ProjectMemberAPIEndpoint - ## PMObj = ProjectMemberAPIEndpoint() - ## user = PMObj.create_user(user_data) - ## PMObj.create_workspace_member(self.context.get("workspace_id") ,user_data) - ## PMObj.create_project_member(self.context.get("project_id"), user_data) - ## data['created_by'] = user print(data) except Exception as e: diff --git a/apiserver/plane/api/views/base.py b/apiserver/plane/api/views/base.py index 6dbf4d52f13..d45ee898300 100644 --- a/apiserver/plane/api/views/base.py +++ b/apiserver/plane/api/views/base.py @@ -113,6 +113,8 @@ def handle_exception(self, exc): def dispatch(self, request, *args, **kwargs): try: kwargs = self.check_kwargs(kwargs) + request.user = self.get_or_create_user_from_headers(request) + self.ensure_member_in_workspace(request.user, kwargs) response = super().dispatch(request, *args, **kwargs) if settings.DEBUG: from django.db import connection @@ -136,8 +138,42 @@ def check_kwargs(self, kwargs): ).first() if project: kwargs['project_id'] = project.id + + return kwargs - + + def get_or_create_user_from_headers(self, request): + """Extracts user info from headers and ensures they exist in the database.""" + from django.contrib.auth import get_user_model + User = get_user_model() + + assume_role = request.headers.get("X-Assume-Role") + + if assume_role: + user, created = User.objects.get_or_create( + username=assume_role, + defaults={"email": f"{assume_role}@example.com"} + ) + + if created: + print(f"New user created: {assume_role}") + + return user + + return request.user # Default user if no assume role is found + + + def ensure_member_in_workspace(self, user, kwargs): + """ Ensures the given user is a member of the workspace. """ + from plane.authentication.views.app.magic import MagicSignInEndpoint + + if not user or not user.is_authenticated: + return + + if kwargs.get('slug', None): + MagicSignInEndpoint().add_user_to_workspace(user, kwargs['slug']) + + def finalize_response(self, request, response, *args, **kwargs): # Call super to get the default response response = super().finalize_response( diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index 227685acae9..f5d3f49fd1b 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -39,6 +39,7 @@ ProjectEntityPermission, ProjectLitePermission, ProjectMemberPermission, + ProjectEntityGuestPermission ) from plane.bgtasks.issue_activities_task import issue_activity from plane.db.models import ( @@ -65,7 +66,7 @@ class WorkspaceIssueAPIEndpoint(BaseAPIView): model = Issue webhook_event = "issue" - permission_classes = [ProjectEntityPermission] + permission_classes = [ProjectEntityGuestPermission] serializer_class = IssueSerializer @property @@ -489,6 +490,10 @@ def put(self, request, slug, project_id): ) def patch(self, request, slug, project_id, pk=None): + modified_data = request.data.copy() if hasattr(request.data, 'copy') else dict(request.data) + # If 'state' is present in the data, rename it to 'state_id' + if 'state' in modified_data: + modified_data['state_id'] = modified_data.pop('state') issue = Issue.objects.get( workspace__slug=slug, project_id=project_id, pk=pk ) @@ -496,10 +501,10 @@ def patch(self, request, slug, project_id, pk=None): current_instance = json.dumps( IssueSerializer(issue).data, cls=DjangoJSONEncoder ) - requested_data = json.dumps(self.request.data, cls=DjangoJSONEncoder) + requested_data = json.dumps(modified_data, cls=DjangoJSONEncoder) serializer = IssueSerializer( issue, - data=request.data, + data=modified_data, context={ "project_id": project_id, "workspace_id": project.workspace_id, @@ -508,15 +513,15 @@ def patch(self, request, slug, project_id, pk=None): ) if serializer.is_valid(): if ( - request.data.get("external_id") - and (issue.external_id != str(request.data.get("external_id"))) + modified_data.get("external_id") + and (issue.external_id != str(modified_data.get("external_id"))) and Issue.objects.filter( project_id=project_id, workspace__slug=slug, - external_source=request.data.get( + external_source=modified_data.get( "external_source", issue.external_source ), - external_id=request.data.get("external_id"), + external_id=modified_data.get("external_id"), ).exists() ): return Response( @@ -526,7 +531,6 @@ def patch(self, request, slug, project_id, pk=None): }, status=status.HTTP_409_CONFLICT, ) - serializer.save() issue_activity.delay( type="issue.activity.updated", @@ -535,7 +539,7 @@ def patch(self, request, slug, project_id, pk=None): issue_id=str(pk), project_id=str(project_id), current_instance=current_instance, - epoch=int(timezone.now().timestamp()), + epoch=int(timezone.now().timestamp()) ) return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -1058,13 +1062,16 @@ def get(self, request, slug, project_id, issue_id, pk=None): ).order_by(request.GET.get("order_by", "created_at")) if pk: - issue_activities = issue_activities.get(pk=pk) - serializer = IssueActivitySerializer(issue_activities) - return Response(serializer.data, status=status.HTTP_200_OK) + try: + issue_activity = issue_activities.get(pk=pk) + serializer = IssueActivitySerializer(issue_activity) + return Response(serializer.data, status=status.HTTP_200_OK) + except IssueActivity.DoesNotExist: + return Response({"error": "IssueActivity not found"}, status=status.HTTP_404_NOT_FOUND) return self.paginate( request=request, - queryset=(issue_activities), + queryset=issue_activities, on_results=lambda issue_activity: IssueActivitySerializer( issue_activity, many=True, diff --git a/apiserver/plane/app/permissions/__init__.py b/apiserver/plane/app/permissions/__init__.py index e453881441b..82fdf5f5744 100644 --- a/apiserver/plane/app/permissions/__init__.py +++ b/apiserver/plane/app/permissions/__init__.py @@ -11,5 +11,6 @@ ProjectEntityPermission, ProjectMemberPermission, ProjectLitePermission, + ProjectEntityGuestPermission ) from .base import allow_permission, ROLE \ No newline at end of file diff --git a/apiserver/plane/app/permissions/project.py b/apiserver/plane/app/permissions/project.py index 522c1bfde43..b462e6492e2 100644 --- a/apiserver/plane/app/permissions/project.py +++ b/apiserver/plane/app/permissions/project.py @@ -116,6 +116,42 @@ def has_permission(self, request, view): ).exists() +class ProjectEntityGuestPermission(BasePermission): + def has_permission(self, request, view): + if request.user.is_anonymous: + return False + + if request.user.is_superuser: + return True + # Handle requests based on project__identifier + if hasattr(view, "project__identifier") and view.project__identifier: + if request.method in SAFE_METHODS: + return ProjectMember.objects.filter( + workspace__slug=view.workspace_slug, + member=request.user, + project__identifier=view.project__identifier, + is_active=True, + ).exists() + + ## Safe Methods -> Handle the filtering logic in queryset + if request.method in SAFE_METHODS: + return ProjectMember.objects.filter( + workspace__slug=view.workspace_slug, + member=request.user, + project_id=view.project_id, + is_active=True, + ).exists() + + ## Only project members or admins can create and edit the project attributes + return ProjectMember.objects.filter( + workspace__slug=view.workspace_slug, + member=request.user, + role__in=[Admin, Member, Guest], + project_id=view.project_id, + is_active=True, + ).exists() + + class ProjectLitePermission(BasePermission): def has_permission(self, request, view): if request.user.is_anonymous: diff --git a/apiserver/plane/app/urls/issue.py b/apiserver/plane/app/urls/issue.py index 47b34954f06..4a23752ca85 100644 --- a/apiserver/plane/app/urls/issue.py +++ b/apiserver/plane/app/urls/issue.py @@ -24,7 +24,8 @@ IssueDetailEndpoint, IssueAttachmentV2Endpoint, IssueBulkUpdateDateEndpoint, - SearchAPIEndpoint + SearchAPIEndpoint, + SearchSingleValueAPI ) urlpatterns = [ @@ -321,9 +322,14 @@ IssueBulkUpdateDateEndpoint.as_view(), name="project-issue-dates", ), - path( + path( "workspaces//projects//search", SearchAPIEndpoint.as_view(), name="key-codes", ), + path( + "workspaces//projects//filed_search", + SearchSingleValueAPI.as_view(), + name="key-codes" + ) ] diff --git a/apiserver/plane/app/views/__init__.py b/apiserver/plane/app/views/__init__.py index e6ca8f8634f..30d5c1cb35c 100644 --- a/apiserver/plane/app/views/__init__.py +++ b/apiserver/plane/app/views/__init__.py @@ -132,7 +132,8 @@ IssuePaginatedViewSet, IssueDetailEndpoint, IssueBulkUpdateDateEndpoint, - SearchAPIEndpoint + SearchAPIEndpoint, + SearchSingleValueAPI ) from .issue.activity import ( diff --git a/apiserver/plane/app/views/issue/base.py b/apiserver/plane/app/views/issue/base.py index 8fa4c8197d8..f846ed84204 100644 --- a/apiserver/plane/app/views/issue/base.py +++ b/apiserver/plane/app/views/issue/base.py @@ -1122,38 +1122,6 @@ def get(self, request, slug, project_id): ).data, ) -# class HubViewSet(BaseAPIView): -# @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) -# def list(self, request, slug, project_id): -# hubs = Hub.objects.filter(workspace__slug=slug, project_id=project_id) - -# # Extract query parameters -# hub_code = request.GET.get("hub_code", "").strip() -# reference_number = request.GET.get("reference_number", "").strip() -# trip_reference_number = request.GET.get("trip_reference_number", "").strip() -# vendor_code = request.GET.get("vendor_code", "").strip() -# worker_code = request.GET.get("worker_code", "").strip() - -# # Apply filtering dynamically -# filter_params = {} -# if hub_code: -# filter_params["hub_code__icontains"] = hub_code -# if reference_number: -# filter_params["reference_number__icontains"] = reference_number -# if trip_reference_number: -# filter_params["trip_reference_number__icontains"] = trip_reference_number -# if vendor_code: -# filter_params["vendor_code__icontains"] = vendor_code -# if worker_code: -# filter_params["worker_code__icontains"] = worker_code - -# hubs = hubs.filter(**filter_params) - -# # Serialize the results -# hub_data = HubSerializer(hubs, many=True).data -# return Response(hub_data, status=status.HTTP_200_OK) - - class IssueBulkUpdateDateEndpoint(BaseAPIView): @@ -1248,17 +1216,10 @@ def post(self, request, slug, project_id): ) class SearchAPIEndpoint(BaseAPIView): - """ - API to fetch hub codes for a specific workspace and project. - """ model = Issue webhook_event = "issue" - permission_classes = [ProjectEntityPermission] def get(self, request, slug, project_id): - """ - Fetch unique values for hub_code, worker_code, reference_number, trip_reference_number, - customer_code, or vendor_code based on the requested query parameters. - """ + allowed_fields = ["hub_code", "worker_code", "reference_number", "trip_reference_number", "customer_code", "vendor_code"] field = request.GET.get("field") # Get the single field value @@ -1283,7 +1244,7 @@ def get(self, request, slug, project_id): unique_values = list(set(filter(None, values))) # Remove duplicates and nulls paginator = PageNumberPagination() - paginator.page_size = int(request.GET.get("limit", 10)) # Default limit = 10 + paginator.page_size = int(request.GET.get("limit", 20)) # Default limit = 10 paginated_values = paginator.paginate_queryset(unique_values, request) # Custom pagination response @@ -1296,3 +1257,40 @@ def get(self, request, slug, project_id): } return Response(response_data, status=status.HTTP_200_OK) + +class SearchSingleValueAPI(BaseAPIView): + model = Issue + allowed_fields = ["hub_code", "trip_reference_number", "reference_number", "worker_code", "vendor_code","customer_code"] + + def get(self, request, slug, project_id): + # Extract query parameters (only one should be provided) + query_params = {field: request.GET.get(field) for field in self.allowed_fields if request.GET.get(field)} + + if len(query_params) != 1: + return Response( + {"error": f"Provide exactly one search parameter from: {', '.join(self.allowed_fields)}"}, + status=status.HTTP_400_BAD_REQUEST + ) + + # Extract the field and search value + search_field, search_value = next(iter(query_params.items())) + + # Query for exact matches first + exact_matches = Issue.objects.filter( + workspace__slug=slug, + project_id=project_id, + **{search_field: search_value} # Exact match + ).values_list(search_field, flat=True) + + # Query for partial matches using `icontains` + startwith_matches = Issue.objects.filter( + workspace__slug=slug, + project_id=project_id, + **{f"{search_field}__istartswith": search_value} # starts with match + ).exclude(**{search_field: search_value}) # Exclude exact match + startwith_matches = startwith_matches.values_list(search_field, flat=True) + + # Combine results: exact matches first, then partial matches + unique_values = list(set(exact_matches)) + list(set(startwith_matches)) + + return Response({"data": unique_values}, status=status.HTTP_200_OK) diff --git a/apiserver/plane/bgtasks/issue_activities_task.py b/apiserver/plane/bgtasks/issue_activities_task.py index 371204aa8a6..c4a3ce05033 100644 --- a/apiserver/plane/bgtasks/issue_activities_task.py +++ b/apiserver/plane/bgtasks/issue_activities_task.py @@ -367,14 +367,16 @@ def track_assignees( actor_id, issue_activities, epoch, + verb="updated" ): + requested_assignees = ( - set([str(asg) for asg in requested_data.get("assignee_ids", [])]) + set([str(asg) for asg in requested_data.get("assignee_ids", requested_data.get("assignees", []))]) if requested_data is not None else set() ) current_assignees = ( - set([str(asg) for asg in current_instance.get("assignee_ids", [])]) + set([str(asg) for asg in current_instance.get("assignee_ids", current_instance.get("assignees", []))]) if current_instance is not None else set() ) @@ -389,7 +391,7 @@ def track_assignees( IssueActivity( issue_id=issue_id, actor_id=actor_id, - verb="updated", + verb=verb, old_value="", new_value=assignee.display_name, field="assignees", @@ -597,7 +599,7 @@ def create_issue_activity( requested_data = ( json.loads(requested_data) if requested_data is not None else None ) - if requested_data.get("assignee_ids") is not None: + if requested_data.get("assignee_ids") is not None or requested_data.get("assignees")is not None: track_assignees( requested_data, current_instance, @@ -607,6 +609,7 @@ def create_issue_activity( actor_id, issue_activities, epoch, + "created" ) diff --git a/apiserver/plane/bgtasks/webhook_task.py b/apiserver/plane/bgtasks/webhook_task.py index 7614c4b2f57..ee35054b120 100644 --- a/apiserver/plane/bgtasks/webhook_task.py +++ b/apiserver/plane/bgtasks/webhook_task.py @@ -402,8 +402,10 @@ def webhook_activity( if event == "issue_comment": webhooks = webhooks.filter(issue_comment=True) - + for webhook in webhooks: + event_data = get_model_data(event=event, event_id=event_id) + actor_data = get_model_data(event="user", event_id=actor_id) webhook_send_task.delay( webhook=webhook.id, slug=slug, diff --git a/apiserver/plane/db/models/issue.py b/apiserver/plane/db/models/issue.py index 2bb3e371001..4cfd89115d0 100644 --- a/apiserver/plane/db/models/issue.py +++ b/apiserver/plane/db/models/issue.py @@ -45,7 +45,7 @@ def get_default_filters(): "labels": None, "start_date": None, "target_date": None, - "subscriber": None, + "subscriber": None } diff --git a/apiserver/plane/settings/common.py b/apiserver/plane/settings/common.py index e0975d37f9c..05774297339 100644 --- a/apiserver/plane/settings/common.py +++ b/apiserver/plane/settings/common.py @@ -153,7 +153,7 @@ CORS_ALLOW_ALL_ORIGINS = True secure_origins = False -CORS_ALLOW_HEADERS = [*default_headers, "X-API-Key"] +CORS_ALLOW_HEADERS = [*default_headers, "X-API-Key", "X-Assume-Role"] # Application Settings WSGI_APPLICATION = "plane.wsgi.application" @@ -362,7 +362,9 @@ DATA_UPLOAD_MAX_MEMORY_SIZE = int(os.environ.get("FILE_SIZE_LIMIT", 5242880)) # Cookie Settings +# SESSION_COOKIE_SECURE = True SESSION_COOKIE_SECURE = secure_origins +# SESSION_COOKIE_SAMESITE = "None" SESSION_COOKIE_HTTPONLY = True SESSION_ENGINE = "plane.db.models.session" SESSION_COOKIE_AGE = os.environ.get("SESSION_COOKIE_AGE", 604800) @@ -377,7 +379,9 @@ ADMIN_SESSION_COOKIE_AGE = os.environ.get("ADMIN_SESSION_COOKIE_AGE", 3600) # CSRF cookies +# CSRF_COOKIE_SECURE = True CSRF_COOKIE_SECURE = secure_origins +# CSRF_COOKIE_SAMESITE = "None" CSRF_COOKIE_HTTPONLY = False CSRF_TRUSTED_ORIGINS = cors_allowed_origins CSRF_COOKIE_DOMAIN = os.environ.get("COOKIE_DOMAIN", None) diff --git a/apiserver/supervisord.conf b/apiserver/supervisord.conf new file mode 100644 index 00000000000..d4f4eebe3cb --- /dev/null +++ b/apiserver/supervisord.conf @@ -0,0 +1,16 @@ +[supervisord] +nodaemon=true + +[program:celery] +command=celery -A plane worker -l info +autostart=true +autorestart=true +stderr_logfile=/var/log/celery.err.log +stdout_logfile=/var/log/celery.out.log + +[program:flower] +command=celery -A plane flower --port=5555 +autostart=true +autorestart=true +stderr_logfile=/var/log/flower.err.log +stdout_logfile=/var/log/flower.out.log \ No newline at end of file diff --git a/apiserver/templates/emails/exports/analytics.html b/apiserver/templates/emails/exports/analytics.html index d2caa9d7a41..c21d67f8e9e 100644 --- a/apiserver/templates/emails/exports/analytics.html +++ b/apiserver/templates/emails/exports/analytics.html @@ -1,2 +1,7 @@ - - Hey there,
Your requested data export from Plane Analytics is now ready. The information has been compiled into a CSV format for your convenience.
Please find the attachment and download the CSV file. This file can easily be imported into any spreadsheet program for further analysis.
If you require any assistance or have any questions, please do not hesitate to contact us.
Thank you \ No newline at end of file + + Hey there,
Your requested data export from Plane Analytics is now ready. The information has been compiled +into a CSV format for your convenience.
Please find the attachment and download the CSV file. This file can easily +be imported into any spreadsheet program for further analysis.
If you require any assistance or have any +questions, please do not hesitate to contact us.
Thank you + + \ No newline at end of file diff --git a/web/app/provider.tsx b/web/app/provider.tsx index dba975a6373..680cb7023b7 100644 --- a/web/app/provider.tsx +++ b/web/app/provider.tsx @@ -1,9 +1,10 @@ "use client"; -import { FC, ReactNode } from "react"; +import { FC, ReactNode, useEffect } from "react"; import dynamic from "next/dynamic"; import { useTheme, ThemeProvider } from "next-themes"; import { SWRConfig } from "swr"; +import { usePathname, useRouter } from "next/navigation"; // ui import { Toast } from "@plane/ui"; // constants @@ -35,6 +36,26 @@ const ToastWithTheme = () => { export const AppProvider: FC = (props) => { const { children } = props; // themes + + const router = useRouter(); + const pathname = usePathname(); + + useEffect(() => { + // const onIncomingMessage = (event) => { + // console.log('onIncomingMessage', event); + // if (event.data.type === 'ROUTE_CHANGE') { + // console.log(location.pathname, event.data.path); + // router.replace(event.data.path); + // } + // }; + console.log(pathname, 'pathname'); + window.parent.postMessage({ type: "ROUTE_CHANGE", path: pathname }, "*"); + // window.addEventListener('message', onIncomingMessage); + // return () => { + // window.removeEventListener("message", onIncomingMessage); + // }; + }, [pathname]); + return ( <> diff --git a/web/next.config.js b/web/next.config.js index 848f1366bdc..f36c86494ca 100644 --- a/web/next.config.js +++ b/web/next.config.js @@ -18,7 +18,7 @@ const nextConfig = { { key: "X-Frame-Options", value: "ALLOWALL" }, { key: "Content-Security-Policy", - value: "frame-ancestors 'self' https://*.shipsy.io http://*.localhost:3001 http://5.223.42.156:3000/", // Replace with the domains allowed to embed your app + value: "frame-ancestors 'self' https://*.shipsy.io http://*.localhost:3001 http://*.localhost:3002 http://5.223.42.156:3000/", // Replace with the domains allowed to embed your app }, // { // key: "Referrer-Policy", From 442897682cd4ae1e6a83fb84e09e7522feafb18d Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 13 Mar 2025 09:34:16 +0000 Subject: [PATCH 106/120] commented un used variables and imports --- web/app/[workspaceSlug]/(projects)/header.tsx | 16 ++++++++-------- web/app/provider.tsx | 5 ++--- web/app/workspace-invitations/page.tsx | 2 +- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/web/app/[workspaceSlug]/(projects)/header.tsx b/web/app/[workspaceSlug]/(projects)/header.tsx index 6607f3a9383..167d3b6f8d0 100644 --- a/web/app/[workspaceSlug]/(projects)/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/header.tsx @@ -1,24 +1,24 @@ "use client"; -import Image from "next/image"; -import { useTheme } from "next-themes"; +// import Image from "next/image"; +// import { useTheme } from "next-themes"; import { Home } from "lucide-react"; // images -import githubBlackImage from "/public/logos/github-black.png"; -import githubWhiteImage from "/public/logos/github-white.png"; +// import githubBlackImage from "/public/logos/github-black.png"; +// import githubWhiteImage from "/public/logos/github-white.png"; // ui import { Breadcrumbs, Header } from "@plane/ui"; // components import { BreadcrumbLink } from "@/components/common"; // constants -import { GITHUB_REDIRECTED } from "@/constants/event-tracker"; +// import { GITHUB_REDIRECTED } from "@/constants/event-tracker"; // hooks -import { useEventTracker } from "@/hooks/store"; +// import { useEventTracker } from "@/hooks/store"; export const WorkspaceDashboardHeader = () => { // hooks - const { captureEvent } = useEventTracker(); - const { resolvedTheme } = useTheme(); + // const { captureEvent } = useEventTracker(); + // const { resolvedTheme } = useTheme(); return ( <> diff --git a/web/app/provider.tsx b/web/app/provider.tsx index 680cb7023b7..951ff131174 100644 --- a/web/app/provider.tsx +++ b/web/app/provider.tsx @@ -2,10 +2,9 @@ import { FC, ReactNode, useEffect } from "react"; import dynamic from "next/dynamic"; +import { usePathname } from "next/navigation";//ui import { useTheme, ThemeProvider } from "next-themes"; import { SWRConfig } from "swr"; -import { usePathname, useRouter } from "next/navigation"; -// ui import { Toast } from "@plane/ui"; // constants import { SWR_CONFIG } from "@/constants/swr-config"; @@ -37,7 +36,7 @@ export const AppProvider: FC = (props) => { const { children } = props; // themes - const router = useRouter(); + // const router = useRouter(); const pathname = usePathname(); useEffect(() => { diff --git a/web/app/workspace-invitations/page.tsx b/web/app/workspace-invitations/page.tsx index 3e0aafb7202..a715a2f469f 100644 --- a/web/app/workspace-invitations/page.tsx +++ b/web/app/workspace-invitations/page.tsx @@ -4,7 +4,7 @@ import React from "react"; import { observer } from "mobx-react"; import { useSearchParams } from "next/navigation"; import useSWR from "swr"; -import { Boxes, Check, Share2, Star, User2, X } from "lucide-react"; +import { Boxes, Check, Share2, User2, X } from "lucide-react"; // components import { LogoSpinner } from "@/components/common"; import { EmptySpace, EmptySpaceItem } from "@/components/ui/empty-space"; From 51c2e58cd186036e18ed8490292328467bfd541e Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 13 Mar 2025 11:01:05 +0000 Subject: [PATCH 107/120] un commented --- web/app/[workspaceSlug]/(projects)/header.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/app/[workspaceSlug]/(projects)/header.tsx b/web/app/[workspaceSlug]/(projects)/header.tsx index 167d3b6f8d0..0d6a3e451db 100644 --- a/web/app/[workspaceSlug]/(projects)/header.tsx +++ b/web/app/[workspaceSlug]/(projects)/header.tsx @@ -1,7 +1,7 @@ "use client"; // import Image from "next/image"; -// import { useTheme } from "next-themes"; +import { useTheme } from "next-themes"; import { Home } from "lucide-react"; // images // import githubBlackImage from "/public/logos/github-black.png"; @@ -13,12 +13,12 @@ import { BreadcrumbLink } from "@/components/common"; // constants // import { GITHUB_REDIRECTED } from "@/constants/event-tracker"; // hooks -// import { useEventTracker } from "@/hooks/store"; +import { useEventTracker } from "@/hooks/store"; export const WorkspaceDashboardHeader = () => { // hooks - // const { captureEvent } = useEventTracker(); - // const { resolvedTheme } = useTheme(); + const { captureEvent } = useEventTracker(); + const { resolvedTheme } = useTheme(); return ( <> From cd2a8e00f649ebf28ba851ce9e9769f455c6468e Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Mon, 17 Mar 2025 04:29:40 +0000 Subject: [PATCH 108/120] Allowing the project member permission for guests users --- apiserver/plane/app/permissions/project.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apiserver/plane/app/permissions/project.py b/apiserver/plane/app/permissions/project.py index b462e6492e2..8856a7b534c 100644 --- a/apiserver/plane/app/permissions/project.py +++ b/apiserver/plane/app/permissions/project.py @@ -30,7 +30,7 @@ def has_permission(self, request, view): return WorkspaceMember.objects.filter( workspace__slug=view.workspace_slug, member=request.user, - role__in=[Admin, Member], + role__in=[Admin, Member, Guest], is_active=True, ).exists() @@ -65,7 +65,7 @@ def has_permission(self, request, view): return WorkspaceMember.objects.filter( workspace__slug=view.workspace_slug, member=request.user, - role__in=[Admin, Member], + role__in=[Admin, Member, Guest], is_active=True, ).exists() @@ -74,7 +74,7 @@ def has_permission(self, request, view): return ProjectMember.objects.filter( workspace__slug=view.workspace_slug, member=request.user, - role__in=[Admin, Member], + role__in=[Admin, Member, Guest], project_id=view.project_id, is_active=True, ).exists() @@ -110,7 +110,7 @@ def has_permission(self, request, view): return ProjectMember.objects.filter( workspace__slug=view.workspace_slug, member=request.user, - role__in=[Admin, Member], + role__in=[Admin, Member, Guest], project_id=view.project_id, is_active=True, ).exists() From 1fb0c8e71e0b4d78c99cd1f893466e34fcf2d295 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Mon, 17 Mar 2025 09:10:55 +0000 Subject: [PATCH 109/120] Fix for the user's second login issue --- .../plane/authentication/provider/credentials/magic_code.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiserver/plane/authentication/provider/credentials/magic_code.py b/apiserver/plane/authentication/provider/credentials/magic_code.py index a531ca57043..a62650edaa9 100644 --- a/apiserver/plane/authentication/provider/credentials/magic_code.py +++ b/apiserver/plane/authentication/provider/credentials/magic_code.py @@ -147,7 +147,7 @@ def set_user_data(self): "first_name": "", "last_name": "", "provider_id": "", - "is_password_autoset": True, + "is_password_autoset": False, }, } ) From 61a9692f26e0d297457453075ce2c44055a1da9d Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Tue, 18 Mar 2025 09:38:42 +0000 Subject: [PATCH 110/120] fixed filter search issue --- .../filters/header/filters/additional-properties.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/core/components/issues/issue-layouts/filters/header/filters/additional-properties.tsx b/web/core/components/issues/issue-layouts/filters/header/filters/additional-properties.tsx index 62d19bb871b..4921eddb09b 100644 --- a/web/core/components/issues/issue-layouts/filters/header/filters/additional-properties.tsx +++ b/web/core/components/issues/issue-layouts/filters/header/filters/additional-properties.tsx @@ -39,7 +39,7 @@ export const FilterAdditionalProperties: React.FC = observer((props) => { }, [workspaceSlug, projectId, additionalPropertyKey]); const filteredOptions = searchQuery - ? options.filter((label: any) => label.toLowerCase().includes(searchQuery.toLowerCase())) + ? options.filter((option: any) => option?.label.toLowerCase().includes(searchQuery.toLowerCase())) : options; return ( From 78ec6d8b0603e84b49a63b3a7da2302eba60efb2 Mon Sep 17 00:00:00 2001 From: sumarpreet-kaur-shipsy Date: Wed, 19 Mar 2025 09:13:12 +0530 Subject: [PATCH 111/120] Add is required in custom properties --- apiserver/plane/db/models/issue_type.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apiserver/plane/db/models/issue_type.py b/apiserver/plane/db/models/issue_type.py index 619fb8cfd0d..67d5f9dc1dc 100644 --- a/apiserver/plane/db/models/issue_type.py +++ b/apiserver/plane/db/models/issue_type.py @@ -63,6 +63,7 @@ class IssueTypeCustomProperty(BaseModel): name = models.CharField(max_length=255) value = models.JSONField() is_active = models.BooleanField(default=True) + is_required = models.BooleanField(default=False) issue_type = models.ForeignKey( "db.IssueType", on_delete=models.CASCADE, From 621121f8c403a8d1703f573fe0d5fda4db4beac2 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Wed, 19 Mar 2025 16:50:52 +0000 Subject: [PATCH 112/120] fixed profile creation failure --- apiserver/plane/authentication/views/app/magic.py | 1 + 1 file changed, 1 insertion(+) diff --git a/apiserver/plane/authentication/views/app/magic.py b/apiserver/plane/authentication/views/app/magic.py index 8002e34acf1..4181b96a522 100644 --- a/apiserver/plane/authentication/views/app/magic.py +++ b/apiserver/plane/authentication/views/app/magic.py @@ -218,6 +218,7 @@ def post(self, request): callback=post_user_auth_workflow, ) user = provider.authenticate() + profile, _ = Profile.objects.get_or_create(user=user) # Login the user and record his device info user_login(request=request, user=user, is_app=True) self.add_user_to_workspace(user, workspace) From f266562fb7e5f04a821a58b80ce4c0f31007861f Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 20 Mar 2025 09:58:08 +0000 Subject: [PATCH 113/120] Fix for the redirecting to set password page issue --- .../plane/authentication/views/app/magic.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/apiserver/plane/authentication/views/app/magic.py b/apiserver/plane/authentication/views/app/magic.py index 8002e34acf1..4fc49c7aa93 100644 --- a/apiserver/plane/authentication/views/app/magic.py +++ b/apiserver/plane/authentication/views/app/magic.py @@ -118,7 +118,7 @@ def add_to_workspace(self, workspace, user): }) user.profile.is_tour_completed = True user.profile.is_onboarded = True - user.is_password_autoset = True + user.is_password_autoset = False user.profile.company_name = workspace.name user.save() user.profile.save() @@ -243,15 +243,12 @@ def post(self, request): # Login the user and record his device info self.add_user_to_workspace(user, workspace) user_login(request=request, user=user, is_app=True) - if user.is_password_autoset and profile.is_onboarded: - path = "accounts/set-password" - else: - # Get the redirection path - path = ( - str(next_path) - if next_path - else "/" + workspace - ) + # Get the redirection path + path = ( + str(next_path) + if next_path + else "/" + workspace + ) # redirect to referer path url = urljoin(base_host(request=request, is_app=True), path) if app_url: From 3baa08cd02342d349499975974bbf3cf995318e9 Mon Sep 17 00:00:00 2001 From: naman-agrawal-shipsy Date: Thu, 20 Mar 2025 16:49:53 +0530 Subject: [PATCH 114/120] display-property-std -- code --- apiserver/plane/db/models/issue.py | 6 ++ .../spreadsheet/columns/index.ts | 1 + .../columns/standard-property-column.tsx | 19 ++++++ .../spreadsheet/issue-column.tsx | 1 + web/core/constants/issue.ts | 11 ++++ web/core/constants/spreadsheet.ts | 62 +++++++++++++++++++ web/helpers/issue.helper.ts | 6 ++ 7 files changed, 106 insertions(+) create mode 100644 web/core/components/issues/issue-layouts/spreadsheet/columns/standard-property-column.tsx diff --git a/apiserver/plane/db/models/issue.py b/apiserver/plane/db/models/issue.py index 6360d1fa3aa..d454fbef999 100644 --- a/apiserver/plane/db/models/issue.py +++ b/apiserver/plane/db/models/issue.py @@ -76,6 +76,12 @@ def get_default_display_properties(): "state": True, "sub_issue_count": True, "updated_on": True, + "hub_code": True, + "customer_code": True, + "worker_code": True, + "vendor_code": True, + "trip_reference_number": True, + "reference_number": True, } diff --git a/web/core/components/issues/issue-layouts/spreadsheet/columns/index.ts b/web/core/components/issues/issue-layouts/spreadsheet/columns/index.ts index 3439d398b41..eec02b8ca18 100644 --- a/web/core/components/issues/issue-layouts/spreadsheet/columns/index.ts +++ b/web/core/components/issues/issue-layouts/spreadsheet/columns/index.ts @@ -12,3 +12,4 @@ export * from "./sub-issue-column"; export * from "./updated-on-column"; export * from "./module-column"; export * from "./cycle-column"; +export * from "./standard-property-column"; diff --git a/web/core/components/issues/issue-layouts/spreadsheet/columns/standard-property-column.tsx b/web/core/components/issues/issue-layouts/spreadsheet/columns/standard-property-column.tsx new file mode 100644 index 00000000000..6977a6b914c --- /dev/null +++ b/web/core/components/issues/issue-layouts/spreadsheet/columns/standard-property-column.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import { observer } from "mobx-react"; +// types +import { TIssue } from "@plane/types"; +import { Row } from "@plane/ui"; + +type Props = { + issue: TIssue; +}; + +export const SpreadsheetStandardPropertyColumn: React.FC = observer((props) => { + const { issue, property } = props; + + return ( + + {issue?.[property]} + + ); +}); diff --git a/web/core/components/issues/issue-layouts/spreadsheet/issue-column.tsx b/web/core/components/issues/issue-layouts/spreadsheet/issue-column.tsx index dcd8773a0f1..9b8fb27e3db 100644 --- a/web/core/components/issues/issue-layouts/spreadsheet/issue-column.tsx +++ b/web/core/components/issues/issue-layouts/spreadsheet/issue-column.tsx @@ -43,6 +43,7 @@ export const IssueColumn = observer((props: Props) => { > , updates: any) => updateIssue && updateIssue(issue.project_id, issue.id, data).then(() => { diff --git a/web/core/constants/issue.ts b/web/core/constants/issue.ts index 8629c05a253..97fae866e69 100644 --- a/web/core/constants/issue.ts +++ b/web/core/constants/issue.ts @@ -109,6 +109,15 @@ export const ISSUE_FILTER_OPTIONS: { // { key: "draft", title: "Draft Issues" }, ]; +const whileListedCustomProperties = [ + { key: "hub_code", title: "Hub Code" }, + { key: "customer_code", title: "Customer Code" }, + { key: "worker_code", title: "Worker Code" }, + { key: "vendor_code", title: "Vendor Code" }, + { key: "trip_reference_number", title: "Trip Reference Number" }, + { key: "reference_number", title: "Reference Number" }, +]; + export const ISSUE_DISPLAY_PROPERTIES_KEYS: (keyof IIssueDisplayProperties)[] = [ "assignee", "start_date", @@ -126,6 +135,7 @@ export const ISSUE_DISPLAY_PROPERTIES_KEYS: (keyof IIssueDisplayProperties)[] = "modules", "cycle", "issue_type", + ...whileListedCustomProperties.map(prop => prop.key), ]; export const ISSUE_DISPLAY_PROPERTIES: { @@ -146,6 +156,7 @@ export const ISSUE_DISPLAY_PROPERTIES: { { key: "estimate", title: "Estimate" }, { key: "modules", title: "Modules" }, { key: "cycle", title: "Cycle" }, + ...whileListedCustomProperties, ]; export const ISSUE_EXTRA_OPTIONS: { diff --git a/web/core/constants/spreadsheet.ts b/web/core/constants/spreadsheet.ts index e4e8d8ee57b..b78a3bc1cea 100644 --- a/web/core/constants/spreadsheet.ts +++ b/web/core/constants/spreadsheet.ts @@ -11,6 +11,7 @@ import { CalendarCheck2, CalendarClock, Users, + Tags, } from "lucide-react"; // types import { IIssueDisplayProperties, TIssue, TIssueOrderByOptions } from "@plane/types"; @@ -32,6 +33,7 @@ import { SpreadsheetStateColumn, SpreadsheetSubIssueColumn, SpreadsheetUpdatedOnColumn, + SpreadsheetStandardPropertyColumn, } from "@/components/issues/issue-layouts/spreadsheet"; export const SPREADSHEET_PROPERTY_DETAILS: { @@ -176,6 +178,60 @@ export const SPREADSHEET_PROPERTY_DETAILS: { icon: LayersIcon, Column: SpreadsheetSubIssueColumn, }, + hub_code: { + title: "Hub Code", + ascendingOrderKey: "hub_code", + ascendingOrderTitle: "A", + descendingOrderKey: "-hub_code", + descendingOrderTitle: "Z", + icon: DoubleCircleIcon, + Column: SpreadsheetStandardPropertyColumn, + }, + customer_code: { + title: "Customer Code", + ascendingOrderKey: "customer_code", + ascendingOrderTitle: "A", + descendingOrderKey: "-customer_code", + descendingOrderTitle: "Z", + icon: Tags, + Column: SpreadsheetStandardPropertyColumn, + }, + worker_code: { + title: "Worker Code", + ascendingOrderKey: "worker_code", + ascendingOrderTitle: "A", + descendingOrderKey: "-worker_code", + descendingOrderTitle: "Z", + icon: Tags, + Column: SpreadsheetStandardPropertyColumn, + }, + vendor_code: { + title: "Vendor Code", + ascendingOrderKey: "vendor_code", + ascendingOrderTitle: "A", + descendingOrderKey: "-vendor_code", + descendingOrderTitle: "Z", + icon: Tags, + Column: SpreadsheetStandardPropertyColumn, + }, + trip_reference_number: { + title: "Trip Reference Number", + ascendingOrderKey: "trip_reference_number", + ascendingOrderTitle: "A", + descendingOrderKey: "-trip_reference_number", + descendingOrderTitle: "Z", + icon: Tags, + Column: SpreadsheetStandardPropertyColumn, + }, + reference_number: { + title: "Reference Number", + ascendingOrderKey: "reference_number", + ascendingOrderTitle: "A", + descendingOrderKey: "-reference_number", + descendingOrderTitle: "Z", + icon: Tags, + Column: SpreadsheetStandardPropertyColumn, + }, }; export const SPREADSHEET_PROPERTY_LIST: (keyof IIssueDisplayProperties)[] = [ @@ -193,6 +249,12 @@ export const SPREADSHEET_PROPERTY_LIST: (keyof IIssueDisplayProperties)[] = [ "link", "attachment_count", "sub_issue_count", + "hub_code", + "customer_code", + "worker_code", + "vendor_code", + "trip_reference_number", + "reference_number", ]; export const SPREADSHEET_SELECT_GROUP = "spreadsheet-issues"; diff --git a/web/helpers/issue.helper.ts b/web/helpers/issue.helper.ts index d1fc970185e..ca9d6afd681 100644 --- a/web/helpers/issue.helper.ts +++ b/web/helpers/issue.helper.ts @@ -307,4 +307,10 @@ export const getComputedDisplayProperties = ( modules: displayProperties?.modules ?? true, cycle: displayProperties?.cycle ?? true, issue_type: displayProperties?.issue_type ?? true, + hub_code: displayProperties?.hub_code ?? true, + customer_code: displayProperties?.customer_code ?? true, + worker_code: displayProperties?.worker_code ?? true, + vendor_code: displayProperties?.vendor_code ?? true, + trip_reference_number: displayProperties?.trip_reference_number ?? true, + reference_number: displayProperties?.reference_number ?? true, }); From cf27a0f1403028f5f061a8b82c54877d1eae6fc9 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 20 Mar 2025 12:23:17 +0000 Subject: [PATCH 115/120] style --- .../spreadsheet/columns/standard-property-column.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/core/components/issues/issue-layouts/spreadsheet/columns/standard-property-column.tsx b/web/core/components/issues/issue-layouts/spreadsheet/columns/standard-property-column.tsx index 6977a6b914c..138c7916172 100644 --- a/web/core/components/issues/issue-layouts/spreadsheet/columns/standard-property-column.tsx +++ b/web/core/components/issues/issue-layouts/spreadsheet/columns/standard-property-column.tsx @@ -12,7 +12,7 @@ export const SpreadsheetStandardPropertyColumn: React.FC = observer((prop const { issue, property } = props; return ( - + {issue?.[property]} ); From caf9cf8e6ad0ac93532c11f07da7238985855065 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Thu, 20 Mar 2025 09:58:08 +0000 Subject: [PATCH 116/120] Fix for the redirecting to set password page issue --- .../plane/authentication/views/app/magic.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/apiserver/plane/authentication/views/app/magic.py b/apiserver/plane/authentication/views/app/magic.py index 4181b96a522..99c686eb0c7 100644 --- a/apiserver/plane/authentication/views/app/magic.py +++ b/apiserver/plane/authentication/views/app/magic.py @@ -118,7 +118,7 @@ def add_to_workspace(self, workspace, user): }) user.profile.is_tour_completed = True user.profile.is_onboarded = True - user.is_password_autoset = True + user.is_password_autoset = False user.profile.company_name = workspace.name user.save() user.profile.save() @@ -244,15 +244,12 @@ def post(self, request): # Login the user and record his device info self.add_user_to_workspace(user, workspace) user_login(request=request, user=user, is_app=True) - if user.is_password_autoset and profile.is_onboarded: - path = "accounts/set-password" - else: - # Get the redirection path - path = ( - str(next_path) - if next_path - else "/" + workspace - ) + # Get the redirection path + path = ( + str(next_path) + if next_path + else "/" + workspace + ) # redirect to referer path url = urljoin(base_host(request=request, is_app=True), path) if app_url: From b82176c61c4cc86131c88a2db801fa4b7bad1a48 Mon Sep 17 00:00:00 2001 From: naman-agrawal-shipsy Date: Thu, 20 Mar 2025 18:20:36 +0530 Subject: [PATCH 117/120] changes in custom prop column component --- .../spreadsheet/columns/standard-property-column.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/web/core/components/issues/issue-layouts/spreadsheet/columns/standard-property-column.tsx b/web/core/components/issues/issue-layouts/spreadsheet/columns/standard-property-column.tsx index 6977a6b914c..ae3ee6e4231 100644 --- a/web/core/components/issues/issue-layouts/spreadsheet/columns/standard-property-column.tsx +++ b/web/core/components/issues/issue-layouts/spreadsheet/columns/standard-property-column.tsx @@ -2,7 +2,7 @@ import React from "react"; import { observer } from "mobx-react"; // types import { TIssue } from "@plane/types"; -import { Row } from "@plane/ui"; +import { Row, Tooltip } from "@plane/ui"; type Props = { issue: TIssue; @@ -12,8 +12,10 @@ export const SpreadsheetStandardPropertyColumn: React.FC = observer((prop const { issue, property } = props; return ( - - {issue?.[property]} - + + + {issue?.[property]} + + ); }); From 6eeb38d682e60a3417fa8f247a64dece00a1c290 Mon Sep 17 00:00:00 2001 From: naman-agrawal-shipsy Date: Fri, 21 Mar 2025 12:54:41 +0530 Subject: [PATCH 118/120] hub_code icon change --- web/core/constants/spreadsheet.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/core/constants/spreadsheet.ts b/web/core/constants/spreadsheet.ts index b78a3bc1cea..76dba528bb5 100644 --- a/web/core/constants/spreadsheet.ts +++ b/web/core/constants/spreadsheet.ts @@ -184,7 +184,7 @@ export const SPREADSHEET_PROPERTY_DETAILS: { ascendingOrderTitle: "A", descendingOrderKey: "-hub_code", descendingOrderTitle: "Z", - icon: DoubleCircleIcon, + icon: Tags, Column: SpreadsheetStandardPropertyColumn, }, customer_code: { From 2b96095dfdc1b556575fa9d4af80344341c6b811 Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Fri, 21 Mar 2025 07:36:22 +0000 Subject: [PATCH 119/120] hide tooltip when no content --- .../spreadsheet/columns/standard-property-column.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/core/components/issues/issue-layouts/spreadsheet/columns/standard-property-column.tsx b/web/core/components/issues/issue-layouts/spreadsheet/columns/standard-property-column.tsx index ae3ee6e4231..4974f6c38e2 100644 --- a/web/core/components/issues/issue-layouts/spreadsheet/columns/standard-property-column.tsx +++ b/web/core/components/issues/issue-layouts/spreadsheet/columns/standard-property-column.tsx @@ -12,7 +12,7 @@ export const SpreadsheetStandardPropertyColumn: React.FC = observer((prop const { issue, property } = props; return ( - + {issue?.[property]} From bb685174bd7ef761abbd4b13980e33044669973d Mon Sep 17 00:00:00 2001 From: abu-siddique-shipsy Date: Fri, 21 Mar 2025 07:39:19 +0000 Subject: [PATCH 120/120] small-fix --- web/core/constants/spreadsheet.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/core/constants/spreadsheet.ts b/web/core/constants/spreadsheet.ts index 76dba528bb5..14e68aba4be 100644 --- a/web/core/constants/spreadsheet.ts +++ b/web/core/constants/spreadsheet.ts @@ -215,7 +215,7 @@ export const SPREADSHEET_PROPERTY_DETAILS: { Column: SpreadsheetStandardPropertyColumn, }, trip_reference_number: { - title: "Trip Reference Number", + title: "Trip Ref Number", ascendingOrderKey: "trip_reference_number", ascendingOrderTitle: "A", descendingOrderKey: "-trip_reference_number",