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-2717] chore: implemented issue attachment upload progress #5901

Merged
merged 6 commits into from
Oct 29, 2024

Conversation

aaryan610
Copy link
Collaborator

@aaryan610 aaryan610 commented Oct 23, 2024

Improvements:

An upload indicator is now displayed while the issue attachment is being uploaded.

Media:

Screen.Recording.2024-10-23.at.17.36.19.mov

Plane issue: WEB-2717

Summary by CodeRabbit

  • New Features

    • Introduced components for displaying upload status: IssueAttachmentsUploadItem and IssueAttachmentsUploadDetails.
    • Enhanced attachment upload process with real-time progress tracking.
    • Updated rendering logic to reflect ongoing uploads and attachment statuses.
  • Bug Fixes

    • Improved error handling during file uploads.
  • Refactor

    • Streamlined attachment operations through a new attachmentHelpers structure.
    • Updated component props for better clarity and maintainability.
    • Enhanced the IssueAttachmentRoot component for improved reactivity with MobX.
  • Documentation

    • Expanded module exports to include new upload-related components.

Copy link
Contributor

coderabbitai bot commented Oct 23, 2024

Walkthrough

The pull request introduces significant modifications to the attachment handling components in the web application. Key changes include the renaming and type adjustments of props related to attachment operations, the introduction of new components for upload status representation, and enhancements to existing components to improve the handling of file uploads. The overall structure is streamlined by utilizing a new useAttachmentOperations hook, which consolidates logic for managing attachment operations and upload statuses, thereby improving maintainability and reactivity.

Changes

File Path Change Summary
web/core/components/issues/attachment/attachment-detail.tsx Renamed handleAttachmentOperations to attachmentHelpers, updated its type, and modified import statements.
web/core/components/issues/attachment/attachment-item-list.tsx Replaced handleAttachmentOperations with attachmentHelpers, adjusted file drop handling, and renamed state variable.
web/core/components/issues/attachment/attachment-list-upload-item.tsx Introduced IssueAttachmentsUploadItem component to display upload status with progress indicators.
web/core/components/issues/attachment/attachment-upload-details.tsx Introduced IssueAttachmentsUploadDetails component for displaying detailed upload information.
web/core/components/issues/attachment/attachment-upload.tsx Updated import path for TAttachmentOperations, modified prop names and types for attachment operations.
web/core/components/issues/attachment/attachments-list.tsx Changed handleAttachmentOperations to attachmentHelpers, updated rendering logic for upload statuses.
web/core/components/issues/attachment/delete-attachment-modal.tsx Renamed prop handleAttachmentOperations to attachmentOperations, updated type to only include remove operation.
web/core/components/issues/attachment/index.ts Added exports for new upload-related components.
web/core/components/issues/attachment/root.tsx Wrapped component with observer, introduced useAttachmentOperations hook for managing attachment logic.
web/core/components/issues/issue-detail-widgets/attachments/content.tsx Wrapped component with observer, renamed handleAttachmentOperations to attachmentHelpers.
web/core/components/issues/issue-detail-widgets/attachments/helper.tsx Updated useAttachmentOperations return type and logic to include upload status handling.
web/core/components/issues/issue-detail-widgets/attachments/quick-action-button.tsx Renamed variable handleAttachmentOperations to attachmentOperations.
web/core/components/issues/issue-detail-widgets/issue-detail-widget-collapsibles.tsx Updated rendering logic to check for upload statuses alongside attachment count.
web/core/services/api.service.ts Enhanced type safety for HTTP methods by specifying AxiosRequestConfig type for config parameter.
web/core/services/file-upload.service.ts Updated uploadFile method to include optional uploadProgressHandler for tracking upload progress.
web/core/services/issue/issue_attachment.service.ts Added optional uploadProgressHandler parameter to uploadIssueAttachment method for progress tracking.
web/core/store/issue/issue-details/attachment.store.ts Introduced TAttachmentUploadStatus, updated IIssueAttachmentStore to include upload status handling and new methods.

Possibly related PRs

Suggested labels

🎨UI / UX, 🐛bug

Suggested reviewers

  • sriramveeraghanta
  • SatishGandham

🐇 In the meadow where we play,
Attachments dance in bright array.
With helpers now, we swiftly glide,
Uploads tracked, with joy and pride.
A hop, a skip, our code's refined,
In every change, new paths we find! 🌼✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 5f31921 and ea5156b.

📒 Files selected for processing (1)
  • web/core/components/issues/issue-detail-widgets/issue-detail-widget-collapsibles.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/core/components/issues/issue-detail-widgets/issue-detail-widget-collapsibles.tsx

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.

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: 14

🧹 Outside diff range and nitpick comments (15)
web/core/components/issues/issue-detail-widgets/attachments/content.tsx (1)

19-25: Consider adding JSDoc comments for better documentation

The rename from handleAttachmentOperations to attachmentHelpers improves clarity. While the changes are good, consider adding JSDoc comments to document the shape and purpose of the attachmentHelpers object.

+ /** Helper functions and state for managing issue attachments and upload progress */
  const attachmentHelpers = useAttachmentOperations(workspaceSlug, projectId, issueId);
web/core/services/file-upload.service.ts (1)

24-24: Consider adding type safety for upload progress events

The progress handler implementation could benefit from stronger typing.

// Add this type at the top of the file
type UploadProgressEvent = {
  loaded: number;
  total?: number;
  progress?: number;
};

This would help consumers of the service better understand the shape of progress events they'll receive.

web/core/components/issues/attachment/root.tsx (1)

Line range hint 1-37: Well-structured architecture for upload progress tracking

The architectural changes demonstrate good practices:

  1. Using MobX for reactive state management
  2. Consolidating attachment logic in a custom hook
  3. Clear separation of concerns between upload and list components

This structure will make it easier to maintain and extend the upload progress functionality.

web/core/components/issues/attachment/attachments-list.tsx (1)

30-32: Consider handling empty states.

The upload status implementation looks good, but consider handling empty states for better UX.

Consider adding empty state handling:

 return (
   <>
+    {(!uploadStatus?.length && !issueAttachments?.length) && (
+      <div className="text-custom-text-400 text-sm">
+        No attachments yet
+      </div>
+    )}
     {uploadStatus?.map((uploadStatus) => (
       <IssueAttachmentsUploadDetails key={uploadStatus.id} uploadStatus={uploadStatus} />
     ))}

Also applies to: 38-38

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

31-52: LGTM! Enhanced type safety for HTTP methods.

The addition of AxiosRequestConfig typing improves type safety and enables proper typing for upload progress callbacks, which is essential for the attachment upload feature.

Consider improving type safety further by replacing any with a more specific type for the data parameter in the delete method:

-  delete(url: string, data?: any, config: AxiosRequestConfig = {}) {
+  delete<T = any>(url: string, data?: T, config: AxiosRequestConfig = {}) {
web/core/components/issues/attachment/attachment-list-upload-item.tsx (1)

19-45: Extract magic numbers as constants.

Consider extracting hard-coded values to named constants for better maintainability.

+const ICON_SIZE = 18;
+const PROGRESS_INDICATOR_SIZE = 20;
+const PROGRESS_INDICATOR_STROKE_WIDTH = 3;

 export const IssueAttachmentsUploadItem: React.FC<Props> = observer((props) => {
   // ...
-  const fileIcon = getFileIcon(fileExtension, 18);
+  const fileIcon = getFileIcon(fileExtension, ICON_SIZE);
   // ...
-          <CircularProgressIndicator size={20} strokeWidth={3} percentage={uploadStatus.progress} />
+          <CircularProgressIndicator 
+            size={PROGRESS_INDICATOR_SIZE}
+            strokeWidth={PROGRESS_INDICATOR_STROKE_WIDTH}
+            percentage={uploadStatus.progress}
+          />
web/core/components/issues/attachment/attachment-upload.tsx (2)

Line range hint 18-32: Implementation missing upload progress tracking.

While the component handles the upload operation and loading state, it doesn't implement the progress tracking mentioned in the PR objectives. Consider enhancing the implementation to show upload progress.

Here's a suggested implementation using XMLHttpRequest or Axios for progress tracking:

 const onDrop = useCallback(
   (acceptedFiles: File[]) => {
     const currentFile: File = acceptedFiles[0];
     if (!currentFile || !workspaceSlug) return;

     setIsLoading(true);
-    attachmentOperations.create(currentFile).finally(() => setIsLoading(false));
+    const onProgress = (progress: number) => {
+      // Update progress state
+      setUploadProgress(progress);
+    };
+    
+    attachmentOperations.create(currentFile, onProgress).finally(() => {
+      setIsLoading(false);
+      setUploadProgress(0);
+    });
   },
-  [attachmentOperations, workspaceSlug]
+  [attachmentOperations, workspaceSlug, setUploadProgress]
 );

Line range hint 44-64: Enhance UI to show upload progress.

The UI currently only shows a "Uploading..." message. To meet the PR objectives, consider adding a progress indicator.

Here's a suggested UI enhancement:

 <span className="flex items-center gap-2">
   {isDragActive ? (
     <p>Drop here...</p>
   ) : fileError ? (
     <p className="text-center text-red-500">{fileError}</p>
   ) : isLoading ? (
-    <p className="text-center">Uploading...</p>
+    <div className="flex flex-col items-center gap-1 w-full">
+      <p className="text-center">Uploading... {uploadProgress}%</p>
+      <div className="w-full h-1 bg-custom-border-200 rounded-full overflow-hidden">
+        <div 
+          className="h-full bg-custom-primary transition-all duration-300" 
+          style={{ width: `${uploadProgress}%` }}
+        />
+      </div>
+    </div>
   ) : (
     <p className="text-center">Click or drag a file here</p>
   )}
 </span>
web/core/components/issues/issue-detail-widgets/attachments/quick-action-button.tsx (2)

Line range hint 44-54: Consider adding upload progress feedback.

While the file upload functionality works correctly, the PR's objective mentions implementing upload progress indication. Consider adding progress feedback using the attachment operations to improve user experience.

Here's a suggested implementation:

 attachmentOperations
-  .create(currentFile)
+  .create(currentFile, {
+    onProgress: (progress) => {
+      // Update UI with upload progress
+      setUploadProgress(progress);
+    }
+  })
   .catch(() => {

This would require adding:

  1. A progress state: const [uploadProgress, setUploadProgress] = useState(0);
  2. UI elements to display the progress during upload

Line range hint 82-87: Address the TODO comment regarding event propagation.

The current event propagation handling through an extra div is not ideal. Consider refactoring this to handle the event directly in the button component.

Here's a suggested implementation:

-    <div
-      onClick={(e) => {
-        // TODO: Remove extra div and move event propagation to button
-        e.stopPropagation();
-      }}
-    >
-      <button {...getRootProps()} type="button" disabled={disabled}>
+    <button
+      {...getRootProps()}
+      type="button"
+      disabled={disabled}
+      onClick={(e) => {
+        e.stopPropagation();
+        getRootProps().onClick?.(e);
+      }}
+    >
       <input {...getInputProps()} />
       {customButton ? customButton : <Plus className="h-4 w-4" />}
-      </button>
-    </div>
+    </button>
web/core/components/issues/attachment/attachment-detail.tsx (1)

Line range hint 1-95: Consider splitting the component for better maintainability.

While the changes look good, consider the following improvements:

  1. Extract the file metadata display logic into a separate component
  2. Move magic numbers (e.g., truncateText length of 10) to constants
  3. Consider using a custom hook for file metadata calculations

This would improve maintainability and make the component more focused.

Example refactor for the file metadata component:

type FileMetadataProps = {
  fileName: string;
  fileExtension: string;
  fileIcon: React.ReactNode;
  uploadInfo: {
    uploaderName: string;
    uploadDate: string;
  };
  size: number;
};

const FILE_NAME_MAX_LENGTH = 10;

const FileMetadata: FC<FileMetadataProps> = ({
  fileName,
  fileExtension,
  fileIcon,
  uploadInfo,
  size,
}) => (
  <div className="flex flex-col gap-1">
    <div className="flex items-center gap-2">
      <Tooltip tooltipContent={fileName} isMobile={isMobile}>
        <span className="text-sm">
          {truncateText(fileName, FILE_NAME_MAX_LENGTH)}
        </span>
      </Tooltip>
      <Tooltip
        isMobile={isMobile}
        tooltipContent={`${uploadInfo.uploaderName} uploaded on ${uploadInfo.uploadDate}`}
      >
        <span>
          <AlertCircle className="h-3 w-3" />
        </span>
      </Tooltip>
    </div>
    <div className="flex items-center gap-3 text-xs text-custom-text-200">
      <span>{fileExtension.toUpperCase()}</span>
      <span>{convertBytesToSize(size)}</span>
    </div>
  </div>
);
web/core/components/issues/attachment/attachment-item-list.tsx (1)

86-113: LGTM: Well-implemented upload progress UI with room for minor enhancement.

The UI implementation effectively shows upload progress and maintains good UX. However, consider improving the disabled state visibility.

Consider enhancing the disabled state styling:

-className={`relative flex flex-col ${isDragActive && issueAttachments.length < 3 ? "min-h-[200px]" : ""} ${disabled ? "cursor-not-allowed" : "cursor-pointer"}`}
+className={`relative flex flex-col ${isDragActive && issueAttachments.length < 3 ? "min-h-[200px]" : ""} ${
+  disabled ? "cursor-not-allowed opacity-60" : "cursor-pointer"
+}`}
web/core/components/issues/issue-detail-widgets/attachments/helper.tsx (2)

Line range hint 36-69: Refactor to eliminate duplicate validation logic

Both the create and remove methods perform the same validation check for workspaceSlug, projectId, and issueId. To enhance maintainability and reduce code duplication, consider extracting this validation into a separate helper function.

Here's how you might refactor the code:

 const validateRequiredFields = () => {
   if (!workspaceSlug || !projectId || !issueId) {
     throw new Error("Missing required fields");
   }
 };

 const attachmentOperations: TAttachmentOperations = useMemo(
   () => ({
     create: async (file) => {
-      if (!workspaceSlug || !projectId || !issueId) throw new Error("Missing required fields");
+      validateRequiredFields();
       // Rest of the create logic...
     },
     remove: async (attachmentId) => {
-      if (!workspaceSlug || !projectId || !issueId) throw new Error("Missing required fields");
+      validateRequiredFields();
       // Rest of the remove logic...
     },
   }),
   [/* dependencies */]
 );

66-66: Ensure consistent error handling in create and remove methods

In the create method, the caught error is rethrown after logging, allowing upstream handlers to deal with it. In contrast, the remove method does not rethrow the error after catching it. For consistency and better error propagation, consider rethrowing the error in the remove method as well.

Apply this diff to rethrow the error in the remove method:

         } catch (error) {
           captureIssueEvent({
             eventName: "Issue attachment deleted",
             payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
             updates: {
               changed_property: "attachment",
               change_details: "",
             },
           });
           setToast({
             message: "The Attachment could not be removed",
             type: TOAST_TYPE.ERROR,
             title: "Attachment not removed",
           });
+          throw error;
         }

Also applies to: 90-102

web/core/store/issue/issue-details/attachment.store.ts (1)

167-168: Enhance error handling in the upload process

Currently, errors during the upload are logged to the console and re-thrown. Consider providing user feedback to inform them of the upload failure, improving the user experience.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 25a4107 and d99c964.

📒 Files selected for processing (17)
  • web/core/components/issues/attachment/attachment-detail.tsx (2 hunks)
  • web/core/components/issues/attachment/attachment-item-list.tsx (3 hunks)
  • web/core/components/issues/attachment/attachment-list-upload-item.tsx (1 hunks)
  • web/core/components/issues/attachment/attachment-upload-details.tsx (1 hunks)
  • web/core/components/issues/attachment/attachment-upload.tsx (2 hunks)
  • web/core/components/issues/attachment/attachments-list.tsx (1 hunks)
  • web/core/components/issues/attachment/delete-attachment-modal.tsx (2 hunks)
  • web/core/components/issues/attachment/index.ts (1 hunks)
  • web/core/components/issues/attachment/root.tsx (3 hunks)
  • web/core/components/issues/issue-detail-widgets/attachments/content.tsx (2 hunks)
  • web/core/components/issues/issue-detail-widgets/attachments/helper.tsx (3 hunks)
  • web/core/components/issues/issue-detail-widgets/attachments/quick-action-button.tsx (3 hunks)
  • web/core/components/issues/issue-detail-widgets/issue-detail-widget-collapsibles.tsx (2 hunks)
  • web/core/services/api.service.ts (2 hunks)
  • web/core/services/file-upload.service.ts (2 hunks)
  • web/core/services/issue/issue_attachment.service.ts (3 hunks)
  • web/core/store/issue/issue-details/attachment.store.ts (6 hunks)
🧰 Additional context used
🪛 Biome
web/core/store/issue/issue-details/attachment.store.ts

[error] 155-155: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (38)
web/core/components/issues/attachment/index.ts (1)

4-5: LGTM! Well-organized exports for the new upload progress components.

The new exports are properly organized, following the existing barrel file pattern and maintaining alphabetical order. They align well with the PR's objective of implementing attachment upload progress indicators.

web/core/components/issues/issue-detail-widgets/attachments/content.tsx (3)

3-3: LGTM: MobX integration for reactive updates

The addition of the observer import is appropriate for enabling reactive updates to the upload progress state.


16-16: LGTM: Component correctly wrapped with observer

The component is properly wrapped with the observer HOC, enabling it to react to changes in observable state, which is essential for real-time upload progress updates.


21-26: Verify prop type consistency across components

Let's verify that the attachmentHelpers prop type is consistent across related components.

✅ Verification successful

Let me gather more information about the type definitions to ensure proper typing across components.

This will help us verify the type consistency and proper usage of attachmentHelpers across components.


Let me gather one final piece of information to verify the Props type definitions in the components where attachmentHelpers is used.

This will help us confirm the type consistency across all components using attachmentHelpers.


Let me try one more focused search to find the Props type definition for IssueAttachmentItemList.

This will help us verify the type consistency specifically for the IssueAttachmentItemList component.


Based on the gathered information, I can now provide the final response:

Props type consistency verified for attachmentHelpers

The attachmentHelpers prop is properly typed and consistently used across components:

  • The IssueAttachmentItemList component correctly defines it as TAttachmentHelpers type
  • The type TAttachmentHelpers is properly defined in web/core/components/issues/issue-detail-widgets/attachments/helper.tsx
  • The same type is consistently imported and used in related components:
    • attachment-detail.tsx
    • attachments-list.tsx
    • attachment-item-list.tsx
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for IssueAttachmentItemList component definition and usages
ast-grep --pattern 'type Props = {
  $$$
  attachmentHelpers: $_
  $$$
}'

# Search for other components using attachmentHelpers
rg -A 2 'attachmentHelpers.*=' 

Length of output: 2401


Script:

#!/bin/bash
# Search for attachmentHelpers type definition and the hook that creates it
ast-grep --pattern 'useAttachmentOperations'

# Search for Props type definitions in attachment-related components
ast-grep --pattern 'interface Props {
  $$$
  attachmentHelpers: $_
  $$$
}'

# Also search with 'type' keyword
ast-grep --pattern 'type Props = {
  $$$
  attachmentHelpers: $_
  $$$
}'

# Look for the actual type definition of attachmentHelpers
rg "type.*AttachmentHelpers.*=" -A 3

Length of output: 1640


Script:

#!/bin/bash
# Search for Props definitions in the specific components we found earlier
rg -B 2 -A 5 "Props.*{" web/core/components/issues/attachment/attachment-item-list.tsx web/core/components/issues/attachment/attachments-list.tsx web/core/components/issues/attachment/attachment-detail.tsx

# Also check the imports of TAttachmentHelpers type
rg "import.*TAttachmentHelpers" 

Length of output: 38638


Script:

#!/bin/bash
# Search for IssueAttachmentItemList component and its Props definition
rg -B 5 -A 10 "IssueAttachmentItemList" web/core/components/issues/attachment/attachment-item-list.tsx

# Also search for TAttachmentHelpers usage
rg "TAttachmentHelpers" --type ts

Length of output: 1965

web/core/services/file-upload.service.ts (3)

1-1: LGTM: Clean import addition

The addition of AxiosRequestConfig type import is appropriate for typing the new progress handler parameter.


12-16: Verify error handling for the progress handler

The method signature change looks good, but we should ensure proper error handling for the progress handler callback.

Consider wrapping the progress handler invocation in a try-catch block to prevent upload failures if the handler throws an error. Here's a suggested implementation:

  async uploadFile(
    url: string,
    data: FormData,
    uploadProgressHandler?: AxiosRequestConfig["onUploadProgress"]
  ): Promise<void> {
+   const safeProgressHandler = uploadProgressHandler 
+     ? (event: any) => {
+         try {
+           uploadProgressHandler(event);
+         } catch (error) {
+           console.error('Error in upload progress handler:', error);
+         }
+       }
+     : undefined;
    this.cancelSource = axios.CancelToken.source();
    return this.post(url, data, {
      // ... other config
-     onUploadProgress: uploadProgressHandler,
+     onUploadProgress: safeProgressHandler,
    })

Line range hint 1-35: Verify integration with error handling system

The implementation looks solid, but let's verify the error handling integration across the codebase.

✅ Verification successful

Based on the search results, I can now verify the error handling integration in the codebase. Let me provide the final response:

Error handling implementation is properly integrated across the codebase

The error handling in file-upload.service.ts follows the established patterns across the codebase:

  1. Proper error propagation with axios error handling:
.catch((error) => {
  if (axios.isCancel(error)) {
    console.log(error.message);
  } else {
    throw error?.response?.data;
  }
})
  1. Consistent error handling with:
  • Upload cancellation support via cancelUpload()
  • Progress tracking via onUploadProgress handler
  • Error response data extraction
  • Special handling for cancelled uploads

The implementation aligns with the error handling patterns used across other services in the codebase, making it consistent and reliable.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for error handling patterns in related files
echo "Checking error handling patterns..."
rg -A 5 "catch.*error" --type ts

echo "Checking upload progress handler usage..."
rg -A 5 "onUploadProgress" --type ts

echo "Checking cancelUpload usage..."
rg -A 5 "cancelUpload" --type ts

Length of output: 305432

web/core/components/issues/attachment/root.tsx (4)

3-6: LGTM: Clean import restructuring

The imports have been streamlined to support the new reactive architecture using MobX and the consolidated attachment operations hook.


18-18: LGTM: Enhanced reactivity with MobX observer

Wrapping the component with observer ensures reactive updates to attachment upload status changes, which is crucial for implementing the upload progress indicator.


31-31: Verify prop usage in child components

The prop structure has been updated to support upload progress tracking. Let's verify the implementation in child components.

#!/bin/bash
# Description: Check how child components use the new props
# Expected: Components should properly handle upload progress

# Check IssueAttachmentUpload implementation
ast-grep --pattern 'const IssueAttachmentUpload = $_({ attachmentOperations }: $_) => {
  $$$
}'

# Check IssueAttachmentsList implementation
ast-grep --pattern 'const IssueAttachmentsList = $_({ attachmentHelpers }: $_) => {
  $$$
}'

Also applies to: 33-33


22-22: Verify hook implementation details

The hook usage looks correct, but let's verify its implementation to ensure it properly handles upload progress tracking.

✅ Verification successful

Let me try different search patterns to locate the hook implementation and progress tracking logic.


Let's check the implementation in the helper file we found and look for progress tracking logic.


Let me check the implementation of createAttachment function since it's being used in the hook.


Hook implementation correctly handles upload progress tracking

The useAttachmentOperations hook is properly implemented with upload progress tracking functionality. Here's what I found:

  • The hook uses createAttachment from the store which includes progress tracking through:
    • Maintains upload status in attachmentsUploadStatusMap with progress percentage
    • Provides progress updates through a callback in the upload service
    • Exposes upload status through getAttachmentsUploadStatusByIssueId method
  • The hook returns both operations and snapshot of upload status:
    return {
      operations: attachmentOperations,
      snapshot: { uploadStatus: attachmentsUploadStatus }
    };
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the implementation of useAttachmentOperations hook
# Expected: Hook should handle upload progress tracking

# Look for the hook implementation
ast-grep --pattern 'export const useAttachmentOperations = $_'

# Check for progress tracking logic
rg -A 10 'uploadProgress|onProgress|progress' --type ts --type tsx

Length of output: 193


Script:

#!/bin/bash
# Search for the hook implementation with a broader pattern
ast-grep --pattern 'useAttachmentOperations'

# Search for the hook implementation file
fd 'attachment.*operations' --type f

# Look for upload-related logic with broader context
rg -A 15 'upload.*attachment|attachment.*upload' --type ts --type tsx

Length of output: 1193


Script:

#!/bin/bash
# Check the helper file implementation
cat "web/core/components/issues/issue-detail-widgets/attachments/helper.tsx"

# Look for upload-related logic with correct file type
rg -A 15 'upload.*attachment|attachment.*upload' --type typescript --type tsx

Length of output: 4025


Script:

#!/bin/bash
# Look for createAttachment implementation
ast-grep --pattern 'createAttachment = async'

# Check the store file for attachment implementation
fd 'attachment.store' --type f --exec cat {}

Length of output: 6924

web/core/components/issues/attachment/attachments-list.tsx (4)

5-6: LGTM! Clean import organization.

The imports are well-structured and properly categorized with comments.

Also applies to: 9-9


18-18: LGTM! Clean state management structure.

Good use of destructuring and clear separation between store and props usage.

Also applies to: 24-25


Line range hint 1-42: Verify integration with new components.

Let's ensure proper integration with the upload details component and helpers.

✅ Verification successful

Let me gather more information about the implementation details of the related components.


Based on the gathered information, I can now provide the final response as the implementation details are clear.

Integration with upload components and helpers is properly implemented

The verification shows that:

  • IssueAttachmentsUploadDetails correctly handles upload status display through the uploadStatus prop
  • TAttachmentHelpers type is consistently used across components and properly structured with operations and snapshot properties
  • Both list components (IssueAttachmentsList and IssueAttachmentItemList) correctly implement the attachment helpers and upload status handling
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the implementation of related components
# Expect: IssueAttachmentsUploadDetails should handle upload status display
# Expect: TAttachmentHelpers should be used consistently

# Check IssueAttachmentsUploadDetails implementation
ast-grep --pattern 'const IssueAttachmentsUploadDetails = $$$'

# Check consistent usage of attachmentHelpers
rg -l "attachmentHelpers.*TAttachmentHelpers"

Length of output: 234


Script:

#!/bin/bash
# Check IssueAttachmentsUploadDetails implementation and its usage of upload status
rg -A 10 "IssueAttachmentsUploadDetails.*=.*" web/core/components/issues/attachment/

# Check the helper type definition and its usage
rg -A 5 "type TAttachmentHelpers = " web/core/components/issues/

# Check attachment-item-list.tsx content as it uses attachmentHelpers
cat web/core/components/issues/attachment/attachment-item-list.tsx

Length of output: 7436


13-13: Verify the structure of TAttachmentHelpers type.

The type change from specific operation handler to a more generic helpers object is a good abstraction.

Let's verify the type definition:

✅ Verification successful

Let me try a different approach to find the type definition.


Let me check the related types (TAttachmentOperations and TAttachmentSnapshot) to fully understand the structure.


I can now generate the final response based on the comprehensive type information found.

Type structure verified and properly supports upload progress tracking

The TAttachmentHelpers type is well-structured with:

  • operations: Handles file operations (create/remove)
  • snapshot: Contains upload status tracking via uploadStatus array

The type change from handleAttachmentOperations to attachmentHelpers is justified as it now encompasses both operations and status tracking capabilities, which aligns with the PR's objective to handle upload progress.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the structure of TAttachmentHelpers type
# Expect: Type should include upload status handling capabilities

ast-grep --pattern 'type TAttachmentHelpers = {
  $$$
}'

Length of output: 234


Script:

#!/bin/bash
# Search for TAttachmentHelpers type definition using ripgrep
rg "type TAttachmentHelpers" -A 5

# Also search in potential type definition files
fd "types?.ts" --exec rg "TAttachmentHelpers" {} \;

Length of output: 666


Script:

#!/bin/bash
# Search for related type definitions
rg "type TAttachmentOperations" -A 10
rg "type TAttachmentSnapshot" -A 10

# Also search for their usage context
rg "TAttachmentOperations|TAttachmentSnapshot" -B 2 -A 2

Length of output: 10572

web/core/services/api.service.ts (2)

2-2: LGTM! Important type import for upload progress tracking.

The addition of AxiosRequestConfig type is crucial as it provides type safety for upload progress configuration through the onUploadProgress callback.


38-40: Let me try a different search strategy to find attachment upload implementations.

#!/bin/bash
# Search for attachment-related files and their content
echo "Searching for attachment-related files..."
rg -l "attachment" --type ts

echo -e "\nSearching for file upload implementations..."
rg -A 5 "upload.*file|file.*upload" --type ts

echo -e "\nSearching for post method usage with FormData..."
rg -A 5 "post.*FormData|FormData.*post" --type ts

The previous search had issues with the file type and the pattern might have been too specific. This new search should help us find:

  1. Files related to attachments
  2. File upload implementations
  3. POST requests using FormData (common pattern for file uploads)
web/core/components/issues/attachment/delete-attachment-modal.tsx (2)

11-11: LGTM! Type definition improvement

The change from Exclude to Pick is a good improvement as it makes the type definition more explicit and follows the principle of least privilege by only selecting the required operation.

Also applies to: 13-13


19-19: LGTM! Prop naming improvement

The rename to attachmentOperations is more concise and better reflects the prop's purpose. This change maintains type safety while improving readability.

web/core/components/issues/attachment/attachment-list-upload-item.tsx (2)

1-17: LGTM! Well-organized imports and clean type definition.

The imports are properly categorized and the Props type is clearly defined.


19-45: Verify integration with attachment store.

Let's verify that the component correctly observes changes from the MobX store.

✅ Verification successful

Let me gather more information about the store implementation and usage to verify the integration.


Based on the gathered information, I can now generate the final response as we have sufficient evidence about the store integration.

Integration with attachment store is properly implemented

The component correctly integrates with the MobX store:

  • Component is wrapped with observer HOC to react to store changes
  • Store implements observable attachmentsUploadStatusMap with proper typing
  • Upload status updates are handled through MobX actions that modify the observable map
  • Parent component correctly passes the upload status as props
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for attachment store implementation and verify upload status handling
ast-grep --pattern 'interface IIssueAttachmentStore {
  $$$
  attachmentsUploadStatusMap: $_;
  $$$
}'

# Search for store usage in parent components
rg -A 5 'IssueAttachmentsUploadItem.*uploadStatus'

Length of output: 754


Script:

#!/bin/bash
# Search for attachment store implementation
ast-grep --pattern 'class $_ implements IIssueAttachmentStore {
  $$$
  attachmentsUploadStatusMap = $_;
  $$$
}'

# Search for store initialization and usage
rg -A 5 'attachmentsUploadStatusMap'

# Search for observer usage in the component
rg -B 5 'IssueAttachmentsUploadItem.*observer'

Length of output: 4685

web/core/components/issues/attachment/attachment-upload-details.tsx (1)

1-18: LGTM: Well-organized imports and clean type definition.

The imports are properly organized with clear grouping, and the Props interface is focused and simple.

web/core/components/issues/attachment/attachment-upload.tsx (2)

7-14: LGTM! Type definitions enhance safety and clarity.

The type refinements and prop renaming improve type safety by explicitly restricting operations to "create" only, while making the interface more semantic.


Line range hint 1-64: Verify implementation against PR requirements.

The current implementation shows a basic loading state but doesn't fully meet the PR objective of showing upload progress. Please ensure that:

  1. The attachment upload API supports progress tracking
  2. Progress information is properly passed through attachmentOperations.create
  3. UI reflects the actual upload progress
web/core/components/issues/issue-detail-widgets/issue-detail-widget-collapsibles.tsx (2)

28-28: LGTM: Clean hook dependency addition

The addition of getAttachmentsUploadStatusByIssueId to the destructured hooks is well-placed and follows the existing pattern.


40-42: LGTM: Improved attachment visibility logic

The new implementation elegantly handles both existing attachments and in-progress uploads. The condition ensures the attachment section remains visible during active uploads, providing better user feedback.

A few key points worth noting:

  1. The section shows if there are existing attachments (attachment_count > 0)
  2. The section also shows if there are ongoing uploads (attachmentUploads.length > 0)
  3. The null checks (!!) provide good safety against undefined values
web/core/components/issues/issue-detail-widgets/attachments/quick-action-button.tsx (2)

33-33: LGTM! Improved hook usage with explicit destructuring.

The destructuring of operations from the hook makes the code more maintainable by clearly showing what we're consuming from the hook.


70-70: LGTM! Proper dependency array update.

The dependency array correctly includes all necessary values used within the callback.

web/core/services/issue/issue_attachment.service.ts (3)

1-1: LGTM: Appropriate type import for upload progress handling

The addition of AxiosRequestConfig import is correct for typing the upload progress handler.


39-40: LGTM: Well-typed optional parameter addition

The upload progress handler is correctly implemented as an optional parameter with the appropriate axios type, maintaining backward compatibility.


50-54: Verify FileUploadService implementation

Let's verify that the FileUploadService's uploadFile method properly implements the upload progress handler parameter.

✅ Verification successful

Let me try a different search strategy to locate the FileUploadService implementation.

This will help us:

  1. Find files containing FileUploadService class
  2. Look for uploadFile method implementation with context

The first command helps locate the file, while the second one searches for the method implementation with surrounding context to verify the progress handler parameter usage.


Based on the search results, I can now provide a final response:

FileUploadService properly implements the upload progress handler parameter

The implementation in web/core/services/file-upload.service.ts shows that the uploadFile method correctly accepts and handles the uploadProgressHandler parameter:

async uploadFile(
  url: string,
  data: FormData,
  uploadProgressHandler?: AxiosRequestConfig["onUploadProgress"]
): Promise<void>

The method signature matches the usage in issue_attachment.service.ts, and the parameter is properly typed as an optional Axios upload progress handler. This confirms that the integration is correctly implemented.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for the uploadFile method implementation in FileUploadService
ast-grep --pattern 'class FileUploadService {
  $$$
  uploadFile($$$) {
    $$$
  }
  $$$
}'

Length of output: 94


Script:

#!/bin/bash
# Search for files containing FileUploadService
fd -t f . | xargs rg "FileUploadService" -l

# Search for uploadFile method implementation
rg "uploadFile.*\(" -A 10

Length of output: 25945

web/core/components/issues/attachment/attachment-detail.tsx (3)

22-22: LGTM! Import refactor improves code organization.

Moving the attachment helpers to a dedicated file improves code organization and maintainability.


26-30: LGTM! Props interface is well-defined.

The prop rename to attachmentHelpers better describes its purpose, and the type constraint appropriately restricts available operations.


34-34: LGTM! Component implementation properly adapted.

The component correctly implements the new attachment helpers structure while maintaining its original functionality.

Also applies to: 59-59

web/core/components/issues/attachment/attachment-item-list.tsx (3)

11-11: LGTM: Type and import changes align with upload progress feature.

The introduction of TAttachmentHelpers type and IssueAttachmentsUploadItem component properly supports the new upload progress functionality.

Also applies to: 14-14, 21-21


26-26: LGTM: Improved state management and prop handling.

The renaming of isLoading to isUploading and the structured destructuring of attachmentHelpers improve code clarity and semantic accuracy.

Also applies to: 28-28, 35-37


Line range hint 1-122: Verify upload progress implementation meets requirements.

The implementation successfully achieves the PR objective of showing upload progress for issue attachments. The code includes:

  • Real-time upload status display
  • Proper error handling
  • User-friendly drag and drop interface
  • Disabled state handling

Let's verify the component's integration:

✅ Verification successful

Let me try different commands to verify the implementation.


Implementation is properly integrated and consistent across the codebase

The verification shows that:

  • The attachmentHelpers prop is consistently used throughout the attachment-related components hierarchy:
    • Initialized in parent components (content.tsx and root.tsx)
    • Properly passed down through attachments-list.tsx to attachment-item-list.tsx
    • Correctly utilized in child components for operations and snapshot access
  • The IssueAttachmentsUploadItem component is:
    • Properly implemented in attachment-list-upload-item.tsx
    • Correctly imported and used in attachment-item-list.tsx
    • Exported through the barrel file (index.ts)

The implementation demonstrates proper component integration, consistent prop passing, and adherence to the component hierarchy design.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for consistent usage of the new attachmentHelpers prop
rg -l "attachmentHelpers" --type typescript

# Verify the presence of required components
rg -l "IssueAttachmentsUploadItem" --type typescript

Length of output: 166


Script:

#!/bin/bash
# Check for consistent usage of the new attachmentHelpers prop
rg "attachmentHelpers"

# Verify the presence of required components
rg "IssueAttachmentsUploadItem"

# Look for related attachment upload components and their usage
rg "attachment-list-upload-item"

Length of output: 2642

web/core/components/issues/issue-detail-widgets/attachments/helper.tsx (1)

105-110: Handle potential undefined value in attachmentsUploadStatus

The attachmentsUploadStatus retrieved by getAttachmentsUploadStatusByIssueId(issueId) may be undefined. Ensure that any components consuming snapshot.uploadStatus handle the undefined case gracefully to prevent runtime errors.

Consider providing a default value or adding null checks where uploadStatus is used.

 return {
   operations: attachmentOperations,
-  snapshot: { uploadStatus: attachmentsUploadStatus },
+  snapshot: { uploadStatus: attachmentsUploadStatus || [] },
 };

Comment on lines +23 to +25
const fileName = uploadStatus.name;
const fileExtension = getFileExtension(uploadStatus.name ?? "");
const fileIcon = getFileIcon(fileExtension, 18);
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 null checks and memoize derived values.

The file name and extension calculations should be memoized and protected against undefined values.

-  const fileName = uploadStatus.name;
-  const fileExtension = getFileExtension(uploadStatus.name ?? "");
-  const fileIcon = getFileIcon(fileExtension, 18);
+  const fileName = useMemo(() => uploadStatus.name ?? "", [uploadStatus.name]);
+  const fileExtension = useMemo(
+    () => getFileExtension(fileName),
+    [fileName]
+  );
+  const fileIcon = useMemo(
+    () => getFileIcon(fileExtension, 18),
+    [fileExtension]
+  );
📝 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 fileName = uploadStatus.name;
const fileExtension = getFileExtension(uploadStatus.name ?? "");
const fileIcon = getFileIcon(fileExtension, 18);
const fileName = useMemo(() => uploadStatus.name ?? "", [uploadStatus.name]);
const fileExtension = useMemo(
() => getFileExtension(fileName),
[fileName]
);
const fileIcon = useMemo(
() => getFileIcon(fileExtension, 18),
[fileExtension]
);

Comment on lines +29 to +43
return (
<div className="flex items-center justify-between gap-3 h-11 bg-custom-background-90 pl-9 pr-2 pointer-events-none">
<div className="flex items-center gap-3 text-sm truncate">
<div className="flex-shrink-0">{fileIcon}</div>
<Tooltip tooltipContent={fileName} isMobile={isMobile}>
<p className="text-custom-text-200 font-medium truncate">{fileName}</p>
</Tooltip>
</div>
<div className="flex-shrink-0 flex items-center gap-2">
<span className="flex-shrink-0">
<CircularProgressIndicator size={20} strokeWidth={3} percentage={uploadStatus.progress} />
</span>
<div className="flex-shrink-0 text-sm font-medium">{uploadStatus.progress}% done</div>
</div>
</div>
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

Enhance accessibility attributes.

The upload item should have proper ARIA attributes for better screen reader support.

-    <div className="flex items-center justify-between gap-3 h-11 bg-custom-background-90 pl-9 pr-2 pointer-events-none">
+    <div 
+      role="status"
+      aria-label={`Uploading ${fileName}, ${uploadStatus.progress}% complete`}
+      className="flex items-center justify-between gap-3 h-11 bg-custom-background-90 pl-9 pr-2 pointer-events-none"
+    >
📝 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
return (
<div className="flex items-center justify-between gap-3 h-11 bg-custom-background-90 pl-9 pr-2 pointer-events-none">
<div className="flex items-center gap-3 text-sm truncate">
<div className="flex-shrink-0">{fileIcon}</div>
<Tooltip tooltipContent={fileName} isMobile={isMobile}>
<p className="text-custom-text-200 font-medium truncate">{fileName}</p>
</Tooltip>
</div>
<div className="flex-shrink-0 flex items-center gap-2">
<span className="flex-shrink-0">
<CircularProgressIndicator size={20} strokeWidth={3} percentage={uploadStatus.progress} />
</span>
<div className="flex-shrink-0 text-sm font-medium">{uploadStatus.progress}% done</div>
</div>
</div>
return (
<div
role="status"
aria-label={`Uploading ${fileName}, ${uploadStatus.progress}% complete`}
className="flex items-center justify-between gap-3 h-11 bg-custom-background-90 pl-9 pr-2 pointer-events-none"
>
<div className="flex items-center gap-3 text-sm truncate">
<div className="flex-shrink-0">{fileIcon}</div>
<Tooltip tooltipContent={fileName} isMobile={isMobile}>
<p className="text-custom-text-200 font-medium truncate">{fileName}</p>
</Tooltip>
</div>
<div className="flex-shrink-0 flex items-center gap-2">
<span className="flex-shrink-0">
<CircularProgressIndicator size={20} strokeWidth={3} percentage={uploadStatus.progress} />
</span>
<div className="flex-shrink-0 text-sm font-medium">{uploadStatus.progress}% done</div>
</div>
</div>

Comment on lines +20 to +54
export const IssueAttachmentsUploadDetails: React.FC<Props> = observer((props) => {
// props
const { uploadStatus } = props;
// derived values
const fileName = uploadStatus.name;
const fileExtension = getFileExtension(uploadStatus.name ?? "");
const fileIcon = getFileIcon(fileExtension, 28);
// hooks
const { isMobile } = usePlatformOS();

return (
<div className="flex h-[60px] items-center justify-between gap-1 rounded-md border-[2px] border-custom-border-200 bg-custom-background-90 px-4 py-2 text-sm pointer-events-none">
<div className="flex-shrink-0 flex items-center gap-3">
<div className="h-7 w-7">{fileIcon}</div>
<div className="flex flex-col gap-1">
<div className="flex items-center gap-2">
<Tooltip tooltipContent={fileName} isMobile={isMobile}>
<span className="text-sm">{truncateText(`${fileName}`, 10)}</span>
</Tooltip>
</div>

<div className="flex items-center gap-3 text-xs text-custom-text-200">
<span>{fileExtension.toUpperCase()}</span>
</div>
</div>
</div>
<div className="flex-shrink-0 flex items-center gap-2">
<span className="flex-shrink-0">
<CircularProgressIndicator size={20} strokeWidth={3} percentage={uploadStatus.progress} />
</span>
<div className="flex-shrink-0 text-sm font-medium">{uploadStatus.progress}% done</div>
</div>
</div>
);
});
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

Consider performance optimizations.

The component could benefit from performance optimizations to prevent unnecessary re-renders.

  1. Memoize derived values:
+  const derivedValues = useMemo(() => ({
+    fileName: uploadStatus.name || "Unknown file",
+    fileExtension: getFileExtension(uploadStatus.name ?? ""),
+    fileIcon: getFileIcon(fileExtension, 28)
+  }), [uploadStatus.name]);
  1. Extract the progress indicator into a separate memoized component to prevent re-renders of the entire component when only the progress changes.

Committable suggestion was skipped due to low confidence.

web/core/store/issue/issue-details/attachment.store.ts Outdated Show resolved Hide resolved
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

🧹 Outside diff range and nitpick comments (3)
web/core/store/issue/issue-details/attachment.store.ts (3)

17-23: Add JSDoc comments to improve type documentation.

Consider adding JSDoc comments to document the purpose and constraints of each field in the TAttachmentUploadStatus type.

+/**
+ * Represents the upload status of an attachment
+ * @property id - Temporary UUID for tracking the upload
+ * @property name - Original filename
+ * @property progress - Upload progress percentage (0-100)
+ * @property size - File size in bytes
+ * @property type - MIME type of the file
+ */
export type TAttachmentUploadStatus = {
  id: string;
  name: string;
  progress: number;
  size: number;
  type: string;
};

60-60: Initialize nested map structure in constructor.

Consider initializing the nested map structure in the constructor to prevent potential runtime errors when accessing nested properties.

-attachmentsUploadStatusMap: Record<string, Record<string, TAttachmentUploadStatus>> = {};
+attachmentsUploadStatusMap: Record<string, Record<string, TAttachmentUploadStatus>> = {};
+
+constructor(rootStore: IIssueRootStore) {
+  // ... existing code ...
+  runInAction(() => {
+    this.attachmentsUploadStatusMap = new Proxy({}, {
+      get: (target, issueId: string) => target[issueId] || (target[issueId] = {})
+    });
+  });
+}

96-100: Add type guards and defensive checks.

The helper method could be more robust with additional type guards and null checks.

getAttachmentsUploadStatusByIssueId = computedFn((issueId: string) => {
-  if (!issueId) return undefined;
+  if (!issueId || typeof issueId !== 'string') return undefined;
+  if (!(issueId in this.attachmentsUploadStatusMap)) return undefined;
  const attachmentsUploadStatus = Object.values(this.attachmentsUploadStatusMap[issueId] ?? {});
-  return attachmentsUploadStatus ?? undefined;
+  return attachmentsUploadStatus.length > 0 ? attachmentsUploadStatus : undefined;
});
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between d99c964 and 65af4de.

📒 Files selected for processing (1)
  • web/core/store/issue/issue-details/attachment.store.ts (6 hunks)
🧰 Additional context used
🪛 Biome
web/core/store/issue/issue-details/attachment.store.ts

[error] 160-160: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

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: 2

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 65af4de and 49693d0.

📒 Files selected for processing (1)
  • web/core/store/issue/issue-details/attachment.store.ts (6 hunks)
🧰 Additional context used
🪛 Biome
web/core/store/issue/issue-details/attachment.store.ts

[error] 160-160: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (3)
web/core/store/issue/issue-details/attachment.store.ts (3)

17-23: LGTM: Well-structured type definitions for upload status tracking

The new TAttachmentUploadStatus type and interface updates are well-designed, providing a comprehensive structure for tracking upload progress.

Also applies to: 47-47, 51-51


60-60: LGTM: Proper MobX observable setup

The attachmentsUploadStatusMap is correctly initialized and properly configured as an observable property.

Also applies to: 72-72


135-157: 🛠️ Refactor suggestion

Consider adding file validation

The method should validate the file before upload (size limits, allowed types, etc.).

 createAttachment = async (workspaceSlug: string, projectId: string, issueId: string, file: File) => {
   const tempId = uuidv4();
+  // Validate file
+  const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
+  const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'application/pdf'];
+  
+  if (file.size > MAX_FILE_SIZE) {
+    throw new Error(`File size exceeds ${MAX_FILE_SIZE / (1024 * 1024)}MB limit`);
+  }
+  if (!ALLOWED_TYPES.includes(file.type)) {
+    throw new Error(`File type ${file.type} not supported`);
+  }
+
   try {

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: 1

🧹 Outside diff range and nitpick comments (1)
web/core/components/issues/attachment/attachment-item-list.tsx (1)

96-129: Consider enhancing accessibility for drag and drop.

While the UI improvements for upload status are great, consider these accessibility enhancements:

  1. Add ARIA labels to the drag and drop area
  2. Ensure keyboard navigation support for the upload functionality
 <div
   {...getRootProps()}
+  role="button"
+  aria-label="Upload attachment"
   className={`relative flex flex-col ${isDragActive && issueAttachments.length < 3 ? "min-h-[200px]" : ""} ${
     disabled ? "cursor-not-allowed" : "cursor-pointer"
   }`}
 >
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 49693d0 and c909aa9.

📒 Files selected for processing (4)
  • web/core/components/issues/attachment/attachment-item-list.tsx (4 hunks)
  • web/core/components/issues/issue-detail-widgets/attachments/content.tsx (2 hunks)
  • web/core/components/issues/issue-detail-widgets/attachments/helper.tsx (3 hunks)
  • web/core/components/issues/issue-detail-widgets/attachments/quick-action-button.tsx (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • web/core/components/issues/issue-detail-widgets/attachments/content.tsx
  • web/core/components/issues/issue-detail-widgets/attachments/quick-action-button.tsx
🔇 Additional comments (5)
web/core/components/issues/issue-detail-widgets/attachments/helper.tsx (2)

3-22: LGTM! Well-structured type definitions.

The new type definitions create a clear separation between operations and status snapshots, making the code more maintainable and type-safe.


Line range hint 23-110: LGTM! Comprehensive implementation with proper error handling and analytics.

The hook implementation provides a robust solution for managing attachment operations with:

  • Proper loading states and user feedback
  • Comprehensive error handling
  • Analytics tracking for both success and failure cases
  • Efficient memoization with complete dependency array
web/core/components/issues/attachment/attachment-item-list.tsx (3)

10-22: LGTM! Props and types are well-structured.

The refactoring of props to use TAttachmentHelpers improves type safety and maintainability.


Line range hint 58-84: LGTM! Upload handling logic is sound.

The upload logic correctly handles file restrictions and provides user feedback.


27-39: Verify state synchronization.

While the state management is cleaner, be cautious of potential race conditions between isUploading and uploadStatus. Consider if both states are necessary or if uploadStatus alone could handle all cases.

Comment on lines 37 to 39
try {
if (!workspaceSlug || !projectId || !issueId) throw new Error("Missing required fields");

const attachmentUploadPromise = createAttachment(workspaceSlug, projectId, issueId, file);
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

Consider improving error messages and standardizing event payloads.

  1. The error message "Missing required fields" could be more specific about which fields are missing.
  2. The event tracking payload structure differs between create and remove methods - the remove method includes additional update details on failure.

Consider applying these improvements:

-          if (!workspaceSlug || !projectId || !issueId) throw new Error("Missing required fields");
+          if (!workspaceSlug || !projectId || !issueId) {
+            const missingFields = [
+              !workspaceSlug && "workspaceSlug",
+              !projectId && "projectId",
+              !issueId && "issueId",
+            ].filter(Boolean);
+            throw new Error(`Missing required fields: ${missingFields.join(", ")}`);
+          }

And standardize the event payload structure:

           captureIssueEvent({
             eventName: "Issue attachment deleted",
             payload: { id: issueId, state: "FAILED", element: "Issue detail page" },
-            updates: {
-              changed_property: "attachment",
-              change_details: "",
-            },
           });

Also applies to: 69-73

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: 3

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between c909aa9 and 5f31921.

📒 Files selected for processing (1)
  • web/core/store/issue/issue-details/attachment.store.ts (6 hunks)
🧰 Additional context used
🪛 Biome
web/core/store/issue/issue-details/attachment.store.ts

[error] 160-160: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🔇 Additional comments (1)
web/core/store/issue/issue-details/attachment.store.ts (1)

17-23: LGTM! Well-structured type definitions.

The new TAttachmentUploadStatus type and interface updates are well-designed, providing comprehensive tracking of upload status with all necessary fields.

Also applies to: 47-47, 51-51

Comment on lines +164 to +166
this.rootIssueStore.issues.updateIssue(issueId, {
attachment_count: issueAttachmentsCount + 1, // increment attachment count
});
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

Potential race condition in attachment count update.

The current implementation might lead to incorrect attachment counts if multiple uploads complete simultaneously, as the count is read and updated in separate operations.

-          this.rootIssueStore.issues.updateIssue(issueId, {
-            attachment_count: issueAttachmentsCount + 1, // increment attachment count
-          });
+          // Use atomic increment operation
+          this.rootIssueStore.issues.updateIssue(issueId, (issue) => ({
+            ...issue,
+            attachment_count: (issue.attachment_count || 0) + 1,
+          }));
📝 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.rootIssueStore.issues.updateIssue(issueId, {
attachment_count: issueAttachmentsCount + 1, // increment attachment count
});
// Use atomic increment operation
this.rootIssueStore.issues.updateIssue(issueId, (issue) => ({
...issue,
attachment_count: (issue.attachment_count || 0) + 1,
}));

Comment on lines +129 to +133
debouncedUpdateProgress = debounce((issueId: string, tempId: string, progress: number) => {
runInAction(() => {
set(this.attachmentsUploadStatusMap, [issueId, tempId, "progress"], progress);
});
}, 16);
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

Consider increasing the debounce interval.

The current debounce interval of 16ms (roughly one frame) might be too aggressive, potentially causing unnecessary re-renders. Consider increasing it to a more reasonable value (e.g., 100-250ms) to balance responsiveness and performance.

   debouncedUpdateProgress = debounce((issueId: string, tempId: string, progress: number) => {
     runInAction(() => {
       set(this.attachmentsUploadStatusMap, [issueId, tempId, "progress"], progress);
     });
-  }, 16);
+  }, 100); // Increased interval for better performance
📝 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
debouncedUpdateProgress = debounce((issueId: string, tempId: string, progress: number) => {
runInAction(() => {
set(this.attachmentsUploadStatusMap, [issueId, tempId, "progress"], progress);
});
}, 16);
debouncedUpdateProgress = debounce((issueId: string, tempId: string, progress: number) => {
runInAction(() => {
set(this.attachmentsUploadStatusMap, [issueId, tempId, "progress"], progress);
});
}, 100); // Increased interval for better performance

Comment on lines +135 to +157
createAttachment = async (workspaceSlug: string, projectId: string, issueId: string, file: File) => {
const tempId = uuidv4();
try {
// update attachment upload status
runInAction(() => {
update(this.attachments, [issueId], (attachmentIds = []) => uniq(concat(attachmentIds, [response.id])));
set(this.attachmentMap, response.id, response);
this.rootIssueStore.issues.updateIssue(issueId, {
attachment_count: issueAttachmentsCount + 1, // increment attachment count
set(this.attachmentsUploadStatusMap, [issueId, tempId], {
id: tempId,
name: file.name,
progress: 0,
size: file.size,
type: file.type,
});
});
}
const response = await this.issueAttachmentService.uploadIssueAttachment(
workspaceSlug,
projectId,
issueId,
file,
(progressEvent) => {
const progressPercentage = Math.round((progressEvent.progress ?? 0) * 100);
this.debouncedUpdateProgress(issueId, tempId, progressPercentage);
}
);
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

⚠️ Potential issue

Use existing file size constant and add type validation

The codebase already defines MAX_STATIC_FILE_SIZE in constants/common.ts for file size validation. Let's reuse this constant instead of defining a new one.

+import { MAX_STATIC_FILE_SIZE } from "@/constants/common";
+
   createAttachment = async (workspaceSlug: string, projectId: string, issueId: string, file: File) => {
     const tempId = uuidv4();
+    const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'application/pdf']; // adjust as needed
+    
+    if (file.size > MAX_STATIC_FILE_SIZE) {
+      throw new Error(`File size exceeds maximum limit of ${MAX_STATIC_FILE_SIZE / (1024 * 1024)}MB`);
+    }
+    
+    if (!ALLOWED_TYPES.includes(file.type)) {
+      throw new Error(`File type ${file.type} is not supported`);
+    }
+
     try {
       // update attachment upload status
🔗 Analysis chain

Add file validation before upload.

The method should validate the file size and type before initiating the upload to prevent unnecessary network requests and provide better user feedback.

   createAttachment = async (workspaceSlug: string, projectId: string, issueId: string, file: File) => {
     const tempId = uuidv4();
+    const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
+    const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'application/pdf']; // adjust as needed
+    
+    if (file.size > MAX_FILE_SIZE) {
+      throw new Error(`File size exceeds maximum limit of ${MAX_FILE_SIZE / (1024 * 1024)}MB`);
+    }
+    
+    if (!ALLOWED_TYPES.includes(file.type)) {
+      throw new Error(`File type ${file.type} is not supported`);
+    }
+
     try {
       // update attachment upload status

Let me verify if there are any file type restrictions defined in the codebase.


Let me check if there are any existing file validation utilities or services that could be reused.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if file size limits are defined elsewhere
rg -i "file.*size.*limit|max.*file.*size" --type ts

Length of output: 5961


Script:

#!/bin/bash
# Check for file type restrictions or allowed file types
rg -i "allowed.*file.*type|file.*type.*support|mime.*type" --type ts

Length of output: 175


Script:

#!/bin/bash
# Search for file validation related code
rg -i "validate.*file|file.*validation" --type ts -A 5

Length of output: 143

@pushya22 pushya22 merged commit c423d7d into preview Oct 29, 2024
14 of 15 checks passed
@pushya22 pushya22 deleted the chore/upload-progress branch October 29, 2024 13:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants