Skip to content

Conversation

@imehedi357
Copy link

@imehedi357 imehedi357 commented Dec 22, 2025

  • Fixed the issue of downloads to be based on the filters and removed pagination on download.
  • Enhanced the withdraw download logs filename to include the filter information if available.

All Submissions:

  • My code follow the WordPress' coding standards
  • My code satisfies feature requirements
  • My code is tested
  • My code passes the PHPCS tests
  • My code has proper inline documentation
  • I've included related pull request(s) (optional)
  • I've included developer documentation (optional)
  • I've added proper labels to this pull request

Changes proposed in this Pull Request:

  1. Added set_filename_from_filters() method in WithdrawLogExporter.php:

    • Generates dynamic CSV filenames based on applied filters
    • Includes vendor name (if filtered by vendor)
    • Includes status (pending, approved, cancelled)
    • Includes payment method (paypal, bank, etc.)
    • Includes date range in mon-DD format (e.g., dec-01_to-dec-31)
  2. Updated WithdrawController.php:

    • Calls set_filename_from_filters() before generating the CSV file
    • Passes generated filename in the download URL parameter
  3. Updated Hooks.php:

    • Reads filename from URL parameter during download
    • Sets the filename on exporter before calling export()
  4. Fixed export button in index.tsx:

    • Updated export query to explicitly pass per_page, page, search, status, and filterArgs to ensure all filters are applied. (This was the bug that caused the filter to not working previously.)
    • Added comment explaining that totalItems is used to fetch all matching records in a single request to avoid pagination only when downloading.

Files Changed:

  • includes/Admin/WithdrawLogExporter.php - Added set_filename_from_filters() method
  • includes/REST/WithdrawController.php - Call set_filename_from_filters() and pass filename in URL
  • includes/Withdraw/Hooks.php - Read filename from URL and set it before export
  • src/admin/dashboard/pages/withdraw/index.tsx - Fixed per_page handling and export query parameters

Related Pull Request(s)

  • N/A

Closes

How to test the changes in this Pull Request:

  1. Go to Dokan → Withdraw in admin dashboard
  2. Apply various filters:
    • Select a specific vendor
    • Select a status (Pending, Approved, Cancelled)
    • Select a payment method
    • Select a date range
  3. Click the Export button
  4. Verify the downloaded CSV filename includes the filter information:
    • No filters: dokan-withdraw-log-export.csv
    • With vendor: dokan-withdraw-log-export_store-name.csv
    • With status: dokan-withdraw-log-export_cancelled.csv
    • With date range: dokan-withdraw-log-export_dec-01_to_dec-31.csv
    • Multiple filters: dokan-withdraw-log-export_store-name_pending_paypal_dec-01_to_dec-31.csv
  5. Verify the CSV contains all filtered records (not just first visible page - pagination is bypassed)

Changelog entry

Enhanced Withdraw Export CSV Filename

Previously, the withdraw export CSV file was always downloaded with a generic filename dokan-withdraw-log-export.csv regardless of any filters applied. Additionally, the export query parameters were not explicitly passed, which led to issues with the filter not working.

Now, the exported CSV filename dynamically includes the applied filter information, making it easier to identify and organize downloaded files. The export also explicitly passes all filter parameters to ensure all matching records are exported in a single request, bypassing pagination. For example, exporting cancelled withdrawals for "My Store" in December will generate a filename like dokan-withdraw-log-export_my-store_cancelled_dec-01_to_dec-31.csv.

Before Changes

  • Exported CSV always had generic filename: dokan-withdraw-log-export.csv
  • Difficult to identify which filters were applied when looking at downloaded files
  • Export query used ...view spread which may not have included all filter parameters
  • Pagination made only the visible page logs to download and not all the logs.

After Changes

  • Exported CSV filename now includes applied filters
  • Example filenames:
    • dokan-withdraw-log-export.csv (no filters)
    • dokan-withdraw-log-export_my-store.csv (vendor filter)
    • dokan-withdraw-log-export_cancelled.csv (status filter)
    • dokan-withdraw-log-export_my-store_pending_paypal_dec-01_to_dec-31.csv (all filters used)
  • Easy to identify and organize downloaded export files
  • Export query explicitly passes per_page, page, search, status, and filterArgs to ensure all filters are applied
  • All matching records are exported in a single request (pagination bypassed only when downloading)

Feature Video (optional)

N/A

PR Self Review Checklist:

  • Code is not following code style guidelines
  • Bad naming: make sure you would understand your code if you read it a few months from now.
  • KISS: Keep it simple, Sweetie (not stupid!).
  • DRY: Don't Repeat Yourself.
  • Code that is not readable: too many nested 'if's are a bad sign.
  • Performance issues
  • Complicated constructions that need refactoring or comments: code should almost always be self-explanatory.
  • Grammar errors.

FOR PR REVIEWER ONLY:

As a reviewer, your feedback should be focused on the idea, not the person. Seek to understand, be respectful, and focus on constructive dialog.

As a contributor, your responsibility is to learn from suggestions and iterate your pull request should it be needed based on feedback. Seek to collaborate and produce the best possible contribution to the greater whole.

  • Correct — Does the change do what it’s supposed to? ie: code 100% fulfilling the requirements?
  • Secure — Would a nefarious party find some way to exploit this change? ie: everything is sanitized/escaped appropriately for any SQL or XSS injection possibilities?
  • Readable — Will your future self be able to understand this change months down the road?
  • Elegant — Does the change fit aesthetically within the overall style and architecture?

Summary by CodeRabbit

  • New Features

    • Export filenames now reflect applied filters (vendor, status, payment method, date range) for clearer file names.
    • Export button shows a spinner and progress percentage, and is disabled during export; completes by redirecting to the generated file.
  • Bug Fixes / Improvements

    • Export flow switched to a paginated multi-step process with consistent batch sizing and accepts a filename parameter for downloads.

✏️ Tip: You can customize this high-level summary in your review settings.

- Fixed the issue of downloads to be based on the filters and removed pagination on download.
- Enhanced the withdraw download logs filename to include the filter information if available.
@imehedi357 imehedi357 requested a review from mrabbani December 22, 2025 07:03
@imehedi357 imehedi357 added Needs: Testing This requires further testing Needs: Dev Review It requires a developer review and approval labels Dec 22, 2025
@imehedi357 imehedi357 self-assigned this Dec 22, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 22, 2025

📝 Walkthrough

Walkthrough

Frontend switches to a paginated multi-step export with progress; the REST controller sets the exporter filename from active filters and returns it in the download URL; WithdrawLogExporter gains a filter-aware filename builder; hooks read an optional filename URL parameter to initialize the exporter.

Changes

Cohort / File(s) Summary
Export filename generation
includes/Admin/WithdrawLogExporter.php
Changed default protected $filename to dokan-withdraw-log.csv and added public function set_filename_from_filters( $filters = [] ) to build and set a sanitized, truncated filename including vendor (from user_id), status, payment_method, and start/end dates when provided.
REST API export handling
includes/REST/WithdrawController.php
Uses $request['page'] for paging, calls set_filename_from_filters(...) before export, sets exporter total from $withdraws->total, and includes filename => $exporter->get_filename() in the completed-export download URL.
Export hook middleware
includes/Withdraw/Hooks.php
Reads optional filename request parameter, sanitizes/unslashes it, and calls exporter set_filename() before export download is served.
Frontend export UX & flow
src/admin/dashboard/pages/withdraw/index.tsx, src/admin/pages/Withdraw.vue
Frontend adds isExporting and exportProgress, implements multi-step/paginated export with polling and progress updates, disables button during export, and uses fixed per_page = 100 with explicit page argument when iterating pages.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor User
    participant Frontend as Withdraw Page (React/Vue)
    participant Controller as REST WithdrawController
    participant Exporter as WithdrawLogExporter
    participant Hooks as Withdraw Hooks
    Note over Frontend,Controller: Export initiated with active filters

    User->>Frontend: Click "Export" (filters applied)
    Frontend->>Controller: GET /withdraw/export?page=1&per_page=100&filters...
    Controller->>Exporter: set_filename_from_filters({user_id,status,payment_method,start_date,end_date})
    Exporter-->>Controller: filename set
    Controller->>Exporter: query & set_items(page,per_page)
    Controller->>Exporter: set_total($withdraws->total)
    Controller->>Exporter: generate_export()
    Exporter-->>Controller: export file ready
    Controller-->>Frontend: 200 { download_url: /admin/download?...&filename=THE_FILENAME }
    Frontend->>User: redirect to download URL
    User->>Hooks: GET download URL (includes filename)
    Hooks->>Exporter: set_filename(sanitized_filename)  -- when filename param present
    Hooks-->>User: serve CSV download (browser saves CSV with filter-aware name)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • mrabbani

Poem

🐰
I stitched a filename neat and bright,
With vendor, dates, and status right,
Pages polled and progress shown,
A tidy CSV to call your own,
Hop — the export's done tonight!

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding dynamic filenames for CSV exports based on filters, which is the core enhancement in this PR.
Description check ✅ Passed The PR description is comprehensive, including changes proposed, related issues, testing steps, changelog, before/after comparisons, and checklist items from the template.
Linked Issues check ✅ Passed The PR addresses all requirements from issue #5251: it makes exports respect filters [5251], adds filter information to filenames [5251], bypasses pagination for exports [5251], and fixes the UX bug where downloads ignored filters [5251].
Out of Scope Changes check ✅ Passed All changes are directly scoped to the linked issue #5251: exporter filename method, controller export call, hooks filename handling, and UI export query parameters.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch enhance/withdraw-export-filename-filters

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
includes/REST/WithdrawController.php (1)

394-394: Critical: Incorrect argument type passed to set_total_rows().

Line 394 attempts to use $statuses[ $args['status'] ] as the total row count, but:

  • $statuses is an array of status name strings: ['pending', 'completed', 'cancelled']
  • $args['status'] is a numeric status code (0, 1, or 2)
  • This results in passing a string (e.g., 'pending') to set_total_rows(), which expects an integer (line 97 uses absint())

The correct value should come from $withdraw_count (populated on line 362), which contains the actual counts.

🔎 Proposed fix
-        $exporter->set_total_rows( $statuses[ $args['status'] ] );
+        // Use total count from withdraws object
+        $exporter->set_total_rows( $withdraws->total );

Note: $withdraws->total (line 372) contains the total count for the current filter, which is the appropriate value for the exporter.

🧹 Nitpick comments (2)
includes/Admin/WithdrawLogExporter.php (2)

116-122: Add validation for vendor data to prevent empty filename components.

If dokan_get_store_info() returns an empty array or the store name is missing, and sanitize_title() produces an empty string from the store name, an empty element could be added to $parts, resulting in consecutive underscores in the filename (e.g., dokan-withdraw-log-export__pending).

🔎 Proposed fix to validate store name before adding
         // Add vendor name if filtered by user_id.
         if ( ! empty( $filters['user_id'] ) ) {
             $store_info = dokan_get_store_info( absint( $filters['user_id'] ) );
             $store_name = ! empty( $store_info['store_name'] )
                 ? sanitize_title( $store_info['store_name'] )
                 : 'vendor-' . absint( $filters['user_id'] );
-            $parts[]    = $store_name;
+            // Only add if store_name is not empty after sanitization
+            if ( ! empty( $store_name ) ) {
+                $parts[] = $store_name;
+            }
         }

124-132: Validate sanitized values before adding to filename parts.

Similar to the vendor name, sanitize_title() on status and payment_method could theoretically return empty strings if the input contains only special characters. This would result in consecutive underscores in the filename.

🔎 Proposed fix to validate before adding
         // Add status if filtered.
         if ( ! empty( $filters['status'] ) ) {
-            $parts[] = sanitize_title( $filters['status'] );
+            $sanitized_status = sanitize_title( $filters['status'] );
+            if ( ! empty( $sanitized_status ) ) {
+                $parts[] = $sanitized_status;
+            }
         }

         // Add payment method if filtered.
         if ( ! empty( $filters['payment_method'] ) ) {
-            $parts[] = sanitize_title( $filters['payment_method'] );
+            $sanitized_method = sanitize_title( $filters['payment_method'] );
+            if ( ! empty( $sanitized_method ) ) {
+                $parts[] = $sanitized_method;
+            }
         }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9fc1d2d and 315ef23.

📒 Files selected for processing (4)
  • includes/Admin/WithdrawLogExporter.php
  • includes/REST/WithdrawController.php
  • includes/Withdraw/Hooks.php
  • src/admin/dashboard/pages/withdraw/index.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
includes/Admin/WithdrawLogExporter.php (1)
includes/functions.php (1)
  • dokan_get_store_info (1239-1241)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: e2e tests (3, 3)
  • GitHub Check: e2e tests (2, 3)
  • GitHub Check: e2e tests (1, 3)
  • GitHub Check: api tests (1, 1)
🔇 Additional comments (5)
includes/Withdraw/Hooks.php (1)

50-54: LGTM! Filename parameter integration looks good.

The filename is properly sanitized using sanitize_file_name() and the nonce verification exemption is appropriate since the download action itself is already nonce-protected on line 40.

includes/Admin/WithdrawLogExporter.php (1)

134-145: LGTM! Date range handling is robust.

The date validation properly checks that both strtotime() calls succeed before adding the date range to the filename. Using wp_date() ensures timezone-aware formatting, and strtolower() provides consistent formatting.

includes/REST/WithdrawController.php (2)

380-389: LGTM! Filter parameters are properly extracted and passed.

The filters are correctly extracted from the request and $args, with appropriate fallbacks to empty strings. The set_filename_from_filters() method will handle empty values gracefully by only including non-empty components.


404-410: LGTM! Filename parameter properly added to export URL.

The add_query_arg() function automatically handles URL encoding of the filename parameter, ensuring special characters are properly escaped.

src/admin/dashboard/pages/withdraw/index.tsx (1)

550-556: The concern about performance timeouts with large datasets is unfounded. WordPress REST API enforces a 100-record per_page maximum for performance reasons, which will automatically cap the export request regardless of the totalItems value. If there are more than 100 withdrawal records, the export will be incomplete rather than causing a timeout. Consider implementing pagination for the export to handle large datasets properly.

Likely an incorrect or invalid review comment.

@mrabbani mrabbani added Dev Review Done and removed Needs: Dev Review It requires a developer review and approval labels Dec 23, 2025
@dev-shahed dev-shahed added Type: Bug and removed Needs: Testing This requires further testing labels Dec 23, 2025
@dev-shahed
Copy link
Member

dev-shahed commented Dec 23, 2025

@imehedi357 Items more than 100 can't be downloaded and not showing any error...We should also improve the export button while user waiting for start downloading the file

also got some error on debug:

[24-Dec-2025 04:26:58 UTC] PHP Warning:  Undefined array key "payment" in /Users/wedevs/Documents/Sites/dokantesting/wp-content/plugins/dokan-pro/includes/CustomWithdrawMethod.php on line 293
[24-Dec-2025 04:26:58 UTC] PHP Warning:  Undefined array key "payment" in /Users/wedevs/Documents/Sites/dokantesting/wp-content/plugins/dokan-pro/includes/Withdraw/Manager.php on line 1293
[24-Dec-2025 04:26:58 UTC] PHP Warning:  Undefined variable $request in /Users/wedevs/Documents/Sites/dokantesting/wp-content/plugins/dokan-lite/includes/REST/StoreSettingController.php on line 133
[24-Dec-2025 04:26:58 UTC] PHP Warning:  Undefined variable $request in /Users/wedevs/Documents/Sites/dokantesting/wp-content/plugins/dokan-lite/includes/REST/StoreSettingController.php on line 133
[24-Dec-2025 04:26:58 UTC] PHP Warning:  Undefined variable $request in /Users/wedevs/Documents/Sites/dokantesting/wp-content/plugins/dokan-lite/includes/REST/StoreSettingController.php on line 133
[24-Dec-2025 04:26:58 UTC] PHP Warning:  Undefined variable $request in /Users/wedevs/Documents/Sites/dokantesting/wp-content/plugins/dokan-lite/includes/REST/StoreSettingController.php on line 133

- Fixed the issue of download over 100 not executing and Add polling and loading state to new React dashboard export
- Fix backend pagination issues (remove unused $statuses, fix page/total_rows)
- Fix old Vue dashboard export pagination

Ensures exports work correctly for large datasets and provides better UX with
progress feedback during export operations.
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/admin/pages/Withdraw.vue (1)

670-679: Critical bug: Incorrect payment method filter in export.

Line 676 uses self.paymentMethods.id, but paymentMethods is an array (populated at lines 442-447), not an object. This will always evaluate to undefined, breaking payment method filtering during export.

🔎 Proposed fix
 const args = {
     is_export: true,
     per_page: 100,
     page: page,
     status: self.currentStatus,
     user_id: user_id,
-    payment_method: self.paymentMethods.id,
+    payment_method: self.filter.payment_method.id,
     start_date: self.filterTransactionDate.start_date,
     end_date: self.filterTransactionDate.end_date
 };
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 315ef23 and eb52646.

📒 Files selected for processing (3)
  • includes/REST/WithdrawController.php
  • src/admin/dashboard/pages/withdraw/index.tsx
  • src/admin/pages/Withdraw.vue
🚧 Files skipped from review as they are similar to previous changes (1)
  • includes/REST/WithdrawController.php
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: e2e tests (2, 3)
  • GitHub Check: api tests (1, 1)
  • GitHub Check: e2e tests (3, 3)
  • GitHub Check: e2e tests (1, 3)
🔇 Additional comments (4)
src/admin/pages/Withdraw.vue (1)

672-673: LGTM! Pagination parameters correctly updated.

The changes properly support the multi-step export flow:

  • Fixed per_page: 100 provides a reasonable batch size for CSV export
  • Using the function's page parameter (instead of currentPage) correctly supports recursive pagination via response.step (line 700)
src/admin/dashboard/pages/withdraw/index.tsx (3)

24-24: LGTM! Import addition for loading spinner.

The Loader2 icon is properly imported and used in the export button's loading state (line 603).


91-92: LGTM! Proper state management for export UX.

The state variables correctly track export status and progress, enabling the button to disable during export and display real-time progress feedback to users.


601-614: LGTM! Excellent user feedback during export.

The conditional rendering provides clear visual feedback:

  • Animated spinner indicates active export
  • Real-time progress percentage when available
  • Fallback to generic "Exporting..." message
  • Clean normal state with icon and label

Comment on lines 547 to 600
<button
type="button"
className="inline-flex items-center gap-2 rounded-md border border-gray-300 bg-white px-3 py-1.5 text-sm text-[#575757] hover:bg-[#7047EB] hover:text-white"
className="inline-flex items-center gap-2 rounded-md border border-gray-300 bg-white px-3 py-1.5 text-sm text-[#575757] hover:bg-[#7047EB] hover:text-white disabled:opacity-50 disabled:cursor-not-allowed"
disabled={ isExporting }
onClick={ async () => {
setIsExporting( true );
setExportProgress( 0 );
try {
// Minimal placeholder; backend export flow may vary.
// Attempt to hit export endpoint via same query params.
const path = addQueryArgs( 'dokan/v2/withdraw', {
...view,
is_export: true,
} );
const res = await apiFetch( { path } );
if ( res && res.url ) {
window.location.assign( res.url as string );
let page = 1;
let isComplete = false;
let downloadUrl = null;

// Poll until export is complete
while ( ! isComplete ) {
const path = addQueryArgs( 'dokan/v2/withdraw', {
per_page: 100,
page: page,
search: view?.search ?? '',
status: view?.status === 'all' ? '' : view?.status,
...filterArgs,
is_export: true,
} );

const res = await apiFetch( { path } );

// Update progress if available
if ( res?.percentage !== undefined ) {
setExportProgress( res.percentage );
}

if ( res?.step === 'done' ) {
isComplete = true;
downloadUrl = res.url;
} else if ( res?.step ) {
// Continue to next page
page = res.step;
} else {
throw new Error( 'Invalid response from export' );
}
}

if ( downloadUrl ) {
window.location.assign( downloadUrl as string );
}
} catch ( e ) {
// eslint-disable-next-line no-console
console.error( 'Export failed or not supported yet', e );
alert( __( 'Export failed. Please try again.', 'dokan-lite' ) );
} finally {
setIsExporting( false );
setExportProgress( 0 );
}
} }
>
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 | 🟠 Major

Potential memory leak: Add cleanup for unmounted component.

The export loop (lines 560-586) continues making API calls until completion, but there's no mechanism to abort if the component unmounts during export. This can cause memory leaks and unnecessary API calls.

Additionally, consider adding a maximum iteration limit as a safety mechanism to prevent infinite loops if the backend returns unexpected responses.

🔎 Suggested improvements

Consider using an AbortController to cancel pending requests on unmount:

 onClick={ async () => {
     setIsExporting( true );
     setExportProgress( 0 );
+    const abortController = new AbortController();
+    
+    // Cleanup function
+    const cleanup = () => {
+        abortController.abort();
+        setIsExporting( false );
+        setExportProgress( 0 );
+    };
+
     try {
         let page = 1;
         let isComplete = false;
         let downloadUrl = null;
+        const MAX_ITERATIONS = 1000; // Safety limit
+        let iterations = 0;

         // Poll until export is complete
         while ( ! isComplete ) {
+            if ( iterations++ >= MAX_ITERATIONS ) {
+                throw new Error( 'Export timeout: too many pages' );
+            }
+
             const path = addQueryArgs( 'dokan/v2/withdraw', {
                 per_page: 100,
                 page: page,
                 search: view?.search ?? '',
                 status: view?.status === 'all' ? '' : view?.status,
                 ...filterArgs,
                 is_export: true,
             } );

-            const res = await apiFetch( { path } );
+            const res = await apiFetch( { 
+                path,
+                signal: abortController.signal 
+            } );

Then use a useEffect cleanup:

useEffect(() => {
    // Return cleanup function that aborts export on unmount
    return () => {
        if (isExporting) {
            // abort logic here
        }
    };
}, [isExporting]);

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

🤖 Prompt for AI Agents
In src/admin/dashboard/pages/withdraw/index.tsx around lines 547 to 600, the
export polling loop can continue after the component unmounts and may never
terminate; add cancellation and a safety iteration limit: create an
AbortController for the export flow and pass its signal to apiFetch calls, track
a mounted flag or use the controller to avoid calling setState after unmount,
enforce a maxIterations counter (e.g., 1000) to break the loop if responses are
unexpected, and add a useEffect cleanup that aborts the controller when the
component unmounts or when export is cancelled so pending requests stop and
state updates are prevented.

@imehedi357 imehedi357 added Needs: Testing This requires further testing Needs: Dev Review It requires a developer review and approval and removed Type: Bug Dev Review Done labels Dec 26, 2025
@imehedi357
Copy link
Author

@mrabbani and @dev-shahed vai, Fixed the download limit issue and also added feedback to the user that the download is happening.

Copy link
Member

@mrabbani mrabbani left a comment

Choose a reason for hiding this comment

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

Code Review Summary

Overall, this is a solid implementation that adds dynamic filename generation for withdraw log exports and fixes export issues for large datasets. The code structure is clean and follows good practices. However, there are a few issues that should be addressed before merging.


✅ Positive Changes

  1. Dynamic filename generation - The set_filename_from_filters() method effectively generates descriptive filenames based on applied filters
  2. Bug fix for large exports - Fixed the pagination issue by using $withdraws->total instead of hardcoded status array
  3. User experience improvements - Added loading states, progress indicators, and proper export polling in the React component
  4. Code structure - Good separation of concerns and clear method naming

🔴 Critical Issues (Must Fix)

1. Date Format Ambiguity

Location: includes/Admin/WithdrawLogExporter.php:141-143

Using M-d format (e.g., "jan-15") doesn't include the year, which can be ambiguous for date ranges spanning years or when files are saved long-term.

Recommendation: Consider including the year if dates span different years:

$start_format = ( wp_date( 'Y', $start_timestamp ) !== wp_date( 'Y', $end_timestamp ) ) ? 'Y-M-d' : 'M-d';
$start = strtolower( wp_date( $start_format, $start_timestamp ) );
$end   = strtolower( wp_date( 'Y-M-d', $end_timestamp ) );
$parts[] = $start . '_to_' . $end;

2. Potential Long Filename Issue

Location: includes/Admin/WithdrawLogExporter.php:119

Very long store names could create extremely long filenames. Most filesystems have limits (typically 255 characters).

Recommendation: Consider truncating very long store names:

$store_name = ! empty( $store_info['store_name'] )
    ? sanitize_title( $store_info['store_name'] )
    : 'vendor-' . absint( $filters['user_id'] );
    
// Truncate if too long (e.g., max 50 chars for store name part)
if ( strlen( $store_name ) > 50 ) {
    $store_name = substr( $store_name, 0, 47 ) . '...';
}

✅ Code Quality Observations

  1. Security ✅ - Proper use of sanitize_text_field(), sanitize_file_name(), absint(), and nonce verification
  2. Error handling ✅ - The React component has good error handling with try/catch and user feedback
  3. Consistency ✅ - The Vue component uses a similar approach (fixed per_page: 100)
  4. Documentation ✅ - Good inline documentation for the new method

🧪 Testing Recommendations

  1. Test filename generation with various filter combinations:

    • All filters applied
    • No filters applied
    • Only vendor filter
    • Only date range
    • Special characters in store names
  2. Test edge cases:

    • Very long store names
    • Date ranges spanning multiple years
    • Empty/null filter values
  3. Verify the exported file has the correct name and opens correctly

  4. Test the export flow with large datasets to ensure pagination works correctly

Once these issues are addressed, this PR should be ready to merge. Great work on improving the user experience with dynamic filenames and fixing the export pagination issue!

@mrabbani mrabbani added Needs: Author Reply and removed Needs: Dev Review It requires a developer review and approval labels Dec 29, 2025
- Shorten base name from 'dokan-withdraw-log-export' to 'dokan-withdraw-log'
- Truncate vendor names >50 chars using '-trunc' (replaces '...' to avoid sanitization issues)
- Always include year in date format (Y-M-d) for clarity

Max filename length: ~160 chars
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

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eb52646 and deef546.

📒 Files selected for processing (1)
  • includes/Admin/WithdrawLogExporter.php
🧰 Additional context used
🧬 Code graph analysis (1)
includes/Admin/WithdrawLogExporter.php (1)
includes/functions.php (1)
  • dokan_get_store_info (1239-1241)
🪛 GitHub Check: Run PHPCS inspection
includes/Admin/WithdrawLogExporter.php

[failure] 126-126:
Whitespace found at end of line


[failure] 121-121:
Whitespace found at end of line

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: api tests (1, 1)
  • GitHub Check: e2e tests (1, 3)
  • GitHub Check: e2e tests (2, 3)
  • GitHub Check: e2e tests (3, 3)
🔇 Additional comments (2)
includes/Admin/WithdrawLogExporter.php (2)

38-38: LGTM: Default filename shortened.

The simplified base filename aligns well with the dynamic naming strategy introduced below.


112-153: Code is well-implemented and safely handles filename length.

The implementation correctly handles all components:

  • Sanitizes all user inputs with sanitize_title() and absint()
  • Validates timestamps before formatting dates
  • Truncates vendor names to 50 characters
  • Conditionally includes only applied filters

The worst-case filename length (with all filters applied: vendor + status + payment method + date range) totals approximately 125 characters, well within the ~160-character limit mentioned in the commit message and below the standard 255-character filesystem limit. The defined statuses (pending, completed, cancelled) and payment methods (paypal, bank, skrill) are limited in length, and the vendor name truncation at 50 chars provides the primary safeguard.

No additional changes needed.

$store_name = ! empty( $store_info['store_name'] )
? sanitize_title( $store_info['store_name'] )
: 'vendor-' . absint( $filters['user_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 | 🟡 Minor

Fix trailing whitespace.

Static analysis detected trailing whitespace on lines 121 and 126. Please remove to comply with coding standards.

🔎 Proposed fix
             $store_name = ! empty( $store_info['store_name'] )
                 ? sanitize_title( $store_info['store_name'] )
                 : 'vendor-' . absint( $filters['user_id'] );
-            
+
             // Truncate if too long (max 50 chars for store name part).
             if ( strlen( $store_name ) > 50 ) {
                 $store_name = substr( $store_name, 0, 44 ) . '-trunc';
             }
-            
+
             $parts[] = $store_name;

Also applies to: 126-126

🧰 Tools
🪛 GitHub Check: Run PHPCS inspection

[failure] 121-121:
Whitespace found at end of line

🤖 Prompt for AI Agents
In includes/Admin/WithdrawLogExporter.php around lines 121 and 126, there are
trailing whitespace characters at the ends of those lines; remove the trailing
spaces so the lines end immediately after the intended code/text (no extra
spaces or tabs) and save the file to comply with coding standards and static
analysis.

@imehedi357
Copy link
Author

@mrabbani vai, Thanks for the feedback.
Made the following changes accordingly:

  • Date format: switched to Y-M-d to always include the year and avoid ambiguity.
  • Long filenames: truncate store names over 50 characters using -trunc (replaces '...' to avoid sanitize_file_name() collapsing dots).
  • Base filename: shortened from dokan-withdraw-log-export to dokan-withdraw-log.

The max filename with all filters should be now within ~160 chars.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Needs: Dev Review It requires a developer review and approval Needs: Testing This requires further testing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants