diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 92b44af5b..d7b94d245 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -1,8 +1,9 @@
name: "CodeQL"
on:
+ workflow_dispatch:
push:
- branches: ["master"]
+ branches: ["develop", "preview", "master"]
pull_request:
branches: ["develop", "preview", "master"]
schedule:
diff --git a/apiserver/plane/api/serializers/issue.py b/apiserver/plane/api/serializers/issue.py
index b8f194b32..c78b109ef 100644
--- a/apiserver/plane/api/serializers/issue.py
+++ b/apiserver/plane/api/serializers/issue.py
@@ -1,32 +1,33 @@
-from lxml import html
+from django.core.exceptions import ValidationError
+from django.core.validators import URLValidator
# Django imports
from django.utils import timezone
-from django.core.validators import URLValidator
-from django.core.exceptions import ValidationError
+from lxml import html
# Third party imports
from rest_framework import serializers
# Module imports
from plane.db.models import (
- User,
Issue,
- State,
+ IssueActivity,
IssueAssignee,
- Label,
+ IssueAttachment,
+ IssueComment,
IssueLabel,
IssueLink,
- IssueComment,
- IssueAttachment,
- IssueActivity,
+ Label,
ProjectMember,
+ State,
+ User,
)
+
from .base import BaseSerializer
-from .cycle import CycleSerializer, CycleLiteSerializer
-from .module import ModuleSerializer, ModuleLiteSerializer
-from .user import UserLiteSerializer
+from .cycle import CycleLiteSerializer, CycleSerializer
+from .module import ModuleLiteSerializer, ModuleSerializer
from .state import StateLiteSerializer
+from .user import UserLiteSerializer
class IssueSerializer(BaseSerializer):
@@ -79,7 +80,7 @@ class IssueSerializer(BaseSerializer):
data["description_html"] = parsed_str
except Exception as e:
- raise serializers.ValidationError(f"Invalid HTML: {str(e)}")
+ raise serializers.ValidationError("Invalid HTML passed")
# Validate assignees are from project
if data.get("assignees", []):
@@ -294,7 +295,7 @@ class IssueLinkSerializer(BaseSerializer):
raise serializers.ValidationError("Invalid URL format.")
# Check URL scheme
- if not value.startswith(('http://', 'https://')):
+ if not value.startswith(("http://", "https://")):
raise serializers.ValidationError("Invalid URL scheme.")
return value
@@ -366,7 +367,7 @@ class IssueCommentSerializer(BaseSerializer):
data["comment_html"] = parsed_str
except Exception as e:
- raise serializers.ValidationError(f"Invalid HTML: {str(e)}")
+ raise serializers.ValidationError("Invalid HTML passed")
return data
diff --git a/apiserver/plane/app/views/cycle/base.py b/apiserver/plane/app/views/cycle/base.py
index 48e475a74..d323a0f63 100644
--- a/apiserver/plane/app/views/cycle/base.py
+++ b/apiserver/plane/app/views/cycle/base.py
@@ -103,7 +103,8 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
.annotate(is_favorite=Exists(favorite_subquery))
.annotate(
total_issues=Count(
- "issue_cycle",
+ "issue_cycle__issue__id",
+ distinct=True,
filter=Q(
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
@@ -112,7 +113,8 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
)
.annotate(
completed_issues=Count(
- "issue_cycle__issue__state__group",
+ "issue_cycle__issue__id",
+ distinct=True,
filter=Q(
issue_cycle__issue__state__group="completed",
issue_cycle__issue__archived_at__isnull=True,
@@ -122,7 +124,8 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
)
.annotate(
cancelled_issues=Count(
- "issue_cycle__issue__state__group",
+ "issue_cycle__issue__id",
+ distinct=True,
filter=Q(
issue_cycle__issue__state__group="cancelled",
issue_cycle__issue__archived_at__isnull=True,
@@ -132,7 +135,8 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
)
.annotate(
started_issues=Count(
- "issue_cycle__issue__state__group",
+ "issue_cycle__issue__id",
+ distinct=True,
filter=Q(
issue_cycle__issue__state__group="started",
issue_cycle__issue__archived_at__isnull=True,
@@ -142,7 +146,8 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
)
.annotate(
unstarted_issues=Count(
- "issue_cycle__issue__state__group",
+ "issue_cycle__issue__id",
+ distinct=True,
filter=Q(
issue_cycle__issue__state__group="unstarted",
issue_cycle__issue__archived_at__isnull=True,
@@ -152,7 +157,8 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
)
.annotate(
backlog_issues=Count(
- "issue_cycle__issue__state__group",
+ "issue_cycle__issue__id",
+ distinct=True,
filter=Q(
issue_cycle__issue__state__group="backlog",
issue_cycle__issue__archived_at__isnull=True,
diff --git a/apiserver/plane/app/views/module/base.py b/apiserver/plane/app/views/module/base.py
index 7769aee3f..3fe3a078a 100644
--- a/apiserver/plane/app/views/module/base.py
+++ b/apiserver/plane/app/views/module/base.py
@@ -1,54 +1,57 @@
# Python imports
import json
-# Django Imports
-from django.utils import timezone
-from django.db.models import (
- Prefetch,
- F,
- OuterRef,
- Exists,
- Count,
- Q,
- Func,
- Subquery,
- IntegerField,
-)
from django.contrib.postgres.aggregates import ArrayAgg
from django.contrib.postgres.fields import ArrayField
-from django.db.models import Value, UUIDField
+from django.db.models import (
+ Count,
+ Exists,
+ F,
+ Func,
+ IntegerField,
+ OuterRef,
+ Prefetch,
+ Q,
+ Subquery,
+ UUIDField,
+ Value,
+)
from django.db.models.functions import Coalesce
+# Django Imports
+from django.utils import timezone
+from rest_framework import status
+
# Third party imports
from rest_framework.response import Response
-from rest_framework import status
-# Module imports
-from .. import BaseViewSet, BaseAPIView, WebhookMixin
-from plane.app.serializers import (
- ModuleWriteSerializer,
- ModuleSerializer,
- ModuleLinkSerializer,
- ModuleFavoriteSerializer,
- ModuleUserPropertiesSerializer,
- ModuleDetailSerializer,
-)
from plane.app.permissions import (
ProjectEntityPermission,
ProjectLitePermission,
)
-from plane.db.models import (
- Module,
- ModuleIssue,
- Project,
- Issue,
- ModuleLink,
- ModuleFavorite,
- ModuleUserProperties,
+from plane.app.serializers import (
+ ModuleDetailSerializer,
+ ModuleFavoriteSerializer,
+ ModuleLinkSerializer,
+ ModuleSerializer,
+ ModuleUserPropertiesSerializer,
+ ModuleWriteSerializer,
)
from plane.bgtasks.issue_activites_task import issue_activity
+from plane.db.models import (
+ Issue,
+ Module,
+ ModuleFavorite,
+ ModuleIssue,
+ ModuleLink,
+ ModuleUserProperties,
+ Project,
+)
from plane.utils.analytics_plot import burndown_plot
+# Module imports
+from .. import BaseAPIView, BaseViewSet, WebhookMixin
+
class ModuleViewSet(WebhookMixin, BaseViewSet):
model = Module
@@ -392,9 +395,11 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
"completion_chart": {},
}
- if queryset.first().start_date and queryset.first().target_date:
+ # Fetch the modules
+ modules = queryset.first()
+ if modules and modules.start_date and modules.target_date:
data["distribution"]["completion_chart"] = burndown_plot(
- queryset=queryset.first(),
+ queryset=modules,
slug=slug,
project_id=project_id,
module_id=pk,
diff --git a/apiserver/plane/app/views/workspace/estimate.py b/apiserver/plane/app/views/workspace/estimate.py
index fa75d5b88..59a23d867 100644
--- a/apiserver/plane/app/views/workspace/estimate.py
+++ b/apiserver/plane/app/views/workspace/estimate.py
@@ -3,15 +3,10 @@ from rest_framework import status
from rest_framework.response import Response
# Module imports
+from plane.app.permissions import WorkspaceEntityPermission
from plane.app.serializers import WorkspaceEstimateSerializer
from plane.app.views.base import BaseAPIView
-from plane.db.models import Project, Estimate
-from plane.app.permissions import WorkspaceEntityPermission
-
-# Django imports
-from django.db.models import (
- Prefetch,
-)
+from plane.db.models import Estimate, Project
from plane.utils.cache import cache_response
@@ -25,15 +20,11 @@ class WorkspaceEstimatesEndpoint(BaseAPIView):
estimate_ids = Project.objects.filter(
workspace__slug=slug, estimate__isnull=False
).values_list("estimate_id", flat=True)
- estimates = Estimate.objects.filter(
- pk__in=estimate_ids
- ).prefetch_related(
- Prefetch(
- "points",
- queryset=Project.objects.select_related(
- "estimate", "workspace"
- ),
- )
+ estimates = (
+ Estimate.objects.filter(pk__in=estimate_ids, workspace__slug=slug)
+ .prefetch_related("points")
+ .select_related("workspace", "project")
)
+
serializer = WorkspaceEstimateSerializer(estimates, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
diff --git a/apiserver/templates/emails/invitations/project_invitation.html b/apiserver/templates/emails/invitations/project_invitation.html
index 630a5eab3..def576601 100644
--- a/apiserver/templates/emails/invitations/project_invitation.html
+++ b/apiserver/templates/emails/invitations/project_invitation.html
@@ -1,349 +1,1815 @@
-
-
-
-
-
-
- {{ first_name }} invited you to join {{ project_name }} on Plane
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+ |
+
+
+
+
+ |
+
+
+
+
+
+
+
-
- |
-
-
- |
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- |
-
-
- |
-
-
- |
-
-
- |
-
-
-
-
-
- |
-
-
- {{first_name}} has invited you to join the
- {{project_name}} project on Plane
-
- |
- |
-
-
- |
-
-
-
-
- |
-
-
-
-
-
- |
- |
- |
-
-
- |
-
-
- Note: Plane is still in its early days, not everything will be perfect yet, and hiccups may happen. Please let us know of any suggestions, ideas, or bugs that you encounter on our Discord or GitHub, and we will use your feedback to improve on our upcoming releases.
-
- |
- |
-
-
- |
- |
- |
-
-
- |
-
-
- |
-
-
- |
-
-
- |
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+ |
+
+
+
+
+ |
+
+
+ |
+
+
+
+
+
+
+
+ |
+
+
+
+ {{first_name}}
+ has invited you to
+ join the
+
+
+
+ {{project_name}}
+ project on Plane
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+ |
+
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+ |
+
+
+
+ Note: Plane is still
+ in its early days, not
+ everything will be
+ perfect yet, and
+ hiccups may happen.
+ Please let us know of
+ any suggestions,
+ ideas, or bugs that
+ you encounter on our Discord
+ or GitHub, and we will use
+ your feedback to
+ improve on our
+ upcoming
+ releases.
+
+
+ |
+
+
+ |
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
-
- |
-
-
-
- |
-
-
-
-
-
- |
-
-
-
-
- |
-
-
- |
-
-
- |
-
-
- |
+
+
+
+ |
+ |
+
+
+
+
+ |
+
+
+ |
-
- |
-
-
-
+ |
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+ |
+
+
+