Skip to content
4 changes: 1 addition & 3 deletions apiserver/plane/api/serializers/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ class Meta:
"id",
"workspace",
"project",
"created_by",
"updated_by",
"updated_at",
]
Expand Down Expand Up @@ -338,9 +337,7 @@ class Meta:
"workspace",
"project",
"issue",
"created_by",
"updated_by",
"created_at",
"updated_at",
]

Expand Down Expand Up @@ -433,3 +430,4 @@ class Meta:
"created_at",
"updated_at",
]

6 changes: 3 additions & 3 deletions apiserver/plane/api/urls/member.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
from django.urls import path

from plane.api.views import (
WorkspaceMemberAPIEndpoint,
ProjectMemberAPIEndpoint,
)

urlpatterns = [
path(
"workspaces/<str:slug>/members/",
WorkspaceMemberAPIEndpoint.as_view(),
"workspaces/<str:slug>/projects/<str:project_id>/members/",
ProjectMemberAPIEndpoint.as_view(),
name="users",
),
]
3 changes: 2 additions & 1 deletion apiserver/plane/api/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
ModuleArchiveUnarchiveAPIEndpoint,
)

from .member import WorkspaceMemberAPIEndpoint
from .member import ProjectMemberAPIEndpoint

from .inbox import InboxIssueAPIEndpoint

106 changes: 60 additions & 46 deletions apiserver/plane/api/views/cycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -661,75 +661,89 @@ def post(self, request, slug, project_id, cycle_id):
workspace__slug=slug, project_id=project_id, pk=cycle_id
)

issues = Issue.objects.filter(
pk__in=issues, workspace__slug=slug, project_id=project_id
).values_list("id", flat=True)
if (
cycle.end_date is not None
and cycle.end_date < timezone.now().date()
):
return Response(
{
"error": "The Cycle has already been completed so no new issues can be added"
},
status=status.HTTP_400_BAD_REQUEST,
)

# Get all CycleIssues already created
cycle_issues = list(CycleIssue.objects.filter(issue_id__in=issues))
update_cycle_issue_activity = []
record_to_create = []
records_to_update = []

for issue in issues:
cycle_issue = [
cycle_issue
for cycle_issue in cycle_issues
if str(cycle_issue.issue_id) in issues
]
# Update only when cycle changes
if len(cycle_issue):
if cycle_issue[0].cycle_id != cycle_id:
update_cycle_issue_activity.append(
{
"old_cycle_id": str(cycle_issue[0].cycle_id),
"new_cycle_id": str(cycle_id),
"issue_id": str(cycle_issue[0].issue_id),
}
)
cycle_issue[0].cycle_id = cycle_id
records_to_update.append(cycle_issue[0])
else:
record_to_create.append(
CycleIssue(
project_id=project_id,
workspace=cycle.workspace,
created_by=request.user,
updated_by=request.user,
cycle=cycle,
issue_id=issue,
)
)
cycle_issues = list(
CycleIssue.objects.filter(
~Q(cycle_id=cycle_id), issue_id__in=issues
)
)

CycleIssue.objects.bulk_create(
record_to_create,
batch_size=10,
existing_issues = [
str(cycle_issue.issue_id)
for cycle_issue in cycle_issues
if str(cycle_issue.issue_id) in issues
]
new_issues = list(set(issues) - set(existing_issues))

# New issues to create
created_records = CycleIssue.objects.bulk_create(
[
CycleIssue(
project_id=project_id,
workspace_id=cycle.workspace_id,
cycle_id=cycle_id,
issue_id=issue,
)
for issue in new_issues
],
ignore_conflicts=True,
batch_size=10,
)

# Updated Issues
updated_records = []
update_cycle_issue_activity = []
# Iterate over each cycle_issue in cycle_issues
for cycle_issue in cycle_issues:
old_cycle_id = cycle_issue.cycle_id
# Update the cycle_issue's cycle_id
cycle_issue.cycle_id = cycle_id
# Add the modified cycle_issue to the records_to_update list
updated_records.append(cycle_issue)
# Record the update activity
update_cycle_issue_activity.append(
{
"old_cycle_id": str(old_cycle_id),
"new_cycle_id": str(cycle_id),
"issue_id": str(cycle_issue.issue_id),
}
)

# Update the cycle issues
CycleIssue.objects.bulk_update(
records_to_update,
["cycle"],
batch_size=10,
updated_records, ["cycle_id"], batch_size=100
)

# Capture Issue Activity
issue_activity.delay(
type="cycle.activity.created",
requested_data=json.dumps({"cycles_list": str(issues)}),
requested_data=json.dumps({"cycles_list": issues}),
actor_id=str(self.request.user.id),
issue_id=None,
project_id=str(self.kwargs.get("project_id", None)),
current_instance=json.dumps(
{
"updated_cycle_issues": update_cycle_issue_activity,
"created_cycle_issues": serializers.serialize(
"json", record_to_create
"json", created_records
),
}
),
epoch=int(timezone.now().timestamp()),
notification=True,
origin=request.META.get("HTTP_ORIGIN"),
)

# Return all Cycle Issues
return Response(
CycleIssueSerializer(self.get_queryset(), many=True).data,
Expand Down
48 changes: 44 additions & 4 deletions apiserver/plane/api/views/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,25 @@ def get_queryset(self):
).distinct()

def get(self, request, slug, project_id, pk=None):
external_id = request.GET.get("external_id")
external_source = request.GET.get("external_source")

if external_id and external_source:
issue = Issue.objects.get(
external_id=external_id,
external_source=external_source,
workspace__slug=slug,
project_id=project_id,
)
return Response(
IssueSerializer(
issue,
fields=self.fields,
expand=self.expand,
).data,
status=status.HTTP_200_OK,
)

if pk:
issue = Issue.issue_objects.annotate(
sub_issues_count=Issue.issue_objects.filter(
Expand Down Expand Up @@ -315,8 +334,11 @@ def post(self, request, slug, project_id):
project_id=project_id,
pk=serializer.data["id"],
).first()
issue.created_at = request.data.get("created_at")
issue.save(update_fields=["created_at"])
issue.created_at = request.data.get("created_at", timezone.now())
issue.created_by_id = request.data.get(
"created_by", request.user.id
)
issue.save(update_fields=["created_at", "created_by"])

# Track the issue
issue_activity.delay(
Expand Down Expand Up @@ -610,14 +632,20 @@ def post(self, request, slug, project_id, issue_id):
project_id=project_id,
issue_id=issue_id,
)

link = IssueLink.objects.get(pk=serializer.data["id"])
link.created_by_id = request.data.get(
"created_by", request.user.id
)
link.save(update_fields=["created_by"])
issue_activity.delay(
type="link.activity.created",
requested_data=json.dumps(
serializer.data, cls=DjangoJSONEncoder
),
actor_id=str(self.request.user.id),
issue_id=str(self.kwargs.get("issue_id")),
project_id=str(self.kwargs.get("project_id")),
actor_id=str(link.created_by_id),
current_instance=None,
epoch=int(timezone.now().timestamp()),
)
Expand Down Expand Up @@ -771,12 +799,24 @@ def post(self, request, slug, project_id, issue_id):
issue_id=issue_id,
actor=request.user,
)
issue_comment = IssueComment.objects.get(
pk=serializer.data.get("id")
)
# Update the created_at and the created_by and save the comment
issue_comment.created_at = request.data.get(
"created_at", timezone.now()
)
issue_comment.created_by_id = request.data.get(
"created_by", request.user.id
)
issue_comment.save(update_fields=["created_at", "created_by"])

issue_activity.delay(
type="comment.activity.created",
requested_data=json.dumps(
serializer.data, cls=DjangoJSONEncoder
),
actor_id=str(self.request.user.id),
actor_id=str(issue_comment.created_by_id),
issue_id=str(self.kwargs.get("issue_id")),
project_id=str(self.kwargs.get("project_id")),
current_instance=None,
Expand Down
28 changes: 17 additions & 11 deletions apiserver/plane/api/views/member.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,19 @@
ProjectMember,
)

from plane.app.permissions import (
ProjectMemberPermission,
)


# API endpoint to get and insert users inside the workspace
class WorkspaceMemberAPIEndpoint(BaseAPIView):
class ProjectMemberAPIEndpoint(BaseAPIView):
permission_classes = [
ProjectMemberPermission,
]

# Get all the users that are present inside the workspace
def get(self, request, slug):
def get(self, request, slug, project_id):
# Check if the workspace exists
if not Workspace.objects.filter(slug=slug).exists():
return Response(
Expand All @@ -34,29 +42,28 @@ def get(self, request, slug):
)

# Get the workspace members that are present inside the workspace
workspace_members = WorkspaceMember.objects.filter(
workspace__slug=slug
)
project_members = ProjectMember.objects.filter(
project_id=project_id, workspace__slug=slug
).values_list("member_id", flat=True)

# Get all the users that are present inside the workspace
users = UserLiteSerializer(
User.objects.filter(
id__in=workspace_members.values_list("member_id", flat=True)
id__in=project_members,
),
many=True,
).data

return Response(users, status=status.HTTP_200_OK)

# Insert a new user inside the workspace, and assign the user to the project
def post(self, request, slug):
def post(self, request, slug, project_id):
# Check if user with email already exists, and send bad request if it's
# not present, check for workspace and valid project mandat
# ------------------- Validation -------------------
if (
request.data.get("email") is None
or request.data.get("display_name") is None
or request.data.get("project_id") is None
):
return Response(
{
Expand All @@ -76,9 +83,7 @@ def post(self, request, slug):
)

workspace = Workspace.objects.filter(slug=slug).first()
project = Project.objects.filter(
pk=request.data.get("project_id")
).first()
project = Project.objects.filter(pk=project_id).first()

if not all([workspace, project]):
return Response(
Expand Down Expand Up @@ -145,3 +150,4 @@ def post(self, request, slug):
user_data = UserLiteSerializer(user).data

return Response(user_data, status=status.HTTP_201_CREATED)

4 changes: 3 additions & 1 deletion apiserver/plane/bgtasks/issue_activites_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,8 @@ def create_issue_activity(
epoch=epoch,
)
issue_activity.created_at = issue.created_at
issue_activity.save(update_fields=["created_at"])
issue_activity.actor_id = issue.created_by_id
issue_activity.save(update_fields=["created_at", "actor_id"])
requested_data = (
json.loads(requested_data) if requested_data is not None else None
)
Expand Down Expand Up @@ -1773,3 +1774,4 @@ def issue_activity(
except Exception as e:
log_exception(e)
return