chore: added external api for worklogs (#2337)

* chore: added external api for worklogs

* chore: project feature validation

* chore: changed the variable name

* chore: changed duration validation
This commit is contained in:
Bavisetti Narayan
2025-01-29 19:50:44 +05:30
committed by GitHub
parent 66ed9198aa
commit c9938b79ee
10 changed files with 231 additions and 7 deletions

View File

@@ -10,9 +10,7 @@ from .asset import urlpatterns as asset_patterns
from .issue_type import urlpatterns as issue_type_patterns
# ee imports
from plane.ee.urls.api.issue_property import urlpatterns as ee_issue_property_patterns
from plane.ee.urls.api.workspace import urlpatterns as ee_workspace_patterns
from plane.ee.urls.api.job import urlpatterns as ee_job_patterns
from plane.ee.urls.api import urlpatterns as ee_api_urls
urlpatterns = [
*project_patterns,
@@ -25,7 +23,5 @@ urlpatterns = [
*member_patterns,
*issue_type_patterns,
*asset_patterns,
*ee_issue_property_patterns,
*ee_workspace_patterns,
*ee_job_patterns
*ee_api_urls,
]

View File

@@ -93,3 +93,4 @@ from .api.job import ImportReportAPISerializer, ImportJobAPISerializer
from .api.workspace.credential import WorkspaceCredentialAPISerializer
from .api.workspace.connection import WorkspaceConnectionAPISerializer
from .api.workspace.entity_connection import WorkspaceEntityConnectionAPISerializer
from .api.worklog import IssueWorkLogAPISerializer

View File

@@ -4,3 +4,4 @@ from .issue_property import (
IssuePropertyValueAPISerializer,
IssuePropertyActivityAPISerializer,
)
from .worklog import IssueWorkLogAPISerializer

View File

@@ -0,0 +1,33 @@
# Module imports
from plane.app.serializers.base import BaseSerializer
from plane.ee.models import IssueWorkLog
# Third party imports
from rest_framework import serializers
class IssueWorkLogAPISerializer(BaseSerializer):
class Meta:
model = IssueWorkLog
fields = [
"id",
"created_at",
"updated_at",
"description",
"duration",
"created_by",
"updated_by",
"project_id",
"workspace_id",
"logged_by",
]
read_only_fields = ["logged_by", "workspace", "project"]
def validate(self, data):
if (
data.get("duration", None) is not None
and data.get("duration") < 0
):
raise serializers.ValidationError("Duration cannot be negative")
return data

View File

@@ -0,0 +1,12 @@
from .worklog import urlpatterns as worklog_patterns
from .issue_property import urlpatterns as issue_property_patterns
from .workspace import urlpatterns as workspace_patterns
from .job import urlpatterns as job_patterns
urlpatterns = [
*worklog_patterns,
*issue_property_patterns,
*workspace_patterns,
*job_patterns,
]

View File

@@ -0,0 +1,23 @@
# Django imports
from django.urls import path
# Module imports
from plane.ee.views import IssueWorklogAPIEndpoint, ProjectWorklogAPIEndpoint
urlpatterns = [
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/worklogs/",
IssueWorklogAPIEndpoint.as_view(),
name="worklogs",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/worklogs/<uuid:pk>/",
IssueWorklogAPIEndpoint.as_view(),
name="worklogs",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/total-worklogs/",
ProjectWorklogAPIEndpoint.as_view(),
name="project-worklogs",
),
]

View File

@@ -103,3 +103,8 @@ from plane.ee.views.app.job.report import ImportReportView
from plane.ee.views.api.job.base import ImportJobAPIView
from plane.ee.views.api.job.report import ImportReportAPIView
from plane.ee.views.api.worklog import (
IssueWorklogAPIEndpoint,
ProjectWorklogAPIEndpoint,
)

View File

@@ -7,4 +7,8 @@ from .issue_property import (
from plane.ee.views.api.workspace.credential import WorkspaceCredentialAPIView
from plane.ee.views.api.workspace.connection import WorkspaceConnectionAPIView
from plane.ee.views.api.workspace.entity_connection import WorkspaceEntityConnectionAPIView
from plane.ee.views.api.workspace.entity_connection import WorkspaceEntityConnectionAPIView
from plane.ee.views.api.worklog.issue_worklog import (
IssueWorklogAPIEndpoint,
ProjectWorklogAPIEndpoint,
)

View File

@@ -0,0 +1 @@
from .issue_worklog import IssueWorklogAPIEndpoint, ProjectWorklogAPIEndpoint

View File

@@ -0,0 +1,148 @@
# Django imports
from django.utils import timezone
from django.db.models import Sum
# Third Party imports
from rest_framework import status
from rest_framework.response import Response
# Module imports
from plane.ee.permissions import ProjectEntityPermission
from plane.db.models import Issue, Project
from plane.ee.models import IssueWorkLog
from plane.ee.views.base import BaseAPIView
from plane.payment.flags.flag import FeatureFlag
from plane.ee.serializers import IssueWorkLogAPISerializer
from plane.payment.flags.flag_decorator import check_feature_flag
from plane.app.permissions import allow_permission, ROLE
class IssueWorklogAPIEndpoint(BaseAPIView):
permission_classes = [ProjectEntityPermission]
@check_feature_flag(FeatureFlag.ISSUE_WORKLOG)
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
def post(self, request, slug, project_id, issue_id):
project_feature = Project.objects.filter(
workspace__slug=slug, pk=project_id
).first()
if not project_feature.is_time_tracking_enabled:
return Response(
{"message": "Worklog is not enabled for the project"},
status=status.HTTP_404_NOT_FOUND,
)
serializer = IssueWorkLogAPISerializer(data=request.data)
if serializer.is_valid():
serializer.save(
project_id=project_id, issue_id=issue_id, logged_by=request.user
)
# Get the issue to update
issue = Issue.objects.get(pk=issue_id)
issue.updated_at = timezone.now()
issue.save(update_fields=["updated_at"])
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@check_feature_flag(FeatureFlag.ISSUE_WORKLOG)
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
def get(self, request, slug, project_id, issue_id):
project_feature = Project.objects.filter(
workspace__slug=slug, pk=project_id
).first()
if not project_feature.is_time_tracking_enabled:
return Response(
{"message": "Worklog is not enabled for the project"},
status=status.HTTP_404_NOT_FOUND,
)
worklogs = IssueWorkLog.objects.filter(
issue_id=issue_id,
project_id=project_id,
workspace__slug=slug,
project__project_projectmember__member=request.user,
project__project_projectmember__is_active=True,
)
serializer = IssueWorkLogAPISerializer(worklogs, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
@check_feature_flag(FeatureFlag.ISSUE_WORKLOG)
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
def patch(self, request, slug, project_id, issue_id, pk):
project_feature = Project.objects.filter(
workspace__slug=slug, pk=project_id
).first()
if not project_feature.is_time_tracking_enabled:
return Response(
{"message": "Worklog is not enabled for the project"},
status=status.HTTP_404_NOT_FOUND,
)
worklog = IssueWorkLog.objects.get(
pk=pk, issue_id=issue_id, project_id=project_id, workspace__slug=slug
)
serializer = IssueWorkLogAPISerializer(worklog, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
# Get the issue to update
issue = Issue.objects.get(pk=issue_id)
issue.updated_at = timezone.now()
issue.save(update_fields=["updated_at"])
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@check_feature_flag(FeatureFlag.ISSUE_WORKLOG)
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
def delete(self, request, slug, project_id, issue_id, pk):
project_feature = Project.objects.filter(
workspace__slug=slug, pk=project_id
).first()
if not project_feature.is_time_tracking_enabled:
return Response(
{"message": "Worklog is not enabled for the project"},
status=status.HTTP_404_NOT_FOUND,
)
worklog = IssueWorkLog.objects.get(
pk=pk, issue_id=issue_id, project_id=project_id, workspace__slug=slug
)
worklog.delete()
# Get the issue to update
issue = Issue.objects.get(pk=issue_id)
issue.updated_at = timezone.now()
issue.save(update_fields=["updated_at"])
return Response(status=status.HTTP_204_NO_CONTENT)
class ProjectWorklogAPIEndpoint(BaseAPIView):
"""
ViewSet to fetch total worklog duration for each unique issue.
"""
@check_feature_flag(FeatureFlag.ISSUE_WORKLOG)
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
def get(self, request, slug, project_id):
project_feature = Project.objects.filter(
workspace__slug=slug, pk=project_id
).first()
if not project_feature.is_time_tracking_enabled:
return Response(
{"message": "Worklog is not enabled for the project"},
status=status.HTTP_404_NOT_FOUND,
)
worklog_data = (
IssueWorkLog.objects.filter(
project_id=project_id,
workspace__slug=slug,
project__project_projectmember__member=request.user,
project__project_projectmember__is_active=True,
)
.values("issue_id")
.annotate(duration=Sum("duration"))
)
return Response(worklog_data, status=status.HTTP_200_OK)