mirror of
https://github.com/makeplane/plane.git
synced 2026-07-14 14:31:37 +02:00
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:
committed by
GitHub
parent
66ed9198aa
commit
c9938b79ee
@@ -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,
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,3 +4,4 @@ from .issue_property import (
|
||||
IssuePropertyValueAPISerializer,
|
||||
IssuePropertyActivityAPISerializer,
|
||||
)
|
||||
from .worklog import IssueWorkLogAPISerializer
|
||||
|
||||
33
apiserver/plane/ee/serializers/api/worklog.py
Normal file
33
apiserver/plane/ee/serializers/api/worklog.py
Normal 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
|
||||
12
apiserver/plane/ee/urls/api/__init__.py
Normal file
12
apiserver/plane/ee/urls/api/__init__.py
Normal 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,
|
||||
]
|
||||
23
apiserver/plane/ee/urls/api/worklog.py
Normal file
23
apiserver/plane/ee/urls/api/worklog.py
Normal 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",
|
||||
),
|
||||
]
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
1
apiserver/plane/ee/views/api/worklog/__init__.py
Normal file
1
apiserver/plane/ee/views/api/worklog/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .issue_worklog import IssueWorklogAPIEndpoint, ProjectWorklogAPIEndpoint
|
||||
148
apiserver/plane/ee/views/api/worklog/issue_worklog.py
Normal file
148
apiserver/plane/ee/views/api/worklog/issue_worklog.py
Normal 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)
|
||||
Reference in New Issue
Block a user