Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3d555971c | ||
|
|
2161bf176b | ||
|
|
11258686ad |
@@ -33,14 +33,9 @@ jobs:
|
||||
deploy:
|
||||
- space/**
|
||||
|
||||
- name: Setup .npmrc for repository
|
||||
run: |
|
||||
echo -e "@tiptap-pro:registry=https://registry.tiptap.dev/\n//registry.tiptap.dev/:_authToken=${{ secrets.TIPTAP_TOKEN }}" > .npmrc
|
||||
|
||||
- name: Build Plane's Main App
|
||||
if: steps.changed-files.outputs.web_any_changed == 'true'
|
||||
run: |
|
||||
mv ./.npmrc ./web
|
||||
cd web
|
||||
yarn
|
||||
yarn build
|
||||
|
||||
@@ -22,10 +22,6 @@ jobs:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Setup .npmrc for repository
|
||||
run: |
|
||||
echo -e "@tiptap-pro:registry=https://registry.tiptap.dev/\n//registry.tiptap.dev/:_authToken=${{ secrets.TIPTAP_TOKEN }}" > .npmrc
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker (Docker Hub) from Github Release
|
||||
id: metaFrontend
|
||||
uses: docker/metadata-action@v4.3.0
|
||||
|
||||
@@ -59,17 +59,6 @@ chmod +x setup.sh
|
||||
|
||||
> If running in a cloud env replace localhost with public facing IP address of the VM
|
||||
|
||||
- Setup Tiptap Pro
|
||||
|
||||
Visit [Tiptap Pro](https://collab.tiptap.dev/pro-extensions) and signup (it is free).
|
||||
|
||||
Create a **`.npmrc`** file, copy the following and replace your registry token generated from Tiptap Pro.
|
||||
|
||||
```
|
||||
@tiptap-pro:registry=https://registry.tiptap.dev/
|
||||
//registry.tiptap.dev/:_authToken=YOUR_REGISTRY_TOKEN
|
||||
```
|
||||
|
||||
- Run Docker compose up
|
||||
|
||||
```bash
|
||||
|
||||
@@ -93,6 +93,7 @@ from plane.api.views import (
|
||||
IssueRelationViewSet,
|
||||
CommentReactionViewSet,
|
||||
IssueDraftViewSet,
|
||||
TransferProjectIssueEndpoint,
|
||||
## End Issues
|
||||
# States
|
||||
StateViewSet,
|
||||
@@ -856,6 +857,11 @@ urlpatterns = [
|
||||
ExportIssuesEndpoint.as_view(),
|
||||
name="export-issues",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/transfer-issues/",
|
||||
TransferProjectIssueEndpoint.as_view(),
|
||||
name="transfer-issues",
|
||||
),
|
||||
## End Issues
|
||||
## Issue Activity
|
||||
path(
|
||||
|
||||
@@ -90,6 +90,7 @@ from .issue import (
|
||||
IssueRetrievePublicEndpoint,
|
||||
ProjectIssuesPublicEndpoint,
|
||||
IssueDraftViewSet,
|
||||
TransferProjectIssueEndpoint,
|
||||
)
|
||||
|
||||
from .auth_extended import (
|
||||
|
||||
@@ -71,6 +71,8 @@ from plane.db.models import (
|
||||
IssueProperty,
|
||||
Label,
|
||||
IssueLink,
|
||||
IssueLabel,
|
||||
IssueAssignee,
|
||||
IssueAttachment,
|
||||
State,
|
||||
IssueSubscriber,
|
||||
@@ -81,6 +83,8 @@ from plane.db.models import (
|
||||
IssueVote,
|
||||
IssueRelation,
|
||||
ProjectPublicMember,
|
||||
CycleIssue,
|
||||
ModuleIssue,
|
||||
)
|
||||
from plane.bgtasks.issue_activites_task import issue_activity
|
||||
from plane.utils.grouper import group_results
|
||||
@@ -279,7 +283,8 @@ class IssueViewSet(BaseViewSet):
|
||||
|
||||
if group_by:
|
||||
return Response(
|
||||
group_results(issues, group_by, sub_group_by), status=status.HTTP_200_OK
|
||||
group_results(issues, group_by, sub_group_by),
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
return Response(issues, status=status.HTTP_200_OK)
|
||||
@@ -460,10 +465,11 @@ class UserWorkSpaceIssues(BaseAPIView):
|
||||
{"error": "Group by and sub group by cannot be same"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
||||
if group_by:
|
||||
return Response(
|
||||
group_results(issues, group_by, sub_group_by), status=status.HTTP_200_OK
|
||||
group_results(issues, group_by, sub_group_by),
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
return Response(issues, status=status.HTTP_200_OK)
|
||||
@@ -2114,7 +2120,7 @@ class IssueRelationViewSet(BaseViewSet):
|
||||
project_id=str(project_id),
|
||||
current_instance=None,
|
||||
)
|
||||
|
||||
|
||||
if relation == "blocking":
|
||||
return Response(
|
||||
RelatedIssueSerializer(issue_relation, many=True).data,
|
||||
@@ -2157,6 +2163,8 @@ class IssueRelationViewSet(BaseViewSet):
|
||||
.select_related("issue")
|
||||
.distinct()
|
||||
)
|
||||
|
||||
|
||||
class IssueRetrievePublicEndpoint(BaseAPIView):
|
||||
permission_classes = [
|
||||
AllowAny,
|
||||
@@ -2366,7 +2374,6 @@ class IssueDraftViewSet(BaseViewSet):
|
||||
serializer_class = IssueFlatSerializer
|
||||
model = Issue
|
||||
|
||||
|
||||
def perform_update(self, serializer):
|
||||
requested_data = json.dumps(self.request.data, cls=DjangoJSONEncoder)
|
||||
current_instance = (
|
||||
@@ -2385,7 +2392,6 @@ class IssueDraftViewSet(BaseViewSet):
|
||||
)
|
||||
|
||||
return super().perform_update(serializer)
|
||||
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
current_instance = (
|
||||
@@ -2406,7 +2412,6 @@ class IssueDraftViewSet(BaseViewSet):
|
||||
)
|
||||
return super().perform_destroy(instance)
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
Issue.objects.annotate(
|
||||
@@ -2432,7 +2437,6 @@ class IssueDraftViewSet(BaseViewSet):
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@method_decorator(gzip_page)
|
||||
def list(self, request, slug, project_id):
|
||||
try:
|
||||
@@ -2541,7 +2545,6 @@ class IssueDraftViewSet(BaseViewSet):
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
||||
def create(self, request, slug, project_id):
|
||||
try:
|
||||
project = Project.objects.get(pk=project_id)
|
||||
@@ -2575,7 +2578,6 @@ class IssueDraftViewSet(BaseViewSet):
|
||||
{"error": "Project was not found"}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
|
||||
|
||||
def retrieve(self, request, slug, project_id, pk=None):
|
||||
try:
|
||||
issue = Issue.objects.get(
|
||||
@@ -2586,4 +2588,109 @@ class IssueDraftViewSet(BaseViewSet):
|
||||
return Response(
|
||||
{"error": "Issue Does not exist"}, status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
|
||||
|
||||
|
||||
class TransferProjectIssueEndpoint(BaseAPIView):
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
|
||||
def post(self, request, slug, project_id):
|
||||
try:
|
||||
issue_ids = request.data.get("issue_ids", [])
|
||||
transfer_project_id = request.data.get("transfer_project_id", False)
|
||||
|
||||
if not issue_ids or not transfer_project_id:
|
||||
return Response(
|
||||
{"error": "Issue ids and transafer project id is required"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# The project that all issues need to be transfered
|
||||
transfer_project = Project.objects.get(
|
||||
workspace__slug=slug, pk=transfer_project_id
|
||||
)
|
||||
|
||||
# Get the default state of the new project
|
||||
default_state = State.objects.filter(
|
||||
workspace__slug=slug, project_id=transfer_project_id, default=True,
|
||||
).first()
|
||||
|
||||
# Fetch all the issues
|
||||
issues = Issue.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id, pk__in=issue_ids
|
||||
)
|
||||
|
||||
# Append all the issues
|
||||
bulk_issues = []
|
||||
for issue in issues:
|
||||
if str(issue.project_id) != str(transfer_project_id):
|
||||
issue.project_id = transfer_project_id
|
||||
if default_state is not None:
|
||||
issue.state = default_state
|
||||
bulk_issues.append(issue)
|
||||
|
||||
# Bulk update
|
||||
moved_issues_count = Issue.objects.bulk_update(
|
||||
bulk_issues, ["project_id", "state"], batch_size=100
|
||||
)
|
||||
|
||||
# Activity logs
|
||||
if moved_issues_count:
|
||||
[
|
||||
issue_activity.delay(
|
||||
type="issue.transfer.activity",
|
||||
issue_id=str(issue.id),
|
||||
requested_data=json.dumps({"old_project_id": str(project_id)}),
|
||||
current_instance=None,
|
||||
project_id=transfer_project_id,
|
||||
actor_id=request.user.id,
|
||||
)
|
||||
for issue in bulk_issues
|
||||
]
|
||||
|
||||
# Issue IDs
|
||||
issue_ids = [issue.id for issue in bulk_issues]
|
||||
|
||||
# Transfer attachments
|
||||
issue_attachments = IssueAttachment.objects.filter(issue_id__in=issue_ids, workspace__slug=slug, project_id=project_id)
|
||||
bulk_attachment = []
|
||||
for issue_attachment in issue_attachments:
|
||||
issue_attachment.project_id = transfer_project_id
|
||||
bulk_attachment.append(issue_attachment)
|
||||
|
||||
IssueAttachment.objects.bulk_update(bulk_attachment, ["project_id"], batch_size=100)
|
||||
|
||||
# Transfer Links
|
||||
issue_links = IssueLink.objects.filter(issue_id__in=issue_ids, workspace__slug=slug, project_id=project_id)
|
||||
bulk_links = []
|
||||
for issue_link in issue_links:
|
||||
issue_link.project_id = transfer_project_id
|
||||
bulk_links.append(issue_link)
|
||||
IssueLink.objects.bulk_update(issue_links, ["project_id"], batch_size=100)
|
||||
|
||||
# Delete all the other attached properties
|
||||
# Delete all the issue labels in the old project
|
||||
IssueLabel.objects.filter(issue_id__in=issue_ids, workspace__slug=slug, project_id=project_id).delete()
|
||||
# Delete assignees
|
||||
IssueAssignee.objects.filter(issue_id__in=issue_ids, workspace__slug=slug, project_id=project_id).delete()
|
||||
# Delete attached cycles
|
||||
CycleIssue.objects.filter(issue_id__in=issue_ids, workspace__slug=slug, project_id=project_id).delete()
|
||||
# Delete attached modules
|
||||
ModuleIssue.objects.filter(issue_id__in=issue_ids, workspace__slug=slug, project_id=project_id).delete()
|
||||
|
||||
return Response(
|
||||
{"message": f"{moved_issues_count} issue(s) moved to {transfer_project.name}"},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
except Project.DoesNotExist:
|
||||
return Response(
|
||||
{"error": "Transfer project does not exist"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
# except Exception as e:
|
||||
# capture_exception(e)
|
||||
# return Response(
|
||||
# {"error": "Something went wrong please try again later"},
|
||||
# status=status.HTTP_400_BAD_REQUEST,
|
||||
# )
|
||||
|
||||
@@ -1191,6 +1191,29 @@ def delete_draft_issue_activity(
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def transfer_issue_activity(requested_data, current_instance, issue_id, project, actor, issue_activities):
|
||||
|
||||
requested_data = json.loads(requested_data) if requested_data is not None else None
|
||||
|
||||
# Old project
|
||||
old_project = Project.objects.get(pk=requested_data.get("old_project_id"))
|
||||
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
issue_id=issue_id,
|
||||
verb="updated",
|
||||
project=project,
|
||||
workspace=project.workspace,
|
||||
comment=f"moved the issue",
|
||||
old_identifier=requested_data.get("old_project_id"),
|
||||
new_identifier=project.id,
|
||||
old_value=old_project.name,
|
||||
new_value=project.name,
|
||||
actor=actor,
|
||||
)
|
||||
)
|
||||
|
||||
# Receive message from room group
|
||||
@shared_task
|
||||
def issue_activity(
|
||||
@@ -1265,6 +1288,7 @@ def issue_activity(
|
||||
"issue_draft.activity.created": create_draft_issue_activity,
|
||||
"issue_draft.activity.updated": update_draft_issue_activity,
|
||||
"issue_draft.activity.deleted": delete_draft_issue_activity,
|
||||
"issue.transfer.activity": transfer_issue_activity,
|
||||
}
|
||||
|
||||
func = ACTIVITY_MAPPER.get(type)
|
||||
|
||||
@@ -10,15 +10,4 @@ cp ./space/.env.example ./space/.env
|
||||
cp ./apiserver/.env.example ./apiserver/.env
|
||||
|
||||
# Generate the SECRET_KEY that will be used by django
|
||||
echo -e "SECRET_KEY=\"$(tr -dc 'a-z0-9' < /dev/urandom | head -c50)\"" >> ./apiserver/.env
|
||||
|
||||
# Generate Prompt for taking tiptap auth key
|
||||
echo -e "\n\e[1;38m Instructions for generating TipTap Pro Extensions Auth Token \e[0m \n"
|
||||
|
||||
echo -e "\e[1;38m 1. Head over to TipTap cloud's Pro Extensions Page, https://collab.tiptap.dev/pro-extensions \e[0m"
|
||||
echo -e "\e[1;38m 2. Copy the token given to you under the first paragraph, after 'Here it is' \e[0m \n"
|
||||
|
||||
read -p $'\e[1;32m Please Enter Your TipTap Pro Extensions Authentication Token: \e[0m \e[1;36m' authToken
|
||||
|
||||
echo "@tiptap-pro:registry=https://registry.tiptap.dev/
|
||||
//registry.tiptap.dev/:_authToken=${authToken}" > .npmrc
|
||||
echo -e "SECRET_KEY=\"$(tr -dc 'a-z0-9' < /dev/urandom | head -c50)\"" >> ./apiserver/.env
|
||||
@@ -18,7 +18,6 @@ import Gapcursor from "@tiptap/extension-gapcursor";
|
||||
import ts from "highlight.js/lib/languages/typescript";
|
||||
|
||||
import "highlight.js/styles/github-dark.css";
|
||||
import UniqueID from "@tiptap-pro/extension-unique-id";
|
||||
import UpdatedImage from "./updated-image";
|
||||
import isValidHttpUrl from "../bubble-menu/utils/link-validator";
|
||||
import { CustomTableCell } from "./table/table-cell";
|
||||
@@ -121,9 +120,6 @@ export const TiptapExtensions = (
|
||||
},
|
||||
includeChildren: true,
|
||||
}),
|
||||
UniqueID.configure({
|
||||
types: ["image"],
|
||||
}),
|
||||
SlashCommand(workspaceSlug, setIsSubmitting),
|
||||
TiptapUnderline,
|
||||
TextStyle,
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
"@heroicons/react": "^2.0.12",
|
||||
"@mui/icons-material": "^5.14.1",
|
||||
"@mui/material": "^5.14.1",
|
||||
"@tiptap-pro/extension-unique-id": "^2.1.0",
|
||||
"@tiptap/extension-code-block-lowlight": "^2.0.4",
|
||||
"@tiptap/extension-color": "^2.0.4",
|
||||
"@tiptap/extension-gapcursor": "^2.1.7",
|
||||
|
||||
@@ -18,7 +18,6 @@ import Gapcursor from "@tiptap/extension-gapcursor";
|
||||
import ts from "highlight.js/lib/languages/typescript";
|
||||
|
||||
import "highlight.js/styles/github-dark.css";
|
||||
import UniqueID from "@tiptap-pro/extension-unique-id";
|
||||
import UpdatedImage from "./updated-image";
|
||||
import isValidHttpUrl from "../bubble-menu/utils/link-validator";
|
||||
import { CustomTableCell } from "./table/table-cell";
|
||||
@@ -121,9 +120,6 @@ export const TiptapExtensions = (
|
||||
},
|
||||
includeChildren: true,
|
||||
}),
|
||||
UniqueID.configure({
|
||||
types: ["image"],
|
||||
}),
|
||||
SlashCommand(workspaceSlug, setIsSubmitting),
|
||||
TiptapUnderline,
|
||||
TextStyle,
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
"@nivo/pie": "0.80.0",
|
||||
"@nivo/scatterplot": "0.80.0",
|
||||
"@sentry/nextjs": "^7.36.0",
|
||||
"@tiptap-pro/extension-unique-id": "^2.1.0",
|
||||
"@tiptap/extension-code-block-lowlight": "^2.0.4",
|
||||
"@tiptap/extension-color": "^2.0.4",
|
||||
"@tiptap/extension-gapcursor": "^2.1.7",
|
||||
|
||||
@@ -2174,13 +2174,6 @@
|
||||
lodash.merge "^4.6.2"
|
||||
postcss-selector-parser "6.0.10"
|
||||
|
||||
"@tiptap-pro/extension-unique-id@^2.1.0":
|
||||
version "2.2.3"
|
||||
resolved "https://registry.tiptap.dev/@tiptap-pro%2fextension-unique-id/-/extension-unique-id-2.2.3.tgz#151a570ef8363bf460bf5b08dc0581fb182ebabc"
|
||||
integrity sha512-Y1jM+6hebNltFZ+0fbC+NcOCU647KjRtqJ7jEZVFoP12ZMocZNqTTabsSIys0UXOkSaJy3/H6Z/ybygAHY/dBg==
|
||||
dependencies:
|
||||
uuid "^8.3.2"
|
||||
|
||||
"@tiptap/core@^2.1.8":
|
||||
version "2.1.8"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.1.8.tgz#4555dc7d86580dee790d4aded1ce7fb79319da70"
|
||||
@@ -8188,11 +8181,6 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
|
||||
|
||||
uuid@^8.3.2:
|
||||
version "8.3.2"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||
|
||||
uuid@^9.0.0:
|
||||
version "9.0.1"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30"
|
||||
|
||||
Reference in New Issue
Block a user