Skip to content

Commit 4d88dba

Browse files
release: v0.25.2 (#6736)
2 parents e61ff87 + b478e36 commit 4d88dba

File tree

143 files changed

+3538
-626
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

143 files changed

+3538
-626
lines changed

.env.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,9 @@ USE_MINIO=1
3838

3939
# Nginx Configuration
4040
NGINX_PORT=80
41+
42+
# Force HTTPS for handling SSL Termination
43+
MINIO_ENDPOINT_SSL=0
44+
45+
# API key rate limit
46+
API_KEY_RATE_LIMIT="60/minute"

admin/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "admin",
33
"description": "Admin UI for Plane",
4-
"version": "0.25.1",
4+
"version": "0.25.2",
55
"license": "AGPL-3.0",
66
"private": true,
77
"scripts": {

apiserver/.env.example

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,10 @@ APP_BASE_URL=
5959

6060

6161
# Hard delete files after days
62-
HARD_DELETE_AFTER_DAYS=60
62+
HARD_DELETE_AFTER_DAYS=60
63+
64+
# Force HTTPS for handling SSL Termination
65+
MINIO_ENDPOINT_SSL=0
66+
67+
# API key rate limit
68+
API_KEY_RATE_LIMIT="60/minute"

apiserver/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "plane-api",
3-
"version": "0.25.1",
3+
"version": "0.25.2",
44
"license": "AGPL-3.0",
55
"private": true,
66
"description": "API server powering Plane's backend"

apiserver/plane/api/rate_limit.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
# python imports
2+
import os
3+
4+
# Third party imports
15
from rest_framework.throttling import SimpleRateThrottle
26

37

48
class ApiKeyRateThrottle(SimpleRateThrottle):
59
scope = "api_key"
6-
rate = "60/minute"
10+
rate = os.environ.get("API_KEY_RATE_LIMIT", "60/minute")
711

812
def get_cache_key(self, request, view):
913
# Retrieve the API key from the request header

apiserver/plane/app/serializers/issue.py

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -111,25 +111,23 @@ def to_representation(self, instance):
111111
data["label_ids"] = label_ids if label_ids else []
112112
return data
113113

114-
def validate(self, data):
114+
def validate(self, attrs):
115115
if (
116-
data.get("start_date", None) is not None
117-
and data.get("target_date", None) is not None
118-
and data.get("start_date", None) > data.get("target_date", None)
116+
attrs.get("start_date", None) is not None
117+
and attrs.get("target_date", None) is not None
118+
and attrs.get("start_date", None) > attrs.get("target_date", None)
119119
):
120120
raise serializers.ValidationError("Start date cannot exceed target date")
121-
return data
122121

123-
def get_valid_assignees(self, assignees, project_id):
124-
if not assignees:
125-
return []
122+
if attrs.get("assignee_ids", []):
123+
attrs["assignee_ids"] = ProjectMember.objects.filter(
124+
project_id=self.context["project_id"],
125+
role__gte=15,
126+
is_active=True,
127+
member_id__in=attrs["assignee_ids"],
128+
).values_list("member_id", flat=True)
126129

127-
return ProjectMember.objects.filter(
128-
project_id=project_id,
129-
role__gte=15,
130-
is_active=True,
131-
member_id__in=assignees
132-
).values_list('member_id', flat=True)
130+
return attrs
133131

134132
def create(self, validated_data):
135133
assignees = validated_data.pop("assignee_ids", None)
@@ -146,33 +144,35 @@ def create(self, validated_data):
146144
created_by_id = issue.created_by_id
147145
updated_by_id = issue.updated_by_id
148146

149-
valid_assignee_ids = self.get_valid_assignees(assignees, project_id)
150-
if valid_assignee_ids is not None and len(valid_assignee_ids):
147+
if assignees is not None and len(assignees):
151148
try:
152149
IssueAssignee.objects.bulk_create(
153150
[
154151
IssueAssignee(
155-
assignee_id=user_id,
152+
assignee_id=assignee_id,
156153
issue=issue,
157154
project_id=project_id,
158155
workspace_id=workspace_id,
159156
created_by_id=created_by_id,
160157
updated_by_id=updated_by_id,
161158
)
162-
for user_id in valid_assignee_ids
159+
for assignee_id in assignees
163160
],
164161
batch_size=10,
165162
)
166163
except IntegrityError:
167164
pass
168165
else:
169166
# Then assign it to default assignee, if it is a valid assignee
170-
if default_assignee_id is not None and ProjectMember.objects.filter(
171-
member_id=default_assignee_id,
172-
project_id=project_id,
173-
role__gte=15,
174-
is_active=True
175-
).exists():
167+
if (
168+
default_assignee_id is not None
169+
and ProjectMember.objects.filter(
170+
member_id=default_assignee_id,
171+
project_id=project_id,
172+
role__gte=15,
173+
is_active=True,
174+
).exists()
175+
):
176176
try:
177177
IssueAssignee.objects.create(
178178
assignee_id=default_assignee_id,
@@ -216,21 +216,20 @@ def update(self, instance, validated_data):
216216
created_by_id = instance.created_by_id
217217
updated_by_id = instance.updated_by_id
218218

219-
valid_assignee_ids = self.get_valid_assignees(assignees, project_id)
220-
if valid_assignee_ids is not None:
219+
if assignees is not None:
221220
IssueAssignee.objects.filter(issue=instance).delete()
222221
try:
223222
IssueAssignee.objects.bulk_create(
224223
[
225224
IssueAssignee(
226-
assignee_id=user_id,
225+
assignee_id=assignee_id,
227226
issue=instance,
228227
project_id=project_id,
229228
workspace_id=workspace_id,
230229
created_by_id=created_by_id,
231230
updated_by_id=updated_by_id,
232231
)
233-
for user_id in valid_assignee_ids
232+
for assignee_id in assignees
234233
],
235234
batch_size=10,
236235
ignore_conflicts=True,

apiserver/plane/app/views/intake/base.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,9 @@ def list(self, request, slug, project_id):
178178
workspace__slug=slug, project_id=project_id
179179
).first()
180180
if not intake:
181-
return Response({"error": "Intake not found"}, status=status.HTTP_404_NOT_FOUND)
181+
return Response(
182+
{"error": "Intake not found"}, status=status.HTTP_404_NOT_FOUND
183+
)
182184

183185
project = Project.objects.get(pk=project_id)
184186
filters = issue_filters(request.GET, "GET", "issue__")
@@ -385,7 +387,7 @@ def partial_update(self, request, slug, project_id, pk):
385387
}
386388

387389
issue_serializer = IssueCreateSerializer(
388-
issue, data=issue_data, partial=True
390+
issue, data=issue_data, partial=True, context={"project_id": project_id}
389391
)
390392

391393
if issue_serializer.is_valid():

apiserver/plane/app/views/issue/base.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -635,7 +635,9 @@ def partial_update(self, request, slug, project_id, pk=None):
635635
)
636636

637637
requested_data = json.dumps(self.request.data, cls=DjangoJSONEncoder)
638-
serializer = IssueCreateSerializer(issue, data=request.data, partial=True)
638+
serializer = IssueCreateSerializer(
639+
issue, data=request.data, partial=True, context={"project_id": project_id}
640+
)
639641
if serializer.is_valid():
640642
serializer.save()
641643
issue_activity.delay(
@@ -1099,7 +1101,6 @@ def post(self, request, slug, project_id):
10991101

11001102

11011103
class IssueMetaEndpoint(BaseAPIView):
1102-
11031104
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="PROJECT")
11041105
def get(self, request, slug, project_id, issue_id):
11051106
issue = Issue.issue_objects.only("sequence_id", "project__identifier").get(
@@ -1115,14 +1116,12 @@ def get(self, request, slug, project_id, issue_id):
11151116

11161117

11171118
class IssueDetailIdentifierEndpoint(BaseAPIView):
1118-
11191119
def strict_str_to_int(self, s):
1120-
if not s.isdigit() and not (s.startswith('-') and s[1:].isdigit()):
1120+
if not s.isdigit() and not (s.startswith("-") and s[1:].isdigit()):
11211121
raise ValueError("Invalid integer string")
11221122
return int(s)
11231123

11241124
def get(self, request, slug, project_identifier, issue_identifier):
1125-
11261125
# Check if the issue identifier is a valid integer
11271126
try:
11281127
issue_identifier = self.strict_str_to_int(issue_identifier)
@@ -1134,8 +1133,7 @@ def get(self, request, slug, project_identifier, issue_identifier):
11341133

11351134
# Fetch the project
11361135
project = Project.objects.get(
1137-
identifier__iexact=project_identifier,
1138-
workspace__slug=slug,
1136+
identifier__iexact=project_identifier, workspace__slug=slug
11391137
)
11401138

11411139
# Check if the user is a member of the project
@@ -1237,8 +1235,8 @@ def get(self, request, slug, project_identifier, issue_identifier):
12371235
.annotate(
12381236
is_subscribed=Exists(
12391237
IssueSubscriber.objects.filter(
1240-
workspace__slug=slug,
1241-
project_id=project.id,
1238+
workspace__slug=slug,
1239+
project_id=project.id,
12421240
issue__sequence_id=issue_identifier,
12431241
subscriber=request.user,
12441242
)

apiserver/plane/settings/storage.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,20 @@ def __init__(self, request=None):
3232
) or os.environ.get("MINIO_ENDPOINT_URL")
3333

3434
if os.environ.get("USE_MINIO") == "1":
35+
36+
# Determine protocol based on environment variable
37+
if os.environ.get("MINIO_ENDPOINT_SSL") == "1":
38+
endpoint_protocol = "https"
39+
else:
40+
endpoint_protocol = request.scheme if request else "http"
3541
# Create an S3 client for MinIO
3642
self.s3_client = boto3.client(
3743
"s3",
3844
aws_access_key_id=self.aws_access_key_id,
3945
aws_secret_access_key=self.aws_secret_access_key,
4046
region_name=self.aws_region,
4147
endpoint_url=(
42-
f"{request.scheme}://{request.get_host()}"
48+
f"{endpoint_protocol}://{request.get_host()}"
4349
if request
4450
else self.aws_s3_endpoint_url
4551
),

apiserver/plane/space/views/intake.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
# Module imports
1414
from .base import BaseViewSet
15-
from plane.db.models import IntakeIssue, Issue, State, IssueLink, FileAsset, DeployBoard
15+
from plane.db.models import IntakeIssue, Issue, IssueLink, FileAsset, DeployBoard
1616
from plane.app.serializers import (
1717
IssueSerializer,
1818
IntakeIssueSerializer,
@@ -202,7 +202,12 @@ def partial_update(self, request, anchor, intake_id, pk):
202202
"description": issue_data.get("description", issue.description),
203203
}
204204

205-
issue_serializer = IssueCreateSerializer(issue, data=issue_data, partial=True)
205+
issue_serializer = IssueCreateSerializer(
206+
issue,
207+
data=issue_data,
208+
partial=True,
209+
context={"project_id": project_deploy_board.project_id},
210+
)
206211

207212
if issue_serializer.is_valid():
208213
current_instance = issue

apiserver/requirements/base.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# base requirements
22

33
# django
4-
Django==4.2.18
4+
Django==4.2.20
55
# rest framework
66
djangorestframework==3.15.2
77
# postgres

deploy/selfhost/docker-compose.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ x-app-env: &app-env
5050
DATABASE_URL: ${DATABASE_URL:-postgresql://plane:plane@plane-db/plane}
5151
SECRET_KEY: ${SECRET_KEY:-60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5}
5252
AMQP_URL: ${AMQP_URL:-amqp://plane:plane@plane-mq:5672/plane}
53+
API_KEY_RATE_LIMIT: ${API_KEY_RATE_LIMIT:-60/minute}
54+
MINIO_ENDPOINT_SSL: ${MINIO_ENDPOINT_SSL:-0}
5355

5456
services:
5557
web:

deploy/selfhost/variables.env

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,8 @@ GUNICORN_WORKERS=1
5858
# UNCOMMENT `DOCKER_PLATFORM` IF YOU ARE ON `ARM64` AND DOCKER IMAGE IS NOT AVAILABLE FOR RESPECTIVE `APP_RELEASE`
5959
# DOCKER_PLATFORM=linux/amd64
6060

61+
# Force HTTPS for handling SSL Termination
62+
MINIO_ENDPOINT_SSL=0
63+
64+
# API key rate limit
65+
API_KEY_RATE_LIMIT="60/minute"

live/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "live",
3-
"version": "0.25.1",
3+
"version": "0.25.2",
44
"license": "AGPL-3.0",
55
"description": "A realtime collaborative server powers Plane's rich text editor",
66
"main": "./src/server.ts",

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "plane",
33
"description": "Open-source project management that unlocks customer value",
44
"repository": "https://github.com/makeplane/plane.git",
5-
"version": "0.25.1",
5+
"version": "0.25.2",
66
"license": "AGPL-3.0",
77
"private": true,
88
"workspaces": [

packages/constants/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@plane/constants",
3-
"version": "0.25.1",
3+
"version": "0.25.2",
44
"private": true,
55
"main": "./src/index.ts",
66
"license": "AGPL-3.0"

0 commit comments

Comments
 (0)