diff --git a/apiserver/plane/app/views/page.py b/apiserver/plane/app/views/page.py index 1d8ff1fbb..4a1a540b6 100644 --- a/apiserver/plane/app/views/page.py +++ b/apiserver/plane/app/views/page.py @@ -1,26 +1,36 @@ # Python imports -from datetime import date, datetime, timedelta +import json +from datetime import datetime # Django imports from django.db import connection from django.db.models import Exists, OuterRef, Q -from django.utils import timezone from django.utils.decorators import method_decorator from django.views.decorators.gzip import gzip_page + # Third party imports from rest_framework import status from rest_framework.response import Response from plane.app.permissions import ProjectEntityPermission -from plane.app.serializers import (IssueLiteSerializer, PageFavoriteSerializer, - PageLogSerializer, PageSerializer, - SubPageSerializer) -from plane.db.models import (Issue, IssueActivity, IssueAssignee, Page, - PageFavorite, PageLog, ProjectMember) +from plane.app.serializers import ( + PageFavoriteSerializer, + PageLogSerializer, + PageSerializer, + SubPageSerializer, +) +from plane.db.models import ( + Page, + PageFavorite, + PageLog, + ProjectMember, +) # Module imports from .base import BaseAPIView, BaseViewSet +from plane.bgtasks.page_transaction_task import page_transaction + def unarchive_archive_page_and_descendants(page_id, archived_at): # Your SQL query @@ -81,6 +91,8 @@ class PageViewSet(BaseViewSet): if serializer.is_valid(): serializer.save() + # capture the page transaction + page_transaction.delay(request.data, None, serializer.data["id"]) return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -117,6 +129,17 @@ class PageViewSet(BaseViewSet): serializer = PageSerializer(page, data=request.data, partial=True) if serializer.is_valid(): serializer.save() + # capture the page transaction + if request.data.get("description_html"): + page_transaction.delay( + new_value=request.data, + old_value=json.dumps( + { + "description_html": page.description_html, + } + ), + page_id=pk, + ) return Response(serializer.data, status=status.HTTP_200_OK) return Response( serializer.errors, status=status.HTTP_400_BAD_REQUEST diff --git a/apiserver/plane/bgtasks/page_transaction_task.py b/apiserver/plane/bgtasks/page_transaction_task.py new file mode 100644 index 000000000..9cf4add05 --- /dev/null +++ b/apiserver/plane/bgtasks/page_transaction_task.py @@ -0,0 +1,75 @@ +# Python imports +import json + +# Django imports +from django.utils import timezone + +# Third-party imports +from bs4 import BeautifulSoup + +# Module imports +from plane.db.models import Page, PageLog +from celery import shared_task + + +def extract_components(value, tag): + try: + mentions = [] + html = value.get("description_html") + soup = BeautifulSoup(html, "html.parser") + mention_tags = soup.find_all(tag) + + for mention_tag in mention_tags: + mention = { + "id": mention_tag.get("id"), + "entity_identifier": mention_tag.get("entity_identifier"), + "entity_name": mention_tag.get("entity_name"), + } + mentions.append(mention) + + return mentions + except Exception as e: + return [] + + +@shared_task +def page_transaction(new_value, old_value, page_id): + page = Page.objects.get(pk=page_id) + new_page_mention = PageLog.objects.filter(page_id=page_id).exists() + + old_value = json.loads(old_value) + + new_transactions = [] + deleted_transaction_ids = set() + + components = ["mention-component", "issue-embed-component", "img"] + for component in components: + old_mentions = extract_components(old_value, component) + new_mentions = extract_components(new_value, component) + + new_mentions_ids = {mention["id"] for mention in new_mentions} + old_mention_ids = {mention["id"] for mention in old_mentions} + deleted_transaction_ids.update(old_mention_ids - new_mentions_ids) + + new_transactions.extend( + PageLog( + transaction=mention["id"], + page_id=page_id, + entity_identifier=mention["entity_identifier"], + entity_name=mention["entity_name"], + workspace_id=page.workspace_id, + project_id=page.project_id, + created_at=timezone.now(), + updated_at=timezone.now(), + ) + for mention in new_mentions + if mention["id"] not in old_mention_ids or not new_page_mention + ) + + # Create new PageLog objects for new transactions + PageLog.objects.bulk_create(new_transactions, batch_size=10, ignore_conflicts=True) + + # Delete the removed transactions + PageLog.objects.filter( + transaction__in=deleted_transaction_ids + ).delete() diff --git a/apiserver/plane/db/models/page.py b/apiserver/plane/db/models/page.py index 6ed94798a..afa362b64 100644 --- a/apiserver/plane/db/models/page.py +++ b/apiserver/plane/db/models/page.py @@ -81,7 +81,7 @@ class PageLog(ProjectBaseModel): ordering = ("-created_at",) def __str__(self): - return f"{self.page.name} {self.type}" + return f"{self.page.name} {self.entity_name}" class PageBlock(ProjectBaseModel):