diff --git a/.env.example b/.env.example index 166fbb9b7..fc1aef49d 100644 --- a/.env.example +++ b/.env.example @@ -21,10 +21,12 @@ NEXT_PUBLIC_TRACK_EVENTS=0 NEXT_PUBLIC_SLACK_CLIENT_ID="" # Backend - # Debug value for api server use it as 0 for production use DEBUG=0 +# Error logs +SENTRY_DSN="" + # Database Settings PGUSER="plane" PGPASSWORD="plane" diff --git a/apiserver/plane/api/views/inbox.py b/apiserver/plane/api/views/inbox.py index 738c6ef9f..0e4c1603e 100644 --- a/apiserver/plane/api/views/inbox.py +++ b/apiserver/plane/api/views/inbox.py @@ -22,7 +22,7 @@ from plane.db.models import ( State, IssueLink, IssueAttachment, - IssueActivity, + ProjectMember, ) from plane.api.serializers import ( IssueSerializer, @@ -246,13 +246,28 @@ class InboxIssueViewSet(BaseViewSet): inbox_issue = InboxIssue.objects.get( pk=pk, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id ) + # Get the project member + project_member = ProjectMember.objects.get(workspace__slug=slug, project_id=project_id, member=request.user) + # Only project members admins and created_by users can access this endpoint + if project_member.role <= 10 and str(inbox_issue.created_by_id) != str(request.user.id): + return Response({"error": "You cannot edit inbox issues"}, status=status.HTTP_400_BAD_REQUEST) + # Get issue data issue_data = request.data.pop("issue", False) if bool(issue_data): issue = Issue.objects.get( pk=inbox_issue.issue_id, workspace__slug=slug, project_id=project_id ) + # Only allow guests and viewers to edit name and description + if project_member <= 10: + # viewers and guests since only viewers and guests + issue_data = { + "name": issue_data.get("name", issue.name), + "description_html": issue_data.get("description_html", issue.description_html), + "description": issue_data.get("description", issue.description) + } + issue_serializer = IssueCreateSerializer( issue, data=issue_data, partial=True ) @@ -279,46 +294,50 @@ class InboxIssueViewSet(BaseViewSet): issue_serializer.errors, status=status.HTTP_400_BAD_REQUEST ) - serializer = InboxIssueSerializer( - inbox_issue, data=request.data, partial=True - ) + # Only project admins and members can edit inbox issue attributes + if project_member.role > 10: + serializer = InboxIssueSerializer( + inbox_issue, data=request.data, partial=True + ) - if serializer.is_valid(): - serializer.save() - # Update the issue state if the issue is rejected or marked as duplicate - if serializer.data["status"] in [-1, 2]: - issue = Issue.objects.get( - pk=inbox_issue.issue_id, - workspace__slug=slug, - project_id=project_id, - ) - state = State.objects.filter( - group="cancelled", workspace__slug=slug, project_id=project_id - ).first() - if state is not None: - issue.state = state - issue.save() - - # Update the issue state if it is accepted - if serializer.data["status"] in [1]: - issue = Issue.objects.get( - pk=inbox_issue.issue_id, - workspace__slug=slug, - project_id=project_id, - ) - - # Update the issue state only if it is in triage state - if issue.state.name == "Triage": - # Move to default state + if serializer.is_valid(): + serializer.save() + # Update the issue state if the issue is rejected or marked as duplicate + if serializer.data["status"] in [-1, 2]: + issue = Issue.objects.get( + pk=inbox_issue.issue_id, + workspace__slug=slug, + project_id=project_id, + ) state = State.objects.filter( - workspace__slug=slug, project_id=project_id, default=True + group="cancelled", workspace__slug=slug, project_id=project_id ).first() if state is not None: issue.state = state issue.save() - return Response(serializer.data, status=status.HTTP_200_OK) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + # Update the issue state if it is accepted + if serializer.data["status"] in [1]: + issue = Issue.objects.get( + pk=inbox_issue.issue_id, + workspace__slug=slug, + project_id=project_id, + ) + + # Update the issue state only if it is in triage state + if issue.state.name == "Triage": + # Move to default state + state = State.objects.filter( + workspace__slug=slug, project_id=project_id, default=True + ).first() + if state is not None: + issue.state = state + issue.save() + + return Response(serializer.data, status=status.HTTP_200_OK) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + else: + return Response(InboxIssueSerializer(inbox_issue).data, status=status.HTTP_200_OK) except InboxIssue.DoesNotExist: return Response( {"error": "Inbox Issue does not exist"}, @@ -347,3 +366,25 @@ class InboxIssueViewSet(BaseViewSet): {"error": "Something went wrong please try again later"}, status=status.HTTP_400_BAD_REQUEST, ) + + def destroy(self, request, slug, project_id, inbox_id, pk): + try: + inbox_issue = InboxIssue.objects.get( + pk=pk, workspace__slug=slug, project_id=project_id, inbox_id=inbox_id + ) + # Get the project member + project_member = ProjectMember.objects.get(workspace__slug=slug, project_id=project_id, member=request.user) + + if project_member.role <= 10 and str(inbox_issue.created_by_id) != str(request.user.id): + return Response({"error": "You cannot delete inbox issue"}, status=status.HTTP_400_BAD_REQUEST) + + inbox_issue.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + except InboxIssue.DoesNotExist: + return Response({"error": "Inbox Issue does not exists"}, status=status.HTTP_400_BAD_REQUEST) + except Exception as e: + capture_exception(e) + return Response( + {"error": "Something went wrong please try again later"}, + status=status.HTTP_400_BAD_REQUEST, + ) \ No newline at end of file diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index 5a08b0f0c..81f7889b5 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -592,6 +592,20 @@ class SubIssuesEndpoint(BaseAPIView): .annotate(count=Func(F("id"), function="Count")) .values("count") ) + .annotate( + link_count=IssueLink.objects.filter(issue=OuterRef("id")) + .order_by() + .annotate(count=Func(F("id"), function="Count")) + .values("count") + ) + .annotate( + attachment_count=IssueAttachment.objects.filter( + issue=OuterRef("id") + ) + .order_by() + .annotate(count=Func(F("id"), function="Count")) + .values("count") + ) ) state_distribution = ( diff --git a/apiserver/plane/api/views/people.py b/apiserver/plane/api/views/people.py index 9dad5380b..8e19fea1a 100644 --- a/apiserver/plane/api/views/people.py +++ b/apiserver/plane/api/views/people.py @@ -98,20 +98,6 @@ class UpdateUserOnBoardedEndpoint(BaseAPIView): user = User.objects.get(pk=request.user.id) user.is_onboarded = request.data.get("is_onboarded", False) user.save() - - if user.last_workspace_id is not None: - user_role = WorkspaceMember.objects.filter( - workspace_id=user.last_workspace_id, member=request.user.id - ).first() - return Response( - { - "message": "Updated successfully", - "role": user_role.company_role - if user_role is not None - else None, - }, - status=status.HTTP_200_OK, - ) return Response( {"message": "Updated successfully"}, status=status.HTTP_200_OK ) diff --git a/apiserver/plane/bgtasks/analytic_plot_export.py b/apiserver/plane/bgtasks/analytic_plot_export.py index 37362416f..27b625445 100644 --- a/apiserver/plane/bgtasks/analytic_plot_export.py +++ b/apiserver/plane/bgtasks/analytic_plot_export.py @@ -169,6 +169,8 @@ def analytic_export_task(email, data, slug): msg.send(fail_silently=False) except Exception as e: - print(e) + # Print logs if in DEBUG mode + if settings.DEBUG: + print(e) capture_exception(e) return diff --git a/apiserver/plane/bgtasks/email_verification_task.py b/apiserver/plane/bgtasks/email_verification_task.py index 89551044b..93b15c425 100644 --- a/apiserver/plane/bgtasks/email_verification_task.py +++ b/apiserver/plane/bgtasks/email_verification_task.py @@ -39,5 +39,8 @@ def email_verification(first_name, email, token, current_site): msg.send() return except Exception as e: + # Print logs if in DEBUG mode + if settings.DEBUG: + print(e) capture_exception(e) return diff --git a/apiserver/plane/bgtasks/forgot_password_task.py b/apiserver/plane/bgtasks/forgot_password_task.py index 687e4f976..93283dfd5 100644 --- a/apiserver/plane/bgtasks/forgot_password_task.py +++ b/apiserver/plane/bgtasks/forgot_password_task.py @@ -37,5 +37,8 @@ def forgot_password(first_name, email, uidb64, token, current_site): msg.send() return except Exception as e: + # Print logs if in DEBUG mode + if settings.DEBUG: + print(e) capture_exception(e) return diff --git a/apiserver/plane/bgtasks/importer_task.py b/apiserver/plane/bgtasks/importer_task.py index 85ac1c89b..757ef601b 100644 --- a/apiserver/plane/bgtasks/importer_task.py +++ b/apiserver/plane/bgtasks/importer_task.py @@ -175,5 +175,8 @@ def service_importer(service, importer_id): importer = Importer.objects.get(pk=importer_id) importer.status = "failed" importer.save() + # Print logs if in DEBUG mode + if settings.DEBUG: + print(e) capture_exception(e) return diff --git a/apiserver/plane/bgtasks/issue_activites_task.py b/apiserver/plane/bgtasks/issue_activites_task.py index 9fa0d2fac..9e4f9475f 100644 --- a/apiserver/plane/bgtasks/issue_activites_task.py +++ b/apiserver/plane/bgtasks/issue_activites_task.py @@ -1047,5 +1047,8 @@ def issue_activity( return except Exception as e: + # Print logs if in DEBUG mode + if settings.DEBUG: + print(e) capture_exception(e) return diff --git a/apiserver/plane/bgtasks/magic_link_code_task.py b/apiserver/plane/bgtasks/magic_link_code_task.py index 29851c435..91cc461bb 100644 --- a/apiserver/plane/bgtasks/magic_link_code_task.py +++ b/apiserver/plane/bgtasks/magic_link_code_task.py @@ -31,4 +31,7 @@ def magic_link(email, key, token, current_site): return except Exception as e: capture_exception(e) + # Print logs if in DEBUG mode + if settings.DEBUG: + print(e) return diff --git a/apiserver/plane/bgtasks/project_invitation_task.py b/apiserver/plane/bgtasks/project_invitation_task.py index 7f1125f80..8b8ef6e48 100644 --- a/apiserver/plane/bgtasks/project_invitation_task.py +++ b/apiserver/plane/bgtasks/project_invitation_task.py @@ -50,5 +50,8 @@ def project_invitation(email, project_id, token, current_site): except (Project.DoesNotExist, ProjectMemberInvite.DoesNotExist) as e: return except Exception as e: + # Print logs if in DEBUG mode + if settings.DEBUG: + print(e) capture_exception(e) return diff --git a/apiserver/plane/bgtasks/user_welcome_task.py b/apiserver/plane/bgtasks/user_welcome_task.py index bea2ee33d..33f4b5686 100644 --- a/apiserver/plane/bgtasks/user_welcome_task.py +++ b/apiserver/plane/bgtasks/user_welcome_task.py @@ -29,5 +29,8 @@ def send_welcome_slack(user_id, created, message): print(f"Got an error: {e.response['error']}") return except Exception as e: + # Print logs if in DEBUG mode + if settings.DEBUG: + print(e) capture_exception(e) return diff --git a/apiserver/plane/bgtasks/workspace_invitation_task.py b/apiserver/plane/bgtasks/workspace_invitation_task.py index 7b2bada0a..d84a0b414 100644 --- a/apiserver/plane/bgtasks/workspace_invitation_task.py +++ b/apiserver/plane/bgtasks/workspace_invitation_task.py @@ -66,5 +66,8 @@ def workspace_invitation(email, workspace_id, token, current_site, invitor): except (Workspace.DoesNotExist, WorkspaceMemberInvite.DoesNotExist) as e: return except Exception as e: + # Print logs if in DEBUG mode + if settings.DEBUG: + print(e) capture_exception(e) return diff --git a/apps/app/components/inbox/filters-list.tsx b/apps/app/components/inbox/filters-list.tsx index aa8213b2f..264b925a2 100644 --- a/apps/app/components/inbox/filters-list.tsx +++ b/apps/app/components/inbox/filters-list.tsx @@ -98,7 +98,7 @@ export const InboxFiltersList = () => { type="button" onClick={() => setFilters({ - priority: null, + inbox_status: null, }) } > diff --git a/docker-compose-hub.yml b/docker-compose-hub.yml index 8087bd427..63f196300 100644 --- a/docker-compose-hub.yml +++ b/docker-compose-hub.yml @@ -28,6 +28,8 @@ services: env_file: - .env environment: + DEBUG: ${DEBUG} + SENTRY_DSN: ${SENTRY_DSN} DJANGO_SETTINGS_MODULE: plane.settings.production DATABASE_URL: postgres://${PGUSER}:${PGPASSWORD}@${PGHOST}:5432/${PGDATABASE} REDIS_URL: redis://plane-redis:6379/ @@ -52,7 +54,6 @@ services: DEFAULT_EMAIL: ${DEFAULT_EMAIL} DEFAULT_PASSWORD: ${DEFAULT_PASSWORD} USE_MINIO: ${USE_MINIO} - DEBUG: ${DEBUG} depends_on: - plane-db - plane-redis @@ -65,6 +66,8 @@ services: env_file: - .env environment: + DEBUG: ${DEBUG} + SENTRY_DSN: ${SENTRY_DSN} DJANGO_SETTINGS_MODULE: plane.settings.production DATABASE_URL: postgres://${PGUSER}:${PGPASSWORD}@${PGHOST}:5432/${PGDATABASE} REDIS_URL: redis://plane-redis:6379/ @@ -89,7 +92,6 @@ services: DEFAULT_EMAIL: ${DEFAULT_EMAIL} DEFAULT_PASSWORD: ${DEFAULT_PASSWORD} USE_MINIO: ${USE_MINIO} - DEBUG: ${DEBUG} depends_on: - plane-api - plane-db diff --git a/docker-compose.yml b/docker-compose.yml index bacfe5cb2..640bb723e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -38,6 +38,7 @@ services: - .env environment: DEBUG: ${DEBUG} + SENTRY_DSN: ${SENTRY_DSN} DJANGO_SETTINGS_MODULE: plane.settings.production DATABASE_URL: postgres://${PGUSER}:${PGPASSWORD}@${PGHOST}:5432/${PGDATABASE} REDIS_URL: redis://plane-redis:6379/ @@ -80,6 +81,7 @@ services: - .env environment: DEBUG: ${DEBUG} + SENTRY_DSN: ${SENTRY_DSN} DJANGO_SETTINGS_MODULE: plane.settings.production DATABASE_URL: postgres://${PGUSER}:${PGPASSWORD}@${PGHOST}:5432/${PGDATABASE} REDIS_URL: redis://plane-redis:6379/