Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 18865b46fe | |||
| 2d60337eac | |||
| f3ac26e5c9 | |||
| d5a55de17a | |||
| 6f497b024b | |||
| a3e8ee6045 | |||
| c1ac6e4244 | |||
| 6d98619082 | |||
| 52d3169542 | |||
| 5989b1a134 | |||
| 291bb5c899 | |||
| 2ef00efaab | |||
| c5f96466e9 | |||
| 35938b57af | |||
| 1b1b160c04 | |||
| 4149e84e62 | |||
| 9408e92e44 | |||
| e9680cab74 | |||
| 229610513a | |||
| f9d9c92c83 | |||
| 89588d4451 | |||
| 3eb911837c | |||
| 4b50b27a74 | |||
| f44db89f41 | |||
| 8c3189e1be | |||
| eee2145734 | |||
| 106710f3d0 | |||
| db8c4f92e8 | |||
| a6cc2c93f8 | |||
| 0428ea06f6 | |||
| 7082f7014d | |||
| c7c729d81b | |||
| 97eb8d43d4 | |||
| 1217af1d5f | |||
| 13083a77eb | |||
| 0cd36b854e | |||
| 1d314dd25f | |||
| 1743717351 | |||
| acba451803 | |||
| 2193e8c79c | |||
| 4c6ab984c3 | |||
| 7574206a41 | |||
| eebc327b10 |
@@ -83,7 +83,7 @@ jobs:
|
||||
endpoint: ${{ env.BUILDX_ENDPOINT }}
|
||||
|
||||
- name: Build and Push to Docker Hub
|
||||
uses: docker/build-push-action@v5.1.0
|
||||
uses: docker/build-push-action@v6.9.0
|
||||
with:
|
||||
context: ./aio
|
||||
file: ./aio/Dockerfile-base-full
|
||||
@@ -124,7 +124,7 @@ jobs:
|
||||
endpoint: ${{ env.BUILDX_ENDPOINT }}
|
||||
|
||||
- name: Build and Push to Docker Hub
|
||||
uses: docker/build-push-action@v5.1.0
|
||||
uses: docker/build-push-action@v6.9.0
|
||||
with:
|
||||
context: ./aio
|
||||
file: ./aio/Dockerfile-base-slim
|
||||
|
||||
@@ -128,7 +128,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build and Push to Docker Hub
|
||||
uses: docker/build-push-action@v5.1.0
|
||||
uses: docker/build-push-action@v6.9.0
|
||||
with:
|
||||
context: .
|
||||
file: ./aio/Dockerfile-app
|
||||
@@ -188,7 +188,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build and Push to Docker Hub
|
||||
uses: docker/build-push-action@v5.1.0
|
||||
uses: docker/build-push-action@v6.9.0
|
||||
with:
|
||||
context: .
|
||||
file: ./aio/Dockerfile-app
|
||||
|
||||
@@ -367,7 +367,7 @@ jobs:
|
||||
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: softprops/action-gh-release@v2.0.8
|
||||
uses: softprops/action-gh-release@v2.1.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
|
||||
with:
|
||||
|
||||
@@ -29,11 +29,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
@@ -59,6 +59,6 @@ jobs:
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build and Push to Docker Hub
|
||||
uses: docker/build-push-action@v5.1.0
|
||||
uses: docker/build-push-action@v6.9.0
|
||||
with:
|
||||
context: .
|
||||
file: ./aio/Dockerfile-app
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4.1.1
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Fetch all history for all branches and tags
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4.1.1
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -18,4 +18,4 @@ from .module import (
|
||||
ModuleIssueSerializer,
|
||||
ModuleLiteSerializer,
|
||||
)
|
||||
from .inbox import InboxIssueSerializer
|
||||
from .intake import IntakeIssueSerializer
|
||||
|
||||
+5
-3
@@ -1,15 +1,17 @@
|
||||
# Module improts
|
||||
from .base import BaseSerializer
|
||||
from .issue import IssueExpandSerializer
|
||||
from plane.db.models import InboxIssue
|
||||
from plane.db.models import IntakeIssue
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
class InboxIssueSerializer(BaseSerializer):
|
||||
class IntakeIssueSerializer(BaseSerializer):
|
||||
|
||||
issue_detail = IssueExpandSerializer(read_only=True, source="issue")
|
||||
inbox = serializers.UUIDField(source="intake.id", read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = InboxIssue
|
||||
model = IntakeIssue
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"id",
|
||||
@@ -20,6 +20,7 @@ class ProjectSerializer(BaseSerializer):
|
||||
member_role = serializers.IntegerField(read_only=True)
|
||||
is_deployed = serializers.BooleanField(read_only=True)
|
||||
cover_image_url = serializers.CharField(read_only=True)
|
||||
inbox_view = serializers.BooleanField(read_only=True, source="intake_view")
|
||||
|
||||
class Meta:
|
||||
model = Project
|
||||
|
||||
@@ -3,7 +3,7 @@ from .state import urlpatterns as state_patterns
|
||||
from .issue import urlpatterns as issue_patterns
|
||||
from .cycle import urlpatterns as cycle_patterns
|
||||
from .module import urlpatterns as module_patterns
|
||||
from .inbox import urlpatterns as inbox_patterns
|
||||
from .intake import urlpatterns as intake_patterns
|
||||
from .member import urlpatterns as member_patterns
|
||||
|
||||
urlpatterns = [
|
||||
@@ -12,6 +12,6 @@ urlpatterns = [
|
||||
*issue_patterns,
|
||||
*cycle_patterns,
|
||||
*module_patterns,
|
||||
*inbox_patterns,
|
||||
*intake_patterns,
|
||||
*member_patterns,
|
||||
]
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
from django.urls import path
|
||||
|
||||
from plane.api.views import InboxIssueAPIEndpoint
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/inbox-issues/",
|
||||
InboxIssueAPIEndpoint.as_view(),
|
||||
name="inbox-issue",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/inbox-issues/<uuid:issue_id>/",
|
||||
InboxIssueAPIEndpoint.as_view(),
|
||||
name="inbox-issue",
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,27 @@
|
||||
from django.urls import path
|
||||
|
||||
from plane.api.views import IntakeIssueAPIEndpoint
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/inbox-issues/",
|
||||
IntakeIssueAPIEndpoint.as_view(),
|
||||
name="inbox-issue",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/inbox-issues/<uuid:issue_id>/",
|
||||
IntakeIssueAPIEndpoint.as_view(),
|
||||
name="inbox-issue",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/intake-issues/",
|
||||
IntakeIssueAPIEndpoint.as_view(),
|
||||
name="intake-issue",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/intake-issues/<uuid:issue_id>/",
|
||||
IntakeIssueAPIEndpoint.as_view(),
|
||||
name="intake-issue",
|
||||
),
|
||||
]
|
||||
@@ -27,5 +27,4 @@ from .module import (
|
||||
|
||||
from .member import ProjectMemberAPIEndpoint
|
||||
|
||||
from .inbox import InboxIssueAPIEndpoint
|
||||
|
||||
from .intake import IntakeIssueAPIEndpoint
|
||||
|
||||
@@ -14,12 +14,12 @@ from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
|
||||
# Module imports
|
||||
from plane.api.serializers import InboxIssueSerializer, IssueSerializer
|
||||
from plane.api.serializers import IntakeIssueSerializer, IssueSerializer
|
||||
from plane.app.permissions import ProjectLitePermission
|
||||
from plane.bgtasks.issue_activities_task import issue_activity
|
||||
from plane.db.models import (
|
||||
Inbox,
|
||||
InboxIssue,
|
||||
Intake,
|
||||
IntakeIssue,
|
||||
Issue,
|
||||
Project,
|
||||
ProjectMember,
|
||||
@@ -29,10 +29,10 @@ from plane.db.models import (
|
||||
from .base import BaseAPIView
|
||||
|
||||
|
||||
class InboxIssueAPIEndpoint(BaseAPIView):
|
||||
class IntakeIssueAPIEndpoint(BaseAPIView):
|
||||
"""
|
||||
This viewset automatically provides `list`, `create`, `retrieve`,
|
||||
`update` and `destroy` actions related to inbox issues.
|
||||
`update` and `destroy` actions related to intake issues.
|
||||
|
||||
"""
|
||||
|
||||
@@ -40,15 +40,15 @@ class InboxIssueAPIEndpoint(BaseAPIView):
|
||||
ProjectLitePermission,
|
||||
]
|
||||
|
||||
serializer_class = InboxIssueSerializer
|
||||
model = InboxIssue
|
||||
serializer_class = IntakeIssueSerializer
|
||||
model = IntakeIssue
|
||||
|
||||
filterset_fields = [
|
||||
"status",
|
||||
]
|
||||
|
||||
def get_queryset(self):
|
||||
inbox = Inbox.objects.filter(
|
||||
intake = Intake.objects.filter(
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
project_id=self.kwargs.get("project_id"),
|
||||
).first()
|
||||
@@ -58,16 +58,16 @@ class InboxIssueAPIEndpoint(BaseAPIView):
|
||||
pk=self.kwargs.get("project_id"),
|
||||
)
|
||||
|
||||
if inbox is None and not project.inbox_view:
|
||||
return InboxIssue.objects.none()
|
||||
if intake is None and not project.intake_view:
|
||||
return IntakeIssue.objects.none()
|
||||
|
||||
return (
|
||||
InboxIssue.objects.filter(
|
||||
IntakeIssue.objects.filter(
|
||||
Q(snoozed_till__gte=timezone.now())
|
||||
| Q(snoozed_till__isnull=True),
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
project_id=self.kwargs.get("project_id"),
|
||||
inbox_id=inbox.id,
|
||||
intake_id=intake.id,
|
||||
)
|
||||
.select_related("issue", "workspace", "project")
|
||||
.order_by(self.kwargs.get("order_by", "-created_at"))
|
||||
@@ -75,22 +75,22 @@ class InboxIssueAPIEndpoint(BaseAPIView):
|
||||
|
||||
def get(self, request, slug, project_id, issue_id=None):
|
||||
if issue_id:
|
||||
inbox_issue_queryset = self.get_queryset().get(issue_id=issue_id)
|
||||
inbox_issue_data = InboxIssueSerializer(
|
||||
inbox_issue_queryset,
|
||||
intake_issue_queryset = self.get_queryset().get(issue_id=issue_id)
|
||||
intake_issue_data = IntakeIssueSerializer(
|
||||
intake_issue_queryset,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
).data
|
||||
return Response(
|
||||
inbox_issue_data,
|
||||
intake_issue_data,
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
issue_queryset = self.get_queryset()
|
||||
return self.paginate(
|
||||
request=request,
|
||||
queryset=(issue_queryset),
|
||||
on_results=lambda inbox_issues: InboxIssueSerializer(
|
||||
inbox_issues,
|
||||
on_results=lambda intake_issues: IntakeIssueSerializer(
|
||||
intake_issues,
|
||||
many=True,
|
||||
fields=self.fields,
|
||||
expand=self.expand,
|
||||
@@ -104,7 +104,7 @@ class InboxIssueAPIEndpoint(BaseAPIView):
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
inbox = Inbox.objects.filter(
|
||||
intake = Intake.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
).first()
|
||||
|
||||
@@ -113,11 +113,11 @@ class InboxIssueAPIEndpoint(BaseAPIView):
|
||||
pk=project_id,
|
||||
)
|
||||
|
||||
# Inbox view
|
||||
if inbox is None and not project.inbox_view:
|
||||
# Intake view
|
||||
if intake is None and not project.intake_view:
|
||||
return Response(
|
||||
{
|
||||
"error": "Inbox is not enabled for this project enable it through the project's api"
|
||||
"error": "Intake is not enabled for this project enable it through the project's api"
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
@@ -139,7 +139,7 @@ class InboxIssueAPIEndpoint(BaseAPIView):
|
||||
state, _ = State.objects.get_or_create(
|
||||
name="Triage",
|
||||
group="triage",
|
||||
description="Default state for managing all Inbox Issues",
|
||||
description="Default state for managing all Intake Issues",
|
||||
project_id=project_id,
|
||||
color="#ff7700",
|
||||
is_triage=True,
|
||||
@@ -157,12 +157,12 @@ class InboxIssueAPIEndpoint(BaseAPIView):
|
||||
state=state,
|
||||
)
|
||||
|
||||
# create an inbox issue
|
||||
inbox_issue = InboxIssue.objects.create(
|
||||
inbox_id=inbox.id,
|
||||
# create an intake issue
|
||||
intake_issue = IntakeIssue.objects.create(
|
||||
intake_id=intake.id,
|
||||
project_id=project_id,
|
||||
issue=issue,
|
||||
source=request.data.get("source", "in-app"),
|
||||
source=request.data.get("source", "IN-APP"),
|
||||
)
|
||||
# Create an Issue Activity
|
||||
issue_activity.delay(
|
||||
@@ -173,32 +173,37 @@ class InboxIssueAPIEndpoint(BaseAPIView):
|
||||
project_id=str(project_id),
|
||||
current_instance=None,
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
inbox=str(inbox_issue.id),
|
||||
intake=str(intake_issue.id),
|
||||
)
|
||||
|
||||
serializer = InboxIssueSerializer(inbox_issue)
|
||||
serializer = IntakeIssueSerializer(intake_issue)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
def patch(self, request, slug, project_id, issue_id):
|
||||
inbox = Inbox.objects.filter(
|
||||
intake = Intake.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
).first()
|
||||
|
||||
# Inbox view
|
||||
if inbox is None:
|
||||
project = Project.objects.get(
|
||||
workspace__slug=slug,
|
||||
pk=project_id,
|
||||
)
|
||||
|
||||
# Intake view
|
||||
if intake is None and not project.intake_view:
|
||||
return Response(
|
||||
{
|
||||
"error": "Inbox is not enabled for this project enable it through the project's api"
|
||||
"error": "Intake is not enabled for this project enable it through the project's api"
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# Get the inbox issue
|
||||
inbox_issue = InboxIssue.objects.get(
|
||||
# Get the intake issue
|
||||
intake_issue = IntakeIssue.objects.get(
|
||||
issue_id=issue_id,
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
inbox_id=inbox.id,
|
||||
intake_id=intake.id,
|
||||
)
|
||||
|
||||
# Get the project member
|
||||
@@ -210,11 +215,11 @@ class InboxIssueAPIEndpoint(BaseAPIView):
|
||||
)
|
||||
|
||||
# Only project members admins and created_by users can access this endpoint
|
||||
if project_member.role <= 5 and str(inbox_issue.created_by_id) != str(
|
||||
if project_member.role <= 5 and str(intake_issue.created_by_id) != str(
|
||||
request.user.id
|
||||
):
|
||||
return Response(
|
||||
{"error": "You cannot edit inbox issues"},
|
||||
{"error": "You cannot edit intake issues"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
@@ -283,7 +288,7 @@ class InboxIssueAPIEndpoint(BaseAPIView):
|
||||
cls=DjangoJSONEncoder,
|
||||
),
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
inbox=(inbox_issue.id),
|
||||
intake=(intake_issue.id),
|
||||
)
|
||||
issue_serializer.save()
|
||||
else:
|
||||
@@ -291,13 +296,13 @@ class InboxIssueAPIEndpoint(BaseAPIView):
|
||||
issue_serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# Only project admins and members can edit inbox issue attributes
|
||||
# Only project admins and members can edit intake issue attributes
|
||||
if project_member.role > 15:
|
||||
serializer = InboxIssueSerializer(
|
||||
inbox_issue, data=request.data, partial=True
|
||||
serializer = IntakeIssueSerializer(
|
||||
intake_issue, data=request.data, partial=True
|
||||
)
|
||||
current_instance = json.dumps(
|
||||
InboxIssueSerializer(inbox_issue).data, cls=DjangoJSONEncoder
|
||||
IntakeIssueSerializer(intake_issue).data, cls=DjangoJSONEncoder
|
||||
)
|
||||
|
||||
if serializer.is_valid():
|
||||
@@ -340,7 +345,7 @@ class InboxIssueAPIEndpoint(BaseAPIView):
|
||||
|
||||
# create a activity for status change
|
||||
issue_activity.delay(
|
||||
type="inbox.activity.created",
|
||||
type="intake.activity.created",
|
||||
requested_data=json.dumps(
|
||||
request.data, cls=DjangoJSONEncoder
|
||||
),
|
||||
@@ -351,7 +356,7 @@ class InboxIssueAPIEndpoint(BaseAPIView):
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
notification=False,
|
||||
origin=request.META.get("HTTP_ORIGIN"),
|
||||
inbox=str(inbox_issue.id),
|
||||
intake=str(intake_issue.id),
|
||||
)
|
||||
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
@@ -360,12 +365,12 @@ class InboxIssueAPIEndpoint(BaseAPIView):
|
||||
)
|
||||
else:
|
||||
return Response(
|
||||
InboxIssueSerializer(inbox_issue).data,
|
||||
IntakeIssueSerializer(intake_issue).data,
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
def delete(self, request, slug, project_id, issue_id):
|
||||
inbox = Inbox.objects.filter(
|
||||
intake = Intake.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
).first()
|
||||
|
||||
@@ -374,25 +379,25 @@ class InboxIssueAPIEndpoint(BaseAPIView):
|
||||
pk=project_id,
|
||||
)
|
||||
|
||||
# Inbox view
|
||||
if inbox is None and not project.inbox_view:
|
||||
# Intake view
|
||||
if intake is None and not project.intake_view:
|
||||
return Response(
|
||||
{
|
||||
"error": "Inbox is not enabled for this project enable it through the project's api"
|
||||
"error": "Intake is not enabled for this project enable it through the project's api"
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# Get the inbox issue
|
||||
inbox_issue = InboxIssue.objects.get(
|
||||
# Get the intake issue
|
||||
intake_issue = IntakeIssue.objects.get(
|
||||
issue_id=issue_id,
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
inbox_id=inbox.id,
|
||||
intake_id=intake.id,
|
||||
)
|
||||
|
||||
# Check the issue status
|
||||
if inbox_issue.status in [-2, -1, 0, 2]:
|
||||
if intake_issue.status in [-2, -1, 0, 2]:
|
||||
# Delete the issue also
|
||||
issue = Issue.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id, pk=issue_id
|
||||
@@ -412,5 +417,5 @@ class InboxIssueAPIEndpoint(BaseAPIView):
|
||||
)
|
||||
issue.delete()
|
||||
|
||||
inbox_issue.delete()
|
||||
intake_issue.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
@@ -18,7 +18,7 @@ from plane.app.permissions import ProjectBasePermission
|
||||
# Module imports
|
||||
from plane.db.models import (
|
||||
Cycle,
|
||||
Inbox,
|
||||
Intake,
|
||||
IssueUserProperty,
|
||||
Module,
|
||||
Project,
|
||||
@@ -285,6 +285,11 @@ class ProjectAPIEndpoint(BaseAPIView):
|
||||
current_instance = json.dumps(
|
||||
ProjectSerializer(project).data, cls=DjangoJSONEncoder
|
||||
)
|
||||
|
||||
intake_view = request.data.get(
|
||||
"inbox_view", request.data.get("intake_view", False)
|
||||
)
|
||||
|
||||
if project.archived_at:
|
||||
return Response(
|
||||
{"error": "Archived project cannot be updated"},
|
||||
@@ -293,21 +298,24 @@ class ProjectAPIEndpoint(BaseAPIView):
|
||||
|
||||
serializer = ProjectSerializer(
|
||||
project,
|
||||
data={**request.data},
|
||||
data={
|
||||
**request.data,
|
||||
"intake_view": intake_view,
|
||||
},
|
||||
context={"workspace_id": workspace.id},
|
||||
partial=True,
|
||||
)
|
||||
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
if serializer.data["inbox_view"]:
|
||||
inbox = Inbox.objects.filter(
|
||||
if serializer.data["intake_view"]:
|
||||
intake = Intake.objects.filter(
|
||||
project=project,
|
||||
is_default=True,
|
||||
).first()
|
||||
if not inbox:
|
||||
Inbox.objects.create(
|
||||
name=f"{project.name} Inbox",
|
||||
if not intake:
|
||||
Intake.objects.create(
|
||||
name=f"{project.name} Intake",
|
||||
project=project,
|
||||
is_default=True,
|
||||
)
|
||||
@@ -316,7 +324,7 @@ class ProjectAPIEndpoint(BaseAPIView):
|
||||
State.objects.get_or_create(
|
||||
name="Triage",
|
||||
group="triage",
|
||||
description="Default state for managing all Inbox Issues",
|
||||
description="Default state for managing all Intake Issues",
|
||||
project_id=pk,
|
||||
color="#ff7700",
|
||||
is_triage=True,
|
||||
|
||||
@@ -57,7 +57,7 @@ from .issue import (
|
||||
IssueFlatSerializer,
|
||||
IssueStateSerializer,
|
||||
IssueLinkSerializer,
|
||||
IssueInboxSerializer,
|
||||
IssueIntakeSerializer,
|
||||
IssueLiteSerializer,
|
||||
IssueAttachmentSerializer,
|
||||
IssueSubscriberSerializer,
|
||||
@@ -102,12 +102,12 @@ from .estimate import (
|
||||
WorkspaceEstimateSerializer,
|
||||
)
|
||||
|
||||
from .inbox import (
|
||||
InboxSerializer,
|
||||
InboxIssueSerializer,
|
||||
IssueStateInboxSerializer,
|
||||
InboxIssueLiteSerializer,
|
||||
InboxIssueDetailSerializer,
|
||||
from .intake import (
|
||||
IntakeSerializer,
|
||||
IntakeIssueSerializer,
|
||||
IssueStateIntakeSerializer,
|
||||
IntakeIssueLiteSerializer,
|
||||
IntakeIssueDetailSerializer,
|
||||
)
|
||||
|
||||
from .analytic import AnalyticViewSerializer
|
||||
|
||||
@@ -60,7 +60,7 @@ class DynamicBaseSerializer(BaseSerializer):
|
||||
CycleIssueSerializer,
|
||||
IssueLiteSerializer,
|
||||
IssueRelationSerializer,
|
||||
InboxIssueLiteSerializer,
|
||||
IntakeIssueLiteSerializer,
|
||||
IssueReactionLiteSerializer,
|
||||
IssueLinkLiteSerializer,
|
||||
RelatedIssueSerializer,
|
||||
@@ -84,8 +84,8 @@ class DynamicBaseSerializer(BaseSerializer):
|
||||
"issue_cycle": CycleIssueSerializer,
|
||||
"parent": IssueLiteSerializer,
|
||||
"issue_relation": IssueRelationSerializer,
|
||||
"issue_intake": IntakeIssueLiteSerializer,
|
||||
"issue_related": RelatedIssueSerializer,
|
||||
"issue_inbox": InboxIssueLiteSerializer,
|
||||
"issue_reactions": IssueReactionLiteSerializer,
|
||||
"issue_link": IssueLinkLiteSerializer,
|
||||
"sub_issues": IssueLiteSerializer,
|
||||
@@ -102,7 +102,7 @@ class DynamicBaseSerializer(BaseSerializer):
|
||||
"labels",
|
||||
"issue_cycle",
|
||||
"issue_relation",
|
||||
"issue_inbox",
|
||||
"issue_intake",
|
||||
"issue_reactions",
|
||||
"issue_attachment",
|
||||
"issue_link",
|
||||
@@ -132,7 +132,7 @@ class DynamicBaseSerializer(BaseSerializer):
|
||||
LabelSerializer,
|
||||
CycleIssueSerializer,
|
||||
IssueRelationSerializer,
|
||||
InboxIssueLiteSerializer,
|
||||
IntakeIssueLiteSerializer,
|
||||
IssueLiteSerializer,
|
||||
IssueReactionLiteSerializer,
|
||||
IssueAttachmentLiteSerializer,
|
||||
@@ -158,8 +158,8 @@ class DynamicBaseSerializer(BaseSerializer):
|
||||
"issue_cycle": CycleIssueSerializer,
|
||||
"parent": IssueLiteSerializer,
|
||||
"issue_relation": IssueRelationSerializer,
|
||||
"issue_intake": IntakeIssueLiteSerializer,
|
||||
"issue_related": RelatedIssueSerializer,
|
||||
"issue_inbox": InboxIssueLiteSerializer,
|
||||
"issue_reactions": IssueReactionLiteSerializer,
|
||||
"issue_attachment": IssueAttachmentLiteSerializer,
|
||||
"issue_link": IssueLinkLiteSerializer,
|
||||
|
||||
+14
-14
@@ -4,22 +4,22 @@ from rest_framework import serializers
|
||||
# Module imports
|
||||
from .base import BaseSerializer
|
||||
from .issue import (
|
||||
IssueInboxSerializer,
|
||||
IssueIntakeSerializer,
|
||||
LabelLiteSerializer,
|
||||
IssueDetailSerializer,
|
||||
)
|
||||
from .project import ProjectLiteSerializer
|
||||
from .state import StateLiteSerializer
|
||||
from .user import UserLiteSerializer
|
||||
from plane.db.models import Inbox, InboxIssue, Issue
|
||||
from plane.db.models import Intake, IntakeIssue, Issue
|
||||
|
||||
|
||||
class InboxSerializer(BaseSerializer):
|
||||
class IntakeSerializer(BaseSerializer):
|
||||
project_detail = ProjectLiteSerializer(source="project", read_only=True)
|
||||
pending_issue_count = serializers.IntegerField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Inbox
|
||||
model = Intake
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"project",
|
||||
@@ -27,11 +27,11 @@ class InboxSerializer(BaseSerializer):
|
||||
]
|
||||
|
||||
|
||||
class InboxIssueSerializer(BaseSerializer):
|
||||
issue = IssueInboxSerializer(read_only=True)
|
||||
class IntakeIssueSerializer(BaseSerializer):
|
||||
issue = IssueIntakeSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = InboxIssue
|
||||
model = IntakeIssue
|
||||
fields = [
|
||||
"id",
|
||||
"status",
|
||||
@@ -53,14 +53,14 @@ class InboxIssueSerializer(BaseSerializer):
|
||||
return super().to_representation(instance)
|
||||
|
||||
|
||||
class InboxIssueDetailSerializer(BaseSerializer):
|
||||
class IntakeIssueDetailSerializer(BaseSerializer):
|
||||
issue = IssueDetailSerializer(read_only=True)
|
||||
duplicate_issue_detail = IssueInboxSerializer(
|
||||
duplicate_issue_detail = IssueIntakeSerializer(
|
||||
read_only=True, source="duplicate_to"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = InboxIssue
|
||||
model = IntakeIssue
|
||||
fields = [
|
||||
"id",
|
||||
"status",
|
||||
@@ -85,14 +85,14 @@ class InboxIssueDetailSerializer(BaseSerializer):
|
||||
return super().to_representation(instance)
|
||||
|
||||
|
||||
class InboxIssueLiteSerializer(BaseSerializer):
|
||||
class IntakeIssueLiteSerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = InboxIssue
|
||||
model = IntakeIssue
|
||||
fields = ["id", "status", "duplicate_to", "snoozed_till", "source"]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
class IssueStateInboxSerializer(BaseSerializer):
|
||||
class IssueStateIntakeSerializer(BaseSerializer):
|
||||
state_detail = StateLiteSerializer(read_only=True, source="state")
|
||||
project_detail = ProjectLiteSerializer(read_only=True, source="project")
|
||||
label_details = LabelLiteSerializer(
|
||||
@@ -102,7 +102,7 @@ class IssueStateInboxSerializer(BaseSerializer):
|
||||
read_only=True, source="assignees", many=True
|
||||
)
|
||||
sub_issues_count = serializers.IntegerField(read_only=True)
|
||||
issue_inbox = InboxIssueLiteSerializer(read_only=True, many=True)
|
||||
issue_intake = IntakeIssueLiteSerializer(read_only=True, many=True)
|
||||
|
||||
class Meta:
|
||||
model = Issue
|
||||
@@ -95,6 +95,8 @@ class IssueCreateSerializer(BaseSerializer):
|
||||
write_only=True,
|
||||
required=False,
|
||||
)
|
||||
project_id = serializers.UUIDField(source="project.id", read_only=True)
|
||||
workspace_id = serializers.UUIDField(source="workspace.id", read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Issue
|
||||
@@ -643,7 +645,7 @@ class IssueStateSerializer(DynamicBaseSerializer):
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class IssueInboxSerializer(DynamicBaseSerializer):
|
||||
class IssueIntakeSerializer(DynamicBaseSerializer):
|
||||
label_ids = serializers.ListField(
|
||||
child=serializers.UUIDField(),
|
||||
required=False,
|
||||
|
||||
@@ -12,6 +12,7 @@ class NotificationSerializer(BaseSerializer):
|
||||
read_only=True, source="triggered_by"
|
||||
)
|
||||
is_inbox_issue = serializers.BooleanField(read_only=True)
|
||||
is_intake_issue = serializers.BooleanField(read_only=True)
|
||||
is_mentioned_notification = serializers.BooleanField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
|
||||
@@ -22,6 +22,7 @@ class ProjectSerializer(BaseSerializer):
|
||||
workspace_detail = WorkspaceLiteSerializer(
|
||||
source="workspace", read_only=True
|
||||
)
|
||||
inbox_view = serializers.BooleanField(read_only=True, source="intake_view")
|
||||
|
||||
class Meta:
|
||||
model = Project
|
||||
@@ -119,6 +120,7 @@ class ProjectListSerializer(DynamicBaseSerializer):
|
||||
anchor = serializers.CharField(read_only=True)
|
||||
members = serializers.SerializerMethodField()
|
||||
cover_image_url = serializers.CharField(read_only=True)
|
||||
inbox_view = serializers.BooleanField(read_only=True, source="intake_view")
|
||||
|
||||
def get_members(self, obj):
|
||||
project_members = getattr(obj, "members_list", None)
|
||||
|
||||
@@ -5,7 +5,7 @@ from .cycle import urlpatterns as cycle_urls
|
||||
from .dashboard import urlpatterns as dashboard_urls
|
||||
from .estimate import urlpatterns as estimate_urls
|
||||
from .external import urlpatterns as external_urls
|
||||
from .inbox import urlpatterns as inbox_urls
|
||||
from .intake import urlpatterns as intake_urls
|
||||
from .issue import urlpatterns as issue_urls
|
||||
from .module import urlpatterns as module_urls
|
||||
from .notification import urlpatterns as notification_urls
|
||||
@@ -25,7 +25,7 @@ urlpatterns = [
|
||||
*dashboard_urls,
|
||||
*estimate_urls,
|
||||
*external_urls,
|
||||
*inbox_urls,
|
||||
*intake_urls,
|
||||
*issue_urls,
|
||||
*module_urls,
|
||||
*notification_urls,
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
from django.urls import path
|
||||
|
||||
|
||||
from plane.app.views import (
|
||||
InboxViewSet,
|
||||
InboxIssueViewSet,
|
||||
)
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/inboxes/",
|
||||
InboxViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
name="inbox",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/inboxes/<uuid:pk>/",
|
||||
InboxViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
name="inbox",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/inbox-issues/",
|
||||
InboxIssueViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
name="inbox-issue",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/inbox-issues/<uuid:pk>/",
|
||||
InboxIssueViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
name="inbox-issue",
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,95 @@
|
||||
from django.urls import path
|
||||
|
||||
|
||||
from plane.app.views import (
|
||||
IntakeViewSet,
|
||||
IntakeIssueViewSet,
|
||||
)
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/intakes/",
|
||||
IntakeViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
name="intake",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/intakes/<uuid:pk>/",
|
||||
IntakeViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
name="intake",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/intake-issues/",
|
||||
IntakeIssueViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
name="intake-issue",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/intake-issues/<uuid:pk>/",
|
||||
IntakeIssueViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
name="intake-issue",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/inboxes/",
|
||||
IntakeViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
name="inbox",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/inboxes/<uuid:pk>/",
|
||||
IntakeViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
name="inbox",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/inbox-issues/",
|
||||
IntakeIssueViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
name="inbox-issue",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/inbox-issues/<uuid:pk>/",
|
||||
IntakeIssueViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
name="inbox-issue",
|
||||
),
|
||||
]
|
||||
@@ -220,7 +220,7 @@ from .estimate.base import (
|
||||
EstimatePointEndpoint,
|
||||
)
|
||||
|
||||
from .inbox.base import InboxViewSet, InboxIssueViewSet
|
||||
from .intake.base import IntakeViewSet, IntakeIssueViewSet
|
||||
|
||||
from .analytic.base import (
|
||||
AnalyticsEndpoint,
|
||||
|
||||
@@ -146,7 +146,12 @@ class UserAssetsV2Endpoint(BaseAPIView):
|
||||
)
|
||||
|
||||
# Check if the file type is allowed
|
||||
allowed_types = ["image/jpeg", "image/png", "image/webp", "image/jpg"]
|
||||
allowed_types = [
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/webp",
|
||||
"image/jpg",
|
||||
]
|
||||
if type not in allowed_types:
|
||||
return Response(
|
||||
{
|
||||
@@ -317,7 +322,7 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
|
||||
|
||||
# Project Cover
|
||||
elif entity_type == FileAsset.EntityTypeContext.PROJECT_COVER:
|
||||
project = Project.objects.filter(id=asset.workspace_id).first()
|
||||
project = Project.objects.filter(id=asset.project_id).first()
|
||||
if project is None:
|
||||
return
|
||||
# Delete the previous cover image
|
||||
@@ -387,7 +392,13 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
|
||||
)
|
||||
|
||||
# Check if the file type is allowed
|
||||
allowed_types = ["image/jpeg", "image/png", "image/webp", "image/jpg"]
|
||||
allowed_types = [
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/webp",
|
||||
"image/jpg",
|
||||
"image/gif",
|
||||
]
|
||||
if type not in allowed_types:
|
||||
return Response(
|
||||
{
|
||||
@@ -620,7 +631,13 @@ class ProjectAssetEndpoint(BaseAPIView):
|
||||
)
|
||||
|
||||
# Check if the file type is allowed
|
||||
allowed_types = ["image/jpeg", "image/png", "image/webp", "image/jpg"]
|
||||
allowed_types = [
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/webp",
|
||||
"image/jpg",
|
||||
"image/gif",
|
||||
]
|
||||
if type not in allowed_types:
|
||||
return Response(
|
||||
{
|
||||
@@ -738,6 +755,11 @@ class ProjectAssetEndpoint(BaseAPIView):
|
||||
|
||||
class ProjectBulkAssetEndpoint(BaseAPIView):
|
||||
|
||||
def save_project_cover(self, asset, project_id):
|
||||
project = Project.objects.get(id=project_id)
|
||||
project.cover_image_asset_id = asset.id
|
||||
project.save()
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def post(self, request, slug, project_id, entity_id):
|
||||
asset_ids = request.data.get("asset_ids", [])
|
||||
@@ -773,6 +795,7 @@ class ProjectBulkAssetEndpoint(BaseAPIView):
|
||||
assets.update(
|
||||
project_id=project_id,
|
||||
)
|
||||
[self.save_project_cover(asset, project_id) for asset in assets]
|
||||
|
||||
if asset.entity_type == FileAsset.EntityTypeContext.ISSUE_DESCRIPTION:
|
||||
assets.update(
|
||||
|
||||
@@ -12,6 +12,7 @@ from django.views.decorators.gzip import gzip_page
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
|
||||
|
||||
# Module imports
|
||||
from .. import BaseViewSet
|
||||
from plane.app.serializers import (
|
||||
@@ -39,6 +40,7 @@ from plane.utils.paginator import (
|
||||
from plane.app.permissions import allow_permission, ROLE
|
||||
|
||||
|
||||
|
||||
class CycleIssueViewSet(BaseViewSet):
|
||||
serializer_class = CycleIssueSerializer
|
||||
model = CycleIssue
|
||||
@@ -194,10 +196,10 @@ class CycleIssueViewSet(BaseViewSet):
|
||||
group_by_field_name=group_by,
|
||||
sub_group_by_field_name=sub_group_by,
|
||||
count_filter=Q(
|
||||
Q(issue_inbox__status=1)
|
||||
| Q(issue_inbox__status=-1)
|
||||
| Q(issue_inbox__status=2)
|
||||
| Q(issue_inbox__isnull=True),
|
||||
Q(issue_intake__status=1)
|
||||
| Q(issue_intake__status=-1)
|
||||
| Q(issue_intake__status=2)
|
||||
| Q(issue_intake__isnull=True),
|
||||
archived_at__isnull=True,
|
||||
is_draft=False,
|
||||
),
|
||||
@@ -223,10 +225,10 @@ class CycleIssueViewSet(BaseViewSet):
|
||||
),
|
||||
group_by_field_name=group_by,
|
||||
count_filter=Q(
|
||||
Q(issue_inbox__status=1)
|
||||
| Q(issue_inbox__status=-1)
|
||||
| Q(issue_inbox__status=2)
|
||||
| Q(issue_inbox__isnull=True),
|
||||
Q(issue_intake__status=1)
|
||||
| Q(issue_intake__status=-1)
|
||||
| Q(issue_intake__status=2)
|
||||
| Q(issue_intake__isnull=True),
|
||||
archived_at__isnull=True,
|
||||
is_draft=False,
|
||||
),
|
||||
|
||||
+76
-86
@@ -18,8 +18,8 @@ from rest_framework.response import Response
|
||||
from ..base import BaseViewSet
|
||||
from plane.app.permissions import allow_permission, ROLE
|
||||
from plane.db.models import (
|
||||
Inbox,
|
||||
InboxIssue,
|
||||
Intake,
|
||||
IntakeIssue,
|
||||
Issue,
|
||||
State,
|
||||
IssueLink,
|
||||
@@ -31,18 +31,18 @@ from plane.db.models import (
|
||||
from plane.app.serializers import (
|
||||
IssueCreateSerializer,
|
||||
IssueSerializer,
|
||||
InboxSerializer,
|
||||
InboxIssueSerializer,
|
||||
InboxIssueDetailSerializer,
|
||||
IntakeSerializer,
|
||||
IntakeIssueSerializer,
|
||||
IntakeIssueDetailSerializer,
|
||||
)
|
||||
from plane.utils.issue_filters import issue_filters
|
||||
from plane.bgtasks.issue_activities_task import issue_activity
|
||||
|
||||
|
||||
class InboxViewSet(BaseViewSet):
|
||||
class IntakeViewSet(BaseViewSet):
|
||||
|
||||
serializer_class = InboxSerializer
|
||||
model = Inbox
|
||||
serializer_class = IntakeSerializer
|
||||
model = Intake
|
||||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
@@ -54,8 +54,8 @@ class InboxViewSet(BaseViewSet):
|
||||
)
|
||||
.annotate(
|
||||
pending_issue_count=Count(
|
||||
"issue_inbox",
|
||||
filter=Q(issue_inbox__status=-2),
|
||||
"issue_intake",
|
||||
filter=Q(issue_intake__status=-2),
|
||||
)
|
||||
)
|
||||
.select_related("workspace", "project")
|
||||
@@ -63,9 +63,9 @@ class InboxViewSet(BaseViewSet):
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def list(self, request, slug, project_id):
|
||||
inbox = self.get_queryset().first()
|
||||
intake = self.get_queryset().first()
|
||||
return Response(
|
||||
InboxSerializer(inbox).data,
|
||||
IntakeSerializer(intake).data,
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
@@ -75,26 +75,26 @@ class InboxViewSet(BaseViewSet):
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||
def destroy(self, request, slug, project_id, pk):
|
||||
inbox = Inbox.objects.filter(
|
||||
intake = Intake.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id, pk=pk
|
||||
).first()
|
||||
# Handle default inbox delete
|
||||
if inbox.is_default:
|
||||
# Handle default intake delete
|
||||
if intake.is_default:
|
||||
return Response(
|
||||
{"error": "You cannot delete the default inbox"},
|
||||
{"error": "You cannot delete the default intake"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
inbox.delete()
|
||||
intake.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
class InboxIssueViewSet(BaseViewSet):
|
||||
class IntakeIssueViewSet(BaseViewSet):
|
||||
|
||||
serializer_class = InboxIssueSerializer
|
||||
model = InboxIssue
|
||||
serializer_class = IntakeIssueSerializer
|
||||
model = IntakeIssue
|
||||
|
||||
filterset_fields = [
|
||||
"status",
|
||||
"statulls",
|
||||
]
|
||||
|
||||
def get_queryset(self):
|
||||
@@ -107,8 +107,8 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
.prefetch_related("assignees", "labels", "issue_module__module")
|
||||
.prefetch_related(
|
||||
Prefetch(
|
||||
"issue_inbox",
|
||||
queryset=InboxIssue.objects.only(
|
||||
"issue_intake",
|
||||
queryset=IntakeIssue.objects.only(
|
||||
"status", "duplicate_to", "snoozed_till", "source"
|
||||
),
|
||||
)
|
||||
@@ -184,14 +184,14 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
|
||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
|
||||
def list(self, request, slug, project_id):
|
||||
inbox_id = Inbox.objects.filter(
|
||||
intake_id = Intake.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
).first()
|
||||
project = Project.objects.get(pk=project_id)
|
||||
filters = issue_filters(request.GET, "GET", "issue__")
|
||||
inbox_issue = (
|
||||
InboxIssue.objects.filter(
|
||||
inbox_id=inbox_id.id, project_id=project_id, **filters
|
||||
intake_issue = (
|
||||
IntakeIssue.objects.filter(
|
||||
intake_id=intake_id.id, project_id=project_id, **filters
|
||||
)
|
||||
.select_related("issue")
|
||||
.prefetch_related(
|
||||
@@ -211,14 +211,14 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
)
|
||||
)
|
||||
).order_by(request.GET.get("order_by", "-issue__created_at"))
|
||||
# inbox status filter
|
||||
inbox_status = [
|
||||
# Intake status filter
|
||||
intake_status = [
|
||||
item
|
||||
for item in request.GET.get("status", "-2").split(",")
|
||||
if item != "null"
|
||||
]
|
||||
if inbox_status:
|
||||
inbox_issue = inbox_issue.filter(status__in=inbox_status)
|
||||
if intake_status:
|
||||
intake_issue = intake_issue.filter(status__in=intake_status)
|
||||
|
||||
if (
|
||||
ProjectMember.objects.filter(
|
||||
@@ -230,12 +230,12 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
).exists()
|
||||
and not project.guest_view_all_features
|
||||
):
|
||||
inbox_issue = inbox_issue.filter(created_by=request.user)
|
||||
intake_issue = intake_issue.filter(created_by=request.user)
|
||||
return self.paginate(
|
||||
request=request,
|
||||
queryset=(inbox_issue),
|
||||
on_results=lambda inbox_issues: InboxIssueSerializer(
|
||||
inbox_issues,
|
||||
queryset=(intake_issue),
|
||||
on_results=lambda intake_issues: IntakeIssueSerializer(
|
||||
intake_issues,
|
||||
many=True,
|
||||
).data,
|
||||
)
|
||||
@@ -261,16 +261,6 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# Create or get state
|
||||
state, _ = State.objects.get_or_create(
|
||||
name="Triage",
|
||||
group="triage",
|
||||
description="Default state for managing all Inbox Issues",
|
||||
project_id=project_id,
|
||||
color="#ff7700",
|
||||
is_triage=True,
|
||||
)
|
||||
|
||||
# create an issue
|
||||
project = Project.objects.get(pk=project_id)
|
||||
serializer = IssueCreateSerializer(
|
||||
@@ -283,15 +273,15 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
inbox_id = Inbox.objects.filter(
|
||||
intake_id = Intake.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
).first()
|
||||
# create an inbox issue
|
||||
inbox_issue = InboxIssue.objects.create(
|
||||
inbox_id=inbox_id.id,
|
||||
# create an intake issue
|
||||
intake_issue = IntakeIssue.objects.create(
|
||||
intake_id=intake_id.id,
|
||||
project_id=project_id,
|
||||
issue_id=serializer.data["id"],
|
||||
source=request.data.get("source", "in-app"),
|
||||
source=request.data.get("source", "IN-APP"),
|
||||
)
|
||||
# Create an Issue Activity
|
||||
issue_activity.delay(
|
||||
@@ -304,10 +294,10 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
notification=True,
|
||||
origin=request.META.get("HTTP_ORIGIN"),
|
||||
inbox=str(inbox_issue.id),
|
||||
intake=str(intake_issue.id),
|
||||
)
|
||||
inbox_issue = (
|
||||
InboxIssue.objects.select_related("issue")
|
||||
intake_issue = (
|
||||
IntakeIssue.objects.select_related("issue")
|
||||
.prefetch_related(
|
||||
"issue__labels",
|
||||
"issue__assignees",
|
||||
@@ -339,12 +329,12 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
),
|
||||
)
|
||||
.get(
|
||||
inbox_id=inbox_id.id,
|
||||
intake_id=intake_id.id,
|
||||
issue_id=serializer.data["id"],
|
||||
project_id=project_id,
|
||||
)
|
||||
)
|
||||
serializer = InboxIssueDetailSerializer(inbox_issue)
|
||||
serializer = IntakeIssueDetailSerializer(intake_issue)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
else:
|
||||
return Response(
|
||||
@@ -353,14 +343,14 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN], creator=True, model=Issue)
|
||||
def partial_update(self, request, slug, project_id, pk):
|
||||
inbox_id = Inbox.objects.filter(
|
||||
intake_id = Intake.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
).first()
|
||||
inbox_issue = InboxIssue.objects.get(
|
||||
intake_issue = IntakeIssue.objects.get(
|
||||
issue_id=pk,
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
inbox_id=inbox_id,
|
||||
intake_id=intake_id,
|
||||
)
|
||||
# Get the project member
|
||||
project_member = ProjectMember.objects.get(
|
||||
@@ -370,11 +360,11 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
is_active=True,
|
||||
)
|
||||
# Only project members admins and created_by users can access this endpoint
|
||||
if project_member.role <= 5 and str(inbox_issue.created_by_id) != str(
|
||||
if project_member.role <= 5 and str(intake_issue.created_by_id) != str(
|
||||
request.user.id
|
||||
):
|
||||
return Response(
|
||||
{"error": "You cannot edit inbox issues"},
|
||||
{"error": "You cannot edit intake issues"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
@@ -405,7 +395,7 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
),
|
||||
).get(
|
||||
pk=inbox_issue.issue_id,
|
||||
pk=intake_issue.issue_id,
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
)
|
||||
@@ -443,7 +433,7 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
notification=True,
|
||||
origin=request.META.get("HTTP_ORIGIN"),
|
||||
inbox=str(inbox_issue.id),
|
||||
intake=str(intake_issue.id),
|
||||
)
|
||||
issue_serializer.save()
|
||||
else:
|
||||
@@ -451,20 +441,20 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
issue_serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# Only project admins and members can edit inbox issue attributes
|
||||
# Only project admins and members can edit intake issue attributes
|
||||
if project_member.role > 15:
|
||||
serializer = InboxIssueSerializer(
|
||||
inbox_issue, data=request.data, partial=True
|
||||
serializer = IntakeIssueSerializer(
|
||||
intake_issue, data=request.data, partial=True
|
||||
)
|
||||
current_instance = json.dumps(
|
||||
InboxIssueSerializer(inbox_issue).data, cls=DjangoJSONEncoder
|
||||
IntakeIssueSerializer(intake_issue).data, cls=DjangoJSONEncoder
|
||||
)
|
||||
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,
|
||||
pk=intake_issue.issue_id,
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
)
|
||||
@@ -480,7 +470,7 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
# Update the issue state if it is accepted
|
||||
if serializer.data["status"] in [1]:
|
||||
issue = Issue.objects.get(
|
||||
pk=inbox_issue.issue_id,
|
||||
pk=intake_issue.issue_id,
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
)
|
||||
@@ -498,7 +488,7 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
issue.save()
|
||||
# create a activity for status change
|
||||
issue_activity.delay(
|
||||
type="inbox.activity.created",
|
||||
type="intake.activity.created",
|
||||
requested_data=json.dumps(
|
||||
request.data, cls=DjangoJSONEncoder
|
||||
),
|
||||
@@ -509,11 +499,11 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
notification=False,
|
||||
origin=request.META.get("HTTP_ORIGIN"),
|
||||
inbox=(inbox_issue.id),
|
||||
intake=(intake_issue.id),
|
||||
)
|
||||
|
||||
inbox_issue = (
|
||||
InboxIssue.objects.select_related("issue")
|
||||
intake_issue = (
|
||||
IntakeIssue.objects.select_related("issue")
|
||||
.prefetch_related(
|
||||
"issue__labels",
|
||||
"issue__assignees",
|
||||
@@ -547,18 +537,18 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
),
|
||||
)
|
||||
.get(
|
||||
inbox_id=inbox_id.id,
|
||||
intake_id=intake_id.id,
|
||||
issue_id=pk,
|
||||
project_id=project_id,
|
||||
)
|
||||
)
|
||||
serializer = InboxIssueDetailSerializer(inbox_issue).data
|
||||
serializer = IntakeIssueDetailSerializer(intake_issue).data
|
||||
return Response(serializer, status=status.HTTP_200_OK)
|
||||
return Response(
|
||||
serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
else:
|
||||
serializer = InboxIssueDetailSerializer(inbox_issue).data
|
||||
serializer = IntakeIssueDetailSerializer(intake_issue).data
|
||||
return Response(serializer, status=status.HTTP_200_OK)
|
||||
|
||||
@allow_permission(
|
||||
@@ -571,12 +561,12 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
model=Issue,
|
||||
)
|
||||
def retrieve(self, request, slug, project_id, pk):
|
||||
inbox_id = Inbox.objects.filter(
|
||||
intake_id = Intake.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
).first()
|
||||
project = Project.objects.get(pk=project_id)
|
||||
inbox_issue = (
|
||||
InboxIssue.objects.select_related("issue")
|
||||
intake_issue = (
|
||||
IntakeIssue.objects.select_related("issue")
|
||||
.prefetch_related(
|
||||
"issue__labels",
|
||||
"issue__assignees",
|
||||
@@ -605,7 +595,7 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
Value([], output_field=ArrayField(UUIDField())),
|
||||
),
|
||||
)
|
||||
.get(inbox_id=inbox_id.id, issue_id=pk, project_id=project_id)
|
||||
.get(intake_id=intake_id.id, issue_id=pk, project_id=project_id)
|
||||
)
|
||||
if (
|
||||
ProjectMember.objects.filter(
|
||||
@@ -616,13 +606,13 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
is_active=True,
|
||||
).exists()
|
||||
and not project.guest_view_all_features
|
||||
and not inbox_issue.created_by == request.user
|
||||
and not intake_issue.created_by == request.user
|
||||
):
|
||||
return Response(
|
||||
{"error": "You are not allowed to view this issue"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
issue = InboxIssueDetailSerializer(inbox_issue).data
|
||||
issue = IntakeIssueDetailSerializer(intake_issue).data
|
||||
return Response(
|
||||
issue,
|
||||
status=status.HTTP_200_OK,
|
||||
@@ -630,23 +620,23 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN], creator=True, model=Issue)
|
||||
def destroy(self, request, slug, project_id, pk):
|
||||
inbox_id = Inbox.objects.filter(
|
||||
intake_id = Intake.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
).first()
|
||||
inbox_issue = InboxIssue.objects.get(
|
||||
intake_issue = IntakeIssue.objects.get(
|
||||
issue_id=pk,
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
inbox_id=inbox_id,
|
||||
intake_id=intake_id,
|
||||
)
|
||||
|
||||
# Check the issue status
|
||||
if inbox_issue.status in [-2, -1, 0, 2]:
|
||||
if intake_issue.status in [-2, -1, 0, 2]:
|
||||
# Delete the issue also
|
||||
issue = Issue.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id, pk=pk
|
||||
).first()
|
||||
issue.delete()
|
||||
|
||||
inbox_issue.delete()
|
||||
intake_issue.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
@@ -171,10 +171,10 @@ class IssueArchiveViewSet(BaseViewSet):
|
||||
group_by_field_name=group_by,
|
||||
sub_group_by_field_name=sub_group_by,
|
||||
count_filter=Q(
|
||||
Q(issue_inbox__status=1)
|
||||
| Q(issue_inbox__status=-1)
|
||||
| Q(issue_inbox__status=2)
|
||||
| Q(issue_inbox__isnull=True),
|
||||
Q(issue_intake__status=1)
|
||||
| Q(issue_intake__status=-1)
|
||||
| Q(issue_intake__status=2)
|
||||
| Q(issue_intake__isnull=True),
|
||||
archived_at__isnull=True,
|
||||
is_draft=False,
|
||||
),
|
||||
@@ -200,10 +200,10 @@ class IssueArchiveViewSet(BaseViewSet):
|
||||
),
|
||||
group_by_field_name=group_by,
|
||||
count_filter=Q(
|
||||
Q(issue_inbox__status=1)
|
||||
| Q(issue_inbox__status=-1)
|
||||
| Q(issue_inbox__status=2)
|
||||
| Q(issue_inbox__isnull=True),
|
||||
Q(issue_intake__status=1)
|
||||
| Q(issue_intake__status=-1)
|
||||
| Q(issue_intake__status=2)
|
||||
| Q(issue_intake__isnull=True),
|
||||
archived_at__isnull=True,
|
||||
is_draft=False,
|
||||
),
|
||||
|
||||
@@ -336,10 +336,10 @@ class IssueViewSet(BaseViewSet):
|
||||
group_by_field_name=group_by,
|
||||
sub_group_by_field_name=sub_group_by,
|
||||
count_filter=Q(
|
||||
Q(issue_inbox__status=1)
|
||||
| Q(issue_inbox__status=-1)
|
||||
| Q(issue_inbox__status=2)
|
||||
| Q(issue_inbox__isnull=True),
|
||||
Q(issue_intake__status=1)
|
||||
| Q(issue_intake__status=-1)
|
||||
| Q(issue_intake__status=2)
|
||||
| Q(issue_intake__isnull=True),
|
||||
archived_at__isnull=True,
|
||||
is_draft=False,
|
||||
),
|
||||
@@ -364,10 +364,10 @@ class IssueViewSet(BaseViewSet):
|
||||
),
|
||||
group_by_field_name=group_by,
|
||||
count_filter=Q(
|
||||
Q(issue_inbox__status=1)
|
||||
| Q(issue_inbox__status=-1)
|
||||
| Q(issue_inbox__status=2)
|
||||
| Q(issue_inbox__isnull=True),
|
||||
Q(issue_intake__status=1)
|
||||
| Q(issue_intake__status=-1)
|
||||
| Q(issue_intake__status=2)
|
||||
| Q(issue_intake__isnull=True),
|
||||
archived_at__isnull=True,
|
||||
is_draft=False,
|
||||
),
|
||||
|
||||
@@ -160,10 +160,10 @@ class ModuleIssueViewSet(BaseViewSet):
|
||||
group_by_field_name=group_by,
|
||||
sub_group_by_field_name=sub_group_by,
|
||||
count_filter=Q(
|
||||
Q(issue_inbox__status=1)
|
||||
| Q(issue_inbox__status=-1)
|
||||
| Q(issue_inbox__status=2)
|
||||
| Q(issue_inbox__isnull=True),
|
||||
Q(issue_intake__status=1)
|
||||
| Q(issue_intake__status=-1)
|
||||
| Q(issue_intake__status=2)
|
||||
| Q(issue_intake__isnull=True),
|
||||
archived_at__isnull=True,
|
||||
is_draft=False,
|
||||
),
|
||||
@@ -189,10 +189,10 @@ class ModuleIssueViewSet(BaseViewSet):
|
||||
),
|
||||
group_by_field_name=group_by,
|
||||
count_filter=Q(
|
||||
Q(issue_inbox__status=1)
|
||||
| Q(issue_inbox__status=-1)
|
||||
| Q(issue_inbox__status=2)
|
||||
| Q(issue_inbox__isnull=True),
|
||||
Q(issue_intake__status=1)
|
||||
| Q(issue_intake__status=-1)
|
||||
| Q(issue_intake__status=2)
|
||||
| Q(issue_intake__isnull=True),
|
||||
archived_at__isnull=True,
|
||||
is_draft=False,
|
||||
),
|
||||
|
||||
@@ -53,9 +53,9 @@ class NotificationViewSet(BaseViewSet, BasePaginator):
|
||||
mentioned = request.GET.get("mentioned", False)
|
||||
q_filters = Q()
|
||||
|
||||
inbox_issue = Issue.objects.filter(
|
||||
intake_issue = Issue.objects.filter(
|
||||
pk=OuterRef("entity_identifier"),
|
||||
issue_inbox__status__in=[0, 2, -2],
|
||||
issue_intake__status__in=[0, 2, -2],
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
)
|
||||
|
||||
@@ -64,7 +64,8 @@ class NotificationViewSet(BaseViewSet, BasePaginator):
|
||||
workspace__slug=slug, receiver_id=request.user.id
|
||||
)
|
||||
.filter(entity_name="issue")
|
||||
.annotate(is_inbox_issue=Exists(inbox_issue))
|
||||
.annotate(is_inbox_issue=Exists(intake_issue))
|
||||
.annotate(is_intake_issue=Exists(intake_issue))
|
||||
.annotate(
|
||||
is_mentioned_notification=Case(
|
||||
When(sender__icontains="mentioned", then=True),
|
||||
|
||||
@@ -38,7 +38,7 @@ from plane.app.permissions import (
|
||||
from plane.db.models import (
|
||||
UserFavorite,
|
||||
Cycle,
|
||||
Inbox,
|
||||
Intake,
|
||||
DeployBoard,
|
||||
IssueUserProperty,
|
||||
Issue,
|
||||
@@ -429,6 +429,9 @@ class ProjectViewSet(BaseViewSet):
|
||||
)
|
||||
|
||||
workspace = Workspace.objects.get(slug=slug)
|
||||
intake_view = request.data.get(
|
||||
"inbox_view", request.data.get("intake_view", False)
|
||||
)
|
||||
|
||||
project = Project.objects.get(pk=pk)
|
||||
current_instance = json.dumps(
|
||||
@@ -442,21 +445,24 @@ class ProjectViewSet(BaseViewSet):
|
||||
|
||||
serializer = ProjectSerializer(
|
||||
project,
|
||||
data={**request.data},
|
||||
data={
|
||||
**request.data,
|
||||
"intake_view": intake_view,
|
||||
},
|
||||
context={"workspace_id": workspace.id},
|
||||
partial=True,
|
||||
)
|
||||
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
if serializer.data["inbox_view"]:
|
||||
inbox = Inbox.objects.filter(
|
||||
if intake_view:
|
||||
intake = Intake.objects.filter(
|
||||
project=project,
|
||||
is_default=True,
|
||||
).first()
|
||||
if not inbox:
|
||||
Inbox.objects.create(
|
||||
name=f"{project.name} Inbox",
|
||||
if not intake:
|
||||
Intake.objects.create(
|
||||
name=f"{project.name} Intake",
|
||||
project=project,
|
||||
is_default=True,
|
||||
)
|
||||
@@ -465,7 +471,7 @@ class ProjectViewSet(BaseViewSet):
|
||||
State.objects.get_or_create(
|
||||
name="Triage",
|
||||
group="triage",
|
||||
description="Default state for managing all Inbox Issues",
|
||||
description="Default state for managing all Intake Issues",
|
||||
project_id=pk,
|
||||
color="#ff7700",
|
||||
is_triage=True,
|
||||
@@ -759,7 +765,7 @@ class DeployBoardViewSet(BaseViewSet):
|
||||
def create(self, request, slug, project_id):
|
||||
comments = request.data.get("is_comments_enabled", False)
|
||||
reactions = request.data.get("is_reactions_enabled", False)
|
||||
inbox = request.data.get("inbox", None)
|
||||
intake = request.data.get("intake", None)
|
||||
votes = request.data.get("is_votes_enabled", False)
|
||||
views = request.data.get(
|
||||
"views",
|
||||
@@ -777,7 +783,7 @@ class DeployBoardViewSet(BaseViewSet):
|
||||
entity_identifier=project_id,
|
||||
project_id=project_id,
|
||||
)
|
||||
project_deploy_board.inbox = inbox
|
||||
project_deploy_board.intake = intake
|
||||
project_deploy_board.view_props = views
|
||||
project_deploy_board.is_votes_enabled = votes
|
||||
project_deploy_board.is_comments_enabled = comments
|
||||
|
||||
@@ -32,7 +32,6 @@ from plane.db.models import (
|
||||
Session,
|
||||
)
|
||||
from plane.license.models import Instance, InstanceAdmin
|
||||
from plane.utils.cache import cache_response, invalidate_cache
|
||||
from plane.utils.paginator import BasePaginator
|
||||
from plane.authentication.utils.host import user_ip
|
||||
from plane.bgtasks.user_deactivation_email_task import user_deactivation_email
|
||||
@@ -49,7 +48,6 @@ class UserEndpoint(BaseViewSet):
|
||||
def get_object(self):
|
||||
return self.request.user
|
||||
|
||||
@cache_response(60 * 60)
|
||||
@method_decorator(cache_control(private=True, max_age=12))
|
||||
@method_decorator(vary_on_cookie)
|
||||
def retrieve(self, request):
|
||||
@@ -59,14 +57,12 @@ class UserEndpoint(BaseViewSet):
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
@cache_response(60 * 60)
|
||||
@method_decorator(cache_control(private=True, max_age=12))
|
||||
@method_decorator(vary_on_cookie)
|
||||
def retrieve_user_settings(self, request):
|
||||
serialized_data = UserMeSettingsSerializer(request.user).data
|
||||
return Response(serialized_data, status=status.HTTP_200_OK)
|
||||
|
||||
@cache_response(60 * 60)
|
||||
def retrieve_instance_admin(self, request):
|
||||
instance = Instance.objects.first()
|
||||
is_admin = InstanceAdmin.objects.filter(
|
||||
@@ -76,19 +72,9 @@ class UserEndpoint(BaseViewSet):
|
||||
{"is_instance_admin": is_admin}, status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
@invalidate_cache(
|
||||
path="/api/users/me/",
|
||||
)
|
||||
@invalidate_cache(
|
||||
path="/api/users/me/settings/",
|
||||
)
|
||||
def partial_update(self, request, *args, **kwargs):
|
||||
return super().partial_update(request, *args, **kwargs)
|
||||
|
||||
@invalidate_cache(path="/api/users/me/")
|
||||
@invalidate_cache(
|
||||
path="/api/users/me/workspaces/", multiple=True, user=False
|
||||
)
|
||||
def deactivate(self, request):
|
||||
# Check all workspace user is active
|
||||
user = self.get_object()
|
||||
@@ -235,7 +221,6 @@ class UserSessionEndpoint(BaseAPIView):
|
||||
|
||||
class UpdateUserOnBoardedEndpoint(BaseAPIView):
|
||||
|
||||
@invalidate_cache(path="/api/users/me/")
|
||||
def patch(self, request):
|
||||
profile = Profile.objects.get(user_id=request.user.id)
|
||||
profile.is_onboarded = request.data.get("is_onboarded", False)
|
||||
@@ -247,7 +232,6 @@ class UpdateUserOnBoardedEndpoint(BaseAPIView):
|
||||
|
||||
class UpdateUserTourCompletedEndpoint(BaseAPIView):
|
||||
|
||||
@invalidate_cache(path="/api/users/me/")
|
||||
def patch(self, request):
|
||||
profile = Profile.objects.get(user_id=request.user.id)
|
||||
profile.is_tour_completed = request.data.get(
|
||||
@@ -305,7 +289,6 @@ class ProfileEndpoint(BaseAPIView):
|
||||
serializer = ProfileSerializer(profile)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@invalidate_cache("/api/users/me/settings/")
|
||||
def patch(self, request):
|
||||
profile = Profile.objects.get(user=request.user)
|
||||
serializer = ProfileSerializer(
|
||||
|
||||
@@ -370,10 +370,10 @@ class WorkspaceViewIssuesViewSet(BaseViewSet):
|
||||
group_by_field_name=group_by,
|
||||
sub_group_by_field_name=sub_group_by,
|
||||
count_filter=Q(
|
||||
Q(issue_inbox__status=1)
|
||||
| Q(issue_inbox__status=-1)
|
||||
| Q(issue_inbox__status=2)
|
||||
| Q(issue_inbox__isnull=True),
|
||||
Q(issue_intake__status=1)
|
||||
| Q(issue_intake__status=-1)
|
||||
| Q(issue_intake__status=2)
|
||||
| Q(issue_intake__isnull=True),
|
||||
archived_at__isnull=True,
|
||||
is_draft=False,
|
||||
),
|
||||
@@ -399,10 +399,10 @@ class WorkspaceViewIssuesViewSet(BaseViewSet):
|
||||
),
|
||||
group_by_field_name=group_by,
|
||||
count_filter=Q(
|
||||
Q(issue_inbox__status=1)
|
||||
| Q(issue_inbox__status=-1)
|
||||
| Q(issue_inbox__status=2)
|
||||
| Q(issue_inbox__isnull=True),
|
||||
Q(issue_intake__status=1)
|
||||
| Q(issue_intake__status=-1)
|
||||
| Q(issue_intake__status=2)
|
||||
| Q(issue_intake__isnull=True),
|
||||
archived_at__isnull=True,
|
||||
is_draft=False,
|
||||
),
|
||||
|
||||
@@ -44,7 +44,6 @@ from plane.db.models import (
|
||||
WorkspaceTheme,
|
||||
)
|
||||
from plane.app.permissions import ROLE, allow_permission
|
||||
from plane.utils.cache import cache_response, invalidate_cache
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.cache import cache_control
|
||||
from django.views.decorators.vary import vary_on_cookie
|
||||
@@ -99,9 +98,6 @@ class WorkSpaceViewSet(BaseViewSet):
|
||||
.select_related("owner")
|
||||
)
|
||||
|
||||
@invalidate_cache(path="/api/workspaces/", user=False)
|
||||
@invalidate_cache(path="/api/users/me/workspaces/")
|
||||
@invalidate_cache(path="/api/instances/", user=False)
|
||||
def create(self, request):
|
||||
try:
|
||||
serializer = WorkSpaceSerializer(data=request.data)
|
||||
@@ -147,7 +143,6 @@ class WorkSpaceViewSet(BaseViewSet):
|
||||
status=status.HTTP_410_GONE,
|
||||
)
|
||||
|
||||
@cache_response(60 * 60 * 2)
|
||||
@allow_permission(
|
||||
[
|
||||
ROLE.ADMIN,
|
||||
@@ -159,8 +154,6 @@ class WorkSpaceViewSet(BaseViewSet):
|
||||
def list(self, request, *args, **kwargs):
|
||||
return super().list(request, *args, **kwargs)
|
||||
|
||||
@invalidate_cache(path="/api/workspaces/", user=False)
|
||||
@invalidate_cache(path="/api/users/me/workspaces/")
|
||||
@allow_permission(
|
||||
[
|
||||
ROLE.ADMIN,
|
||||
@@ -170,13 +163,6 @@ class WorkSpaceViewSet(BaseViewSet):
|
||||
def partial_update(self, request, *args, **kwargs):
|
||||
return super().partial_update(request, *args, **kwargs)
|
||||
|
||||
@invalidate_cache(path="/api/workspaces/", user=False)
|
||||
@invalidate_cache(
|
||||
path="/api/users/me/workspaces/", multiple=True, user=False
|
||||
)
|
||||
@invalidate_cache(
|
||||
path="/api/users/me/settings/", multiple=True, user=False
|
||||
)
|
||||
@allow_permission([ROLE.ADMIN], level="WORKSPACE")
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
return super().destroy(request, *args, **kwargs)
|
||||
@@ -190,7 +176,6 @@ class UserWorkSpacesEndpoint(BaseAPIView):
|
||||
"owner",
|
||||
]
|
||||
|
||||
@cache_response(60 * 60 * 2)
|
||||
@method_decorator(cache_control(private=True, max_age=12))
|
||||
@method_decorator(vary_on_cookie)
|
||||
def get(self, request):
|
||||
|
||||
@@ -40,7 +40,7 @@ from plane.db.models import (
|
||||
WorkspaceMember,
|
||||
DraftIssue,
|
||||
)
|
||||
from plane.utils.cache import cache_response, invalidate_cache
|
||||
from plane.utils.cache import invalidate_cache
|
||||
|
||||
from .. import BaseViewSet
|
||||
|
||||
@@ -66,7 +66,6 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
||||
.select_related("member")
|
||||
)
|
||||
|
||||
@cache_response(60 * 60 * 2)
|
||||
@allow_permission(
|
||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
|
||||
)
|
||||
@@ -93,12 +92,6 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
||||
)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@invalidate_cache(
|
||||
path="/api/workspaces/:slug/members/",
|
||||
url_params=True,
|
||||
user=False,
|
||||
multiple=True,
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN], level="WORKSPACE")
|
||||
def partial_update(self, request, slug, pk):
|
||||
workspace_member = WorkspaceMember.objects.get(
|
||||
@@ -127,16 +120,6 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@invalidate_cache(
|
||||
path="/api/workspaces/:slug/members/",
|
||||
url_params=True,
|
||||
user=False,
|
||||
multiple=True,
|
||||
)
|
||||
@invalidate_cache(path="/api/users/me/settings/", multiple=True)
|
||||
@invalidate_cache(
|
||||
path="/api/users/me/workspaces/", user=False, multiple=True
|
||||
)
|
||||
@allow_permission(allowed_roles=[ROLE.ADMIN], level="WORKSPACE")
|
||||
def destroy(self, request, slug, pk):
|
||||
# Check the user role who is deleting the user
|
||||
|
||||
@@ -204,10 +204,10 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
|
||||
group_by_field_name=group_by,
|
||||
sub_group_by_field_name=sub_group_by,
|
||||
count_filter=Q(
|
||||
Q(issue_inbox__status=1)
|
||||
| Q(issue_inbox__status=-1)
|
||||
| Q(issue_inbox__status=2)
|
||||
| Q(issue_inbox__isnull=True),
|
||||
Q(issue_intake__status=1)
|
||||
| Q(issue_intake__status=-1)
|
||||
| Q(issue_intake__status=2)
|
||||
| Q(issue_intake__isnull=True),
|
||||
archived_at__isnull=True,
|
||||
is_draft=False,
|
||||
),
|
||||
@@ -231,10 +231,10 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
|
||||
),
|
||||
group_by_field_name=group_by,
|
||||
count_filter=Q(
|
||||
Q(issue_inbox__status=1)
|
||||
| Q(issue_inbox__status=-1)
|
||||
| Q(issue_inbox__status=2)
|
||||
| Q(issue_inbox__isnull=True),
|
||||
Q(issue_intake__status=1)
|
||||
| Q(issue_intake__status=-1)
|
||||
| Q(issue_intake__status=2)
|
||||
| Q(issue_intake__isnull=True),
|
||||
archived_at__isnull=True,
|
||||
is_draft=False,
|
||||
),
|
||||
|
||||
@@ -30,8 +30,8 @@ from plane.db.models import (
|
||||
Page,
|
||||
ProjectPage,
|
||||
PageLabel,
|
||||
Inbox,
|
||||
InboxIssue,
|
||||
Intake,
|
||||
IntakeIssue,
|
||||
)
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ def create_project(workspace, user_id):
|
||||
: random.randint(2, 12 if len(name) - 1 >= 12 else len(name) - 1)
|
||||
].upper(),
|
||||
created_by_id=user_id,
|
||||
inbox_view=True,
|
||||
intake_view=True,
|
||||
)
|
||||
|
||||
# Add current member as project member
|
||||
@@ -406,18 +406,18 @@ def create_issues(workspace, project, user_id, issue_count):
|
||||
return issues
|
||||
|
||||
|
||||
def create_inbox_issues(workspace, project, user_id, inbox_issue_count):
|
||||
issues = create_issues(workspace, project, user_id, inbox_issue_count)
|
||||
inbox, create = Inbox.objects.get_or_create(
|
||||
name="Inbox",
|
||||
def create_intake_issues(workspace, project, user_id, intake_issue_count):
|
||||
issues = create_issues(workspace, project, user_id, intake_issue_count)
|
||||
intake, create = Intake.objects.get_or_create(
|
||||
name="Intake",
|
||||
project=project,
|
||||
is_default=True,
|
||||
)
|
||||
InboxIssue.objects.bulk_create(
|
||||
IntakeIssue.objects.bulk_create(
|
||||
[
|
||||
InboxIssue(
|
||||
IntakeIssue(
|
||||
issue=issue,
|
||||
inbox=inbox,
|
||||
intake=intake,
|
||||
status=(status := [-2, -1, 0, 1, 2][random.randint(0, 4)]),
|
||||
snoozed_till=(
|
||||
datetime.now() + timedelta(days=random.randint(1, 30))
|
||||
@@ -599,7 +599,7 @@ def create_dummy_data(
|
||||
cycle_count,
|
||||
module_count,
|
||||
pages_count,
|
||||
inbox_issue_count,
|
||||
intake_issue_count,
|
||||
):
|
||||
workspace = Workspace.objects.get(slug=slug)
|
||||
|
||||
@@ -660,12 +660,12 @@ def create_dummy_data(
|
||||
issue_count=issue_count,
|
||||
)
|
||||
|
||||
# create inbox issues
|
||||
create_inbox_issues(
|
||||
# create intake issues
|
||||
create_intake_issues(
|
||||
workspace=workspace,
|
||||
project=project,
|
||||
user_id=user_id,
|
||||
inbox_issue_count=inbox_issue_count,
|
||||
intake_issue_count=intake_issue_count,
|
||||
)
|
||||
|
||||
# create issue parent
|
||||
|
||||
@@ -1567,7 +1567,7 @@ def delete_draft_issue_activity(
|
||||
)
|
||||
|
||||
|
||||
def create_inbox_activity(
|
||||
def create_intake_activity(
|
||||
requested_data,
|
||||
current_instance,
|
||||
issue_id,
|
||||
@@ -1596,8 +1596,8 @@ def create_inbox_activity(
|
||||
issue_id=issue_id,
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment="updated the inbox status",
|
||||
field="inbox",
|
||||
comment="updated the intake status",
|
||||
field="intake",
|
||||
verb=requested_data.get("status"),
|
||||
actor_id=actor_id,
|
||||
epoch=epoch,
|
||||
@@ -1620,7 +1620,7 @@ def issue_activity(
|
||||
subscriber=True,
|
||||
notification=False,
|
||||
origin=None,
|
||||
inbox=None,
|
||||
intake=None,
|
||||
):
|
||||
try:
|
||||
issue_activities = []
|
||||
@@ -1668,7 +1668,7 @@ def issue_activity(
|
||||
"issue_draft.activity.created": create_draft_issue_activity,
|
||||
"issue_draft.activity.updated": update_draft_issue_activity,
|
||||
"issue_draft.activity.deleted": delete_draft_issue_activity,
|
||||
"inbox.activity.created": create_inbox_activity,
|
||||
"intake.activity.created": create_intake_activity,
|
||||
}
|
||||
|
||||
func = ACTIVITY_MAPPER.get(type)
|
||||
@@ -1695,12 +1695,12 @@ def issue_activity(
|
||||
event=(
|
||||
"issue_comment"
|
||||
if activity.field == "comment"
|
||||
else "inbox_issue" if inbox else "issue"
|
||||
else "intake_issue" if intake else "issue"
|
||||
),
|
||||
event_id=(
|
||||
activity.issue_comment_id
|
||||
if activity.field == "comment"
|
||||
else inbox if inbox else activity.issue_id
|
||||
else intake if intake else activity.issue_id
|
||||
),
|
||||
verb=activity.verb,
|
||||
field=(
|
||||
|
||||
@@ -51,10 +51,10 @@ def archive_old_issues():
|
||||
& Q(issue_module__isnull=False)
|
||||
),
|
||||
).filter(
|
||||
Q(issue_inbox__status=1)
|
||||
| Q(issue_inbox__status=-1)
|
||||
| Q(issue_inbox__status=2)
|
||||
| Q(issue_inbox__isnull=True)
|
||||
Q(issue_intake__status=1)
|
||||
| Q(issue_intake__status=-1)
|
||||
| Q(issue_intake__status=2)
|
||||
| Q(issue_intake__isnull=True)
|
||||
)
|
||||
|
||||
# Check if Issues
|
||||
@@ -129,10 +129,10 @@ def close_old_issues():
|
||||
& Q(issue_module__isnull=False)
|
||||
),
|
||||
).filter(
|
||||
Q(issue_inbox__status=1)
|
||||
| Q(issue_inbox__status=-1)
|
||||
| Q(issue_inbox__status=2)
|
||||
| Q(issue_inbox__isnull=True)
|
||||
Q(issue_intake__status=1)
|
||||
| Q(issue_intake__status=-1)
|
||||
| Q(issue_intake__status=2)
|
||||
| Q(issue_intake__isnull=True)
|
||||
)
|
||||
|
||||
# Check if Issues
|
||||
|
||||
@@ -27,7 +27,7 @@ from plane.api.serializers import (
|
||||
ModuleSerializer,
|
||||
ProjectSerializer,
|
||||
UserLiteSerializer,
|
||||
InboxIssueSerializer,
|
||||
IntakeIssueSerializer,
|
||||
)
|
||||
from plane.db.models import (
|
||||
Cycle,
|
||||
@@ -40,7 +40,7 @@ from plane.db.models import (
|
||||
User,
|
||||
Webhook,
|
||||
WebhookLog,
|
||||
InboxIssue,
|
||||
IntakeIssue,
|
||||
)
|
||||
from plane.license.utils.instance_value import get_email_configuration
|
||||
from plane.utils.exception_logger import log_exception
|
||||
@@ -54,7 +54,7 @@ SERIALIZER_MAPPER = {
|
||||
"module_issue": ModuleIssueSerializer,
|
||||
"issue_comment": IssueCommentSerializer,
|
||||
"user": UserLiteSerializer,
|
||||
"inbox_issue": InboxIssueSerializer,
|
||||
"intake_issue": IntakeIssueSerializer,
|
||||
}
|
||||
|
||||
MODEL_MAPPER = {
|
||||
@@ -66,7 +66,7 @@ MODEL_MAPPER = {
|
||||
"module_issue": ModuleIssue,
|
||||
"issue_comment": IssueComment,
|
||||
"user": User,
|
||||
"inbox_issue": InboxIssue,
|
||||
"intake_issue": IntakeIssue,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ app.conf.beat_schedule = {
|
||||
},
|
||||
"run-every-6-hours-for-instance-trace": {
|
||||
"task": "plane.license.bgtasks.tracer.instance_traces",
|
||||
"schedule": crontab(hour="*/6"),
|
||||
"schedule": crontab(hour="*/6", minute=0),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -62,13 +62,15 @@ class Command(BaseCommand):
|
||||
project_count = int(input("Number of projects to be created: "))
|
||||
|
||||
for i in range(project_count):
|
||||
print(f"Please provide the following details for project {i+1}:")
|
||||
print(
|
||||
f"Please provide the following details for project {i+1}:"
|
||||
)
|
||||
issue_count = int(input("Number of issues to be created: "))
|
||||
cycle_count = int(input("Number of cycles to be created: "))
|
||||
module_count = int(input("Number of modules to be created: "))
|
||||
pages_count = int(input("Number of pages to be created: "))
|
||||
inbox_issue_count = int(
|
||||
input("Number of inbox issues to be created: ")
|
||||
intake_issue_count = int(
|
||||
input("Number of intake issues to be created: ")
|
||||
)
|
||||
|
||||
from plane.bgtasks.dummy_data_task import create_dummy_data
|
||||
@@ -81,7 +83,7 @@ class Command(BaseCommand):
|
||||
cycle_count=cycle_count,
|
||||
module_count=module_count,
|
||||
pages_count=pages_count,
|
||||
inbox_issue_count=inbox_issue_count,
|
||||
intake_issue_count=intake_issue_count,
|
||||
)
|
||||
|
||||
self.stdout.write(
|
||||
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
# Generated by Django 4.2.15 on 2024-11-06 08:41
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('db', '0084_remove_label_label_unique_name_project_when_deleted_at_null_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameModel(
|
||||
old_name="Inbox",
|
||||
new_name="Intake",
|
||||
),
|
||||
migrations.AlterModelTable(
|
||||
name="Intake",
|
||||
table="intakes",
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name="Intake",
|
||||
options={
|
||||
"verbose_name": "Intake",
|
||||
"verbose_name_plural": "Intakes",
|
||||
"ordering": ("name",),
|
||||
},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="Intake",
|
||||
name="description",
|
||||
field=models.TextField(
|
||||
blank=True, verbose_name="Intake Description"
|
||||
),
|
||||
),
|
||||
migrations.RenameModel(
|
||||
old_name="InboxIssue",
|
||||
new_name="IntakeIssue",
|
||||
),
|
||||
# Rename the 'inbox' field to 'intake'
|
||||
migrations.RenameField(
|
||||
model_name="IntakeIssue",
|
||||
old_name="inbox",
|
||||
new_name="intake",
|
||||
),
|
||||
# Update ForeignKey related_name for 'intake'
|
||||
migrations.AlterField(
|
||||
model_name="IntakeIssue",
|
||||
name="intake",
|
||||
field=models.ForeignKey(
|
||||
"db.Intake",
|
||||
related_name="issue_intake",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
),
|
||||
),
|
||||
# Update ForeignKey related_name for 'issue'
|
||||
migrations.AlterField(
|
||||
model_name="IntakeIssue",
|
||||
name="issue",
|
||||
field=models.ForeignKey(
|
||||
"db.Issue",
|
||||
related_name="issue_intake",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
),
|
||||
),
|
||||
# Update ForeignKey related_name for 'duplicate_to'
|
||||
migrations.AlterField(
|
||||
model_name="IntakeIssue",
|
||||
name="duplicate_to",
|
||||
field=models.ForeignKey(
|
||||
"db.Issue",
|
||||
related_name="intake_duplicate",
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
# Update Meta options
|
||||
migrations.AlterModelOptions(
|
||||
name="IntakeIssue",
|
||||
options={
|
||||
"verbose_name": "IntakeIssue",
|
||||
"verbose_name_plural": "IntakeIssues",
|
||||
"ordering": ("-created_at",),
|
||||
},
|
||||
),
|
||||
# Update db_table
|
||||
migrations.AlterModelTable(
|
||||
name="IntakeIssue",
|
||||
table="intake_issues",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="project",
|
||||
old_name="inbox_view",
|
||||
new_name="intake_view",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="deployboard",
|
||||
old_name="inbox",
|
||||
new_name="intake",
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name="projectdeployboard",
|
||||
old_name="inbox",
|
||||
new_name="intake",
|
||||
),
|
||||
migrations.RemoveConstraint(
|
||||
model_name="intake",
|
||||
name="inbox_unique_name_project_when_deleted_at_null",
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="deployboard",
|
||||
name="intake",
|
||||
field=models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="publish_intake",
|
||||
to="db.intake",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="projectdeployboard",
|
||||
name="intake",
|
||||
field=models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="board_intake",
|
||||
to="db.intake",
|
||||
),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name="intake",
|
||||
constraint=models.UniqueConstraint(
|
||||
condition=models.Q(("deleted_at__isnull", True)),
|
||||
fields=("name", "project"),
|
||||
name="intake_unique_name_project_when_deleted_at_null",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -15,7 +15,7 @@ from .draft import (
|
||||
from .estimate import Estimate, EstimatePoint
|
||||
from .exporter import ExporterHistory
|
||||
from .importer import Importer
|
||||
from .inbox import Inbox, InboxIssue
|
||||
from .intake import Intake, IntakeIssue
|
||||
from .integration import (
|
||||
GithubCommentSync,
|
||||
GithubIssueSync,
|
||||
@@ -93,7 +93,7 @@ from .page import Page, PageLog, PageLabel
|
||||
|
||||
from .estimate import Estimate, EstimatePoint
|
||||
|
||||
from .inbox import Inbox, InboxIssue
|
||||
from .intake import Intake, IntakeIssue
|
||||
|
||||
from .analytic import AnalyticView
|
||||
|
||||
|
||||
@@ -34,9 +34,9 @@ class DeployBoard(WorkspaceBaseModel):
|
||||
)
|
||||
is_comments_enabled = models.BooleanField(default=False)
|
||||
is_reactions_enabled = models.BooleanField(default=False)
|
||||
inbox = models.ForeignKey(
|
||||
"db.Inbox",
|
||||
related_name="board_inbox",
|
||||
intake = models.ForeignKey(
|
||||
"db.Intake",
|
||||
related_name="publish_intake",
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
)
|
||||
|
||||
@@ -5,17 +5,17 @@ from django.db import models
|
||||
from plane.db.models.project import ProjectBaseModel
|
||||
|
||||
|
||||
class Inbox(ProjectBaseModel):
|
||||
class Intake(ProjectBaseModel):
|
||||
name = models.CharField(max_length=255)
|
||||
description = models.TextField(
|
||||
verbose_name="Inbox Description", blank=True
|
||||
verbose_name="Intake Description", blank=True
|
||||
)
|
||||
is_default = models.BooleanField(default=False)
|
||||
view_props = models.JSONField(default=dict)
|
||||
logo_props = models.JSONField(default=dict)
|
||||
|
||||
def __str__(self):
|
||||
"""Return name of the Inbox"""
|
||||
"""Return name of the intake"""
|
||||
return f"{self.name} <{self.project.name}>"
|
||||
|
||||
class Meta:
|
||||
@@ -24,21 +24,21 @@ class Inbox(ProjectBaseModel):
|
||||
models.UniqueConstraint(
|
||||
fields=["name", "project"],
|
||||
condition=models.Q(deleted_at__isnull=True),
|
||||
name="inbox_unique_name_project_when_deleted_at_null",
|
||||
name="intake_unique_name_project_when_deleted_at_null",
|
||||
)
|
||||
]
|
||||
verbose_name = "Inbox"
|
||||
verbose_name_plural = "Inboxes"
|
||||
db_table = "inboxes"
|
||||
verbose_name = "Intake"
|
||||
verbose_name_plural = "Intakes"
|
||||
db_table = "intakes"
|
||||
ordering = ("name",)
|
||||
|
||||
|
||||
class InboxIssue(ProjectBaseModel):
|
||||
inbox = models.ForeignKey(
|
||||
"db.Inbox", related_name="issue_inbox", on_delete=models.CASCADE
|
||||
class IntakeIssue(ProjectBaseModel):
|
||||
intake = models.ForeignKey(
|
||||
"db.Intake", related_name="issue_intake", on_delete=models.CASCADE
|
||||
)
|
||||
issue = models.ForeignKey(
|
||||
"db.Issue", related_name="issue_inbox", on_delete=models.CASCADE
|
||||
"db.Issue", related_name="issue_intake", on_delete=models.CASCADE
|
||||
)
|
||||
status = models.IntegerField(
|
||||
choices=(
|
||||
@@ -53,7 +53,7 @@ class InboxIssue(ProjectBaseModel):
|
||||
snoozed_till = models.DateTimeField(null=True)
|
||||
duplicate_to = models.ForeignKey(
|
||||
"db.Issue",
|
||||
related_name="inbox_duplicate",
|
||||
related_name="intake_duplicate",
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
)
|
||||
@@ -69,11 +69,11 @@ class InboxIssue(ProjectBaseModel):
|
||||
extra = models.JSONField(default=dict)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "InboxIssue"
|
||||
verbose_name_plural = "InboxIssues"
|
||||
db_table = "inbox_issues"
|
||||
verbose_name = "IntakeIssue"
|
||||
verbose_name_plural = "IntakeIssues"
|
||||
db_table = "intake_issues"
|
||||
ordering = ("-created_at",)
|
||||
|
||||
def __str__(self):
|
||||
"""Return name of the Issue"""
|
||||
return f"{self.issue.name} <{self.inbox.name}>"
|
||||
return f"{self.issue.name} <{self.intake.name}>"
|
||||
@@ -86,10 +86,10 @@ class IssueManager(SoftDeletionManager):
|
||||
super()
|
||||
.get_queryset()
|
||||
.filter(
|
||||
models.Q(issue_inbox__status=1)
|
||||
| models.Q(issue_inbox__status=-1)
|
||||
| models.Q(issue_inbox__status=2)
|
||||
| models.Q(issue_inbox__isnull=True)
|
||||
models.Q(issue_intake__status=1)
|
||||
| models.Q(issue_intake__status=-1)
|
||||
| models.Q(issue_intake__status=2)
|
||||
| models.Q(issue_intake__isnull=True)
|
||||
)
|
||||
.filter(deleted_at__isnull=True)
|
||||
.filter(state__is_triage=False)
|
||||
|
||||
@@ -95,7 +95,7 @@ class Project(BaseModel):
|
||||
cycle_view = models.BooleanField(default=True)
|
||||
issue_views_view = models.BooleanField(default=True)
|
||||
page_view = models.BooleanField(default=True)
|
||||
inbox_view = models.BooleanField(default=False)
|
||||
intake_view = models.BooleanField(default=False)
|
||||
is_time_tracking_enabled = models.BooleanField(default=False)
|
||||
is_issue_type_enabled = models.BooleanField(default=False)
|
||||
guest_view_all_features = models.BooleanField(default=False)
|
||||
@@ -309,9 +309,9 @@ class ProjectDeployBoard(ProjectBaseModel):
|
||||
)
|
||||
comments = models.BooleanField(default=False)
|
||||
reactions = models.BooleanField(default=False)
|
||||
inbox = models.ForeignKey(
|
||||
"db.Inbox",
|
||||
related_name="bord_inbox",
|
||||
intake = models.ForeignKey(
|
||||
"db.Intake",
|
||||
related_name="board_intake",
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
)
|
||||
|
||||
@@ -13,7 +13,6 @@ class InstanceSerializer(BaseSerializer):
|
||||
model = Instance
|
||||
exclude = [
|
||||
"license_key",
|
||||
"user_count"
|
||||
]
|
||||
read_only_fields = [
|
||||
"id",
|
||||
|
||||
@@ -3,7 +3,7 @@ from celery import shared_task
|
||||
from opentelemetry import trace
|
||||
|
||||
# Module imports
|
||||
from plane.license.models import Instance
|
||||
from plane.license.models import Instance, InstanceAdmin
|
||||
from plane.db.models import (
|
||||
User,
|
||||
Workspace,
|
||||
@@ -25,6 +25,7 @@ def instance_traces():
|
||||
|
||||
# Check if the instance is registered
|
||||
instance = Instance.objects.first()
|
||||
instance_admin = InstanceAdmin.objects.first()
|
||||
|
||||
# If instance is None then return
|
||||
if instance is None:
|
||||
@@ -52,6 +53,17 @@ def instance_traces():
|
||||
span.set_attribute(
|
||||
"is_telemetry_enabled", instance.is_telemetry_enabled
|
||||
)
|
||||
span.set_attribute(
|
||||
"is_support_required", instance.is_support_required
|
||||
)
|
||||
span.set_attribute("is_setup_done", instance.is_setup_done)
|
||||
span.set_attribute(
|
||||
"is_signup_screen_visited", instance.is_signup_screen_visited
|
||||
)
|
||||
span.set_attribute("is_verified", instance.is_verified)
|
||||
span.set_attribute("edition", instance.edition)
|
||||
span.set_attribute("domain", instance.domain)
|
||||
span.set_attribute("is_test", instance.is_test)
|
||||
span.set_attribute("user_count", user_count)
|
||||
span.set_attribute("workspace_count", workspace_count)
|
||||
span.set_attribute("project_count", project_count)
|
||||
@@ -62,6 +74,13 @@ def instance_traces():
|
||||
span.set_attribute("module_issue_count", module_issue_count)
|
||||
span.set_attribute("page_count", page_count)
|
||||
|
||||
if instance_admin:
|
||||
span.set_attribute("admin_email", instance_admin.user.email)
|
||||
span.set_attribute(
|
||||
"admin_name",
|
||||
f"{instance_admin.user.first_name} {instance_admin.user.last_name}",
|
||||
)
|
||||
|
||||
# Workspace details
|
||||
for workspace in Workspace.objects.all():
|
||||
# Count of all models
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Python imports
|
||||
import json
|
||||
import secrets
|
||||
import os
|
||||
|
||||
# Django imports
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
@@ -8,10 +9,7 @@ from django.utils import timezone
|
||||
from django.conf import settings
|
||||
|
||||
# Module imports
|
||||
from plane.license.models import Instance
|
||||
from plane.db.models import (
|
||||
User,
|
||||
)
|
||||
from plane.license.models import Instance, EditiontTypes
|
||||
from plane.license.bgtasks.tracer import instance_traces
|
||||
|
||||
|
||||
@@ -32,7 +30,6 @@ class Command(BaseCommand):
|
||||
payload = {
|
||||
"instance_key": settings.INSTANCE_KEY,
|
||||
"version": data.get("version", 0.1),
|
||||
"user_count": User.objects.filter(is_bot=False).count(),
|
||||
}
|
||||
return payload
|
||||
|
||||
@@ -54,11 +51,11 @@ class Command(BaseCommand):
|
||||
instance = Instance.objects.create(
|
||||
instance_name="Plane Community Edition",
|
||||
instance_id=secrets.token_hex(12),
|
||||
license_key=None,
|
||||
current_version=payload.get("version"),
|
||||
latest_version=payload.get("version"),
|
||||
last_checked_at=timezone.now(),
|
||||
user_count=payload.get("user_count", 0),
|
||||
is_test=os.environ.get("IS_TEST", "0") == "1",
|
||||
edition=EditiontTypes.PLANE_CE.value,
|
||||
)
|
||||
|
||||
self.stdout.write(self.style.SUCCESS("Instance registered"))
|
||||
@@ -69,9 +66,10 @@ class Command(BaseCommand):
|
||||
payload = self.read_package_json()
|
||||
# Update the instance details
|
||||
instance.last_checked_at = timezone.now()
|
||||
instance.user_count = payload.get("user_count", 0)
|
||||
instance.current_version = payload.get("version")
|
||||
instance.latest_version = payload.get("version")
|
||||
instance.is_test = os.environ.get("IS_TEST", "0") == "1"
|
||||
instance.edition = EditiontTypes.PLANE_CE.value
|
||||
instance.save()
|
||||
|
||||
# Call the instance traces task
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# Generated by Django 4.2.15 on 2024-11-19 14:04
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('license', '0004_changelog_deleted_at_instance_deleted_at_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='instance',
|
||||
old_name='product',
|
||||
new_name='edition',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='instance',
|
||||
name='license_key',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='instance',
|
||||
name='user_count',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='instance',
|
||||
name='is_test',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
@@ -1 +1,6 @@
|
||||
from .instance import Instance, InstanceAdmin, InstanceConfiguration
|
||||
from .instance import (
|
||||
Instance,
|
||||
InstanceAdmin,
|
||||
InstanceConfiguration,
|
||||
EditiontTypes,
|
||||
)
|
||||
|
||||
@@ -11,7 +11,7 @@ from plane.db.models import BaseModel
|
||||
ROLE_CHOICES = ((20, "Admin"),)
|
||||
|
||||
|
||||
class ProductTypes(Enum):
|
||||
class EditiontTypes(Enum):
|
||||
PLANE_CE = "plane-ce"
|
||||
|
||||
|
||||
@@ -20,11 +20,10 @@ class Instance(BaseModel):
|
||||
instance_name = models.CharField(max_length=255)
|
||||
whitelist_emails = models.TextField(blank=True, null=True)
|
||||
instance_id = models.CharField(max_length=255, unique=True)
|
||||
license_key = models.CharField(max_length=256, null=True, blank=True)
|
||||
current_version = models.CharField(max_length=255)
|
||||
latest_version = models.CharField(max_length=255, null=True, blank=True)
|
||||
product = models.CharField(
|
||||
max_length=255, default=ProductTypes.PLANE_CE.value
|
||||
edition = models.CharField(
|
||||
max_length=255, default=EditiontTypes.PLANE_CE.value
|
||||
)
|
||||
domain = models.TextField(blank=True)
|
||||
# Instance specifics
|
||||
@@ -37,9 +36,8 @@ class Instance(BaseModel):
|
||||
is_setup_done = models.BooleanField(default=False)
|
||||
# signup screen
|
||||
is_signup_screen_visited = models.BooleanField(default=False)
|
||||
# users
|
||||
user_count = models.PositiveBigIntegerField(default=0)
|
||||
is_verified = models.BooleanField(default=False)
|
||||
is_test = models.BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Instance"
|
||||
|
||||
@@ -35,8 +35,6 @@ SECRET_KEY = os.environ.get("SECRET_KEY", get_random_secret_key())
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = int(os.environ.get("DEBUG", "0"))
|
||||
|
||||
# Initialize Django instrumentation
|
||||
DjangoInstrumentor().instrument()
|
||||
# Configure the tracer provider
|
||||
service_name = os.environ.get("SERVICE_NAME", "plane-ce-api")
|
||||
resource = Resource.create({"service.name": service_name})
|
||||
@@ -46,6 +44,8 @@ otel_endpoint = os.environ.get("OTLP_ENDPOINT", "https://telemetry.plane.so")
|
||||
otlp_exporter = OTLPSpanExporter(endpoint=otel_endpoint)
|
||||
span_processor = BatchSpanProcessor(otlp_exporter)
|
||||
trace.get_tracer_provider().add_span_processor(span_processor)
|
||||
# Initialize Django instrumentation
|
||||
DjangoInstrumentor().instrument()
|
||||
|
||||
|
||||
# Allowed Hosts
|
||||
|
||||
+7
-7
@@ -9,16 +9,16 @@ from .project import ProjectLiteSerializer
|
||||
from .issue import IssueFlatSerializer, LabelLiteSerializer
|
||||
from plane.db.models import (
|
||||
Issue,
|
||||
InboxIssue,
|
||||
IntakeIssue,
|
||||
)
|
||||
|
||||
|
||||
class InboxIssueSerializer(BaseSerializer):
|
||||
class IntakeIssueSerializer(BaseSerializer):
|
||||
issue_detail = IssueFlatSerializer(source="issue", read_only=True)
|
||||
project_detail = ProjectLiteSerializer(source="project", read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = InboxIssue
|
||||
model = IntakeIssue
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"project",
|
||||
@@ -26,14 +26,14 @@ class InboxIssueSerializer(BaseSerializer):
|
||||
]
|
||||
|
||||
|
||||
class InboxIssueLiteSerializer(BaseSerializer):
|
||||
class IntakeIssueLiteSerializer(BaseSerializer):
|
||||
class Meta:
|
||||
model = InboxIssue
|
||||
model = IntakeIssue
|
||||
fields = ["id", "status", "duplicate_to", "snoozed_till", "source"]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
class IssueStateInboxSerializer(BaseSerializer):
|
||||
class IssueStateIntakeSerializer(BaseSerializer):
|
||||
state_detail = StateLiteSerializer(read_only=True, source="state")
|
||||
project_detail = ProjectLiteSerializer(read_only=True, source="project")
|
||||
label_details = LabelLiteSerializer(
|
||||
@@ -44,7 +44,7 @@ class IssueStateInboxSerializer(BaseSerializer):
|
||||
)
|
||||
sub_issues_count = serializers.IntegerField(read_only=True)
|
||||
bridge_id = serializers.UUIDField(read_only=True)
|
||||
issue_inbox = InboxIssueLiteSerializer(read_only=True, many=True)
|
||||
issue_intake = IntakeIssueLiteSerializer(read_only=True, many=True)
|
||||
|
||||
class Meta:
|
||||
model = Issue
|
||||
@@ -1,11 +1,11 @@
|
||||
from .inbox import urlpatterns as inbox_urls
|
||||
from .intake import urlpatterns as intake_urls
|
||||
from .issue import urlpatterns as issue_urls
|
||||
from .project import urlpatterns as project_urls
|
||||
from .asset import urlpatterns as asset_urls
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
*inbox_urls,
|
||||
*intake_urls,
|
||||
*issue_urls,
|
||||
*project_urls,
|
||||
*asset_urls,
|
||||
|
||||
@@ -2,15 +2,26 @@ from django.urls import path
|
||||
|
||||
|
||||
from plane.space.views import (
|
||||
InboxIssuePublicViewSet,
|
||||
IntakeIssuePublicViewSet,
|
||||
IssueVotePublicViewSet,
|
||||
WorkspaceProjectDeployBoardEndpoint,
|
||||
)
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
"anchor/<str:anchor>/inboxes/<uuid:inbox_id>/inbox-issues/",
|
||||
InboxIssuePublicViewSet.as_view(
|
||||
"anchor/<str:anchor>/intakes/<uuid:intake_id>/intake-issues/",
|
||||
IntakeIssuePublicViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
name="intake-issue",
|
||||
),
|
||||
path(
|
||||
"anchor/<str:anchor>/intakes/<uuid:intake_id>/inbox-issues/",
|
||||
IntakeIssuePublicViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
@@ -19,15 +30,15 @@ urlpatterns = [
|
||||
name="inbox-issue",
|
||||
),
|
||||
path(
|
||||
"anchor/<str:anchor>/inboxes/<uuid:inbox_id>/inbox-issues/<uuid:pk>/",
|
||||
InboxIssuePublicViewSet.as_view(
|
||||
"anchor/<str:anchor>/intakes/<uuid:intake_id>/intake-issues/<uuid:pk>/",
|
||||
IntakeIssuePublicViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
name="inbox-issue",
|
||||
name="intake-issue",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/project-boards/",
|
||||
@@ -14,7 +14,7 @@ from .issue import (
|
||||
ProjectIssuesPublicEndpoint,
|
||||
)
|
||||
|
||||
from .inbox import InboxIssuePublicViewSet
|
||||
from .intake import IntakeIssuePublicViewSet
|
||||
|
||||
from .cycle import ProjectCyclesEndpoint
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ from rest_framework.response import Response
|
||||
# Module imports
|
||||
from .base import BaseViewSet
|
||||
from plane.db.models import (
|
||||
InboxIssue,
|
||||
IntakeIssue,
|
||||
Issue,
|
||||
State,
|
||||
IssueLink,
|
||||
@@ -22,17 +22,17 @@ from plane.db.models import (
|
||||
)
|
||||
from plane.app.serializers import (
|
||||
IssueSerializer,
|
||||
InboxIssueSerializer,
|
||||
IntakeIssueSerializer,
|
||||
IssueCreateSerializer,
|
||||
IssueStateInboxSerializer,
|
||||
IssueStateIntakeSerializer,
|
||||
)
|
||||
from plane.utils.issue_filters import issue_filters
|
||||
from plane.bgtasks.issue_activities_task import issue_activity
|
||||
|
||||
|
||||
class InboxIssuePublicViewSet(BaseViewSet):
|
||||
serializer_class = InboxIssueSerializer
|
||||
model = InboxIssue
|
||||
class IntakeIssuePublicViewSet(BaseViewSet):
|
||||
serializer_class = IntakeIssueSerializer
|
||||
model = IntakeIssue
|
||||
|
||||
filterset_fields = [
|
||||
"status",
|
||||
@@ -52,34 +52,34 @@ class InboxIssuePublicViewSet(BaseViewSet):
|
||||
| Q(snoozed_till__isnull=True),
|
||||
project_id=self.kwargs.get("project_id"),
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
inbox_id=self.kwargs.get("inbox_id"),
|
||||
intake_id=self.kwargs.get("intake_id"),
|
||||
)
|
||||
.select_related("issue", "workspace", "project")
|
||||
)
|
||||
return InboxIssue.objects.none()
|
||||
return IntakeIssue.objects.none()
|
||||
|
||||
def list(self, request, anchor, inbox_id):
|
||||
def list(self, request, anchor, intake_id):
|
||||
project_deploy_board = DeployBoard.objects.get(
|
||||
anchor=anchor, entity_name="project"
|
||||
)
|
||||
if project_deploy_board.inbox is None:
|
||||
if project_deploy_board.intake is None:
|
||||
return Response(
|
||||
{"error": "Inbox is not enabled for this Project Board"},
|
||||
{"error": "Intake is not enabled for this Project Board"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
filters = issue_filters(request.query_params, "GET")
|
||||
issues = (
|
||||
Issue.objects.filter(
|
||||
issue_inbox__inbox_id=inbox_id,
|
||||
issue_intake__intake_id=intake_id,
|
||||
workspace_id=project_deploy_board.workspace_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
)
|
||||
.filter(**filters)
|
||||
.annotate(bridge_id=F("issue_inbox__id"))
|
||||
.annotate(bridge_id=F("issue_intake__id"))
|
||||
.select_related("workspace", "project", "state", "parent")
|
||||
.prefetch_related("assignees", "labels")
|
||||
.order_by("issue_inbox__snoozed_till", "issue_inbox__status")
|
||||
.order_by("issue_intake__snoozed_till", "issue_intake__status")
|
||||
.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(
|
||||
parent=OuterRef("id")
|
||||
@@ -105,26 +105,26 @@ class InboxIssuePublicViewSet(BaseViewSet):
|
||||
)
|
||||
.prefetch_related(
|
||||
Prefetch(
|
||||
"issue_inbox",
|
||||
queryset=InboxIssue.objects.only(
|
||||
"issue_intake",
|
||||
queryset=IntakeIssue.objects.only(
|
||||
"status", "duplicate_to", "snoozed_till", "source"
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
issues_data = IssueStateInboxSerializer(issues, many=True).data
|
||||
issues_data = IssueStateIntakeSerializer(issues, many=True).data
|
||||
return Response(
|
||||
issues_data,
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
def create(self, request, anchor, inbox_id):
|
||||
def create(self, request, anchor, intake_id):
|
||||
project_deploy_board = DeployBoard.objects.get(
|
||||
anchor=anchor, entity_name="project"
|
||||
)
|
||||
if project_deploy_board.inbox is None:
|
||||
if project_deploy_board.intake is None:
|
||||
return Response(
|
||||
{"error": "Inbox is not enabled for this Project Board"},
|
||||
{"error": "Intake is not enabled for this Project Board"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
@@ -151,7 +151,7 @@ class InboxIssuePublicViewSet(BaseViewSet):
|
||||
state, _ = State.objects.get_or_create(
|
||||
name="Triage",
|
||||
group="backlog",
|
||||
description="Default state for managing all Inbox Issues",
|
||||
description="Default state for managing all Intake Issues",
|
||||
project_id=project_deploy_board.project_id,
|
||||
color="#ff7700",
|
||||
)
|
||||
@@ -178,37 +178,37 @@ class InboxIssuePublicViewSet(BaseViewSet):
|
||||
current_instance=None,
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
)
|
||||
# create an inbox issue
|
||||
InboxIssue.objects.create(
|
||||
inbox_id=inbox_id,
|
||||
# create an intake issue
|
||||
IntakeIssue.objects.create(
|
||||
intake_id=intake_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
issue=issue,
|
||||
source=request.data.get("source", "in-app"),
|
||||
source=request.data.get("source", "IN-APP"),
|
||||
)
|
||||
|
||||
serializer = IssueStateInboxSerializer(issue)
|
||||
serializer = IssueStateIntakeSerializer(issue)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
def partial_update(self, request, anchor, inbox_id, pk):
|
||||
def partial_update(self, request, anchor, intake_id, pk):
|
||||
project_deploy_board = DeployBoard.objects.get(
|
||||
anchor=anchor, entity_name="project"
|
||||
)
|
||||
if project_deploy_board.inbox is None:
|
||||
if project_deploy_board.intake is None:
|
||||
return Response(
|
||||
{"error": "Inbox is not enabled for this Project Board"},
|
||||
{"error": "Intake is not enabled for this Project Board"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
inbox_issue = InboxIssue.objects.get(
|
||||
intake_issue = IntakeIssue.objects.get(
|
||||
pk=pk,
|
||||
workspace_id=project_deploy_board.workspace_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
inbox_id=inbox_id,
|
||||
intake_id=intake_id,
|
||||
)
|
||||
# Get the project member
|
||||
if str(inbox_issue.created_by_id) != str(request.user.id):
|
||||
if str(intake_issue.created_by_id) != str(request.user.id):
|
||||
return Response(
|
||||
{"error": "You cannot edit inbox issues"},
|
||||
{"error": "You cannot edit intake issues"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
@@ -216,7 +216,7 @@ class InboxIssuePublicViewSet(BaseViewSet):
|
||||
issue_data = request.data.pop("issue", False)
|
||||
|
||||
issue = Issue.objects.get(
|
||||
pk=inbox_issue.issue_id,
|
||||
pk=intake_issue.issue_id,
|
||||
workspace_id=project_deploy_board.workspace_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
)
|
||||
@@ -256,52 +256,52 @@ class InboxIssuePublicViewSet(BaseViewSet):
|
||||
issue_serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
def retrieve(self, request, anchor, inbox_id, pk):
|
||||
def retrieve(self, request, anchor, intake_id, pk):
|
||||
project_deploy_board = DeployBoard.objects.get(
|
||||
anchor=anchor, entity_name="project"
|
||||
)
|
||||
if project_deploy_board.inbox is None:
|
||||
if project_deploy_board.intake is None:
|
||||
return Response(
|
||||
{"error": "Inbox is not enabled for this Project Board"},
|
||||
{"error": "Intake is not enabled for this Project Board"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
inbox_issue = InboxIssue.objects.get(
|
||||
intake_issue = IntakeIssue.objects.get(
|
||||
pk=pk,
|
||||
workspace_id=project_deploy_board.workspace_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
inbox_id=inbox_id,
|
||||
intake_id=intake_id,
|
||||
)
|
||||
issue = Issue.objects.get(
|
||||
pk=inbox_issue.issue_id,
|
||||
pk=intake_issue.issue_id,
|
||||
workspace_id=project_deploy_board.workspace_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
)
|
||||
serializer = IssueStateInboxSerializer(issue)
|
||||
serializer = IssueStateIntakeSerializer(issue)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
def destroy(self, request, anchor, inbox_id, pk):
|
||||
def destroy(self, request, anchor, intake_id, pk):
|
||||
project_deploy_board = DeployBoard.objects.get(
|
||||
anchor=anchor, entity_name="project"
|
||||
)
|
||||
if project_deploy_board.inbox is None:
|
||||
if project_deploy_board.intake is None:
|
||||
return Response(
|
||||
{"error": "Inbox is not enabled for this Project Board"},
|
||||
{"error": "Intake is not enabled for this Project Board"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
inbox_issue = InboxIssue.objects.get(
|
||||
intake_issue = IntakeIssue.objects.get(
|
||||
pk=pk,
|
||||
workspace_id=project_deploy_board.workspace_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
inbox_id=inbox_id,
|
||||
intake_id=intake_id,
|
||||
)
|
||||
|
||||
if str(inbox_issue.created_by_id) != str(request.user.id):
|
||||
if str(intake_issue.created_by_id) != str(request.user.id):
|
||||
return Response(
|
||||
{"error": "You cannot delete inbox issue"},
|
||||
{"error": "You cannot delete intake issue"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
inbox_issue.delete()
|
||||
intake_issue.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
@@ -194,10 +194,10 @@ class ProjectIssuesPublicEndpoint(BaseAPIView):
|
||||
group_by_field_name=group_by,
|
||||
sub_group_by_field_name=sub_group_by,
|
||||
count_filter=Q(
|
||||
Q(issue_inbox__status=1)
|
||||
| Q(issue_inbox__status=-1)
|
||||
| Q(issue_inbox__status=2)
|
||||
| Q(issue_inbox__isnull=True),
|
||||
Q(issue_intake__status=1)
|
||||
| Q(issue_intake__status=-1)
|
||||
| Q(issue_intake__status=2)
|
||||
| Q(issue_intake__status=True),
|
||||
archived_at__isnull=True,
|
||||
is_draft=False,
|
||||
),
|
||||
@@ -222,10 +222,10 @@ class ProjectIssuesPublicEndpoint(BaseAPIView):
|
||||
),
|
||||
group_by_field_name=group_by,
|
||||
count_filter=Q(
|
||||
Q(issue_inbox__status=1)
|
||||
| Q(issue_inbox__status=-1)
|
||||
| Q(issue_inbox__status=2)
|
||||
| Q(issue_inbox__isnull=True),
|
||||
Q(issue_intake__status=1)
|
||||
| Q(issue_intake__status=-1)
|
||||
| Q(issue_intake__status=2)
|
||||
| Q(issue_intake__status=True),
|
||||
archived_at__isnull=True,
|
||||
is_draft=False,
|
||||
),
|
||||
|
||||
@@ -233,6 +233,7 @@ def filter_assignees(params, issue_filter, method, prefix=""):
|
||||
and params.get("assignees") != "null"
|
||||
):
|
||||
issue_filter[f"{prefix}assignees__in"] = params.get("assignees")
|
||||
issue_filter[f"{prefix}issue_assignee__deleted_at__isnull"] = True
|
||||
return issue_filter
|
||||
|
||||
|
||||
@@ -451,6 +452,27 @@ def filter_module(params, issue_filter, method, prefix=""):
|
||||
return issue_filter
|
||||
|
||||
|
||||
def filter_intake_status(params, issue_filter, method, prefix=""):
|
||||
if method == "GET":
|
||||
status = [
|
||||
item
|
||||
for item in params.get("intake_status").split(",")
|
||||
if item != "null"
|
||||
]
|
||||
if len(status) and "" not in status:
|
||||
issue_filter[f"{prefix}issue_intake__status__in"] = status
|
||||
else:
|
||||
if (
|
||||
params.get("intake_status", None)
|
||||
and len(params.get("intake_status"))
|
||||
and params.get("intake_status") != "null"
|
||||
):
|
||||
issue_filter[f"{prefix}issue_intake__status__in"] = params.get(
|
||||
"inbox_status"
|
||||
)
|
||||
return issue_filter
|
||||
|
||||
|
||||
def filter_inbox_status(params, issue_filter, method, prefix=""):
|
||||
if method == "GET":
|
||||
status = [
|
||||
@@ -459,14 +481,14 @@ def filter_inbox_status(params, issue_filter, method, prefix=""):
|
||||
if item != "null"
|
||||
]
|
||||
if len(status) and "" not in status:
|
||||
issue_filter[f"{prefix}issue_inbox__status__in"] = status
|
||||
issue_filter[f"{prefix}issue_intake__status__in"] = status
|
||||
else:
|
||||
if (
|
||||
params.get("inbox_status", None)
|
||||
and len(params.get("inbox_status"))
|
||||
and params.get("inbox_status") != "null"
|
||||
):
|
||||
issue_filter[f"{prefix}issue_inbox__status__in"] = params.get(
|
||||
issue_filter[f"{prefix}issue_intake__status__in"] = params.get(
|
||||
"inbox_status"
|
||||
)
|
||||
return issue_filter
|
||||
@@ -562,6 +584,7 @@ def issue_filters(query_params, method, prefix=""):
|
||||
"project": filter_project,
|
||||
"cycle": filter_cycle,
|
||||
"module": filter_module,
|
||||
"intake_status": filter_intake_status,
|
||||
"inbox_status": filter_inbox_status,
|
||||
"sub_issue": filter_sub_issue_toggle,
|
||||
"subscriber": filter_subscribed_issues,
|
||||
|
||||
@@ -63,7 +63,7 @@ pytz==2024.1
|
||||
# jwt
|
||||
PyJWT==2.8.0
|
||||
# OpenTelemetry
|
||||
opentelemetry-api==1.27.0
|
||||
opentelemetry-sdk==1.27.0
|
||||
opentelemetry-instrumentation-django==0.48b0
|
||||
opentelemetry-exporter-otlp==1.27.0
|
||||
opentelemetry-api==1.28.1
|
||||
opentelemetry-sdk==1.28.1
|
||||
opentelemetry-instrumentation-django==0.49b1
|
||||
opentelemetry-exporter-otlp==1.28.1
|
||||
+1
-1
@@ -22,7 +22,7 @@
|
||||
"devDependencies": {
|
||||
"prettier": "latest",
|
||||
"prettier-plugin-tailwindcss": "^0.5.4",
|
||||
"turbo": "^2.1.1"
|
||||
"turbo": "^2.3.0"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22",
|
||||
"name": "plane"
|
||||
|
||||
@@ -46,7 +46,7 @@ export const CoreEditorExtensionsWithoutProps = [
|
||||
CustomQuoteExtension,
|
||||
CustomHorizontalRule.configure({
|
||||
HTMLAttributes: {
|
||||
class: "my-4 border-custom-border-400",
|
||||
class: "py-4 border-custom-border-400",
|
||||
},
|
||||
}),
|
||||
CustomLinkExtension.configure({
|
||||
|
||||
@@ -78,7 +78,7 @@ export const CoreEditorExtensions = (args: TArguments) => {
|
||||
DropHandlerExtension(),
|
||||
CustomHorizontalRule.configure({
|
||||
HTMLAttributes: {
|
||||
class: "my-4 border-custom-border-400",
|
||||
class: "py-4 border-custom-border-400",
|
||||
},
|
||||
}),
|
||||
CustomKeymap,
|
||||
|
||||
@@ -67,7 +67,7 @@ export const CoreReadOnlyEditorExtensions = (props: Props) => {
|
||||
CustomQuoteExtension,
|
||||
CustomHorizontalRule.configure({
|
||||
HTMLAttributes: {
|
||||
class: "my-4 border-custom-border-400",
|
||||
class: "py-4 border-custom-border-400",
|
||||
},
|
||||
}),
|
||||
CustomLinkExtension.configure({
|
||||
|
||||
@@ -55,7 +55,7 @@ export const useCollaborativeEditor = (props: TCollaborativeEditorProps) => {
|
||||
},
|
||||
onSynced: () => setHasServerSynced(true),
|
||||
}),
|
||||
[id, realtimeConfig, serverHandler, user.id]
|
||||
[id, realtimeConfig, serverHandler, user]
|
||||
);
|
||||
|
||||
// destroy and disconnect connection on unmount
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
cursor: text;
|
||||
font-family: var(--font-style);
|
||||
font-size: var(--font-size-regular);
|
||||
line-height: 1.2;
|
||||
font-weight: 400;
|
||||
color: inherit;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
@@ -248,11 +248,6 @@ div[data-type="horizontalRule"] {
|
||||
}
|
||||
}
|
||||
|
||||
/* image resizer */
|
||||
.moveable-control-box {
|
||||
z-index: 10 !important;
|
||||
}
|
||||
|
||||
/* Cursor styles for the inline code blocks */
|
||||
@keyframes blink {
|
||||
49% {
|
||||
@@ -314,13 +309,23 @@ ul[data-type="taskList"] ul[data-type="taskList"] {
|
||||
}
|
||||
/* end numbered, bulleted and to-do lists spacing */
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
/* tailwind typography */
|
||||
.prose :where(h1):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
|
||||
&:not(:first-child) {
|
||||
margin-top: 2rem;
|
||||
padding-top: 28px;
|
||||
}
|
||||
|
||||
margin-bottom: 4px;
|
||||
padding-bottom: 4px;
|
||||
font-size: var(--font-size-h1);
|
||||
line-height: var(--line-height-h1);
|
||||
font-weight: 600;
|
||||
@@ -328,10 +333,10 @@ ul[data-type="taskList"] ul[data-type="taskList"] {
|
||||
|
||||
.prose :where(h2):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
|
||||
&:not(:first-child) {
|
||||
margin-top: 1.4rem;
|
||||
padding-top: 28px;
|
||||
}
|
||||
|
||||
margin-bottom: 1px;
|
||||
padding-bottom: 4px;
|
||||
font-size: var(--font-size-h2);
|
||||
line-height: var(--line-height-h2);
|
||||
font-weight: 600;
|
||||
@@ -339,10 +344,10 @@ ul[data-type="taskList"] ul[data-type="taskList"] {
|
||||
|
||||
.prose :where(h3):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
|
||||
&:not(:first-child) {
|
||||
margin-top: 1rem;
|
||||
padding-top: 28px;
|
||||
}
|
||||
|
||||
margin-bottom: 1px;
|
||||
padding-bottom: 4px;
|
||||
font-size: var(--font-size-h3);
|
||||
line-height: var(--line-height-h3);
|
||||
font-weight: 600;
|
||||
@@ -350,10 +355,10 @@ ul[data-type="taskList"] ul[data-type="taskList"] {
|
||||
|
||||
.prose :where(h4):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
|
||||
&:not(:first-child) {
|
||||
margin-top: 1rem;
|
||||
padding-top: 28px;
|
||||
}
|
||||
|
||||
margin-bottom: 1px;
|
||||
padding-bottom: 4px;
|
||||
font-size: var(--font-size-h4);
|
||||
line-height: var(--line-height-h4);
|
||||
font-weight: 600;
|
||||
@@ -361,10 +366,10 @@ ul[data-type="taskList"] ul[data-type="taskList"] {
|
||||
|
||||
.prose :where(h5):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
|
||||
&:not(:first-child) {
|
||||
margin-top: 1rem;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
margin-bottom: 1px;
|
||||
padding-bottom: 4px;
|
||||
font-size: var(--font-size-h5);
|
||||
line-height: var(--line-height-h5);
|
||||
font-weight: 600;
|
||||
@@ -372,30 +377,40 @@ ul[data-type="taskList"] ul[data-type="taskList"] {
|
||||
|
||||
.prose :where(h6):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
|
||||
&:not(:first-child) {
|
||||
margin-top: 1rem;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
margin-bottom: 1px;
|
||||
padding-bottom: 4px;
|
||||
font-size: var(--font-size-h6);
|
||||
line-height: var(--line-height-h6);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.prose :where(p):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
|
||||
&:not(:first-child) {
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
&:not(:first-child) {
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
margin-bottom: 1px;
|
||||
padding: 3px 0;
|
||||
font-size: var(--font-size-regular);
|
||||
line-height: var(--line-height-regular);
|
||||
}
|
||||
|
||||
p + p {
|
||||
padding-top: 8px !important;
|
||||
}
|
||||
|
||||
.prose :where(ol):not(:where([class~="not-prose"], [class~="not-prose"] *)) li p,
|
||||
.prose :where(ul):not(:where([class~="not-prose"], [class~="not-prose"] *)) li p {
|
||||
font-size: var(--font-size-list);
|
||||
@@ -432,11 +447,6 @@ ul[data-type="taskList"] ul[data-type="taskList"] {
|
||||
[data-text-color="purple"] {
|
||||
color: var(--editor-colors-purple-text);
|
||||
}
|
||||
/* [data-text-color="pink-blue-gradient"] {
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
background-image: linear-gradient(90deg, #a961cd 50%, #e75962 100%);
|
||||
} */
|
||||
/* end text colors */
|
||||
|
||||
/* background colors */
|
||||
|
||||
@@ -27,10 +27,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
.table-wrapper table th {
|
||||
font-weight: 500;
|
||||
text-align: left;
|
||||
background-color: rgba(var(--color-background-90));
|
||||
.table-wrapper table {
|
||||
th {
|
||||
font-weight: 500;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
tr[background="none"],
|
||||
tr:not([background]) {
|
||||
th {
|
||||
background-color: rgba(var(--color-background-90));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-wrapper table .selectedCell {
|
||||
|
||||
@@ -46,8 +46,8 @@
|
||||
--font-size-h5: 1.125rem;
|
||||
--font-size-h6: 1rem;
|
||||
--font-size-regular: 1rem;
|
||||
--font-size-code: 0.85rem;
|
||||
--font-size-list: var(--font-size-regular);
|
||||
--font-size-code: var(--font-size-regular);
|
||||
|
||||
--line-height-h1: 2.25rem;
|
||||
--line-height-h2: 2rem;
|
||||
@@ -56,8 +56,8 @@
|
||||
--line-height-h5: 1.5rem;
|
||||
--line-height-h6: 1.5rem;
|
||||
--line-height-regular: 1.5rem;
|
||||
--line-height-code: 1.5rem;
|
||||
--line-height-list: var(--line-height-regular);
|
||||
--line-height-code: var(--line-height-regular);
|
||||
}
|
||||
&.small-font {
|
||||
--font-size-h1: 1.4rem;
|
||||
@@ -67,8 +67,8 @@
|
||||
--font-size-h5: 0.9rem;
|
||||
--font-size-h6: 0.8rem;
|
||||
--font-size-regular: 0.8rem;
|
||||
--font-size-code: 0.8rem;
|
||||
--font-size-list: var(--font-size-regular);
|
||||
--font-size-code: var(--font-size-regular);
|
||||
|
||||
--line-height-h1: 1.8rem;
|
||||
--line-height-h2: 1.6rem;
|
||||
@@ -77,8 +77,8 @@
|
||||
--line-height-h5: 1.2rem;
|
||||
--line-height-h6: 1.2rem;
|
||||
--line-height-regular: 1.2rem;
|
||||
--line-height-code: 1.2rem;
|
||||
--line-height-list: var(--line-height-regular);
|
||||
--line-height-code: var(--line-height-regular);
|
||||
}
|
||||
/* end font sizes and line heights */
|
||||
|
||||
|
||||
+5
-1
@@ -18,7 +18,11 @@ export interface IProject {
|
||||
close_in: number;
|
||||
created_at: Date;
|
||||
created_by: string;
|
||||
cover_image_url: string;
|
||||
// only for uploading the cover image
|
||||
cover_image_asset?: null;
|
||||
cover_image?: string;
|
||||
// only for rendering the cover image
|
||||
cover_image_url: readonly string;
|
||||
cycle_view: boolean;
|
||||
issue_views_view: boolean;
|
||||
module_view: boolean;
|
||||
|
||||
Vendored
+13
-4
@@ -3,7 +3,6 @@ import { TUserPermissions } from "./enums";
|
||||
|
||||
type TLoginMediums = "email" | "magic-code" | "github" | "gitlab" | "google";
|
||||
|
||||
|
||||
export interface IUserLite {
|
||||
avatar_url: string;
|
||||
display_name: string;
|
||||
@@ -14,7 +13,11 @@ export interface IUserLite {
|
||||
last_name: string;
|
||||
}
|
||||
export interface IUser extends IUserLite {
|
||||
cover_image_url: string | null;
|
||||
// only for uploading the cover image
|
||||
cover_image_asset?: string | null;
|
||||
cover_image?: string | null;
|
||||
// only for rendering the cover image
|
||||
cover_image_url: readonly (string | null);
|
||||
date_joined: string;
|
||||
email: string;
|
||||
is_active: boolean;
|
||||
@@ -90,7 +93,6 @@ export interface IUserTheme {
|
||||
sidebarBackground: string | undefined;
|
||||
}
|
||||
|
||||
|
||||
export interface IUserMemberLite extends IUserLite {
|
||||
email?: string;
|
||||
}
|
||||
@@ -153,7 +155,14 @@ export interface IUserProfileProjectSegregation {
|
||||
id: string;
|
||||
pending_issues: number;
|
||||
}[];
|
||||
user_data: Pick<IUser, "avatar_url" | "cover_image_url" | "display_name" | "first_name" | "last_name"> & {
|
||||
user_data: Pick<
|
||||
IUser,
|
||||
| "avatar_url"
|
||||
| "cover_image_url"
|
||||
| "display_name"
|
||||
| "first_name"
|
||||
| "last_name"
|
||||
> & {
|
||||
date_joined: Date;
|
||||
user_timezone: string;
|
||||
};
|
||||
|
||||
Vendored
+2
-2
@@ -40,8 +40,8 @@ export type TIssueOrderByOptions =
|
||||
| "-issue_cycle__cycle__name"
|
||||
| "target_date"
|
||||
| "-target_date"
|
||||
| "estimate_point"
|
||||
| "-estimate_point"
|
||||
| "estimate_point__key"
|
||||
| "-estimate_point__key"
|
||||
| "start_date"
|
||||
| "-start_date"
|
||||
| "link_count"
|
||||
|
||||
@@ -101,6 +101,19 @@
|
||||
--color-sidebar-shadow-2xl: var(--color-shadow-2xl);
|
||||
--color-sidebar-shadow-3xl: var(--color-shadow-3xl);
|
||||
--color-sidebar-shadow-4xl: var(--color-shadow-4xl);
|
||||
|
||||
/* pi */
|
||||
--color-pi-50: var(--color-background-90);
|
||||
--color-pi-100: var(--color-background-90);
|
||||
--color-pi-200: var(--color-primary-200);
|
||||
--color-pi-300: var(--color-primary-200);
|
||||
--color-pi-400: var(--color-primary-200);
|
||||
--color-pi-500: var(--color-primary-200);
|
||||
--color-pi-600: 151, 150, 246;
|
||||
--color-pi-700: var(--color-primary-100);
|
||||
--color-pi-800: 57, 56, 149;
|
||||
--color-pi-900: 30, 29, 78;
|
||||
--color-pi-950: 14, 14, 37;
|
||||
}
|
||||
|
||||
[data-theme="light"],
|
||||
@@ -110,6 +123,19 @@
|
||||
--color-background-100: 255, 255, 255; /* primary bg */
|
||||
--color-background-90: 247, 247, 247; /* secondary bg */
|
||||
--color-background-80: 232, 232, 232; /* tertiary bg */
|
||||
|
||||
/* pi */
|
||||
--color-pi-50: var(--color-background-90);
|
||||
--color-pi-100: var(--color-background-90);
|
||||
--color-pi-200: var(--color-primary-200);
|
||||
--color-pi-300: var(--color-primary-200);
|
||||
--color-pi-400: var(--color-primary-200);
|
||||
--color-pi-500: var(--color-primary-200);
|
||||
--color-pi-600: 151, 150, 246;
|
||||
--color-pi-700: var(--color-primary-100);
|
||||
--color-pi-800: 57, 56, 149;
|
||||
--color-pi-900: 30, 29, 78;
|
||||
--color-pi-950: 14, 14, 37;
|
||||
}
|
||||
|
||||
[data-theme="light"] {
|
||||
@@ -200,6 +226,18 @@
|
||||
--color-shadow-xl: 0px 0px 14px 0px rgba(0, 0, 0, 0.25), 0px 6px 10px 0px rgba(0, 0, 0, 0.55);
|
||||
--color-shadow-2xl: 0px 0px 18px 0px rgba(0, 0, 0, 0.25), 0px 8px 12px 0px rgba(0, 0, 0, 0.6);
|
||||
--color-shadow-3xl: 0px 4px 24px 0px rgba(0, 0, 0, 0.3), 0px 12px 40px 0px rgba(0, 0, 0, 0.65);
|
||||
/* pi */
|
||||
--color-pi-50: var(--color-background-90);
|
||||
--color-pi-100: var(--color-background-90);
|
||||
--color-pi-200: var(--color-primary-200);
|
||||
--color-pi-300: var(--color-primary-200);
|
||||
--color-pi-400: var(--color-primary-200);
|
||||
--color-pi-500: var(--color-primary-200);
|
||||
--color-pi-600: 151, 150, 246;
|
||||
--color-pi-700: var(--color-primary-100);
|
||||
--color-pi-800: 57, 56, 149;
|
||||
--color-pi-900: 30, 29, 78;
|
||||
--color-pi-950: 14, 14, 37;
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
|
||||
@@ -253,7 +253,7 @@ export class IssueDetailStore implements IIssueDetailStore {
|
||||
anchor,
|
||||
{
|
||||
entity_identifier: commentID ?? "",
|
||||
entity_type: EFileAssetType.ISSUE_ATTACHMENT,
|
||||
entity_type: EFileAssetType.ISSUE_DESCRIPTION,
|
||||
},
|
||||
file
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
@@ -62,6 +62,11 @@ const WorkspaceDashboardPage = observer(() => {
|
||||
workspace_slug && project_id && is_inbox_issue ? () => fetchUserProjectInfo(workspace_slug, project_id) : null
|
||||
);
|
||||
|
||||
const embedRemoveCurrentNotification = useCallback(
|
||||
() => setCurrentSelectedNotificationId(undefined),
|
||||
[setCurrentSelectedNotificationId]
|
||||
);
|
||||
|
||||
// clearing up the selected notifications when unmounting the page
|
||||
useEffect(
|
||||
() => () => {
|
||||
@@ -95,15 +100,12 @@ const WorkspaceDashboardPage = observer(() => {
|
||||
projectId={project_id}
|
||||
inboxIssueId={issue_id}
|
||||
isNotificationEmbed
|
||||
embedRemoveCurrentNotification={() => setCurrentSelectedNotificationId(undefined)}
|
||||
embedRemoveCurrentNotification={embedRemoveCurrentNotification}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<IssuePeekOverview
|
||||
embedIssue
|
||||
embedRemoveCurrentNotification={() => setCurrentSelectedNotificationId(undefined)}
|
||||
/>
|
||||
<IssuePeekOverview embedIssue embedRemoveCurrentNotification={embedRemoveCurrentNotification} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -70,11 +70,15 @@ const ProfileSettingsPage = observer(() => {
|
||||
first_name: formData.first_name,
|
||||
last_name: formData.last_name,
|
||||
avatar_url: formData.avatar_url,
|
||||
cover_image_url: formData.cover_image_url,
|
||||
role: formData.role,
|
||||
display_name: formData?.display_name,
|
||||
user_timezone: formData.user_timezone,
|
||||
};
|
||||
// if unsplash or a pre-defined image is uploaded, delete the old uploaded asset
|
||||
if (formData.cover_image_url?.startsWith("http")) {
|
||||
payload.cover_image = formData.cover_image_url;
|
||||
payload.cover_image_asset = null;
|
||||
}
|
||||
|
||||
const updateCurrentUserDetail = updateCurrentUser(payload).finally(() => setIsLoading(false));
|
||||
setPromiseToast(updateCurrentUserDetail, {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./maintenance-message";
|
||||
@@ -0,0 +1,6 @@
|
||||
export const MaintenanceMessage = () => (
|
||||
<h1 className="text-xl font-medium text-custom-text-100 text-center md:text-left">
|
||||
Plane didn't start up. This could be because one or more Plane services failed to start. <br /> Choose View
|
||||
Logs from setup.sh and Docker logs to be sure.
|
||||
</h1>
|
||||
);
|
||||
@@ -1,5 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { FC, Fragment } from "react";
|
||||
|
||||
export const MaintenanceMode: FC = () => <Fragment />;
|
||||
@@ -74,6 +74,11 @@ export const CreateProjectForm: FC<TCreateProjectFormProps> = observer((props) =
|
||||
// Upper case identifier
|
||||
formData.identifier = formData.identifier?.toUpperCase();
|
||||
const coverImage = formData.cover_image_url;
|
||||
// if unsplash or a pre-defined image is uploaded, delete the old uploaded asset
|
||||
if (coverImage?.startsWith("http")) {
|
||||
formData.cover_image = coverImage;
|
||||
formData.cover_image_asset = null;
|
||||
}
|
||||
|
||||
return createProject(workspaceSlug.toString(), formData)
|
||||
.then(async (res) => {
|
||||
|
||||
@@ -13,7 +13,7 @@ export const getRelationActivityContent = (activity: TIssueActivity | undefined)
|
||||
case "duplicate":
|
||||
return activity.old_value === "" ? `marked this issue as duplicate of ` : `removed this issue as a duplicate of `;
|
||||
case "relates_to":
|
||||
activity.old_value === "" ? `marked that this issue relates to ` : `removed the relation from `;
|
||||
return activity.old_value === "" ? `marked that this issue relates to ` : `removed the relation from `;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
@@ -2,21 +2,24 @@
|
||||
|
||||
// icons
|
||||
import { Home, Inbox, PenSquare } from "lucide-react";
|
||||
|
||||
// ui
|
||||
import { UserActivityIcon } from "@plane/ui";
|
||||
import { Props } from "@/components/icons/types";
|
||||
import { TLinkOptions } from "@/constants/dashboard";
|
||||
import { EUserPermissions } from "@/plane-web/constants/user-permissions";
|
||||
// plane web types
|
||||
import { TSidebarUserMenuItemKeys } from "@/plane-web/types/dashboard";
|
||||
|
||||
export const SIDEBAR_USER_MENU_ITEMS: {
|
||||
key: string;
|
||||
export type TSidebarUserMenuItems = {
|
||||
key: TSidebarUserMenuItemKeys;
|
||||
label: string;
|
||||
href: string;
|
||||
access: EUserPermissions[];
|
||||
highlight: (pathname: string, baseUrl: string, options?: TLinkOptions) => boolean;
|
||||
Icon: React.FC<Props>;
|
||||
}[] = [
|
||||
};
|
||||
|
||||
export const SIDEBAR_USER_MENU_ITEMS: TSidebarUserMenuItems[] = [
|
||||
{
|
||||
key: "home",
|
||||
label: "Home",
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// plane web types
|
||||
import { TSidebarUserMenuItemKeys } from "@/plane-web/types/dashboard";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const isUserFeatureEnabled = (featureKey: TSidebarUserMenuItemKeys) => true;
|
||||
@@ -5,7 +5,11 @@ import { computedFn } from "mobx-utils";
|
||||
// components
|
||||
import { ChartDataType, IBlockUpdateDependencyData, IGanttBlock, TGanttViews } from "@/components/gantt-chart";
|
||||
import { currentViewDataWithView } from "@/components/gantt-chart/data";
|
||||
import { getDateFromPositionOnGantt, getItemPositionWidth } from "@/components/gantt-chart/views/helpers";
|
||||
import {
|
||||
getDateFromPositionOnGantt,
|
||||
getItemPositionWidth,
|
||||
getPositionFromDate,
|
||||
} from "@/components/gantt-chart/views/helpers";
|
||||
// helpers
|
||||
import { renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
||||
// store
|
||||
@@ -47,6 +51,7 @@ export interface IBaseTimelineStore {
|
||||
initGantt: () => void;
|
||||
|
||||
getDateFromPositionOnGantt: (position: number, offsetDays: number) => Date | undefined;
|
||||
getPositionFromDateOnGantt: (date: string | Date, offSetWidth: number) => number | undefined;
|
||||
}
|
||||
|
||||
export class BaseTimeLineStore implements IBaseTimelineStore {
|
||||
@@ -186,7 +191,7 @@ export class BaseTimeLineStore implements IBaseTimelineStore {
|
||||
start_date: blockData?.start_date ?? undefined,
|
||||
target_date: blockData?.target_date ?? undefined,
|
||||
};
|
||||
if (this.currentViewData && this.currentViewData?.data?.startDate && this.currentViewData?.data?.dayWidth) {
|
||||
if (this.currentViewData && (this.currentViewData?.data?.startDate || this.currentViewData?.data?.dayWidth)) {
|
||||
block.position = getItemPositionWidth(this.currentViewData, block);
|
||||
}
|
||||
|
||||
@@ -227,6 +232,15 @@ export class BaseTimeLineStore implements IBaseTimelineStore {
|
||||
return Math.round(position / this.currentViewData.data.dayWidth);
|
||||
};
|
||||
|
||||
/**
|
||||
* returns position of the date on chart
|
||||
*/
|
||||
getPositionFromDateOnGantt = computedFn((date: string | Date, offSetWidth: number) => {
|
||||
if (!this.currentViewData) return;
|
||||
|
||||
return getPositionFromDate(this.currentViewData, date, offSetWidth);
|
||||
});
|
||||
|
||||
/**
|
||||
* returns the date at which the position corresponds to on the timeline chart
|
||||
*/
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export type TSidebarUserMenuItemKeys = "home" | "your-work" | "notifications" | "drafts";
|
||||
@@ -54,7 +54,7 @@ export const RichTextEditor = forwardRef<EditorRefApi, RichTextEditorWrapperProp
|
||||
suggestions: mentionSuggestions,
|
||||
}}
|
||||
{...rest}
|
||||
containerClassName={cn("relative pl-3 pb-3", containerClassName)}
|
||||
containerClassName={cn("relative pl-3", containerClassName)}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -68,7 +68,7 @@ export const BlockRow: React.FC<Props> = observer((props) => {
|
||||
// hide the block if it doesn't have start and target dates and showAllBlocks is false
|
||||
if (!block || !block.data || (!showAllBlocks && !(block.start_date && block.target_date))) return null;
|
||||
|
||||
const isBlockVisibleOnChart = block.start_date && block.target_date;
|
||||
const isBlockVisibleOnChart = block.start_date || block.target_date;
|
||||
const isBlockSelected = selectionHelpers.getIsEntitySelected(block.id);
|
||||
const isBlockFocused = selectionHelpers.getIsEntityActive(block.id);
|
||||
const isBlockHoveredOn = isBlockActive(block.id);
|
||||
|
||||
@@ -46,10 +46,11 @@ export const GanttChartBlock: React.FC<Props> = observer((props) => {
|
||||
|
||||
const { isMoving, handleBlockDrag } = useGanttResizable(block, resizableRef, ganttContainerRef, updateBlockDates);
|
||||
|
||||
// hide the block if it doesn't have start and target dates and showAllBlocks is false
|
||||
if (!block || (!showAllBlocks && !(block.start_date && block.target_date))) return null;
|
||||
const isBlockVisibleOnChart = block?.start_date || block?.target_date;
|
||||
const isBlockComplete = block?.start_date && block?.target_date;
|
||||
|
||||
const isBlockVisibleOnChart = block.start_date && block.target_date;
|
||||
// hide the block if it doesn't have start and target dates and showAllBlocks is false
|
||||
if (!block || (!showAllBlocks && !isBlockVisibleOnChart)) return null;
|
||||
|
||||
if (!block.data) return null;
|
||||
|
||||
@@ -63,7 +64,7 @@ export const GanttChartBlock: React.FC<Props> = observer((props) => {
|
||||
ref={resizableRef}
|
||||
style={{
|
||||
height: `${BLOCK_HEIGHT}px`,
|
||||
transform: `translateX(${block.position?.marginLeft}px)`,
|
||||
marginLeft: `${block.position?.marginLeft}px`,
|
||||
width: `${block.position?.width}px`,
|
||||
}}
|
||||
>
|
||||
@@ -88,7 +89,7 @@ export const GanttChartBlock: React.FC<Props> = observer((props) => {
|
||||
handleBlockDrag={handleBlockDrag}
|
||||
enableBlockLeftResize={enableBlockLeftResize}
|
||||
enableBlockRightResize={enableBlockRightResize}
|
||||
enableBlockMove={enableBlockMove}
|
||||
enableBlockMove={enableBlockMove && !!isBlockComplete}
|
||||
isMoving={isMoving}
|
||||
ganttContainerRef={ganttContainerRef}
|
||||
/>
|
||||
|
||||
@@ -28,7 +28,7 @@ import { IssueBulkOperationsRoot } from "@/plane-web/components/issues";
|
||||
import { useBulkOperationStatus } from "@/plane-web/hooks/use-bulk-operation-status";
|
||||
//
|
||||
import { GanttChartRowList } from "../blocks/block-row-list";
|
||||
import { GANTT_SELECT_GROUP, HEADER_HEIGHT } from "../constants";
|
||||
import { DEFAULT_BLOCK_WIDTH, GANTT_SELECT_GROUP, HEADER_HEIGHT } from "../constants";
|
||||
import { getItemPositionWidth } from "../views";
|
||||
import { TimelineDragHelper } from "./timeline-drag-helper";
|
||||
|
||||
@@ -108,14 +108,20 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
|
||||
|
||||
const approxRangeLeft = scrollLeft;
|
||||
const approxRangeRight = scrollWidth - (scrollLeft + clientWidth);
|
||||
const calculatedRangeRight = itemsContainerWidth - (scrollLeft + clientWidth);
|
||||
|
||||
if (approxRangeRight < clientWidth) updateCurrentViewRenderPayload("right", currentView);
|
||||
if (approxRangeLeft < clientWidth) updateCurrentViewRenderPayload("left", currentView);
|
||||
if (approxRangeRight < clientWidth || calculatedRangeRight < clientWidth) {
|
||||
updateCurrentViewRenderPayload("right", currentView);
|
||||
}
|
||||
if (approxRangeLeft < clientWidth) {
|
||||
updateCurrentViewRenderPayload("left", currentView);
|
||||
}
|
||||
};
|
||||
|
||||
const handleScrollToBlock = (block: IGanttBlock) => {
|
||||
const scrollContainer = ganttContainerRef.current as HTMLDivElement;
|
||||
const scrollToDate = getDate(block.start_date);
|
||||
const scrollToEndDate = !block.start_date && block.target_date;
|
||||
const scrollToDate = block.start_date ? getDate(block.start_date) : getDate(block.target_date);
|
||||
let chartData;
|
||||
|
||||
if (!scrollContainer || !currentViewData || !scrollToDate) return;
|
||||
@@ -129,7 +135,8 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
|
||||
const updatedPosition = getItemPositionWidth(chartData ?? currentViewData, block);
|
||||
|
||||
setTimeout(() => {
|
||||
if (updatedPosition) scrollContainer.scrollLeft = updatedPosition.marginLeft - 4;
|
||||
if (updatedPosition)
|
||||
scrollContainer.scrollLeft = updatedPosition.marginLeft - 4 - (scrollToEndDate ? DEFAULT_BLOCK_WIDTH : 0);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -189,6 +196,7 @@ export const GanttChartMainContent: React.FC<Props> = observer((props) => {
|
||||
style={{
|
||||
width: `${itemsContainerWidth}px`,
|
||||
transform: `translateY(${HEADER_HEIGHT}px)`,
|
||||
paddingBottom: `${HEADER_HEIGHT}px`,
|
||||
}}
|
||||
>
|
||||
<GanttChartRowList
|
||||
|
||||
@@ -25,7 +25,7 @@ export const MonthChartView: FC<any> = observer(() => {
|
||||
const marginLeftDays = getNumberOfDaysBetweenTwoDates(monthsStartDate, weeksStartDate);
|
||||
|
||||
return (
|
||||
<div className={`absolute top-0 left-0 h-max w-max flex`} style={{ minHeight: `calc(100% + ${HEADER_HEIGHT}px` }}>
|
||||
<div className={`absolute top-0 left-0 min-h-full h-max w-max flex`}>
|
||||
{currentViewData && (
|
||||
<div className="relative flex flex-col outline-[0.25px] outline outline-custom-border-200">
|
||||
{/** Header Div */}
|
||||
|
||||
@@ -15,7 +15,7 @@ export const QuarterChartView: FC<any> = observer(() => {
|
||||
const quarterBlocks: IQuarterMonthBlock[] = groupMonthsToQuarters(monthBlocks);
|
||||
|
||||
return (
|
||||
<div className={`absolute top-0 left-0 h-max w-max flex`} style={{ minHeight: `calc(100% + ${HEADER_HEIGHT}px` }}>
|
||||
<div className={`absolute top-0 left-0 min-h-full h-max w-max flex`}>
|
||||
{currentViewData &&
|
||||
quarterBlocks?.map((quarterBlock, rootIndex) => (
|
||||
<div
|
||||
|
||||
@@ -13,7 +13,7 @@ export const WeekChartView: FC<any> = observer(() => {
|
||||
const weekBlocks: IWeekBlock[] = renderView;
|
||||
|
||||
return (
|
||||
<div className={`absolute top-0 left-0 h-max w-max flex`} style={{ minHeight: `calc(100% + ${HEADER_HEIGHT}px` }}>
|
||||
<div className={`absolute top-0 left-0 min-h-full h-max w-max flex`}>
|
||||
{currentViewData &&
|
||||
weekBlocks?.map((block, rootIndex) => (
|
||||
<div
|
||||
|
||||
@@ -6,4 +6,6 @@ export const GANTT_BREADCRUMBS_HEIGHT = 40;
|
||||
|
||||
export const SIDEBAR_WIDTH = 360;
|
||||
|
||||
export const DEFAULT_BLOCK_WIDTH = 60;
|
||||
|
||||
export const GANTT_SELECT_GROUP = "gantt-issues";
|
||||
|
||||
@@ -71,7 +71,7 @@ export const useGanttResizable = (
|
||||
// calculate new marginLeft and update the initial marginLeft to the newly calculated one
|
||||
marginLeft = Math.round(mouseX / dayWidth) * dayWidth;
|
||||
// get Dimensions from dom's style
|
||||
const prevMarginLeft = parseFloat(resizableDiv.style.transform.slice(11, -3));
|
||||
const prevMarginLeft = parseFloat(resizableDiv.style.marginLeft.slice(0, -2));
|
||||
const prevWidth = parseFloat(resizableDiv.style.width.slice(0, -2));
|
||||
// calculate new width
|
||||
const marginDelta = prevMarginLeft - marginLeft;
|
||||
@@ -88,7 +88,7 @@ export const useGanttResizable = (
|
||||
if (width < dayWidth) return;
|
||||
|
||||
resizableDiv.style.width = `${width}px`;
|
||||
resizableDiv.style.transform = `translateX(${marginLeft}px)`;
|
||||
resizableDiv.style.marginLeft = `${marginLeft}px`;
|
||||
|
||||
const deltaLeft = Math.round((marginLeft - (block.position?.marginLeft ?? 0)) / dayWidth) * dayWidth;
|
||||
const deltaWidth = Math.round((width - (block.position?.width ?? 0)) / dayWidth) * dayWidth;
|
||||
|
||||
@@ -34,7 +34,7 @@ export const GanttDnDHOC = observer((props: Props) => {
|
||||
draggable({
|
||||
element,
|
||||
canDrag: () => isDragEnabled,
|
||||
getInitialData: () => ({ id }),
|
||||
getInitialData: () => ({ id, dragInstanceId: "GANTT_REORDER" }),
|
||||
onDragStart: () => {
|
||||
setIsDragging(true);
|
||||
},
|
||||
@@ -44,7 +44,7 @@ export const GanttDnDHOC = observer((props: Props) => {
|
||||
}),
|
||||
dropTargetForElements({
|
||||
element,
|
||||
canDrop: ({ source }) => source?.data?.id !== id,
|
||||
canDrop: ({ source }) => source?.data?.id !== id && source?.data?.dragInstanceId === "GANTT_REORDER",
|
||||
getData: ({ input, element }) => {
|
||||
const data = { id };
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user