diff --git a/.deepsource.toml b/.deepsource.toml
deleted file mode 100644
index 2b40af672d7..00000000000
--- a/.deepsource.toml
+++ /dev/null
@@ -1,23 +0,0 @@
-version = 1
-
-exclude_patterns = [
- "bin/**",
- "**/node_modules/",
- "**/*.min.js"
-]
-
-[[analyzers]]
-name = "shell"
-
-[[analyzers]]
-name = "javascript"
-
- [analyzers.meta]
- plugins = ["react"]
- environment = ["nodejs"]
-
-[[analyzers]]
-name = "python"
-
- [analyzers.meta]
- runtime_version = "3.x.x"
\ No newline at end of file
diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml
index 60ebe583418..ed381453219 100644
--- a/.github/workflows/auto-merge.yml
+++ b/.github/workflows/auto-merge.yml
@@ -8,13 +8,13 @@ on:
env:
CURRENT_BRANCH: ${{ github.ref_name }}
- SOURCE_BRANCH: ${{ secrets.SYNC_TARGET_BRANCH_NAME }} # The sync branch such as "sync/ce"
- TARGET_BRANCH: ${{ secrets.TARGET_BRANCH }} # The target branch that you would like to merge changes like develop
+ SOURCE_BRANCH: ${{ secrets.SYNC_SOURCE_BRANCH_NAME }} # The sync branch such as "sync/ce"
+ TARGET_BRANCH: ${{ secrets.SYNC_TARGET_BRANCH_NAME }} # The target branch that you would like to merge changes like develop
GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} # Personal access token required to modify contents and workflows
- REVIEWER: ${{ secrets.REVIEWER }}
+ REVIEWER: ${{ secrets.SYNC_PR_REVIEWER }}
jobs:
- Check_Branch:
+ Check_Branch:
runs-on: ubuntu-latest
outputs:
BRANCH_MATCH: ${{ steps.check-branch.outputs.MATCH }}
@@ -27,7 +27,7 @@ jobs:
else
echo "MATCH=false" >> $GITHUB_OUTPUT
fi
-
+
Auto_Merge:
if: ${{ needs.Check_Branch.outputs.BRANCH_MATCH == 'true' }}
needs: [Check_Branch]
@@ -41,6 +41,11 @@ jobs:
with:
fetch-depth: 0 # Fetch all history for all branches and tags
+ - name: Setup Git
+ run: |
+ git config user.name "GitHub Actions"
+ git config user.email "actions@github.com"
+
- name: Setup GH CLI and Git Config
run: |
type -p curl >/dev/null || (sudo apt update && sudo apt install curl -y)
@@ -50,20 +55,6 @@ jobs:
sudo apt update
sudo apt install gh -y
- - id: git-author
- name: Setup Git CLI from Github Token
- run: |
- VIEWER_JSON=$(gh api graphql -f query='query { viewer { name login databaseId }}' --jq '.data.viewer')
- VIEWER_NAME=$(jq --raw-output '.name | values' <<< "${VIEWER_JSON}")
- VIEWER_LOGIN=$(jq --raw-output '.login' <<< "${VIEWER_JSON}")
- VIEWER_DATABASE_ID=$(jq --raw-output '.databaseId' <<< "${VIEWER_JSON}")
-
- USER_NAME="${VIEWER_NAME:-${VIEWER_LOGIN}}"
- USER_EMAIL="${VIEWER_DATABASE_ID}+${VIEWER_LOGIN}@users.noreply.github.com"
-
- git config --global user.name ${USER_NAME}
- git config --global user.email ${USER_EMAIL}
-
- name: Check for merge conflicts
id: conflicts
run: |
@@ -88,10 +79,6 @@ jobs:
- name: Create PR to Target Branch
if: env.HAS_CONFLICTS == 'true'
run: |
- # Use GitHub CLI to create PR and specify author and committer
- PR_URL=$(gh pr create --base $TARGET_BRANCH --head $SOURCE_BRANCH \
- --title "sync: merge conflicts need to be resolved" \
- --body "" \
- --reviewer $REVIEWER )
+ # Replace 'username' with the actual GitHub username of the reviewer.
+ PR_URL=$(gh pr create --base $TARGET_BRANCH --head $SOURCE_BRANCH --title "sync: merge conflicts need to be resolved" --body "" --reviewer $REVIEWER)
echo "Pull Request created: $PR_URL"
-
diff --git a/.github/workflows/build-branch.yml b/.github/workflows/build-branch.yml
index 008f29b66cf..2977f4cda56 100644
--- a/.github/workflows/build-branch.yml
+++ b/.github/workflows/build-branch.yml
@@ -6,7 +6,6 @@ on:
branches:
- master
- preview
- - develop
release:
types: [released, prereleased]
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 92b44af5b6a..d7b94d2456d 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/.gitignore b/.gitignore
index 0b655bd0e75..3989f43561e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,6 +51,7 @@ staticfiles
mediafiles
.env
.DS_Store
+logs/
node_modules/
assets/dist/
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 148568d76fb..f40c1a244bb 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -50,7 +50,6 @@ chmod +x setup.sh
docker compose -f docker-compose-local.yml up
```
-
## Missing a Feature?
If a feature is missing, you can directly _request_ a new one [here](https://github.com/makeplane/plane/issues/new?assignees=&labels=feature&template=feature_request.yml&title=%F0%9F%9A%80+Feature%3A+). You also can do the same by choosing "🚀 Feature" when raising a [New Issue](https://github.com/makeplane/plane/issues/new/choose) on our GitHub Repository.
diff --git a/ENV_SETUP.md b/ENV_SETUP.md
index bfc30019624..df05683efd9 100644
--- a/ENV_SETUP.md
+++ b/ENV_SETUP.md
@@ -53,7 +53,6 @@ NGINX_PORT=80
NEXT_PUBLIC_DEPLOY_URL="http://localhost/spaces"
```
-
## {PROJECT_FOLDER}/apiserver/.env
diff --git a/README.md b/README.md
index 0a9bc0a8a37..a9b3cede1ec 100644
--- a/README.md
+++ b/README.md
@@ -26,10 +26,10 @@ To selfhost please follow
- Website •
- Releases •
- Twitter •
- Documentation
+ Website •
+ Releases •
+ Twitter •
+ Documentation
@@ -49,30 +49,28 @@ To selfhost please follow
-
\ No newline at end of file
+
diff --git a/apiserver/.env.example b/apiserver/.env.example
index 6c39183639f..f35cf213a27 100644
--- a/apiserver/.env.example
+++ b/apiserver/.env.example
@@ -14,10 +14,6 @@ POSTGRES_HOST="plane-db"
POSTGRES_DB="plane"
DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}/${POSTGRES_DB}
-# Oauth variables
-GOOGLE_CLIENT_ID=""
-GITHUB_CLIENT_ID=""
-GITHUB_CLIENT_SECRET=""
# Redis Settings
REDIS_HOST="plane-redis"
@@ -34,11 +30,6 @@ AWS_S3_BUCKET_NAME="uploads"
# Maximum file upload limit
FILE_SIZE_LIMIT=5242880
-# GPT settings
-OPENAI_API_BASE="https://api.openai.com/v1" # deprecated
-OPENAI_API_KEY="sk-" # deprecated
-GPT_ENGINE="gpt-3.5-turbo" # deprecated
-
# Settings related to Docker
DOCKERIZED=1 # deprecated
@@ -74,4 +65,3 @@ WEB_URL="http://localhost"
# Gunicorn Workers
GUNICORN_WORKERS=2
-
diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api
index 0e4e0ac501b..34a50334ad4 100644
--- a/apiserver/Dockerfile.api
+++ b/apiserver/Dockerfile.api
@@ -48,8 +48,10 @@ USER root
RUN apk --no-cache add "bash~=5.2"
COPY ./bin ./bin/
+RUN mkdir -p /code/plane/logs
RUN chmod +x ./bin/takeoff ./bin/worker ./bin/beat
RUN chmod -R 777 /code
+RUN chown -R captain:plane /code
USER captain
diff --git a/apiserver/Dockerfile.dev b/apiserver/Dockerfile.dev
index bd6684fd5f4..06f15231c6e 100644
--- a/apiserver/Dockerfile.dev
+++ b/apiserver/Dockerfile.dev
@@ -35,6 +35,7 @@ RUN addgroup -S plane && \
COPY . .
+RUN mkdir -p /code/plane/logs
RUN chown -R captain.plane /code
RUN chmod -R +x /code/bin
RUN chmod -R 777 /code
diff --git a/apiserver/back_migration.py b/apiserver/back_migration.py
index a0e45416a45..328b9db2be3 100644
--- a/apiserver/back_migration.py
+++ b/apiserver/back_migration.py
@@ -182,7 +182,7 @@ def update_label_color():
labels = Label.objects.filter(color="")
updated_labels = []
for label in labels:
- label.color = "#" + "%06x" % random.randint(0, 0xFFFFFF)
+ label.color = f"#{random.randint(0, 0xFFFFFF+1):06X}"
updated_labels.append(label)
Label.objects.bulk_update(updated_labels, ["color"], batch_size=100)
diff --git a/apiserver/bin/takeoff b/apiserver/bin/takeoff
index efea53f8749..5a1da1570a1 100755
--- a/apiserver/bin/takeoff
+++ b/apiserver/bin/takeoff
@@ -21,11 +21,15 @@ SIGNATURE=$(echo "$HOSTNAME$MAC_ADDRESS$CPU_INFO$MEMORY_INFO$DISK_INFO" | sha256
export MACHINE_SIGNATURE=$SIGNATURE
# Register instance
-python manage.py register_instance $MACHINE_SIGNATURE
+python manage.py register_instance "$MACHINE_SIGNATURE"
+
# Load the configuration variable
python manage.py configure_instance
# Create the default bucket
python manage.py create_bucket
-exec gunicorn -w $GUNICORN_WORKERS -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:${PORT:-8000} --max-requests 1200 --max-requests-jitter 1000 --access-logfile -
+# Clear Cache before starting to remove stale values
+python manage.py clear_cache
+
+exec gunicorn -w "$GUNICORN_WORKERS" -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:"${PORT:-8000}" --max-requests 1200 --max-requests-jitter 1000 --access-logfile -
diff --git a/apiserver/bin/takeoff.local b/apiserver/bin/takeoff.local
index 8f62370ecf4..3194009b2c4 100755
--- a/apiserver/bin/takeoff.local
+++ b/apiserver/bin/takeoff.local
@@ -21,12 +21,15 @@ SIGNATURE=$(echo "$HOSTNAME$MAC_ADDRESS$CPU_INFO$MEMORY_INFO$DISK_INFO" | sha256
export MACHINE_SIGNATURE=$SIGNATURE
# Register instance
-python manage.py register_instance $MACHINE_SIGNATURE
+python manage.py register_instance "$MACHINE_SIGNATURE"
# Load the configuration variable
python manage.py configure_instance
# Create the default bucket
python manage.py create_bucket
+# Clear Cache before starting to remove stale values
+python manage.py clear_cache
+
python manage.py runserver 0.0.0.0:8000 --settings=plane.settings.local
diff --git a/apiserver/package.json b/apiserver/package.json
index 060944406ca..2474aa2f299 100644
--- a/apiserver/package.json
+++ b/apiserver/package.json
@@ -1,4 +1,4 @@
{
"name": "plane-api",
- "version": "0.16.0"
+ "version": "0.17.0"
}
diff --git a/apiserver/plane/api/serializers/issue.py b/apiserver/plane/api/serializers/issue.py
index 4c8d6e815b1..c78b109efdf 100644
--- a/apiserver/plane/api/serializers/issue.py
+++ b/apiserver/plane/api/serializers/issue.py
@@ -1,31 +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 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):
@@ -78,7 +80,7 @@ def validate(self, data):
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", []):
@@ -284,6 +286,20 @@ class Meta:
"updated_at",
]
+ def validate_url(self, value):
+ # Check URL format
+ validate_url = URLValidator()
+ try:
+ validate_url(value)
+ except ValidationError:
+ raise serializers.ValidationError("Invalid URL format.")
+
+ # Check URL scheme
+ if not value.startswith(("http://", "https://")):
+ raise serializers.ValidationError("Invalid URL scheme.")
+
+ return value
+
# Validation if url already exists
def create(self, validated_data):
if IssueLink.objects.filter(
@@ -295,6 +311,17 @@ def create(self, validated_data):
)
return IssueLink.objects.create(**validated_data)
+ def update(self, instance, validated_data):
+ if IssueLink.objects.filter(
+ url=validated_data.get("url"),
+ issue_id=instance.issue_id,
+ ).exists():
+ raise serializers.ValidationError(
+ {"error": "URL already exists for this Issue"}
+ )
+
+ return super().update(instance, validated_data)
+
class IssueAttachmentSerializer(BaseSerializer):
class Meta:
@@ -340,7 +367,7 @@ def validate(self, data):
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/api/serializers/project.py b/apiserver/plane/api/serializers/project.py
index 342cc1a81da..9dd4c9b85f3 100644
--- a/apiserver/plane/api/serializers/project.py
+++ b/apiserver/plane/api/serializers/project.py
@@ -6,8 +6,6 @@
Project,
ProjectIdentifier,
WorkspaceMember,
- State,
- Estimate,
)
from .base import BaseSerializer
diff --git a/apiserver/plane/api/urls/cycle.py b/apiserver/plane/api/urls/cycle.py
index 593e501bf98..0a775454bc5 100644
--- a/apiserver/plane/api/urls/cycle.py
+++ b/apiserver/plane/api/urls/cycle.py
@@ -4,6 +4,7 @@
CycleAPIEndpoint,
CycleIssueAPIEndpoint,
TransferCycleIssueAPIEndpoint,
+ CycleArchiveUnarchiveAPIEndpoint,
)
urlpatterns = [
@@ -32,4 +33,14 @@
TransferCycleIssueAPIEndpoint.as_view(),
name="transfer-issues",
),
+ path(
+ "workspaces//projects//cycles//archive/",
+ CycleArchiveUnarchiveAPIEndpoint.as_view(),
+ name="cycle-archive-unarchive",
+ ),
+ path(
+ "workspaces//projects//archived-cycles/",
+ CycleArchiveUnarchiveAPIEndpoint.as_view(),
+ name="cycle-archive-unarchive",
+ ),
]
diff --git a/apiserver/plane/api/urls/module.py b/apiserver/plane/api/urls/module.py
index 4309f44e968..a131f4d4f92 100644
--- a/apiserver/plane/api/urls/module.py
+++ b/apiserver/plane/api/urls/module.py
@@ -1,6 +1,10 @@
from django.urls import path
-from plane.api.views import ModuleAPIEndpoint, ModuleIssueAPIEndpoint
+from plane.api.views import (
+ ModuleAPIEndpoint,
+ ModuleIssueAPIEndpoint,
+ ModuleArchiveUnarchiveAPIEndpoint,
+)
urlpatterns = [
path(
@@ -23,4 +27,14 @@
ModuleIssueAPIEndpoint.as_view(),
name="module-issues",
),
+ path(
+ "workspaces//projects//modules//archive/",
+ ModuleArchiveUnarchiveAPIEndpoint.as_view(),
+ name="module-archive-unarchive",
+ ),
+ path(
+ "workspaces//projects//archived-modules/",
+ ModuleArchiveUnarchiveAPIEndpoint.as_view(),
+ name="module-archive-unarchive",
+ ),
]
diff --git a/apiserver/plane/api/urls/project.py b/apiserver/plane/api/urls/project.py
index 1ed450c8614..490371ccab1 100644
--- a/apiserver/plane/api/urls/project.py
+++ b/apiserver/plane/api/urls/project.py
@@ -1,6 +1,9 @@
from django.urls import path
-from plane.api.views import ProjectAPIEndpoint
+from plane.api.views import (
+ ProjectAPIEndpoint,
+ ProjectArchiveUnarchiveAPIEndpoint,
+)
urlpatterns = [
path(
@@ -13,4 +16,9 @@
ProjectAPIEndpoint.as_view(),
name="project",
),
+ path(
+ "workspaces//projects//archive/",
+ ProjectArchiveUnarchiveAPIEndpoint.as_view(),
+ name="project-archive-unarchive",
+ ),
]
diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py
index 0da79566f45..574ec69b6a9 100644
--- a/apiserver/plane/api/views/__init__.py
+++ b/apiserver/plane/api/views/__init__.py
@@ -1,4 +1,4 @@
-from .project import ProjectAPIEndpoint
+from .project import ProjectAPIEndpoint, ProjectArchiveUnarchiveAPIEndpoint
from .state import StateAPIEndpoint
@@ -14,8 +14,13 @@
CycleAPIEndpoint,
CycleIssueAPIEndpoint,
TransferCycleIssueAPIEndpoint,
+ CycleArchiveUnarchiveAPIEndpoint,
)
-from .module import ModuleAPIEndpoint, ModuleIssueAPIEndpoint
+from .module import (
+ ModuleAPIEndpoint,
+ ModuleIssueAPIEndpoint,
+ ModuleArchiveUnarchiveAPIEndpoint,
+)
from .inbox import InboxIssueAPIEndpoint
diff --git a/apiserver/plane/api/views/base.py b/apiserver/plane/api/views/base.py
index edb89f9b187..0cf5e8731c8 100644
--- a/apiserver/plane/api/views/base.py
+++ b/apiserver/plane/api/views/base.py
@@ -1,27 +1,26 @@
# Python imports
-import zoneinfo
-import json
from urllib.parse import urlparse
+import zoneinfo
# Django imports
from django.conf import settings
-from django.db import IntegrityError
from django.core.exceptions import ObjectDoesNotExist, ValidationError
+from django.db import IntegrityError
from django.utils import timezone
+from rest_framework import status
+from rest_framework.permissions import IsAuthenticated
+from rest_framework.response import Response
# Third party imports
from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework.permissions import IsAuthenticated
-from rest_framework import status
-from sentry_sdk import capture_exception
# Module imports
from plane.api.middleware.api_authentication import APIKeyAuthentication
from plane.api.rate_limit import ApiKeyRateThrottle
-from plane.utils.paginator import BasePaginator
from plane.bgtasks.webhook_task import send_webhook
+from plane.utils.exception_logger import log_exception
+from plane.utils.paginator import BasePaginator
class TimezoneMixin:
@@ -107,27 +106,23 @@ def handle_exception(self, exc):
if isinstance(e, ValidationError):
return Response(
- {
- "error": "The provided payload is not valid please try with a valid payload"
- },
+ {"error": "Please provide valid detail"},
status=status.HTTP_400_BAD_REQUEST,
)
if isinstance(e, ObjectDoesNotExist):
return Response(
- {"error": f"The required object does not exist."},
+ {"error": "The requested resource does not exist."},
status=status.HTTP_404_NOT_FOUND,
)
if isinstance(e, KeyError):
return Response(
- {"error": f" The required key does not exist."},
+ {"error": "The required key does not exist."},
status=status.HTTP_400_BAD_REQUEST,
)
- if settings.DEBUG:
- print(e)
- capture_exception(e)
+ log_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
diff --git a/apiserver/plane/api/views/cycle.py b/apiserver/plane/api/views/cycle.py
index 84931f46be9..637d713c319 100644
--- a/apiserver/plane/api/views/cycle.py
+++ b/apiserver/plane/api/views/cycle.py
@@ -2,7 +2,7 @@
import json
# Django imports
-from django.db.models import Q, Count, Sum, Prefetch, F, OuterRef, Func
+from django.db.models import Q, Count, Sum, F, OuterRef, Func
from django.utils import timezone
from django.core import serializers
@@ -140,7 +140,9 @@ def get_queryset(self):
def get(self, request, slug, project_id, pk=None):
if pk:
- queryset = self.get_queryset().get(pk=pk)
+ queryset = (
+ self.get_queryset().filter(archived_at__isnull=True).get(pk=pk)
+ )
data = CycleSerializer(
queryset,
fields=self.fields,
@@ -150,7 +152,9 @@ def get(self, request, slug, project_id, pk=None):
data,
status=status.HTTP_200_OK,
)
- queryset = self.get_queryset()
+ queryset = (
+ self.get_queryset().filter(archived_at__isnull=True)
+ )
cycle_view = request.GET.get("cycle_view", "all")
# Current Cycle
@@ -291,6 +295,11 @@ def patch(self, request, slug, project_id, pk):
cycle = Cycle.objects.get(
workspace__slug=slug, project_id=project_id, pk=pk
)
+ if cycle.archived_at:
+ return Response(
+ {"error": "Archived cycle cannot be edited"},
+ status=status.HTTP_400_BAD_REQUEST,
+ )
request_data = request.data
@@ -321,7 +330,9 @@ def patch(self, request, slug, project_id, pk):
and Cycle.objects.filter(
project_id=project_id,
workspace__slug=slug,
- external_source=request.data.get("external_source", cycle.external_source),
+ external_source=request.data.get(
+ "external_source", cycle.external_source
+ ),
external_id=request.data.get("external_id"),
).exists()
):
@@ -366,6 +377,139 @@ def delete(self, request, slug, project_id, pk):
return Response(status=status.HTTP_204_NO_CONTENT)
+class CycleArchiveUnarchiveAPIEndpoint(BaseAPIView):
+
+ permission_classes = [
+ ProjectEntityPermission,
+ ]
+
+ def get_queryset(self):
+ return (
+ Cycle.objects.filter(workspace__slug=self.kwargs.get("slug"))
+ .filter(project_id=self.kwargs.get("project_id"))
+ .filter(
+ project__project_projectmember__member=self.request.user,
+ project__project_projectmember__is_active=True,
+ )
+ .filter(archived_at__isnull=False)
+ .select_related("project")
+ .select_related("workspace")
+ .select_related("owned_by")
+ .annotate(
+ total_issues=Count(
+ "issue_cycle",
+ filter=Q(
+ issue_cycle__issue__archived_at__isnull=True,
+ issue_cycle__issue__is_draft=False,
+ ),
+ )
+ )
+ .annotate(
+ completed_issues=Count(
+ "issue_cycle__issue__state__group",
+ filter=Q(
+ issue_cycle__issue__state__group="completed",
+ issue_cycle__issue__archived_at__isnull=True,
+ issue_cycle__issue__is_draft=False,
+ ),
+ )
+ )
+ .annotate(
+ cancelled_issues=Count(
+ "issue_cycle__issue__state__group",
+ filter=Q(
+ issue_cycle__issue__state__group="cancelled",
+ issue_cycle__issue__archived_at__isnull=True,
+ issue_cycle__issue__is_draft=False,
+ ),
+ )
+ )
+ .annotate(
+ started_issues=Count(
+ "issue_cycle__issue__state__group",
+ filter=Q(
+ issue_cycle__issue__state__group="started",
+ issue_cycle__issue__archived_at__isnull=True,
+ issue_cycle__issue__is_draft=False,
+ ),
+ )
+ )
+ .annotate(
+ unstarted_issues=Count(
+ "issue_cycle__issue__state__group",
+ filter=Q(
+ issue_cycle__issue__state__group="unstarted",
+ issue_cycle__issue__archived_at__isnull=True,
+ issue_cycle__issue__is_draft=False,
+ ),
+ )
+ )
+ .annotate(
+ backlog_issues=Count(
+ "issue_cycle__issue__state__group",
+ filter=Q(
+ issue_cycle__issue__state__group="backlog",
+ issue_cycle__issue__archived_at__isnull=True,
+ issue_cycle__issue__is_draft=False,
+ ),
+ )
+ )
+ .annotate(
+ total_estimates=Sum("issue_cycle__issue__estimate_point")
+ )
+ .annotate(
+ completed_estimates=Sum(
+ "issue_cycle__issue__estimate_point",
+ filter=Q(
+ issue_cycle__issue__state__group="completed",
+ issue_cycle__issue__archived_at__isnull=True,
+ issue_cycle__issue__is_draft=False,
+ ),
+ )
+ )
+ .annotate(
+ started_estimates=Sum(
+ "issue_cycle__issue__estimate_point",
+ filter=Q(
+ issue_cycle__issue__state__group="started",
+ issue_cycle__issue__archived_at__isnull=True,
+ issue_cycle__issue__is_draft=False,
+ ),
+ )
+ )
+ .order_by(self.kwargs.get("order_by", "-created_at"))
+ .distinct()
+ )
+
+ def get(self, request, slug, project_id):
+ return self.paginate(
+ request=request,
+ queryset=(self.get_queryset()),
+ on_results=lambda cycles: CycleSerializer(
+ cycles,
+ many=True,
+ fields=self.fields,
+ expand=self.expand,
+ ).data,
+ )
+
+ def post(self, request, slug, project_id, pk):
+ cycle = Cycle.objects.get(
+ pk=pk, project_id=project_id, workspace__slug=slug
+ )
+ cycle.archived_at = timezone.now()
+ cycle.save()
+ return Response(status=status.HTTP_204_NO_CONTENT)
+
+ def delete(self, request, slug, project_id, pk):
+ cycle = Cycle.objects.get(
+ pk=pk, project_id=project_id, workspace__slug=slug
+ )
+ cycle.archived_at = None
+ cycle.save()
+ return Response(status=status.HTTP_204_NO_CONTENT)
+
+
class CycleIssueAPIEndpoint(WebhookMixin, BaseAPIView):
"""
This viewset automatically provides `list`, `create`,
diff --git a/apiserver/plane/api/views/inbox.py b/apiserver/plane/api/views/inbox.py
index c1079345ac2..fb36ea2a940 100644
--- a/apiserver/plane/api/views/inbox.py
+++ b/apiserver/plane/api/views/inbox.py
@@ -119,7 +119,7 @@ def post(self, request, slug, project_id):
)
# Check for valid priority
- if not request.data.get("issue", {}).get("priority", "none") in [
+ if request.data.get("issue", {}).get("priority", "none") not in [
"low",
"medium",
"high",
diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py
index bf3313779c2..4b59dc02076 100644
--- a/apiserver/plane/api/views/issue.py
+++ b/apiserver/plane/api/views/issue.py
@@ -1,22 +1,22 @@
# Python imports
import json
-from itertools import chain
+
+from django.core.serializers.json import DjangoJSONEncoder
# Django imports
from django.db import IntegrityError
from django.db.models import (
- OuterRef,
- Func,
- Q,
- F,
Case,
- When,
- Value,
CharField,
- Max,
Exists,
+ F,
+ Func,
+ Max,
+ OuterRef,
+ Q,
+ Value,
+ When,
)
-from django.core.serializers.json import DjangoJSONEncoder
from django.utils import timezone
# Third party imports
@@ -24,31 +24,32 @@
from rest_framework.response import Response
# Module imports
-from .base import BaseAPIView, WebhookMixin
+from plane.api.serializers import (
+ IssueActivitySerializer,
+ IssueCommentSerializer,
+ IssueLinkSerializer,
+ IssueSerializer,
+ LabelSerializer,
+)
from plane.app.permissions import (
ProjectEntityPermission,
- ProjectMemberPermission,
ProjectLitePermission,
+ ProjectMemberPermission,
)
+from plane.bgtasks.issue_activites_task import issue_activity
from plane.db.models import (
Issue,
+ IssueActivity,
IssueAttachment,
+ IssueComment,
IssueLink,
- Project,
Label,
+ Project,
ProjectMember,
- IssueComment,
- IssueActivity,
-)
-from plane.bgtasks.issue_activites_task import issue_activity
-from plane.api.serializers import (
- IssueSerializer,
- LabelSerializer,
- IssueLinkSerializer,
- IssueCommentSerializer,
- IssueActivitySerializer,
)
+from .base import BaseAPIView, WebhookMixin
+
class IssueAPIEndpoint(WebhookMixin, BaseAPIView):
"""
@@ -356,6 +357,7 @@ def get_queryset(self):
project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True,
)
+ .filter(project__archived_at__isnull=True)
.select_related("project")
.select_related("workspace")
.select_related("parent")
@@ -488,6 +490,7 @@ def get_queryset(self):
project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True,
)
+ .filter(project__archived_at__isnull=True)
.order_by(self.kwargs.get("order_by", "-created_at"))
.distinct()
)
@@ -617,6 +620,7 @@ def get_queryset(self):
project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True,
)
+ .filter(project__archived_at__isnull=True)
.select_related("workspace", "project", "issue", "actor")
.annotate(
is_member=Exists(
@@ -653,7 +657,6 @@ def get(self, request, slug, project_id, issue_id, pk=None):
)
def post(self, request, slug, project_id, issue_id):
-
# Validation check if the issue already exists
if (
request.data.get("external_id")
@@ -679,7 +682,6 @@ def post(self, request, slug, project_id, issue_id):
status=status.HTTP_409_CONFLICT,
)
-
serializer = IssueCommentSerializer(data=request.data)
if serializer.is_valid():
serializer.save(
@@ -717,7 +719,10 @@ def patch(self, request, slug, project_id, issue_id, pk):
# Validation check if the issue already exists
if (
request.data.get("external_id")
- and (issue_comment.external_id != str(request.data.get("external_id")))
+ and (
+ issue_comment.external_id
+ != str(request.data.get("external_id"))
+ )
and IssueComment.objects.filter(
project_id=project_id,
workspace__slug=slug,
@@ -735,7 +740,6 @@ def patch(self, request, slug, project_id, issue_id, pk):
status=status.HTTP_409_CONFLICT,
)
-
serializer = IssueCommentSerializer(
issue_comment, data=request.data, partial=True
)
@@ -792,6 +796,7 @@ def get(self, request, slug, project_id, issue_id, pk=None):
project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True,
)
+ .filter(project__archived_at__isnull=True)
.select_related("actor", "workspace", "issue", "project")
).order_by(request.GET.get("order_by", "created_at"))
diff --git a/apiserver/plane/api/views/module.py b/apiserver/plane/api/views/module.py
index 2e5bb85e2b7..643221dcabe 100644
--- a/apiserver/plane/api/views/module.py
+++ b/apiserver/plane/api/views/module.py
@@ -67,6 +67,7 @@ def get_queryset(self):
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
),
+ distinct=True,
),
)
.annotate(
@@ -77,6 +78,7 @@ def get_queryset(self):
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
),
+ distinct=True,
)
)
.annotate(
@@ -87,6 +89,7 @@ def get_queryset(self):
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
),
+ distinct=True,
)
)
.annotate(
@@ -97,6 +100,7 @@ def get_queryset(self):
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
),
+ distinct=True,
)
)
.annotate(
@@ -107,6 +111,7 @@ def get_queryset(self):
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
),
+ distinct=True,
)
)
.annotate(
@@ -117,6 +122,7 @@ def get_queryset(self):
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
),
+ distinct=True,
)
)
.order_by(self.kwargs.get("order_by", "-created_at"))
@@ -165,6 +171,11 @@ def patch(self, request, slug, project_id, pk):
module = Module.objects.get(
pk=pk, project_id=project_id, workspace__slug=slug
)
+ if module.archived_at:
+ return Response(
+ {"error": "Archived module cannot be edited"},
+ status=status.HTTP_400_BAD_REQUEST,
+ )
serializer = ModuleSerializer(
module,
data=request.data,
@@ -178,7 +189,9 @@ def patch(self, request, slug, project_id, pk):
and Module.objects.filter(
project_id=project_id,
workspace__slug=slug,
- external_source=request.data.get("external_source", module.external_source),
+ external_source=request.data.get(
+ "external_source", module.external_source
+ ),
external_id=request.data.get("external_id"),
).exists()
):
@@ -195,7 +208,9 @@ def patch(self, request, slug, project_id, pk):
def get(self, request, slug, project_id, pk=None):
if pk:
- queryset = self.get_queryset().get(pk=pk)
+ queryset = (
+ self.get_queryset().filter(archived_at__isnull=True).get(pk=pk)
+ )
data = ModuleSerializer(
queryset,
fields=self.fields,
@@ -207,7 +222,7 @@ def get(self, request, slug, project_id, pk=None):
)
return self.paginate(
request=request,
- queryset=(self.get_queryset()),
+ queryset=(self.get_queryset().filter(archived_at__isnull=True)),
on_results=lambda modules: ModuleSerializer(
modules,
many=True,
@@ -277,6 +292,7 @@ def get_queryset(self):
project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True,
)
+ .filter(project__archived_at__isnull=True)
.select_related("project")
.select_related("workspace")
.select_related("module")
@@ -444,3 +460,123 @@ def delete(self, request, slug, project_id, module_id, issue_id):
epoch=int(timezone.now().timestamp()),
)
return Response(status=status.HTTP_204_NO_CONTENT)
+
+
+class ModuleArchiveUnarchiveAPIEndpoint(BaseAPIView):
+
+ permission_classes = [
+ ProjectEntityPermission,
+ ]
+
+ def get_queryset(self):
+ return (
+ Module.objects.filter(project_id=self.kwargs.get("project_id"))
+ .filter(workspace__slug=self.kwargs.get("slug"))
+ .filter(archived_at__isnull=False)
+ .select_related("project")
+ .select_related("workspace")
+ .select_related("lead")
+ .prefetch_related("members")
+ .prefetch_related(
+ Prefetch(
+ "link_module",
+ queryset=ModuleLink.objects.select_related(
+ "module", "created_by"
+ ),
+ )
+ )
+ .annotate(
+ total_issues=Count(
+ "issue_module",
+ filter=Q(
+ issue_module__issue__archived_at__isnull=True,
+ issue_module__issue__is_draft=False,
+ ),
+ distinct=True,
+ ),
+ )
+ .annotate(
+ completed_issues=Count(
+ "issue_module__issue__state__group",
+ filter=Q(
+ issue_module__issue__state__group="completed",
+ issue_module__issue__archived_at__isnull=True,
+ issue_module__issue__is_draft=False,
+ ),
+ distinct=True,
+ )
+ )
+ .annotate(
+ cancelled_issues=Count(
+ "issue_module__issue__state__group",
+ filter=Q(
+ issue_module__issue__state__group="cancelled",
+ issue_module__issue__archived_at__isnull=True,
+ issue_module__issue__is_draft=False,
+ ),
+ distinct=True,
+ )
+ )
+ .annotate(
+ started_issues=Count(
+ "issue_module__issue__state__group",
+ filter=Q(
+ issue_module__issue__state__group="started",
+ issue_module__issue__archived_at__isnull=True,
+ issue_module__issue__is_draft=False,
+ ),
+ distinct=True,
+ )
+ )
+ .annotate(
+ unstarted_issues=Count(
+ "issue_module__issue__state__group",
+ filter=Q(
+ issue_module__issue__state__group="unstarted",
+ issue_module__issue__archived_at__isnull=True,
+ issue_module__issue__is_draft=False,
+ ),
+ distinct=True,
+ )
+ )
+ .annotate(
+ backlog_issues=Count(
+ "issue_module__issue__state__group",
+ filter=Q(
+ issue_module__issue__state__group="backlog",
+ issue_module__issue__archived_at__isnull=True,
+ issue_module__issue__is_draft=False,
+ ),
+ distinct=True,
+ )
+ )
+ .order_by(self.kwargs.get("order_by", "-created_at"))
+ )
+
+ def get(self, request, slug, project_id):
+ return self.paginate(
+ request=request,
+ queryset=(self.get_queryset()),
+ on_results=lambda modules: ModuleSerializer(
+ modules,
+ many=True,
+ fields=self.fields,
+ expand=self.expand,
+ ).data,
+ )
+
+ def post(self, request, slug, project_id, pk):
+ module = Module.objects.get(
+ pk=pk, project_id=project_id, workspace__slug=slug
+ )
+ module.archived_at = timezone.now()
+ module.save()
+ return Response(status=status.HTTP_204_NO_CONTENT)
+
+ def delete(self, request, slug, project_id, pk):
+ module = Module.objects.get(
+ pk=pk, project_id=project_id, workspace__slug=slug
+ )
+ module.archived_at = None
+ module.save()
+ return Response(status=status.HTTP_204_NO_CONTENT)
diff --git a/apiserver/plane/api/views/project.py b/apiserver/plane/api/views/project.py
index cb1f7dc7bc0..e0bce5514a2 100644
--- a/apiserver/plane/api/views/project.py
+++ b/apiserver/plane/api/views/project.py
@@ -1,4 +1,5 @@
# Django imports
+from django.utils import timezone
from django.db import IntegrityError
from django.db.models import Exists, OuterRef, Q, F, Func, Subquery, Prefetch
@@ -11,7 +12,6 @@
from plane.db.models import (
Workspace,
Project,
- ProjectFavorite,
ProjectMember,
ProjectDeployBoard,
State,
@@ -40,7 +40,10 @@ def get_queryset(self):
return (
Project.objects.filter(workspace__slug=self.kwargs.get("slug"))
.filter(
- Q(project_projectmember__member=self.request.user)
+ Q(
+ project_projectmember__member=self.request.user,
+ project_projectmember__is_active=True,
+ )
| Q(network=2)
)
.select_related(
@@ -150,7 +153,7 @@ def post(self, request, slug):
serializer.save()
# Add the user as Administrator to the project
- project_member = ProjectMember.objects.create(
+ _ = ProjectMember.objects.create(
project_id=serializer.data["id"],
member=request.user,
role=20,
@@ -245,12 +248,12 @@ def post(self, request, slug):
{"name": "The project name is already taken"},
status=status.HTTP_410_GONE,
)
- except Workspace.DoesNotExist as e:
+ except Workspace.DoesNotExist:
return Response(
{"error": "Workspace does not exist"},
status=status.HTTP_404_NOT_FOUND,
)
- except ValidationError as e:
+ except ValidationError:
return Response(
{"identifier": "The project identifier is already taken"},
status=status.HTTP_410_GONE,
@@ -261,6 +264,12 @@ def patch(self, request, slug, project_id=None):
workspace = Workspace.objects.get(slug=slug)
project = Project.objects.get(pk=project_id)
+ if project.archived_at:
+ return Response(
+ {"error": "Archived project cannot be updated"},
+ status=status.HTTP_400_BAD_REQUEST,
+ )
+
serializer = ProjectSerializer(
project,
data={**request.data},
@@ -307,7 +316,7 @@ def patch(self, request, slug, project_id=None):
{"error": "Project does not exist"},
status=status.HTTP_404_NOT_FOUND,
)
- except ValidationError as e:
+ except ValidationError:
return Response(
{"identifier": "The project identifier is already taken"},
status=status.HTTP_410_GONE,
@@ -317,3 +326,22 @@ def delete(self, request, slug, project_id):
project = Project.objects.get(pk=project_id, workspace__slug=slug)
project.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
+
+
+class ProjectArchiveUnarchiveAPIEndpoint(BaseAPIView):
+
+ permission_classes = [
+ ProjectBasePermission,
+ ]
+
+ def post(self, request, slug, project_id):
+ project = Project.objects.get(pk=project_id, workspace__slug=slug)
+ project.archived_at = timezone.now()
+ project.save()
+ return Response(status=status.HTTP_204_NO_CONTENT)
+
+ def delete(self, request, slug, project_id):
+ project = Project.objects.get(pk=project_id, workspace__slug=slug)
+ project.archived_at = None
+ project.save()
+ return Response(status=status.HTTP_204_NO_CONTENT)
diff --git a/apiserver/plane/api/views/state.py b/apiserver/plane/api/views/state.py
index ec10f9babe1..4ee899831fb 100644
--- a/apiserver/plane/api/views/state.py
+++ b/apiserver/plane/api/views/state.py
@@ -28,6 +28,7 @@ def get_queryset(self):
project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True,
)
+ .filter(project__archived_at__isnull=True)
.filter(~Q(name="Triage"))
.select_related("project")
.select_related("workspace")
@@ -66,8 +67,10 @@ def post(self, request, slug, project_id):
serializer.save(project_id=project_id)
return Response(serializer.data, status=status.HTTP_200_OK)
- return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
- except IntegrityError as e:
+ return Response(
+ serializer.errors, status=status.HTTP_400_BAD_REQUEST
+ )
+ except IntegrityError:
state = State.objects.filter(
workspace__slug=slug,
project_id=project_id,
@@ -136,7 +139,9 @@ def patch(self, request, slug, project_id, state_id=None):
and State.objects.filter(
project_id=project_id,
workspace__slug=slug,
- external_source=request.data.get("external_source", state.external_source),
+ external_source=request.data.get(
+ "external_source", state.external_source
+ ),
external_id=request.data.get("external_id"),
).exists()
):
diff --git a/apiserver/plane/app/serializers/__init__.py b/apiserver/plane/app/serializers/__init__.py
index 9bdd4baaf9d..22673dabceb 100644
--- a/apiserver/plane/app/serializers/__init__.py
+++ b/apiserver/plane/app/serializers/__init__.py
@@ -86,16 +86,6 @@
from .api import APITokenSerializer, APITokenReadSerializer
-from .integration import (
- IntegrationSerializer,
- WorkspaceIntegrationSerializer,
- GithubIssueSyncSerializer,
- GithubRepositorySerializer,
- GithubRepositorySyncSerializer,
- GithubCommentSyncSerializer,
- SlackProjectSyncSerializer,
-)
-
from .importer import ImporterSerializer
from .page import (
@@ -121,7 +111,10 @@
from .analytic import AnalyticViewSerializer
-from .notification import NotificationSerializer, UserNotificationPreferenceSerializer
+from .notification import (
+ NotificationSerializer,
+ UserNotificationPreferenceSerializer,
+)
from .exporter import ExporterHistorySerializer
diff --git a/apiserver/plane/app/serializers/cycle.py b/apiserver/plane/app/serializers/cycle.py
index a273b349c3d..13d321780d8 100644
--- a/apiserver/plane/app/serializers/cycle.py
+++ b/apiserver/plane/app/serializers/cycle.py
@@ -11,6 +11,7 @@
CycleUserProperties,
)
+
class CycleWriteSerializer(BaseSerializer):
def validate(self, data):
if (
@@ -30,6 +31,7 @@ class Meta:
"workspace",
"project",
"owned_by",
+ "archived_at",
]
@@ -47,7 +49,6 @@ class CycleSerializer(BaseSerializer):
# active | draft | upcoming | completed
status = serializers.CharField(read_only=True)
-
class Meta:
model = Cycle
fields = [
diff --git a/apiserver/plane/app/serializers/dashboard.py b/apiserver/plane/app/serializers/dashboard.py
index 8fca3c9064b..b0ed8841beb 100644
--- a/apiserver/plane/app/serializers/dashboard.py
+++ b/apiserver/plane/app/serializers/dashboard.py
@@ -18,9 +18,4 @@ class WidgetSerializer(BaseSerializer):
class Meta:
model = Widget
- fields = [
- "id",
- "key",
- "is_visible",
- "widget_filters"
- ]
\ No newline at end of file
+ fields = ["id", "key", "is_visible", "widget_filters"]
diff --git a/apiserver/plane/app/serializers/estimate.py b/apiserver/plane/app/serializers/estimate.py
index 6753900803e..d28f38c75ab 100644
--- a/apiserver/plane/app/serializers/estimate.py
+++ b/apiserver/plane/app/serializers/estimate.py
@@ -74,5 +74,3 @@ class Meta:
"name",
"description",
]
-
-
diff --git a/apiserver/plane/app/serializers/integration/__init__.py b/apiserver/plane/app/serializers/integration/__init__.py
deleted file mode 100644
index 112ff02d162..00000000000
--- a/apiserver/plane/app/serializers/integration/__init__.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from .base import IntegrationSerializer, WorkspaceIntegrationSerializer
-from .github import (
- GithubRepositorySerializer,
- GithubRepositorySyncSerializer,
- GithubIssueSyncSerializer,
- GithubCommentSyncSerializer,
-)
-from .slack import SlackProjectSyncSerializer
diff --git a/apiserver/plane/app/serializers/integration/base.py b/apiserver/plane/app/serializers/integration/base.py
deleted file mode 100644
index 01e484ed027..00000000000
--- a/apiserver/plane/app/serializers/integration/base.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Module imports
-from plane.app.serializers import BaseSerializer
-from plane.db.models import Integration, WorkspaceIntegration
-
-
-class IntegrationSerializer(BaseSerializer):
- class Meta:
- model = Integration
- fields = "__all__"
- read_only_fields = [
- "verified",
- ]
-
-
-class WorkspaceIntegrationSerializer(BaseSerializer):
- integration_detail = IntegrationSerializer(
- read_only=True, source="integration"
- )
-
- class Meta:
- model = WorkspaceIntegration
- fields = "__all__"
diff --git a/apiserver/plane/app/serializers/integration/github.py b/apiserver/plane/app/serializers/integration/github.py
deleted file mode 100644
index 850bccf1b3a..00000000000
--- a/apiserver/plane/app/serializers/integration/github.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# Module imports
-from plane.app.serializers import BaseSerializer
-from plane.db.models import (
- GithubIssueSync,
- GithubRepository,
- GithubRepositorySync,
- GithubCommentSync,
-)
-
-
-class GithubRepositorySerializer(BaseSerializer):
- class Meta:
- model = GithubRepository
- fields = "__all__"
-
-
-class GithubRepositorySyncSerializer(BaseSerializer):
- repo_detail = GithubRepositorySerializer(source="repository")
-
- class Meta:
- model = GithubRepositorySync
- fields = "__all__"
-
-
-class GithubIssueSyncSerializer(BaseSerializer):
- class Meta:
- model = GithubIssueSync
- fields = "__all__"
- read_only_fields = [
- "project",
- "workspace",
- "repository_sync",
- ]
-
-
-class GithubCommentSyncSerializer(BaseSerializer):
- class Meta:
- model = GithubCommentSync
- fields = "__all__"
- read_only_fields = [
- "project",
- "workspace",
- "repository_sync",
- "issue_sync",
- ]
diff --git a/apiserver/plane/app/serializers/integration/slack.py b/apiserver/plane/app/serializers/integration/slack.py
deleted file mode 100644
index 9c461c5b9b5..00000000000
--- a/apiserver/plane/app/serializers/integration/slack.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# Module imports
-from plane.app.serializers import BaseSerializer
-from plane.db.models import SlackProjectSync
-
-
-class SlackProjectSyncSerializer(BaseSerializer):
- class Meta:
- model = SlackProjectSync
- fields = "__all__"
- read_only_fields = [
- "project",
- "workspace",
- "workspace_integration",
- ]
diff --git a/apiserver/plane/app/serializers/issue.py b/apiserver/plane/app/serializers/issue.py
index 411c5b73f88..fc0e6f838ce 100644
--- a/apiserver/plane/app/serializers/issue.py
+++ b/apiserver/plane/app/serializers/issue.py
@@ -1,5 +1,7 @@
# Django imports
from django.utils import timezone
+from django.core.validators import URLValidator
+from django.core.exceptions import ValidationError
# Third Party imports
from rest_framework import serializers
@@ -7,7 +9,7 @@
# Module imports
from .base import BaseSerializer, DynamicBaseSerializer
from .user import UserLiteSerializer
-from .state import StateSerializer, StateLiteSerializer
+from .state import StateLiteSerializer
from .project import ProjectLiteSerializer
from .workspace import WorkspaceLiteSerializer
from plane.db.models import (
@@ -31,7 +33,6 @@
IssueVote,
IssueRelation,
State,
- Project,
)
@@ -432,6 +433,20 @@ class Meta:
"issue",
]
+ def validate_url(self, value):
+ # Check URL format
+ validate_url = URLValidator()
+ try:
+ validate_url(value)
+ except ValidationError:
+ raise serializers.ValidationError("Invalid URL format.")
+
+ # Check URL scheme
+ if not value.startswith(('http://', 'https://')):
+ raise serializers.ValidationError("Invalid URL scheme.")
+
+ return value
+
# Validation if url already exists
def create(self, validated_data):
if IssueLink.objects.filter(
@@ -443,9 +458,19 @@ def create(self, validated_data):
)
return IssueLink.objects.create(**validated_data)
+ def update(self, instance, validated_data):
+ if IssueLink.objects.filter(
+ url=validated_data.get("url"),
+ issue_id=instance.issue_id,
+ ).exists():
+ raise serializers.ValidationError(
+ {"error": "URL already exists for this Issue"}
+ )
+
+ return super().update(instance, validated_data)
-class IssueLinkLiteSerializer(BaseSerializer):
+class IssueLinkLiteSerializer(BaseSerializer):
class Meta:
model = IssueLink
fields = [
@@ -476,7 +501,6 @@ class Meta:
class IssueAttachmentLiteSerializer(DynamicBaseSerializer):
-
class Meta:
model = IssueAttachment
fields = [
@@ -505,13 +529,12 @@ class Meta:
class IssueReactionLiteSerializer(DynamicBaseSerializer):
-
class Meta:
model = IssueReaction
fields = [
"id",
- "actor_id",
- "issue_id",
+ "actor",
+ "issue",
"reaction",
]
@@ -601,15 +624,18 @@ class IssueSerializer(DynamicBaseSerializer):
# ids
cycle_id = serializers.PrimaryKeyRelatedField(read_only=True)
module_ids = serializers.ListField(
- child=serializers.UUIDField(), required=False,
+ child=serializers.UUIDField(),
+ required=False,
)
# Many to many
label_ids = serializers.ListField(
- child=serializers.UUIDField(), required=False,
+ child=serializers.UUIDField(),
+ required=False,
)
assignee_ids = serializers.ListField(
- child=serializers.UUIDField(), required=False,
+ child=serializers.UUIDField(),
+ required=False,
)
# Count items
@@ -649,19 +675,7 @@ class Meta:
read_only_fields = fields
-class IssueDetailSerializer(IssueSerializer):
- description_html = serializers.CharField()
- is_subscribed = serializers.BooleanField(read_only=True)
-
- class Meta(IssueSerializer.Meta):
- fields = IssueSerializer.Meta.fields + [
- "description_html",
- "is_subscribed",
- ]
-
-
class IssueLiteSerializer(DynamicBaseSerializer):
-
class Meta:
model = Issue
fields = [
diff --git a/apiserver/plane/app/serializers/module.py b/apiserver/plane/app/serializers/module.py
index 4aabfc50efd..dfdd265cd92 100644
--- a/apiserver/plane/app/serializers/module.py
+++ b/apiserver/plane/app/serializers/module.py
@@ -3,7 +3,6 @@
# Module imports
from .base import BaseSerializer, DynamicBaseSerializer
-from .user import UserLiteSerializer
from .project import ProjectLiteSerializer
from plane.db.models import (
@@ -40,6 +39,7 @@ class Meta:
"updated_by",
"created_at",
"updated_at",
+ "archived_at",
]
def to_representation(self, instance):
@@ -142,7 +142,6 @@ class Meta:
class ModuleLinkSerializer(BaseSerializer):
-
class Meta:
model = ModuleLink
fields = "__all__"
@@ -215,13 +214,12 @@ class Meta:
read_only_fields = fields
-
class ModuleDetailSerializer(ModuleSerializer):
-
link_module = ModuleLinkSerializer(read_only=True, many=True)
+ sub_issues = serializers.IntegerField(read_only=True)
class Meta(ModuleSerializer.Meta):
- fields = ModuleSerializer.Meta.fields + ['link_module']
+ fields = ModuleSerializer.Meta.fields + ["link_module", "sub_issues"]
class ModuleFavoriteSerializer(BaseSerializer):
diff --git a/apiserver/plane/app/serializers/notification.py b/apiserver/plane/app/serializers/notification.py
index 2152fcf0f9c..c6713a3540d 100644
--- a/apiserver/plane/app/serializers/notification.py
+++ b/apiserver/plane/app/serializers/notification.py
@@ -15,7 +15,6 @@ class Meta:
class UserNotificationPreferenceSerializer(BaseSerializer):
-
class Meta:
model = UserNotificationPreference
fields = "__all__"
diff --git a/apiserver/plane/app/serializers/page.py b/apiserver/plane/app/serializers/page.py
index a0f5986d69f..4dfe6ea9d65 100644
--- a/apiserver/plane/app/serializers/page.py
+++ b/apiserver/plane/app/serializers/page.py
@@ -3,7 +3,7 @@
# Module imports
from .base import BaseSerializer
-from .issue import IssueFlatSerializer, LabelLiteSerializer
+from .issue import LabelLiteSerializer
from .workspace import WorkspaceLiteSerializer
from .project import ProjectLiteSerializer
from plane.db.models import (
@@ -12,8 +12,6 @@
PageFavorite,
PageLabel,
Label,
- Issue,
- Module,
)
diff --git a/apiserver/plane/app/serializers/project.py b/apiserver/plane/app/serializers/project.py
index 999233442a4..a0c2318e381 100644
--- a/apiserver/plane/app/serializers/project.py
+++ b/apiserver/plane/app/serializers/project.py
@@ -95,14 +95,19 @@ class Meta:
"identifier",
"name",
"cover_image",
- "icon_prop",
- "emoji",
+ "logo_props",
"description",
]
read_only_fields = fields
class ProjectListSerializer(DynamicBaseSerializer):
+ total_issues = serializers.IntegerField(read_only=True)
+ archived_issues = serializers.IntegerField(read_only=True)
+ archived_sub_issues = serializers.IntegerField(read_only=True)
+ draft_issues = serializers.IntegerField(read_only=True)
+ draft_sub_issues = serializers.IntegerField(read_only=True)
+ sub_issues = serializers.IntegerField(read_only=True)
is_favorite = serializers.BooleanField(read_only=True)
total_members = serializers.IntegerField(read_only=True)
total_cycles = serializers.IntegerField(read_only=True)
diff --git a/apiserver/plane/app/serializers/user.py b/apiserver/plane/app/serializers/user.py
index 8cd48827e13..d6c15ee7fa9 100644
--- a/apiserver/plane/app/serializers/user.py
+++ b/apiserver/plane/app/serializers/user.py
@@ -4,7 +4,6 @@
# Module import
from .base import BaseSerializer
from plane.db.models import User, Workspace, WorkspaceMemberInvite
-from plane.license.models import InstanceAdmin, Instance
class UserSerializer(BaseSerializer):
@@ -99,13 +98,13 @@ def get_workspace(self, obj):
).first()
return {
"last_workspace_id": obj.last_workspace_id,
- "last_workspace_slug": workspace.slug
- if workspace is not None
- else "",
+ "last_workspace_slug": (
+ workspace.slug if workspace is not None else ""
+ ),
"fallback_workspace_id": obj.last_workspace_id,
- "fallback_workspace_slug": workspace.slug
- if workspace is not None
- else "",
+ "fallback_workspace_slug": (
+ workspace.slug if workspace is not None else ""
+ ),
"invites": workspace_invites,
}
else:
@@ -120,12 +119,16 @@ def get_workspace(self, obj):
return {
"last_workspace_id": None,
"last_workspace_slug": None,
- "fallback_workspace_id": fallback_workspace.id
- if fallback_workspace is not None
- else None,
- "fallback_workspace_slug": fallback_workspace.slug
- if fallback_workspace is not None
- else None,
+ "fallback_workspace_id": (
+ fallback_workspace.id
+ if fallback_workspace is not None
+ else None
+ ),
+ "fallback_workspace_slug": (
+ fallback_workspace.slug
+ if fallback_workspace is not None
+ else None
+ ),
"invites": workspace_invites,
}
diff --git a/apiserver/plane/app/serializers/webhook.py b/apiserver/plane/app/serializers/webhook.py
index 95ca149ffa4..175dea3047d 100644
--- a/apiserver/plane/app/serializers/webhook.py
+++ b/apiserver/plane/app/serializers/webhook.py
@@ -1,5 +1,4 @@
# Python imports
-import urllib
import socket
import ipaddress
from urllib.parse import urlparse
diff --git a/apiserver/plane/app/urls/__init__.py b/apiserver/plane/app/urls/__init__.py
index f2b11f12761..40b96687d33 100644
--- a/apiserver/plane/app/urls/__init__.py
+++ b/apiserver/plane/app/urls/__init__.py
@@ -6,9 +6,7 @@
from .dashboard import urlpatterns as dashboard_urls
from .estimate import urlpatterns as estimate_urls
from .external import urlpatterns as external_urls
-from .importer import urlpatterns as importer_urls
from .inbox import urlpatterns as inbox_urls
-from .integration import urlpatterns as integration_urls
from .issue import urlpatterns as issue_urls
from .module import urlpatterns as module_urls
from .notification import urlpatterns as notification_urls
@@ -32,9 +30,7 @@
*dashboard_urls,
*estimate_urls,
*external_urls,
- *importer_urls,
*inbox_urls,
- *integration_urls,
*issue_urls,
*module_urls,
*notification_urls,
diff --git a/apiserver/plane/app/urls/cycle.py b/apiserver/plane/app/urls/cycle.py
index 740b0ab4386..2e1779420da 100644
--- a/apiserver/plane/app/urls/cycle.py
+++ b/apiserver/plane/app/urls/cycle.py
@@ -8,6 +8,7 @@
CycleFavoriteViewSet,
TransferCycleIssueEndpoint,
CycleUserPropertiesEndpoint,
+ CycleArchiveUnarchiveEndpoint,
)
@@ -90,4 +91,14 @@
CycleUserPropertiesEndpoint.as_view(),
name="cycle-user-filters",
),
+ path(
+ "workspaces//projects//cycles//archive/",
+ CycleArchiveUnarchiveEndpoint.as_view(),
+ name="cycle-archive-unarchive",
+ ),
+ path(
+ "workspaces//projects//archived-cycles/",
+ CycleArchiveUnarchiveEndpoint.as_view(),
+ name="cycle-archive-unarchive",
+ ),
]
diff --git a/apiserver/plane/app/urls/external.py b/apiserver/plane/app/urls/external.py
index 774e6fb7cd3..8db87a24928 100644
--- a/apiserver/plane/app/urls/external.py
+++ b/apiserver/plane/app/urls/external.py
@@ -2,7 +2,6 @@
from plane.app.views import UnsplashEndpoint
-from plane.app.views import ReleaseNotesEndpoint
from plane.app.views import GPTIntegrationEndpoint
@@ -12,11 +11,6 @@
UnsplashEndpoint.as_view(),
name="unsplash",
),
- path(
- "release-notes/",
- ReleaseNotesEndpoint.as_view(),
- name="release-notes",
- ),
path(
"workspaces//projects//ai-assistant/",
GPTIntegrationEndpoint.as_view(),
diff --git a/apiserver/plane/app/urls/importer.py b/apiserver/plane/app/urls/importer.py
deleted file mode 100644
index f3a018d7894..00000000000
--- a/apiserver/plane/app/urls/importer.py
+++ /dev/null
@@ -1,37 +0,0 @@
-from django.urls import path
-
-
-from plane.app.views import (
- ServiceIssueImportSummaryEndpoint,
- ImportServiceEndpoint,
- UpdateServiceImportStatusEndpoint,
-)
-
-
-urlpatterns = [
- path(
- "workspaces//importers//",
- ServiceIssueImportSummaryEndpoint.as_view(),
- name="importer-summary",
- ),
- path(
- "workspaces//projects/importers//",
- ImportServiceEndpoint.as_view(),
- name="importer",
- ),
- path(
- "workspaces//importers/",
- ImportServiceEndpoint.as_view(),
- name="importer",
- ),
- path(
- "workspaces//importers///",
- ImportServiceEndpoint.as_view(),
- name="importer",
- ),
- path(
- "workspaces//projects//service//importers//",
- UpdateServiceImportStatusEndpoint.as_view(),
- name="importer-status",
- ),
-]
diff --git a/apiserver/plane/app/urls/integration.py b/apiserver/plane/app/urls/integration.py
deleted file mode 100644
index cf3f82d5a49..00000000000
--- a/apiserver/plane/app/urls/integration.py
+++ /dev/null
@@ -1,150 +0,0 @@
-from django.urls import path
-
-
-from plane.app.views import (
- IntegrationViewSet,
- WorkspaceIntegrationViewSet,
- GithubRepositoriesEndpoint,
- GithubRepositorySyncViewSet,
- GithubIssueSyncViewSet,
- GithubCommentSyncViewSet,
- BulkCreateGithubIssueSyncEndpoint,
- SlackProjectSyncViewSet,
-)
-
-
-urlpatterns = [
- path(
- "integrations/",
- IntegrationViewSet.as_view(
- {
- "get": "list",
- "post": "create",
- }
- ),
- name="integrations",
- ),
- path(
- "integrations//",
- IntegrationViewSet.as_view(
- {
- "get": "retrieve",
- "patch": "partial_update",
- "delete": "destroy",
- }
- ),
- name="integrations",
- ),
- path(
- "workspaces//workspace-integrations/",
- WorkspaceIntegrationViewSet.as_view(
- {
- "get": "list",
- }
- ),
- name="workspace-integrations",
- ),
- path(
- "workspaces//workspace-integrations//",
- WorkspaceIntegrationViewSet.as_view(
- {
- "post": "create",
- }
- ),
- name="workspace-integrations",
- ),
- path(
- "workspaces//workspace-integrations//provider/",
- WorkspaceIntegrationViewSet.as_view(
- {
- "get": "retrieve",
- "delete": "destroy",
- }
- ),
- name="workspace-integrations",
- ),
- # Github Integrations
- path(
- "workspaces//workspace-integrations//github-repositories/",
- GithubRepositoriesEndpoint.as_view(),
- ),
- path(
- "workspaces//projects//workspace-integrations//github-repository-sync/",
- GithubRepositorySyncViewSet.as_view(
- {
- "get": "list",
- "post": "create",
- }
- ),
- ),
- path(
- "workspaces//projects//workspace-integrations//github-repository-sync//",
- GithubRepositorySyncViewSet.as_view(
- {
- "get": "retrieve",
- "delete": "destroy",
- }
- ),
- ),
- path(
- "workspaces//projects//github-repository-sync//github-issue-sync/",
- GithubIssueSyncViewSet.as_view(
- {
- "post": "create",
- "get": "list",
- }
- ),
- ),
- path(
- "workspaces//projects//github-repository-sync//bulk-create-github-issue-sync/",
- BulkCreateGithubIssueSyncEndpoint.as_view(),
- ),
- path(
- "workspaces//projects//github-repository-sync//github-issue-sync//",
- GithubIssueSyncViewSet.as_view(
- {
- "get": "retrieve",
- "delete": "destroy",
- }
- ),
- ),
- path(
- "workspaces//projects//github-repository-sync//github-issue-sync//github-comment-sync/",
- GithubCommentSyncViewSet.as_view(
- {
- "post": "create",
- "get": "list",
- }
- ),
- ),
- path(
- "workspaces//projects//github-repository-sync//github-issue-sync//github-comment-sync//",
- GithubCommentSyncViewSet.as_view(
- {
- "get": "retrieve",
- "delete": "destroy",
- }
- ),
- ),
- ## End Github Integrations
- # Slack Integration
- path(
- "workspaces//projects//workspace-integrations//project-slack-sync/",
- SlackProjectSyncViewSet.as_view(
- {
- "post": "create",
- "get": "list",
- }
- ),
- ),
- path(
- "workspaces//projects//workspace-integrations//project-slack-sync//",
- SlackProjectSyncViewSet.as_view(
- {
- "delete": "destroy",
- "get": "retrieve",
- }
- ),
- ),
- ## End Slack Integration
-]
diff --git a/apiserver/plane/app/urls/issue.py b/apiserver/plane/app/urls/issue.py
index 4ee70450b37..0d3b9e0634c 100644
--- a/apiserver/plane/app/urls/issue.py
+++ b/apiserver/plane/app/urls/issue.py
@@ -1,30 +1,26 @@
from django.urls import path
-
from plane.app.views import (
- IssueListEndpoint,
- IssueViewSet,
- LabelViewSet,
BulkCreateIssueLabelsEndpoint,
BulkDeleteIssuesEndpoint,
- BulkImportIssuesEndpoint,
- UserWorkSpaceIssues,
SubIssuesEndpoint,
IssueLinkViewSet,
IssueAttachmentEndpoint,
+ CommentReactionViewSet,
ExportIssuesEndpoint,
IssueActivityEndpoint,
+ IssueArchiveViewSet,
IssueCommentViewSet,
- IssueSubscriberViewSet,
+ IssueDraftViewSet,
+ IssueListEndpoint,
IssueReactionViewSet,
- CommentReactionViewSet,
- IssueUserDisplayPropertyEndpoint,
- IssueArchiveViewSet,
IssueRelationViewSet,
- IssueDraftViewSet,
+ IssueSubscriberViewSet,
+ IssueUserDisplayPropertyEndpoint,
+ IssueViewSet,
+ LabelViewSet,
)
-
urlpatterns = [
path(
"workspaces//projects//issues/list/",
@@ -85,18 +81,7 @@
BulkDeleteIssuesEndpoint.as_view(),
name="project-issues-bulk",
),
- path(
- "workspaces//projects//bulk-import-issues//",
- BulkImportIssuesEndpoint.as_view(),
- name="project-issues-bulk",
- ),
- # deprecated endpoint TODO: remove once confirmed
- path(
- "workspaces//my-issues/",
- UserWorkSpaceIssues.as_view(),
- name="workspace-issues",
- ),
- ##
+ ##
path(
"workspaces//projects//issues//sub-issues/",
SubIssuesEndpoint.as_view(),
diff --git a/apiserver/plane/app/urls/module.py b/apiserver/plane/app/urls/module.py
index 5e9f4f1230c..a730fcd5054 100644
--- a/apiserver/plane/app/urls/module.py
+++ b/apiserver/plane/app/urls/module.py
@@ -6,8 +6,8 @@
ModuleIssueViewSet,
ModuleLinkViewSet,
ModuleFavoriteViewSet,
- BulkImportModulesEndpoint,
ModuleUserPropertiesEndpoint,
+ ModuleArchiveUnarchiveEndpoint,
)
@@ -106,14 +106,19 @@
),
name="user-favorite-module",
),
- path(
- "workspaces//projects//bulk-import-modules//",
- BulkImportModulesEndpoint.as_view(),
- name="bulk-modules-create",
- ),
path(
"workspaces//projects//modules//user-properties/",
ModuleUserPropertiesEndpoint.as_view(),
name="cycle-user-filters",
),
+ path(
+ "workspaces//projects//modules//archive/",
+ ModuleArchiveUnarchiveEndpoint.as_view(),
+ name="module-archive-unarchive",
+ ),
+ path(
+ "workspaces//projects//archived-modules/",
+ ModuleArchiveUnarchiveEndpoint.as_view(),
+ name="module-archive-unarchive",
+ ),
]
diff --git a/apiserver/plane/app/urls/project.py b/apiserver/plane/app/urls/project.py
index f8ecac4c068..7ea636df8e9 100644
--- a/apiserver/plane/app/urls/project.py
+++ b/apiserver/plane/app/urls/project.py
@@ -14,6 +14,7 @@
ProjectPublicCoverImagesEndpoint,
ProjectDeployBoardViewSet,
UserProjectRolesEndpoint,
+ ProjectArchiveUnarchiveEndpoint,
)
@@ -175,4 +176,9 @@
),
name="project-deploy-board",
),
+ path(
+ "workspaces//projects//archive/",
+ ProjectArchiveUnarchiveEndpoint.as_view(),
+ name="project-archive-unarchive",
+ ),
]
diff --git a/apiserver/plane/app/urls/workspace.py b/apiserver/plane/app/urls/workspace.py
index a70ff18e535..8b21bb9e1b2 100644
--- a/apiserver/plane/app/urls/workspace.py
+++ b/apiserver/plane/app/urls/workspace.py
@@ -22,6 +22,7 @@
WorkspaceUserPropertiesEndpoint,
WorkspaceStatesEndpoint,
WorkspaceEstimatesEndpoint,
+ ExportWorkspaceUserActivityEndpoint,
WorkspaceModulesEndpoint,
WorkspaceCyclesEndpoint,
)
@@ -191,6 +192,11 @@
WorkspaceUserActivityEndpoint.as_view(),
name="workspace-user-activity",
),
+ path(
+ "workspaces//user-activity//export/",
+ ExportWorkspaceUserActivityEndpoint.as_view(),
+ name="export-workspace-user-activity",
+ ),
path(
"workspaces//user-profile//",
WorkspaceUserProfileEndpoint.as_view(),
diff --git a/apiserver/plane/app/urls_deprecated.py b/apiserver/plane/app/urls_deprecated.py
deleted file mode 100644
index 2a47285aa21..00000000000
--- a/apiserver/plane/app/urls_deprecated.py
+++ /dev/null
@@ -1,1810 +0,0 @@
-from django.urls import path
-
-from rest_framework_simplejwt.views import TokenRefreshView
-
-# Create your urls here.
-
-from plane.app.views import (
- # Authentication
- SignUpEndpoint,
- SignInEndpoint,
- SignOutEndpoint,
- MagicSignInEndpoint,
- MagicSignInGenerateEndpoint,
- OauthEndpoint,
- ## End Authentication
- # Auth Extended
- ForgotPasswordEndpoint,
- VerifyEmailEndpoint,
- ResetPasswordEndpoint,
- RequestEmailVerificationEndpoint,
- ChangePasswordEndpoint,
- ## End Auth Extender
- # User
- UserEndpoint,
- UpdateUserOnBoardedEndpoint,
- UpdateUserTourCompletedEndpoint,
- UserActivityEndpoint,
- ## End User
- # Workspaces
- WorkSpaceViewSet,
- UserWorkSpacesEndpoint,
- InviteWorkspaceEndpoint,
- JoinWorkspaceEndpoint,
- WorkSpaceMemberViewSet,
- WorkspaceMembersEndpoint,
- WorkspaceInvitationsViewset,
- UserWorkspaceInvitationsEndpoint,
- WorkspaceMemberUserEndpoint,
- WorkspaceMemberUserViewsEndpoint,
- WorkSpaceAvailabilityCheckEndpoint,
- TeamMemberViewSet,
- AddTeamToProjectEndpoint,
- UserLastProjectWithWorkspaceEndpoint,
- UserWorkspaceInvitationEndpoint,
- UserActivityGraphEndpoint,
- UserIssueCompletedGraphEndpoint,
- UserWorkspaceDashboardEndpoint,
- WorkspaceThemeViewSet,
- WorkspaceUserProfileStatsEndpoint,
- WorkspaceUserActivityEndpoint,
- WorkspaceUserProfileEndpoint,
- WorkspaceUserProfileIssuesEndpoint,
- WorkspaceLabelsEndpoint,
- LeaveWorkspaceEndpoint,
- ## End Workspaces
- # File Assets
- FileAssetEndpoint,
- UserAssetsEndpoint,
- ## End File Assets
- # Projects
- ProjectViewSet,
- InviteProjectEndpoint,
- ProjectMemberViewSet,
- ProjectMemberEndpoint,
- ProjectMemberInvitationsViewset,
- ProjectMemberUserEndpoint,
- AddMemberToProjectEndpoint,
- ProjectJoinEndpoint,
- UserProjectInvitationsViewset,
- ProjectIdentifierEndpoint,
- ProjectFavoritesViewSet,
- LeaveProjectEndpoint,
- ProjectPublicCoverImagesEndpoint,
- ## End Projects
- # Issues
- IssueViewSet,
- WorkSpaceIssuesEndpoint,
- IssueActivityEndpoint,
- IssueCommentViewSet,
- UserWorkSpaceIssues,
- BulkDeleteIssuesEndpoint,
- BulkImportIssuesEndpoint,
- ProjectUserViewsEndpoint,
- IssueUserDisplayPropertyEndpoint,
- LabelViewSet,
- SubIssuesEndpoint,
- IssueLinkViewSet,
- BulkCreateIssueLabelsEndpoint,
- IssueAttachmentEndpoint,
- IssueArchiveViewSet,
- IssueSubscriberViewSet,
- IssueCommentPublicViewSet,
- IssueReactionViewSet,
- IssueRelationViewSet,
- CommentReactionViewSet,
- IssueDraftViewSet,
- ## End Issues
- # States
- StateViewSet,
- ## End States
- # Estimates
- ProjectEstimatePointEndpoint,
- BulkEstimatePointEndpoint,
- ## End Estimates
- # Views
- GlobalViewViewSet,
- GlobalViewIssuesViewSet,
- IssueViewViewSet,
- IssueViewFavoriteViewSet,
- ## End Views
- # Cycles
- CycleViewSet,
- CycleIssueViewSet,
- CycleDateCheckEndpoint,
- CycleFavoriteViewSet,
- TransferCycleIssueEndpoint,
- ## End Cycles
- # Modules
- ModuleViewSet,
- ModuleIssueViewSet,
- ModuleFavoriteViewSet,
- ModuleLinkViewSet,
- BulkImportModulesEndpoint,
- ## End Modules
- # Pages
- PageViewSet,
- PageLogEndpoint,
- SubPagesEndpoint,
- PageFavoriteViewSet,
- CreateIssueFromBlockEndpoint,
- ## End Pages
- # Api Tokens
- ApiTokenEndpoint,
- ## End Api Tokens
- # Integrations
- IntegrationViewSet,
- WorkspaceIntegrationViewSet,
- GithubRepositoriesEndpoint,
- GithubRepositorySyncViewSet,
- GithubIssueSyncViewSet,
- GithubCommentSyncViewSet,
- BulkCreateGithubIssueSyncEndpoint,
- SlackProjectSyncViewSet,
- ## End Integrations
- # Importer
- ServiceIssueImportSummaryEndpoint,
- ImportServiceEndpoint,
- UpdateServiceImportStatusEndpoint,
- ## End importer
- # Search
- GlobalSearchEndpoint,
- IssueSearchEndpoint,
- ## End Search
- # External
- GPTIntegrationEndpoint,
- ReleaseNotesEndpoint,
- UnsplashEndpoint,
- ## End External
- # Inbox
- InboxViewSet,
- InboxIssueViewSet,
- ## End Inbox
- # Analytics
- AnalyticsEndpoint,
- AnalyticViewViewset,
- SavedAnalyticEndpoint,
- ExportAnalyticsEndpoint,
- DefaultAnalyticsEndpoint,
- ## End Analytics
- # Notification
- NotificationViewSet,
- UnreadNotificationEndpoint,
- MarkAllReadNotificationViewSet,
- ## End Notification
- # Public Boards
- ProjectDeployBoardViewSet,
- ProjectIssuesPublicEndpoint,
- ProjectDeployBoardPublicSettingsEndpoint,
- IssueReactionPublicViewSet,
- CommentReactionPublicViewSet,
- InboxIssuePublicViewSet,
- IssueVotePublicViewSet,
- WorkspaceProjectDeployBoardEndpoint,
- IssueRetrievePublicEndpoint,
- ## End Public Boards
- ## Exporter
- ExportIssuesEndpoint,
- ## End Exporter
- # Configuration
- ConfigurationEndpoint,
- ## End Configuration
-)
-
-
-# TODO: Delete this file
-# This url file has been deprecated use apiserver/plane/urls folder to create new urls
-
-urlpatterns = [
- # Social Auth
- path("social-auth/", OauthEndpoint.as_view(), name="oauth"),
- # Auth
- path("sign-up/", SignUpEndpoint.as_view(), name="sign-up"),
- path("sign-in/", SignInEndpoint.as_view(), name="sign-in"),
- path("sign-out/", SignOutEndpoint.as_view(), name="sign-out"),
- # Magic Sign In/Up
- path(
- "magic-generate/",
- MagicSignInGenerateEndpoint.as_view(),
- name="magic-generate",
- ),
- path(
- "magic-sign-in/", MagicSignInEndpoint.as_view(), name="magic-sign-in"
- ),
- path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
- # Email verification
- path("email-verify/", VerifyEmailEndpoint.as_view(), name="email-verify"),
- path(
- "request-email-verify/",
- RequestEmailVerificationEndpoint.as_view(),
- name="request-reset-email",
- ),
- # Password Manipulation
- path(
- "reset-password///",
- ResetPasswordEndpoint.as_view(),
- name="password-reset",
- ),
- path(
- "forgot-password/",
- ForgotPasswordEndpoint.as_view(),
- name="forgot-password",
- ),
- # User Profile
- path(
- "users/me/",
- UserEndpoint.as_view(
- {"get": "retrieve", "patch": "partial_update", "delete": "destroy"}
- ),
- name="users",
- ),
- path(
- "users/me/settings/",
- UserEndpoint.as_view(
- {
- "get": "retrieve_user_settings",
- }
- ),
- name="users",
- ),
- path(
- "users/me/change-password/",
- ChangePasswordEndpoint.as_view(),
- name="change-password",
- ),
- path(
- "users/me/onboard/",
- UpdateUserOnBoardedEndpoint.as_view(),
- name="user-onboard",
- ),
- path(
- "users/me/tour-completed/",
- UpdateUserTourCompletedEndpoint.as_view(),
- name="user-tour",
- ),
- path(
- "users/workspaces//activities/",
- UserActivityEndpoint.as_view(),
- name="user-activities",
- ),
- # user workspaces
- path(
- "users/me/workspaces/",
- UserWorkSpacesEndpoint.as_view(),
- name="user-workspace",
- ),
- # user workspace invitations
- path(
- "users/me/invitations/workspaces/",
- UserWorkspaceInvitationsEndpoint.as_view(
- {"get": "list", "post": "create"}
- ),
- name="user-workspace-invitations",
- ),
- # user workspace invitation
- path(
- "users/me/invitations//",
- UserWorkspaceInvitationEndpoint.as_view(
- {
- "get": "retrieve",
- }
- ),
- name="workspace",
- ),
- # user join workspace
- # User Graphs
- path(
- "users/me/workspaces//activity-graph/",
- UserActivityGraphEndpoint.as_view(),
- name="user-activity-graph",
- ),
- path(
- "users/me/workspaces//issues-completed-graph/",
- UserIssueCompletedGraphEndpoint.as_view(),
- name="completed-graph",
- ),
- path(
- "users/me/workspaces//dashboard/",
- UserWorkspaceDashboardEndpoint.as_view(),
- name="user-workspace-dashboard",
- ),
- ## User Graph
- path(
- "users/me/invitations/workspaces///join/",
- JoinWorkspaceEndpoint.as_view(),
- name="user-join-workspace",
- ),
- # user project invitations
- path(
- "users/me/invitations/projects/",
- UserProjectInvitationsViewset.as_view(
- {"get": "list", "post": "create"}
- ),
- name="user-project-invitaions",
- ),
- ## Workspaces ##
- path(
- "workspace-slug-check/",
- WorkSpaceAvailabilityCheckEndpoint.as_view(),
- name="workspace-availability",
- ),
- path(
- "workspaces/",
- WorkSpaceViewSet.as_view(
- {
- "get": "list",
- "post": "create",
- }
- ),
- name="workspace",
- ),
- path(
- "workspaces//",
- WorkSpaceViewSet.as_view(
- {
- "get": "retrieve",
- "put": "update",
- "patch": "partial_update",
- "delete": "destroy",
- }
- ),
- name="workspace",
- ),
- path(
- "workspaces//invite/",
- InviteWorkspaceEndpoint.as_view(),
- name="workspace",
- ),
- path(
- "workspaces//invitations/",
- WorkspaceInvitationsViewset.as_view({"get": "list"}),
- name="workspace",
- ),
- path(
- "workspaces//invitations//",
- WorkspaceInvitationsViewset.as_view(
- {
- "delete": "destroy",
- "get": "retrieve",
- }
- ),
- name="workspace",
- ),
- path(
- "workspaces//members/",
- WorkSpaceMemberViewSet.as_view({"get": "list"}),
- name="workspace",
- ),
- path(
- "workspaces//members//",
- WorkSpaceMemberViewSet.as_view(
- {
- "patch": "partial_update",
- "delete": "destroy",
- "get": "retrieve",
- }
- ),
- name="workspace",
- ),
- path(
- "workspaces//workspace-members/",
- WorkspaceMembersEndpoint.as_view(),
- name="workspace-members",
- ),
- path(
- "workspaces//teams/",
- TeamMemberViewSet.as_view(
- {
- "get": "list",
- "post": "create",
- }
- ),
- name="workspace",
- ),
- path(
- "workspaces//teams//",
- TeamMemberViewSet.as_view(
- {
- "put": "update",
- "patch": "partial_update",
- "delete": "destroy",
- "get": "retrieve",
- }
- ),
- name="workspace",
- ),
- path(
- "users/last-visited-workspace/",
- UserLastProjectWithWorkspaceEndpoint.as_view(),
- name="workspace-project-details",
- ),
- path(
- "workspaces//workspace-members/me/",
- WorkspaceMemberUserEndpoint.as_view(),
- name="workspace-member-details",
- ),
- path(
- "workspaces//workspace-views/",
- WorkspaceMemberUserViewsEndpoint.as_view(),
- name="workspace-member-details",
- ),
- path(
- "workspaces//workspace-themes/",
- WorkspaceThemeViewSet.as_view(
- {
- "get": "list",
- "post": "create",
- }
- ),
- name="workspace-themes",
- ),
- path(
- "workspaces//workspace-themes//",
- WorkspaceThemeViewSet.as_view(
- {
- "get": "retrieve",
- "patch": "partial_update",
- "delete": "destroy",
- }
- ),
- name="workspace-themes",
- ),
- path(
- "workspaces//user-stats//",
- WorkspaceUserProfileStatsEndpoint.as_view(),
- name="workspace-user-stats",
- ),
- path(
- "workspaces//user-activity//",
- WorkspaceUserActivityEndpoint.as_view(),
- name="workspace-user-activity",
- ),
- path(
- "workspaces//user-profile//",
- WorkspaceUserProfileEndpoint.as_view(),
- name="workspace-user-profile-page",
- ),
- path(
- "workspaces//user-issues//",
- WorkspaceUserProfileIssuesEndpoint.as_view(),
- name="workspace-user-profile-issues",
- ),
- path(
- "workspaces//labels/",
- WorkspaceLabelsEndpoint.as_view(),
- name="workspace-labels",
- ),
- path(
- "workspaces//members/leave/",
- LeaveWorkspaceEndpoint.as_view(),
- name="workspace-labels",
- ),
- ## End Workspaces ##
- # Projects
- path(
- "workspaces//projects/",
- ProjectViewSet.as_view(
- {
- "get": "list",
- "post": "create",
- }
- ),
- name="project",
- ),
- path(
- "workspaces//projects//",
- ProjectViewSet.as_view(
- {
- "get": "retrieve",
- "put": "update",
- "patch": "partial_update",
- "delete": "destroy",
- }
- ),
- name="project",
- ),
- path(
- "workspaces//project-identifiers/",
- ProjectIdentifierEndpoint.as_view(),
- name="project-identifiers",
- ),
- path(
- "workspaces//projects//invite/",
- InviteProjectEndpoint.as_view(),
- name="project",
- ),
- path(
- "workspaces//projects//members/",
- ProjectMemberViewSet.as_view({"get": "list"}),
- name="project",
- ),
- path(
- "workspaces//projects//members//",
- ProjectMemberViewSet.as_view(
- {
- "get": "retrieve",
- "patch": "partial_update",
- "delete": "destroy",
- }
- ),
- name="project",
- ),
- path(
- "workspaces//projects//project-members/",
- ProjectMemberEndpoint.as_view(),
- name="project",
- ),
- path(
- "workspaces//projects//members/add/",
- AddMemberToProjectEndpoint.as_view(),
- name="project",
- ),
- path(
- "workspaces//projects/join/",
- ProjectJoinEndpoint.as_view(),
- name="project",
- ),
- path(
- "workspaces//projects//team-invite/",
- AddTeamToProjectEndpoint.as_view(),
- name="projects",
- ),
- path(
- "workspaces//projects//invitations/",
- ProjectMemberInvitationsViewset.as_view({"get": "list"}),
- name="workspace",
- ),
- path(
- "workspaces//projects//invitations//",
- ProjectMemberInvitationsViewset.as_view(
- {
- "get": "retrieve",
- "delete": "destroy",
- }
- ),
- name="project",
- ),
- path(
- "workspaces//projects//project-views/",
- ProjectUserViewsEndpoint.as_view(),
- name="project-view",
- ),
- path(
- "workspaces//projects//project-members/me/",
- ProjectMemberUserEndpoint.as_view(),
- name="project-view",
- ),
- path(
- "workspaces//user-favorite-projects/",
- ProjectFavoritesViewSet.as_view(
- {
- "get": "list",
- "post": "create",
- }
- ),
- name="project",
- ),
- path(
- "workspaces//user-favorite-projects//",
- ProjectFavoritesViewSet.as_view(
- {
- "delete": "destroy",
- }
- ),
- name="project",
- ),
- path(
- "workspaces//projects//members/leave/",
- LeaveProjectEndpoint.as_view(),
- name="project",
- ),
- path(
- "project-covers/",
- ProjectPublicCoverImagesEndpoint.as_view(),
- name="project-covers",
- ),
- # End Projects
- # States
- path(
- "workspaces//projects//states/",
- StateViewSet.as_view(
- {
- "get": "list",
- "post": "create",
- }
- ),
- name="project-states",
- ),
- path(
- "workspaces//projects//states//",
- StateViewSet.as_view(
- {
- "get": "retrieve",
- "put": "update",
- "patch": "partial_update",
- "delete": "destroy",
- }
- ),
- name="project-state",
- ),
- # End States ##
- # Estimates
- path(
- "workspaces//projects//project-estimates/",
- ProjectEstimatePointEndpoint.as_view(),
- name="project-estimate-points",
- ),
- path(
- "workspaces//projects//estimates/",
- BulkEstimatePointEndpoint.as_view(
- {
- "get": "list",
- "post": "create",
- }
- ),
- name="bulk-create-estimate-points",
- ),
- path(
- "workspaces//projects//estimates//",
- BulkEstimatePointEndpoint.as_view(
- {
- "get": "retrieve",
- "patch": "partial_update",
- "delete": "destroy",
- }
- ),
- name="bulk-create-estimate-points",
- ),
- # End Estimates ##
- # Views
- path(
- "workspaces//projects//views/",
- IssueViewViewSet.as_view(
- {
- "get": "list",
- "post": "create",
- }
- ),
- name="project-view",
- ),
- path(
- "workspaces//projects//views//",
- IssueViewViewSet.as_view(
- {
- "get": "retrieve",
- "put": "update",
- "patch": "partial_update",
- "delete": "destroy",
- }
- ),
- name="project-view",
- ),
- path(
- "workspaces//views/",
- GlobalViewViewSet.as_view(
- {
- "get": "list",
- "post": "create",
- }
- ),
- name="global-view",
- ),
- path(
- "workspaces//views//",
- GlobalViewViewSet.as_view(
- {
- "get": "retrieve",
- "put": "update",
- "patch": "partial_update",
- "delete": "destroy",
- }
- ),
- name="global-view",
- ),
- path(
- "workspaces//issues/",
- GlobalViewIssuesViewSet.as_view(
- {
- "get": "list",
- }
- ),
- name="global-view-issues",
- ),
- path(
- "workspaces//projects//user-favorite-views/",
- IssueViewFavoriteViewSet.as_view(
- {
- "get": "list",
- "post": "create",
- }
- ),
- name="user-favorite-view",
- ),
- path(
- "workspaces//projects//user-favorite-views//",
- IssueViewFavoriteViewSet.as_view(
- {
- "delete": "destroy",
- }
- ),
- name="user-favorite-view",
- ),
- ## End Views
- ## Cycles
- path(
- "workspaces//projects//cycles/",
- CycleViewSet.as_view(
- {
- "get": "list",
- "post": "create",
- }
- ),
- name="project-cycle",
- ),
- path(
- "workspaces//projects//cycles//",
- CycleViewSet.as_view(
- {
- "get": "retrieve",
- "put": "update",
- "patch": "partial_update",
- "delete": "destroy",
- }
- ),
- name="project-cycle",
- ),
- path(
- "workspaces//projects//cycles//cycle-issues/",
- CycleIssueViewSet.as_view(
- {
- "get": "list",
- "post": "create",
- }
- ),
- name="project-cycle",
- ),
- path(
- "workspaces//projects//cycles//cycle-issues//",
- CycleIssueViewSet.as_view(
- {
- "get": "retrieve",
- "put": "update",
- "patch": "partial_update",
- "delete": "destroy",
- }
- ),
- name="project-cycle",
- ),
- path(
- "workspaces//projects//cycles/date-check/",
- CycleDateCheckEndpoint.as_view(),
- name="project-cycle",
- ),
- path(
- "workspaces//projects//user-favorite-cycles/",
- CycleFavoriteViewSet.as_view(
- {
- "get": "list",
- "post": "create",
- }
- ),
- name="user-favorite-cycle",
- ),
- path(
- "workspaces//projects//user-favorite-cycles//",
- CycleFavoriteViewSet.as_view(
- {
- "delete": "destroy",
- }
- ),
- name="user-favorite-cycle",
- ),
- path(
- "workspaces//projects//cycles//transfer-issues/",
- TransferCycleIssueEndpoint.as_view(),
- name="transfer-issues",
- ),
- ## End Cycles
- # Issue
- path(
- "workspaces//projects//issues/",
- IssueViewSet.as_view(
- {
- "get": "list",
- "post": "create",
- }
- ),
- name="project-issue",
- ),
- path(
- "workspaces//projects//issues//",
- IssueViewSet.as_view(
- {
- "get": "retrieve",
- "put": "update",
- "patch": "partial_update",
- "delete": "destroy",
- }
- ),
- name="project-issue",
- ),
- path(
- "workspaces//projects//issue-labels/",
- LabelViewSet.as_view(
- {
- "get": "list",
- "post": "create",
- }
- ),
- name="project-issue-labels",
- ),
- path(
- "workspaces//projects//issue-labels//",
- LabelViewSet.as_view(
- {
- "get": "retrieve",
- "put": "update",
- "patch": "partial_update",
- "delete": "destroy",
- }
- ),
- name="project-issue-labels",
- ),
- path(
- "workspaces//projects//bulk-create-labels/",
- BulkCreateIssueLabelsEndpoint.as_view(),
- name="project-bulk-labels",
- ),
- path(
- "workspaces//projects//bulk-delete-issues/",
- BulkDeleteIssuesEndpoint.as_view(),
- name="project-issues-bulk",
- ),
- path(
- "workspaces//projects//bulk-import-issues//",
- BulkImportIssuesEndpoint.as_view(),
- name="project-issues-bulk",
- ),
- path(
- "workspaces//my-issues/",
- UserWorkSpaceIssues.as_view(),
- name="workspace-issues",
- ),
- path(
- "workspaces//projects//issues//sub-issues/",
- SubIssuesEndpoint.as_view(),
- name="sub-issues",
- ),
- path(
- "workspaces//projects//issues//issue-links/",
- IssueLinkViewSet.as_view(
- {
- "get": "list",
- "post": "create",
- }
- ),
- name="project-issue-links",
- ),
- path(
- "workspaces//projects//issues//issue-links//",
- IssueLinkViewSet.as_view(
- {
- "get": "retrieve",
- "put": "update",
- "patch": "partial_update",
- "delete": "destroy",
- }
- ),
- name="project-issue-links",
- ),
- path(
- "workspaces//projects//issues//issue-attachments/",
- IssueAttachmentEndpoint.as_view(),
- name="project-issue-attachments",
- ),
- path(
- "workspaces//projects//issues//issue-attachments//",
- IssueAttachmentEndpoint.as_view(),
- name="project-issue-attachments",
- ),
- path(
- "workspaces//export-issues/",
- ExportIssuesEndpoint.as_view(),
- name="export-issues",
- ),
- ## End Issues
- ## Issue Activity
- path(
- "workspaces//projects//issues/