Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WEB-1435] chore: conflict free issue description #6120

Open
wants to merge 36 commits into
base: preview
Choose a base branch
from

Conversation

aaryan610
Copy link
Collaborator

@aaryan610 aaryan610 commented Nov 29, 2024

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced description_binary field to issue serializers for handling binary descriptions.
    • Added new endpoints for updating and retrieving issue descriptions, including support for archived issues.
    • Added methods for updating and retrieving binary descriptions in various view sets.
    • Implemented collaborative rich text editor components for enhanced editing capabilities.
    • New hooks and utilities for managing binary data and collaborative editing.
    • Added updateDescriptionBinary methods in services for updating issue descriptions using binary payloads.
  • Bug Fixes

    • Improved error handling across various services and components related to issue descriptions.
  • Documentation

    • Updated type definitions to include new properties and methods for managing issue descriptions.
  • Chores

    • Cleaned up unused imports and simplified code structure in several components and services.

NarayanBavisetti and others added 30 commits October 24, 2024 14:26
…keplane/plane into chore/conflict-free-issue-description
@aaryan610 aaryan610 marked this pull request as draft November 29, 2024 10:26
Copy link
Contributor

coderabbitai bot commented Nov 29, 2024

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This pull request introduces comprehensive changes to handle issue descriptions in binary format across multiple components of the Plane application. The modifications span serializers, views, URLs, services, and frontend components, enabling a more flexible approach to storing and managing issue descriptions. Key changes include adding a description_binary field to serializers, creating new URL endpoints for description management, and updating frontend components to support binary description handling.

Changes

File Change Summary
apiserver/plane/app/serializers/draft.py Added description_binary field to DraftIssueDetailSerializer
apiserver/plane/app/serializers/issue.py Introduced Base64BinaryField for handling binary data
apiserver/plane/app/urls/intake.py Added new URL for inbox issue descriptions
apiserver/plane/app/urls/issue.py Added URLs for issue and archived issue descriptions
apiserver/plane/app/urls/workspace.py Added URL for draft issue descriptions
apiserver/plane/app/views/intake/base.py Added methods to retrieve and update issue descriptions
apiserver/plane/app/views/issue/archive.py Added method to retrieve archived issue descriptions
apiserver/plane/app/views/issue/base.py Added methods to retrieve and update issue descriptions
apiserver/plane/app/views/workspace/draft.py Added methods for draft issue description management
apiserver/plane/settings/common.py Added LIVE_BASE_URL environment variable
web/core/services/issue/issue.service.ts Added updateDescriptionBinary method
web/core/store/issue/issue-details/issue.store.ts Updated IssueStore to support binary description updates
web/core/hooks/use-issue-description.ts Introduced a hook for managing issue description state

Sequence Diagram

sequenceDiagram
    participant Client
    participant Frontend
    participant Backend
    participant Database

    Client->>Frontend: Request Issue Description
    Frontend->>Backend: Fetch Description Binary
    Backend->>Database: Retrieve Description
    Database-->>Backend: Return Binary Description
    Backend-->>Frontend: Send Binary Description
    Frontend->>Frontend: Convert Binary to HTML/JSON
    Frontend-->>Client: Display Description
Loading

Possibly related PRs

Suggested Labels

🧹chore, 🌟enhancement

Suggested Reviewers

  • SatishGandham
  • sriramveeraghanta

Poem

🐰 A Rabbit's Ode to Binary Bliss 🐰
In bytes and bits, descriptions dance,
A digital canvas, a coding trance.
From HTML to binary's embrace,
Our issues now have a flexible face!
Hop, hop, hooray for data's new might! 🚀


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@aaryan610 aaryan610 changed the title chore: conflict free issue description and issue version history chore: conflict free issue description Dec 2, 2024
@aaryan610 aaryan610 changed the title chore: conflict free issue description [WEB-1435] chore: conflict free issue description Dec 2, 2024
@aaryan610 aaryan610 marked this pull request as ready for review December 2, 2024 10:55
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

🧹 Outside diff range and nitpick comments (26)
web/core/hooks/use-page-fallback.ts (1)

Line range hint 32-38: Consider adding error handling for binary conversion

While the use of convertBinaryDataToBase64String is a good improvement, consider adding try-catch error handling around the conversion and update process to gracefully handle any potential conversion failures.

-    const encodedBinary = convertBinaryDataToBase64String(binary);
-
-    await updatePageDescription({
-      description_binary: encodedBinary,
-      description_html: html,
-      description: json,
-    });
+    try {
+      const encodedBinary = convertBinaryDataToBase64String(binary);
+      await updatePageDescription({
+        description_binary: encodedBinary,
+        description_html: html,
+        description: json,
+      });
+    } catch (error) {
+      console.error('Failed to convert or update description:', error);
+      // Consider adding error handling/reporting mechanism
+    }
web/core/components/editor/rich-text-editor/collaborative-editor.tsx (1)

35-36: Filter out undefined member details

getUserDetails may return undefined if a user is not found. To prevent potential errors, filter out any undefined values when mapping projectMemberIds to projectMemberDetails.

Apply this diff to improve the code:

 const projectMemberDetails = projectMemberIds
-  ?.map((id) => getUserDetails(id) as IUserLite);
+  ?.map((id) => getUserDetails(id))
+  .filter((user): user is IUserLite => user !== undefined);
apiserver/plane/app/views/workspace/draft.py (2)

388-389: Provide informative error message on server error

When the external service does not return a 200 response, the method returns a 500 error without any message. Consider adding an error message to help clients understand the cause of the failure.

Apply this diff to enhance the error response:

     return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+    return Response(
+        {"error": "Failed to update the issue description"},
+        status=status.HTTP_500_INTERNAL_SERVER_ERROR,
+    )

334-339: Eliminate code duplication in stream_data function

The stream_data function is identical in both retrieve_description and update_description methods. Refactor this code to eliminate duplication by extracting it into a separate method.

Here's how you might refactor the code:

def _stream_description_data(self, binary_data):
    if binary_data:
        yield binary_data
    else:
        yield b""

Then, update both methods to use this helper:

# In retrieve_description
-    def stream_data():
-        if binary_data:
-            yield binary_data
-        else:
-            yield b""
-
     response = StreamingHttpResponse(
-        stream_data(), content_type="application/octet-stream"
+        self._stream_description_data(binary_data), content_type="application/octet-stream"
     )

# In update_description
-    def stream_data():
-        if issue.description_binary:
-            yield issue.description_binary
-        else:
-            yield b""
-
     response = StreamingHttpResponse(
-        stream_data(), content_type="application/octet-stream"
+        self._stream_description_data(issue.description_binary), content_type="application/octet-stream"
     )

Also applies to: 375-380

packages/editor/src/core/components/editors/rich-text/index.ts (1)

1-2: Ensure consistent export order

For better readability and consistency, consider grouping and ordering the export statements logically, perhaps by placing collaborative editors together and standard editors afterwards.

Suggested change:

 export * from "./collaborative-editor";
 export * from "./collaborative-read-only-editor";
 export * from "./editor";
 export * from "./read-only-editor";
packages/editor/src/core/hooks/use-collaborative-rich-text-read-only-editor.ts (2)

46-49: Consider making SideMenuExtension configuration more flexible.

The SideMenuExtension has hardcoded values for aiEnabled and dragDropEnabled. Consider making these configurable through props for better reusability.

-      SideMenuExtension({
-        aiEnabled: false,
-        dragDropEnabled: true,
-      }),
+      SideMenuExtension({
+        aiEnabled: props.aiEnabled ?? false,
+        dragDropEnabled: props.dragDropEnabled ?? true,
+      }),

63-65: Consider exposing the provider in the return value.

For better integration with the collaborative features, consider exposing the provider instance in the return value. This would allow parent components to access provider state or handle reconnection scenarios.

   return {
     editor,
+    provider,
   };
packages/editor/src/core/types/collaboration-hook.ts (1)

Line range hint 22-34: Add JSDoc comments to improve type documentation

The base type TCollaborativeEditorHookCommonProps would benefit from documentation explaining the purpose and usage of each property.

Consider adding JSDoc:

+/**
+ * Common properties shared across all collaborative editor hooks
+ */
 type TCollaborativeEditorHookCommonProps = {
+  /** List of extensions to disable in the editor */
   disabledExtensions: TExtensions[];
+  /** CSS class name for the editor container */
   editorClassName: string;
   // ... rest of the properties
 };
web/core/hooks/use-issue-description.ts (3)

9-13: Consider making updateDescription required for non-readonly mode

The optional updateDescription parameter suggests the hook can be used without update capability, but this isn't clearly documented.

Consider splitting into separate types:

type TReadOnlyArgs = {
  descriptionBinary: string | null;
  descriptionHTML: string | null;
};

type TEditableArgs = TReadOnlyArgs & {
  updateDescription: (data: string) => Promise<ArrayBuffer>;
};

20-34: Add retry mechanism for description updates

The error handling for updateDescription could be more robust by implementing retries for transient failures.

Consider implementing a retry mechanism:

 const resolveConflictsAndUpdateDescription = useCallback(
   async (encodedDescription: string, editorRef: EditorRefApi | null) => {
     if (!updateDescription) return;
+    const MAX_RETRIES = 3;
+    let attempts = 0;
     try {
-      const conflictFreeEncodedDescription = await updateDescription(encodedDescription);
+      while (attempts < MAX_RETRIES) {
+        try {
+          const conflictFreeEncodedDescription = await updateDescription(encodedDescription);
+          const decodedDescription = conflictFreeEncodedDescription
+            ? new Uint8Array(conflictFreeEncodedDescription)
+            : new Uint8Array();
+          editorRef?.setProviderDocument(decodedDescription);
+          break;
+        } catch (error) {
+          attempts++;
+          if (attempts === MAX_RETRIES) throw error;
+          await new Promise(resolve => setTimeout(resolve, 1000 * attempts));
+        }
+      }
     } catch (error) {
       console.error("Error while updating description", error);
     }
   },
   [updateDescription]
 );

36-48: Add cleanup for state management

The effect should clean up the state when the component unmounts.

Consider adding cleanup:

 useEffect(() => {
   if (descriptionBinary) return;
   if (savedDescriptionBinary) {
     const savedDescriptionBuffer = convertBase64StringToBinaryData(savedDescriptionBinary);
     const decodedSavedDescription = savedDescriptionBuffer
       ? new Uint8Array(savedDescriptionBuffer)
       : new Uint8Array();
     setDescriptionBinary(decodedSavedDescription);
   } else {
     const decodedDescriptionHTML = getBinaryDataFromRichTextEditorHTMLString(descriptionHTML ?? "<p></p>");
     setDescriptionBinary(decodedDescriptionHTML);
   }
+  return () => {
+    setDescriptionBinary(null);
+  };
 }, [descriptionBinary, descriptionHTML, savedDescriptionBinary]);
packages/editor/src/core/components/editors/rich-text/collaborative-editor.tsx (2)

30-41: Consider memoizing the hook configuration object

The configuration object passed to useCollaborativeRichTextEditor is recreated on every render.

Consider using useMemo:

+ const editorConfig = React.useMemo(
+   () => ({
      disabledExtensions,
      editorClassName,
      fileHandler,
      forwardedRef,
      id,
      mentionHandler,
      onChange,
      placeholder,
      tabIndex,
      value,
+   }),
+   [disabledExtensions, editorClassName, fileHandler, forwardedRef, id, mentionHandler, onChange, placeholder, tabIndex, value]
+ );

- const { editor } = useCollaborativeRichTextEditor({
-   disabledExtensions,
-   editorClassName,
-   fileHandler,
-   forwardedRef,
-   id,
-   mentionHandler,
-   onChange,
-   placeholder,
-   tabIndex,
-   value,
- });
+ const { editor } = useCollaborativeRichTextEditor(editorConfig);

51-63: Consider error boundary for editor failures

The editor rendering could potentially fail during collaborative operations.

Consider wrapping the editor content with an error boundary to gracefully handle runtime errors:

+ import { ErrorBoundary } from '@/components/error-boundary';

  return (
    <EditorContainer
      displayConfig={displayConfig}
      editor={editor}
      editorContainerClassName={editorContainerClassName}
      id={id}
    >
+     <ErrorBoundary fallback={<div>Error loading editor</div>}>
        <EditorBubbleMenu editor={editor} />
        <div className="flex flex-col">
          <EditorContentWrapper editor={editor} id={id} tabIndex={tabIndex} />
        </div>
+     </ErrorBoundary>
    </EditorContainer>
  );
packages/editor/src/core/components/editors/rich-text/collaborative-read-only-editor.tsx (1)

37-41: Consider memoizing the editorContainerClassName.

Since getEditorClassNames is called on every render with static values, consider memoizing the result using useMemo.

-  const editorContainerClassName = getEditorClassNames({
+  const editorContainerClassName = React.useMemo(() => getEditorClassNames({
     noBorder: true,
     borderOnFocus: false,
     containerClassName,
-  });
+  }), [containerClassName]);
web/core/components/editor/rich-text-editor/collaborative-read-only-editor.tsx (1)

64-64: Consider making the padding configurable.

The hardcoded pl-3 class in the container className override might not be suitable for all use cases.

-      containerClassName={cn(props.containerClassName, "relative pl-3")}
+      containerClassName={cn(props.containerClassName, "relative", props.noPadding ? "" : "pl-3")}
live/src/core/lib/page.ts (1)

50-51: Consider adding retry mechanism for HTML transformation.

The HTML to binary transformation is critical but lacks retry logic. Consider adding retries for transient failures.

 const pageDetails = await pageService.fetchDetails(workspaceSlug, projectId, pageId, cookie);
-const contentBinary = getBinaryDataFromDocumentEditorHTMLString(pageDetails.description_html ?? "<p></p>");
+const contentBinary = await retry(
+  () => getBinaryDataFromDocumentEditorHTMLString(pageDetails.description_html ?? "<p></p>"),
+  { retries: 3, delay: 1000 }
+);

Also applies to: 54-54

live/src/server.ts (1)

62-79: Verify error handling completeness in conflict resolution endpoint

The endpoint implementation looks solid with proper error handling, but consider a few improvements:

  1. Add request body validation for the structure of original_document and updates
  2. Consider adding request size limits to prevent DOS attacks
  3. Add specific error types in the response for better client-side handling
 router.post("/resolve-document-conflicts", (req, res) => {
+  // Add request size validation
+  if (req.get('content-length') > 1024 * 1024) { // 1MB limit
+    res.status(413).send({
+      error: 'payload_too_large',
+      message: 'Request body too large'
+    });
+    return;
+  }
+
   const { original_document, updates } = req.body as TResolveConflictsRequestBody;
   try {
     if (original_document === undefined || updates === undefined) {
       res.status(400).send({
+        error: 'missing_fields',
         message: "Missing required fields",
       });
       return;
     }
+    // Add structure validation
+    if (!Array.isArray(updates)) {
+      res.status(400).send({
+        error: 'invalid_updates',
+        message: "Updates must be an array"
+      });
+      return;
+    }
     const resolvedDocument = resolveDocumentConflicts(req.body);
     res.status(200).json(resolvedDocument);
   } catch (error) {
     manualLogger.error("Error in /resolve-document-conflicts endpoint:", error);
     res.status(500).send({
+      error: 'internal_error',
       message: "Internal server error",
     });
   }
 });
web/core/components/issues/peek-overview/issue-detail.tsx (1)

102-117: Strengthen the description_binary check

The current check !== undefined might allow null values through. Consider a more robust check.

-{issue.description_binary !== undefined && (
+{issue.description_binary != null && (

Also, consider adding a loading state:

+{issue.description_binary === undefined && (
+  <div className="-ml-3 h-32 w-full animate-pulse rounded-md bg-gray-100" />
+)}
packages/editor/src/core/hooks/use-read-only-editor.ts (1)

Line range hint 100-108: Consider validating providerDocument state

While the implementation works, it might benefit from additional validation to ensure the providerDocument is in a valid state before encoding.

-const documentBinary = providerDocument ? Y.encodeStateAsUpdate(providerDocument) : null;
+const documentBinary = providerDocument?.store ? Y.encodeStateAsUpdate(providerDocument) : null;

Also, consider adding JSDoc documentation for the new prop:

/**
 * Optional Y.Doc instance for direct document state access.
 * When provided, takes precedence over provider.document for binary encoding.
 */
providerDocument?: Y.Doc;
web/core/services/page/project-page.service.ts (1)

Line range hint 131-142: Enhance error handling for binary data fetching

While the type specification and headers are correct, the error handling could be improved to handle binary response errors more gracefully.

Consider this enhancement:

 async fetchDescriptionBinary(workspaceSlug: string, projectId: string, pageId: string): Promise<ArrayBuffer> {
   return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/pages/${pageId}/description/`, {
     headers: {
       "Content-Type": "application/octet-stream",
     },
     responseType: "arraybuffer",
   })
     .then((response) => response?.data)
     .catch((error) => {
-      throw error?.response?.data;
+      // Handle binary response errors
+      const errorMessage = error.response?.headers?.['content-type']?.includes('application/json')
+        ? JSON.parse(new TextDecoder().decode(error.response.data))
+        : { detail: 'Failed to fetch binary description' };
+      throw errorMessage;
     });
 }
web/core/components/pages/editor/page-root.tsx (1)

53-55: Enhance error message with more context

While the error handling is good, the error message could be more informative by including the context of the operation.

Consider this enhancement:

 if (!page.id) {
-  throw new Error("Required fields missing while fetching binary description");
+  throw new Error(`Required field 'id' is missing while fetching binary description for page "${page.name || 'unknown'}"`);
 }
web/core/components/inbox/content/issue-root.tsx (2)

Line range hint 116-131: Fix inconsistent event tracking payload

The failure case is incorrectly tracking the event as "SUCCESS".

 } catch {
   setToast({
     title: "Issue update failed",
     type: TOAST_TYPE.ERROR,
     message: "Issue update failed",
   });
   captureIssueEvent({
     eventName: "Inbox issue updated",
-    payload: { state: "SUCCESS", element: "Inbox" },
+    payload: { state: "FAILED", element: "Inbox" },
     updates: {
       changed_property: Object.keys(data).join(","),
       change_details: Object.values(data).join(","),
     },
     path: pathname,
   });
 }

Line range hint 47-57: Prevent potential memory leak in setTimeout

The setTimeout should be cleaned up when the component unmounts.

 useEffect(() => {
+  let timeoutId: NodeJS.Timeout;
   if (isSubmitting === "submitted") {
     setShowAlert(false);
-    setTimeout(async () => {
+    timeoutId = setTimeout(async () => {
       setIsSubmitting("saved");
     }, 3000);
   } else if (isSubmitting === "submitting") {
     setShowAlert(true);
   }
+  return () => {
+    if (timeoutId) clearTimeout(timeoutId);
+  };
 }, [isSubmitting, setShowAlert, setIsSubmitting]);
web/core/store/inbox/inbox-issue.store.ts (1)

180-198: Consider enhancing error handling and state management.

The implementation looks good but could benefit from some improvements:

  1. Consider preserving the previous description state for rollback on failure
  2. The error message could include more details about what went wrong

Here's a suggested improvement:

 updateIssueDescription = async (descriptionBinary: string): Promise<ArrayBuffer> => {
+  const previousDescription = this.issue.description_binary;
   try {
     if (!this.issue.id) throw new Error("Issue id is missing");
     const res = await this.inboxIssueService.updateDescriptionBinary(
       this.workspaceSlug,
       this.projectId,
       this.issue.id,
       {
         description_binary: descriptionBinary,
       }
     );
     set(this.issue, "description_binary", descriptionBinary);
     // fetching activity
     this.fetchIssueActivity();
     return res;
-  } catch {
-    throw new Error("Failed to update local issue description");
+  } catch (error) {
+    set(this.issue, "description_binary", previousDescription);
+    throw new Error(`Failed to update issue description: ${error.message}`);
   }
 };
web/core/components/issues/issue-detail/root.tsx (1)

128-134: Consider enhancing error handling.

While the error handling is present, it could be improved by:

  1. Including more specific error information
  2. Adding error logging
 updateDescription: async (workspaceSlug, projectId, issueId, descriptionBinary) => {
   try {
     return await updateIssueDescription(workspaceSlug, projectId, issueId, descriptionBinary);
   } catch (error) {
-    throw new Error("Failed to update issue description");
+    console.error("Error updating issue description:", error);
+    throw new Error(`Failed to update issue description: ${error.message}`);
   }
 },
apiserver/plane/app/serializers/issue.py (1)

645-656: Add docstring and consider field selection optimization.

The IssueDetailSerializer would benefit from:

  1. Documentation explaining the purpose and usage of binary description
  2. Field selection optimization to avoid loading both description formats when not needed

Consider applying these enhancements:

 class IssueDetailSerializer(IssueSerializer):
+    """
+    Serializer for detailed issue representation including binary description support.
+    
+    The binary description field allows storing rich text content in a conflict-free format,
+    encoded as base64 to ensure safe transmission in JSON.
+    """
     description_html = serializers.CharField()
-    description_binary = Base64BinaryField()
+    description_binary = Base64BinaryField(
+        max_size=10 * 1024 * 1024,  # 10MB limit
+        help_text="Base64 encoded binary representation of the description"
+    )
     is_subscribed = serializers.BooleanField(read_only=True)

     class Meta(IssueSerializer.Meta):
         fields = IssueSerializer.Meta.fields + [
             "description_html",
             "is_subscribed",
             "description_binary",
         ]
         read_only_fields = fields
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        # Optimize field selection based on request
+        if self.context.get('request'):
+            fields = self.context['request'].query_params.get('fields', '').split(',')
+            if 'description_binary' not in fields:
+                self.fields.pop('description_binary', None)
+            if 'description_html' not in fields:
+                self.fields.pop('description_html', None)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 3c6006d and 9e5b104.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (61)
  • apiserver/plane/app/serializers/draft.py (1 hunks)
  • apiserver/plane/app/serializers/issue.py (2 hunks)
  • apiserver/plane/app/urls/intake.py (1 hunks)
  • apiserver/plane/app/urls/issue.py (2 hunks)
  • apiserver/plane/app/urls/workspace.py (1 hunks)
  • apiserver/plane/app/views/intake/base.py (3 hunks)
  • apiserver/plane/app/views/issue/archive.py (2 hunks)
  • apiserver/plane/app/views/issue/base.py (3 hunks)
  • apiserver/plane/app/views/workspace/draft.py (2 hunks)
  • apiserver/plane/settings/common.py (1 hunks)
  • live/.prettierignore (1 hunks)
  • live/.prettierrc (1 hunks)
  • live/package.json (1 hunks)
  • live/src/core/helpers/page.ts (0 hunks)
  • live/src/core/lib/page.ts (3 hunks)
  • live/src/core/resolve-conflicts.ts (1 hunks)
  • live/src/server.ts (4 hunks)
  • packages/editor/src/core/components/editors/document/collaborative-editor.tsx (2 hunks)
  • packages/editor/src/core/components/editors/document/collaborative-read-only-editor.tsx (2 hunks)
  • packages/editor/src/core/components/editors/editor-wrapper.tsx (2 hunks)
  • packages/editor/src/core/components/editors/rich-text/collaborative-editor.tsx (1 hunks)
  • packages/editor/src/core/components/editors/rich-text/collaborative-read-only-editor.tsx (1 hunks)
  • packages/editor/src/core/components/editors/rich-text/index.ts (1 hunks)
  • packages/editor/src/core/helpers/yjs-utils.ts (1 hunks)
  • packages/editor/src/core/helpers/yjs.ts (0 hunks)
  • packages/editor/src/core/hooks/use-collaborative-document-editor.ts (3 hunks)
  • packages/editor/src/core/hooks/use-collaborative-document-read-only-editor.ts (2 hunks)
  • packages/editor/src/core/hooks/use-collaborative-rich-text-editor.ts (1 hunks)
  • packages/editor/src/core/hooks/use-collaborative-rich-text-read-only-editor.ts (1 hunks)
  • packages/editor/src/core/hooks/use-editor.ts (4 hunks)
  • packages/editor/src/core/hooks/use-read-only-editor.ts (3 hunks)
  • packages/editor/src/core/providers/custom-collaboration-provider.ts (1 hunks)
  • packages/editor/src/core/providers/index.ts (1 hunks)
  • packages/editor/src/core/types/collaboration-hook.ts (3 hunks)
  • packages/editor/src/core/types/editor.ts (3 hunks)
  • packages/editor/src/core/types/index.ts (1 hunks)
  • packages/editor/src/index.ts (2 hunks)
  • packages/editor/src/lib.ts (1 hunks)
  • packages/types/src/issues/issue.d.ts (1 hunks)
  • web/core/components/core/modals/gpt-assistant-popover.tsx (1 hunks)
  • web/core/components/editor/rich-text-editor/collaborative-editor.tsx (1 hunks)
  • web/core/components/editor/rich-text-editor/collaborative-read-only-editor.tsx (1 hunks)
  • web/core/components/editor/rich-text-editor/index.ts (1 hunks)
  • web/core/components/inbox/content/issue-root.tsx (9 hunks)
  • web/core/components/inbox/modals/create-modal/issue-description.tsx (1 hunks)
  • web/core/components/issues/description-input.tsx (1 hunks)
  • web/core/components/issues/issue-detail/main-content.tsx (2 hunks)
  • web/core/components/issues/issue-detail/root.tsx (4 hunks)
  • web/core/components/issues/peek-overview/issue-detail.tsx (2 hunks)
  • web/core/components/issues/peek-overview/root.tsx (3 hunks)
  • web/core/components/pages/editor/page-root.tsx (1 hunks)
  • web/core/components/profile/activity/activity-list.tsx (1 hunks)
  • web/core/components/profile/activity/profile-activity-list.tsx (1 hunks)
  • web/core/hooks/use-issue-description.ts (1 hunks)
  • web/core/hooks/use-page-fallback.ts (2 hunks)
  • web/core/services/inbox/inbox-issue.service.ts (2 hunks)
  • web/core/services/issue/issue.service.ts (2 hunks)
  • web/core/services/page/project-page.service.ts (1 hunks)
  • web/core/store/inbox/inbox-issue.store.ts (3 hunks)
  • web/core/store/issue/issue-details/issue.store.ts (5 hunks)
  • web/core/store/issue/issue-details/root.store.ts (2 hunks)
💤 Files with no reviewable changes (2)
  • packages/editor/src/core/helpers/yjs.ts
  • live/src/core/helpers/page.ts
✅ Files skipped from review due to trivial changes (8)
  • apiserver/plane/settings/common.py
  • live/.prettierrc
  • packages/editor/src/core/types/index.ts
  • packages/editor/src/core/providers/index.ts
  • packages/editor/src/lib.ts
  • live/.prettierignore
  • web/core/components/editor/rich-text-editor/index.ts
  • web/core/components/profile/activity/profile-activity-list.tsx
🔇 Additional comments (65)
web/core/hooks/use-page-fallback.ts (2)

3-3: LGTM! Clean import organization

The addition of convertBinaryDataToBase64String import is well-organized and clearly indicates its purpose.


11-11: Great improvement in type safety!

The change from Promise<any> to Promise<ArrayBuffer> provides better type safety and clarity about the expected return type.

Let's verify that this type change is consistently applied across the codebase:

✅ Verification successful

Let me gather more information to verify the type consistency.


Let me check the actual implementation to verify the type consistency.


Type change is correctly implemented and consistent

The change from Promise<any> to Promise<ArrayBuffer> is properly aligned with the implementation. The service method fetchDescriptionBinary returns Promise<ArrayBuffer> and all usages across the codebase handle the binary data appropriately, including the conversion to Uint8Array where needed.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining Promise<any> types in related files
# and verify consistent usage of ArrayBuffer return type

# Search for fetchPageDescription implementations
ast-grep --pattern 'fetchPageDescription = async () => { $$$ }'

# Search for related type definitions
rg -l "fetchPageDescription.*Promise"

Length of output: 141


Script:

#!/bin/bash
# Let's check the actual implementation and usage of fetchPageDescription
rg "fetchPageDescription" -A 5 -B 5

# Also check for any ArrayBuffer type usage in related contexts
rg "ArrayBuffer" -A 2 -B 2

Length of output: 73874


Script:

# Check the implementation of fetchDescriptionBinary to verify return type
rg -A 5 "fetchDescriptionBinary" web/core/services/page/project-page.service.ts

# Also check for any calls to this method
rg -A 2 "fetchDescriptionBinary\(" 

Length of output: 1542

packages/editor/src/core/providers/custom-collaboration-provider.ts (1)

48-54: Ensure hasSynced is managed correctly

The hasSynced property is initialized but not updated within the class. Ensure that hasSynced is set to true when synchronization is complete so that documentUpdateHandler functions as intended.

apiserver/plane/app/views/intake/base.py (4)

616-616: Handle exception when issue does not exist

Using get() without handling the DoesNotExist exception can lead to server errors if the issue does not exist. Modify to use filter().first() and handle the None case.

Apply this diff to fix the issue:

 def update_description(self, request, slug, project_id, pk):
-    issue = Issue.objects.get(
+    issue = Issue.objects.filter(
         workspace__slug=slug, project_id=project_id, pk=pk
+    ).first()
+    if issue is None:
+        return Response({"error": "Issue not found"}, status=404)

627-627: Validate 'updates' field in request data

The updates field from request.data is used without validation. Ensure that description_binary is properly validated to prevent potential errors or security issues.


665-665: Provide informative error message on server error

When the external service fails to process the request, a generic 500 error is returned without any message. Add an error message to clarify the cause for clients.

Apply this diff to enhance the error response:

     return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+    return Response(
+        {"error": "Failed to update the issue description"},
+        status=status.HTTP_500_INTERNAL_SERVER_ERROR,
+    )

600-605: Eliminate code duplication in stream_data function

The stream_data function is duplicated in both retrieve_description and update_description methods. Refactor to extract this into a shared helper method.

Refactor example:

def _stream_description_data(self, binary_data):
    if binary_data:
        yield binary_data
    else:
        yield b""

Then, update both methods accordingly.

Also applies to: 652-656

apiserver/plane/app/views/issue/base.py (4)

708-710: Handle exception when issue does not exist

Using get() without exception handling can cause server errors if the issue does not exist. Use filter().first() and check for None to handle this case appropriately.

Apply this diff to fix the issue:

 def update_description(self, request, slug, project_id, pk):
-    issue = Issue.issue_objects.get(
+    issue = Issue.issue_objects.filter(
         workspace__slug=slug, project_id=project_id, pk=pk
+    ).first()
+    if issue is None:
+        return Response({"error": "Issue not found"}, status=404)

719-719: Validate 'updates' field in request data

Ensure that request.data.get("description_binary") is validated before use to prevent potential security risks or data processing errors.


757-758: Provide informative error message on server error

When the external service fails, the method returns a generic 500 error without explanation. Providing an error message can help clients understand the failure.

Apply this diff to enhance the error response:

     return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+    return Response(
+        {"error": "Failed to update the issue description"},
+        status=status.HTTP_500_INTERNAL_SERVER_ERROR,
+    )

693-698: Eliminate code duplication in stream_data function

The stream_data function is duplicated in both retrieve_description and update_description. Consolidate this code into a single helper method to improve maintainability.

Refactor example:

def _stream_description_data(self, binary_data):
    if binary_data:
        yield binary_data
    else:
        yield b""

Update both methods to use the helper.

Also applies to: 743-748

packages/editor/src/index.ts (2)

13-14: Export statements are appropriate

The addition of CollaborativeRichTextEditorWithRef and CollaborativeRichTextReadOnlyEditorWithRef enhances the editor's functionality.


30-30: Update export path for Yjs utilities

The change from yjs to yjs-utils accurately reflects the updated helper utilities.

packages/editor/src/core/components/editors/editor-wrapper.tsx (1)

1-1: LGTM! Type improvement using AnyExtension.

The change from Extension<any, any>[] to AnyExtension[] is a good improvement that better represents the type and removes the use of any.

Also applies to: 15-15

live/src/core/resolve-conflicts.ts (1)

8-17: LGTM! Well-structured type definitions

The types clearly define the contract for request and response, making the API surface explicit and type-safe.

packages/editor/src/core/types/collaboration-hook.ts (1)

49-69: LGTM! Well-structured type hierarchy

The separation between rich text and document editor types is clean and follows good inheritance patterns. The reuse of common props reduces duplication.

apiserver/plane/app/urls/intake.py (1)

56-65: LGTM! Verify endpoint permissions

The new endpoint follows RESTful conventions and maintains consistency with existing patterns. The path structure and HTTP methods are appropriate for handling issue descriptions.

Let's verify the permissions setup:

✅ Verification successful

Based on the code analysis, I can now generate the final response:

Permissions are properly configured for the inbox issue description endpoints

The IntakeIssueViewSet inherits from BaseViewSet and both retrieve_description and update_description methods are explicitly decorated with @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]), which follows the same permission pattern as other issue-related operations in the codebase. This ensures that:

  • Only authenticated users with appropriate roles (Admin, Member, or Guest) can access these endpoints
  • The permissions are consistent with the rest of the issue management functionality
  • The implementation follows the project's established permission patterns
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if permissions are properly configured for the new endpoint

# Search for permission classes in the IntakeIssueViewSet
ast-grep --pattern 'class IntakeIssueViewSet {
  $$$
  retrieve_description($$$) {
    $$$
  }
  update_description($$$) {
    $$$
  }
}'

# Check for any existing permission tests
rg -l "test.*inbox.*issue.*description" -g "test_*.py"

Length of output: 196


Script:

#!/bin/bash
# Let's try a different approach to find the IntakeIssueViewSet implementation

# Find the file containing IntakeIssueViewSet
fd "IntakeIssueViewSet" --type f

# Search for permission_classes in the found files
rg "permission_classes" -A 5 -B 5

# Look for any test files related to inbox issues
rg "class.*Test.*Inbox.*Issue" -g "test_*.py"

Length of output: 71240


Script:

#!/bin/bash
# Let's find the IntakeIssueViewSet implementation and check its permissions

# Search for IntakeIssueViewSet class definition
rg "class IntakeIssueViewSet" -A 10

# Search for retrieve_description and update_description methods
rg "def (retrieve|update)_description" -A 10

Length of output: 7733


Script:

#!/bin/bash
# Let's check the permissions for IntakeIssueViewSet and its base class

# Find permission_classes in IntakeIssueViewSet file
rg "permission_classes" apiserver/plane/app/views/intake/base.py -B 5 -A 5

# Check if there are any specific permission overrides for description methods
rg "def get_permissions" apiserver/plane/app/views/intake/base.py -A 10

Length of output: 148


Script:

#!/bin/bash
# Let's check the entire IntakeIssueViewSet file content to understand its permissions

# Display the content of the file
cat apiserver/plane/app/views/intake/base.py

# Also check if it inherits from any base classes
rg "class.*BaseViewSet.*:" -A 5

Length of output: 49244

packages/editor/src/core/components/editors/document/collaborative-read-only-editor.tsx (1)

11-11: LGTM! Verify hook renaming consistency

The hook renaming improves clarity by being more specific about its purpose (Document + ReadOnly). The implementation remains functionally unchanged.

Let's verify the renaming consistency across the codebase:

Also applies to: 40-40

✅ Verification successful

Hook renaming is consistent across the codebase

The verification shows that:

  • The old hook name useReadOnlyCollaborativeEditor has been completely removed
  • The new naming pattern is consistently applied across similar hooks:
    • Document editors: useCollaborativeDocumentEditor, useCollaborativeDocumentReadOnlyEditor
    • Rich text editors: useCollaborativeRichTextEditor, useCollaborativeRichTextReadOnlyEditor
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining old hook references

# Search for any remaining references to the old hook name
rg "useReadOnlyCollaborativeEditor"

# Search for consistent naming pattern in other similar hooks
rg "useCollaborative.*Editor"

Length of output: 2346

packages/editor/src/core/components/editors/rich-text/collaborative-editor.tsx (2)

14-28: LGTM! Props interface is well-structured

The component's props interface is comprehensive and includes all necessary configuration options for a collaborative editor. Default values are properly handled.


66-70: LGTM! Proper ref forwarding implementation

The ref forwarding implementation follows React best practices and properly types the forwarded ref.

packages/editor/src/core/components/editors/rich-text/collaborative-read-only-editor.tsx (2)

14-35: LGTM! Props handling and hook usage are well-implemented.

The component properly destructures props with appropriate default values and passes them correctly to the custom hook.


60-70: LGTM! Proper ref forwarding implementation.

The component correctly implements ref forwarding with proper TypeScript types and display name.

packages/editor/src/core/components/editors/document/collaborative-editor.tsx (1)

11-11: Verify the impact of switching to useCollaborativeDocumentEditor.

The change from useCollaborativeEditor to useCollaborativeDocumentEditor seems to be a refactor to use a more specific hook. Let's verify that all consumers are updated accordingly.

Also applies to: 46-46

✅ Verification successful

The hook change appears to be correctly implemented

The search results show that the old hook useCollaborativeEditor is no longer used anywhere in the codebase, and the new useCollaborativeDocumentEditor hook is consistently used in the collaborative editor component. The implementation and all references are aligned.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any remaining usage of the old hook
rg "useCollaborativeEditor" --type ts --type tsx

# Search for all usages of the new hook to ensure consistency
rg "useCollaborativeDocumentEditor" --type ts --type tsx

Length of output: 161


Script:

#!/bin/bash
# Search for any remaining usage of the old hook without file type restrictions
rg "useCollaborativeEditor"

# Search for all usages of the new hook to ensure consistency
rg "useCollaborativeDocumentEditor"

Length of output: 575

web/core/components/editor/rich-text-editor/collaborative-read-only-editor.tsx (1)

19-27: LGTM! Well-structured type definition.

The type definition properly extends the base interface while omitting props that will be handled internally.

packages/editor/src/core/hooks/use-collaborative-document-read-only-editor.ts (2)

10-10: LGTM! Clear and descriptive renaming.

The hook and type renaming from useReadOnlyCollaborativeEditor to useCollaborativeDocumentReadOnlyEditor better reflects its purpose and context.

Also applies to: 12-12


85-85: LGTM! Proper provider document propagation.

The providerDocument property is correctly passed from provider.document to the useReadOnlyEditor hook.

live/src/core/lib/page.ts (4)

1-5: LGTM! More specific and descriptive imports.

The imports have been updated to use more specific function names that better describe their purpose with the document editor.


15-15: LGTM! Improved error handling.

The error message for invalid updatedDescription is now more precise and informative.

Also applies to: 18-18


69-69: LGTM! Proper fallback mechanism.

The code correctly handles empty binary data by falling back to HTML transformation.

Also applies to: 73-73


26-26: Verify binary data transformation and persistence.

The code correctly transforms and persists binary data, but we should verify the data integrity across the transformation pipeline.

Also applies to: 34-34

✅ Verification successful

Let me analyze the binary data transformation by looking at the implementation of these functions.


Let me search for the implementation of the binary data transformation functions and their error handling.


Let me check for error handling in the page.ts file where these transformations are used.


Based on the code analysis, I can now provide a final response about the binary data transformation verification:

Binary data transformation has proper error handling and logging in place

The transformation pipeline is well-structured with:

  • Clear conversion steps from HTML -> JSON -> Y.Doc -> Uint8Array
  • Error handling that logs transformation failures and re-throws errors for proper error propagation
  • Proper fallback handling with null coalescing for HTML input (descriptionHTML ?? "<p></p>")
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for potential data loss or corruption in binary transformations
# Look for error handling patterns in binary data transformations

# Search for binary data handling patterns
rg -A 3 'getAllDocumentFormatsFromDocumentEditorBinaryData|getBinaryDataFromDocumentEditorHTMLString'

# Look for error handling patterns around binary data
rg -A 3 'catch.*error.*binary'

Length of output: 67370


Script:

#!/bin/bash
# Search for the implementation of binary data transformation functions
ast-grep --pattern 'export const getAllDocumentFormatsFromDocumentEditorBinaryData = $_'

# Search for error handling around binary data transformations
rg -A 5 'catch.*error.*binary|error.*binary.*transform'

Length of output: 67537


Script:

#!/bin/bash
# Search for the implementation of binary data transformation functions
ast-grep --pattern 'export const getBinaryDataFromDocumentEditorHTMLString = $_'

# Search for error handling around these transformations
rg -A 5 'catch.*error.*transform|error.*transform.*binary'

Length of output: 1060


Script:

#!/bin/bash
# Look for error handling in page.ts around the binary transformations
rg -A 5 'try.*getAllDocumentFormatsFromDocumentEditorBinaryData|try.*getBinaryDataFromDocumentEditorHTMLString' live/src/core/lib/page.ts

# Also check for any error logging or handling related to these functions
rg -A 5 'manualLogger.error.*transform|catch.*transform' live/src/core/lib/page.ts

Length of output: 382

packages/types/src/issues/issue.d.ts (1)

53-53: LGTM! Type definition aligns with binary data support.

The addition of the optional description_binary field to the TIssue type properly supports the new binary description functionality.

web/core/components/inbox/modals/create-modal/issue-description.tsx (1)

13-13: Verify the simplified import path for RichTextEditor

The import path has been simplified from a specific path to a more general one. While this suggests better organization, we should ensure that the correct component is being imported.

✅ Verification successful

Import path change for RichTextEditor is valid

The simplified import path @/components/editor is correct. The codebase shows that:

  1. The RichTextEditor component is properly exported from the editor components directory
  2. The component is used consistently across multiple files with the same import pattern
  3. The implementation in web/core/components/editor/rich-text-editor/editor.tsx contains the full functionality needed, including mentions, file handling, and editor flagging
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the export of RichTextEditor from @/components/editor
# Expected: Find the export statement for RichTextEditor

# Check for RichTextEditor exports
ast-grep --pattern 'export { RichTextEditor }'

Length of output: 48


Script:

#!/bin/bash
# Let's try different approaches to find RichTextEditor

# 1. Look for any export of RichTextEditor (including default exports)
ast-grep --pattern 'export $_ RichTextEditor'

# 2. Look for the component definition
ast-grep --pattern 'const RichTextEditor = $_'

# 3. Search for files that might contain the editor component
fd editor

# 4. Search for RichTextEditor references in the codebase
rg "RichTextEditor" -A 2

Length of output: 25098

packages/editor/src/core/hooks/use-collaborative-document-editor.ts (2)

14-14: LGTM: Hook renamed for better clarity

The hook has been renamed from useCollaborativeEditor to useCollaborativeDocumentEditor, which better reflects its purpose.


105-105: Verify the impact of adding providerDocument

The addition of providerDocument to the editor configuration enhances the integration with the Hocuspocus provider.

✅ Verification successful

Based on the search results, I can generate the final response as we have sufficient information about the usage of providerDocument in the codebase.

The addition of providerDocument is consistent with the codebase's patterns

The providerDocument property is consistently used across multiple editor hooks in the codebase, including read-only and collaborative variants. It's properly integrated with Y.js (Y.Doc) for collaborative editing functionality, making this addition a valid enhancement.

  • Used in base hooks: use-editor.ts and use-read-only-editor.ts as an optional prop
  • Consistently passed as provider.document in all collaborative variants
  • Properly utilized for document state management with Y.js operations
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for other uses of providerDocument in the codebase
# Expected: Find related usage and potential impacts

rg "providerDocument" -A 3

Length of output: 3835

live/src/server.ts (2)

13-14: LGTM: Clean import organization

The imports are well-organized, grouping related functionality together.


101-101: LGTM: Clear logging message

The log message clearly indicates the successful closure of WebSocket connections.

web/core/services/inbox/inbox-issue.service.ts (2)

2-2: LGTM: Clean import addition

The addition of TDocumentPayload type is properly imported and used in the new method.


79-96: Consider using PATCH instead of POST for updates

While the implementation is functionally correct, using POST for updates deviates from RESTful conventions. POST is typically used for creating new resources, while PATCH is more appropriate for partial updates.

Consider this modification:

- return this.post(
+ return this.patch(
    `/api/workspaces/${workspaceSlug}/projects/${projectId}/inbox-issues/${inboxIssueId}/description/`,
    data,
    {
      responseType: "arraybuffer",
    }
  )

Let's verify if other description update endpoints follow the same pattern:

web/core/components/issues/peek-overview/issue-detail.tsx (1)

15-17: LGTM: Clean import reorganization

The hooks import has been properly categorized under "plane web hooks".

packages/editor/src/core/hooks/use-read-only-editor.ts (1)

35-35: LGTM: Clean type addition

The optional providerDocument prop is properly typed as Y.Doc.

web/core/components/issues/issue-detail/main-content.tsx (2)

25-26: LGTM: Debounced duplicate issues hook integration

The addition of the debounced hook for duplicate issues is a good optimization to prevent excessive API calls.


117-132: 🛠️ Refactor suggestion

Improve the conditional rendering check for description_binary

The current check description_binary !== undefined might allow null values to pass through. Consider using a more strict check.

Apply this change:

-{issue.description_binary !== undefined && (
+{issue.description_binary != null && (

Also, verify that all required props are properly typed to prevent runtime errors.

✅ Verification successful

Use strict null check for description_binary

Based on the codebase analysis, description_binary is defined as nullable in multiple places:

  • In the database schema as BinaryField(null=True)
  • In TypeScript types as description_binary?: string

The current check !== undefined would allow null values to pass through, which could cause issues. The suggested != null check is more appropriate as it handles both undefined and null cases.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for IssueDescriptionInput prop types definition
ast-grep --pattern 'interface $name {
  $$$
  descriptionBinary: $_
  $$$
}'

Length of output: 78


Script:

#!/bin/bash
# Let's try a different approach to find the IssueDescriptionInput component and its prop types
ast-grep --pattern 'type $name = {
  $$$
  descriptionBinary: $_
  $$$
}'

# Also search for the component definition
ast-grep --pattern 'const IssueDescriptionInput = $_'

# And let's check how description_binary is used in other places
rg "description_binary" -A 2 -B 2

Length of output: 25640

packages/editor/src/core/types/editor.ts (3)

128-128: LGTM: Type definition for value property

The optional string type for the value property is correctly defined.


139-143: LGTM: Well-structured collaborative editor interface

The ICollaborativeRichTextEditor interface correctly:

  • Omits properties that would conflict with collaborative editing
  • Uses Uint8Array for binary data handling
  • Maintains type safety with proper method signatures

175-177: LGTM: Read-only collaborative editor interface

The ICollaborativeRichTextReadOnlyEditor interface properly extends the base interface while maintaining read-only constraints.

apiserver/plane/app/urls/workspace.py (1)

213-222: LGTM: Well-structured URL pattern for draft issue descriptions

The endpoint is properly designed with:

  • Secure identifier usage (UUID)
  • Clear RESTful methods (GET/POST)
  • Consistent naming convention
web/core/components/inbox/content/issue-root.tsx (1)

76-76: Verify the empty fetch method implementation

The fetch method is empty. If it's intentionally empty, consider removing it or adding a comment explaining why.

✅ Verification successful

Let me gather more context about this component and its usage to better understand the purpose of this fetch method.


Let me check how this issueOperations object is being used in the component and if there are any patterns of similar implementations elsewhere.


Based on the search results, I can now generate the final response as I have enough context about the issueOperations and its usage in the codebase.

Empty fetch method is intentional for inbox issue operations

The empty fetch method in issue-root.tsx is intentional as it's part of the inbox-specific issue operations interface. Unlike the main issue detail components which require fetching, inbox issues are already loaded through the inbox state management, making the fetch operation unnecessary.

Key observations:

  • The empty fetch is part of the issueOperations object used specifically in the inbox context
  • Other components like peek-overview/root.tsx and issue-detail/root.tsx have proper fetch implementations
  • The inbox component still implements other required operations like update, remove, etc.
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for usages of the fetch method
rg -A 2 "fetch: async \(\)" 

Length of output: 283


Script:

#!/bin/bash
# Get more context around the fetch method and the component structure
rg -B 5 -A 10 "fetch: async \(\)" web/core/components/inbox/content/issue-root.tsx

# Look for any related hooks or context that might explain this pattern
ast-grep --pattern 'const $_ = use$_($$$)'

Length of output: 620


Script:

#!/bin/bash
# Search for TIssueOperations type definition
rg "type TIssueOperations" -A 5

# Search for other usages of issueOperations
rg "issueOperations" -B 2 -A 2

# Look for similar patterns in other components
ast-grep --pattern 'const $_ = useMemo(() => ({
  fetch: $_,
  $$$
}), $_)'

Length of output: 51320

web/core/components/profile/activity/activity-list.tsx (1)

11-11: LGTM!

The import path simplification aligns with the codebase's refactoring efforts while maintaining the same functionality.

web/core/components/core/modals/gpt-assistant-popover.tsx (1)

12-12: LGTM!

The import path simplification is consistent with the codebase's refactoring efforts while preserving the component's functionality.

web/core/store/inbox/inbox-issue.store.ts (1)

29-29: LGTM!

The new method is properly declared in the interface with clear documentation and correctly registered as a MobX action.

Also applies to: 82-82

apiserver/plane/app/urls/issue.py (1)

64-73: LGTM! Well-structured API endpoints

The new URL patterns for description management are well-designed:

  • Follow RESTful conventions
  • Maintain consistent structure with other endpoints
  • Properly scope resources by workspace/project/issue

Also applies to: 246-254

web/core/store/issue/issue-details/issue.store.ts (2)

61-68: LGTM! Clean store initialization

The constructor changes properly initialize both root and detail stores, maintaining clean separation of concerns.


168-168: LGTM! Proper payload update

The issue payload correctly includes the binary description field.

packages/editor/src/core/hooks/use-editor.ts (3)

49-49: LGTM: New provider document prop enhances document management

The addition of providerDocument as an optional Y.Doc type provides better control over document handling.


Line range hint 300-304: LGTM: Consistent document update handling

The setProviderDocument method correctly uses the new providerDocument prop, maintaining consistency with the new document management approach.


Line range hint 222-229: Verify document binary generation

The change to use providerDocument for binary generation looks correct, but we should ensure all consumers are updated accordingly.

✅ Verification successful

The change to use providerDocument for binary generation is consistent across the codebase

The verification shows that the binary generation pattern using Y.encodeStateAsUpdate(providerDocument) is consistently implemented across the codebase:

  • In use-editor.ts and use-read-only-editor.ts hooks
  • All collaborative editor hooks correctly pass provider.document as providerDocument
  • The binary format is properly consumed by the API and store layers through description_binary
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any remaining uses of provider?.document that might need updating
rg -A 2 'provider\?\.document' 

Length of output: 32


Script:

#!/bin/bash
# Let's search for any usage of Y.encodeStateAsUpdate to understand the binary generation pattern
rg -A 2 'Y\.encodeStateAsUpdate'

# Also search for providerDocument to see its usage
rg -A 2 'providerDocument'

# Search for any document binary related patterns
rg -A 2 'documentBinary|binary:'

Length of output: 9255

web/core/services/issue/issue.service.ts (1)

392-405: LGTM: Well-implemented binary description update

The updateDescriptionBinary method is well-structured with:

  • Proper typing using Pick<TDocumentPayload, "description_binary">
  • Correct response type handling with ArrayBuffer
  • Consistent error handling pattern
web/core/components/issues/peek-overview/root.tsx (2)

96-105: LGTM: Robust error handling in description update

The implementation includes:

  • Validation of required fields
  • Clear error messages
  • Proper error propagation

332-342: LGTM: Comprehensive dependency array update

The addition of updateIssueDescription to the dependency array ensures proper memoization of the issueOperations object.

apiserver/plane/app/views/issue/archive.py (1)

299-323: LGTM! The implementation follows best practices.

The retrieve_description method is well-implemented with:

  • Proper permission handling for different user roles
  • Efficient streaming response for binary data
  • Appropriate error handling
  • Secure issue validation

However, consider adding content type validation and size limits for the binary data.

web/core/components/issues/issue-detail/root.tsx (2)

29-34: LGTM! The type definition is well-structured.

The updateDescription method signature is properly typed with all necessary parameters.


334-334: LGTM! Dependencies are properly updated.

The updateIssueDescription dependency is correctly added to the useMemo dependencies array.

web/core/store/issue/issue-details/root.store.ts (2)

195-195: LGTM! Constructor initialization is correct.

The IssueStore is properly initialized with both rootStore and this instance.


260-265: LGTM! Method implementation follows the established pattern.

The updateIssueDescription method correctly delegates to the issue store's implementation.

apiserver/plane/app/serializers/issue.py (1)

631-656: Verify integration points and test coverage.

Let's ensure the new binary field implementation is properly integrated:

✅ Verification successful

Let me gather more information about the integration points since the previous command had issues with Python file type recognition.


Binary field implementation is properly integrated across the codebase

The description_binary field is well integrated throughout the codebase with:

  • Proper model definition in Issue model with corresponding migrations (0066, 0070)
  • Consistent usage in views with proper binary data handling (base64 encoding/decoding)
  • Integration in multiple serializers (IssueDetailSerializer, DraftIssueSerializer)
  • Implementation in related features (drafts, archives, intake)
  • Proper streaming support for binary data in views
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check integration points and test coverage for binary description

# Check views using IssueDetailSerializer
echo "Checking views using IssueDetailSerializer..."
rg -l "IssueDetailSerializer" --type python

# Check for related tests
echo "Checking test coverage..."
rg -l "description_binary" --type python --glob "*test*.py"

# Check for migrations
echo "Checking for related migrations..."
fd -e py -p ".*migrations.*" | xargs rg "description"

Length of output: 8451


Script:

#!/bin/bash
# Check views and tests with proper file type
echo "Checking views using IssueDetailSerializer..."
rg -t py "IssueDetailSerializer" -l

echo "\nChecking usage of description_binary..."
rg -t py "description_binary" -A 3

echo "\nChecking model definition..."
rg -t py "class Issue\(" -A 10

Length of output: 15353

Comment on lines +33 to +35
this.document.on("update", this.documentUpdateHandler.bind(this));
this.document.on("destroy", this.documentDestroyHandler.bind(this));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix event listener removal to prevent memory leaks

The event listeners added in the constructor using bound functions are not being removed correctly in documentDestroyHandler. Since bind creates new function references, the handlers passed to off are not the same as those passed to on. This can lead to memory leaks.

Apply this diff to fix the issue:

 export class CustomCollaborationProvider {
+  private boundDocumentUpdateHandler: (update: Uint8Array, origin: any) => void;
+  private boundDocumentDestroyHandler: () => void;

   constructor(configuration: CollaborationProviderConfiguration) {
     this.hasSynced = false;
     this.setConfiguration(configuration);
-    this.document.on("update", this.documentUpdateHandler.bind(this));
-    this.document.on("destroy", this.documentDestroyHandler.bind(this));
+    this.boundDocumentUpdateHandler = this.documentUpdateHandler.bind(this);
+    this.boundDocumentDestroyHandler = this.documentDestroyHandler.bind(this);
+    this.document.on("update", this.boundDocumentUpdateHandler);
+    this.document.on("destroy", this.boundDocumentDestroyHandler);
   }

   documentDestroyHandler() {
-    this.document.off("update", this.documentUpdateHandler);
-    this.document.off("destroy", this.documentDestroyHandler);
+    this.document.off("update", this.boundDocumentUpdateHandler);
+    this.document.off("destroy", this.boundDocumentDestroyHandler);
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
this.document.on("update", this.documentUpdateHandler.bind(this));
this.document.on("destroy", this.documentDestroyHandler.bind(this));
}
export class CustomCollaborationProvider {
private boundDocumentUpdateHandler: (update: Uint8Array, origin: any) => void;
private boundDocumentDestroyHandler: () => void;
constructor(configuration: CollaborationProviderConfiguration) {
this.hasSynced = false;
this.setConfiguration(configuration);
this.boundDocumentUpdateHandler = this.documentUpdateHandler.bind(this);
this.boundDocumentDestroyHandler = this.documentDestroyHandler.bind(this);
this.document.on("update", this.boundDocumentUpdateHandler);
this.document.on("destroy", this.boundDocumentDestroyHandler);
}
documentDestroyHandler() {
this.document.off("update", this.boundDocumentUpdateHandler);
this.document.off("destroy", this.boundDocumentDestroyHandler);
}
}

Comment on lines +30 to +37
const provider = useMemo(
() =>
new CustomCollaborationProvider({
name: id,
onChange,
}),
[id]
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Include onChange in the dependency array of useMemo

The useMemo hook depends on both id and onChange but only includes id in its dependency array. If onChange changes, the provider won't be updated accordingly. Include onChange to ensure the provider reflects the latest onChange handler.

Apply this diff to fix the issue:

 const provider = useMemo(
   () =>
     new CustomCollaborationProvider({
       name: id,
       onChange,
     }),
-  [id]
+  [id, onChange]
 );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const provider = useMemo(
() =>
new CustomCollaborationProvider({
name: id,
onChange,
}),
[id]
);
const provider = useMemo(
() =>
new CustomCollaborationProvider({
name: id,
onChange,
}),
[id, onChange]
);

Comment on lines +36 to +44
export const convertBinaryDataToBase64String = (document: Uint8Array): string =>
Buffer.from(document).toString("base64");

/**
* @description this function decodes base64 string to binary data
* @param {string} document
* @returns {ArrayBuffer}
*/
export const convertBase64StringToBinaryData = (document: string): ArrayBuffer => Buffer.from(document, "base64");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Replace Buffer with browser-compatible methods

Using Buffer is not compatible with browser environments as it's a Node.js API. Use TextEncoder, TextDecoder, or other browser-compatible methods to handle base64 encoding and decoding.

Apply this diff to fix the issue:

-export const convertBinaryDataToBase64String = (document: Uint8Array): string =>
-  Buffer.from(document).toString("base64");
+export const convertBinaryDataToBase64String = (document: Uint8Array): string => {
+  let binary = '';
+  document.forEach((byte) => (binary += String.fromCharCode(byte)));
+  return btoa(binary);
+};

-export const convertBase64StringToBinaryData = (document: string): ArrayBuffer => Buffer.from(document, "base64");
+export const convertBase64StringToBinaryData = (document: string): Uint8Array => {
+  const binaryString = atob(document);
+  const len = binaryString.length;
+  const bytes = new Uint8Array(len);
+  for (let i = 0; i < len; i++) {
+    bytes[i] = binaryString.charCodeAt(i);
+  }
+  return bytes;
+};

Ensure all environments where this code runs support btoa and atob, or consider using TextEncoder and TextDecoder.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const convertBinaryDataToBase64String = (document: Uint8Array): string =>
Buffer.from(document).toString("base64");
/**
* @description this function decodes base64 string to binary data
* @param {string} document
* @returns {ArrayBuffer}
*/
export const convertBase64StringToBinaryData = (document: string): ArrayBuffer => Buffer.from(document, "base64");
export const convertBinaryDataToBase64String = (document: Uint8Array): string => {
let binary = '';
document.forEach((byte) => (binary += String.fromCharCode(byte)));
return btoa(binary);
};
/**
* @description this function decodes base64 string to binary data
* @param {string} document
* @returns {ArrayBuffer}
*/
export const convertBase64StringToBinaryData = (document: string): Uint8Array => {
const binaryString = atob(document);
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes;
};

Comment on lines +29 to 30
key: string;
placeholder?: string | ((isFocused: boolean, value: string) => string);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid using key as a prop name

key is a special prop in React used for identifying elements in lists. Using it as a regular prop can cause unexpected behavior. Rename the prop to avoid conflicts.

Apply this diff to fix the issue:

 export type IssueDescriptionInputProps = {
   containerClassName?: string;
   descriptionBinary: string | null;
   descriptionHTML: string;
   disabled?: boolean;
   issueId: string;
-  key: string;
+  editorKey: string;
   placeholder?: string | ((isFocused: boolean, value: string) => string);
   projectId: string;
   setIsSubmitting: (initialValue: "submitting" | "submitted" | "saved") => void;
   updateDescription: (data: string) => Promise<ArrayBuffer>;
   workspaceSlug: string;
 };

Update the destructuring and usage:

 export const IssueDescriptionInput: FC<IssueDescriptionInputProps> = observer((props) => {
   const {
     containerClassName,
     descriptionBinary: savedDescriptionBinary,
     descriptionHTML,
     disabled,
     issueId,
     placeholder,
     projectId,
     setIsSubmitting,
     updateDescription,
     workspaceSlug,
-    key,
+    editorKey,
   } = props;

And in the component usage:

 {!disabled ? (
   <CollaborativeRichTextEditor
-    key={issueId}
+    key={editorKey}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
key: string;
placeholder?: string | ((isFocused: boolean, value: string) => string);
editorKey: string;
placeholder?: string | ((isFocused: boolean, value: string) => string);

Comment on lines +63 to 72
const debouncedDescriptionSave = useCallback(
debounce(async (updatedDescription: Uint8Array) => {
const editor = editorRef.current;
if (!editor) return;
const encodedDescription = convertBinaryDataToBase64String(updatedDescription);
await resolveConflictsAndUpdateDescription(encodedDescription, editor);
setIsSubmitting("submitted");
}, 1500),
[handleSubmit, issueId]
[]
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Include dependencies in useCallback for debouncedDescriptionSave

The useCallback hook has an empty dependency array but uses resolveConflictsAndUpdateDescription and setIsSubmitting. To ensure the latest references are used, include them in the dependency array.

Apply this diff to fix the issue:

 const debouncedDescriptionSave = useCallback(
   debounce(async (updatedDescription: Uint8Array) => {
     const editor = editorRef.current;
     if (!editor) return;
     const encodedDescription = convertBinaryDataToBase64String(updatedDescription);
     await resolveConflictsAndUpdateDescription(encodedDescription, editor);
     setIsSubmitting("submitted");
   }, 1500),
-  []
+  [resolveConflictsAndUpdateDescription, setIsSubmitting]
 );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const debouncedDescriptionSave = useCallback(
debounce(async (updatedDescription: Uint8Array) => {
const editor = editorRef.current;
if (!editor) return;
const encodedDescription = convertBinaryDataToBase64String(updatedDescription);
await resolveConflictsAndUpdateDescription(encodedDescription, editor);
setIsSubmitting("submitted");
}, 1500),
[handleSubmit, issueId]
[]
);
const debouncedDescriptionSave = useCallback(
debounce(async (updatedDescription: Uint8Array) => {
const editor = editorRef.current;
if (!editor) return;
const encodedDescription = convertBinaryDataToBase64String(updatedDescription);
await resolveConflictsAndUpdateDescription(encodedDescription, editor);
setIsSubmitting("submitted");
}, 1500),
[resolveConflictsAndUpdateDescription, setIsSubmitting]
);

@@ -1,4 +1,4 @@
import { useEffect, useMemo, useState } from "react";
import { useEffect, useImperativeHandle, useMemo, useState } from "react";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Let me generate the final response based on the shell script results.

useImperativeHandle is used in other editor hooks, but not in use-collaborative-document-editor.ts

The useImperativeHandle import appears to be unused in the current file. It's used in other editor hooks like use-read-only-editor.ts, use-editor.ts, and mentions-list.tsx, but not in use-collaborative-document-editor.ts. Consider removing this unused import.

🔗 Analysis chain

Verify useImperativeHandle usage

The useImperativeHandle import has been added, but its usage is not visible in the provided code.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if useImperativeHandle is used in the codebase
# Expected: Find usage of useImperativeHandle

ast-grep --pattern 'useImperativeHandle($$$)'

Length of output: 21551

Comment on lines +133 to +139
updateDescription: async (_workspaceSlug, _projectId, _issueId, descriptionBinary) => {
try {
return await inboxIssue.updateIssueDescription(descriptionBinary);
} catch {
throw new Error("Failed to update issue description");
}
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance error handling in updateDescription

The error handling could be improved by:

  1. Including error details in the thrown error
  2. Adding error logging
  3. Using the toast notification system consistently
 updateDescription: async (_workspaceSlug, _projectId, _issueId, descriptionBinary) => {
   try {
     return await inboxIssue.updateIssueDescription(descriptionBinary);
-  } catch {
-    throw new Error("Failed to update issue description");
+  } catch (error) {
+    console.error("Error updating issue description:", error);
+    setToast({
+      title: "Error!",
+      type: TOAST_TYPE.ERROR,
+      message: "Failed to update issue description",
+    });
+    throw error;
   }
 },

Committable suggestion skipped: line range outside the PR's diff.

@@ -265,7 +265,11 @@ class Meta:

class DraftIssueDetailSerializer(DraftIssueSerializer):
description_html = serializers.CharField()
description_binary = serializers.CharField()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Consider using Base64BinaryField for consistency and validation

The description_binary field is implemented as a plain CharField which could lead to:

  1. Inconsistency with other serializers that use Base64BinaryField
  2. Missing validation for base64-encoded data
  3. Potential security risks from unvalidated binary content

Consider using the same field type as other serializers:

-    description_binary = serializers.CharField()
+    description_binary = Base64BinaryField()

Also applies to: 271-274

Comment on lines +207 to +219
updateIssueDescription = async (
workspaceSlug: string,
projectId: string,
issueId: string,
descriptionBinary: string
): Promise<ArrayBuffer> => {
const res = await this.issueService.updateDescriptionBinary(workspaceSlug, projectId, issueId, {
description_binary: descriptionBinary,
});
this.rootIssueStore.issues.updateIssue(issueId, { description_binary: descriptionBinary });
this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);
return res;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for description updates

The updateIssueDescription method should handle potential errors that might occur during the API call or store updates.

Consider adding error handling:

   updateIssueDescription = async (
     workspaceSlug: string,
     projectId: string,
     issueId: string,
     descriptionBinary: string
   ): Promise<ArrayBuffer> => {
+    try {
       const res = await this.issueService.updateDescriptionBinary(workspaceSlug, projectId, issueId, {
         description_binary: descriptionBinary,
       });
       this.rootIssueStore.issues.updateIssue(issueId, { description_binary: descriptionBinary });
       this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);
       return res;
+    } catch (error) {
+      console.error("Failed to update issue description:", error);
+      throw error;
+    }
   };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
updateIssueDescription = async (
workspaceSlug: string,
projectId: string,
issueId: string,
descriptionBinary: string
): Promise<ArrayBuffer> => {
const res = await this.issueService.updateDescriptionBinary(workspaceSlug, projectId, issueId, {
description_binary: descriptionBinary,
});
this.rootIssueStore.issues.updateIssue(issueId, { description_binary: descriptionBinary });
this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);
return res;
};
updateIssueDescription = async (
workspaceSlug: string,
projectId: string,
issueId: string,
descriptionBinary: string
): Promise<ArrayBuffer> => {
try {
const res = await this.issueService.updateDescriptionBinary(workspaceSlug, projectId, issueId, {
description_binary: descriptionBinary,
});
this.rootIssueStore.issues.updateIssue(issueId, { description_binary: descriptionBinary });
this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);
return res;
} catch (error) {
console.error("Failed to update issue description:", error);
throw error;
}
};

Comment on lines +631 to +644
class Base64BinaryField(serializers.CharField):
def to_representation(self, value):
# Encode the binary data to base64 string for JSON response
if value:
return base64.b64encode(value).decode("utf-8")
return None

def to_internal_value(self, data):
# Decode the base64 string to binary data when saving
try:
return base64.b64decode(data)
except (TypeError, ValueError):
raise serializers.ValidationError("Invalid base64-encoded data")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add validation for binary data size and content.

The Base64BinaryField implementation needs additional safeguards:

  1. Maximum size validation to prevent memory issues
  2. Content type validation to ensure security
  3. Better error handling to distinguish between different error cases

Consider applying this enhancement:

 class Base64BinaryField(serializers.CharField):
+    def __init__(self, *args, **kwargs):
+        self.max_size = kwargs.pop('max_size', 10 * 1024 * 1024)  # 10MB default
+        super().__init__(*args, **kwargs)
+
     def to_representation(self, value):
         # Encode the binary data to base64 string for JSON response
         if value:
             return base64.b64encode(value).decode("utf-8")
         return None

     def to_internal_value(self, data):
         # Decode the base64 string to binary data when saving
         try:
+            if not data:
+                return None
             decoded = base64.b64decode(data)
+            if len(decoded) > self.max_size:
+                raise serializers.ValidationError(
+                    f"Binary data exceeds maximum size of {self.max_size} bytes"
+                )
             return decoded
         except TypeError:
-            raise serializers.ValidationError("Invalid base64-encoded data")
+            raise serializers.ValidationError("Data is not a valid string")
+        except ValueError:
+            raise serializers.ValidationError("Invalid base64 encoding")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class Base64BinaryField(serializers.CharField):
def to_representation(self, value):
# Encode the binary data to base64 string for JSON response
if value:
return base64.b64encode(value).decode("utf-8")
return None
def to_internal_value(self, data):
# Decode the base64 string to binary data when saving
try:
return base64.b64decode(data)
except (TypeError, ValueError):
raise serializers.ValidationError("Invalid base64-encoded data")
class Base64BinaryField(serializers.CharField):
def __init__(self, *args, **kwargs):
self.max_size = kwargs.pop('max_size', 10 * 1024 * 1024) # 10MB default
super().__init__(*args, **kwargs)
def to_representation(self, value):
# Encode the binary data to base64 string for JSON response
if value:
return base64.b64encode(value).decode("utf-8")
return None
def to_internal_value(self, data):
# Decode the base64 string to binary data when saving
try:
if not data:
return None
decoded = base64.b64decode(data)
if len(decoded) > self.max_size:
raise serializers.ValidationError(
f"Binary data exceeds maximum size of {self.max_size} bytes"
)
return decoded
except TypeError:
raise serializers.ValidationError("Data is not a valid string")
except ValueError:
raise serializers.ValidationError("Invalid base64 encoding")

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
packages/editor/src/core/types/editor.ts (1)

136-140: Add JSDoc documentation for the collaborative interface.

The interface is well-structured, but adding JSDoc documentation would improve maintainability by explaining:

  • The purpose of using binary format (Uint8Array)
  • The collaborative editing features
  • The relationship with IEditorProps

Add documentation like this:

+/**
+ * Interface for collaborative rich text editor that handles binary data.
+ * Extends IEditorProps but replaces string-based value handling with Uint8Array
+ * for real-time collaboration support.
+ */
export interface ICollaborativeRichTextEditor extends Omit<IEditorProps, "initialValue" | "onChange" | "value"> {
apiserver/plane/app/views/issue/base.py (2)

691-715: Consider enhancing error handling and memory efficiency.

While the implementation is functional, consider these improvements:

  1. Add validation for binary data content type.
  2. Implement chunked streaming for large binary data.
  3. Add error handling for corrupted binary data.
 @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
 def retrieve_description(self, request, slug, project_id, pk):
     issue = Issue.issue_objects.filter(
         pk=pk, workspace__slug=slug, project_id=project_id
     ).first()
     if issue is None:
         return Response(
             {"error": "Issue not found"},
             status=404,
         )
     binary_data = issue.description_binary
+    if binary_data and not isinstance(binary_data, bytes):
+        return Response(
+            {"error": "Invalid binary data format"},
+            status=500,
+        )

     def stream_data():
+        chunk_size = 8192  # 8KB chunks
         if binary_data:
-            yield binary_data
+            for i in range(0, len(binary_data), chunk_size):
+                yield binary_data[i:i + chunk_size]
         else:
             yield b""

     response = StreamingHttpResponse(
         stream_data(), content_type="application/octet-stream"
     )
     response["Content-Disposition"] = (
         'attachment; filename="issue_description.bin"'
     )
     return response

717-767: Consider extracting external service communication to a dedicated service class.

To improve maintainability and separation of concerns, consider moving the external service communication logic to a dedicated service class. This would:

  1. Make the code more testable
  2. Centralize error handling and retry logic
  3. Make it easier to mock in tests
  4. Allow reuse in other parts of the application

Example structure:

# services/document_service.py
class DocumentService:
    def __init__(self, base_url, timeout):
        self.base_url = base_url
        self.timeout = timeout

    def resolve_conflicts(self, original_document, updates):
        # External service communication logic here
        pass

# views/issue/base.py
from services.document_service import DocumentService

def update_description(self, request, slug, project_id, pk):
    document_service = DocumentService(
        settings.LIVE_BASE_URL,
        settings.EXTERNAL_SERVICE_TIMEOUT
    )
    result = document_service.resolve_conflicts(
        base64_description,
        request.data.get("description_binary")
    )
    # Handle result
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9e5b104 and 5e98bf6.

📒 Files selected for processing (11)
  • apiserver/plane/app/urls/workspace.py (1 hunks)
  • apiserver/plane/app/views/issue/base.py (3 hunks)
  • apiserver/plane/settings/common.py (1 hunks)
  • live/package.json (1 hunks)
  • packages/editor/src/core/hooks/use-editor.ts (4 hunks)
  • packages/editor/src/core/hooks/use-read-only-editor.ts (3 hunks)
  • packages/editor/src/core/types/collaboration-hook.ts (3 hunks)
  • packages/editor/src/core/types/editor.ts (2 hunks)
  • packages/editor/src/core/types/index.ts (1 hunks)
  • packages/editor/src/index.ts (2 hunks)
  • packages/types/src/issues/issue.d.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • apiserver/plane/settings/common.py
  • packages/editor/src/core/hooks/use-read-only-editor.ts
  • live/package.json
  • packages/editor/src/index.ts
  • packages/editor/src/core/hooks/use-editor.ts
  • apiserver/plane/app/urls/workspace.py
  • packages/editor/src/core/types/collaboration-hook.ts
  • packages/types/src/issues/issue.d.ts
🔇 Additional comments (4)
packages/editor/src/core/types/index.ts (1)

2-2: LGTM: New collaboration hook export added

The addition of the collaboration-hook export aligns with the PR's focus on collaborative editing capabilities.

packages/editor/src/core/types/editor.ts (1)

171-173: Add documentation and verify collaborative read-only requirements.

The type definition looks good but could be improved:

  1. Add JSDoc documentation for clarity
  2. Verify if any additional collaborative-specific properties are needed for read-only mode

Add documentation and verify requirements:

+/**
+ * Type for collaborative rich text editor in read-only mode.
+ * Handles binary data representation of the content.
+ */
export type ICollaborativeRichTextReadOnlyEditor = Omit<IReadOnlyEditorProps, "initialValue"> & {

Let's verify if any collaborative features are missing:

✅ Verification successful

Documentation added and collaborative read-only requirements verified

The type definition is well-structured and has all the necessary collaborative-specific properties. After reviewing the codebase:

  1. The type correctly extends IReadOnlyEditorProps with omission of initialValue since collaborative editors use binary value instead.
  2. The value: Uint8Array property is appropriate for collaborative functionality as it's used to handle binary data representation of the content.
  3. The type is used consistently across the codebase in collaborative read-only editor implementations.
  4. No additional collaborative-specific properties are needed as:
    • File handling is inherited from IReadOnlyEditorProps
    • Mention handling is inherited from IReadOnlyEditorProps
    • Collaborative state is managed through the binary value property

Here's the documented version:

/**
 * Type definition for a collaborative rich text editor in read-only mode.
 * Extends the base read-only editor props while adapting it for collaborative features.
 * 
 * @extends {Omit<IReadOnlyEditorProps, "initialValue">}
 * @property {Uint8Array} value - Binary representation of the collaborative document content
 */
export type ICollaborativeRichTextReadOnlyEditor = Omit<IReadOnlyEditorProps, "initialValue"> & {
  value: Uint8Array;
};
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for usage of collaborative read-only components to verify requirements
rg -t typescript -t tsx "ICollaborativeRichTextReadOnlyEditor" -A 5

# Search for other collaborative read-only implementations for comparison
ast-grep --pattern 'interface $_ReadOnlyEditor {
  $$$
}'

Length of output: 162


Script:

#!/bin/bash
# Search for usage and implementations with proper file type extensions
rg "ICollaborativeRichTextReadOnlyEditor" -g "*.ts" -g "*.tsx" -A 5

# Search for read-only editor interfaces and types
rg "ReadOnlyEditor|readonly.*editor" -g "*.ts" -g "*.tsx" -i -A 5

# Search for collaborative editor implementations
rg "collaborative.*editor|collab.*editor" -g "*.ts" -g "*.tsx" -i -A 5

# Look for the base IReadOnlyEditorProps interface
rg "IReadOnlyEditorProps" -g "*.ts" -g "*.tsx" -A 5

Length of output: 68177

apiserver/plane/app/views/issue/base.py (2)

3-4: LGTM: Required imports added for binary data handling.

The new imports are appropriate for handling binary data streaming and external service communication.

Also applies to: 23-23, 26-26


717-767: ⚠️ Potential issue

Address security and reliability concerns in external service communication.

Several critical issues need attention:

  1. The hardcoded URL path /resolve-document-conflicts/ should be moved to settings.
  2. External service requests should have a timeout.
  3. Response data should be validated before usage.
  4. Consider adding retry logic for failed requests.
 def update_description(self, request, slug, project_id, pk):
     issue = Issue.issue_objects.get(
         workspace__slug=slug, project_id=project_id, pk=pk
     )
     base64_description = issue.description_binary
     if base64_description:
         base64_description = base64.b64encode(base64_description).decode(
             "utf-8"
         )
     data = {
         "original_document": base64_description,
         "updates": request.data.get("description_binary"),
     }
-    base_url = f"{settings.LIVE_BASE_URL}/resolve-document-conflicts/"
+    base_url = f"{settings.LIVE_BASE_URL}{settings.DOCUMENT_CONFLICTS_PATH}"
     try:
-        response = requests.post(base_url, json=data, headers=None)
+        response = requests.post(
+            base_url,
+            json=data,
+            headers=None,
+            timeout=settings.EXTERNAL_SERVICE_TIMEOUT
+        )
     except requests.RequestException:
         return Response(
             {"error": "Failed to connect to the external service"},
             status=status.HTTP_502_BAD_GATEWAY,
         )

     if response.status_code == 200:
+        response_data = response.json()
+        if not all(k in response_data for k in ["description", "description_html", "description_binary"]):
+            return Response(
+                {"error": "Invalid response from external service"},
+                status=status.HTTP_502_BAD_GATEWAY
+            )
+
         issue.description = response.json().get(
             "description", issue.description
         )
         issue.description_html = response.json().get("description_html")
         response_description_binary = response.json().get(
             "description_binary"
         )
+        try:
             issue.description_binary = base64.b64decode(
                 response_description_binary
             )
+        except Exception:
+            return Response(
+                {"error": "Invalid binary data received from external service"},
+                status=status.HTTP_502_BAD_GATEWAY
+            )
         issue.save()

Run the following script to verify the settings configuration:

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (3)
packages/editor/src/core/hooks/use-collaborative-rich-text-editor.ts (1)

31-38: ⚠️ Potential issue

Add onChange to the dependency array

The provider is memoized with only id in the dependency array, but it also depends on onChange. This could lead to stale callbacks.

  const provider = useMemo(
    () =>
      new CustomCollaborationProvider({
        name: id,
        onChange,
      }),
-    [id]
+    [id, onChange]
  );
web/core/components/issues/description-input.tsx (2)

31-31: ⚠️ Potential issue

Avoid using key as a prop name

key is a special prop in React used for identifying elements in lists. Using it as a regular prop can cause unexpected behavior.

Rename the prop to avoid conflicts:

-  key: string;
+  editorKey: string;

65-74: ⚠️ Potential issue

Include dependencies in useCallback for debouncedDescriptionSave

The callback uses resolveConflictsAndUpdateDescription and setIsSubmitting but they're not included in the dependency array.

Add the missing dependencies:

 const debouncedDescriptionSave = useCallback(
   debounce(async (updatedDescription: Uint8Array) => {
     const editor = editorRef.current;
     if (!editor) return;
     const encodedDescription = convertBinaryDataToBase64String(updatedDescription);
     await resolveConflictsAndUpdateDescription(encodedDescription, editor);
     setIsSubmitting("submitted");
   }, 1500),
-  []
+  [resolveConflictsAndUpdateDescription, setIsSubmitting]
 );
🧹 Nitpick comments (3)
packages/editor/src/core/hooks/use-collaborative-rich-text-editor.ts (1)

58-69: Consider making AI and drag-drop configuration configurable

The SideMenuExtension has hardcoded values for aiEnabled and dragDropEnabled. These should be configurable through props for better flexibility.

  SideMenuExtension({
-   aiEnabled: false,
-   dragDropEnabled: true,
+   aiEnabled: props.aiEnabled ?? false,
+   dragDropEnabled: props.dragDropEnabled ?? true,
  }),
packages/editor/src/core/components/editors/rich-text/collaborative-editor.tsx (1)

60-62: Enhance accessibility for editor content

The editor content wrapper lacks ARIA attributes for better screen reader support.

- <div className="flex flex-col">
+ <div 
+   className="flex flex-col" 
+   role="textbox" 
+   aria-label="Collaborative rich text editor"
+ >
web/core/components/editor/rich-text-editor/collaborative-editor.tsx (1)

30-30: Consider caching editor flagging results

The useEditorFlagging hook is called on every render. Consider memoizing the results for better performance.

- const { richTextEditor: disabledExtensions } = useEditorFlagging(workspaceSlug?.toString());
+ const { richTextEditor: disabledExtensions } = useMemo(
+   () => useEditorFlagging(workspaceSlug?.toString()),
+   [workspaceSlug]
+ );
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e98bf6 and 0e4d713.

📒 Files selected for processing (8)
  • packages/editor/src/core/components/editors/document/collaborative-editor.tsx (2 hunks)
  • packages/editor/src/core/components/editors/rich-text/collaborative-editor.tsx (1 hunks)
  • packages/editor/src/core/hooks/use-collaborative-document-editor.ts (1 hunks)
  • packages/editor/src/core/hooks/use-collaborative-rich-text-editor.ts (1 hunks)
  • packages/editor/src/core/types/collaboration-hook.ts (2 hunks)
  • web/core/components/editor/rich-text-editor/collaborative-editor.tsx (1 hunks)
  • web/core/components/editor/rich-text-editor/collaborative-read-only-editor.tsx (1 hunks)
  • web/core/components/issues/description-input.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • web/core/components/editor/rich-text-editor/collaborative-read-only-editor.tsx
  • packages/editor/src/core/types/collaboration-hook.ts
🔇 Additional comments (7)
packages/editor/src/core/components/editors/document/collaborative-editor.tsx (2)

47-47: Hook usage is consistent with the new naming.
The updated hook usage is properly adapted and follows the new signature. The function arguments match the expected props for collaborative document editing.


11-11: Import hook name update looks correct.
This rename aligns with the new hook name and helps maintain a clearer distinction for document editing.

To confirm no references to the old hook remain, you can run:

✅ Verification successful

Import update is correct and no old references remain

The comprehensive search results confirm that:

  • The new hook useCollaborativeDocumentEditor is properly defined in its module and correctly imported in the collaborative-editor component
  • There are no remaining references to any old hook name that might have been replaced
  • The codebase consistently uses specific hook names for different editor types (document, rich-text, read-only)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Ensure no references to the old useCollaborativeEditor hook remain.
rg 'useCollaborativeEditor'

Length of output: 27


Script:

#!/bin/bash
# Let's try a more comprehensive search for any variations of the hook name
rg -i "usecollaborative.*editor"

Length of output: 1877


Script:

#!/bin/bash
# Let's also check for any files that might contain this hook definition
fd -e ts -e tsx --exec grep -l "useCollaborative"

Length of output: 638


Script:

#!/bin/bash
# Search for the new hook name specifically
rg "useCollaborativeDocumentEditor"

Length of output: 547

packages/editor/src/core/hooks/use-collaborative-document-editor.ts (2)

12-12: Type import is consistent with revised naming.
Switching to TCollaborativeDocumentEditorHookProps clarifies intent for document-specific collaborative editing.


14-14: Renamed hook improves clarity for collaborative document editing.
Exporting useCollaborativeDocumentEditor reflects a more descriptive naming convention, making the hook’s purpose clearer.

web/core/components/editor/rich-text-editor/collaborative-editor.tsx (1)

42-49: Verify file upload security measures

Ensure that file upload handlers implement proper security measures for the binary data handling.

✅ Verification successful

Let me gather more information about the file upload security measures.


Based on the analysis of the code, I can now provide the final response regarding the file upload security measures.

File upload security measures are properly implemented

The codebase implements several security measures for file uploads:

  1. File size validation:
  • Enforces maximum file size limits through maxFileSize parameter
  • Size checks are performed both client-side and during upload
  • User-friendly error messages display size limits in MB
  1. Error handling and validation:
  • Comprehensive error handling for upload failures
  • Proper error propagation with specific error messages
  • Upload status tracking and cleanup on failures
  1. File handling architecture:
  • Secure file upload through signed URLs
  • Separate services for file upload handling (FileUploadService)
  • Proper content type enforcement with multipart/form-data
  • Asset status tracking and verification after upload
  1. Security features:
  • Workspace and project-level access control
  • Asset ID verification post-upload
  • Proper cleanup of failed uploads
  • Isolated file upload service implementation
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for file upload security implementations
rg -A 5 "uploadFile|handleFileUpload" --type ts

Length of output: 24603


Script:

#!/bin/bash
# Search for file size validation and file type checks
rg -A 5 "maxFileSize|validateFile|fileType|mimeType|allowedTypes" --type ts

# Search for security-related error handling in file upload
rg -A 5 "catch.*upload|error.*upload|throw.*upload" --type ts

Length of output: 30616

web/core/components/issues/description-input.tsx (2)

76-97: LGTM! Well-structured loading state

The skeleton loader provides excellent user feedback and matches the editor's layout, enhancing the user experience during data loading.


102-139: LGTM! Solid implementation of collaborative editing

The implementation effectively handles:

  • Collaborative editing with binary data
  • File uploads with proper workspace/project context
  • Real-time updates with debounced saves
  • Proper placeholder handling

Comment on lines +40 to +50
useEffect(() => {
if (provider.hasSynced) return;
if (value && value.length > 0) {
try {
Y.applyUpdate(provider.document, value);
provider.hasSynced = true;
} catch (error) {
console.error("Error applying binary updates to the description", error);
}
}
}, [value, provider.document]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve error handling and sync state management

The current error handling only logs errors but doesn't provide recovery mechanisms or user feedback. Additionally, the sync state management could be improved.

  useEffect(() => {
    if (provider.hasSynced) return;
    if (value && value.length > 0) {
      try {
        Y.applyUpdate(provider.document, value);
        provider.hasSynced = true;
      } catch (error) {
+       provider.hasSynced = false; // Reset sync state on error
        console.error("Error applying binary updates to the description", error);
+       // Notify parent about sync failure
+       onChange?.(new Uint8Array()); // Send empty update to trigger re-sync
      }
    }
-  }, [value, provider.document]);
+  }, [value, provider, onChange]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
if (provider.hasSynced) return;
if (value && value.length > 0) {
try {
Y.applyUpdate(provider.document, value);
provider.hasSynced = true;
} catch (error) {
console.error("Error applying binary updates to the description", error);
}
}
}, [value, provider.document]);
useEffect(() => {
if (provider.hasSynced) return;
if (value && value.length > 0) {
try {
Y.applyUpdate(provider.document, value);
provider.hasSynced = true;
} catch (error) {
provider.hasSynced = false; // Reset sync state on error
console.error("Error applying binary updates to the description", error);
// Notify parent about sync failure
onChange?.(new Uint8Array()); // Send empty update to trigger re-sync
}
}
}, [value, provider, onChange]);

containerClassName,
});

if (!editor) return null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error boundary for editor initialization

The early return when editor is null could lead to a blank screen without user feedback.

- if (!editor) return null;
+ if (!editor) return (
+   <div className="text-sm text-red-500">
+     Failed to initialize editor. Please refresh the page.
+   </div>
+ );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!editor) return null;
if (!editor) return (
<div className="text-sm text-red-500">
Failed to initialize editor. Please refresh the page.
</div>
);

Comment on lines +50 to +56
searchCallback: async (query) => {
const res = await fetchMentions(query);
if (!res) throw new Error("Failed in fetching mentions");
return res;
},
renderComponent: (props) => <EditorMentionsRoot {...props} />,
}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve error handling in mention search

The mention search error handling could be more informative and provide recovery options.

  searchCallback: async (query) => {
    try {
      const res = await fetchMentions(query);
-     if (!res) throw new Error("Failed in fetching mentions");
+     if (!res) throw new Error("Unable to load mentions. Please try again.");
      return res;
+   } catch (error) {
+     console.error("Mention search failed:", error);
+     return []; // Return empty array to prevent editor from breaking
    }
  },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
searchCallback: async (query) => {
const res = await fetchMentions(query);
if (!res) throw new Error("Failed in fetching mentions");
return res;
},
renderComponent: (props) => <EditorMentionsRoot {...props} />,
}}
searchCallback: async (query) => {
try {
const res = await fetchMentions(query);
if (!res) throw new Error("Unable to load mentions. Please try again.");
return res;
} catch (error) {
console.error("Mention search failed:", error);
return []; // Return empty array to prevent editor from breaking
}
},
renderComponent: (props) => <EditorMentionsRoot {...props} />,
}}

Comment on lines +133 to +136
} catch (error) {
console.log("Error in uploading issue asset:", error);
throw new Error("Asset upload failed. Please try again later.");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve error handling in file upload

The current error handling logs to console and throws a generic error, which isn't ideal for production.

Consider these improvements:

-              console.log("Error in uploading issue asset:", error);
-              throw new Error("Asset upload failed. Please try again later.");
+              const errorMessage = error instanceof Error ? error.message : "Asset upload failed";
+              throw new Error(`Failed to upload file: ${errorMessage}. Please try again.`);

Also, consider adding error tracking (e.g., Sentry) for production environments.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (error) {
console.log("Error in uploading issue asset:", error);
throw new Error("Asset upload failed. Please try again later.");
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Asset upload failed";
throw new Error(`Failed to upload file: ${errorMessage}. Please try again.`);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants