diff --git a/apiserver/plane/api/views/inbox.py b/apiserver/plane/api/views/inbox.py index 209b7b6581..5e6e4a2158 100644 --- a/apiserver/plane/api/views/inbox.py +++ b/apiserver/plane/api/views/inbox.py @@ -272,6 +272,9 @@ class InboxIssueAPIEndpoint(BaseAPIView): serializer = InboxIssueSerializer( inbox_issue, data=request.data, partial=True ) + current_instance = json.dumps( + InboxIssueSerializer(inbox_issue).data, cls=DjangoJSONEncoder + ) if serializer.is_valid(): serializer.save() @@ -311,6 +314,21 @@ class InboxIssueAPIEndpoint(BaseAPIView): issue.state = state issue.save() + # create a activity for status change + issue_activity.delay( + type="inbox.activity.created", + requested_data=json.dumps( + request.data, cls=DjangoJSONEncoder + ), + actor_id=str(request.user.id), + issue_id=str(issue_id), + project_id=str(project_id), + current_instance=current_instance, + epoch=int(timezone.now().timestamp()), + notification=False, + origin=request.META.get("HTTP_ORIGIN"), + ) + return Response(serializer.data, status=status.HTTP_200_OK) return Response( serializer.errors, status=status.HTTP_400_BAD_REQUEST diff --git a/apiserver/plane/app/views/inbox/base.py b/apiserver/plane/app/views/inbox/base.py index e08434a6de..8e433a127c 100644 --- a/apiserver/plane/app/views/inbox/base.py +++ b/apiserver/plane/app/views/inbox/base.py @@ -391,7 +391,9 @@ class InboxIssueViewSet(BaseViewSet): serializer = InboxIssueSerializer( inbox_issue, data=request.data, partial=True ) - + current_instance = json.dumps( + InboxIssueSerializer(inbox_issue).data, cls=DjangoJSONEncoder + ) if serializer.is_valid(): serializer.save() # Update the issue state if the issue is rejected or marked as duplicate @@ -429,6 +431,21 @@ class InboxIssueViewSet(BaseViewSet): if state is not None: issue.state = state issue.save() + # create a activity for status change + issue_activity.delay( + type="inbox.activity.created", + requested_data=json.dumps( + request.data, cls=DjangoJSONEncoder + ), + actor_id=str(request.user.id), + issue_id=str(issue_id), + project_id=str(project_id), + current_instance=current_instance, + epoch=int(timezone.now().timestamp()), + notification=False, + origin=request.META.get("HTTP_ORIGIN"), + ) + inbox_issue = ( InboxIssue.objects.filter( inbox_id=inbox_id.id, diff --git a/apiserver/plane/app/views/page/base.py b/apiserver/plane/app/views/page/base.py index 4076a53b19..29dc2dbf5c 100644 --- a/apiserver/plane/app/views/page/base.py +++ b/apiserver/plane/app/views/page/base.py @@ -1,6 +1,7 @@ # Python imports import json from datetime import datetime +from django.core.serializers.json import DjangoJSONEncoder # Django imports from django.db import connection @@ -142,6 +143,7 @@ class PageViewSet(BaseViewSet): serializer = PageDetailSerializer( page, data=request.data, partial=True ) + page_description = page.description_html if serializer.is_valid(): serializer.save() # capture the page transaction @@ -150,11 +152,13 @@ class PageViewSet(BaseViewSet): new_value=request.data, old_value=json.dumps( { - "description_html": page.description_html, - } + "description_html": page_description, + }, + cls=DjangoJSONEncoder, ), 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/dummy_data_task.py b/apiserver/plane/bgtasks/dummy_data_task.py index 7ff110e6f5..e76cdac224 100644 --- a/apiserver/plane/bgtasks/dummy_data_task.py +++ b/apiserver/plane/bgtasks/dummy_data_task.py @@ -1,4 +1,5 @@ # Python imports +import uuid import random from datetime import datetime, timedelta @@ -36,9 +37,11 @@ from plane.db.models import ( def create_project(workspace, user_id): fake = Faker() name = fake.name() + unique_id = str(uuid.uuid4())[:5] + project = Project.objects.create( workspace=workspace, - name=name, + name=f"{name}_{unique_id}", identifier=name[ : random.randint(2, 12 if len(name) - 1 >= 12 else len(name) - 1) ].upper(), diff --git a/apiserver/plane/bgtasks/issue_activites_task.py b/apiserver/plane/bgtasks/issue_activites_task.py index 9a4e57a49f..2d55d55796 100644 --- a/apiserver/plane/bgtasks/issue_activites_task.py +++ b/apiserver/plane/bgtasks/issue_activites_task.py @@ -1553,6 +1553,46 @@ def delete_draft_issue_activity( ) +def create_inbox_activity( + requested_data, + current_instance, + issue_id, + project_id, + workspace_id, + actor_id, + issue_activities, + epoch, +): + requested_data = ( + json.loads(requested_data) if requested_data is not None else None + ) + current_instance = ( + json.loads(current_instance) if current_instance is not None else None + ) + status_dict = { + -2: "Pending", + -1: "Rejected", + 0: "Snoozed", + 1: "Accepted", + 2: "Duplicate", + } + if requested_data.get("status") is not None: + issue_activities.append( + IssueActivity( + issue_id=issue_id, + project_id=project_id, + workspace_id=workspace_id, + comment="updated the inbox status", + field="inbox", + verb=requested_data.get("status"), + actor_id=actor_id, + epoch=epoch, + old_value=status_dict.get(current_instance.get("status")), + new_value=status_dict.get(requested_data.get("status")), + ) + ) + + # Receive message from room group @shared_task def issue_activity( @@ -1613,6 +1653,7 @@ def issue_activity( "issue_draft.activity.created": create_draft_issue_activity, "issue_draft.activity.updated": update_draft_issue_activity, "issue_draft.activity.deleted": delete_draft_issue_activity, + "inbox.activity.created": create_inbox_activity, } func = ACTIVITY_MAPPER.get(type) diff --git a/apiserver/plane/bgtasks/page_transaction_task.py b/apiserver/plane/bgtasks/page_transaction_task.py index 57f4f644e4..eceb3693e7 100644 --- a/apiserver/plane/bgtasks/page_transaction_task.py +++ b/apiserver/plane/bgtasks/page_transaction_task.py @@ -37,10 +37,10 @@ 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) + old_value = json.loads(old_value) if old_value else {} new_transactions = [] - deleted_transaction_ids = set() + deleted_transaction_ids = set() # TODO - Add "issue-embed-component", "img", "todo" components components = ["mention-component"] @@ -49,8 +49,8 @@ def page_transaction(new_value, old_value, page_id): 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) + old_mention_ids = {mention["id"] for mention in old_mentions} + deleted_transaction_ids.update(old_mention_ids - new_mentions_ids) new_transactions.extend( PageLog( @@ -68,9 +68,9 @@ def page_transaction(new_value, old_value, page_id): ) # Create new PageLog objects for new transactions - PageLog.objects.bulk_create(new_transactions, batch_size=10, ignore_conflicts=True) + 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() + PageLog.objects.filter(transaction__in=deleted_transaction_ids).delete() diff --git a/packages/types/src/pragmatic.d.ts b/packages/types/src/pragmatic.d.ts index ca47e2d37a..439e2b54fb 100644 --- a/packages/types/src/pragmatic.d.ts +++ b/packages/types/src/pragmatic.d.ts @@ -8,18 +8,27 @@ export type TDropTargetMiscellaneousData = { isActiveDueToStickiness: boolean; }; -export interface IPragmaticDropPayload { - location: { - initial: { - dropTargets: (TDropTarget & TDropTargetMiscellaneousData)[]; - }; - current: { - dropTargets: (TDropTarget & TDropTargetMiscellaneousData)[]; - }; - previous: { - dropTargets: (TDropTarget & TDropTargetMiscellaneousData)[]; - }; +export interface IPragmaticPayloadLocation { + initial: { + dropTargets: (TDropTarget & TDropTargetMiscellaneousData)[]; }; + current: { + dropTargets: (TDropTarget & TDropTargetMiscellaneousData)[]; + }; + previous: { + dropTargets: (TDropTarget & TDropTargetMiscellaneousData)[]; + }; +} + +export interface IPragmaticDropPayload { + location: IPragmaticPayloadLocation; source: TDropTarget; self: TDropTarget & TDropTargetMiscellaneousData; } + +export type InstructionType = + | "reparent" + | "reorder-above" + | "reorder-below" + | "make-child" + | "instruction-blocked"; \ No newline at end of file diff --git a/packages/ui/src/drop-indicator.tsx b/packages/ui/src/drop-indicator.tsx index 228c1a3301..7ffc83a4ba 100644 --- a/packages/ui/src/drop-indicator.tsx +++ b/packages/ui/src/drop-indicator.tsx @@ -3,9 +3,12 @@ import { cn } from "../helpers"; type Props = { isVisible: boolean; + classNames?: string; }; export const DropIndicator = (props: Props) => { + const { isVisible, classNames = "" } = props; + return (
{ before:left-0 before:relative before:block before:top-[-2px] before:h-[6px] before:w-[6px] before:rounded after:left-[calc(100%-6px)] after:relative after:block after:top-[-8px] after:h-[6px] after:w-[6px] after:rounded`, { - "bg-custom-primary-100 before:bg-custom-primary-100 after:bg-custom-primary-100": props.isVisible, - } + "bg-custom-primary-100 before:bg-custom-primary-100 after:bg-custom-primary-100": isVisible, + }, + classNames )} /> ); diff --git a/packages/ui/src/form-fields/textarea.tsx b/packages/ui/src/form-fields/textarea.tsx index 271b76d83a..de225d68f3 100644 --- a/packages/ui/src/form-fields/textarea.tsx +++ b/packages/ui/src/form-fields/textarea.tsx @@ -1,6 +1,8 @@ -import * as React from "react"; +import React, { useRef } from "react"; // helpers import { cn } from "../../helpers"; +// hooks +import { useAutoResizeTextArea } from "../hooks/use-auto-resize-textarea"; export interface TextAreaProps extends React.TextareaHTMLAttributes { mode?: "primary" | "transparent"; @@ -8,21 +10,6 @@ export interface TextAreaProps extends React.TextareaHTMLAttributes when the value changes. -const useAutoSizeTextArea = (textAreaRef: HTMLTextAreaElement | null, value: any) => { - React.useEffect(() => { - if (textAreaRef) { - // We need to reset the height momentarily to get the correct scrollHeight for the textarea - textAreaRef.style.height = "0px"; - const scrollHeight = textAreaRef.scrollHeight; - - // We then set the height directly, outside of the render loop - // Trying to set this with state or a ref will product an incorrect value. - textAreaRef.style.height = scrollHeight + "px"; - } - }, [textAreaRef, value]); -}; - const TextArea = React.forwardRef((props, ref) => { const { id, @@ -35,10 +22,10 @@ const TextArea = React.forwardRef((props, re className = "", ...rest } = props; - - const textAreaRef = React.useRef(ref); - - useAutoSizeTextArea(textAreaRef?.current, value); + // refs + const textAreaRef = useRef(ref); + // auto re-size + useAutoResizeTextArea(textAreaRef); return (