diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index e39f91f6f..6343c740e 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -49,7 +49,7 @@ USER root RUN apk --update --no-cache add "bash~=5.1" COPY ./bin ./bin/ -RUN chmod +x ./bin/channel-worker ./bin/takeoff ./bin/worker +RUN chmod +x ./bin/takeoff ./bin/worker USER captain diff --git a/apiserver/Procfile b/apiserver/Procfile index 026a3f953..35f6e9aa8 100644 --- a/apiserver/Procfile +++ b/apiserver/Procfile @@ -1,3 +1,2 @@ web: gunicorn -w 4 -k uvicorn.workers.UvicornWorker plane.asgi:application --bind 0.0.0.0:$PORT --config gunicorn.config.py --max-requests 10000 --max-requests-jitter 1000 --access-logfile - -worker: python manage.py rqworker -channel-worker: python manage.py runworker issue-activites \ No newline at end of file +worker: python manage.py rqworker \ No newline at end of file diff --git a/apiserver/bin/channel-worker b/apiserver/bin/channel-worker deleted file mode 100755 index 90ba63d50..000000000 --- a/apiserver/bin/channel-worker +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -set -e - -python manage.py wait_for_db -python manage.py migrate -python manage.py runworker issue-activites \ No newline at end of file diff --git a/apiserver/plane/api/consumers/__init__.py b/apiserver/plane/api/consumers/__init__.py deleted file mode 100644 index cbf41cdfa..000000000 --- a/apiserver/plane/api/consumers/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .issue_consumer import IssueConsumer \ No newline at end of file diff --git a/apiserver/plane/api/consumers/issue_consumer.py b/apiserver/plane/api/consumers/issue_consumer.py deleted file mode 100644 index 38eb69967..000000000 --- a/apiserver/plane/api/consumers/issue_consumer.py +++ /dev/null @@ -1,547 +0,0 @@ -from channels.generic.websocket import SyncConsumer -import json -from plane.db.models import IssueActivity, Project, User, Issue, State, Label - - -class IssueConsumer(SyncConsumer): - - # Track Chnages in name - def track_name( - self, - requested_data, - current_instance, - issue_id, - project, - actor, - issue_activities, - ): - if current_instance.get("name") != requested_data.get("name"): - issue_activities.append( - IssueActivity( - issue_id=issue_id, - actor=actor, - verb="updated", - old_value=current_instance.get("name"), - new_value=requested_data.get("name"), - field="name", - project=project, - workspace=project.workspace, - comment=f"{actor.email} updated the start date to {requested_data.get('name')}", - ) - ) - - # Track changes in parent issue - def track_parent( - self, - requested_data, - current_instance, - issue_id, - project, - actor, - issue_activities, - ): - if current_instance.get("parent") != requested_data.get("parent"): - - if requested_data.get("parent") == None: - old_parent = Issue.objects.get(pk=current_instance.get("parent")) - issue_activities.append( - IssueActivity( - issue_id=issue_id, - actor=actor, - verb="updated", - old_value=f"{project.identifier}-{old_parent.sequence_id}", - new_value=None, - field="parent", - project=project, - workspace=project.workspace, - comment=f"{actor.email} updated the parent issue to None", - old_identifier=old_parent.id, - new_identifier=None, - ) - ) - else: - new_parent = Issue.objects.get(pk=requested_data.get("parent")) - old_parent = Issue.objects.filter( - pk=current_instance.get("parent") - ).first() - issue_activities.append( - IssueActivity( - issue_id=issue_id, - actor=actor, - verb="updated", - old_value=f"{project.identifier}-{old_parent.sequence_id}" - if old_parent is not None - else None, - new_value=f"{project.identifier}-{new_parent.sequence_id}", - field="parent", - project=project, - workspace=project.workspace, - comment=f"{actor.email} updated the parent issue to {new_parent.name}", - old_identifier=old_parent.id - if old_parent is not None - else None, - new_identifier=new_parent.id, - ) - ) - - # Track changes in priority - def track_priority( - self, - requested_data, - current_instance, - issue_id, - project, - actor, - issue_activities, - ): - if current_instance.get("priority") != requested_data.get("priority"): - if requested_data.get("priority") == None: - issue_activities.append( - IssueActivity( - issue_id=issue_id, - actor=actor, - verb="updated", - old_value=current_instance.get("parent"), - new_value=requested_data.get("parent"), - field="priority", - project=project, - workspace=project.workspace, - comment=f"{actor.email} updated the priority to None", - ) - ) - else: - issue_activities.append( - IssueActivity( - issue_id=issue_id, - actor=actor, - verb="updated", - old_value=current_instance.get("priority"), - new_value=requested_data.get("priority"), - field="priority", - project=project, - workspace=project.workspace, - comment=f"{actor.email} updated the priority to {requested_data.get('priority')}", - ) - ) - - # Track chnages in state of the issue - def track_state( - self, - requested_data, - current_instance, - issue_id, - project, - actor, - issue_activities, - ): - if current_instance.get("state") != requested_data.get("state"): - - new_state = State.objects.get(pk=requested_data.get("state", None)) - old_state = State.objects.get(pk=current_instance.get("state", None)) - - issue_activities.append( - IssueActivity( - issue_id=issue_id, - actor=actor, - verb="updated", - old_value=old_state.name, - new_value=new_state.name, - field="state", - project=project, - workspace=project.workspace, - comment=f"{actor.email} updated the state to {new_state.name}", - old_identifier=old_state.id, - new_identifier=new_state.id, - ) - ) - - # Track issue description - def track_description( - self, - requested_data, - current_instance, - issue_id, - project, - actor, - issue_activities, - ): - if current_instance.get("description_html") != requested_data.get("description_html"): - - issue_activities.append( - IssueActivity( - issue_id=issue_id, - actor=actor, - verb="updated", - old_value=current_instance.get("description_html"), - new_value=requested_data.get("description_html"), - field="description", - project=project, - workspace=project.workspace, - comment=f"{actor.email} updated the description to {requested_data.get('description_html')}", - ) - ) - - # Track changes in issue target date - def track_target_date( - self, - requested_data, - current_instance, - issue_id, - project, - actor, - issue_activities, - ): - if current_instance.get("target_date") != requested_data.get("target_date"): - if requested_data.get("target_date") == None: - issue_activities.append( - IssueActivity( - issue_id=issue_id, - actor=actor, - verb="updated", - old_value=current_instance.get("target_date"), - new_value=requested_data.get("target_date"), - field="target_date", - project=project, - workspace=project.workspace, - comment=f"{actor.email} updated the target date to None", - ) - ) - else: - issue_activities.append( - IssueActivity( - issue_id=issue_id, - actor=actor, - verb="updated", - old_value=current_instance.get("target_date"), - new_value=requested_data.get("target_date"), - field="target_date", - project=project, - workspace=project.workspace, - comment=f"{actor.email} updated the target date to {requested_data.get('target_date')}", - ) - ) - - # Track changes in issue start date - def track_start_date( - self, - requested_data, - current_instance, - issue_id, - project, - actor, - issue_activities, - ): - if current_instance.get("start_date") != requested_data.get("start_date"): - if requested_data.get("start_date") == None: - issue_activities.append( - IssueActivity( - issue_id=issue_id, - actor=actor, - verb="updated", - old_value=current_instance.get("start_date"), - new_value=requested_data.get("start_date"), - field="start_date", - project=project, - workspace=project.workspace, - comment=f"{actor.email} updated the start date to None", - ) - ) - else: - issue_activities.append( - IssueActivity( - issue_id=issue_id, - actor=actor, - verb="updated", - old_value=current_instance.get("start_date"), - new_value=requested_data.get("start_date"), - field="start_date", - project=project, - workspace=project.workspace, - comment=f"{actor.email} updated the start date to {requested_data.get('start_date')}", - ) - ) - - # Track changes in issue labels - def track_labels( - self, - requested_data, - current_instance, - issue_id, - project, - actor, - issue_activities, - ): - # Label Addition - if len(requested_data.get("labels_list")) > len(current_instance.get("labels")): - - for label in requested_data.get("labels_list"): - if label not in current_instance.get("labels"): - label = Label.objects.get(pk=label) - issue_activities.append( - IssueActivity( - issue_id=issue_id, - actor=actor, - verb="updated", - old_value="", - new_value=label.name, - field="labels", - project=project, - workspace=project.workspace, - comment=f"{actor.email} added label {label.name}", - new_identifier=label.id, - old_identifier=None, - ) - ) - - # Label Removal - if len(requested_data.get("labels_list")) < len(current_instance.get("labels")): - - for label in current_instance.get("labels"): - if label not in requested_data.get("labels_list"): - label = Label.objects.get(pk=label) - issue_activities.append( - IssueActivity( - issue_id=issue_id, - actor=actor, - verb="updated", - old_value=label.name, - new_value="", - field="labels", - project=project, - workspace=project.workspace, - comment=f"{actor.email} removed label {label.name}", - old_identifier=label.id, - new_identifier=None, - ) - ) - - # Track changes in issue assignees - def track_assignees( - self, - requested_data, - current_instance, - issue_id, - project, - actor, - issue_activities, - ): - - # Assignee Addition - if len(requested_data.get("assignees_list")) > len( - current_instance.get("assignees") - ): - - for assignee in requested_data.get("assignees_list"): - if assignee not in current_instance.get("assignees"): - assignee = User.objects.get(pk=assignee) - issue_activities.append( - IssueActivity( - issue_id=issue_id, - actor=actor, - verb="updated", - old_value="", - new_value=assignee.email, - field="assignees", - project=project, - workspace=project.workspace, - comment=f"{actor.email} added assignee {assignee.email}", - new_identifier=actor.id, - ) - ) - - # Assignee Removal - if len(requested_data.get("assignees_list")) < len( - current_instance.get("assignees") - ): - - for assignee in current_instance.get("assignees"): - if assignee not in requested_data.get("assignees_list"): - assignee = User.objects.get(pk=assignee) - issue_activities.append( - IssueActivity( - issue_id=issue_id, - actor=actor, - verb="updated", - old_value=assignee.email, - new_value="", - field="assignee", - project=project, - workspace=project.workspace, - comment=f"{actor.email} removed assignee {assignee.email}", - old_identifier=actor.id, - ) - ) - - # Track changes in blocking issues - def track_blocks( - self, - requested_data, - current_instance, - issue_id, - project, - actor, - issue_activities, - ): - if len(requested_data.get("blocks_list")) > len( - current_instance.get("blocked_issues") - ): - - for block in requested_data.get("blocks_list"): - if ( - len( - [ - blocked - for blocked in current_instance.get("blocked_issues") - if blocked.get("block") == block - ] - ) - == 0 - ): - issue = Issue.objects.get(pk=block) - issue_activities.append( - IssueActivity( - issue_id=issue_id, - actor=actor, - verb="updated", - old_value="", - new_value=f"{project.identifier}-{issue.sequence_id}", - field="blocks", - project=project, - workspace=project.workspace, - comment=f"{actor.email} added blocking issue {project.identifier}-{issue.sequence_id}", - new_identifier=issue.id, - ) - ) - - # Blocked Issue Removal - if len(requested_data.get("blocks_list")) < len( - current_instance.get("blocked_issues") - ): - - for blocked in current_instance.get("blocked_issues"): - if blocked.get("block") not in requested_data.get("blocks_list"): - issue = Issue.objects.get(pk=blocked.get("block")) - issue_activities.append( - IssueActivity( - issue_id=issue_id, - actor=actor, - verb="updated", - old_value=f"{project.identifier}-{issue.sequence_id}", - new_value="", - field="blocks", - project=project, - workspace=project.workspace, - comment=f"{actor.email} removed blocking issue {project.identifier}-{issue.sequence_id}", - old_identifier=issue.id, - ) - ) - - # Track changes in blocked_by issues - def track_blockings( - self, - requested_data, - current_instance, - issue_id, - project, - actor, - issue_activities, - ): - if len(requested_data.get("blockers_list")) > len( - current_instance.get("blocker_issues") - ): - - for block in requested_data.get("blockers_list"): - if ( - len( - [ - blocked - for blocked in current_instance.get("blocker_issues") - if blocked.get("blocked_by") == block - ] - ) - == 0 - ): - issue = Issue.objects.get(pk=block) - issue_activities.append( - IssueActivity( - issue_id=issue_id, - actor=actor, - verb="updated", - old_value="", - new_value=f"{project.identifier}-{issue.sequence_id}", - field="blocking", - project=project, - workspace=project.workspace, - comment=f"{actor.email} added blocked by issue {project.identifier}-{issue.sequence_id}", - new_identifier=issue.id, - ) - ) - - # Blocked Issue Removal - if len(requested_data.get("blockers_list")) < len( - current_instance.get("blocker_issues") - ): - - for blocked in current_instance.get("blocker_issues"): - if blocked.get("blocked_by") not in requested_data.get("blockers_list"): - issue = Issue.objects.get(pk=blocked.get("blocked_by")) - issue_activities.append( - IssueActivity( - issue_id=issue_id, - actor=actor, - verb="updated", - old_value=f"{project.identifier}-{issue.sequence_id}", - new_value="", - field="blocking", - project=project, - workspace=project.workspace, - comment=f"{actor.email} removed blocked by issue {project.identifier}-{issue.sequence_id}", - old_identifier=issue.id, - ) - ) - - # Receive message from room group - def issue_activity(self, event): - - issue_activities = [] - # Remove event type: - event.pop("type") - - requested_data = json.loads(event.get("requested_data")) - current_instance = json.loads(event.get("current_instance")) - issue_id = event.get("issue_id") - actor_id = event.get("actor_id") - project_id = event.get("project_id") - - actor = User.objects.get(pk=actor_id) - - project = Project.objects.get(pk=project_id) - - ISSUE_ACTIVITY_MAPPER = { - "name": self.track_name, - "parent": self.track_parent, - "priority": self.track_priority, - "state": self.track_state, - "description": self.track_description, - "target_date": self.track_target_date, - "start_date": self.track_start_date, - "labels_list": self.track_labels, - "assignees_list": self.track_assignees, - "blocks_list": self.track_blocks, - "blockers_list": self.track_blockings, - } - - for key in requested_data: - func = ISSUE_ACTIVITY_MAPPER.get(key, None) - if func is not None: - func( - requested_data, - current_instance, - issue_id, - project, - actor, - issue_activities, - ) - - # Save all the values to database - IssueActivity.objects.bulk_create(issue_activities) diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index f716c2b11..37082e0ec 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -10,8 +10,6 @@ from django.core.serializers.json import DjangoJSONEncoder from rest_framework.response import Response from rest_framework import status from sentry_sdk import capture_exception -from channels.layers import get_channel_layer -from asgiref.sync import async_to_sync # Module imports from . import BaseViewSet, BaseAPIView @@ -42,6 +40,7 @@ from plane.db.models import ( CycleIssue, ModuleIssue, ) +from plane.bgtasks.issue_activites_task import issue_activity class IssueViewSet(BaseViewSet): @@ -72,12 +71,12 @@ class IssueViewSet(BaseViewSet): def perform_update(self, serializer): requested_data = json.dumps(self.request.data, cls=DjangoJSONEncoder) - current_instance = Issue.objects.filter(pk=self.kwargs.get("pk", None)).first() + current_instance = ( + self.get_queryset().filter(pk=self.kwargs.get("pk", None)).first() + ) if current_instance is not None: - channel_layer = get_channel_layer() - async_to_sync(channel_layer.send)( - "issue-activites", + issue_activity.delay( { "type": "issue.activity", "requested_data": requested_data, diff --git a/apiserver/plane/asgi.py b/apiserver/plane/asgi.py index b5beb1df4..7333baae3 100644 --- a/apiserver/plane/asgi.py +++ b/apiserver/plane/asgi.py @@ -1,6 +1,6 @@ import os -from channels.routing import ProtocolTypeRouter, ChannelNameRouter +from channels.routing import ProtocolTypeRouter from django.core.asgi import get_asgi_application django_asgi_app = get_asgi_application() @@ -10,15 +10,9 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "plane.settings.production") # Initialize Django ASGI application early to ensure the AppRegistry # is populated before importing code that may import ORM models. -from plane.api.consumers import IssueConsumer application = ProtocolTypeRouter( { "http": get_asgi_application(), - "channel": ChannelNameRouter( - { - "issue-activites": IssueConsumer.as_asgi(), - } - ), } ) diff --git a/apiserver/plane/bgtasks/issue_activites_task.py b/apiserver/plane/bgtasks/issue_activites_task.py new file mode 100644 index 000000000..f6debc921 --- /dev/null +++ b/apiserver/plane/bgtasks/issue_activites_task.py @@ -0,0 +1,553 @@ +# Python imports +import json + +# Third Party imports +from django_rq import job +from sentry_sdk import capture_exception + +# Module imports +from plane.db.models import User, Issue, Project, Label, IssueActivity, State + + +# Track Chnages in name +def track_name( + requested_data, + current_instance, + issue_id, + project, + actor, + issue_activities, +): + if current_instance.get("name") != requested_data.get("name"): + issue_activities.append( + IssueActivity( + issue_id=issue_id, + actor=actor, + verb="updated", + old_value=current_instance.get("name"), + new_value=requested_data.get("name"), + field="name", + project=project, + workspace=project.workspace, + comment=f"{actor.email} updated the start date to {requested_data.get('name')}", + ) + ) + + +# Track changes in parent issue +def track_parent( + requested_data, + current_instance, + issue_id, + project, + actor, + issue_activities, +): + if current_instance.get("parent") != requested_data.get("parent"): + + if requested_data.get("parent") == None: + old_parent = Issue.objects.get(pk=current_instance.get("parent")) + issue_activities.append( + IssueActivity( + issue_id=issue_id, + actor=actor, + verb="updated", + old_value=f"{project.identifier}-{old_parent.sequence_id}", + new_value=None, + field="parent", + project=project, + workspace=project.workspace, + comment=f"{actor.email} updated the parent issue to None", + old_identifier=old_parent.id, + new_identifier=None, + ) + ) + else: + new_parent = Issue.objects.get(pk=requested_data.get("parent")) + old_parent = Issue.objects.filter(pk=current_instance.get("parent")).first() + issue_activities.append( + IssueActivity( + issue_id=issue_id, + actor=actor, + verb="updated", + old_value=f"{project.identifier}-{old_parent.sequence_id}" + if old_parent is not None + else None, + new_value=f"{project.identifier}-{new_parent.sequence_id}", + field="parent", + project=project, + workspace=project.workspace, + comment=f"{actor.email} updated the parent issue to {new_parent.name}", + old_identifier=old_parent.id if old_parent is not None else None, + new_identifier=new_parent.id, + ) + ) + + +# Track changes in priority +def track_priority( + requested_data, + current_instance, + issue_id, + project, + actor, + issue_activities, +): + if current_instance.get("priority") != requested_data.get("priority"): + if requested_data.get("priority") == None: + issue_activities.append( + IssueActivity( + issue_id=issue_id, + actor=actor, + verb="updated", + old_value=current_instance.get("priority"), + new_value=None, + field="priority", + project=project, + workspace=project.workspace, + comment=f"{actor.email} updated the priority to None", + ) + ) + else: + issue_activities.append( + IssueActivity( + issue_id=issue_id, + actor=actor, + verb="updated", + old_value=current_instance.get("priority"), + new_value=requested_data.get("priority"), + field="priority", + project=project, + workspace=project.workspace, + comment=f"{actor.email} updated the priority to {requested_data.get('priority')}", + ) + ) + + +# Track chnages in state of the issue +def track_state( + requested_data, + current_instance, + issue_id, + project, + actor, + issue_activities, +): + if current_instance.get("state") != requested_data.get("state"): + + new_state = State.objects.get(pk=requested_data.get("state", None)) + old_state = State.objects.get(pk=current_instance.get("state", None)) + + issue_activities.append( + IssueActivity( + issue_id=issue_id, + actor=actor, + verb="updated", + old_value=old_state.name, + new_value=new_state.name, + field="state", + project=project, + workspace=project.workspace, + comment=f"{actor.email} updated the state to {new_state.name}", + old_identifier=old_state.id, + new_identifier=new_state.id, + ) + ) + + +# Track issue description +def track_description( + requested_data, + current_instance, + issue_id, + project, + actor, + issue_activities, +): + if current_instance.get("description_html") != requested_data.get( + "description_html" + ): + + issue_activities.append( + IssueActivity( + issue_id=issue_id, + actor=actor, + verb="updated", + old_value=current_instance.get("description_html"), + new_value=requested_data.get("description_html"), + field="description", + project=project, + workspace=project.workspace, + comment=f"{actor.email} updated the description to {requested_data.get('description_html')}", + ) + ) + + +# Track changes in issue target date +def track_target_date( + requested_data, + current_instance, + issue_id, + project, + actor, + issue_activities, +): + if current_instance.get("target_date") != requested_data.get("target_date"): + if requested_data.get("target_date") == None: + issue_activities.append( + IssueActivity( + issue_id=issue_id, + actor=actor, + verb="updated", + old_value=current_instance.get("target_date"), + new_value=requested_data.get("target_date"), + field="target_date", + project=project, + workspace=project.workspace, + comment=f"{actor.email} updated the target date to None", + ) + ) + else: + issue_activities.append( + IssueActivity( + issue_id=issue_id, + actor=actor, + verb="updated", + old_value=current_instance.get("target_date"), + new_value=requested_data.get("target_date"), + field="target_date", + project=project, + workspace=project.workspace, + comment=f"{actor.email} updated the target date to {requested_data.get('target_date')}", + ) + ) + + +# Track changes in issue start date +def track_start_date( + requested_data, + current_instance, + issue_id, + project, + actor, + issue_activities, +): + if current_instance.get("start_date") != requested_data.get("start_date"): + if requested_data.get("start_date") == None: + issue_activities.append( + IssueActivity( + issue_id=issue_id, + actor=actor, + verb="updated", + old_value=current_instance.get("start_date"), + new_value=requested_data.get("start_date"), + field="start_date", + project=project, + workspace=project.workspace, + comment=f"{actor.email} updated the start date to None", + ) + ) + else: + issue_activities.append( + IssueActivity( + issue_id=issue_id, + actor=actor, + verb="updated", + old_value=current_instance.get("start_date"), + new_value=requested_data.get("start_date"), + field="start_date", + project=project, + workspace=project.workspace, + comment=f"{actor.email} updated the start date to {requested_data.get('start_date')}", + ) + ) + + +# Track changes in issue labels +def track_labels( + requested_data, + current_instance, + issue_id, + project, + actor, + issue_activities, +): + # Label Addition + if len(requested_data.get("labels_list")) > len(current_instance.get("labels")): + + for label in requested_data.get("labels_list"): + if label not in current_instance.get("labels"): + label = Label.objects.get(pk=label) + issue_activities.append( + IssueActivity( + issue_id=issue_id, + actor=actor, + verb="updated", + old_value="", + new_value=label.name, + field="labels", + project=project, + workspace=project.workspace, + comment=f"{actor.email} added label {label.name}", + new_identifier=label.id, + old_identifier=None, + ) + ) + + # Label Removal + if len(requested_data.get("labels_list")) < len(current_instance.get("labels")): + + for label in current_instance.get("labels"): + if label not in requested_data.get("labels_list"): + label = Label.objects.get(pk=label) + issue_activities.append( + IssueActivity( + issue_id=issue_id, + actor=actor, + verb="updated", + old_value=label.name, + new_value="", + field="labels", + project=project, + workspace=project.workspace, + comment=f"{actor.email} removed label {label.name}", + old_identifier=label.id, + new_identifier=None, + ) + ) + + +# Track changes in issue assignees +def track_assignees( + requested_data, + current_instance, + issue_id, + project, + actor, + issue_activities, +): + + # Assignee Addition + if len(requested_data.get("assignees_list")) > len( + current_instance.get("assignees") + ): + + for assignee in requested_data.get("assignees_list"): + if assignee not in current_instance.get("assignees"): + assignee = User.objects.get(pk=assignee) + issue_activities.append( + IssueActivity( + issue_id=issue_id, + actor=actor, + verb="updated", + old_value="", + new_value=assignee.email, + field="assignees", + project=project, + workspace=project.workspace, + comment=f"{actor.email} added assignee {assignee.email}", + new_identifier=actor.id, + ) + ) + + # Assignee Removal + if len(requested_data.get("assignees_list")) < len( + current_instance.get("assignees") + ): + + for assignee in current_instance.get("assignees"): + if assignee not in requested_data.get("assignees_list"): + assignee = User.objects.get(pk=assignee) + issue_activities.append( + IssueActivity( + issue_id=issue_id, + actor=actor, + verb="updated", + old_value=assignee.email, + new_value="", + field="assignee", + project=project, + workspace=project.workspace, + comment=f"{actor.email} removed assignee {assignee.email}", + old_identifier=actor.id, + ) + ) + + +# Track changes in blocking issues +def track_blocks( + requested_data, + current_instance, + issue_id, + project, + actor, + issue_activities, +): + if len(requested_data.get("blocks_list")) > len( + current_instance.get("blocked_issues") + ): + + for block in requested_data.get("blocks_list"): + if ( + len( + [ + blocked + for blocked in current_instance.get("blocked_issues") + if blocked.get("block") == block + ] + ) + == 0 + ): + issue = Issue.objects.get(pk=block) + issue_activities.append( + IssueActivity( + issue_id=issue_id, + actor=actor, + verb="updated", + old_value="", + new_value=f"{project.identifier}-{issue.sequence_id}", + field="blocks", + project=project, + workspace=project.workspace, + comment=f"{actor.email} added blocking issue {project.identifier}-{issue.sequence_id}", + new_identifier=issue.id, + ) + ) + + # Blocked Issue Removal + if len(requested_data.get("blocks_list")) < len( + current_instance.get("blocked_issues") + ): + + for blocked in current_instance.get("blocked_issues"): + if blocked.get("block") not in requested_data.get("blocks_list"): + issue = Issue.objects.get(pk=blocked.get("block")) + issue_activities.append( + IssueActivity( + issue_id=issue_id, + actor=actor, + verb="updated", + old_value=f"{project.identifier}-{issue.sequence_id}", + new_value="", + field="blocks", + project=project, + workspace=project.workspace, + comment=f"{actor.email} removed blocking issue {project.identifier}-{issue.sequence_id}", + old_identifier=issue.id, + ) + ) + + +# Track changes in blocked_by issues +def track_blockings( + requested_data, + current_instance, + issue_id, + project, + actor, + issue_activities, +): + if len(requested_data.get("blockers_list")) > len( + current_instance.get("blocker_issues") + ): + + for block in requested_data.get("blockers_list"): + if ( + len( + [ + blocked + for blocked in current_instance.get("blocker_issues") + if blocked.get("blocked_by") == block + ] + ) + == 0 + ): + issue = Issue.objects.get(pk=block) + issue_activities.append( + IssueActivity( + issue_id=issue_id, + actor=actor, + verb="updated", + old_value="", + new_value=f"{project.identifier}-{issue.sequence_id}", + field="blocking", + project=project, + workspace=project.workspace, + comment=f"{actor.email} added blocked by issue {project.identifier}-{issue.sequence_id}", + new_identifier=issue.id, + ) + ) + + # Blocked Issue Removal + if len(requested_data.get("blockers_list")) < len( + current_instance.get("blocker_issues") + ): + + for blocked in current_instance.get("blocker_issues"): + if blocked.get("blocked_by") not in requested_data.get("blockers_list"): + issue = Issue.objects.get(pk=blocked.get("blocked_by")) + issue_activities.append( + IssueActivity( + issue_id=issue_id, + actor=actor, + verb="updated", + old_value=f"{project.identifier}-{issue.sequence_id}", + new_value="", + field="blocking", + project=project, + workspace=project.workspace, + comment=f"{actor.email} removed blocked by issue {project.identifier}-{issue.sequence_id}", + old_identifier=issue.id, + ) + ) + + +# Receive message from room group +@job("default") +def issue_activity(event): + try: + issue_activities = [] + + requested_data = json.loads(event.get("requested_data")) + current_instance = json.loads(event.get("current_instance")) + issue_id = event.get("issue_id") + actor_id = event.get("actor_id") + project_id = event.get("project_id") + + actor = User.objects.get(pk=actor_id) + + project = Project.objects.get(pk=project_id) + + ISSUE_ACTIVITY_MAPPER = { + "name": track_name, + "parent": track_parent, + "priority": track_priority, + "state": track_state, + "description": track_description, + "target_date": track_target_date, + "start_date": track_start_date, + "labels_list": track_labels, + "assignees_list": track_assignees, + "blocks_list": track_blocks, + "blockers_list": track_blockings, + } + + for key in requested_data: + func = ISSUE_ACTIVITY_MAPPER.get(key, None) + if func is not None: + func( + requested_data, + current_instance, + issue_id, + project, + actor, + issue_activities, + ) + + # Save all the values to database + _ = IssueActivity.objects.bulk_create(issue_activities) + + return + except Exception as e: + capture_exception(e) + return diff --git a/apiserver/plane/db/apps.py b/apiserver/plane/db/apps.py index c2741bba3..7d4919d08 100644 --- a/apiserver/plane/db/apps.py +++ b/apiserver/plane/db/apps.py @@ -1,52 +1,5 @@ from django.apps import AppConfig -from fieldsignals import post_save_changed class DbConfig(AppConfig): name = "plane.db" - - # def ready(self): - - # post_save_changed.connect( - # self.model_activity, - # sender=self.get_model("Issue"), - # ) - - # def model_activity(self, sender, instance, changed_fields, **kwargs): - - # verb = "created" if instance._state.adding else "changed" - - # import inspect - - # for frame_record in inspect.stack(): - # if frame_record[3] == "get_response": - # request = frame_record[0].f_locals["request"] - # REQUEST_METHOD = request.method - - # if REQUEST_METHOD == "POST": - - # self.get_model("IssueActivity").objects.create( - # issue=instance, project=instance.project, actor=instance.created_by - # ) - - # elif REQUEST_METHOD == "PATCH": - - # try: - # del changed_fields["updated_at"] - # del changed_fields["updated_by"] - # except KeyError as e: - # pass - - # for field_name, (old, new) in changed_fields.items(): - # field = field_name - # old_value = old - # new_value = new - # self.get_model("IssueActivity").objects.create( - # issue=instance, - # verb=verb, - # field=field, - # old_value=old_value, - # new_value=new_value, - # project=instance.project, - # actor=instance.updated_by, - # ) diff --git a/apiserver/plane/db/models/issue.py b/apiserver/plane/db/models/issue.py index f69f11574..c3984b3d2 100644 --- a/apiserver/plane/db/models/issue.py +++ b/apiserver/plane/db/models/issue.py @@ -145,12 +145,8 @@ class IssueActivity(ProjectBaseModel): field = models.CharField( max_length=255, verbose_name="Field Name", blank=True, null=True ) - old_value = models.CharField( - max_length=255, verbose_name="Old Value", blank=True, null=True - ) - new_value = models.CharField( - max_length=255, verbose_name="New Value", blank=True, null=True - ) + old_value = models.TextField(verbose_name="Old Value", blank=True, null=True) + new_value = models.TextField(verbose_name="New Value", blank=True, null=True) comment = models.TextField(verbose_name="Comment", blank=True) attachments = ArrayField(models.URLField(), size=10, blank=True, default=list) diff --git a/apiserver/plane/settings/common.py b/apiserver/plane/settings/common.py index d86598a84..e14c250b4 100644 --- a/apiserver/plane/settings/common.py +++ b/apiserver/plane/settings/common.py @@ -34,9 +34,7 @@ INSTALLED_APPS = [ "rest_framework_simplejwt.token_blacklist", "corsheaders", "taggit", - "fieldsignals", "django_rq", - "channels", ] MIDDLEWARE = [ diff --git a/apiserver/plane/settings/local.py b/apiserver/plane/settings/local.py index 68f897997..4d4af9b77 100644 --- a/apiserver/plane/settings/local.py +++ b/apiserver/plane/settings/local.py @@ -66,11 +66,3 @@ RQ_QUEUES = { WEB_URL = "http://localhost:3000" -CHANNEL_LAYERS = { - "default": { - "BACKEND": "channels_redis.core.RedisChannelLayer", - "CONFIG": { - "hosts": [(REDIS_HOST, REDIS_PORT)], - }, - }, -} diff --git a/apiserver/plane/settings/production.py b/apiserver/plane/settings/production.py index a61d8a909..c83901484 100644 --- a/apiserver/plane/settings/production.py +++ b/apiserver/plane/settings/production.py @@ -1,11 +1,8 @@ """Production settings and globals.""" -import ssl -from typing import Optional from urllib.parse import urlparse import dj_database_url from urllib.parse import urlparse -from redis.asyncio.connection import Connection, RedisSSLContext import sentry_sdk from sentry_sdk.integrations.django import DjangoIntegration @@ -186,64 +183,10 @@ RQ_QUEUES = { } -class CustomSSLConnection(Connection): - def __init__( - self, - ssl_context: Optional[str] = None, - **kwargs, - ): - super().__init__(**kwargs) - self.ssl_context = RedisSSLContext(ssl_context) - - -class RedisSSLContext: - __slots__ = ("context",) - - def __init__( - self, - ssl_context, - ): - self.context = ssl_context - - def get(self): - return self.context - - url = urlparse(os.environ.get("REDIS_URL")) -DOCKERIZED = os.environ.get("DOCKERIZED", False) # Set the variable true if running in docker-compose environment - -if not DOCKERIZED: - - ssl_context = ssl.SSLContext() - ssl_context.check_hostname = False - - CHANNEL_LAYERS = { - "default": { - "BACKEND": "channels_redis.core.RedisChannelLayer", - "CONFIG": { - "hosts": [ - { - "host": url.hostname, - "port": url.port, - "username": url.username, - "password": url.password, - "connection_class": CustomSSLConnection, - "ssl_context": ssl_context, - } - ], - }, - }, - } -else: - CHANNEL_LAYERS = { - "default": { - "BACKEND": "channels_redis.core.RedisChannelLayer", - "CONFIG": { - "hosts": [(os.environ.get("REDIS_URL"))], - }, - }, - } - +DOCKERIZED = os.environ.get( + "DOCKERIZED", False +) # Set the variable true if running in docker-compose environment WEB_URL = os.environ.get("WEB_URL") diff --git a/apiserver/plane/settings/staging.py b/apiserver/plane/settings/staging.py index 429530d94..725f2cd85 100644 --- a/apiserver/plane/settings/staging.py +++ b/apiserver/plane/settings/staging.py @@ -1,11 +1,8 @@ """Production settings and globals.""" -import ssl -from typing import Optional from urllib.parse import urlparse import dj_database_url from urllib.parse import urlparse -from redis.asyncio.connection import Connection, RedisSSLContext import sentry_sdk from sentry_sdk.integrations.django import DjangoIntegration @@ -186,52 +183,5 @@ RQ_QUEUES = { } } -class CustomSSLConnection(Connection): - def __init__( - self, - ssl_context: Optional[str] = None, - **kwargs, - ): - super().__init__(**kwargs) - self.ssl_context = RedisSSLContext(ssl_context) - -class RedisSSLContext: - __slots__ = ( - "context", - ) - - def __init__( - self, - ssl_context, - ): - self.context = ssl_context - - def get(self): - return self.context - - -url = urlparse(os.environ.get("REDIS_URL")) - -ssl_context = ssl.SSLContext() -ssl_context.check_hostname = False - -CHANNEL_LAYERS = { - 'default': { - 'BACKEND': 'channels_redis.core.RedisChannelLayer', - 'CONFIG': { - 'hosts': [ - { - 'host': url.hostname, - 'port': url.port, - 'username': url.username, - 'password': url.password, - 'connection_class': CustomSSLConnection, - 'ssl_context': ssl_context, - } - ], - } - }, -} - WEB_URL = os.environ.get("WEB_URL") diff --git a/apiserver/requirements/base.txt b/apiserver/requirements/base.txt index 7dff0a765..578235003 100644 --- a/apiserver/requirements/base.txt +++ b/apiserver/requirements/base.txt @@ -20,12 +20,9 @@ sentry-sdk==1.13.0 django-s3-storage==0.13.6 django-crum==0.7.9 django-guardian==2.4.0 -django-fieldsignals==0.7.0 dj_rest_auth==2.2.5 google-auth==2.9.1 google-api-python-client==2.55.0 django-rq==2.5.1 django-redis==5.2.0 -channels==4.0.0 -channels-redis==4.0.0 uvicorn==0.20.0 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 058fa474c..04c792066 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -68,21 +68,6 @@ services: env_file: - ./apiserver/.env - plane-channel-worker: - image: plane-api - container_name: plane-channel-worker - restart: always - depends_on: - - redis - - db - - plane-api - command: ./bin/channel-worker - links: - - redis:redis - - db:db - env_file: - - ./apiserver/.env - volumes: pgdata: redisdata: