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 33 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

    • Added support for binary descriptions in issues and inbox issues, allowing for more flexible data handling.
    • Introduced new endpoints for retrieving and updating issue descriptions, both for active and archived issues.
    • Implemented a collaborative rich text editor for enhanced editing capabilities.
  • Bug Fixes

    • Improved error handling for updating issue descriptions and fetching data.
  • Documentation

    • Updated API documentation to reflect new endpoints and features related to issue descriptions.
  • Chores

    • Cleaned up import statements and optimized code structure for better maintainability.

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

Walkthrough

The pull request introduces several enhancements across multiple files, primarily focusing on the management of issue descriptions in binary format. Key changes include the addition of a new description_binary field in various serializers and the implementation of methods to retrieve and update issue descriptions in both active and archived states. New URL patterns are established to support these functionalities, alongside modifications to existing services and stores to accommodate the binary data handling. Overall, the changes enhance the application's capability to manage issue descriptions more effectively.

Changes

File Path Change Summary
apiserver/plane/app/serializers/draft.py Added description_binary field to DraftIssueDetailSerializer. Updated Meta.fields to include it.
apiserver/plane/app/serializers/issue.py Introduced Base64BinaryField. Added description_binary to IssueDetailSerializer.
apiserver/plane/app/urls/intake.py Added new URL path for inbox issue descriptions with GET and POST methods.
apiserver/plane/app/urls/issue.py Added endpoints for updating and retrieving issue descriptions for active and archived issues.
apiserver/plane/app/urls/workspace.py Added URL path for draft issue descriptions with GET and POST methods.
apiserver/plane/app/views/intake/base.py Added retrieve_description and update_description methods in IntakeIssueViewSet.
apiserver/plane/app/views/issue/archive.py Added retrieve_description method in IssueArchiveViewSet.
apiserver/plane/app/views/issue/base.py Added retrieve_description and update_description methods in IssueViewSet.
apiserver/plane/app/views/workspace/draft.py Added retrieve_description and update_description methods in WorkspaceDraftIssueViewSet.
apiserver/plane/settings/common.py Introduced LIVE_BASE_URL environment variable.
live/.prettierignore Added entries to ignore specific directories in Prettier formatting.
live/.prettierrc Added configuration for Prettier formatting options.
live/package.json Removed dependencies: y-prosemirror, y-protocols, yjs.
live/src/core/helpers/page.ts Deleted file related to document format conversions.
live/src/core/lib/page.ts Updated import statements and streamlined asynchronous functions.
live/src/core/resolve-conflicts.ts Introduced functionality for resolving document conflicts with new types and methods.
live/src/server.ts Added POST endpoint for resolving document conflicts.
packages/editor/src/core/components/editors/document/collaborative-editor.tsx Updated hook usage in CollaborativeDocumentEditor.
packages/editor/src/core/components/editors/document/collaborative-read-only-editor.tsx Updated hook usage in CollaborativeDocumentReadOnlyEditor.
packages/editor/src/core/components/editors/editor-wrapper.tsx Updated type definition for extensions prop.
packages/editor/src/core/components/editors/rich-text/collaborative-editor.tsx Added new collaborative rich text editor component.
packages/editor/src/core/components/editors/rich-text/collaborative-read-only-editor.tsx Added new read-only collaborative rich text editor component.
packages/editor/src/core/components/editors/rich-text/index.ts Added exports for new collaborative editors.
packages/editor/src/core/helpers/yjs-utils.ts Introduced utility functions for handling binary data in rich text editing.
packages/editor/src/core/hooks/use-collaborative-document-editor.ts Renamed hook and updated types.
packages/editor/src/core/hooks/use-collaborative-document-read-only-editor.ts Renamed hook and updated types.
packages/editor/src/core/hooks/use-collaborative-rich-text-editor.ts Added new hook for collaborative rich text editing.
packages/editor/src/core/hooks/use-collaborative-rich-text-read-only-editor.ts Added new hook for read-only collaborative rich text editing.
packages/editor/src/core/hooks/use-editor.ts Added providerDocument property to CustomEditorProps.
packages/editor/src/core/hooks/use-read-only-editor.ts Added providerDocument property to CustomReadOnlyEditorProps.
packages/editor/src/core/providers/custom-collaboration-provider.ts Introduced a custom collaboration provider for real-time updates.
packages/editor/src/core/types/collaboration-hook.ts Updated type definitions for collaborative editor hooks.
packages/editor/src/core/types/editor.ts Added new interfaces for collaborative rich text editors.
packages/editor/src/index.ts Added new collaborative rich text editor exports.
web/core/components/editor/rich-text-editor/collaborative-editor.tsx Added new collaborative rich text editor component.
web/core/components/editor/rich-text-editor/collaborative-read-only-editor.tsx Added new read-only collaborative rich text editor component.
web/core/components/issues/description-input.tsx Updated to use collaborative rich text editor and simplified form handling.
web/core/components/issues/issue-detail/main-content.tsx Updated to handle issue descriptions with new binary format.
web/core/components/issues/issue-detail/root.tsx Added updateDescription method to TIssueOperations.
web/core/hooks/use-issue-description.ts Added new hook for managing issue description state.
web/core/services/inbox/inbox-issue.service.ts Added updateDescriptionBinary method to update inbox issue descriptions.
web/core/services/issue/issue.service.ts Added updateDescriptionBinary method for issues.
web/core/services/page/project-page.service.ts Updated fetchDescriptionBinary return type.
web/core/store/inbox/inbox-issue.store.ts Added updateIssueDescription method for local issue updates.
web/core/store/issue/issue-details/issue.store.ts Added updateIssueDescription method and updated constructor.
web/core/store/issue/issue-details/root.store.ts Added updateIssueDescription method and updated constructor.

Possibly related PRs

Suggested labels

🌐frontend, ✍️editor, 🧹chore

Suggested reviewers

  • SatishGandham
  • rahulramesha

🐇 In the meadow where bunnies play,
New fields and paths have come our way.
With binary tales of issues so bright,
We hop along, our hearts alight!
For every change, a joyful cheer,
In our code, we hold so dear! 🐰✨


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 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 +39 to +49
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.

⚠️ Potential issue

Add provider to the dependency array in useEffect

The useEffect hook uses provider.hasSynced and provider.document but only includes value and provider.document in its dependency array. To ensure that the effect runs when the provider changes, include provider in the dependency array.

Apply this diff to fix the issue:

 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]);
+}, [value, provider]);

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

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);

@@ -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")

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