Skip to content

Commit

Permalink
Merge branch 'develop' of https://github.com/makeplane/plane into dev…
Browse files Browse the repository at this point in the history
…elop

# Conflicts:
#	web/components/headers/index.ts
#	web/components/workspace/index.ts
#	web/pages/[workspaceSlug]/active-cycles.tsx
  • Loading branch information
torbenraab committed Jan 28, 2024
2 parents f332da7 + 212f2b5 commit b418c7e
Show file tree
Hide file tree
Showing 350 changed files with 8,277 additions and 8,542 deletions.
1 change: 1 addition & 0 deletions apiserver/plane/app/serializers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
InboxSerializer,
InboxIssueSerializer,
IssueStateInboxSerializer,
InboxIssueLiteSerializer,
)

from .analytic import AnalyticViewSerializer
Expand Down
8 changes: 6 additions & 2 deletions apiserver/plane/app/serializers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ def _filter_fields(self, fields):
CycleIssueSerializer,
IssueFlatSerializer,
IssueRelationSerializer,
InboxIssueLiteSerializer
)

# Expansion mapper
Expand All @@ -80,9 +81,10 @@ def _filter_fields(self, fields):
"issue_cycle": CycleIssueSerializer,
"parent": IssueSerializer,
"issue_relation": IssueRelationSerializer,
"issue_inbox" : InboxIssueLiteSerializer,
}

self.fields[field] = expansion[field](many=True if field in ["members", "assignees", "labels", "issue_cycle", "issue_relation"] else False)
self.fields[field] = expansion[field](many=True if field in ["members", "assignees", "labels", "issue_cycle", "issue_relation", "issue_inbox"] else False)

return self.fields

Expand All @@ -103,6 +105,7 @@ def to_representation(self, instance):
LabelSerializer,
CycleIssueSerializer,
IssueRelationSerializer,
InboxIssueLiteSerializer
)

# Expansion mapper
Expand All @@ -122,7 +125,8 @@ def to_representation(self, instance):
"labels": LabelSerializer,
"issue_cycle": CycleIssueSerializer,
"parent": IssueSerializer,
"issue_relation": IssueRelationSerializer
"issue_relation": IssueRelationSerializer,
"issue_inbox" : InboxIssueLiteSerializer,
}
# Check if field in expansion then expand the field
if expand in expansion:
Expand Down
80 changes: 34 additions & 46 deletions apiserver/plane/app/views/inbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,39 +88,24 @@ class InboxIssueViewSet(BaseViewSet):
]

def get_queryset(self):
return self.filter_queryset(
super()
.get_queryset()
.filter(
Q(snoozed_till__gte=timezone.now())
| Q(snoozed_till__isnull=True),
workspace__slug=self.kwargs.get("slug"),
project_id=self.kwargs.get("project_id"),
inbox_id=self.kwargs.get("inbox_id"),
)
.select_related("issue", "workspace", "project")
)

def list(self, request, slug, project_id, inbox_id):
filters = issue_filters(request.query_params, "GET")
issues = (
return (
Issue.objects.filter(
issue_inbox__inbox_id=inbox_id,
workspace__slug=slug,
project_id=project_id,
project_id=self.kwargs.get("project_id"),
workspace__slug=self.kwargs.get("slug"),
issue_inbox__inbox_id=self.kwargs.get("inbox_id")
)
.filter(**filters)
.select_related("workspace", "project", "state", "parent")
.prefetch_related("assignees", "labels")
.order_by("issue_inbox__snoozed_till", "issue_inbox__status")
.annotate(
sub_issues_count=Issue.issue_objects.filter(
parent=OuterRef("id")
.prefetch_related("labels", "assignees")
.prefetch_related(
Prefetch(
"issue_inbox",
queryset=InboxIssue.objects.only(
"status", "duplicate_to", "snoozed_till", "source"
),
)
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(cycle_id=F("issue_cycle__cycle_id"))
.annotate(module_id=F("issue_module__module_id"))
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
Expand All @@ -135,16 +120,20 @@ def list(self, request, slug, project_id, inbox_id):
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.prefetch_related(
Prefetch(
"issue_inbox",
queryset=InboxIssue.objects.only(
"status", "duplicate_to", "snoozed_till", "source"
),
.annotate(
sub_issues_count=Issue.issue_objects.filter(
parent=OuterRef("id")
)
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
)
issues_data = IssueStateInboxSerializer(issues, many=True).data
).distinct()

def list(self, request, slug, project_id, inbox_id):
filters = issue_filters(request.query_params, "GET")
issue_queryset = self.get_queryset().filter(**filters).order_by("issue_inbox__snoozed_till", "issue_inbox__status")
issues_data = IssueSerializer(issue_queryset, expand=self.expand, many=True).data
return Response(
issues_data,
status=status.HTTP_200_OK,
Expand Down Expand Up @@ -211,7 +200,8 @@ def create(self, request, slug, project_id, inbox_id):
source=request.data.get("source", "in-app"),
)

serializer = IssueStateInboxSerializer(issue)
issue = (self.get_queryset().filter(pk=issue.id).first())
serializer = IssueSerializer(issue ,expand=self.expand)
return Response(serializer.data, status=status.HTTP_200_OK)

def partial_update(self, request, slug, project_id, inbox_id, issue_id):
Expand Down Expand Up @@ -331,22 +321,20 @@ def partial_update(self, request, slug, project_id, inbox_id, issue_id):
if state is not None:
issue.state = state
issue.save()

issue = (self.get_queryset().filter(pk=issue_id).first())
serializer = IssueSerializer(issue, expand=self.expand)
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(
serializer.errors, status=status.HTTP_400_BAD_REQUEST
)
else:
return Response(
InboxIssueSerializer(inbox_issue).data,
status=status.HTTP_200_OK,
)
issue = (self.get_queryset().filter(pk=issue_id).first())
serializer = IssueSerializer(issue ,expand=self.expand)
return Response(serializer.data, status=status.HTTP_200_OK)

def retrieve(self, request, slug, project_id, inbox_id, issue_id):
issue = Issue.objects.get(
pk=issue_id, workspace__slug=slug, project_id=project_id
)
serializer = IssueStateInboxSerializer(issue)
issue = self.get_queryset().filter(pk=issue_id).first()
serializer = IssueSerializer(issue, expand=self.expand,)
return Response(serializer.data, status=status.HTTP_200_OK)

def destroy(self, request, slug, project_id, inbox_id, issue_id):
Expand Down
16 changes: 8 additions & 8 deletions apiserver/plane/app/views/page.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,18 +158,18 @@ def archive(self, request, slug, project_id, page_id):
pk=page_id, workspace__slug=slug, project_id=project_id
)

# only the owner and admin can archive the page
# only the owner or admin can archive the page
if (
ProjectMember.objects.filter(
project_id=project_id,
member=request.user,
is_active=True,
role__gte=20,
role__lte=15,
).exists()
or request.user.id != page.owned_by_id
and request.user.id != page.owned_by_id
):
return Response(
{"error": "Only the owner and admin can archive the page"},
{"error": "Only the owner or admin can archive the page"},
status=status.HTTP_400_BAD_REQUEST,
)

Expand All @@ -182,18 +182,18 @@ def unarchive(self, request, slug, project_id, page_id):
pk=page_id, workspace__slug=slug, project_id=project_id
)

# only the owner and admin can un archive the page
# only the owner or admin can un archive the page
if (
ProjectMember.objects.filter(
project_id=project_id,
member=request.user,
is_active=True,
role__gt=20,
role__lte=15,
).exists()
or request.user.id != page.owned_by_id
and request.user.id != page.owned_by_id
):
return Response(
{"error": "Only the owner and admin can un archive the page"},
{"error": "Only the owner or admin can un archive the page"},
status=status.HTTP_400_BAD_REQUEST,
)

Expand Down
43 changes: 16 additions & 27 deletions apiserver/plane/app/views/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,19 +68,14 @@


class ProjectViewSet(WebhookMixin, BaseViewSet):
serializer_class = ProjectSerializer
serializer_class = ProjectListSerializer
model = Project
webhook_event = "project"

permission_classes = [
ProjectBasePermission,
]

def get_serializer_class(self, *args, **kwargs):
if self.action in ["update", "partial_update"]:
return ProjectSerializer
return ProjectDetailSerializer

def get_queryset(self):
return self.filter_queryset(
super()
Expand Down Expand Up @@ -690,6 +685,19 @@ def create(self, request, slug, project_id):
.order_by("sort_order")
)

bulk_project_members = []
member_roles = {member.get("member_id"): member.get("role") for member in members}
# Update roles in the members array based on the member_roles dictionary
for project_member in ProjectMember.objects.filter(project_id=project_id, member_id__in=[member.get("member_id") for member in members]):
project_member.role = member_roles[str(project_member.member_id)]
project_member.is_active = True
bulk_project_members.append(project_member)

# Update the roles of the existing members
ProjectMember.objects.bulk_update(
bulk_project_members, ["is_active", "role"], batch_size=100
)

for member in members:
sort_order = [
project_member.get("sort_order")
Expand All @@ -716,25 +724,6 @@ def create(self, request, slug, project_id):
)
)

# Check if the user is already a member of the project and is inactive
if ProjectMember.objects.filter(
workspace__slug=slug,
project_id=project_id,
member_id=member.get("member_id"),
is_active=False,
).exists():
member_detail = ProjectMember.objects.get(
workspace__slug=slug,
project_id=project_id,
member_id=member.get("member_id"),
is_active=False,
)
# Check if the user has not deactivated the account
user = User.objects.filter(pk=member.get("member_id")).first()
if user.is_active:
member_detail.is_active = True
member_detail.save(update_fields=["is_active"])

project_members = ProjectMember.objects.bulk_create(
bulk_project_members,
batch_size=10,
Expand All @@ -745,8 +734,8 @@ def create(self, request, slug, project_id):
bulk_issue_props, batch_size=10, ignore_conflicts=True
)

serializer = ProjectMemberSerializer(project_members, many=True)

project_members = ProjectMember.objects.filter(project_id=project_id, member_id__in=[member.get("member_id") for member in members])
serializer = ProjectMemberRoleSerializer(project_members, many=True)
return Response(serializer.data, status=status.HTTP_201_CREATED)

def list(self, request, slug, project_id):
Expand Down
45 changes: 19 additions & 26 deletions apiserver/plane/bgtasks/email_notification_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,30 +164,25 @@ def send_email_notification(
}
)
activity_time = changes.pop("activity_time")
template_data.append(
{
"actor_detail": {
"avatar_url": actor.avatar,
"first_name": actor.first_name,
"last_name": actor.last_name,
},
"changes": changes,
"issue_details": {
"name": issue.name,
"identifier": f"{issue.project.identifier}-{issue.sequence_id}",
},
"activity_time": str(activity_time),
}
)
# Parse the input string into a datetime object
formatted_time = datetime.strptime(activity_time, "%Y-%m-%d %H:%M:%S").strftime("%H:%M %p")

span = f"""<span style='
font-size: 1rem;
font-weight: 700;
line-height: 28px;
"
>
{template_data[0]['actor_detail']['first_name']} {template_data[0]['actor_detail']['last_name']}
</span>"""
if changes:
template_data.append(
{
"actor_detail": {
"avatar_url": actor.avatar,
"first_name": actor.first_name,
"last_name": actor.last_name,
},
"changes": changes,
"issue_details": {
"name": issue.name,
"identifier": f"{issue.project.identifier}-{issue.sequence_id}",
},
"activity_time": str(formatted_time),
}
)

summary = "updates were made to the issue by"

Expand All @@ -204,11 +199,10 @@ def send_email_notification(
"receiver": {
"email": receiver.email,
},
"issue_unsubscribe": f"{base_api}/{str(issue.project.workspace.slug)}/projects/{str(issue.project.id)}/issues/{str(issue.id)}",
"issue_url": f"{base_api}/{str(issue.project.workspace.slug)}/projects/{str(issue.project.id)}/issues/{str(issue.id)}",
"user_preference": f"{base_api}/profile/preferences/email",
"comments": comments,
}
print(json.dumps(context))
html_content = render_to_string(
"emails/notifications/issue-updates.html", context
)
Expand Down Expand Up @@ -236,7 +230,6 @@ def send_email_notification(
EmailNotificationLog.objects.filter(
pk__in=email_notification_ids
).update(sent_at=timezone.now())
print("Email Sent")
return
except Exception as e:
print(e)
Expand Down
2 changes: 1 addition & 1 deletion apiserver/plane/bgtasks/export_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def upload_to_s3(zip_file, workspace_id, token_id, slug):
)
# Create the new url with updated domain and protocol
presigned_url = presigned_url.replace(
"http://plane-minio:9000/uploads/",
f"{settings.AWS_S3_ENDPOINT_URL}/{settings.AWS_STORAGE_BUCKET_NAME}/",
f"{settings.AWS_S3_URL_PROTOCOL}//{settings.AWS_S3_CUSTOM_DOMAIN}/",
)
else:
Expand Down
6 changes: 5 additions & 1 deletion apiserver/plane/bgtasks/notification_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,11 @@ def notifications(
user_id=subscriber
)

for issue_activity in issue_activities_created:
for issue_activity in issue_activities_created:
# If activity done in blocking then blocked by email should not go
if issue_activity.get("issue_detail").get("id") != issue_id:
continue;

# Do not send notification for description update
if issue_activity.get("field") == "description":
continue
Expand Down
2 changes: 1 addition & 1 deletion apiserver/plane/celery.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
},
"check-every-five-minutes-to-send-email-notifications": {
"task": "plane.bgtasks.email_notification_task.stack_email_notification",
"schedule": crontab(minute='*/1')
"schedule": crontab(minute='*/5')
},
}

Expand Down
2 changes: 1 addition & 1 deletion apiserver/runtime.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
python-3.11.6
python-3.11.7
Loading

0 comments on commit b418c7e

Please sign in to comment.