Compare commits

..
473 changed files with 8614 additions and 17094 deletions
+10 -10
View File
@@ -170,7 +170,7 @@ jobs:
name: Checkout Files
uses: actions/checkout@v4
- name: Admin Build and Push
uses: ./.github/actions/build-push-ce
uses: ./.github/actions/buildpush-action
with:
build-release: ${{ needs.branch_build_setup.outputs.build_release }}
build-prerelease: ${{ needs.branch_build_setup.outputs.build_prerelease }}
@@ -196,7 +196,7 @@ jobs:
name: Checkout Files
uses: actions/checkout@v4
- name: Web Build and Push
uses: ./.github/actions/build-push-ce
uses: ./.github/actions/buildpush-action
with:
build-release: ${{ needs.branch_build_setup.outputs.build_release }}
build-prerelease: ${{ needs.branch_build_setup.outputs.build_prerelease }}
@@ -222,7 +222,7 @@ jobs:
name: Checkout Files
uses: actions/checkout@v4
- name: Space Build and Push
uses: ./.github/actions/build-push-ce
uses: ./.github/actions/buildpush-action
with:
build-release: ${{ needs.branch_build_setup.outputs.build_release }}
build-prerelease: ${{ needs.branch_build_setup.outputs.build_prerelease }}
@@ -248,7 +248,7 @@ jobs:
name: Checkout Files
uses: actions/checkout@v4
- name: Live Build and Push
uses: ./.github/actions/build-push-ce
uses: ./.github/actions/buildpush-action
with:
build-release: ${{ needs.branch_build_setup.outputs.build_release }}
build-prerelease: ${{ needs.branch_build_setup.outputs.build_prerelease }}
@@ -274,7 +274,7 @@ jobs:
name: Checkout Files
uses: actions/checkout@v4
- name: Backend Build and Push
uses: ./.github/actions/build-push-ce
uses: ./.github/actions/buildpush-action
with:
build-release: ${{ needs.branch_build_setup.outputs.build_release }}
build-prerelease: ${{ needs.branch_build_setup.outputs.build_prerelease }}
@@ -300,7 +300,7 @@ jobs:
name: Checkout Files
uses: actions/checkout@v4
- name: Proxy Build and Push
uses: ./.github/actions/build-push-ce
uses: ./.github/actions/buildpush-action
with:
build-release: ${{ needs.branch_build_setup.outputs.build_release }}
build-prerelease: ${{ needs.branch_build_setup.outputs.build_prerelease }}
@@ -320,7 +320,7 @@ jobs:
if: ${{ needs.branch_build_setup.outputs.build_type == 'Build' }}
name: Attach Assets to Build
runs-on: ubuntu-20.04
needs: [branch_build_setup]
needs: [ branch_build_setup ]
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -340,7 +340,7 @@ jobs:
${{ github.workspace }}/deploy/selfhost/restore.sh
${{ github.workspace }}/deploy/selfhost/docker-compose.yml
${{ github.workspace }}/deploy/selfhost/variables.env
publish_release:
if: ${{ needs.branch_build_setup.outputs.build_type == 'Release' }}
name: Build Release
@@ -353,7 +353,7 @@ jobs:
branch_build_push_space,
branch_build_push_live,
branch_build_push_apiserver,
branch_build_push_proxy,
branch_build_push_proxy
]
env:
REL_VERSION: ${{ needs.branch_build_setup.outputs.release_version }}
@@ -380,4 +380,4 @@ jobs:
${{ github.workspace }}/deploy/selfhost/setup.sh
${{ github.workspace }}/deploy/selfhost/restore.sh
${{ github.workspace }}/deploy/selfhost/docker-compose.yml
${{ github.workspace }}/deploy/selfhost/variables.env
${{ github.workspace }}/deploy/selfhost/variables.env
@@ -8,13 +8,14 @@ on:
env:
CURRENT_BRANCH: ${{ github.ref_name }}
TARGET_BRANCH: "preview" # The target branch that you would like to merge changes like develop
TARGET_BRANCH: ${{ vars.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: ${{ vars.SYNC_PR_REVIEWER }}
ACCOUNT_USER_NAME: ${{ vars.ACCOUNT_USER_NAME }}
ACCOUNT_USER_EMAIL: ${{ vars.ACCOUNT_USER_EMAIL }}
jobs:
create_pull_request:
Create_PR:
runs-on: ubuntu-latest
permissions:
pull-requests: write
@@ -47,6 +48,6 @@ jobs:
echo "Pull Request already exists: $PR_EXISTS"
else
echo "Creating new pull request"
PR_URL=$(gh pr create --base $TARGET_BRANCH --head $CURRENT_BRANCH --title "${{ vars.SYNC_PR_TITLE }}" --body "")
PR_URL=$(gh pr create --base $TARGET_BRANCH --head $CURRENT_BRANCH --title "sync: community changes" --body "")
echo "Pull Request created: $PR_URL"
fi
@@ -35,8 +35,9 @@ jobs:
env:
GH_TOKEN: ${{ secrets.ACCESS_TOKEN }}
run: |
RUN_ID="${{ github.run_id }}"
TARGET_REPO="${{ vars.SYNC_TARGET_REPO }}"
TARGET_BRANCH="${{ vars.SYNC_TARGET_BRANCH_NAME }}"
TARGET_BRANCH="sync/${RUN_ID}"
SOURCE_BRANCH="${{ env.SOURCE_BRANCH_NAME }}"
git checkout $SOURCE_BRANCH
+1 -1
View File
@@ -41,7 +41,7 @@
"@plane/eslint-config": "*",
"@plane/typescript-config": "*",
"@types/node": "18.16.1",
"@types/react": "^18.3.11",
"@types/react": "^18.2.48",
"@types/react-dom": "^18.2.18",
"@types/uuid": "^9.0.8",
"@types/zxcvbn": "^4.4.4",
+1 -2
View File
@@ -57,6 +57,5 @@ ADMIN_BASE_URL=
SPACE_BASE_URL=
APP_BASE_URL=
# Hard delete files after days
HARD_DELETE_AFTER_DAYS=60
HARD_DELETE_AFTER_DAYS=60
-1
View File
@@ -4,7 +4,6 @@ FROM python:3.12.5-alpine AS backend
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
ENV INSTANCE_CHANGELOG_URL https://api.plane.so/api/public/anchor/8e1c2e4c7bc5493eb7731be3862f6960/pages/
WORKDIR /code
-1
View File
@@ -4,7 +4,6 @@ FROM python:3.12.5-alpine AS backend
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
ENV INSTANCE_CHANGELOG_URL https://api.plane.so/api/public/anchor/8e1c2e4c7bc5493eb7731be3862f6960/pages/
RUN apk --no-cache add \
"bash~=5.2" \
+5 -28
View File
@@ -78,7 +78,6 @@ class CycleAPIEndpoint(BaseAPIView):
filter=Q(
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -89,7 +88,6 @@ class CycleAPIEndpoint(BaseAPIView):
issue_cycle__issue__state__group="completed",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -100,7 +98,6 @@ class CycleAPIEndpoint(BaseAPIView):
issue_cycle__issue__state__group="cancelled",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -111,7 +108,6 @@ class CycleAPIEndpoint(BaseAPIView):
issue_cycle__issue__state__group="started",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -122,7 +118,6 @@ class CycleAPIEndpoint(BaseAPIView):
issue_cycle__issue__state__group="unstarted",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -133,7 +128,6 @@ class CycleAPIEndpoint(BaseAPIView):
issue_cycle__issue__state__group="backlog",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -411,6 +405,10 @@ class CycleAPIEndpoint(BaseAPIView):
)
# Delete the cycle
cycle.delete()
# Delete the cycle issues
CycleIssue.objects.filter(
cycle_id=self.kwargs.get("pk"),
).delete()
# Delete the user favorite cycle
UserFavorite.objects.filter(
entity_type="cycle",
@@ -443,7 +441,6 @@ class CycleArchiveUnarchiveAPIEndpoint(BaseAPIView):
filter=Q(
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -454,7 +451,6 @@ class CycleArchiveUnarchiveAPIEndpoint(BaseAPIView):
issue_cycle__issue__state__group="completed",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -465,7 +461,6 @@ class CycleArchiveUnarchiveAPIEndpoint(BaseAPIView):
issue_cycle__issue__state__group="cancelled",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -476,7 +471,6 @@ class CycleArchiveUnarchiveAPIEndpoint(BaseAPIView):
issue_cycle__issue__state__group="started",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -487,7 +481,6 @@ class CycleArchiveUnarchiveAPIEndpoint(BaseAPIView):
issue_cycle__issue__state__group="unstarted",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -498,7 +491,6 @@ class CycleArchiveUnarchiveAPIEndpoint(BaseAPIView):
issue_cycle__issue__state__group="backlog",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -512,7 +504,6 @@ class CycleArchiveUnarchiveAPIEndpoint(BaseAPIView):
issue_cycle__issue__state__group="completed",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -523,7 +514,6 @@ class CycleArchiveUnarchiveAPIEndpoint(BaseAPIView):
issue_cycle__issue__state__group="started",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -629,10 +619,7 @@ class CycleIssueAPIEndpoint(BaseAPIView):
# List
order_by = request.GET.get("order_by", "created_at")
issues = (
Issue.issue_objects.filter(
issue_cycle__cycle_id=cycle_id,
issue_cycle__deleted_at__isnull=True,
)
Issue.issue_objects.filter(issue_cycle__cycle_id=cycle_id)
.annotate(
sub_issues_count=Issue.issue_objects.filter(
parent=OuterRef("id")
@@ -829,7 +816,6 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView):
filter=Q(
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -840,7 +826,6 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView):
issue_cycle__issue__state__group="completed",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -851,7 +836,6 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView):
issue_cycle__issue__state__group="cancelled",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -862,7 +846,6 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView):
issue_cycle__issue__state__group="started",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -873,7 +856,6 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView):
issue_cycle__issue__state__group="unstarted",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -884,7 +866,6 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView):
issue_cycle__issue__state__group="backlog",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -901,7 +882,6 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView):
assignee_estimate_data = (
Issue.issue_objects.filter(
issue_cycle__cycle_id=cycle_id,
issue_cycle__deleted_at__isnull=True,
workspace__slug=slug,
project_id=project_id,
)
@@ -977,7 +957,6 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView):
label_distribution_data = (
Issue.issue_objects.filter(
issue_cycle__cycle_id=cycle_id,
issue_cycle__deleted_at__isnull=True,
workspace__slug=slug,
project_id=project_id,
)
@@ -1039,7 +1018,6 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView):
assignee_distribution = (
Issue.issue_objects.filter(
issue_cycle__cycle_id=cycle_id,
issue_cycle__deleted_at__isnull=True,
workspace__slug=slug,
project_id=project_id,
)
@@ -1118,7 +1096,6 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView):
label_distribution = (
Issue.issue_objects.filter(
issue_cycle__cycle_id=cycle_id,
issue_cycle__deleted_at__isnull=True,
workspace__slug=slug,
project_id=project_id,
)
+2 -9
View File
@@ -227,10 +227,7 @@ class InboxIssueAPIEndpoint(BaseAPIView):
ArrayAgg(
"labels__id",
distinct=True,
filter=Q(
~Q(labels__id__isnull=True)
& Q(label_issue__deleted_at__isnull=True),
),
filter=~Q(labels__id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -238,11 +235,7 @@ class InboxIssueAPIEndpoint(BaseAPIView):
ArrayAgg(
"assignees__id",
distinct=True,
filter=Q(
~Q(assignees__id__isnull=True)
& Q(assignees__member_project__is_active=True)
& Q(issue_assignee__deleted_at__isnull=True)
),
filter=~Q(assignees__id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
+1 -9
View File
@@ -16,7 +16,6 @@ from django.db.models import (
Q,
Value,
When,
Subquery,
)
from django.utils import timezone
@@ -49,7 +48,6 @@ from plane.db.models import (
Label,
Project,
ProjectMember,
CycleIssue,
)
from .base import BaseAPIView
@@ -204,13 +202,7 @@ class IssueAPIEndpoint(BaseAPIView):
issue_queryset = (
self.get_queryset()
.annotate(
cycle_id=Subquery(
CycleIssue.objects.filter(
issue=OuterRef("id"), deleted_at__isnull=True
).values("cycle_id")[:1]
)
)
.annotate(cycle_id=F("issue_cycle__cycle_id"))
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
+1 -16
View File
@@ -71,7 +71,6 @@ class ModuleAPIEndpoint(BaseAPIView):
filter=Q(
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
issue_module__deleted_at__isnull=True,
),
distinct=True,
),
@@ -83,7 +82,6 @@ class ModuleAPIEndpoint(BaseAPIView):
issue_module__issue__state__group="completed",
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
issue_module__deleted_at__isnull=True,
),
distinct=True,
)
@@ -95,7 +93,6 @@ class ModuleAPIEndpoint(BaseAPIView):
issue_module__issue__state__group="cancelled",
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
issue_module__deleted_at__isnull=True,
),
distinct=True,
)
@@ -107,7 +104,6 @@ class ModuleAPIEndpoint(BaseAPIView):
issue_module__issue__state__group="started",
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
issue_module__deleted_at__isnull=True,
),
distinct=True,
)
@@ -119,7 +115,6 @@ class ModuleAPIEndpoint(BaseAPIView):
issue_module__issue__state__group="unstarted",
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
issue_module__deleted_at__isnull=True,
),
distinct=True,
)
@@ -131,7 +126,6 @@ class ModuleAPIEndpoint(BaseAPIView):
issue_module__issue__state__group="backlog",
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
issue_module__deleted_at__isnull=True,
),
distinct=True,
)
@@ -373,10 +367,7 @@ class ModuleIssueAPIEndpoint(BaseAPIView):
def get(self, request, slug, project_id, module_id):
order_by = request.GET.get("order_by", "created_at")
issues = (
Issue.issue_objects.filter(
issue_module__module_id=module_id,
issue_module__deleted_at__isnull=True,
)
Issue.issue_objects.filter(issue_module__module_id=module_id)
.annotate(
sub_issues_count=Issue.issue_objects.filter(
parent=OuterRef("id")
@@ -561,7 +552,6 @@ class ModuleArchiveUnarchiveAPIEndpoint(BaseAPIView):
filter=Q(
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
issue_module__deleted_at__isnull=True,
),
distinct=True,
),
@@ -573,7 +563,6 @@ class ModuleArchiveUnarchiveAPIEndpoint(BaseAPIView):
issue_module__issue__state__group="completed",
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
issue_module__deleted_at__isnull=True,
),
distinct=True,
)
@@ -585,7 +574,6 @@ class ModuleArchiveUnarchiveAPIEndpoint(BaseAPIView):
issue_module__issue__state__group="cancelled",
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
issue_module__deleted_at__isnull=True,
),
distinct=True,
)
@@ -597,7 +585,6 @@ class ModuleArchiveUnarchiveAPIEndpoint(BaseAPIView):
issue_module__issue__state__group="started",
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
issue_module__deleted_at__isnull=True,
),
distinct=True,
)
@@ -609,7 +596,6 @@ class ModuleArchiveUnarchiveAPIEndpoint(BaseAPIView):
issue_module__issue__state__group="unstarted",
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
issue_module__deleted_at__isnull=True,
),
distinct=True,
)
@@ -621,7 +607,6 @@ class ModuleArchiveUnarchiveAPIEndpoint(BaseAPIView):
issue_module__issue__state__group="backlog",
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
issue_module__deleted_at__isnull=True,
),
distinct=True,
)
+37 -43
View File
@@ -49,47 +49,44 @@ class DynamicBaseSerializer(BaseSerializer):
allowed.append(list(item.keys())[0])
for field in allowed:
if field not in self.fields:
from . import (
WorkspaceLiteSerializer,
ProjectLiteSerializer,
UserLiteSerializer,
StateLiteSerializer,
IssueSerializer,
LabelSerializer,
CycleIssueSerializer,
IssueLiteSerializer,
IssueRelationSerializer,
InboxIssueLiteSerializer,
IssueReactionLiteSerializer,
IssueLinkLiteSerializer,
RelatedIssueSerializer,
)
from . import (
WorkspaceLiteSerializer,
ProjectLiteSerializer,
UserLiteSerializer,
StateLiteSerializer,
IssueSerializer,
LabelSerializer,
CycleIssueSerializer,
IssueLiteSerializer,
IssueRelationSerializer,
InboxIssueLiteSerializer,
IssueReactionLiteSerializer,
IssueLinkLiteSerializer,
)
# Expansion mapper
expansion = {
"user": UserLiteSerializer,
"workspace": WorkspaceLiteSerializer,
"project": ProjectLiteSerializer,
"default_assignee": UserLiteSerializer,
"project_lead": UserLiteSerializer,
"state": StateLiteSerializer,
"created_by": UserLiteSerializer,
"issue": IssueSerializer,
"actor": UserLiteSerializer,
"owned_by": UserLiteSerializer,
"members": UserLiteSerializer,
"assignees": UserLiteSerializer,
"labels": LabelSerializer,
"issue_cycle": CycleIssueSerializer,
"parent": IssueLiteSerializer,
"issue_relation": IssueRelationSerializer,
"issue_related": RelatedIssueSerializer,
"issue_inbox": InboxIssueLiteSerializer,
"issue_reactions": IssueReactionLiteSerializer,
"issue_link": IssueLinkLiteSerializer,
"sub_issues": IssueLiteSerializer,
}
# Expansion mapper
expansion = {
"user": UserLiteSerializer,
"workspace": WorkspaceLiteSerializer,
"project": ProjectLiteSerializer,
"default_assignee": UserLiteSerializer,
"project_lead": UserLiteSerializer,
"state": StateLiteSerializer,
"created_by": UserLiteSerializer,
"issue": IssueSerializer,
"actor": UserLiteSerializer,
"owned_by": UserLiteSerializer,
"members": UserLiteSerializer,
"assignees": UserLiteSerializer,
"labels": LabelSerializer,
"issue_cycle": CycleIssueSerializer,
"parent": IssueLiteSerializer,
"issue_relation": IssueRelationSerializer,
"issue_inbox": InboxIssueLiteSerializer,
"issue_reactions": IssueReactionLiteSerializer,
"issue_link": IssueLinkLiteSerializer,
"sub_issues": IssueLiteSerializer,
}
if field not in self.fields and field in expansion:
self.fields[field] = expansion[field](
@@ -107,7 +104,6 @@ class DynamicBaseSerializer(BaseSerializer):
"issue_attachment",
"issue_link",
"sub_issues",
"issue_related",
]
else False
)
@@ -137,7 +133,6 @@ class DynamicBaseSerializer(BaseSerializer):
IssueReactionLiteSerializer,
IssueAttachmentLiteSerializer,
IssueLinkLiteSerializer,
RelatedIssueSerializer,
)
# Expansion mapper
@@ -158,7 +153,6 @@ class DynamicBaseSerializer(BaseSerializer):
"issue_cycle": CycleIssueSerializer,
"parent": IssueLiteSerializer,
"issue_relation": IssueRelationSerializer,
"issue_related": RelatedIssueSerializer,
"issue_inbox": InboxIssueLiteSerializer,
"issue_reactions": IssueReactionLiteSerializer,
"issue_attachment": IssueAttachmentLiteSerializer,
+1 -1
View File
@@ -204,7 +204,7 @@ class DraftIssueCreateSerializer(BaseSerializer):
if cycle_id != "not_provided":
DraftIssueCycle.objects.filter(draft_issue=instance).delete()
if cycle_id:
if cycle_id is not None:
DraftIssueCycle.objects.create(
cycle_id=cycle_id,
draft_issue=instance,
@@ -65,7 +65,6 @@ class WorkSpaceMemberSerializer(DynamicBaseSerializer):
class WorkspaceMemberMeSerializer(BaseSerializer):
draft_issue_count = serializers.IntegerField(read_only=True)
class Meta:
model = WorkspaceMember
fields = "__all__"
-13
View File
@@ -21,9 +21,7 @@ from plane.app.views import (
BulkArchiveIssuesEndpoint,
DeletedIssuesListViewSet,
IssuePaginatedViewSet,
IssueDetailEndpoint,
IssueAttachmentV2Endpoint,
IssueBulkUpdateDateEndpoint,
)
urlpatterns = [
@@ -42,12 +40,6 @@ urlpatterns = [
),
name="project-issue",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues-detail/",
IssueDetailEndpoint.as_view(),
name="project-issue-detail",
),
# updated v1 paginated issues
# updated v2 paginated issues
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/v2/issues/",
@@ -315,9 +307,4 @@ urlpatterns = [
DeletedIssuesListViewSet.as_view(),
name="deleted-issues",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issue-dates/",
IssueBulkUpdateDateEndpoint.as_view(),
name="project-issue-dates",
),
]
-2
View File
@@ -130,8 +130,6 @@ from .issue.base import (
BulkDeleteIssuesEndpoint,
DeletedIssuesListViewSet,
IssuePaginatedViewSet,
IssueDetailEndpoint,
IssueBulkUpdateDateEndpoint,
)
from .issue.activity import (
+1 -6
View File
@@ -110,10 +110,7 @@ class AnalyticsEndpoint(BaseAPIView):
if x_axis in ["labels__id"] or segment in ["labels__id"]:
label_details = (
Issue.objects.filter(
workspace__slug=slug,
**filters,
labels__id__isnull=False,
label_issue__deleted_at__isnull=True,
workspace__slug=slug, **filters, labels__id__isnull=False
)
.distinct("labels__id")
.order_by("labels__id")
@@ -171,7 +168,6 @@ class AnalyticsEndpoint(BaseAPIView):
workspace__slug=slug,
**filters,
issue_cycle__cycle_id__isnull=False,
issue_cycle__deleted_at__isnull=True,
)
.distinct("issue_cycle__cycle_id")
.order_by("issue_cycle__cycle_id")
@@ -190,7 +186,6 @@ class AnalyticsEndpoint(BaseAPIView):
workspace__slug=slug,
**filters,
issue_module__module_id__isnull=False,
issue_module__deleted_at__isnull=True,
)
.distinct("issue_module__module_id")
.order_by("issue_module__module_id")
+3 -3
View File
@@ -50,7 +50,7 @@ class FileAssetEndpoint(BaseAPIView):
asset_key = str(workspace_id) + "/" + asset_key
file_asset = FileAsset.objects.get(asset=asset_key)
file_asset.is_deleted = True
file_asset.save(update_fields=["is_deleted"])
file_asset.save()
return Response(status=status.HTTP_204_NO_CONTENT)
@@ -59,7 +59,7 @@ class FileAssetViewSet(BaseViewSet):
asset_key = str(workspace_id) + "/" + asset_key
file_asset = FileAsset.objects.get(asset=asset_key)
file_asset.is_deleted = False
file_asset.save(update_fields=["is_deleted"])
file_asset.save()
return Response(status=status.HTTP_204_NO_CONTENT)
@@ -96,5 +96,5 @@ class UserAssetsEndpoint(BaseAPIView):
asset=asset_key, created_by=request.user
)
file_asset.is_deleted = True
file_asset.save(update_fields=["is_deleted"])
file_asset.save()
return Response(status=status.HTTP_204_NO_CONTENT)
+24 -16
View File
@@ -22,7 +22,6 @@ from plane.db.models import (
from plane.settings.storage import S3Storage
from plane.app.permissions import allow_permission, ROLE
from plane.utils.cache import invalidate_cache_directly
from plane.bgtasks.storage_metadata_task import get_asset_object_metadata
class UserAssetsV2Endpoint(BaseAPIView):
@@ -34,7 +33,7 @@ class UserAssetsV2Endpoint(BaseAPIView):
return
asset.is_deleted = True
asset.deleted_at = timezone.now()
asset.save(update_fields=["is_deleted", "deleted_at"])
asset.save()
return
def entity_asset_save(self, asset_id, entity_type, asset, request):
@@ -194,11 +193,14 @@ class UserAssetsV2Endpoint(BaseAPIView):
def patch(self, request, asset_id):
# get the asset id
asset = FileAsset.objects.get(id=asset_id, user_id=request.user.id)
storage = S3Storage(request=request)
# get the storage metadata
asset.is_uploaded = True
# get the storage metadata
if not asset.storage_metadata:
get_asset_object_metadata.delay(asset_id=str(asset_id))
if asset.storage_metadata is None:
asset.storage_metadata = storage.get_object_metadata(
object_name=asset.asset.name
)
# get the entity and save the asset id for the request field
self.entity_asset_save(
asset_id=asset_id,
@@ -209,7 +211,7 @@ class UserAssetsV2Endpoint(BaseAPIView):
# update the attributes
asset.attributes = request.data.get("attributes", asset.attributes)
# save the asset
asset.save(update_fields=["is_uploaded", "attributes"])
asset.save()
return Response(status=status.HTTP_204_NO_CONTENT)
def delete(self, request, asset_id):
@@ -220,7 +222,7 @@ class UserAssetsV2Endpoint(BaseAPIView):
self.entity_asset_delete(
entity_type=asset.entity_type, asset=asset, request=request
)
asset.save(update_fields=["is_deleted", "deleted_at"])
asset.save()
return Response(status=status.HTTP_204_NO_CONTENT)
@@ -279,7 +281,7 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
# Mark the asset as deleted
asset.is_deleted = True
asset.deleted_at = timezone.now()
asset.save(update_fields=["is_deleted", "deleted_at"])
asset.save()
return
def entity_asset_save(self, asset_id, entity_type, asset, request):
@@ -444,11 +446,14 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
def patch(self, request, slug, asset_id):
# get the asset id
asset = FileAsset.objects.get(id=asset_id, workspace__slug=slug)
storage = S3Storage(request=request)
# get the storage metadata
asset.is_uploaded = True
# get the storage metadata
if not asset.storage_metadata:
get_asset_object_metadata.delay(asset_id=str(asset_id))
if asset.storage_metadata is None:
asset.storage_metadata = storage.get_object_metadata(
object_name=asset.asset.name
)
# get the entity and save the asset id for the request field
self.entity_asset_save(
asset_id=asset_id,
@@ -459,7 +464,7 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
# update the attributes
asset.attributes = request.data.get("attributes", asset.attributes)
# save the asset
asset.save(update_fields=["is_uploaded", "attributes"])
asset.save()
return Response(status=status.HTTP_204_NO_CONTENT)
def delete(self, request, slug, asset_id):
@@ -470,7 +475,7 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
self.entity_asset_delete(
entity_type=asset.entity_type, asset=asset, request=request
)
asset.save(update_fields=["is_deleted", "deleted_at"])
asset.save()
return Response(status=status.HTTP_204_NO_CONTENT)
def get(self, request, slug, asset_id):
@@ -549,7 +554,7 @@ class AssetRestoreEndpoint(BaseAPIView):
asset = FileAsset.all_objects.get(id=asset_id, workspace__slug=slug)
asset.is_deleted = False
asset.deleted_at = None
asset.save(update_fields=["is_deleted", "deleted_at"])
asset.save()
return Response(status=status.HTTP_204_NO_CONTENT)
@@ -681,16 +686,19 @@ class ProjectAssetEndpoint(BaseAPIView):
asset = FileAsset.objects.get(
id=pk,
)
storage = S3Storage(request=request)
# get the storage metadata
asset.is_uploaded = True
# get the storage metadata
if not asset.storage_metadata:
get_asset_object_metadata.delay(asset_id=str(pk))
if asset.storage_metadata is None:
asset.storage_metadata = storage.get_object_metadata(
object_name=asset.asset.name
)
# update the attributes
asset.attributes = request.data.get("attributes", asset.attributes)
# save the asset
asset.save(update_fields=["is_uploaded", "attributes"])
asset.save()
return Response(status=status.HTTP_204_NO_CONTENT)
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
@@ -705,7 +713,7 @@ class ProjectAssetEndpoint(BaseAPIView):
asset.is_deleted = True
asset.deleted_at = timezone.now()
# Save the asset
asset.save(update_fields=["is_deleted", "deleted_at"])
asset.save()
return Response(status=status.HTTP_204_NO_CONTENT)
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
+1 -37
View File
@@ -48,7 +48,6 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
estimate_point__estimate__type="points",
state__group="backlog",
issue_cycle__cycle_id=OuterRef("pk"),
issue_cycle__deleted_at__isnull=True,
)
.values("issue_cycle__cycle_id")
.annotate(
@@ -63,7 +62,6 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
estimate_point__estimate__type="points",
state__group="unstarted",
issue_cycle__cycle_id=OuterRef("pk"),
issue_cycle__deleted_at__isnull=True,
)
.values("issue_cycle__cycle_id")
.annotate(
@@ -78,7 +76,6 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
estimate_point__estimate__type="points",
state__group="started",
issue_cycle__cycle_id=OuterRef("pk"),
issue_cycle__deleted_at__isnull=True,
)
.values("issue_cycle__cycle_id")
.annotate(
@@ -93,7 +90,6 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
estimate_point__estimate__type="points",
state__group="cancelled",
issue_cycle__cycle_id=OuterRef("pk"),
issue_cycle__deleted_at__isnull=True,
)
.values("issue_cycle__cycle_id")
.annotate(
@@ -108,7 +104,6 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
estimate_point__estimate__type="points",
state__group="completed",
issue_cycle__cycle_id=OuterRef("pk"),
issue_cycle__deleted_at__isnull=True,
)
.values("issue_cycle__cycle_id")
.annotate(
@@ -122,7 +117,6 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
Issue.issue_objects.filter(
estimate_point__estimate__type="points",
issue_cycle__cycle_id=OuterRef("pk"),
issue_cycle__deleted_at__isnull=True,
)
.values("issue_cycle__cycle_id")
.annotate(
@@ -166,7 +160,6 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
filter=Q(
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__issue__deleted_at__isnull=True,
),
)
)
@@ -178,7 +171,6 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
issue_cycle__issue__state__group="completed",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__issue__deleted_at__isnull=True,
),
)
)
@@ -190,7 +182,6 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
issue_cycle__issue__state__group="cancelled",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__issue__deleted_at__isnull=True,
),
)
)
@@ -202,7 +193,6 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
issue_cycle__issue__state__group="started",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__issue__deleted_at__isnull=True,
),
)
)
@@ -214,7 +204,6 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
issue_cycle__issue__state__group="unstarted",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__issue__deleted_at__isnull=True,
),
)
)
@@ -226,7 +215,6 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
issue_cycle__issue__state__group="backlog",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__issue__deleted_at__isnull=True,
),
)
)
@@ -354,7 +342,6 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
project_id=self.kwargs.get("project_id"),
parent__isnull=False,
issue_cycle__cycle_id=pk,
issue_cycle__deleted_at__isnull=True,
)
.order_by()
.annotate(count=Func(F("id"), function="Count"))
@@ -409,7 +396,6 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
assignee_distribution = (
Issue.issue_objects.filter(
issue_cycle__cycle_id=pk,
issue_cycle__deleted_at__isnull=True,
workspace__slug=slug,
project_id=project_id,
)
@@ -467,7 +453,6 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
label_distribution = (
Issue.issue_objects.filter(
issue_cycle__cycle_id=pk,
issue_cycle__deleted_at__isnull=True,
workspace__slug=slug,
project_id=project_id,
)
@@ -523,33 +508,13 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
assignee_distribution = (
Issue.issue_objects.filter(
issue_cycle__cycle_id=pk,
issue_cycle__deleted_at__isnull=True,
workspace__slug=slug,
project_id=project_id,
)
.annotate(first_name=F("assignees__first_name"))
.annotate(last_name=F("assignees__last_name"))
.annotate(assignee_id=F("assignees__id"))
.annotate(
avatar_url=Case(
# If `avatar_asset` exists, use it to generate the asset URL
When(
assignees__avatar_asset__isnull=False,
then=Concat(
Value("/api/assets/v2/static/"),
"assignees__avatar_asset", # Assuming avatar_asset has an id or relevant field
Value("/"),
),
),
# If `avatar_asset` is None, fall back to using `avatar` field directly
When(
assignees__avatar_asset__isnull=True,
then="assignees__avatar",
),
default=Value(None),
output_field=models.CharField(),
)
)
.annotate(avatar_url=F("assignees__avatar_url"))
.annotate(display_name=F("assignees__display_name"))
.values(
"first_name",
@@ -594,7 +559,6 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
label_distribution = (
Issue.issue_objects.filter(
issue_cycle__cycle_id=pk,
issue_cycle__deleted_at__isnull=True,
workspace__slug=slug,
project_id=project_id,
)
+5 -38
View File
@@ -102,7 +102,6 @@ class CycleViewSet(BaseViewSet):
filter=Q(
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -114,7 +113,6 @@ class CycleViewSet(BaseViewSet):
issue_cycle__issue__state__group="completed",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -144,11 +142,6 @@ class CycleViewSet(BaseViewSet):
distinct=True,
filter=~Q(
issue_cycle__issue__assignees__id__isnull=True
)
& (
Q(
issue_cycle__issue__issue_assignee__deleted_at__isnull=True
)
),
),
Value([], output_field=ArrayField(UUIDField())),
@@ -398,7 +391,6 @@ class CycleViewSet(BaseViewSet):
project_id=self.kwargs.get("project_id"),
parent__isnull=False,
issue_cycle__cycle_id=pk,
issue_cycle__deleted_at__isnull=True,
)
.order_by()
.annotate(count=Func(F("id"), function="Count"))
@@ -496,9 +488,12 @@ class CycleViewSet(BaseViewSet):
notification=True,
origin=request.META.get("HTTP_ORIGIN"),
)
# TODO: Soft delete the cycle break the onetoone relationship with cycle issue
# Delete the cycle
cycle.delete()
# Delete the cycle issues
CycleIssue.objects.filter(
cycle_id=self.kwargs.get("pk"),
).delete()
# Delete the user favorite cycle
UserFavorite.objects.filter(
user=request.user,
@@ -602,7 +597,6 @@ class TransferCycleIssueEndpoint(BaseAPIView):
filter=Q(
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -613,8 +607,6 @@ class TransferCycleIssueEndpoint(BaseAPIView):
issue_cycle__issue__state__group="completed",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__issue__deleted_at__isnull=True,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -625,8 +617,6 @@ class TransferCycleIssueEndpoint(BaseAPIView):
issue_cycle__issue__state__group="cancelled",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__issue__deleted_at__isnull=True,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -637,8 +627,6 @@ class TransferCycleIssueEndpoint(BaseAPIView):
issue_cycle__issue__state__group="started",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__issue__deleted_at__isnull=True,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -649,8 +637,6 @@ class TransferCycleIssueEndpoint(BaseAPIView):
issue_cycle__issue__state__group="unstarted",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__issue__deleted_at__isnull=True,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -661,8 +647,6 @@ class TransferCycleIssueEndpoint(BaseAPIView):
issue_cycle__issue__state__group="backlog",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__issue__deleted_at__isnull=True,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -679,7 +663,6 @@ class TransferCycleIssueEndpoint(BaseAPIView):
assignee_estimate_data = (
Issue.issue_objects.filter(
issue_cycle__cycle_id=cycle_id,
issue_cycle__deleted_at__isnull=True,
workspace__slug=slug,
project_id=project_id,
)
@@ -754,7 +737,6 @@ class TransferCycleIssueEndpoint(BaseAPIView):
label_distribution_data = (
Issue.issue_objects.filter(
issue_cycle__cycle_id=cycle_id,
issue_cycle__deleted_at__isnull=True,
workspace__slug=slug,
project_id=project_id,
)
@@ -816,7 +798,6 @@ class TransferCycleIssueEndpoint(BaseAPIView):
assignee_distribution = (
Issue.issue_objects.filter(
issue_cycle__cycle_id=cycle_id,
issue_cycle__deleted_at__isnull=True,
workspace__slug=slug,
project_id=project_id,
)
@@ -894,7 +875,6 @@ class TransferCycleIssueEndpoint(BaseAPIView):
label_distribution = (
Issue.issue_objects.filter(
issue_cycle__cycle_id=cycle_id,
issue_cycle__deleted_at__isnull=True,
workspace__slug=slug,
project_id=project_id,
)
@@ -1085,7 +1065,6 @@ class CycleProgressEndpoint(BaseAPIView):
Issue.issue_objects.filter(
estimate_point__estimate__type="points",
issue_cycle__cycle_id=cycle_id,
issue_cycle__deleted_at__isnull=True,
workspace__slug=slug,
project_id=project_id,
)
@@ -1138,7 +1117,6 @@ class CycleProgressEndpoint(BaseAPIView):
backlog_issues = Issue.issue_objects.filter(
issue_cycle__cycle_id=cycle_id,
issue_cycle__deleted_at__isnull=True,
workspace__slug=slug,
project_id=project_id,
state__group="backlog",
@@ -1146,7 +1124,6 @@ class CycleProgressEndpoint(BaseAPIView):
unstarted_issues = Issue.issue_objects.filter(
issue_cycle__cycle_id=cycle_id,
issue_cycle__deleted_at__isnull=True,
workspace__slug=slug,
project_id=project_id,
state__group="unstarted",
@@ -1154,7 +1131,6 @@ class CycleProgressEndpoint(BaseAPIView):
started_issues = Issue.issue_objects.filter(
issue_cycle__cycle_id=cycle_id,
issue_cycle__deleted_at__isnull=True,
workspace__slug=slug,
project_id=project_id,
state__group="started",
@@ -1162,7 +1138,6 @@ class CycleProgressEndpoint(BaseAPIView):
cancelled_issues = Issue.issue_objects.filter(
issue_cycle__cycle_id=cycle_id,
issue_cycle__deleted_at__isnull=True,
workspace__slug=slug,
project_id=project_id,
state__group="cancelled",
@@ -1170,7 +1145,6 @@ class CycleProgressEndpoint(BaseAPIView):
completed_issues = Issue.issue_objects.filter(
issue_cycle__cycle_id=cycle_id,
issue_cycle__deleted_at__isnull=True,
workspace__slug=slug,
project_id=project_id,
state__group="completed",
@@ -1178,7 +1152,6 @@ class CycleProgressEndpoint(BaseAPIView):
total_issues = Issue.issue_objects.filter(
issue_cycle__cycle_id=cycle_id,
issue_cycle__deleted_at__isnull=True,
workspace__slug=slug,
project_id=project_id,
).count()
@@ -1237,8 +1210,6 @@ class CycleAnalyticsEndpoint(BaseAPIView):
filter=Q(
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__issue__deleted_at__isnull=True,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -1266,7 +1237,6 @@ class CycleAnalyticsEndpoint(BaseAPIView):
assignee_distribution = (
Issue.issue_objects.filter(
issue_cycle__cycle_id=cycle_id,
issue_cycle__deleted_at__isnull=True,
workspace__slug=slug,
project_id=project_id,
)
@@ -1324,7 +1294,6 @@ class CycleAnalyticsEndpoint(BaseAPIView):
label_distribution = (
Issue.issue_objects.filter(
issue_cycle__cycle_id=cycle_id,
issue_cycle__deleted_at__isnull=True,
workspace__slug=slug,
project_id=project_id,
)
@@ -1371,7 +1340,6 @@ class CycleAnalyticsEndpoint(BaseAPIView):
assignee_distribution = (
Issue.issue_objects.filter(
issue_cycle__cycle_id=cycle_id,
issue_cycle__deleted_at__isnull=True,
project_id=project_id,
workspace__slug=slug,
)
@@ -1430,7 +1398,6 @@ class CycleAnalyticsEndpoint(BaseAPIView):
label_distribution = (
Issue.issue_objects.filter(
issue_cycle__cycle_id=cycle_id,
issue_cycle__deleted_at__isnull=True,
project_id=project_id,
workspace__slug=slug,
)
+3 -12
View File
@@ -3,7 +3,7 @@ import json
# Django imports
from django.core import serializers
from django.db.models import F, Func, OuterRef, Q, Subquery
from django.db.models import F, Func, OuterRef, Q
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.views.decorators.gzip import gzip_page
@@ -90,10 +90,7 @@ class CycleIssueViewSet(BaseViewSet):
order_by_param = request.GET.get("order_by", "created_at")
filters = issue_filters(request.query_params, "GET")
issue_queryset = (
Issue.issue_objects.filter(
issue_cycle__cycle_id=cycle_id,
issue_cycle__deleted_at__isnull=True,
)
Issue.issue_objects.filter(issue_cycle__cycle_id=cycle_id)
.filter(project_id=project_id)
.filter(workspace__slug=slug)
.filter(**filters)
@@ -105,13 +102,7 @@ class CycleIssueViewSet(BaseViewSet):
"issue_cycle__cycle",
)
.filter(**filters)
.annotate(
cycle_id=Subquery(
CycleIssue.objects.filter(
issue=OuterRef("id"), deleted_at__isnull=True
).values("cycle_id")[:1]
)
)
.annotate(cycle_id=F("issue_cycle__cycle_id"))
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
+10 -43
View File
@@ -42,7 +42,6 @@ from plane.db.models import (
Project,
Widget,
WorkspaceMember,
CycleIssue,
)
from plane.utils.issue_filters import issue_filters
@@ -192,13 +191,7 @@ def dashboard_assigned_issues(self, request, slug):
).select_related("issue"),
)
)
.annotate(
cycle_id=Subquery(
CycleIssue.objects.filter(
issue=OuterRef("id"), deleted_at__isnull=True
).values("cycle_id")[:1]
)
)
.annotate(cycle_id=F("issue_cycle__cycle_id"))
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
@@ -225,10 +218,7 @@ def dashboard_assigned_issues(self, request, slug):
ArrayAgg(
"labels__id",
distinct=True,
filter=Q(
~Q(labels__id__isnull=True)
& Q(label_issue__deleted_at__isnull=True),
),
filter=~Q(labels__id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -236,11 +226,8 @@ def dashboard_assigned_issues(self, request, slug):
ArrayAgg(
"assignees__id",
distinct=True,
filter=Q(
~Q(assignees__id__isnull=True)
& Q(assignees__member_project__is_active=True)
& Q(issue_assignee__deleted_at__isnull=True)
),
filter=~Q(assignees__id__isnull=True)
& Q(assignees__member_project__is_active=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -248,11 +235,7 @@ def dashboard_assigned_issues(self, request, slug):
ArrayAgg(
"issue_module__module_id",
distinct=True,
filter=Q(
~Q(issue_module__module_id__isnull=True)
& Q(issue_module__module__archived_at__isnull=True)
& Q(issue_module__deleted_at__isnull=True)
),
filter=~Q(issue_module__module_id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -372,13 +355,7 @@ def dashboard_created_issues(self, request, slug):
.filter(**filters)
.select_related("workspace", "project", "state", "parent")
.prefetch_related("assignees", "labels", "issue_module__module")
.annotate(
cycle_id=Subquery(
CycleIssue.objects.filter(
issue=OuterRef("id"), deleted_at__isnull=True
).values("cycle_id")[:1]
)
)
.annotate(cycle_id=F("issue_cycle__cycle_id"))
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
@@ -405,10 +382,7 @@ def dashboard_created_issues(self, request, slug):
ArrayAgg(
"labels__id",
distinct=True,
filter=Q(
~Q(labels__id__isnull=True)
& Q(label_issue__deleted_at__isnull=True),
),
filter=~Q(labels__id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -416,11 +390,8 @@ def dashboard_created_issues(self, request, slug):
ArrayAgg(
"assignees__id",
distinct=True,
filter=Q(
~Q(assignees__id__isnull=True)
& Q(assignees__member_project__is_active=True)
& Q(issue_assignee__deleted_at__isnull=True)
),
filter=~Q(assignees__id__isnull=True)
& Q(assignees__member_project__is_active=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -428,11 +399,7 @@ def dashboard_created_issues(self, request, slug):
ArrayAgg(
"issue_module__module_id",
distinct=True,
filter=Q(
~Q(issue_module__module_id__isnull=True)
& Q(issue_module__module__archived_at__isnull=True)
& Q(issue_module__deleted_at__isnull=True)
),
filter=~Q(issue_module__module_id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
+16 -65
View File
@@ -3,7 +3,7 @@ import json
# Django import
from django.utils import timezone
from django.db.models import Q, Count, OuterRef, Func, F, Prefetch, Subquery
from django.db.models import Q, Count, OuterRef, Func, F, Prefetch
from django.core.serializers.json import DjangoJSONEncoder
from django.contrib.postgres.aggregates import ArrayAgg
from django.contrib.postgres.fields import ArrayField
@@ -26,7 +26,6 @@ from plane.db.models import (
FileAsset,
Project,
ProjectMember,
CycleIssue,
)
from plane.app.serializers import (
IssueCreateSerializer,
@@ -113,13 +112,7 @@ class InboxIssueViewSet(BaseViewSet):
),
)
)
.annotate(
cycle_id=Subquery(
CycleIssue.objects.filter(
issue=OuterRef("id"), deleted_at__isnull=True
).values("cycle_id")[:1]
)
)
.annotate(cycle_id=F("issue_cycle__cycle_id"))
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
@@ -148,10 +141,7 @@ class InboxIssueViewSet(BaseViewSet):
ArrayAgg(
"labels__id",
distinct=True,
filter=Q(
~Q(labels__id__isnull=True)
& Q(label_issue__deleted_at__isnull=True),
),
filter=~Q(labels__id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -159,11 +149,8 @@ class InboxIssueViewSet(BaseViewSet):
ArrayAgg(
"assignees__id",
distinct=True,
filter=Q(
~Q(assignees__id__isnull=True)
& Q(assignees__member_project__is_active=True)
& Q(issue_assignee__deleted_at__isnull=True)
),
filter=~Q(assignees__id__isnull=True)
& Q(assignees__member_project__is_active=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -171,11 +158,8 @@ class InboxIssueViewSet(BaseViewSet):
ArrayAgg(
"issue_module__module_id",
distinct=True,
filter=Q(
~Q(issue_module__module_id__isnull=True)
& Q(issue_module__module__archived_at__isnull=True)
& Q(issue_module__deleted_at__isnull=True)
),
filter=~Q(issue_module__module_id__isnull=True)
& Q(issue_module__module__archived_at__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -202,10 +186,7 @@ class InboxIssueViewSet(BaseViewSet):
ArrayAgg(
"issue__labels__id",
distinct=True,
filter=Q(
~Q(issue__labels__id__isnull=True)
& Q(issue__label_issue__deleted_at__isnull=True)
),
filter=~Q(issue__labels__id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
)
@@ -317,12 +298,7 @@ class InboxIssueViewSet(BaseViewSet):
ArrayAgg(
"issue__labels__id",
distinct=True,
filter=Q(
~Q(issue__labels__id__isnull=True)
& Q(
issue__label_issue__deleted_at__isnull=True
)
),
filter=~Q(issue__labels__id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -330,10 +306,7 @@ class InboxIssueViewSet(BaseViewSet):
ArrayAgg(
"issue__assignees__id",
distinct=True,
filter=~Q(issue__assignees__id__isnull=True)
& Q(
issue__assignees__member_project__is_active=True
),
filter=~Q(issue__assignees__id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -386,10 +359,7 @@ class InboxIssueViewSet(BaseViewSet):
ArrayAgg(
"labels__id",
distinct=True,
filter=Q(
~Q(labels__id__isnull=True)
& Q(label_issue__deleted_at__isnull=True)
),
filter=~Q(labels__id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -397,10 +367,7 @@ class InboxIssueViewSet(BaseViewSet):
ArrayAgg(
"assignees__id",
distinct=True,
filter=Q(
~Q(assignees__id__isnull=True)
& Q(issue_assignee__deleted_at__isnull=True)
),
filter=~Q(assignees__id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -523,12 +490,7 @@ class InboxIssueViewSet(BaseViewSet):
ArrayAgg(
"issue__labels__id",
distinct=True,
filter=Q(
~Q(issue__labels__id__isnull=True)
& Q(
issue__label_issue__deleted_at__isnull=True
)
),
filter=~Q(issue__labels__id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -536,12 +498,7 @@ class InboxIssueViewSet(BaseViewSet):
ArrayAgg(
"issue__assignees__id",
distinct=True,
filter=Q(
~Q(issue__assignees__id__isnull=True)
& Q(
issue__issue_assignee__deleted_at__isnull=True
)
),
filter=~Q(issue__assignees__id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -586,10 +543,7 @@ class InboxIssueViewSet(BaseViewSet):
ArrayAgg(
"issue__labels__id",
distinct=True,
filter=Q(
~Q(issue__labels__id__isnull=True)
& Q(issue__label_issue__deleted_at__isnull=True)
),
filter=~Q(issue__labels__id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -597,10 +551,7 @@ class InboxIssueViewSet(BaseViewSet):
ArrayAgg(
"issue__assignees__id",
distinct=True,
filter=Q(
~Q(issue__assignees__id__isnull=True)
& Q(issue__issue_assignee__deleted_at__isnull=True)
),
filter=~Q(issue__assignees__id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
+9 -9
View File
@@ -3,7 +3,14 @@ import json
# Django imports
from django.core.serializers.json import DjangoJSONEncoder
from django.db.models import F, Func, OuterRef, Q, Prefetch, Exists, Subquery
from django.db.models import (
F,
Func,
OuterRef,
Q,
Prefetch,
Exists,
)
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.views.decorators.gzip import gzip_page
@@ -27,7 +34,6 @@ from plane.db.models import (
IssueLink,
IssueSubscriber,
IssueReaction,
CycleIssue
)
from plane.utils.grouper import (
issue_group_values,
@@ -65,13 +71,7 @@ class IssueArchiveViewSet(BaseViewSet):
.filter(workspace__slug=self.kwargs.get("slug"))
.select_related("workspace", "project", "state", "parent")
.prefetch_related("assignees", "labels", "issue_module__module")
.annotate(
cycle_id=Subquery(
CycleIssue.objects.filter(
issue=OuterRef("id"), deleted_at__isnull=True
).values("cycle_id")[:1]
)
)
.annotate(cycle_id=F("issue_cycle__cycle_id"))
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
@@ -20,7 +20,6 @@ from plane.db.models import FileAsset, Workspace
from plane.bgtasks.issue_activities_task import issue_activity
from plane.app.permissions import allow_permission, ROLE
from plane.settings.storage import S3Storage
from plane.bgtasks.storage_metadata_task import get_asset_object_metadata
class IssueAttachmentEndpoint(BaseAPIView):
@@ -253,10 +252,12 @@ class IssueAttachmentV2Endpoint(BaseAPIView):
# Update the attachment
issue_attachment.is_uploaded = True
issue_attachment.created_by = request.user
# Get the storage metadata
if not issue_attachment.storage_metadata:
get_asset_object_metadata.delay(str(issue_attachment.id))
if issue_attachment.storage_metadata is None:
storage = S3Storage(request=request)
issue_attachment.storage_metadata = storage.get_object_metadata(
issue_attachment.asset.name
)
issue_attachment.save()
return Response(status=status.HTTP_204_NO_CONTENT)
+20 -307
View File
@@ -14,9 +14,6 @@ from django.db.models import (
Q,
UUIDField,
Value,
Subquery,
Case,
When,
)
from django.db.models.functions import Coalesce
from django.utils import timezone
@@ -45,7 +42,6 @@ from plane.db.models import (
IssueSubscriber,
Project,
ProjectMember,
CycleIssue,
)
from plane.utils.grouper import (
issue_group_values,
@@ -87,13 +83,7 @@ class IssueListEndpoint(BaseAPIView):
.filter(workspace__slug=self.kwargs.get("slug"))
.select_related("workspace", "project", "state", "parent")
.prefetch_related("assignees", "labels", "issue_module__module")
.annotate(
cycle_id=Subquery(
CycleIssue.objects.filter(
issue=OuterRef("id"), deleted_at__isnull=True
).values("cycle_id")[:1]
)
)
.annotate(cycle_id=F("issue_cycle__cycle_id"))
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
@@ -217,13 +207,7 @@ class IssueViewSet(BaseViewSet):
.filter(workspace__slug=self.kwargs.get("slug"))
.select_related("workspace", "project", "state", "parent")
.prefetch_related("assignees", "labels", "issue_module__module")
.annotate(
cycle_id=Subquery(
CycleIssue.objects.filter(
issue=OuterRef("id"), deleted_at__isnull=True
).values("cycle_id")[:1]
)
)
.annotate(cycle_id=F("issue_cycle__cycle_id"))
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
@@ -480,54 +464,14 @@ class IssueViewSet(BaseViewSet):
project = Project.objects.get(pk=project_id, workspace__slug=slug)
issue = (
Issue.objects.filter(
project_id=self.kwargs.get("project_id")
)
.filter(workspace__slug=self.kwargs.get("slug"))
.select_related("workspace", "project", "state", "parent")
.prefetch_related("assignees", "labels", "issue_module__module")
.annotate(
cycle_id=Case(
When(
issue_cycle__cycle__deleted_at__isnull=True,
then=F("issue_cycle__cycle_id"),
),
default=None,
)
)
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(
attachment_count=FileAsset.objects.filter(
issue_id=OuterRef("id"),
entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT,
)
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(
sub_issues_count=Issue.issue_objects.filter(
parent=OuterRef("id")
)
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
self.get_queryset()
.filter(pk=pk)
.annotate(
label_ids=Coalesce(
ArrayAgg(
"labels__id",
distinct=True,
filter=Q(
~Q(labels__id__isnull=True)
& Q(label_issue__deleted_at__isnull=True),
),
filter=~Q(labels__id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -535,11 +479,8 @@ class IssueViewSet(BaseViewSet):
ArrayAgg(
"assignees__id",
distinct=True,
filter=Q(
~Q(assignees__id__isnull=True)
& Q(assignees__member_project__is_active=True)
& Q(issue_assignee__deleted_at__isnull=True)
),
filter=~Q(assignees__id__isnull=True)
& Q(assignees__member_project__is_active=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -547,11 +488,8 @@ class IssueViewSet(BaseViewSet):
ArrayAgg(
"issue_module__module_id",
distinct=True,
filter=Q(
~Q(issue_module__module_id__isnull=True)
& Q(issue_module__module__archived_at__isnull=True)
& Q(issue_module__deleted_at__isnull=True)
),
filter=~Q(issue_module__module_id__isnull=True)
& Q(issue_module__module__archived_at__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -630,10 +568,7 @@ class IssueViewSet(BaseViewSet):
ArrayAgg(
"labels__id",
distinct=True,
filter=Q(
~Q(labels__id__isnull=True)
& Q(label_issue__deleted_at__isnull=True),
),
filter=~Q(labels__id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -641,11 +576,8 @@ class IssueViewSet(BaseViewSet):
ArrayAgg(
"assignees__id",
distinct=True,
filter=Q(
~Q(assignees__id__isnull=True)
& Q(assignees__member_project__is_active=True)
& Q(issue_assignee__deleted_at__isnull=True)
),
filter=~Q(assignees__id__isnull=True)
& Q(assignees__member_project__is_active=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -653,11 +585,7 @@ class IssueViewSet(BaseViewSet):
ArrayAgg(
"issue_module__module_id",
distinct=True,
filter=Q(
~Q(issue_module__module_id__isnull=True)
& Q(issue_module__module__archived_at__isnull=True)
& Q(issue_module__deleted_at__isnull=True)
),
filter=~Q(issue_module__module_id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -820,28 +748,13 @@ class IssuePaginatedViewSet(BaseViewSet):
"workspace", "project", "state", "parent"
)
.prefetch_related("assignees", "labels", "issue_module__module")
.annotate(
cycle_id=Subquery(
CycleIssue.objects.filter(
issue=OuterRef("id"), deleted_at__isnull=True
).values("cycle_id")[:1]
)
)
.annotate(cycle_id=F("issue_cycle__cycle_id"))
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(
attachment_count=FileAsset.objects.filter(
issue_id=OuterRef("id"),
entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT,
)
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(
sub_issues_count=Issue.issue_objects.filter(
parent=OuterRef("id")
@@ -866,7 +779,7 @@ class IssuePaginatedViewSet(BaseViewSet):
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
def list(self, request, slug, project_id):
cursor = request.GET.get("cursor", None)
is_description_required = request.GET.get("description", "false")
is_description_required = request.GET.get("description", False)
updated_at = request.GET.get("updated_at__gt", None)
# required fields
@@ -899,7 +812,7 @@ class IssuePaginatedViewSet(BaseViewSet):
"sub_issues_count",
]
if str(is_description_required).lower() == "true":
if is_description_required:
required_fields.append("description_html")
# querying issues
@@ -933,10 +846,7 @@ class IssuePaginatedViewSet(BaseViewSet):
ArrayAgg(
"labels__id",
distinct=True,
filter=Q(
~Q(labels__id__isnull=True)
& Q(label_issue__deleted_at__isnull=True),
),
filter=~Q(labels__id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -944,11 +854,8 @@ class IssuePaginatedViewSet(BaseViewSet):
ArrayAgg(
"assignees__id",
distinct=True,
filter=Q(
~Q(assignees__id__isnull=True)
& Q(assignees__member_project__is_active=True)
& Q(issue_assignee__deleted_at__isnull=True)
),
filter=~Q(assignees__id__isnull=True)
& Q(assignees__member_project__is_active=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -956,11 +863,8 @@ class IssuePaginatedViewSet(BaseViewSet):
ArrayAgg(
"issue_module__module_id",
distinct=True,
filter=Q(
~Q(issue_module__module_id__isnull=True)
& Q(issue_module__module__archived_at__isnull=True)
& Q(issue_module__deleted_at__isnull=True)
),
filter=~Q(issue_module__module_id__isnull=True)
& Q(issue_module__module__archived_at__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -976,194 +880,3 @@ class IssuePaginatedViewSet(BaseViewSet):
)
return Response(paginated_data, status=status.HTTP_200_OK)
class IssueDetailEndpoint(BaseAPIView):
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
def get(self, request, slug, project_id):
filters = issue_filters(request.query_params, "GET")
issue = (
Issue.issue_objects.filter(
workspace__slug=slug, project_id=project_id
)
.select_related("workspace", "project", "state", "parent")
.prefetch_related("assignees", "labels", "issue_module__module")
.annotate(
cycle_id=Subquery(
CycleIssue.objects.filter(
issue=OuterRef("id"), deleted_at__isnull=True
).values("cycle_id")[:1]
)
)
.annotate(
label_ids=Coalesce(
ArrayAgg(
"labels__id",
distinct=True,
filter=Q(
~Q(labels__id__isnull=True)
& Q(label_issue__deleted_at__isnull=True),
),
),
Value([], output_field=ArrayField(UUIDField())),
),
assignee_ids=Coalesce(
ArrayAgg(
"assignees__id",
distinct=True,
filter=Q(
~Q(assignees__id__isnull=True)
& Q(assignees__member_project__is_active=True)
& Q(issue_assignee__deleted_at__isnull=True)
),
),
Value([], output_field=ArrayField(UUIDField())),
),
module_ids=Coalesce(
ArrayAgg(
"issue_module__module_id",
distinct=True,
filter=Q(
~Q(issue_module__module_id__isnull=True)
& Q(issue_module__module__archived_at__isnull=True)
& Q(issue_module__deleted_at__isnull=True)
),
),
Value([], output_field=ArrayField(UUIDField())),
),
)
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(
attachment_count=FileAsset.objects.filter(
issue_id=OuterRef("id"),
entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT,
)
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(
sub_issues_count=Issue.issue_objects.filter(
parent=OuterRef("id")
)
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
)
issue = issue.filter(**filters)
order_by_param = request.GET.get("order_by", "-created_at")
# Issue queryset
issue, order_by_param = order_issue_queryset(
issue_queryset=issue,
order_by_param=order_by_param,
)
return self.paginate(
request=request,
order_by=order_by_param,
queryset=(issue),
on_results=lambda issue: IssueSerializer(
issue,
many=True,
fields=self.fields,
expand=self.expand,
).data,
)
class IssueBulkUpdateDateEndpoint(BaseAPIView):
def validate_dates(
self, current_start, current_target, new_start, new_target
):
"""
Validate that start date is before target date.
"""
start = new_start or current_start
target = new_target or current_target
if start and target and start > target:
return False
return True
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
def post(self, request, slug, project_id):
updates = request.data.get("updates", [])
issue_ids = [update["id"] for update in updates]
epoch = int(timezone.now().timestamp())
# Fetch all relevant issues in a single query
issues = list(Issue.objects.filter(id__in=issue_ids))
issues_dict = {str(issue.id): issue for issue in issues}
issues_to_update = []
for update in updates:
issue_id = update["id"]
issue = issues_dict.get(issue_id)
if not issue:
continue
start_date = update.get("start_date")
target_date = update.get("target_date")
validate_dates = self.validate_dates(
issue.start_date, issue.target_date, start_date, target_date
)
if not validate_dates:
return Response(
{
"message": "Start date cannot exceed target date",
},
status=status.HTTP_400_BAD_REQUEST,
)
if start_date:
issue_activity.delay(
type="issue.activity.updated",
requested_data=json.dumps(
{"start_date": update.get("start_date")}
),
current_instance=json.dumps(
{"start_date": str(issue.start_date)}
),
issue_id=str(issue_id),
actor_id=str(request.user.id),
project_id=str(project_id),
epoch=epoch,
)
issue.start_date = start_date
issues_to_update.append(issue)
if target_date:
issue_activity.delay(
type="issue.activity.updated",
requested_data=json.dumps(
{"target_date": update.get("target_date")}
),
current_instance=json.dumps(
{"target_date": str(issue.target_date)}
),
issue_id=str(issue_id),
actor_id=str(request.user.id),
project_id=str(project_id),
epoch=epoch,
)
issue.target_date = target_date
issues_to_update.append(issue)
# Bulk update issues
Issue.objects.bulk_update(
issues_to_update, ["start_date", "target_date"]
)
return Response(
{"message": "Issues updated successfully"},
status=status.HTTP_200_OK,
)
+15 -98
View File
@@ -3,16 +3,7 @@ import json
# Django imports
from django.utils import timezone
from django.db.models import (
Q,
OuterRef,
F,
Func,
UUIDField,
Value,
CharField,
Subquery,
)
from django.db.models import Q, OuterRef, F, Func, UUIDField, Value, CharField
from django.core.serializers.json import DjangoJSONEncoder
from django.db.models.functions import Coalesce
from django.contrib.postgres.aggregates import ArrayAgg
@@ -35,10 +26,8 @@ from plane.db.models import (
Issue,
FileAsset,
IssueLink,
CycleIssue,
)
from plane.bgtasks.issue_activities_task import issue_activity
from plane.utils.issue_relation_mapper import get_actual_relation
class IssueRelationViewSet(BaseViewSet):
@@ -90,37 +79,11 @@ class IssueRelationViewSet(BaseViewSet):
related_issue_id=issue_id, relation_type="relates_to"
).values_list("issue_id", flat=True)
# get all start after issues
start_after_issues = issue_relations.filter(
relation_type="start_before", related_issue_id=issue_id
).values_list("issue_id", flat=True)
# get all start_before issues
start_before_issues = issue_relations.filter(
relation_type="start_before", issue_id=issue_id
).values_list("related_issue_id", flat=True)
# get all finish after issues
finish_after_issues = issue_relations.filter(
relation_type="finish_before", related_issue_id=issue_id
).values_list("issue_id", flat=True)
# get all finish before issues
finish_before_issues = issue_relations.filter(
relation_type="finish_before", issue_id=issue_id
).values_list("related_issue_id", flat=True)
queryset = (
Issue.issue_objects.filter(workspace__slug=slug)
.select_related("workspace", "project", "state", "parent")
.prefetch_related("assignees", "labels", "issue_module__module")
.annotate(
cycle_id=Subquery(
CycleIssue.objects.filter(
issue=OuterRef("id"), deleted_at__isnull=True
).values("cycle_id")[:1]
)
)
.annotate(cycle_id=F("issue_cycle__cycle_id"))
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
@@ -149,10 +112,7 @@ class IssueRelationViewSet(BaseViewSet):
ArrayAgg(
"labels__id",
distinct=True,
filter=Q(
~Q(labels__id__isnull=True)
& (Q(label_issue__deleted_at__isnull=True))
),
filter=~Q(labels__id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -160,11 +120,8 @@ class IssueRelationViewSet(BaseViewSet):
ArrayAgg(
"assignees__id",
distinct=True,
filter=Q(
~Q(assignees__id__isnull=True)
& Q(assignees__member_project__is_active=True)
& Q(issue_assignee__deleted_at__isnull=True)
),
filter=~Q(assignees__id__isnull=True)
& Q(assignees__member_project__is_active=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -232,50 +189,12 @@ class IssueRelationViewSet(BaseViewSet):
)
)
.values(*fields),
"start_after": queryset.filter(pk__in=start_after_issues)
.annotate(
relation_type=Value(
"start_after",
output_field=CharField(),
)
)
.values(*fields),
"start_before": queryset.filter(pk__in=start_before_issues)
.annotate(
relation_type=Value(
"start_before",
output_field=CharField(),
)
)
.values(*fields),
"finish_after": queryset.filter(pk__in=finish_after_issues)
.annotate(
relation_type=Value(
"finish_after",
output_field=CharField(),
)
)
.values(*fields),
"finish_before": queryset.filter(pk__in=finish_before_issues)
.annotate(
relation_type=Value(
"finish_before",
output_field=CharField(),
)
)
.values(*fields),
}
return Response(response_data, status=status.HTTP_200_OK)
def create(self, request, slug, project_id, issue_id):
relation_type = request.data.get("relation_type", None)
if relation_type is None:
return Response(
{"message": "Issue relation type is required"},
status=status.HTTP_400_BAD_REQUEST,
)
issues = request.data.get("issues", [])
project = Project.objects.get(pk=project_id)
@@ -283,18 +202,16 @@ class IssueRelationViewSet(BaseViewSet):
[
IssueRelation(
issue_id=(
issue
if relation_type
in ["blocking", "start_after", "finish_after"]
else issue_id
issue if relation_type == "blocking" else issue_id
),
related_issue_id=(
issue_id
if relation_type
in ["blocking", "start_after", "finish_after"]
else issue
issue_id if relation_type == "blocking" else issue
),
relation_type=(
"blocked_by"
if relation_type == "blocking"
else relation_type
),
relation_type=(get_actual_relation(relation_type)),
project_id=project_id,
workspace_id=project.workspace_id,
created_by=request.user,
@@ -318,7 +235,7 @@ class IssueRelationViewSet(BaseViewSet):
origin=request.META.get("HTTP_ORIGIN"),
)
if relation_type in ["blocking", "start_after", "finish_after"]:
if relation_type == "blocking":
return Response(
RelatedIssueSerializer(issue_relation, many=True).data,
status=status.HTTP_201_CREATED,
@@ -333,7 +250,7 @@ class IssueRelationViewSet(BaseViewSet):
relation_type = request.data.get("relation_type", None)
related_issue = request.data.get("related_issue", None)
if relation_type in ["blocking", "start_after", "finish_after"]:
if relation_type == "blocking":
issue_relation = IssueRelation.objects.get(
workspace__slug=slug,
project_id=project_id,
@@ -351,7 +268,7 @@ class IssueRelationViewSet(BaseViewSet):
IssueRelationSerializer(issue_relation).data,
cls=DjangoJSONEncoder,
)
issue_relation.delete()
issue_relation.delete(soft=False)
issue_activity.delay(
type="issue_relation.activity.deleted",
requested_data=json.dumps(request.data, cls=DjangoJSONEncoder),
+13 -23
View File
@@ -3,7 +3,14 @@ import json
# Django imports
from django.utils import timezone
from django.db.models import OuterRef, Func, F, Q, Value, UUIDField, Subquery
from django.db.models import (
OuterRef,
Func,
F,
Q,
Value,
UUIDField,
)
from django.utils.decorators import method_decorator
from django.views.decorators.gzip import gzip_page
from django.contrib.postgres.aggregates import ArrayAgg
@@ -22,7 +29,6 @@ from plane.db.models import (
Issue,
IssueLink,
FileAsset,
CycleIssue,
)
from plane.bgtasks.issue_activities_task import issue_activity
from plane.utils.user_timezone_converter import user_timezone_converter
@@ -42,13 +48,7 @@ class SubIssuesEndpoint(BaseAPIView):
)
.select_related("workspace", "project", "state", "parent")
.prefetch_related("assignees", "labels", "issue_module__module")
.annotate(
cycle_id=Subquery(
CycleIssue.objects.filter(
issue=OuterRef("id"), deleted_at__isnull=True
).values("cycle_id")[:1]
)
)
.annotate(cycle_id=F("issue_cycle__cycle_id"))
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
@@ -77,10 +77,7 @@ class SubIssuesEndpoint(BaseAPIView):
ArrayAgg(
"labels__id",
distinct=True,
filter=Q(
~Q(labels__id__isnull=True)
& Q(label_issue__deleted_at__isnull=True),
),
filter=~Q(labels__id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -88,11 +85,8 @@ class SubIssuesEndpoint(BaseAPIView):
ArrayAgg(
"assignees__id",
distinct=True,
filter=Q(
~Q(assignees__id__isnull=True)
& Q(assignees__member_project__is_active=True)
& Q(issue_assignee__deleted_at__isnull=True)
),
filter=~Q(assignees__id__isnull=True)
& Q(assignees__member_project__is_active=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -100,11 +94,7 @@ class SubIssuesEndpoint(BaseAPIView):
ArrayAgg(
"issue_module__module_id",
distinct=True,
filter=Q(
~Q(issue_module__module_id__isnull=True)
& Q(issue_module__module__archived_at__isnull=True)
& Q(issue_module__deleted_at__isnull=True)
),
filter=~Q(issue_module__module_id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -57,7 +57,6 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
Issue.issue_objects.filter(
state__group="cancelled",
issue_module__module_id=OuterRef("pk"),
issue_module__deleted_at__isnull=True,
)
.values("issue_module__module_id")
.annotate(cnt=Count("pk"))
@@ -67,7 +66,6 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
Issue.issue_objects.filter(
state__group="completed",
issue_module__module_id=OuterRef("pk"),
issue_module__deleted_at__isnull=True,
)
.values("issue_module__module_id")
.annotate(cnt=Count("pk"))
@@ -77,7 +75,6 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
Issue.issue_objects.filter(
state__group="started",
issue_module__module_id=OuterRef("pk"),
issue_module__deleted_at__isnull=True,
)
.values("issue_module__module_id")
.annotate(cnt=Count("pk"))
@@ -87,7 +84,6 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
Issue.issue_objects.filter(
state__group="unstarted",
issue_module__module_id=OuterRef("pk"),
issue_module__deleted_at__isnull=True,
)
.values("issue_module__module_id")
.annotate(cnt=Count("pk"))
@@ -97,7 +93,6 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
Issue.issue_objects.filter(
state__group="backlog",
issue_module__module_id=OuterRef("pk"),
issue_module__deleted_at__isnull=True,
)
.values("issue_module__module_id")
.annotate(cnt=Count("pk"))
@@ -106,7 +101,6 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
total_issues = (
Issue.issue_objects.filter(
issue_module__module_id=OuterRef("pk"),
issue_module__deleted_at__isnull=True,
)
.values("issue_module__module_id")
.annotate(cnt=Count("pk"))
@@ -117,7 +111,6 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
estimate_point__estimate__type="points",
state__group="completed",
issue_module__module_id=OuterRef("pk"),
issue_module__deleted_at__isnull=True,
)
.values("issue_module__module_id")
.annotate(
@@ -132,7 +125,6 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
Issue.issue_objects.filter(
estimate_point__estimate__type="points",
issue_module__module_id=OuterRef("pk"),
issue_module__deleted_at__isnull=True,
)
.values("issue_module__module_id")
.annotate(
@@ -147,7 +139,6 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
estimate_point__estimate__type="points",
state__group="backlog",
issue_module__module_id=OuterRef("pk"),
issue_module__deleted_at__isnull=True,
)
.values("issue_module__module_id")
.annotate(
@@ -162,7 +153,6 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
estimate_point__estimate__type="points",
state__group="unstarted",
issue_module__module_id=OuterRef("pk"),
issue_module__deleted_at__isnull=True,
)
.values("issue_module__module_id")
.annotate(
@@ -177,7 +167,6 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
estimate_point__estimate__type="points",
state__group="started",
issue_module__module_id=OuterRef("pk"),
issue_module__deleted_at__isnull=True,
)
.values("issue_module__module_id")
.annotate(
@@ -192,7 +181,6 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
estimate_point__estimate__type="points",
state__group="cancelled",
issue_module__module_id=OuterRef("pk"),
issue_module__deleted_at__isnull=True,
)
.values("issue_module__module_id")
.annotate(
@@ -349,7 +337,6 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
project_id=self.kwargs.get("project_id"),
parent__isnull=False,
issue_module__module_id=pk,
issue_module__deleted_at__isnull=True,
)
.order_by()
.annotate(count=Func(F("id"), function="Count"))
@@ -373,7 +360,6 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
assignee_distribution = (
Issue.issue_objects.filter(
issue_module__module_id=pk,
issue_module__deleted_at__isnull=True,
workspace__slug=slug,
project_id=project_id,
)
@@ -492,7 +478,6 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
assignee_distribution = (
Issue.issue_objects.filter(
issue_module__module_id=pk,
issue_module__deleted_at__isnull=True,
workspace__slug=slug,
project_id=project_id,
)
@@ -562,7 +547,6 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
label_distribution = (
Issue.issue_objects.filter(
issue_module__module_id=pk,
issue_module__deleted_at__isnull=True,
workspace__slug=slug,
project_id=project_id,
)
-17
View File
@@ -85,7 +85,6 @@ class ModuleViewSet(BaseViewSet):
Issue.issue_objects.filter(
state__group="cancelled",
issue_module__module_id=OuterRef("pk"),
issue_module__deleted_at__isnull=True,
)
.values("issue_module__module_id")
.annotate(cnt=Count("pk"))
@@ -95,7 +94,6 @@ class ModuleViewSet(BaseViewSet):
Issue.issue_objects.filter(
state__group="completed",
issue_module__module_id=OuterRef("pk"),
issue_module__deleted_at__isnull=True,
)
.values("issue_module__module_id")
.annotate(cnt=Count("pk"))
@@ -105,7 +103,6 @@ class ModuleViewSet(BaseViewSet):
Issue.issue_objects.filter(
state__group="started",
issue_module__module_id=OuterRef("pk"),
issue_module__deleted_at__isnull=True,
)
.values("issue_module__module_id")
.annotate(cnt=Count("pk"))
@@ -115,7 +112,6 @@ class ModuleViewSet(BaseViewSet):
Issue.issue_objects.filter(
state__group="unstarted",
issue_module__module_id=OuterRef("pk"),
issue_module__deleted_at__isnull=True,
)
.values("issue_module__module_id")
.annotate(cnt=Count("pk"))
@@ -125,7 +121,6 @@ class ModuleViewSet(BaseViewSet):
Issue.issue_objects.filter(
state__group="backlog",
issue_module__module_id=OuterRef("pk"),
issue_module__deleted_at__isnull=True,
)
.values("issue_module__module_id")
.annotate(cnt=Count("pk"))
@@ -134,7 +129,6 @@ class ModuleViewSet(BaseViewSet):
total_issues = (
Issue.issue_objects.filter(
issue_module__module_id=OuterRef("pk"),
issue_module__deleted_at__isnull=True,
)
.values("issue_module__module_id")
.annotate(cnt=Count("pk"))
@@ -145,7 +139,6 @@ class ModuleViewSet(BaseViewSet):
estimate_point__estimate__type="points",
state__group="completed",
issue_module__module_id=OuterRef("pk"),
issue_module__deleted_at__isnull=True,
)
.values("issue_module__module_id")
.annotate(
@@ -160,7 +153,6 @@ class ModuleViewSet(BaseViewSet):
Issue.issue_objects.filter(
estimate_point__estimate__type="points",
issue_module__module_id=OuterRef("pk"),
issue_module__deleted_at__isnull=True,
)
.values("issue_module__module_id")
.annotate(
@@ -175,7 +167,6 @@ class ModuleViewSet(BaseViewSet):
estimate_point__estimate__type="points",
state__group="backlog",
issue_module__module_id=OuterRef("pk"),
issue_module__deleted_at__isnull=True,
)
.values("issue_module__module_id")
.annotate(
@@ -190,7 +181,6 @@ class ModuleViewSet(BaseViewSet):
estimate_point__estimate__type="points",
state__group="unstarted",
issue_module__module_id=OuterRef("pk"),
issue_module__deleted_at__isnull=True,
)
.values("issue_module__module_id")
.annotate(
@@ -205,7 +195,6 @@ class ModuleViewSet(BaseViewSet):
estimate_point__estimate__type="points",
state__group="started",
issue_module__module_id=OuterRef("pk"),
issue_module__deleted_at__isnull=True,
)
.values("issue_module__module_id")
.annotate(
@@ -220,7 +209,6 @@ class ModuleViewSet(BaseViewSet):
estimate_point__estimate__type="points",
state__group="cancelled",
issue_module__module_id=OuterRef("pk"),
issue_module__deleted_at__isnull=True,
)
.values("issue_module__module_id")
.annotate(
@@ -460,7 +448,6 @@ class ModuleViewSet(BaseViewSet):
project_id=self.kwargs.get("project_id"),
parent__isnull=False,
issue_module__module_id=pk,
issue_module__deleted_at__isnull=True,
)
.order_by()
.annotate(count=Func(F("id"), function="Count"))
@@ -490,7 +477,6 @@ class ModuleViewSet(BaseViewSet):
assignee_distribution = (
Issue.issue_objects.filter(
issue_module__module_id=pk,
issue_module__deleted_at__isnull=True,
workspace__slug=slug,
project_id=project_id,
)
@@ -556,7 +542,6 @@ class ModuleViewSet(BaseViewSet):
label_distribution = (
Issue.issue_objects.filter(
issue_module__module_id=pk,
issue_module__deleted_at__isnull=True,
workspace__slug=slug,
project_id=project_id,
)
@@ -608,7 +593,6 @@ class ModuleViewSet(BaseViewSet):
assignee_distribution = (
Issue.issue_objects.filter(
issue_module__module_id=pk,
issue_module__deleted_at__isnull=True,
workspace__slug=slug,
project_id=project_id,
)
@@ -678,7 +662,6 @@ class ModuleViewSet(BaseViewSet):
label_distribution = (
Issue.issue_objects.filter(
issue_module__module_id=pk,
issue_module__deleted_at__isnull=True,
workspace__slug=slug,
project_id=project_id,
)
+7 -10
View File
@@ -1,7 +1,12 @@
# Python imports
import json
from django.db.models import F, Func, OuterRef, Q, Subquery
from django.db.models import (
F,
Func,
OuterRef,
Q,
)
# Django Imports
from django.utils import timezone
@@ -23,7 +28,6 @@ from plane.db.models import (
IssueLink,
ModuleIssue,
Project,
CycleIssue,
)
from plane.utils.grouper import (
issue_group_values,
@@ -58,17 +62,10 @@ class ModuleIssueViewSet(BaseViewSet):
project_id=self.kwargs.get("project_id"),
workspace__slug=self.kwargs.get("slug"),
issue_module__module_id=self.kwargs.get("module_id"),
issue_module__deleted_at__isnull=True,
)
.select_related("workspace", "project", "state", "parent")
.prefetch_related("assignees", "labels", "issue_module__module")
.annotate(
cycle_id=Subquery(
CycleIssue.objects.filter(
issue=OuterRef("id"), deleted_at__isnull=True
).values("cycle_id")[:1]
)
)
.annotate(cycle_id=F("issue_cycle__cycle_id"))
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
+25 -34
View File
@@ -42,56 +42,47 @@ class IssueSearchEndpoint(BaseAPIView):
issues = search_issues(query, issues)
if parent == "true" and issue_id:
issue = Issue.issue_objects.filter(pk=issue_id).first()
if issue:
issues = issues.filter(
~Q(pk=issue_id),
~Q(pk=issue.parent_id),
~Q(parent_id=issue_id),
)
issue = Issue.issue_objects.get(pk=issue_id)
issues = issues.filter(
~Q(pk=issue_id), ~Q(pk=issue.parent_id), ~Q(parent_id=issue_id)
)
if issue_relation == "true" and issue_id:
issue = Issue.issue_objects.filter(pk=issue_id).first()
if issue:
issues = issues.filter(
~Q(pk=issue_id),
~(
Q(issue_related__issue=issue)
& Q(issue_related__deleted_at__isnull=True)
),
~(
Q(issue_relation__related_issue=issue)
& Q(issue_relation__deleted_at__isnull=True)
),
)
issue = Issue.issue_objects.get(pk=issue_id)
issues = issues.filter(
~Q(pk=issue_id),
~Q(
issue_related__issue=issue,
issue_related__deleted_at__isnull=True,
),
~Q(
issue_relation__related_issue=issue,
issue_related__deleted_at__isnull=True,
),
)
if sub_issue == "true" and issue_id:
issue = Issue.issue_objects.filter(pk=issue_id).first()
if issue:
issues = issues.filter(~Q(pk=issue_id), parent__isnull=True)
issue = Issue.issue_objects.get(pk=issue_id)
issues = issues.filter(~Q(pk=issue_id), parent__isnull=True)
if issue.parent:
issues = issues.filter(~Q(pk=issue.parent_id))
if cycle == "true":
issues = issues.exclude(
Q(issue_cycle__isnull=False)
& Q(issue_cycle__deleted_at__isnull=True)
)
issues = issues.exclude(issue_cycle__isnull=False)
if module:
issues = issues.exclude(
Q(issue_module__module=module)
& Q(issue_module__deleted_at__isnull=True)
)
issues = issues.exclude(issue_module__module=module)
if target_date == "none":
issues = issues.filter(target_date__isnull=True)
if ProjectMember.objects.filter(
project_id=project_id,
member=self.request.user,
is_active=True,
role=5,
role=5
).exists():
issues = issues.filter(created_by=self.request.user)
issues = issues.filter(
created_by=self.request.user
)
return Response(
issues.values(
+8 -33
View File
@@ -9,7 +9,6 @@ from django.db.models import (
Q,
UUIDField,
Value,
Subquery,
)
from django.db.models.functions import Coalesce
from django.utils.decorators import method_decorator
@@ -37,7 +36,6 @@ from plane.db.models import (
WorkspaceMember,
ProjectMember,
Project,
CycleIssue,
)
from plane.utils.grouper import (
issue_group_values,
@@ -207,13 +205,7 @@ class WorkspaceViewIssuesViewSet(BaseViewSet):
)
.select_related("workspace", "project", "state", "parent")
.prefetch_related("assignees", "labels", "issue_module__module")
.annotate(
cycle_id=Subquery(
CycleIssue.objects.filter(
issue=OuterRef("id"), deleted_at__isnull=True
).values("cycle_id")[:1]
)
)
.annotate(cycle_id=F("issue_cycle__cycle_id"))
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
@@ -242,10 +234,7 @@ class WorkspaceViewIssuesViewSet(BaseViewSet):
ArrayAgg(
"labels__id",
distinct=True,
filter=Q(
~Q(labels__id__isnull=True)
& Q(label_issue__deleted_at__isnull=True),
),
filter=~Q(labels__id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -253,11 +242,8 @@ class WorkspaceViewIssuesViewSet(BaseViewSet):
ArrayAgg(
"assignees__id",
distinct=True,
filter=Q(
~Q(assignees__id__isnull=True)
& Q(assignees__member_project__is_active=True)
& Q(issue_assignee__deleted_at__isnull=True)
),
filter=~Q(assignees__id__isnull=True)
& Q(assignees__member_project__is_active=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -265,11 +251,8 @@ class WorkspaceViewIssuesViewSet(BaseViewSet):
ArrayAgg(
"issue_module__module_id",
distinct=True,
filter=Q(
~Q(issue_module__module_id__isnull=True)
& Q(issue_module__module__archived_at__isnull=True)
& Q(issue_module__deleted_at__isnull=True)
),
filter=~Q(issue_module__module_id__isnull=True)
& Q(issue_module__module__archived_at__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -288,13 +271,7 @@ class WorkspaceViewIssuesViewSet(BaseViewSet):
issue_queryset = (
self.get_queryset()
.filter(**filters)
.annotate(
cycle_id=Subquery(
CycleIssue.objects.filter(
issue=OuterRef("id"), deleted_at__isnull=True
).values("cycle_id")[:1]
)
)
.annotate(cycle_id=F("issue_cycle__cycle_id"))
)
# check for the project member role, if the role is 5 then check for the guest_view_all_features if it is true then show all the issues else show only the issues created by the user
@@ -553,9 +530,7 @@ class IssueViewViewSet(BaseViewSet):
serializer.errors, status=status.HTTP_400_BAD_REQUEST
)
@allow_permission(
allowed_roles=[ROLE.ADMIN], creator=True, model=IssueView
)
@allow_permission(allowed_roles=[ROLE.ADMIN], creator=True, model=IssueView)
def destroy(self, request, slug, project_id, pk):
project_view = IssueView.objects.get(
pk=pk,
@@ -33,7 +33,6 @@ class WorkspaceCyclesEndpoint(BaseAPIView):
filter=Q(
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -44,8 +43,6 @@ class WorkspaceCyclesEndpoint(BaseAPIView):
issue_cycle__issue__state__group="completed",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__issue__deleted_at__isnull=True,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -56,8 +53,6 @@ class WorkspaceCyclesEndpoint(BaseAPIView):
issue_cycle__issue__state__group="cancelled",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__issue__deleted_at__isnull=True,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -68,8 +63,6 @@ class WorkspaceCyclesEndpoint(BaseAPIView):
issue_cycle__issue__state__group="started",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__issue__deleted_at__isnull=True,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -80,8 +73,6 @@ class WorkspaceCyclesEndpoint(BaseAPIView):
issue_cycle__issue__state__group="unstarted",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__issue__deleted_at__isnull=True,
issue_cycle__deleted_at__isnull=True,
),
)
)
@@ -92,8 +83,6 @@ class WorkspaceCyclesEndpoint(BaseAPIView):
issue_cycle__issue__state__group="backlog",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
issue_cycle__issue__deleted_at__isnull=True,
issue_cycle__deleted_at__isnull=True,
),
)
)
+22 -40
View File
@@ -8,11 +8,10 @@ from django.core.serializers.json import DjangoJSONEncoder
from django.contrib.postgres.aggregates import ArrayAgg
from django.contrib.postgres.fields import ArrayField
from django.db.models import (
F,
Q,
UUIDField,
Value,
Subquery,
OuterRef,
)
from django.db.models.functions import Coalesce
from django.utils.decorators import method_decorator
@@ -35,9 +34,9 @@ from plane.db.models import (
DraftIssue,
CycleIssue,
ModuleIssue,
DraftIssueModule,
DraftIssueCycle,
Workspace,
FileAsset,
)
from .. import BaseViewSet
from plane.bgtasks.issue_activities_task import issue_activity
@@ -54,22 +53,13 @@ class WorkspaceDraftIssueViewSet(BaseViewSet):
.prefetch_related(
"assignees", "labels", "draft_issue_module__module"
)
.annotate(
cycle_id=Subquery(
DraftIssueCycle.objects.filter(
draft_issue=OuterRef("id"), deleted_at__isnull=True
).values("cycle_id")[:1]
)
)
.annotate(cycle_id=F("draft_issue_cycle__cycle_id"))
.annotate(
label_ids=Coalesce(
ArrayAgg(
"labels__id",
distinct=True,
filter=Q(
~Q(labels__id__isnull=True)
& (Q(draft_label_issue__deleted_at__isnull=True))
),
filter=~Q(labels__id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -77,11 +67,8 @@ class WorkspaceDraftIssueViewSet(BaseViewSet):
ArrayAgg(
"assignees__id",
distinct=True,
filter=Q(
~Q(assignees__id__isnull=True)
& Q(assignees__member_project__is_active=True)
& Q(draft_issue_assignee__deleted_at__isnull=True)
),
filter=~Q(assignees__id__isnull=True)
& Q(assignees__member_project__is_active=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -89,12 +76,9 @@ class WorkspaceDraftIssueViewSet(BaseViewSet):
ArrayAgg(
"draft_issue_module__module_id",
distinct=True,
filter=Q(
~Q(draft_issue_module__module_id__isnull=True)
& Q(
draft_issue_module__module__archived_at__isnull=True
)
& Q(draft_issue_module__deleted_at__isnull=True)
filter=~Q(draft_issue_module__module_id__isnull=True)
& Q(
draft_issue_module__module__archived_at__isnull=True
),
),
Value([], output_field=ArrayField(UUIDField())),
@@ -275,9 +259,9 @@ class WorkspaceDraftIssueViewSet(BaseViewSet):
origin=request.META.get("HTTP_ORIGIN"),
)
if request.data.get("cycle_id", None):
if draft_issue.cycle_id:
created_records = CycleIssue.objects.create(
cycle_id=request.data.get("cycle_id", None),
cycle_id=draft_issue.cycle_id,
issue_id=serializer.data.get("id", None),
project_id=draft_issue.project_id,
workspace_id=draft_issue.workspace_id,
@@ -295,7 +279,7 @@ class WorkspaceDraftIssueViewSet(BaseViewSet):
{
"updated_cycle_issues": None,
"created_cycle_issues": serializers.serialize(
"json", [created_records]
"json", created_records
),
}
),
@@ -304,7 +288,7 @@ class WorkspaceDraftIssueViewSet(BaseViewSet):
origin=request.META.get("HTTP_ORIGIN"),
)
if request.data.get("module_ids", []):
if draft_issue.module_ids:
# bulk create the module
ModuleIssue.objects.bulk_create(
[
@@ -316,11 +300,11 @@ class WorkspaceDraftIssueViewSet(BaseViewSet):
created_by_id=draft_issue.created_by_id,
updated_by_id=draft_issue.updated_by_id,
)
for module in request.data.get("module_ids", [])
for module in draft_issue.module_ids
],
batch_size=10,
)
# Update the activity
# Bulk Update the activity
_ = [
issue_activity.delay(
type="module.activity.created",
@@ -333,20 +317,18 @@ class WorkspaceDraftIssueViewSet(BaseViewSet):
notification=True,
origin=request.META.get("HTTP_ORIGIN"),
)
for module in request.data.get("module_ids", [])
for module in draft_issue.module_ids
]
# Update file assets
file_assets = FileAsset.objects.filter(draft_issue_id=draft_id)
file_assets.update(
issue_id=serializer.data.get("id", None),
entity_type=FileAsset.EntityTypeContext.ISSUE_DESCRIPTION,
draft_issue_id=None,
)
# delete the draft issue
draft_issue.delete()
# delete the draft issue module
DraftIssueModule.objects.filter(draft_issue=draft_issue).delete()
# delete the draft issue cycle
DraftIssueCycle.objects.filter(draft_issue=draft_issue).delete()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+4 -25
View File
@@ -3,11 +3,7 @@ from django.db.models import (
CharField,
Count,
Q,
OuterRef,
Subquery,
IntegerField,
)
from django.db.models.functions import Coalesce
from django.db.models.functions import Cast
# Third party modules
@@ -38,7 +34,6 @@ from plane.db.models import (
User,
Workspace,
WorkspaceMember,
DraftIssue,
)
from plane.utils.cache import cache_response, invalidate_cache
@@ -288,26 +283,10 @@ class WorkspaceMemberUserViewsEndpoint(BaseAPIView):
class WorkspaceMemberUserEndpoint(BaseAPIView):
def get(self, request, slug):
draft_issue_count = (
DraftIssue.objects.filter(
created_by=request.user,
workspace_id=OuterRef("workspace_id"),
)
.values("workspace_id")
.annotate(count=Count("id"))
.values("count")
)
workspace_member = (
WorkspaceMember.objects.filter(
member=request.user, workspace__slug=slug, is_active=True
)
.annotate(
draft_issue_count=Coalesce(
Subquery(draft_issue_count, output_field=IntegerField()), 0
)
)
.first()
workspace_member = WorkspaceMember.objects.get(
member=request.user,
workspace__slug=slug,
is_active=True,
)
serializer = WorkspaceMemberMeSerializer(workspace_member)
return Response(serializer.data, status=status.HTTP_200_OK)
@@ -45,7 +45,6 @@ class WorkspaceModulesEndpoint(BaseAPIView):
filter=Q(
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
issue_module__deleted_at__isnull=True,
),
distinct=True,
),
@@ -57,7 +56,6 @@ class WorkspaceModulesEndpoint(BaseAPIView):
issue_module__issue__state__group="completed",
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
issue_module__deleted_at__isnull=True,
),
distinct=True,
)
@@ -69,7 +67,6 @@ class WorkspaceModulesEndpoint(BaseAPIView):
issue_module__issue__state__group="cancelled",
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
issue_module__deleted_at__isnull=True,
),
distinct=True,
)
@@ -81,7 +78,6 @@ class WorkspaceModulesEndpoint(BaseAPIView):
issue_module__issue__state__group="started",
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
issue_module__deleted_at__isnull=True,
),
distinct=True,
)
@@ -93,7 +89,6 @@ class WorkspaceModulesEndpoint(BaseAPIView):
issue_module__issue__state__group="unstarted",
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
issue_module__deleted_at__isnull=True,
),
distinct=True,
)
@@ -105,7 +100,6 @@ class WorkspaceModulesEndpoint(BaseAPIView):
issue_module__issue__state__group="backlog",
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
issue_module__deleted_at__isnull=True,
),
distinct=True,
)
+1 -8
View File
@@ -14,7 +14,6 @@ from django.db.models import (
Q,
Value,
When,
Subquery,
)
from django.db.models.fields import DateField
from django.db.models.functions import Cast, ExtractWeek
@@ -121,13 +120,7 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
.filter(**filters)
.select_related("workspace", "project", "state", "parent")
.prefetch_related("assignees", "labels", "issue_module__module")
.annotate(
cycle_id=Subquery(
CycleIssue.objects.filter(
issue=OuterRef("id"), deleted_at__isnull=True
).values("cycle_id")[:1]
)
)
.annotate(cycle_id=F("issue_cycle__cycle_id"))
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
+15 -45
View File
@@ -3,7 +3,6 @@ import requests
# Django imports
from django.utils import timezone
from django.db import DatabaseError, IntegrityError
# Module imports
from plane.db.models import Account
@@ -13,7 +12,6 @@ from plane.authentication.adapter.error import (
AuthenticationException,
AUTHENTICATION_ERROR_CODES,
)
from plane.utils.exception_logger import log_exception
class OauthAdapter(Adapter):
@@ -99,48 +97,20 @@ class OauthAdapter(Adapter):
self.user_data = data
def create_update_account(self, user):
try:
# Check if the account already exists
account = Account.objects.filter(
user=user,
provider=self.provider,
provider_account_id=self.user_data.get("user").get(
"provider_id"
),
).first()
# Update the account if it exists
if account:
account.access_token = self.token_data.get("access_token")
account.refresh_token = self.token_data.get(
"refresh_token", None
)
account.access_token_expired_at = self.token_data.get(
account, created = Account.objects.update_or_create(
user=user,
provider=self.provider,
provider_account_id=self.user_data.get("user").get("provider_id"),
defaults={
"access_token": self.token_data.get("access_token"),
"refresh_token": self.token_data.get("refresh_token", None),
"access_token_expired_at": self.token_data.get(
"access_token_expired_at"
)
account.refresh_token_expired_at = self.token_data.get(
),
"refresh_token_expired_at": self.token_data.get(
"refresh_token_expired_at"
)
account.last_connected_at = timezone.now()
account.id_token = self.token_data.get("id_token", "")
account.save()
# Create a new account if it does not exist
else:
Account.objects.create(
user=user,
provider=self.provider,
provider_account_id=self.user_data.get("user", {}).get(
"provider_id"
),
access_token=self.token_data.get("access_token"),
refresh_token=self.token_data.get("refresh_token", None),
access_token_expired_at=self.token_data.get(
"access_token_expired_at"
),
refresh_token_expired_at=self.token_data.get(
"refresh_token_expired_at"
),
last_connected_at=timezone.now(),
id_token=self.token_data.get("id_token", ""),
)
except (DatabaseError, IntegrityError) as e:
log_exception(e)
),
"last_connected_at": timezone.now(),
"id_token": self.token_data.get("id_token", ""),
},
)
@@ -130,10 +130,7 @@ def get_label_details(slug, filters):
"""Fetch label details if required"""
return (
Issue.objects.filter(
workspace__slug=slug,
**filters,
labels__id__isnull=False,
label_issue__deleted_at__isnull=True,
workspace__slug=slug, **filters, labels__id__isnull=False
)
.distinct("labels__id")
.order_by("labels__id")
@@ -159,7 +156,6 @@ def get_module_details(slug, filters):
workspace__slug=slug,
**filters,
issue_module__module_id__isnull=False,
issue_module__deleted_at__isnull=True,
)
.distinct("issue_module__module_id")
.order_by("issue_module__module_id")
@@ -176,7 +172,6 @@ def get_cycle_details(slug, filters):
workspace__slug=slug,
**filters,
issue_cycle__cycle_id__isnull=False,
issue_cycle__deleted_at__isnull=True,
)
.distinct("issue_cycle__cycle_id")
.order_by("issue_cycle__cycle_id")
+2 -2
View File
@@ -21,8 +21,8 @@ def soft_delete_related_objects(
try:
# Check if the field has CASCADE on delete
if (
not hasattr(field.remote_field, "on_delete")
or field.remote_field.on_delete == models.CASCADE
hasattr(field.remote_field, "on_delete")
and field.remote_field.on_delete == models.CASCADE
):
if field.one_to_many:
related_objects = getattr(instance, field.name).all()
@@ -31,7 +31,6 @@ from plane.db.models import (
from plane.settings.redis import redis_instance
from plane.utils.exception_logger import log_exception
from plane.bgtasks.webhook_task import webhook_activity
from plane.utils.issue_relation_mapper import get_inverse_relation
# Track Changes in name
@@ -1395,9 +1394,6 @@ def create_issue_relation_activity(
epoch=epoch,
)
)
inverse_relation = get_inverse_relation(
requested_data.get("relation_type")
)
issue = Issue.objects.get(pk=issue_id)
issue_activities.append(
IssueActivity(
@@ -1406,10 +1402,19 @@ def create_issue_relation_activity(
verb="updated",
old_value="",
new_value=f"{issue.project.identifier}-{issue.sequence_id}",
field=inverse_relation,
field=(
"blocking"
if requested_data.get("relation_type") == "blocked_by"
else (
"blocked_by"
if requested_data.get("relation_type")
== "blocking"
else requested_data.get("relation_type")
)
),
project_id=project_id,
workspace_id=workspace_id,
comment=f"added {inverse_relation} relation",
comment=f'added {"blocking" if requested_data.get("relation_type") == "blocked_by" else ("blocked_by" if requested_data.get("relation_type") == "blocking" else requested_data.get("relation_type")),} relation',
old_identifier=issue_id,
epoch=epoch,
)
@@ -1,28 +0,0 @@
# Third party imports
from celery import shared_task
# Module imports
from plane.db.models import FileAsset
from plane.settings.storage import S3Storage
from plane.utils.exception_logger import log_exception
@shared_task
def get_asset_object_metadata(asset_id):
try:
# Get the asset
asset = FileAsset.objects.get(pk=asset_id)
# Create an instance of the S3 storage
storage = S3Storage()
# Get the storage
asset.storage_metadata = storage.get_object_metadata(
object_name=asset.asset.name
)
# Save the asset
asset.save(update_fields=["storage_metadata"])
return
except FileAsset.DoesNotExist:
return
except Exception as e:
log_exception(e)
return
+4 -4
View File
@@ -32,14 +32,14 @@ app.conf.beat_schedule = {
"task": "plane.bgtasks.email_notification_task.stack_email_notification",
"schedule": crontab(minute="*/5"),
},
"check-every-day-to-delete-hard-delete": {
"task": "plane.bgtasks.deletion_task.hard_delete",
"schedule": crontab(hour=0, minute=0),
},
"check-every-day-to-delete-api-logs": {
"task": "plane.bgtasks.api_logs_task.delete_api_logs",
"schedule": crontab(hour=0, minute=0),
},
"check-every-day-to-delete-hard-delete": {
"task": "plane.bgtasks.deletion_task.hard_delete",
"schedule": crontab(hour=0, minute=0),
},
"run-every-6-hours-for-instance-trace": {
"task": "plane.license.bgtasks.tracer.instance_traces",
"schedule": crontab(hour="*/6"),
@@ -68,6 +68,7 @@ class Command(BaseCommand):
Key="test_permission_check.txt",
Body=b"Test",
)
self.stdout.write("PutObject permission granted.")
permissions["s3:PutObject"] = True
# Clean up
except ClientError as e:
@@ -149,47 +150,23 @@ class Command(BaseCommand):
def handle(self, *args, **options):
# Create a session using the credentials from Django settings
# Check if the bucket exists
s3_client = self.get_s3_client()
# Get the bucket name from the environment
bucket_name = os.environ.get("AWS_S3_BUCKET_NAME")
if not bucket_name:
self.stdout.write(
self.style.ERROR(
"Please set the AWS_S3_BUCKET_NAME environment variable."
)
)
return
self.stdout.write(self.style.NOTICE("Checking bucket..."))
# Check if the bucket exists
try:
# Check if the bucket exists
s3_client = self.get_s3_client()
# Get the bucket name from the environment
bucket_name = os.environ.get("AWS_S3_BUCKET_NAME")
self.stdout.write(self.style.NOTICE("Checking bucket..."))
# Check if the bucket exists
s3_client.head_bucket(Bucket=bucket_name)
except ClientError as e:
error_code = e.response["Error"]["Code"]
if error_code == "404":
self.stdout.write(
self.style.ERROR(f"Bucket '{bucket_name}' does not exist.")
)
return
else:
self.stdout.write(f"Error: {e}")
# If the bucket exists, print a success message
self.stdout.write(
self.style.SUCCESS(f"Bucket '{bucket_name}' exists.")
)
try:
# If the bucket exists, print a success message
self.stdout.write(
self.style.SUCCESS(f"Bucket '{bucket_name}' exists.")
)
# Check the permissions of the access key
permissions = self.check_s3_permissions(bucket_name)
except ClientError as e:
self.stdout.write(f"Error: {e}")
except Exception as e:
self.stdout.write(f"Error: {e}")
# If the access key has the required permissions
try:
if all(permissions.values()):
self.stdout.write(
self.style.SUCCESS(
@@ -198,26 +175,35 @@ class Command(BaseCommand):
)
# Making the existing objects public
self.make_objects_public(bucket_name)
return
except Exception as e:
self.stdout.write(f"Error: {e}")
# write the bucket policy to a file
self.stdout.write(
self.style.WARNING(
"Generating permissions.json for manual bucket policy update."
)
)
try:
# Writing to a file
with open("permissions.json", "w") as f:
f.write(json.dumps(self.generate_bucket_policy(bucket_name)))
self.stdout.write(
self.style.WARNING(
"Permissions have been written to permissions.json."
# If the access key does not have PutBucketPolicy permission
# write the bucket policy to a file
if (
all(
{
k: v
for k, v in permissions.items()
if k != "s3:PutBucketPolicy"
}.values()
)
)
return
except IOError as e:
self.stdout.write(f"Error writing permissions.json: {e}")
return
and not permissions["s3:PutBucketPolicy"]
):
self.stdout.write(
self.style.WARNING(
"Access key does not have PutBucketPolicy permission."
)
)
# Writing to a file
with open("permissions.json", "w") as f:
f.write(
json.dumps(self.generate_bucket_policy(bucket_name))
)
self.stdout.write(
self.style.WARNING(
"Permissions have been written to permissions.json."
)
)
return
except Exception as ex:
# Handle any other exception
self.stdout.write(self.style.ERROR(f"An error occurred: {ex}"))
@@ -3,6 +3,7 @@
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
from plane.db.models import IssueRelation
from sentry_sdk import capture_exception
import uuid
@@ -10,7 +11,6 @@ import uuid
def create_issue_relation(apps, schema_editor):
try:
IssueBlockerModel = apps.get_model("db", "IssueBlocker")
IssueRelation = apps.get_model("db", "IssueRelation")
updated_issue_relation = []
for blocked_issue in IssueBlockerModel.objects.all():
updated_issue_relation.append(
@@ -1,10 +1,11 @@
# Generated by Django 4.2.7 on 2024-01-02 13:15
from plane.db.models import WorkspaceUserProperties, ProjectMember, IssueView
from django.db import migrations
def workspace_user_properties(apps, schema_editor):
WorkspaceMember = apps.get_model("db", "WorkspaceMember")
WorkspaceUserProperties = apps.get_model("db", "WorkspaceUserProperties")
updated_workspace_user_properties = []
for workspace_members in WorkspaceMember.objects.all():
updated_workspace_user_properties.append(
@@ -20,14 +21,12 @@ def workspace_user_properties(apps, schema_editor):
)
)
WorkspaceUserProperties.objects.bulk_create(
updated_workspace_user_properties,
batch_size=2000,
updated_workspace_user_properties, batch_size=2000
)
def project_user_properties(apps, schema_editor):
IssueProperty = apps.get_model("db", "IssueProperty")
ProjectMember = apps.get_model("db", "ProjectMember")
updated_issue_user_properties = []
for issue_property in IssueProperty.objects.all():
project_member = ProjectMember.objects.filter(
@@ -50,7 +49,6 @@ def project_user_properties(apps, schema_editor):
def issue_view(apps, schema_editor):
GlobalView = apps.get_model("db", "GlobalView")
IssueView = apps.get_model("db", "IssueView")
updated_issue_views = []
for global_view in GlobalView.objects.all():
@@ -0,0 +1,179 @@
# Generated by Django 4.2.16 on 2024-10-14 07:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("db", "0080_fileasset_draft_issue_alter_fileasset_entity_type"),
]
operations = [
migrations.AddField(
model_name="issuetype",
name="external_id",
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name="issuetype",
name="external_source",
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.RenameField(
model_name="usernotificationpreference",
old_name="property_change",
new_name="email_property_change",
),
migrations.AlterField(
model_name="usernotificationpreference",
name="email_property_change",
field=models.BooleanField(default=False),
),
migrations.RenameField(
model_name="usernotificationpreference",
old_name="state_change",
new_name="email_state_change",
),
migrations.AlterField(
model_name="usernotificationpreference",
name="email_state_change",
field=models.BooleanField(default=False),
),
migrations.RenameField(
model_name="usernotificationpreference",
old_name="comment",
new_name="email_comment",
),
migrations.AlterField(
model_name="usernotificationpreference",
name="email_comment",
field=models.BooleanField(default=False),
),
migrations.RenameField(
model_name="usernotificationpreference",
old_name="mention",
new_name="email_mention",
),
migrations.AlterField(
model_name="usernotificationpreference",
name="email_mention",
field=models.BooleanField(default=False),
),
migrations.RenameField(
model_name="usernotificationpreference",
old_name="issue_completed",
new_name="email_issue_completed",
),
migrations.AlterField(
model_name="usernotificationpreference",
name="email_issue_completed",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="deployboard",
name="is_disabled",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="usernotificationpreference",
name="email_assignee_change",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="usernotificationpreference",
name="email_cycle_change",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="usernotificationpreference",
name="email_module_change",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="usernotificationpreference",
name="email_priority_change",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="usernotificationpreference",
name="email_reactions",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="usernotificationpreference",
name="email_start_target_date_change",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="usernotificationpreference",
name="in_app_assignee_change",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="usernotificationpreference",
name="in_app_comment",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="usernotificationpreference",
name="in_app_cycle_change",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="usernotificationpreference",
name="in_app_issue_completed",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="usernotificationpreference",
name="in_app_mention",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="usernotificationpreference",
name="in_app_module_change",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="usernotificationpreference",
name="in_app_priority_change",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="usernotificationpreference",
name="in_app_property_change",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="usernotificationpreference",
name="in_app_reactions",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="usernotificationpreference",
name="in_app_start_target_date_change",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="usernotificationpreference",
name="in_app_state_change",
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name="deployboard",
name="entity_name",
field=models.CharField(
choices=[
("project", "Project"),
("issue", "Issue"),
("module", "Module"),
("cycle", "Task"),
("page", "Page"),
("view", "View"),
("inbox", "Inbox"),
],
max_length=30,
),
),
]
@@ -1,187 +0,0 @@
# Generated by Django 4.2.16 on 2024-10-15 11:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("db", "0080_fileasset_draft_issue_alter_fileasset_entity_type"),
]
operations = [
migrations.RemoveField(
model_name="globalview",
name="created_by",
),
migrations.RemoveField(
model_name="globalview",
name="updated_by",
),
migrations.RemoveField(
model_name="globalview",
name="workspace",
),
migrations.AlterUniqueTogether(
name="issueviewfavorite",
unique_together=None,
),
migrations.RemoveField(
model_name="issueviewfavorite",
name="created_by",
),
migrations.RemoveField(
model_name="issueviewfavorite",
name="project",
),
migrations.RemoveField(
model_name="issueviewfavorite",
name="updated_by",
),
migrations.RemoveField(
model_name="issueviewfavorite",
name="user",
),
migrations.RemoveField(
model_name="issueviewfavorite",
name="view",
),
migrations.RemoveField(
model_name="issueviewfavorite",
name="workspace",
),
migrations.AlterUniqueTogether(
name="modulefavorite",
unique_together=None,
),
migrations.RemoveField(
model_name="modulefavorite",
name="created_by",
),
migrations.RemoveField(
model_name="modulefavorite",
name="module",
),
migrations.RemoveField(
model_name="modulefavorite",
name="project",
),
migrations.RemoveField(
model_name="modulefavorite",
name="updated_by",
),
migrations.RemoveField(
model_name="modulefavorite",
name="user",
),
migrations.RemoveField(
model_name="modulefavorite",
name="workspace",
),
migrations.RemoveField(
model_name="pageblock",
name="created_by",
),
migrations.RemoveField(
model_name="pageblock",
name="issue",
),
migrations.RemoveField(
model_name="pageblock",
name="page",
),
migrations.RemoveField(
model_name="pageblock",
name="project",
),
migrations.RemoveField(
model_name="pageblock",
name="updated_by",
),
migrations.RemoveField(
model_name="pageblock",
name="workspace",
),
migrations.AlterUniqueTogether(
name="pagefavorite",
unique_together=None,
),
migrations.RemoveField(
model_name="pagefavorite",
name="created_by",
),
migrations.RemoveField(
model_name="pagefavorite",
name="page",
),
migrations.RemoveField(
model_name="pagefavorite",
name="project",
),
migrations.RemoveField(
model_name="pagefavorite",
name="updated_by",
),
migrations.RemoveField(
model_name="pagefavorite",
name="user",
),
migrations.RemoveField(
model_name="pagefavorite",
name="workspace",
),
migrations.AlterUniqueTogether(
name="projectfavorite",
unique_together=None,
),
migrations.RemoveField(
model_name="projectfavorite",
name="created_by",
),
migrations.RemoveField(
model_name="projectfavorite",
name="project",
),
migrations.RemoveField(
model_name="projectfavorite",
name="updated_by",
),
migrations.RemoveField(
model_name="projectfavorite",
name="user",
),
migrations.RemoveField(
model_name="projectfavorite",
name="workspace",
),
migrations.AddField(
model_name="issuetype",
name="external_id",
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name="issuetype",
name="external_source",
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.DeleteModel(
name="CycleFavorite",
),
migrations.DeleteModel(
name="GlobalView",
),
migrations.DeleteModel(
name="IssueViewFavorite",
),
migrations.DeleteModel(
name="ModuleFavorite",
),
migrations.DeleteModel(
name="PageBlock",
),
migrations.DeleteModel(
name="PageFavorite",
),
migrations.DeleteModel(
name="ProjectFavorite",
),
]
@@ -1,63 +0,0 @@
# Generated by Django 4.2.15 on 2024-10-22 08:00
from django.db import migrations, models
import django.db.models.deletion
import django.db.models.manager
class Migration(migrations.Migration):
dependencies = [
("db", "0081_remove_globalview_created_by_and_more"),
]
operations = [
migrations.AlterModelManagers(
name="issue",
managers=[
("issue_objects", django.db.models.manager.Manager()),
],
),
migrations.AlterField(
model_name="cycleissue",
name="issue",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="issue_cycle",
to="db.issue",
),
),
migrations.AlterField(
model_name="draftissuecycle",
name="draft_issue",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="draft_issue_cycle",
to="db.draftissue",
),
),
migrations.AlterUniqueTogether(
name="cycleissue",
unique_together={("issue", "cycle", "deleted_at")},
),
migrations.AlterUniqueTogether(
name="draftissuecycle",
unique_together={("draft_issue", "cycle", "deleted_at")},
),
migrations.AddConstraint(
model_name="cycleissue",
constraint=models.UniqueConstraint(
condition=models.Q(("deleted_at__isnull", True)),
fields=("cycle", "issue"),
name="cycle_issue_when_deleted_at_null",
),
),
migrations.AddConstraint(
model_name="draftissuecycle",
constraint=models.UniqueConstraint(
condition=models.Q(("deleted_at__isnull", True)),
fields=("draft_issue", "cycle"),
name="draft_issue_cycle_when_deleted_at_null",
),
),
]
@@ -1,874 +0,0 @@
# Generated by Django 4.2.15 on 2024-11-01 17:02
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
("db", "0082_alter_issue_managers_alter_cycleissue_issue_and_more"),
]
operations = [
migrations.CreateModel(
name="Device",
fields=[
(
"created_at",
models.DateTimeField(
auto_now_add=True, verbose_name="Created At"
),
),
(
"updated_at",
models.DateTimeField(
auto_now=True, verbose_name="Last Modified At"
),
),
(
"deleted_at",
models.DateTimeField(
blank=True, null=True, verbose_name="Deleted At"
),
),
(
"id",
models.UUIDField(
db_index=True,
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
unique=True,
),
),
(
"device_id",
models.CharField(blank=True, max_length=255, null=True),
),
(
"device_type",
models.CharField(
choices=[
("ANDROID", "Android"),
("IOS", "iOS"),
("WEB", "Web"),
("DESKTOP", "Desktop"),
],
max_length=255,
),
),
(
"push_token",
models.CharField(blank=True, max_length=255, null=True),
),
("is_active", models.BooleanField(default=True)),
(
"created_by",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="%(class)s_created_by",
to=settings.AUTH_USER_MODEL,
verbose_name="Created By",
),
),
(
"updated_by",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="%(class)s_updated_by",
to=settings.AUTH_USER_MODEL,
verbose_name="Last Modified By",
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="devices",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"verbose_name": "Device",
"verbose_name_plural": "Devices",
"db_table": "devices",
},
),
migrations.AddField(
model_name="issuetype",
name="is_epic",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="workspace",
name="timezone",
field=models.CharField(
choices=[
("Africa/Abidjan", "Africa/Abidjan"),
("Africa/Accra", "Africa/Accra"),
("Africa/Addis_Ababa", "Africa/Addis_Ababa"),
("Africa/Algiers", "Africa/Algiers"),
("Africa/Asmara", "Africa/Asmara"),
("Africa/Asmera", "Africa/Asmera"),
("Africa/Bamako", "Africa/Bamako"),
("Africa/Bangui", "Africa/Bangui"),
("Africa/Banjul", "Africa/Banjul"),
("Africa/Bissau", "Africa/Bissau"),
("Africa/Blantyre", "Africa/Blantyre"),
("Africa/Brazzaville", "Africa/Brazzaville"),
("Africa/Bujumbura", "Africa/Bujumbura"),
("Africa/Cairo", "Africa/Cairo"),
("Africa/Casablanca", "Africa/Casablanca"),
("Africa/Ceuta", "Africa/Ceuta"),
("Africa/Conakry", "Africa/Conakry"),
("Africa/Dakar", "Africa/Dakar"),
("Africa/Dar_es_Salaam", "Africa/Dar_es_Salaam"),
("Africa/Djibouti", "Africa/Djibouti"),
("Africa/Douala", "Africa/Douala"),
("Africa/El_Aaiun", "Africa/El_Aaiun"),
("Africa/Freetown", "Africa/Freetown"),
("Africa/Gaborone", "Africa/Gaborone"),
("Africa/Harare", "Africa/Harare"),
("Africa/Johannesburg", "Africa/Johannesburg"),
("Africa/Juba", "Africa/Juba"),
("Africa/Kampala", "Africa/Kampala"),
("Africa/Khartoum", "Africa/Khartoum"),
("Africa/Kigali", "Africa/Kigali"),
("Africa/Kinshasa", "Africa/Kinshasa"),
("Africa/Lagos", "Africa/Lagos"),
("Africa/Libreville", "Africa/Libreville"),
("Africa/Lome", "Africa/Lome"),
("Africa/Luanda", "Africa/Luanda"),
("Africa/Lubumbashi", "Africa/Lubumbashi"),
("Africa/Lusaka", "Africa/Lusaka"),
("Africa/Malabo", "Africa/Malabo"),
("Africa/Maputo", "Africa/Maputo"),
("Africa/Maseru", "Africa/Maseru"),
("Africa/Mbabane", "Africa/Mbabane"),
("Africa/Mogadishu", "Africa/Mogadishu"),
("Africa/Monrovia", "Africa/Monrovia"),
("Africa/Nairobi", "Africa/Nairobi"),
("Africa/Ndjamena", "Africa/Ndjamena"),
("Africa/Niamey", "Africa/Niamey"),
("Africa/Nouakchott", "Africa/Nouakchott"),
("Africa/Ouagadougou", "Africa/Ouagadougou"),
("Africa/Porto-Novo", "Africa/Porto-Novo"),
("Africa/Sao_Tome", "Africa/Sao_Tome"),
("Africa/Timbuktu", "Africa/Timbuktu"),
("Africa/Tripoli", "Africa/Tripoli"),
("Africa/Tunis", "Africa/Tunis"),
("Africa/Windhoek", "Africa/Windhoek"),
("America/Adak", "America/Adak"),
("America/Anchorage", "America/Anchorage"),
("America/Anguilla", "America/Anguilla"),
("America/Antigua", "America/Antigua"),
("America/Araguaina", "America/Araguaina"),
(
"America/Argentina/Buenos_Aires",
"America/Argentina/Buenos_Aires",
),
(
"America/Argentina/Catamarca",
"America/Argentina/Catamarca",
),
(
"America/Argentina/ComodRivadavia",
"America/Argentina/ComodRivadavia",
),
("America/Argentina/Cordoba", "America/Argentina/Cordoba"),
("America/Argentina/Jujuy", "America/Argentina/Jujuy"),
(
"America/Argentina/La_Rioja",
"America/Argentina/La_Rioja",
),
("America/Argentina/Mendoza", "America/Argentina/Mendoza"),
(
"America/Argentina/Rio_Gallegos",
"America/Argentina/Rio_Gallegos",
),
("America/Argentina/Salta", "America/Argentina/Salta"),
(
"America/Argentina/San_Juan",
"America/Argentina/San_Juan",
),
(
"America/Argentina/San_Luis",
"America/Argentina/San_Luis",
),
("America/Argentina/Tucuman", "America/Argentina/Tucuman"),
("America/Argentina/Ushuaia", "America/Argentina/Ushuaia"),
("America/Aruba", "America/Aruba"),
("America/Asuncion", "America/Asuncion"),
("America/Atikokan", "America/Atikokan"),
("America/Atka", "America/Atka"),
("America/Bahia", "America/Bahia"),
("America/Bahia_Banderas", "America/Bahia_Banderas"),
("America/Barbados", "America/Barbados"),
("America/Belem", "America/Belem"),
("America/Belize", "America/Belize"),
("America/Blanc-Sablon", "America/Blanc-Sablon"),
("America/Boa_Vista", "America/Boa_Vista"),
("America/Bogota", "America/Bogota"),
("America/Boise", "America/Boise"),
("America/Buenos_Aires", "America/Buenos_Aires"),
("America/Cambridge_Bay", "America/Cambridge_Bay"),
("America/Campo_Grande", "America/Campo_Grande"),
("America/Cancun", "America/Cancun"),
("America/Caracas", "America/Caracas"),
("America/Catamarca", "America/Catamarca"),
("America/Cayenne", "America/Cayenne"),
("America/Cayman", "America/Cayman"),
("America/Chicago", "America/Chicago"),
("America/Chihuahua", "America/Chihuahua"),
("America/Ciudad_Juarez", "America/Ciudad_Juarez"),
("America/Coral_Harbour", "America/Coral_Harbour"),
("America/Cordoba", "America/Cordoba"),
("America/Costa_Rica", "America/Costa_Rica"),
("America/Creston", "America/Creston"),
("America/Cuiaba", "America/Cuiaba"),
("America/Curacao", "America/Curacao"),
("America/Danmarkshavn", "America/Danmarkshavn"),
("America/Dawson", "America/Dawson"),
("America/Dawson_Creek", "America/Dawson_Creek"),
("America/Denver", "America/Denver"),
("America/Detroit", "America/Detroit"),
("America/Dominica", "America/Dominica"),
("America/Edmonton", "America/Edmonton"),
("America/Eirunepe", "America/Eirunepe"),
("America/El_Salvador", "America/El_Salvador"),
("America/Ensenada", "America/Ensenada"),
("America/Fort_Nelson", "America/Fort_Nelson"),
("America/Fort_Wayne", "America/Fort_Wayne"),
("America/Fortaleza", "America/Fortaleza"),
("America/Glace_Bay", "America/Glace_Bay"),
("America/Godthab", "America/Godthab"),
("America/Goose_Bay", "America/Goose_Bay"),
("America/Grand_Turk", "America/Grand_Turk"),
("America/Grenada", "America/Grenada"),
("America/Guadeloupe", "America/Guadeloupe"),
("America/Guatemala", "America/Guatemala"),
("America/Guayaquil", "America/Guayaquil"),
("America/Guyana", "America/Guyana"),
("America/Halifax", "America/Halifax"),
("America/Havana", "America/Havana"),
("America/Hermosillo", "America/Hermosillo"),
(
"America/Indiana/Indianapolis",
"America/Indiana/Indianapolis",
),
("America/Indiana/Knox", "America/Indiana/Knox"),
("America/Indiana/Marengo", "America/Indiana/Marengo"),
(
"America/Indiana/Petersburg",
"America/Indiana/Petersburg",
),
("America/Indiana/Tell_City", "America/Indiana/Tell_City"),
("America/Indiana/Vevay", "America/Indiana/Vevay"),
("America/Indiana/Vincennes", "America/Indiana/Vincennes"),
("America/Indiana/Winamac", "America/Indiana/Winamac"),
("America/Indianapolis", "America/Indianapolis"),
("America/Inuvik", "America/Inuvik"),
("America/Iqaluit", "America/Iqaluit"),
("America/Jamaica", "America/Jamaica"),
("America/Jujuy", "America/Jujuy"),
("America/Juneau", "America/Juneau"),
(
"America/Kentucky/Louisville",
"America/Kentucky/Louisville",
),
(
"America/Kentucky/Monticello",
"America/Kentucky/Monticello",
),
("America/Knox_IN", "America/Knox_IN"),
("America/Kralendijk", "America/Kralendijk"),
("America/La_Paz", "America/La_Paz"),
("America/Lima", "America/Lima"),
("America/Los_Angeles", "America/Los_Angeles"),
("America/Louisville", "America/Louisville"),
("America/Lower_Princes", "America/Lower_Princes"),
("America/Maceio", "America/Maceio"),
("America/Managua", "America/Managua"),
("America/Manaus", "America/Manaus"),
("America/Marigot", "America/Marigot"),
("America/Martinique", "America/Martinique"),
("America/Matamoros", "America/Matamoros"),
("America/Mazatlan", "America/Mazatlan"),
("America/Mendoza", "America/Mendoza"),
("America/Menominee", "America/Menominee"),
("America/Merida", "America/Merida"),
("America/Metlakatla", "America/Metlakatla"),
("America/Mexico_City", "America/Mexico_City"),
("America/Miquelon", "America/Miquelon"),
("America/Moncton", "America/Moncton"),
("America/Monterrey", "America/Monterrey"),
("America/Montevideo", "America/Montevideo"),
("America/Montreal", "America/Montreal"),
("America/Montserrat", "America/Montserrat"),
("America/Nassau", "America/Nassau"),
("America/New_York", "America/New_York"),
("America/Nipigon", "America/Nipigon"),
("America/Nome", "America/Nome"),
("America/Noronha", "America/Noronha"),
(
"America/North_Dakota/Beulah",
"America/North_Dakota/Beulah",
),
(
"America/North_Dakota/Center",
"America/North_Dakota/Center",
),
(
"America/North_Dakota/New_Salem",
"America/North_Dakota/New_Salem",
),
("America/Nuuk", "America/Nuuk"),
("America/Ojinaga", "America/Ojinaga"),
("America/Panama", "America/Panama"),
("America/Pangnirtung", "America/Pangnirtung"),
("America/Paramaribo", "America/Paramaribo"),
("America/Phoenix", "America/Phoenix"),
("America/Port-au-Prince", "America/Port-au-Prince"),
("America/Port_of_Spain", "America/Port_of_Spain"),
("America/Porto_Acre", "America/Porto_Acre"),
("America/Porto_Velho", "America/Porto_Velho"),
("America/Puerto_Rico", "America/Puerto_Rico"),
("America/Punta_Arenas", "America/Punta_Arenas"),
("America/Rainy_River", "America/Rainy_River"),
("America/Rankin_Inlet", "America/Rankin_Inlet"),
("America/Recife", "America/Recife"),
("America/Regina", "America/Regina"),
("America/Resolute", "America/Resolute"),
("America/Rio_Branco", "America/Rio_Branco"),
("America/Rosario", "America/Rosario"),
("America/Santa_Isabel", "America/Santa_Isabel"),
("America/Santarem", "America/Santarem"),
("America/Santiago", "America/Santiago"),
("America/Santo_Domingo", "America/Santo_Domingo"),
("America/Sao_Paulo", "America/Sao_Paulo"),
("America/Scoresbysund", "America/Scoresbysund"),
("America/Shiprock", "America/Shiprock"),
("America/Sitka", "America/Sitka"),
("America/St_Barthelemy", "America/St_Barthelemy"),
("America/St_Johns", "America/St_Johns"),
("America/St_Kitts", "America/St_Kitts"),
("America/St_Lucia", "America/St_Lucia"),
("America/St_Thomas", "America/St_Thomas"),
("America/St_Vincent", "America/St_Vincent"),
("America/Swift_Current", "America/Swift_Current"),
("America/Tegucigalpa", "America/Tegucigalpa"),
("America/Thule", "America/Thule"),
("America/Thunder_Bay", "America/Thunder_Bay"),
("America/Tijuana", "America/Tijuana"),
("America/Toronto", "America/Toronto"),
("America/Tortola", "America/Tortola"),
("America/Vancouver", "America/Vancouver"),
("America/Virgin", "America/Virgin"),
("America/Whitehorse", "America/Whitehorse"),
("America/Winnipeg", "America/Winnipeg"),
("America/Yakutat", "America/Yakutat"),
("America/Yellowknife", "America/Yellowknife"),
("Antarctica/Casey", "Antarctica/Casey"),
("Antarctica/Davis", "Antarctica/Davis"),
("Antarctica/DumontDUrville", "Antarctica/DumontDUrville"),
("Antarctica/Macquarie", "Antarctica/Macquarie"),
("Antarctica/Mawson", "Antarctica/Mawson"),
("Antarctica/McMurdo", "Antarctica/McMurdo"),
("Antarctica/Palmer", "Antarctica/Palmer"),
("Antarctica/Rothera", "Antarctica/Rothera"),
("Antarctica/South_Pole", "Antarctica/South_Pole"),
("Antarctica/Syowa", "Antarctica/Syowa"),
("Antarctica/Troll", "Antarctica/Troll"),
("Antarctica/Vostok", "Antarctica/Vostok"),
("Arctic/Longyearbyen", "Arctic/Longyearbyen"),
("Asia/Aden", "Asia/Aden"),
("Asia/Almaty", "Asia/Almaty"),
("Asia/Amman", "Asia/Amman"),
("Asia/Anadyr", "Asia/Anadyr"),
("Asia/Aqtau", "Asia/Aqtau"),
("Asia/Aqtobe", "Asia/Aqtobe"),
("Asia/Ashgabat", "Asia/Ashgabat"),
("Asia/Ashkhabad", "Asia/Ashkhabad"),
("Asia/Atyrau", "Asia/Atyrau"),
("Asia/Baghdad", "Asia/Baghdad"),
("Asia/Bahrain", "Asia/Bahrain"),
("Asia/Baku", "Asia/Baku"),
("Asia/Bangkok", "Asia/Bangkok"),
("Asia/Barnaul", "Asia/Barnaul"),
("Asia/Beirut", "Asia/Beirut"),
("Asia/Bishkek", "Asia/Bishkek"),
("Asia/Brunei", "Asia/Brunei"),
("Asia/Calcutta", "Asia/Calcutta"),
("Asia/Chita", "Asia/Chita"),
("Asia/Choibalsan", "Asia/Choibalsan"),
("Asia/Chongqing", "Asia/Chongqing"),
("Asia/Chungking", "Asia/Chungking"),
("Asia/Colombo", "Asia/Colombo"),
("Asia/Dacca", "Asia/Dacca"),
("Asia/Damascus", "Asia/Damascus"),
("Asia/Dhaka", "Asia/Dhaka"),
("Asia/Dili", "Asia/Dili"),
("Asia/Dubai", "Asia/Dubai"),
("Asia/Dushanbe", "Asia/Dushanbe"),
("Asia/Famagusta", "Asia/Famagusta"),
("Asia/Gaza", "Asia/Gaza"),
("Asia/Harbin", "Asia/Harbin"),
("Asia/Hebron", "Asia/Hebron"),
("Asia/Ho_Chi_Minh", "Asia/Ho_Chi_Minh"),
("Asia/Hong_Kong", "Asia/Hong_Kong"),
("Asia/Hovd", "Asia/Hovd"),
("Asia/Irkutsk", "Asia/Irkutsk"),
("Asia/Istanbul", "Asia/Istanbul"),
("Asia/Jakarta", "Asia/Jakarta"),
("Asia/Jayapura", "Asia/Jayapura"),
("Asia/Jerusalem", "Asia/Jerusalem"),
("Asia/Kabul", "Asia/Kabul"),
("Asia/Kamchatka", "Asia/Kamchatka"),
("Asia/Karachi", "Asia/Karachi"),
("Asia/Kashgar", "Asia/Kashgar"),
("Asia/Kathmandu", "Asia/Kathmandu"),
("Asia/Katmandu", "Asia/Katmandu"),
("Asia/Khandyga", "Asia/Khandyga"),
("Asia/Kolkata", "Asia/Kolkata"),
("Asia/Krasnoyarsk", "Asia/Krasnoyarsk"),
("Asia/Kuala_Lumpur", "Asia/Kuala_Lumpur"),
("Asia/Kuching", "Asia/Kuching"),
("Asia/Kuwait", "Asia/Kuwait"),
("Asia/Macao", "Asia/Macao"),
("Asia/Macau", "Asia/Macau"),
("Asia/Magadan", "Asia/Magadan"),
("Asia/Makassar", "Asia/Makassar"),
("Asia/Manila", "Asia/Manila"),
("Asia/Muscat", "Asia/Muscat"),
("Asia/Nicosia", "Asia/Nicosia"),
("Asia/Novokuznetsk", "Asia/Novokuznetsk"),
("Asia/Novosibirsk", "Asia/Novosibirsk"),
("Asia/Omsk", "Asia/Omsk"),
("Asia/Oral", "Asia/Oral"),
("Asia/Phnom_Penh", "Asia/Phnom_Penh"),
("Asia/Pontianak", "Asia/Pontianak"),
("Asia/Pyongyang", "Asia/Pyongyang"),
("Asia/Qatar", "Asia/Qatar"),
("Asia/Qostanay", "Asia/Qostanay"),
("Asia/Qyzylorda", "Asia/Qyzylorda"),
("Asia/Rangoon", "Asia/Rangoon"),
("Asia/Riyadh", "Asia/Riyadh"),
("Asia/Saigon", "Asia/Saigon"),
("Asia/Sakhalin", "Asia/Sakhalin"),
("Asia/Samarkand", "Asia/Samarkand"),
("Asia/Seoul", "Asia/Seoul"),
("Asia/Shanghai", "Asia/Shanghai"),
("Asia/Singapore", "Asia/Singapore"),
("Asia/Srednekolymsk", "Asia/Srednekolymsk"),
("Asia/Taipei", "Asia/Taipei"),
("Asia/Tashkent", "Asia/Tashkent"),
("Asia/Tbilisi", "Asia/Tbilisi"),
("Asia/Tehran", "Asia/Tehran"),
("Asia/Tel_Aviv", "Asia/Tel_Aviv"),
("Asia/Thimbu", "Asia/Thimbu"),
("Asia/Thimphu", "Asia/Thimphu"),
("Asia/Tokyo", "Asia/Tokyo"),
("Asia/Tomsk", "Asia/Tomsk"),
("Asia/Ujung_Pandang", "Asia/Ujung_Pandang"),
("Asia/Ulaanbaatar", "Asia/Ulaanbaatar"),
("Asia/Ulan_Bator", "Asia/Ulan_Bator"),
("Asia/Urumqi", "Asia/Urumqi"),
("Asia/Ust-Nera", "Asia/Ust-Nera"),
("Asia/Vientiane", "Asia/Vientiane"),
("Asia/Vladivostok", "Asia/Vladivostok"),
("Asia/Yakutsk", "Asia/Yakutsk"),
("Asia/Yangon", "Asia/Yangon"),
("Asia/Yekaterinburg", "Asia/Yekaterinburg"),
("Asia/Yerevan", "Asia/Yerevan"),
("Atlantic/Azores", "Atlantic/Azores"),
("Atlantic/Bermuda", "Atlantic/Bermuda"),
("Atlantic/Canary", "Atlantic/Canary"),
("Atlantic/Cape_Verde", "Atlantic/Cape_Verde"),
("Atlantic/Faeroe", "Atlantic/Faeroe"),
("Atlantic/Faroe", "Atlantic/Faroe"),
("Atlantic/Jan_Mayen", "Atlantic/Jan_Mayen"),
("Atlantic/Madeira", "Atlantic/Madeira"),
("Atlantic/Reykjavik", "Atlantic/Reykjavik"),
("Atlantic/South_Georgia", "Atlantic/South_Georgia"),
("Atlantic/St_Helena", "Atlantic/St_Helena"),
("Atlantic/Stanley", "Atlantic/Stanley"),
("Australia/ACT", "Australia/ACT"),
("Australia/Adelaide", "Australia/Adelaide"),
("Australia/Brisbane", "Australia/Brisbane"),
("Australia/Broken_Hill", "Australia/Broken_Hill"),
("Australia/Canberra", "Australia/Canberra"),
("Australia/Currie", "Australia/Currie"),
("Australia/Darwin", "Australia/Darwin"),
("Australia/Eucla", "Australia/Eucla"),
("Australia/Hobart", "Australia/Hobart"),
("Australia/LHI", "Australia/LHI"),
("Australia/Lindeman", "Australia/Lindeman"),
("Australia/Lord_Howe", "Australia/Lord_Howe"),
("Australia/Melbourne", "Australia/Melbourne"),
("Australia/NSW", "Australia/NSW"),
("Australia/North", "Australia/North"),
("Australia/Perth", "Australia/Perth"),
("Australia/Queensland", "Australia/Queensland"),
("Australia/South", "Australia/South"),
("Australia/Sydney", "Australia/Sydney"),
("Australia/Tasmania", "Australia/Tasmania"),
("Australia/Victoria", "Australia/Victoria"),
("Australia/West", "Australia/West"),
("Australia/Yancowinna", "Australia/Yancowinna"),
("Brazil/Acre", "Brazil/Acre"),
("Brazil/DeNoronha", "Brazil/DeNoronha"),
("Brazil/East", "Brazil/East"),
("Brazil/West", "Brazil/West"),
("CET", "CET"),
("CST6CDT", "CST6CDT"),
("Canada/Atlantic", "Canada/Atlantic"),
("Canada/Central", "Canada/Central"),
("Canada/Eastern", "Canada/Eastern"),
("Canada/Mountain", "Canada/Mountain"),
("Canada/Newfoundland", "Canada/Newfoundland"),
("Canada/Pacific", "Canada/Pacific"),
("Canada/Saskatchewan", "Canada/Saskatchewan"),
("Canada/Yukon", "Canada/Yukon"),
("Chile/Continental", "Chile/Continental"),
("Chile/EasterIsland", "Chile/EasterIsland"),
("Cuba", "Cuba"),
("EET", "EET"),
("EST", "EST"),
("EST5EDT", "EST5EDT"),
("Egypt", "Egypt"),
("Eire", "Eire"),
("Etc/GMT", "Etc/GMT"),
("Etc/GMT+0", "Etc/GMT+0"),
("Etc/GMT+1", "Etc/GMT+1"),
("Etc/GMT+10", "Etc/GMT+10"),
("Etc/GMT+11", "Etc/GMT+11"),
("Etc/GMT+12", "Etc/GMT+12"),
("Etc/GMT+2", "Etc/GMT+2"),
("Etc/GMT+3", "Etc/GMT+3"),
("Etc/GMT+4", "Etc/GMT+4"),
("Etc/GMT+5", "Etc/GMT+5"),
("Etc/GMT+6", "Etc/GMT+6"),
("Etc/GMT+7", "Etc/GMT+7"),
("Etc/GMT+8", "Etc/GMT+8"),
("Etc/GMT+9", "Etc/GMT+9"),
("Etc/GMT-0", "Etc/GMT-0"),
("Etc/GMT-1", "Etc/GMT-1"),
("Etc/GMT-10", "Etc/GMT-10"),
("Etc/GMT-11", "Etc/GMT-11"),
("Etc/GMT-12", "Etc/GMT-12"),
("Etc/GMT-13", "Etc/GMT-13"),
("Etc/GMT-14", "Etc/GMT-14"),
("Etc/GMT-2", "Etc/GMT-2"),
("Etc/GMT-3", "Etc/GMT-3"),
("Etc/GMT-4", "Etc/GMT-4"),
("Etc/GMT-5", "Etc/GMT-5"),
("Etc/GMT-6", "Etc/GMT-6"),
("Etc/GMT-7", "Etc/GMT-7"),
("Etc/GMT-8", "Etc/GMT-8"),
("Etc/GMT-9", "Etc/GMT-9"),
("Etc/GMT0", "Etc/GMT0"),
("Etc/Greenwich", "Etc/Greenwich"),
("Etc/UCT", "Etc/UCT"),
("Etc/UTC", "Etc/UTC"),
("Etc/Universal", "Etc/Universal"),
("Etc/Zulu", "Etc/Zulu"),
("Europe/Amsterdam", "Europe/Amsterdam"),
("Europe/Andorra", "Europe/Andorra"),
("Europe/Astrakhan", "Europe/Astrakhan"),
("Europe/Athens", "Europe/Athens"),
("Europe/Belfast", "Europe/Belfast"),
("Europe/Belgrade", "Europe/Belgrade"),
("Europe/Berlin", "Europe/Berlin"),
("Europe/Bratislava", "Europe/Bratislava"),
("Europe/Brussels", "Europe/Brussels"),
("Europe/Bucharest", "Europe/Bucharest"),
("Europe/Budapest", "Europe/Budapest"),
("Europe/Busingen", "Europe/Busingen"),
("Europe/Chisinau", "Europe/Chisinau"),
("Europe/Copenhagen", "Europe/Copenhagen"),
("Europe/Dublin", "Europe/Dublin"),
("Europe/Gibraltar", "Europe/Gibraltar"),
("Europe/Guernsey", "Europe/Guernsey"),
("Europe/Helsinki", "Europe/Helsinki"),
("Europe/Isle_of_Man", "Europe/Isle_of_Man"),
("Europe/Istanbul", "Europe/Istanbul"),
("Europe/Jersey", "Europe/Jersey"),
("Europe/Kaliningrad", "Europe/Kaliningrad"),
("Europe/Kiev", "Europe/Kiev"),
("Europe/Kirov", "Europe/Kirov"),
("Europe/Kyiv", "Europe/Kyiv"),
("Europe/Lisbon", "Europe/Lisbon"),
("Europe/Ljubljana", "Europe/Ljubljana"),
("Europe/London", "Europe/London"),
("Europe/Luxembourg", "Europe/Luxembourg"),
("Europe/Madrid", "Europe/Madrid"),
("Europe/Malta", "Europe/Malta"),
("Europe/Mariehamn", "Europe/Mariehamn"),
("Europe/Minsk", "Europe/Minsk"),
("Europe/Monaco", "Europe/Monaco"),
("Europe/Moscow", "Europe/Moscow"),
("Europe/Nicosia", "Europe/Nicosia"),
("Europe/Oslo", "Europe/Oslo"),
("Europe/Paris", "Europe/Paris"),
("Europe/Podgorica", "Europe/Podgorica"),
("Europe/Prague", "Europe/Prague"),
("Europe/Riga", "Europe/Riga"),
("Europe/Rome", "Europe/Rome"),
("Europe/Samara", "Europe/Samara"),
("Europe/San_Marino", "Europe/San_Marino"),
("Europe/Sarajevo", "Europe/Sarajevo"),
("Europe/Saratov", "Europe/Saratov"),
("Europe/Simferopol", "Europe/Simferopol"),
("Europe/Skopje", "Europe/Skopje"),
("Europe/Sofia", "Europe/Sofia"),
("Europe/Stockholm", "Europe/Stockholm"),
("Europe/Tallinn", "Europe/Tallinn"),
("Europe/Tirane", "Europe/Tirane"),
("Europe/Tiraspol", "Europe/Tiraspol"),
("Europe/Ulyanovsk", "Europe/Ulyanovsk"),
("Europe/Uzhgorod", "Europe/Uzhgorod"),
("Europe/Vaduz", "Europe/Vaduz"),
("Europe/Vatican", "Europe/Vatican"),
("Europe/Vienna", "Europe/Vienna"),
("Europe/Vilnius", "Europe/Vilnius"),
("Europe/Volgograd", "Europe/Volgograd"),
("Europe/Warsaw", "Europe/Warsaw"),
("Europe/Zagreb", "Europe/Zagreb"),
("Europe/Zaporozhye", "Europe/Zaporozhye"),
("Europe/Zurich", "Europe/Zurich"),
("GB", "GB"),
("GB-Eire", "GB-Eire"),
("GMT", "GMT"),
("GMT+0", "GMT+0"),
("GMT-0", "GMT-0"),
("GMT0", "GMT0"),
("Greenwich", "Greenwich"),
("HST", "HST"),
("Hongkong", "Hongkong"),
("Iceland", "Iceland"),
("Indian/Antananarivo", "Indian/Antananarivo"),
("Indian/Chagos", "Indian/Chagos"),
("Indian/Christmas", "Indian/Christmas"),
("Indian/Cocos", "Indian/Cocos"),
("Indian/Comoro", "Indian/Comoro"),
("Indian/Kerguelen", "Indian/Kerguelen"),
("Indian/Mahe", "Indian/Mahe"),
("Indian/Maldives", "Indian/Maldives"),
("Indian/Mauritius", "Indian/Mauritius"),
("Indian/Mayotte", "Indian/Mayotte"),
("Indian/Reunion", "Indian/Reunion"),
("Iran", "Iran"),
("Israel", "Israel"),
("Jamaica", "Jamaica"),
("Japan", "Japan"),
("Kwajalein", "Kwajalein"),
("Libya", "Libya"),
("MET", "MET"),
("MST", "MST"),
("MST7MDT", "MST7MDT"),
("Mexico/BajaNorte", "Mexico/BajaNorte"),
("Mexico/BajaSur", "Mexico/BajaSur"),
("Mexico/General", "Mexico/General"),
("NZ", "NZ"),
("NZ-CHAT", "NZ-CHAT"),
("Navajo", "Navajo"),
("PRC", "PRC"),
("PST8PDT", "PST8PDT"),
("Pacific/Apia", "Pacific/Apia"),
("Pacific/Auckland", "Pacific/Auckland"),
("Pacific/Bougainville", "Pacific/Bougainville"),
("Pacific/Chatham", "Pacific/Chatham"),
("Pacific/Chuuk", "Pacific/Chuuk"),
("Pacific/Easter", "Pacific/Easter"),
("Pacific/Efate", "Pacific/Efate"),
("Pacific/Enderbury", "Pacific/Enderbury"),
("Pacific/Fakaofo", "Pacific/Fakaofo"),
("Pacific/Fiji", "Pacific/Fiji"),
("Pacific/Funafuti", "Pacific/Funafuti"),
("Pacific/Galapagos", "Pacific/Galapagos"),
("Pacific/Gambier", "Pacific/Gambier"),
("Pacific/Guadalcanal", "Pacific/Guadalcanal"),
("Pacific/Guam", "Pacific/Guam"),
("Pacific/Honolulu", "Pacific/Honolulu"),
("Pacific/Johnston", "Pacific/Johnston"),
("Pacific/Kanton", "Pacific/Kanton"),
("Pacific/Kiritimati", "Pacific/Kiritimati"),
("Pacific/Kosrae", "Pacific/Kosrae"),
("Pacific/Kwajalein", "Pacific/Kwajalein"),
("Pacific/Majuro", "Pacific/Majuro"),
("Pacific/Marquesas", "Pacific/Marquesas"),
("Pacific/Midway", "Pacific/Midway"),
("Pacific/Nauru", "Pacific/Nauru"),
("Pacific/Niue", "Pacific/Niue"),
("Pacific/Norfolk", "Pacific/Norfolk"),
("Pacific/Noumea", "Pacific/Noumea"),
("Pacific/Pago_Pago", "Pacific/Pago_Pago"),
("Pacific/Palau", "Pacific/Palau"),
("Pacific/Pitcairn", "Pacific/Pitcairn"),
("Pacific/Pohnpei", "Pacific/Pohnpei"),
("Pacific/Ponape", "Pacific/Ponape"),
("Pacific/Port_Moresby", "Pacific/Port_Moresby"),
("Pacific/Rarotonga", "Pacific/Rarotonga"),
("Pacific/Saipan", "Pacific/Saipan"),
("Pacific/Samoa", "Pacific/Samoa"),
("Pacific/Tahiti", "Pacific/Tahiti"),
("Pacific/Tarawa", "Pacific/Tarawa"),
("Pacific/Tongatapu", "Pacific/Tongatapu"),
("Pacific/Truk", "Pacific/Truk"),
("Pacific/Wake", "Pacific/Wake"),
("Pacific/Wallis", "Pacific/Wallis"),
("Pacific/Yap", "Pacific/Yap"),
("Poland", "Poland"),
("Portugal", "Portugal"),
("ROC", "ROC"),
("ROK", "ROK"),
("Singapore", "Singapore"),
("Turkey", "Turkey"),
("UCT", "UCT"),
("US/Alaska", "US/Alaska"),
("US/Aleutian", "US/Aleutian"),
("US/Arizona", "US/Arizona"),
("US/Central", "US/Central"),
("US/East-Indiana", "US/East-Indiana"),
("US/Eastern", "US/Eastern"),
("US/Hawaii", "US/Hawaii"),
("US/Indiana-Starke", "US/Indiana-Starke"),
("US/Michigan", "US/Michigan"),
("US/Mountain", "US/Mountain"),
("US/Pacific", "US/Pacific"),
("US/Samoa", "US/Samoa"),
("UTC", "UTC"),
("Universal", "Universal"),
("W-SU", "W-SU"),
("WET", "WET"),
("Zulu", "Zulu"),
],
default="UTC",
max_length=255,
),
),
migrations.AlterField(
model_name="issuerelation",
name="relation_type",
field=models.CharField(
choices=[
("duplicate", "Duplicate"),
("relates_to", "Relates To"),
("blocked_by", "Blocked By"),
("start_before", "Start Before"),
("finish_before", "Finish Before"),
],
default="blocked_by",
max_length=20,
verbose_name="Issue Relation Type",
),
),
migrations.AlterField(
model_name="issuetype",
name="level",
field=models.FloatField(default=0),
),
migrations.AlterField(
model_name="label",
name="project",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="project_%(class)s",
to="db.project",
),
),
migrations.CreateModel(
name="DeviceSession",
fields=[
(
"created_at",
models.DateTimeField(
auto_now_add=True, verbose_name="Created At"
),
),
(
"updated_at",
models.DateTimeField(
auto_now=True, verbose_name="Last Modified At"
),
),
(
"deleted_at",
models.DateTimeField(
blank=True, null=True, verbose_name="Deleted At"
),
),
(
"id",
models.UUIDField(
db_index=True,
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
unique=True,
),
),
("is_active", models.BooleanField(default=True)),
(
"user_agent",
models.CharField(blank=True, max_length=255, null=True),
),
(
"ip_address",
models.GenericIPAddressField(blank=True, null=True),
),
("start_time", models.DateTimeField(auto_now_add=True)),
("end_time", models.DateTimeField(blank=True, null=True)),
(
"created_by",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="%(class)s_created_by",
to=settings.AUTH_USER_MODEL,
verbose_name="Created By",
),
),
(
"device",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="sessions",
to="db.device",
),
),
(
"session",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="device_sessions",
to="db.session",
),
),
(
"updated_by",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="%(class)s_updated_by",
to=settings.AUTH_USER_MODEL,
verbose_name="Last Modified By",
),
),
],
options={
"verbose_name": "Device Session",
"verbose_name_plural": "Device Sessions",
"db_table": "device_sessions",
},
),
]
@@ -1,75 +0,0 @@
# Generated by Django 4.2.15 on 2024-11-05 07:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("db", "0083_device_workspace_timezone_and_more"),
]
operations = [
migrations.RemoveConstraint(
model_name="label",
name="label_unique_name_project_when_deleted_at_null",
),
migrations.AlterUniqueTogether(
name="label",
unique_together=set(),
),
migrations.AddField(
model_name="deployboard",
name="is_disabled",
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name="inboxissue",
name="extra",
field=models.JSONField(default=dict),
),
migrations.AddField(
model_name="inboxissue",
name="source_email",
field=models.TextField(blank=True, null=True),
),
migrations.AddField(
model_name="user",
name="bot_type",
field=models.CharField(
blank=True, max_length=30, null=True, verbose_name="Bot Type"
),
),
migrations.AlterField(
model_name="deployboard",
name="entity_name",
field=models.CharField(blank=True, max_length=30, null=True),
),
migrations.AlterField(
model_name="inboxissue",
name="source",
field=models.CharField(
blank=True, default="IN_APP", max_length=255, null=True
),
),
migrations.AddConstraint(
model_name="label",
constraint=models.UniqueConstraint(
condition=models.Q(
("deleted_at__isnull", True), ("project__isnull", True)
),
fields=("name",),
name="unique_name_when_project_null_and_not_deleted",
),
),
migrations.AddConstraint(
model_name="label",
constraint=models.UniqueConstraint(
condition=models.Q(
("deleted_at__isnull", True), ("project__isnull", False)
),
fields=("project", "name"),
name="unique_project_name_when_not_deleted",
),
),
]
+1 -11
View File
@@ -43,19 +43,9 @@ class UserAuditModel(models.Model):
abstract = True
class SoftDeletionQuerySet(models.QuerySet):
def delete(self, soft=True):
if soft:
return self.update(deleted_at=timezone.now())
else:
return super().delete()
class SoftDeletionManager(models.Manager):
def get_queryset(self):
return SoftDeletionQuerySet(self.model, using=self._db).filter(
deleted_at__isnull=True
)
return super().get_queryset().filter(deleted_at__isnull=True)
class SoftDeleteModel(models.Model):
+8 -14
View File
@@ -2,16 +2,10 @@ from .analytic import AnalyticView
from .api import APIActivityLog, APIToken
from .asset import FileAsset
from .base import BaseModel
from .cycle import Cycle, CycleIssue, CycleUserProperties
from .cycle import Cycle, CycleFavorite, CycleIssue, CycleUserProperties
from .dashboard import Dashboard, DashboardWidget, Widget
from .deploy_board import DeployBoard
from .draft import (
DraftIssue,
DraftIssueAssignee,
DraftIssueLabel,
DraftIssueModule,
DraftIssueCycle,
)
from .draft import DraftIssue, DraftIssueAssignee, DraftIssueLabel, DraftIssueModule, DraftIssueCycle
from .estimate import Estimate, EstimatePoint
from .exporter import ExporterHistory
from .importer import Importer
@@ -41,9 +35,11 @@ from .issue import (
IssueSequence,
IssueSubscriber,
IssueVote,
Label,
)
from .module import (
Module,
ModuleFavorite,
ModuleIssue,
ModuleLink,
ModuleMember,
@@ -56,6 +52,7 @@ from .notification import (
)
from .page import (
Page,
PageFavorite,
PageLabel,
PageLog,
ProjectPage,
@@ -64,6 +61,7 @@ from .page import (
from .project import (
Project,
ProjectBaseModel,
ProjectFavorite,
ProjectIdentifier,
ProjectMember,
ProjectMemberInvite,
@@ -74,7 +72,7 @@ from .session import Session
from .social_connection import SocialLoginConnection
from .state import State
from .user import Account, Profile, User
from .view import IssueView
from .view import IssueView, IssueViewFavorite
from .webhook import Webhook, WebhookLog
from .workspace import (
Team,
@@ -89,7 +87,7 @@ from .workspace import (
from .importer import Importer
from .page import Page, PageLog, PageLabel
from .page import Page, PageLog, PageFavorite, PageLabel
from .estimate import Estimate, EstimatePoint
@@ -114,7 +112,3 @@ from .favorite import UserFavorite
from .issue_type import IssueType
from .recent_visit import UserRecentVisit
from .label import Label
from .device import Device, DeviceSession
+28 -9
View File
@@ -110,7 +110,7 @@ class CycleIssue(ProjectBaseModel):
Cycle Issues
"""
issue = models.ForeignKey(
issue = models.OneToOneField(
"db.Issue", on_delete=models.CASCADE, related_name="issue_cycle"
)
cycle = models.ForeignKey(
@@ -118,14 +118,6 @@ class CycleIssue(ProjectBaseModel):
)
class Meta:
unique_together = ["issue", "cycle", "deleted_at"]
constraints = [
models.UniqueConstraint(
fields=["cycle", "issue"],
condition=models.Q(deleted_at__isnull=True),
name="cycle_issue_when_deleted_at_null",
)
]
verbose_name = "Cycle Issue"
verbose_name_plural = "Cycle Issues"
db_table = "cycle_issues"
@@ -135,6 +127,33 @@ class CycleIssue(ProjectBaseModel):
return f"{self.cycle}"
# DEPRECATED TODO: - Remove in next release
class CycleFavorite(ProjectBaseModel):
"""_summary_
CycleFavorite (model): To store all the cycle favorite of the user
"""
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="cycle_favorites",
)
cycle = models.ForeignKey(
"db.Cycle", on_delete=models.CASCADE, related_name="cycle_favorites"
)
class Meta:
unique_together = ["cycle", "user"]
verbose_name = "Cycle Favorite"
verbose_name_plural = "Cycle Favorites"
db_table = "cycle_favorites"
ordering = ("-created_at",)
def __str__(self):
"""Return user and the cycle"""
return f"{self.user.email} <{self.cycle.name}>"
class CycleUserProperties(ProjectBaseModel):
cycle = models.ForeignKey(
"db.Cycle",
+2 -3
View File
@@ -20,14 +20,13 @@ class DeployBoard(WorkspaceBaseModel):
("cycle", "Task"),
("page", "Page"),
("view", "View"),
("intake", "Intake"),
("inbox", "Inbox"),
)
entity_identifier = models.UUIDField(null=True)
entity_name = models.CharField(
max_length=30,
null=True,
blank=True,
choices=TYPE_CHOICES,
)
anchor = models.CharField(
max_length=255, default=get_anchor, unique=True, db_index=True
-50
View File
@@ -1,50 +0,0 @@
# models.py
from django.db import models
from django.conf import settings
from .base import BaseModel
class Device(BaseModel):
class DeviceType(models.TextChoices):
ANDROID = "ANDROID", "Android"
IOS = "IOS", "iOS"
WEB = "WEB", "Web"
DESKTOP = "DESKTOP", "Desktop"
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="devices",
)
device_id = models.CharField(max_length=255, blank=True, null=True)
device_type = models.CharField(max_length=255, choices=DeviceType.choices)
push_token = models.CharField(max_length=255, blank=True, null=True)
is_active = models.BooleanField(default=True)
class Meta:
db_table = "devices"
verbose_name = "Device"
verbose_name_plural = "Devices"
class DeviceSession(BaseModel):
device = models.ForeignKey(
Device,
on_delete=models.CASCADE,
related_name="sessions",
)
session = models.ForeignKey(
"db.Session",
on_delete=models.CASCADE,
related_name="device_sessions",
)
is_active = models.BooleanField(default=True)
user_agent = models.CharField(max_length=255, null=True, blank=True)
ip_address = models.GenericIPAddressField(null=True, blank=True)
start_time = models.DateTimeField(auto_now_add=True)
end_time = models.DateTimeField(null=True, blank=True)
class Meta:
db_table = "device_sessions"
verbose_name = "Device Session"
verbose_name_plural = "Device Sessions"
+1 -9
View File
@@ -234,7 +234,7 @@ class DraftIssueCycle(WorkspaceBaseModel):
Draft Issue Cycles
"""
draft_issue = models.ForeignKey(
draft_issue = models.OneToOneField(
"db.DraftIssue",
on_delete=models.CASCADE,
related_name="draft_issue_cycle",
@@ -244,14 +244,6 @@ class DraftIssueCycle(WorkspaceBaseModel):
)
class Meta:
unique_together = ["draft_issue", "cycle", "deleted_at"]
constraints = [
models.UniqueConstraint(
fields=["draft_issue", "cycle"],
condition=models.Q(deleted_at__isnull=True),
name="draft_issue_cycle_when_deleted_at_null",
)
]
verbose_name = "Draft Issue Cycle"
verbose_name_plural = "Draft Issue Cycles"
db_table = "draft_issue_cycles"
+1 -8
View File
@@ -57,16 +57,9 @@ class InboxIssue(ProjectBaseModel):
on_delete=models.SET_NULL,
null=True,
)
source = models.CharField(
max_length=255,
default="IN_APP",
null=True,
blank=True,
)
source_email = models.TextField(blank=True, null=True)
source = models.TextField(blank=True, null=True)
external_source = models.CharField(max_length=255, null=True, blank=True)
external_id = models.CharField(max_length=255, blank=True, null=True)
extra = models.JSONField(default=dict)
class Meta:
verbose_name = "InboxIssue"
+47 -4
View File
@@ -12,7 +12,6 @@ from django.db.models import Q
# Module imports
from plane.utils.html_processor import strip_tags
from plane.db.mixins import SoftDeletionManager
from .project import ProjectBaseModel
@@ -80,7 +79,7 @@ def get_default_display_properties():
# TODO: Handle identifiers for Bulk Inserts - nk
class IssueManager(SoftDeletionManager):
class IssueManager(models.Manager):
def get_queryset(self):
return (
super()
@@ -173,6 +172,7 @@ class Issue(ProjectBaseModel):
blank=True,
)
objects = models.Manager()
issue_objects = IssueManager()
class Meta:
@@ -279,8 +279,6 @@ class IssueRelation(ProjectBaseModel):
("duplicate", "Duplicate"),
("relates_to", "Relates To"),
("blocked_by", "Blocked By"),
("start_before", "Start Before"),
("finish_before", "Finish Before"),
)
issue = models.ForeignKey(
@@ -549,6 +547,51 @@ class IssueUserProperty(ProjectBaseModel):
return str(self.user)
class Label(ProjectBaseModel):
parent = models.ForeignKey(
"self",
on_delete=models.CASCADE,
null=True,
blank=True,
related_name="parent_label",
)
name = models.CharField(max_length=255)
description = models.TextField(blank=True)
color = models.CharField(max_length=255, blank=True)
sort_order = models.FloatField(default=65535)
external_source = models.CharField(max_length=255, null=True, blank=True)
external_id = models.CharField(max_length=255, blank=True, null=True)
class Meta:
unique_together = ["name", "project", "deleted_at"]
constraints = [
models.UniqueConstraint(
fields=["name", "project"],
condition=Q(deleted_at__isnull=True),
name="label_unique_name_project_when_deleted_at_null",
)
]
verbose_name = "Label"
verbose_name_plural = "Labels"
db_table = "labels"
ordering = ("-created_at",)
def save(self, *args, **kwargs):
if self._state.adding:
# Get the maximum sequence value from the database
last_id = Label.objects.filter(project=self.project).aggregate(
largest=models.Max("sort_order")
)["largest"]
# if last_id is not None
if last_id is not None:
self.sort_order = last_id + 10000
super(Label, self).save(*args, **kwargs)
def __str__(self):
return str(self.name)
class IssueLabel(ProjectBaseModel):
issue = models.ForeignKey(
"db.Issue", on_delete=models.CASCADE, related_name="label_issue"
+1 -2
View File
@@ -16,10 +16,9 @@ class IssueType(BaseModel):
name = models.CharField(max_length=255)
description = models.TextField(blank=True)
logo_props = models.JSONField(default=dict)
is_epic = models.BooleanField(default=False)
is_default = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
level = models.FloatField(default=0)
level = models.PositiveIntegerField(default=0)
external_source = models.CharField(max_length=255, null=True, blank=True)
external_id = models.CharField(max_length=255, blank=True, null=True)
-55
View File
@@ -1,55 +0,0 @@
from django.db import models
from django.db.models import Q
from .workspace import WorkspaceBaseModel
class Label(WorkspaceBaseModel):
parent = models.ForeignKey(
"self",
on_delete=models.CASCADE,
null=True,
blank=True,
related_name="parent_label",
)
name = models.CharField(max_length=255)
description = models.TextField(blank=True)
color = models.CharField(max_length=255, blank=True)
sort_order = models.FloatField(default=65535)
external_source = models.CharField(max_length=255, null=True, blank=True)
external_id = models.CharField(max_length=255, blank=True, null=True)
class Meta:
constraints = [
# Enforce uniqueness of name when project is NULL and deleted_at is NULL
models.UniqueConstraint(
fields=["name"],
condition=Q(project__isnull=True, deleted_at__isnull=True),
name="unique_name_when_project_null_and_not_deleted",
),
# Enforce uniqueness of project and name when project is not NULL and deleted_at is NULL
models.UniqueConstraint(
fields=["project", "name"],
condition=Q(project__isnull=False, deleted_at__isnull=True),
name="unique_project_name_when_not_deleted",
),
]
verbose_name = "Label"
verbose_name_plural = "Labels"
db_table = "labels"
ordering = ("-created_at",)
def save(self, *args, **kwargs):
if self._state.adding:
# Get the maximum sequence value from the database
last_id = Label.objects.filter(project=self.project).aggregate(
largest=models.Max("sort_order")
)["largest"]
# if last_id is not None
if last_id is not None:
self.sort_order = last_id + 10000
super(Label, self).save(*args, **kwargs)
def __str__(self):
return str(self.name)
+29 -2
View File
@@ -100,9 +100,9 @@ class Module(ProjectBaseModel):
unique_together = ["name", "project", "deleted_at"]
constraints = [
models.UniqueConstraint(
fields=["name", "project"],
fields=['name', 'project'],
condition=Q(deleted_at__isnull=True),
name="module_unique_name_project_when_deleted_at_null",
name='module_unique_name_project_when_deleted_at_null'
)
]
verbose_name = "Module"
@@ -191,6 +191,33 @@ class ModuleLink(ProjectBaseModel):
return f"{self.module.name} {self.url}"
# DEPRECATED TODO: - Remove in next release
class ModuleFavorite(ProjectBaseModel):
"""_summary_
ModuleFavorite (model): To store all the module favorite of the user
"""
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="module_favorites",
)
module = models.ForeignKey(
"db.Module", on_delete=models.CASCADE, related_name="module_favorites"
)
class Meta:
unique_together = ["module", "user"]
verbose_name = "Module Favorite"
verbose_name_plural = "Module Favorites"
db_table = "module_favorites"
ordering = ("-created_at",)
def __str__(self):
"""Return user and the module"""
return f"{self.user.email} <{self.module.name}>"
class ModuleUserProperties(ProjectBaseModel):
module = models.ForeignKey(
"db.Module",
+25 -7
View File
@@ -6,7 +6,6 @@ from django.db import models
from .base import BaseModel
class Notification(BaseModel):
workspace = models.ForeignKey(
"db.Workspace", related_name="notifications", on_delete=models.CASCADE
@@ -90,12 +89,31 @@ class UserNotificationPreference(BaseModel):
null=True,
)
# preference fields
property_change = models.BooleanField(default=True)
state_change = models.BooleanField(default=True)
comment = models.BooleanField(default=True)
mention = models.BooleanField(default=True)
issue_completed = models.BooleanField(default=True)
# email preference fields
email_property_change = models.BooleanField(default=False)
email_state_change = models.BooleanField(default=False)
email_priority_change = models.BooleanField(default=False)
email_assignee_change = models.BooleanField(default=False)
email_start_target_date_change = models.BooleanField(default=False)
email_module_change = models.BooleanField(default=False)
email_cycle_change = models.BooleanField(default=False)
email_reactions = models.BooleanField(default=False)
email_comment = models.BooleanField(default=False)
email_mention = models.BooleanField(default=False)
email_issue_completed = models.BooleanField(default=False)
# in app preference fields
in_app_property_change = models.BooleanField(default=False)
in_app_state_change = models.BooleanField(default=False)
in_app_priority_change = models.BooleanField(default=False)
in_app_assignee_change = models.BooleanField(default=False)
in_app_start_target_date_change = models.BooleanField(default=False)
in_app_module_change = models.BooleanField(default=False)
in_app_cycle_change = models.BooleanField(default=False)
in_app_reactions = models.BooleanField(default=False)
in_app_comment = models.BooleanField(default=False)
in_app_mention = models.BooleanField(default=False)
in_app_issue_completed = models.BooleanField(default=False)
class Meta:
verbose_name = "UserNotificationPreference"
+81
View File
@@ -9,6 +9,7 @@ from django.db import models
# Module imports
from plane.utils.html_processor import strip_tags
from .project import ProjectBaseModel
from .base import BaseModel
@@ -118,6 +119,86 @@ class PageLog(BaseModel):
return f"{self.page.name} {self.entity_name}"
# DEPRECATED TODO: - Remove in next release
class PageBlock(ProjectBaseModel):
page = models.ForeignKey(
"db.Page", on_delete=models.CASCADE, related_name="blocks"
)
name = models.CharField(max_length=255)
description = models.JSONField(default=dict, blank=True)
description_html = models.TextField(blank=True, default="<p></p>")
description_stripped = models.TextField(blank=True, null=True)
issue = models.ForeignKey(
"db.Issue", on_delete=models.SET_NULL, related_name="blocks", null=True
)
completed_at = models.DateTimeField(null=True)
sort_order = models.FloatField(default=65535)
sync = models.BooleanField(default=True)
def save(self, *args, **kwargs):
if self._state.adding:
largest_sort_order = PageBlock.objects.filter(
project=self.project, page=self.page
).aggregate(largest=models.Max("sort_order"))["largest"]
if largest_sort_order is not None:
self.sort_order = largest_sort_order + 10000
# Strip the html tags using html parser
self.description_stripped = (
None
if (self.description_html == "" or self.description_html is None)
else strip_tags(self.description_html)
)
if self.completed_at and self.issue:
try:
from plane.db.models import Issue, State
completed_state = State.objects.filter(
group="completed", project=self.project
).first()
if completed_state is not None:
Issue.objects.update(
pk=self.issue_id, state=completed_state
)
except ImportError:
pass
super(PageBlock, self).save(*args, **kwargs)
class Meta:
verbose_name = "Page Block"
verbose_name_plural = "Page Blocks"
db_table = "page_blocks"
ordering = ("-created_at",)
def __str__(self):
"""Return page and page block"""
return f"{self.page.name} <{self.name}>"
# DEPRECATED TODO: - Remove in next release
class PageFavorite(ProjectBaseModel):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="page_favorites",
)
page = models.ForeignKey(
"db.Page", on_delete=models.CASCADE, related_name="page_favorites"
)
class Meta:
unique_together = ["page", "user"]
verbose_name = "Page Favorite"
verbose_name_plural = "Page Favorites"
db_table = "page_favorites"
ordering = ("-created_at",)
def __str__(self):
"""Return user and the page"""
return f"{self.user.email} <{self.page.name}>"
class PageLabel(BaseModel):
label = models.ForeignKey(
"db.Label", on_delete=models.CASCADE, related_name="page_labels"
+21 -3
View File
@@ -181,9 +181,7 @@ class ProjectBaseModel(BaseModel):
Project, on_delete=models.CASCADE, related_name="project_%(class)s"
)
workspace = models.ForeignKey(
"db.Workspace",
on_delete=models.CASCADE,
related_name="workspace_%(class)s",
"db.Workspace", models.CASCADE, related_name="workspace_%(class)s"
)
class Meta:
@@ -287,6 +285,26 @@ class ProjectIdentifier(AuditModel):
ordering = ("-created_at",)
# DEPRECATED TODO: - Remove in next release
class ProjectFavorite(ProjectBaseModel):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="project_favorites",
)
class Meta:
unique_together = ["project", "user"]
verbose_name = "Project Favorite"
verbose_name_plural = "Project Favorites"
db_table = "project_favorites"
ordering = ("-created_at",)
def __str__(self):
"""Return user of the project"""
return f"{self.user.email} <{self.project.name}>"
def get_anchor():
return uuid4().hex
+5 -18
View File
@@ -107,12 +107,6 @@ class User(AbstractBaseUser, PermissionsMixin):
# my_issues_prop = models.JSONField(null=True)
is_bot = models.BooleanField(default=False)
bot_type = models.CharField(
max_length=30,
verbose_name="Bot Type",
blank=True,
null=True,
)
# timezone
USER_TIMEZONE_CHOICES = tuple(zip(pytz.all_timezones, pytz.all_timezones))
@@ -215,12 +209,6 @@ class Profile(TimeAuditModel):
class Account(TimeAuditModel):
PROVIDER_CHOICES = (
("google", "Google"),
("github", "Github"),
("gitlab", "GitLab"),
)
id = models.UUIDField(
default=uuid.uuid4,
unique=True,
@@ -233,7 +221,11 @@ class Account(TimeAuditModel):
)
provider_account_id = models.CharField(max_length=255)
provider = models.CharField(
choices=PROVIDER_CHOICES,
choices=(
("google", "Google"),
("github", "Github"),
("gitlab", "GitLab"),
),
)
access_token = models.TextField()
access_token_expired_at = models.DateTimeField(null=True)
@@ -260,9 +252,4 @@ def create_user_notification(sender, instance, created, **kwargs):
UserNotificationPreference.objects.create(
user=instance,
property_change=False,
state_change=False,
comment=False,
mention=False,
issue_completed=False,
)
+61
View File
@@ -3,6 +3,8 @@ from django.conf import settings
from django.db import models
# Module import
from .base import BaseModel
from .project import ProjectBaseModel
from .workspace import WorkspaceBaseModel
from plane.utils.issue_filters import issue_filters
@@ -50,6 +52,41 @@ def get_default_display_properties():
"updated_on": True,
}
# DEPRECATED TODO: - Remove in next release
class GlobalView(BaseModel):
workspace = models.ForeignKey(
"db.Workspace", on_delete=models.CASCADE, related_name="global_views"
)
name = models.CharField(max_length=255, verbose_name="View Name")
description = models.TextField(verbose_name="View Description", blank=True)
query = models.JSONField(verbose_name="View Query")
access = models.PositiveSmallIntegerField(
default=1, choices=((0, "Private"), (1, "Public"))
)
query_data = models.JSONField(default=dict)
sort_order = models.FloatField(default=65535)
logo_props = models.JSONField(default=dict)
class Meta:
verbose_name = "Global View"
verbose_name_plural = "Global Views"
db_table = "global_views"
ordering = ("-created_at",)
def save(self, *args, **kwargs):
if self._state.adding:
largest_sort_order = GlobalView.objects.filter(
workspace=self.workspace
).aggregate(largest=models.Max("sort_order"))["largest"]
if largest_sort_order is not None:
self.sort_order = largest_sort_order + 10000
super(GlobalView, self).save(*args, **kwargs)
def __str__(self):
"""Return name of the View"""
return f"{self.name} <{self.workspace.name}>"
class IssueView(WorkspaceBaseModel):
name = models.CharField(max_length=255, verbose_name="View Name")
@@ -72,6 +109,7 @@ class IssueView(WorkspaceBaseModel):
)
is_locked = models.BooleanField(default=False)
class Meta:
verbose_name = "Issue View"
verbose_name_plural = "Issue Views"
@@ -101,3 +139,26 @@ class IssueView(WorkspaceBaseModel):
def __str__(self):
"""Return name of the View"""
return f"{self.name} <{self.project.name}>"
# DEPRECATED TODO: - Remove in next release
class IssueViewFavorite(ProjectBaseModel):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="user_view_favorites",
)
view = models.ForeignKey(
"db.IssueView", on_delete=models.CASCADE, related_name="view_favorites"
)
class Meta:
unique_together = ["view", "user"]
verbose_name = "View Favorite"
verbose_name_plural = "View Favorites"
db_table = "view_favorites"
ordering = ("-created_at",)
def __str__(self):
"""Return user and the view"""
return f"{self.user.email} <{self.view.name}>"
-8
View File
@@ -1,6 +1,3 @@
# Python imports
import pytz
# Django imports
from django.conf import settings
from django.core.exceptions import ValidationError
@@ -120,8 +117,6 @@ def slug_validator(value):
class Workspace(BaseModel):
TIMEZONE_CHOICES = tuple(zip(pytz.all_timezones, pytz.all_timezones))
name = models.CharField(max_length=80, verbose_name="Workspace Name")
logo = models.TextField(verbose_name="Logo", blank=True, null=True)
logo_asset = models.ForeignKey(
@@ -145,9 +140,6 @@ class Workspace(BaseModel):
],
)
organization_size = models.CharField(max_length=20, blank=True, null=True)
timezone = models.CharField(
max_length=255, default="UTC", choices=TIMEZONE_CHOICES
)
def __str__(self):
"""Return name of the Workspace"""
@@ -18,5 +18,3 @@ from .admin import (
InstanceAdminSignOutEndpoint,
InstanceAdminUserSessionEndpoint,
)
from .changelog import ChangeLogEndpoint
@@ -1,35 +0,0 @@
# Python imports
import requests
# Django imports
from django.conf import settings
# Third party imports
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import AllowAny
# plane imports
from .base import BaseAPIView
class ChangeLogEndpoint(BaseAPIView):
permission_classes = [
AllowAny,
]
def fetch_change_logs(self):
response = requests.get(settings.INSTANCE_CHANGELOG_URL)
response.raise_for_status()
return response.json()
def get(self, request):
# Fetch the changelog
if settings.INSTANCE_CHANGELOG_URL:
data = self.fetch_change_logs()
return Response(data, status=status.HTTP_200_OK)
else:
return Response(
{"error": "could not fetch changelog please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
@@ -177,8 +177,6 @@ class InstanceEndpoint(BaseAPIView):
data["space_base_url"] = settings.SPACE_BASE_URL
data["app_base_url"] = settings.APP_BASE_URL
data["instance_changelog_url"] = settings.INSTANCE_CHANGELOG_URL
instance_data = serializer.data
instance_data["workspaces_exist"] = Workspace.objects.count() >= 1
-6
View File
@@ -11,7 +11,6 @@ from plane.license.api.views import (
InstanceAdminUserMeEndpoint,
InstanceAdminSignOutEndpoint,
InstanceAdminUserSessionEndpoint,
ChangeLogEndpoint
)
urlpatterns = [
@@ -20,11 +19,6 @@ urlpatterns = [
InstanceEndpoint.as_view(),
name="instance",
),
path(
"changelog/",
ChangeLogEndpoint.as_view(),
name="instance-changelog",
),
path(
"admins/",
InstanceAdminEndpoint.as_view(),
-4
View File
@@ -384,9 +384,6 @@ APP_BASE_URL = os.environ.get("APP_BASE_URL")
HARD_DELETE_AFTER_DAYS = int(os.environ.get("HARD_DELETE_AFTER_DAYS", 60))
# Instance Changelog URL
INSTANCE_CHANGELOG_URL = os.environ.get("INSTANCE_CHANGELOG_URL", "")
ATTACHMENT_MIME_TYPES = [
# Images
"image/jpeg",
@@ -442,6 +439,5 @@ ATTACHMENT_MIME_TYPES = [
"text/javascript",
"application/json",
"text/xml",
"text/csv",
"application/xml",
]
+1 -5
View File
@@ -39,11 +39,7 @@ class S3Storage(S3Boto3Storage):
aws_access_key_id=self.aws_access_key_id,
aws_secret_access_key=self.aws_secret_access_key,
region_name=self.aws_region,
endpoint_url=(
f"{request.scheme}://{request.get_host()}"
if request
else self.aws_s3_endpoint_url
),
endpoint_url=f"{request.scheme}://{request.get_host()}",
config=boto3.session.Config(signature_version="s3v4"),
)
else:
+2 -10
View File
@@ -35,16 +35,8 @@ def issue_queryset_grouper(queryset, group_by, sub_group_by):
}
annotations_map = {
"assignee_ids": (
"assignees__id",
~Q(assignees__id__isnull=True)
& Q(issue_assignee__deleted_at__isnull=True),
),
"label_ids": (
"labels__id",
~Q(labels__id__isnull=True)
& Q(label_issue__deleted_at__isnull=True),
),
"assignee_ids": ("assignees__id", ~Q(assignees__id__isnull=True)),
"label_ids": ("labels__id", ~Q(labels__id__isnull=True)),
"module_ids": (
"issue_module__module_id",
~Q(issue_module__module_id__isnull=True),
+8 -6
View File
@@ -15,7 +15,6 @@ from rest_framework.response import Response
from .base import BaseAPIView
from plane.db.models import DeployBoard, FileAsset
from plane.settings.storage import S3Storage
from plane.bgtasks.storage_metadata_task import get_asset_object_metadata
class EntityAssetEndpoint(BaseAPIView):
@@ -160,16 +159,19 @@ class EntityAssetEndpoint(BaseAPIView):
# get the asset id
asset = FileAsset.objects.get(id=pk, workspace=deploy_board.workspace)
storage = S3Storage(request=request)
# get the storage metadata
asset.is_uploaded = True
# get the storage metadata
if not asset.storage_metadata:
get_asset_object_metadata.delay(str(asset.id))
if asset.storage_metadata is None:
asset.storage_metadata = storage.get_object_metadata(
object_name=asset.asset.name
)
# update the attributes
asset.attributes = request.data.get("attributes", asset.attributes)
# save the asset
asset.save(update_fields=["attributes", "is_uploaded"])
asset.save()
return Response(status=status.HTTP_204_NO_CONTENT)
def delete(self, request, anchor, pk):
@@ -193,7 +195,7 @@ class EntityAssetEndpoint(BaseAPIView):
asset.is_deleted = True
asset.deleted_at = timezone.now()
# Save the asset
asset.save(update_fields=["is_deleted", "deleted_at"])
asset.save()
return Response(status=status.HTTP_204_NO_CONTENT)
@@ -218,7 +220,7 @@ class AssetRestoreEndpoint(BaseAPIView):
)
asset.is_deleted = False
asset.deleted_at = None
asset.save(update_fields=["is_deleted", "deleted_at"])
asset.save()
return Response(status=status.HTTP_204_NO_CONTENT)
+6 -28
View File
@@ -20,7 +20,6 @@ from django.db.models import (
OuterRef,
Func,
CharField,
Subquery,
)
from django.db.models.functions import Concat
@@ -63,7 +62,6 @@ from plane.db.models import (
IssueVote,
ProjectPublicMember,
FileAsset,
CycleIssue,
)
from plane.bgtasks.issue_activities_task import issue_activity
from plane.utils.issue_filters import issue_filters
@@ -108,13 +106,7 @@ class ProjectIssuesPublicEndpoint(BaseAPIView):
queryset=IssueVote.objects.select_related("actor"),
)
)
.annotate(
cycle_id=Subquery(
CycleIssue.objects.filter(
issue=OuterRef("id"), deleted_at__isnull=True
).values("cycle_id")[:1]
)
)
.annotate(cycle_id=F("issue_cycle__cycle_id"))
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
@@ -703,22 +695,13 @@ class IssueRetrievePublicEndpoint(BaseAPIView):
)
.select_related("workspace", "project", "state", "parent")
.prefetch_related("assignees", "labels", "issue_module__module")
.annotate(
cycle_id=Subquery(
CycleIssue.objects.filter(
issue=OuterRef("id"), deleted_at__isnull=True
).values("cycle_id")[:1]
)
)
.annotate(cycle_id=F("issue_cycle__cycle_id"))
.annotate(
label_ids=Coalesce(
ArrayAgg(
"labels__id",
distinct=True,
filter=Q(
~Q(labels__id__isnull=True)
& Q(label_issue__deleted_at__isnull=True),
),
filter=~Q(labels__id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -726,11 +709,8 @@ class IssueRetrievePublicEndpoint(BaseAPIView):
ArrayAgg(
"assignees__id",
distinct=True,
filter=Q(
~Q(assignees__id__isnull=True)
& Q(assignees__member_project__is_active=True)
& Q(issue_assignee__deleted_at__isnull=True)
),
filter=~Q(assignees__id__isnull=True)
& Q(assignees__member_project__is_active=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
@@ -738,9 +718,7 @@ class IssueRetrievePublicEndpoint(BaseAPIView):
ArrayAgg(
"issue_module__module_id",
distinct=True,
filter=~Q(issue_module__module_id__isnull=True)
& Q(issue_module__module__archived_at__isnull=True)
& Q(issue_module__deleted_at__isnull=True),
filter=~Q(issue_module__module_id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
),
+2 -8
View File
@@ -138,11 +138,10 @@ def burndown_plot(
estimate__type="points",
).exists()
if estimate_type and plot_type == "points" and cycle_id:
issue_estimates = Issue.issue_objects.filter(
issue_estimates = Issue.objects.filter(
workspace__slug=slug,
project_id=project_id,
issue_cycle__cycle_id=cycle_id,
issue_cycle__deleted_at__isnull=True,
estimate_point__isnull=False,
).values_list("estimate_point__value", flat=True)
@@ -150,11 +149,10 @@ def burndown_plot(
total_estimate_points = sum(issue_estimates)
if estimate_type and plot_type == "points" and module_id:
issue_estimates = Issue.issue_objects.filter(
issue_estimates = Issue.objects.filter(
workspace__slug=slug,
project_id=project_id,
issue_module__module_id=module_id,
issue_module__deleted_at__isnull=True,
estimate_point__isnull=False,
).values_list("estimate_point__value", flat=True)
@@ -181,7 +179,6 @@ def burndown_plot(
workspace__slug=slug,
project_id=project_id,
issue_cycle__cycle_id=cycle_id,
issue_cycle__deleted_at__isnull=True,
estimate_point__isnull=False,
)
.annotate(date=TruncDate("completed_at"))
@@ -195,7 +192,6 @@ def burndown_plot(
workspace__slug=slug,
project_id=project_id,
issue_cycle__cycle_id=cycle_id,
issue_cycle__deleted_at__isnull=True,
)
.annotate(date=TruncDate("completed_at"))
.values("date")
@@ -221,7 +217,6 @@ def burndown_plot(
workspace__slug=slug,
project_id=project_id,
issue_module__module_id=module_id,
issue_module__deleted_at__isnull=True,
estimate_point__isnull=False,
)
.annotate(date=TruncDate("completed_at"))
@@ -235,7 +230,6 @@ def burndown_plot(
workspace__slug=slug,
project_id=project_id,
issue_module__module_id=module_id,
issue_module__deleted_at__isnull=True,
)
.annotate(date=TruncDate("completed_at"))
.values("date")
+2 -11
View File
@@ -25,22 +25,13 @@ def issue_queryset_grouper(queryset, group_by, sub_group_by):
}
annotations_map = {
"assignee_ids": (
"assignees__id",
~Q(assignees__id__isnull=True)
& Q(issue_assignee__deleted_at__isnull=True),
),
"label_ids": (
"labels__id",
~Q(labels__id__isnull=True)
& Q(label_issue__deleted_at__isnull=True),
),
"assignee_ids": ("assignees__id", ~Q(assignees__id__isnull=True)),
"label_ids": ("labels__id", ~Q(labels__id__isnull=True)),
"module_ids": (
"issue_module__module_id",
(
~Q(issue_module__module_id__isnull=True)
& Q(issue_module__module__archived_at__isnull=True)
& Q(issue_module__deleted_at__isnull=True)
),
),
}
+8 -22
View File
@@ -48,22 +48,14 @@ def string_date_filter(
if term == "weeks":
if subsequent == "after":
if offset == "fromnow":
issue_filter[f"{date_filter}__gte"] = now + timedelta(
weeks=duration
)
issue_filter[f"{date_filter}__gte"] = now + timedelta(weeks=duration)
else:
issue_filter[f"{date_filter}__gte"] = now - timedelta(
weeks=duration
)
issue_filter[f"{date_filter}__gte"] = now - timedelta(weeks=duration)
else:
if offset == "fromnow":
issue_filter[f"{date_filter}__lte"] = now + timedelta(
weeks=duration
)
issue_filter[f"{date_filter}__lte"] = now + timedelta(weeks=duration)
else:
issue_filter[f"{date_filter}__lte"] = now - timedelta(
weeks=duration
)
issue_filter[f"{date_filter}__lte"] = now - timedelta(weeks=duration)
def date_filter(issue_filter, date_term, queries):
@@ -128,9 +120,7 @@ def filter_state_group(params, issue_filter, method, prefix=""):
and len(params.get("state_group"))
and params.get("state_group") != "null"
):
issue_filter[f"{prefix}state__group__in"] = params.get(
"state_group"
)
issue_filter[f"{prefix}state__group__in"] = params.get("state_group")
return issue_filter
@@ -252,8 +242,8 @@ def filter_mentions(params, issue_filter, method, prefix=""):
and len(params.get("mentions"))
and params.get("mentions") != "null"
):
issue_filter[f"{prefix}issue_mention__mention__id__in"] = (
params.get("mentions")
issue_filter[f"{prefix}issue_mention__mention__id__in"] = params.get(
"mentions"
)
return issue_filter
@@ -421,10 +411,7 @@ def filter_cycle(params, issue_filter, method, prefix=""):
and len(params.get("cycle"))
and params.get("cycle") != "null"
):
issue_filter[f"{prefix}issue_cycle__cycle_id__in"] = params.get(
"cycle"
)
issue_filter[f"{prefix}issue_cycle__deleted_at__isnull"] = True
issue_filter[f"{prefix}issue_cycle__cycle_id__in"] = params.get("cycle")
return issue_filter
@@ -447,7 +434,6 @@ def filter_module(params, issue_filter, method, prefix=""):
issue_filter[f"{prefix}issue_module__module_id__in"] = params.get(
"module"
)
issue_filter[f"{prefix}issue_module__deleted_at__isnull"] = True
return issue_filter
@@ -1,24 +0,0 @@
def get_inverse_relation(relation_type):
relation_mapping = {
"start_after": "start_before",
"finish_after": "finish_before",
"blocked_by": "blocking",
"blocking": "blocked_by",
"start_before": "start_after",
"finish_before": "finish_after",
}
return relation_mapping.get(relation_type, relation_type)
def get_actual_relation(relation_type):
# This function is used to get the actual relation type which is store in database
actual_relation = {
"start_after": "start_before",
"finish_after": "finish_before",
"blocking": "blocked_by",
"blocked_by": "blocked_by",
"start_before": "start_before",
"finish_before": "finish_before",
}
return actual_relation.get(relation_type, relation_type)
+1 -2
View File
@@ -185,7 +185,6 @@ class OffsetPaginator:
class GroupedOffsetPaginator(OffsetPaginator):
# Field mappers - list m2m fields here
FIELD_MAPPER = {
"labels__id": "label_ids",
@@ -683,7 +682,7 @@ class SubGroupedOffsetPaginator(OffsetPaginator):
# for multi groups
result[
self.FIELD_MAPPER.get(self.sub_group_by_field_name)
] = ([] if "None" in sub_group_ids else sub_group_ids)
] = [] if "None" in sub_group_ids else sub_group_ids
# If a result belongs to multiple groups, add it to each group
processed_results[str(group_value)]["results"][
str(sub_group_value)
+1 -1
View File
@@ -34,7 +34,7 @@ x-app-env: &app-env
- SECRET_KEY=${SECRET_KEY:-60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5}
# DATA STORE SETTINGS
- USE_MINIO=${USE_MINIO:-1}
- AWS_REGION=${AWS_REGION:-}
- AWS_REGION=${AWS_REGION:-""}
- AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-"access-key"}
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-"secret-key"}
- AWS_S3_ENDPOINT_URL=${AWS_S3_ENDPOINT_URL:-http://plane-minio:9000}
+24 -16
View File
@@ -1,26 +1,28 @@
// Third-party libraries
import { Redis } from "ioredis";
// Hocuspocus extensions and core
import { Database } from "@hocuspocus/extension-database";
import { Extension } from "@hocuspocus/server";
import { Logger } from "@hocuspocus/extension-logger";
import { Redis as HocusPocusRedis } from "@hocuspocus/extension-redis";
// core helpers and utilities
// Core helpers and utilities
import { manualLogger } from "@/core/helpers/logger.js";
import { getRedisUrl } from "@/core/lib/utils/redis-url.js";
// core libraries
// Core libraries
import {
fetchPageDescriptionBinary,
updatePageDescription,
} from "@/core/lib/page.js";
// plane live libraries
// Core types
import { TDocumentTypes } from "@/core/types/common.js";
// Plane live libraries
import { fetchDocument } from "@/plane-live/lib/fetch-document.js";
import { updateDocument } from "@/plane-live/lib/update-document.js";
// types
import {
type HocusPocusServerContext,
type TDocumentTypes,
} from "@/core/types/common.js";
export const getExtensions: () => Promise<Extension[]> = async () => {
const extensions: Extension[] = [
@@ -31,8 +33,13 @@ export const getExtensions: () => Promise<Extension[]> = async () => {
},
}),
new Database({
fetch: async ({ context, documentName: pageId, requestParameters }) => {
const cookie = (context as HocusPocusServerContext).cookie;
fetch: async ({
documentName: pageId,
requestHeaders,
requestParameters,
}) => {
// request headers
const cookie = requestHeaders.cookie?.toString();
// query params
const params = requestParameters;
const documentType = params.get("documentType")?.toString() as
@@ -47,7 +54,7 @@ export const getExtensions: () => Promise<Extension[]> = async () => {
fetchedData = await fetchPageDescriptionBinary(
params,
pageId,
cookie,
cookie
);
} else {
fetchedData = await fetchDocument({
@@ -64,12 +71,13 @@ export const getExtensions: () => Promise<Extension[]> = async () => {
});
},
store: async ({
context,
state,
documentName: pageId,
requestHeaders,
requestParameters,
}) => {
const cookie = (context as HocusPocusServerContext).cookie;
// request headers
const cookie = requestHeaders.cookie?.toString();
// query params
const params = requestParameters;
const documentType = params.get("documentType")?.toString() as
@@ -116,7 +124,7 @@ export const getExtensions: () => Promise<Extension[]> = async () => {
}
manualLogger.warn(
`Redis Client wasn't able to connect, continuing without Redis (you won't be able to sync data between multiple plane live servers)`,
error,
error
);
reject(error);
});
@@ -130,12 +138,12 @@ export const getExtensions: () => Promise<Extension[]> = async () => {
} catch (error) {
manualLogger.warn(
`Redis Client wasn't able to connect, continuing without Redis (you won't be able to sync data between multiple plane live servers)`,
error,
error
);
}
} else {
manualLogger.warn(
"Redis URL is not set, continuing without Redis (you won't be able to sync data between multiple plane live servers)",
"Redis URL is not set, continuing without Redis (you won't be able to sync data between multiple plane live servers)"
);
}
+5 -29
View File
@@ -4,10 +4,6 @@ import { v4 as uuidv4 } from "uuid";
import { handleAuthentication } from "@/core/lib/authentication.js";
// extensions
import { getExtensions } from "@/core/extensions/index.js";
// editor types
import { TUserDetails } from "@plane/editor";
// types
import { type HocusPocusServerContext } from "@/core/types/common.js";
export const getHocusPocusServer = async () => {
const extensions = await getExtensions();
@@ -16,40 +12,20 @@ export const getHocusPocusServer = async () => {
name: serverName,
onAuthenticate: async ({
requestHeaders,
context,
// user id used as token for authentication
token,
}) => {
let cookie: string | undefined = undefined;
let userId: string | undefined = undefined;
// request headers
const cookie = requestHeaders.cookie?.toString();
// Extract cookie (fallback to request headers) and userId from token (for scenarios where
// the cookies are not passed in the request headers)
try {
const parsedToken = JSON.parse(token) as TUserDetails;
userId = parsedToken.id;
cookie = parsedToken.cookie;
} catch (error) {
// If token parsing fails, fallback to request headers
console.error("Token parsing failed, using request headers:", error);
} finally {
// If cookie is still not found, fallback to request headers
if (!cookie) {
cookie = requestHeaders.cookie?.toString();
}
if (!cookie) {
throw Error("Credentials not provided");
}
if (!cookie || !userId) {
throw new Error("Credentials not provided");
}
// set cookie in context, so it can be used throughout the ws connection
(context as HocusPocusServerContext).cookie = cookie;
try {
await handleAuthentication({
cookie,
userId,
token,
});
} catch (error) {
throw Error("Authentication unsuccessful!");
+3 -3
View File
@@ -7,11 +7,11 @@ const userService = new UserService();
type Props = {
cookie: string;
userId: string;
token: string;
};
export const handleAuthentication = async (props: Props) => {
const { cookie, userId } = props;
const { cookie, token } = props;
// fetch current user info
let response;
try {
@@ -20,7 +20,7 @@ export const handleAuthentication = async (props: Props) => {
manualLogger.error("Failed to fetch current user:", error);
throw error;
}
if (response.id !== userId) {
if (response.id !== token) {
throw Error("Authentication failed: Token doesn't match the current user.");
}
-4
View File
@@ -2,7 +2,3 @@
import { TAdditionalDocumentTypes } from "@/plane-live/types/common.js";
export type TDocumentTypes = "project_page" | TAdditionalDocumentTypes;
export type HocusPocusServerContext = {
cookie: string;
};
+2 -2
View File
@@ -60,12 +60,12 @@ http {
proxy_pass http://space:3002/spaces/;
}
location /${BUCKET_NAME} {
location /${BUCKET_NAME}/ {
proxy_http_version 1.1;
proxy_set_header Upgrade ${dollar}http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host ${dollar}http_host;
proxy_pass http://plane-minio:9000/${BUCKET_NAME};
proxy_pass http://plane-minio:9000/uploads/;
}
}
}
+2 -2
View File
@@ -68,12 +68,12 @@ http {
proxy_pass http://space:3000/spaces/;
}
location /${BUCKET_NAME} {
location /${BUCKET_NAME}/ {
proxy_http_version 1.1;
proxy_set_header Upgrade ${dollar}http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host ${dollar}http_host;
proxy_pass http://plane-minio:9000/${BUCKET_NAME};
proxy_pass http://plane-minio:9000/uploads/;
}
}
}
+3
View File
@@ -24,6 +24,9 @@
"prettier-plugin-tailwindcss": "^0.5.4",
"turbo": "^2.1.1"
},
"resolutions": {
"@types/react": "18.2.48"
},
"packageManager": "yarn@1.22.22",
"name": "plane"
}
+5 -7
View File
@@ -30,13 +30,13 @@
"format": "prettier --write \"**/*.{ts,tsx,md}\""
},
"peerDependencies": {
"react": "^18.3.1",
"react-dom": "18.3.1"
"next": "12.3.2",
"react": "^18.2.0",
"react-dom": "18.2.0"
},
"dependencies": {
"@floating-ui/react": "^0.26.4",
"@hocuspocus/provider": "^2.13.5",
"@plane/helpers": "*",
"@plane/ui": "*",
"@tiptap/core": "^2.1.13",
"@tiptap/extension-blockquote": "^2.1.13",
@@ -48,7 +48,6 @@
"@tiptap/extension-placeholder": "^2.3.0",
"@tiptap/extension-task-item": "^2.1.13",
"@tiptap/extension-task-list": "^2.1.13",
"@tiptap/extension-text-align": "^2.8.0",
"@tiptap/extension-text-style": "^2.7.1",
"@tiptap/extension-underline": "^2.1.13",
"@tiptap/pm": "^2.1.13",
@@ -63,7 +62,6 @@
"lowlight": "^3.0.0",
"lucide-react": "^0.378.0",
"prosemirror-codemark": "^0.4.2",
"prosemirror-utils": "^1.2.2",
"react-moveable": "^0.54.2",
"tailwind-merge": "^1.14.0",
"tippy.js": "^6.3.7",
@@ -78,8 +76,8 @@
"@plane/eslint-config": "*",
"@plane/typescript-config": "*",
"@types/node": "18.15.3",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.2.18",
"@types/react": "^18.2.42",
"@types/react-dom": "^18.2.17",
"postcss": "^8.4.38",
"tailwind-config-custom": "*",
"tsup": "^7.2.0",
@@ -14,8 +14,7 @@ type Props = {
};
export const DocumentEditorAdditionalExtensions = (_props: Props) => {
const { disabledExtensions } = _props;
const extensions: Extensions = disabledExtensions?.includes("slash-commands") ? [] : [SlashCommands()];
const extensions: Extensions = [SlashCommands()];
return extensions;
};
@@ -14,7 +14,6 @@ import { EditorRefApi, ICollaborativeDocumentEditor } from "@/types";
const CollaborativeDocumentEditor = (props: ICollaborativeDocumentEditor) => {
const {
onTransaction,
aiHandler,
containerClassName,
disabledExtensions,
@@ -44,7 +43,6 @@ const CollaborativeDocumentEditor = (props: ICollaborativeDocumentEditor) => {
// use document editor
const { editor, hasServerConnectionFailed, hasServerSynced } = useCollaborativeEditor({
onTransaction,
disabledExtensions,
editorClassName,
embedHandler,
@@ -28,9 +28,6 @@ export const EditorWrapper: React.FC<Props> = (props) => {
forwardedRef,
mentionHandler,
onChange,
onTransaction,
handleEditorReady,
autofocus,
placeholder,
tabIndex,
value,
@@ -46,9 +43,6 @@ export const EditorWrapper: React.FC<Props> = (props) => {
initialValue,
mentionHandler,
onChange,
onTransaction,
handleEditorReady,
autofocus,
placeholder,
tabIndex,
value,
@@ -1,4 +1,4 @@
import { forwardRef, useMemo } from "react";
import { forwardRef } from "react";
// components
import { EditorWrapper } from "@/components/editors/editor-wrapper";
// extensions
@@ -7,15 +7,9 @@ import { EnterKeyExtension } from "@/extensions";
import { EditorRefApi, ILiteTextEditor } from "@/types";
const LiteTextEditor = (props: ILiteTextEditor) => {
const { onEnterKeyPress, disabledExtensions, extensions: externalExtensions = [] } = props;
const { onEnterKeyPress } = props;
const extensions = useMemo(
() => [
...externalExtensions,
...(disabledExtensions?.includes("enter-key") ? [] : [EnterKeyExtension(onEnterKeyPress)]),
],
[externalExtensions, disabledExtensions, onEnterKeyPress]
);
const extensions = [EnterKeyExtension(onEnterKeyPress)];
return <EditorWrapper {...props} extensions={extensions} />;
};
@@ -8,31 +8,24 @@ import { SideMenuExtension, SlashCommands } from "@/extensions";
import { EditorRefApi, IRichTextEditor } from "@/types";
const RichTextEditor = (props: IRichTextEditor) => {
const {
disabledExtensions,
dragDropEnabled,
bubbleMenuEnabled = true,
extensions: externalExtensions = [],
} = props;
const { dragDropEnabled } = props;
const getExtensions = useCallback(() => {
const extensions = [
...externalExtensions,
const extensions = [SlashCommands()];
extensions.push(
SideMenuExtension({
aiEnabled: false,
dragDropEnabled: !!dragDropEnabled,
}),
];
if (!disabledExtensions?.includes("slash-commands")) {
extensions.push(SlashCommands());
}
})
);
return extensions;
}, [dragDropEnabled, disabledExtensions, externalExtensions]);
}, [dragDropEnabled]);
return (
<EditorWrapper {...props} extensions={getExtensions()}>
{(editor) => <>{editor && bubbleMenuEnabled && <EditorBubbleMenu editor={editor} />}</>}
{(editor) => <>{editor && <EditorBubbleMenu editor={editor} />}</>}
</EditorWrapper>
);
};
@@ -1,91 +0,0 @@
import { Editor } from "@tiptap/core";
import { AlignCenter, AlignLeft, AlignRight, LucideIcon } from "lucide-react";
// components
import { TextAlignItem } from "@/components/menus";
// helpers
import { cn } from "@/helpers/common";
// types
import { TEditorCommands } from "@/types";
type Props = {
editor: Editor;
onClose: () => void;
};
export const TextAlignmentSelector: React.FC<Props> = (props) => {
const { editor, onClose } = props;
const menuItem = TextAlignItem(editor);
const textAlignmentOptions: {
itemKey: TEditorCommands;
renderKey: string;
icon: LucideIcon;
command: () => void;
isActive: () => boolean;
}[] = [
{
itemKey: "text-align",
renderKey: "text-align-left",
icon: AlignLeft,
command: () =>
menuItem.command({
alignment: "left",
}),
isActive: () =>
menuItem.isActive({
alignment: "left",
}),
},
{
itemKey: "text-align",
renderKey: "text-align-center",
icon: AlignCenter,
command: () =>
menuItem.command({
alignment: "center",
}),
isActive: () =>
menuItem.isActive({
alignment: "center",
}),
},
{
itemKey: "text-align",
renderKey: "text-align-right",
icon: AlignRight,
command: () =>
menuItem.command({
alignment: "right",
}),
isActive: () =>
menuItem.isActive({
alignment: "right",
}),
},
];
return (
<div className="flex gap-0.5 px-2">
{textAlignmentOptions.map((item) => (
<button
key={item.renderKey}
type="button"
onClick={(e) => {
e.stopPropagation();
item.command();
onClose();
}}
className={cn(
"size-7 grid place-items-center rounded text-custom-text-300 hover:bg-custom-background-80 active:bg-custom-background-80 transition-colors",
{
"bg-custom-background-80 text-custom-text-100": item.isActive(),
}
)}
>
<item.icon className="size-4" />
</button>
))}
</div>
);
};

Some files were not shown because too many files have changed in this diff Show More