magepsycho/magento2-async-bulk-operations-ui

Magento 2 Async Bulk Operations UI

Maintainers

Package info

github.com/MagePsycho/magento2-async-bulk-operations-ui

Homepage

Type:magento2-module

pkg:composer/magepsycho/magento2-async-bulk-operations-ui

Transparency log

Statistics

Installs: 3

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

1.0.0 2026-08-07 11:54 UTC

This package is auto-updated.

Last update: 2026-08-07 12:00:54 UTC


README

Magento 2 Async Bulk Operations UI

Magento 2 Async Bulk Operations UI

Packagist Version Packagist Downloads Supported Magento Versions License

Overview

Magento 2 Async Bulk Operations UI extension gives you a complete admin view of everything Magento_AsynchronousOperations is doing in your store.

Magento Open Source does ship a Bulk Actions grid, but it only shows bulks created by the admin who is logged in, reports a bulk as failed the moment one operation fails, and offers no counts, no progress and no aggregate view. This extension replaces that narrow window with two full grids and a status dashboard — so "did the import finish", "which topic keeps failing" and "what is still queued" are answerable at a glance.

Key Features

  • Lists every bulk — whichever admin, integration or cron created it
  • Derives bulk status from operation counts instead of MAX(status), so one failure no longer hides work still in progress
  • Shows complete / failed / open counts and a progress percentage per bulk
  • Adds a dedicated operations log across all bulks, filterable by topic, status and error code
  • Dashboard with KPI tiles, per-day stacked bars and status doughnuts
  • Inspects the request payload and stored response of any single operation, formatted, in a modal
  • Retries failed operations safely, including a workaround for a core bug that strands bulks "In Progress" forever
  • Separate ACL resources for viewing, retrying and deleting
  • CSV / Excel export on both grids

Feature Highlights

Every Bulk, Not Just Yours

Core grid This extension
Only bulks owned by the logged-in admin (user_id = <you>), plus every integration bulk Every bulk, whichever admin, integration or cron created it
Status = MAX(status) over the operations, so one rejected operation reports the whole bulk as failed while others are still running Status derived from counts: not started / in progress / finished successfully / finished with failure
No counts, no progress Complete / failed / open counts and a progress percentage per bulk
Operations only visible per bulk, in a modal Dedicated operations log across every bulk, filterable by topic, status, error code
No aggregate view Dashboard with KPI tiles, per-day stacked bars and two status doughnuts

Pages

Menu Route Contents
System > Action Logs > Async Bulk Operations magepsycho_asyncoperationsui/bulk/index Dashboard + bulk grid
"Operations Log" button on the bulk grid magepsycho_asyncoperationsui/operation/index Dashboard + operation grid
"View" row action magepsycho_asyncoperationsui/bulk/view/uuid/<uuid> Bulk summary + its operations

Only the bulk grid has a menu entry. The operations log is a diagnostic view of the same tables — topic, error code and result message exist only per operation, so it answers questions the bulk grid structurally cannot ("which topic keeps failing", "everything that failed with this message, across all bulks") — but it is not a second daily destination, so it hangs off the grid.

Row actions: View, Retry Failed, Delete. Both state-changing actions post and are confirm-gated. On the operation grids, View Payload opens the request payload and response in a modal.

Dashboard

Rendered above the grid in every layout, drawn with the Chart.js 4.4 bundled in lib/web/chartjs.

  • KPI tiles: bulks, bulks in progress, bulks with failures, operations, open, failed, success rate
  • Stacked bars: operations per day, split complete / failed / open, over 7 / 30 / 90 / 365 days
  • Doughnuts: bulks by status, operations by status

The first payload is embedded in the page; changing the period re-fetches magepsycho_asyncoperationsui/chart/data as JSON.

Async Bulk Operations dashboard and bulk grid

Days are taken from the parent bulk's start_time, not magento_operation.started_at, which stays null until a consumer picks the operation up — open operations would otherwise have no date. Timestamps come straight from the database and are not converted to the admin locale timezone.

Bulk Details

Every bulk opens onto its own page: the summary counts, its operations with topic, status, error code and result message, and the two state-changing actions.

Bulk operation detail page with its operations

Payload Inspector

The View Payload action on any operation opens the request payload — pretty-printed and syntax-highlighted — next to the stored response. When an operation failed before returning a body, that is said in words rather than shown as an empty box.

Operation payload modal showing request payload and response

This is the detail core stores and never surfaces: without it, "Invalid product data" is the whole story — with it, you can see which SKU carried the bad attribute set. The request body is unwrapped from the operation envelope, where Magento keeps it JSON-encoded twice inside meta_information.

An empty pane is normal and says so: the consumer clears serialized_data once an operation completes, and result_serialized_data only ever holds the body of a successful synchronous call.

Safe Retry

Retry collects the distinct error codes of the bulk's failed operations and hands them to BulkManagement::retryBulk() — the same thing as ticking every row in the core retry modal. Failed operations stored without an error code cannot be targeted by retryBulk(); they are counted and reported in a notice rather than silently ignored.

Core caveat, worked around here. retryBulk() reopens the operation rows and commits, then publishes. For async.* API topics the publish step throws —

Data item corresponding to "sku" must be specified in the message with topic
"async.magento.catalog.api.productrepositoryinterface.deletebyid.delete"

— because the stored payload is not in the shape the message encoder expects on re-publish. Core leaves the operations Open with nothing in the queue, so the bulk reads as In Progress forever. This extension snapshots the affected rows first and restores them if publishing throws, so a failed retry is a no-op with a clear error message. Retry of non-async.* topics is unaffected.

Access Control

ACL resources under System:

  • MagePsycho_AsyncOperationsUi::bulk — view both grids and the detail page
  • MagePsycho_AsyncOperationsUi::retry — retry failed operations
  • MagePsycho_AsyncOperationsUi::delete — delete a bulk

The core grid restricts rows by owner; this one does not, so the ACL resources are the only gate — grant them accordingly.

🛠️ Installation

1 Using Composer (Preferred)

composer require magepsycho/magento2-async-bulk-operations-ui

2 Using Modman

modman init
modman clone git@github.com:MagePsycho/magento2-async-bulk-operations-ui.git

3 Using Zip File

  • Download the Extension Zip File
  • Extract & upload the files to /path/to/magento2/app/code/MagePsycho/AsyncOperationsUi/

After installation by either means, activate the extension with following steps

  1. Enable the module
php bin/magento module:enable MagePsycho_AsyncOperationsUi --clear-static-content
php bin/magento setup:upgrade
  1. Flush the store cache
php bin/magento cache:flush
  1. Deploy static content - in Production mode only
rm -rf pub/static/* var/view_preprocessed/*
php bin/magento setup:static-content:deploy
  1. Go to Admin > SYSTEM > Action Logs > Async Bulk Operations

The extension creates no tables of its own — it reads magento_bulk, magento_operation and admin_user.

Configuration

Stores > Configuration > MagePsycho > Async Bulk Operations

Setting Default Purpose
Show Charts Above Grid Yes Hides the KPI tiles and charts when only the grids are wanted
Default Chart Period 7 days Period preselected in the "Operations per day" chart

Developer Notes

Grid collections are registered globally, on purpose

etc/di.xmlnot etc/adminhtml/di.xml — registers the grid collections on Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory::$collections. Magento_AsynchronousOperations registers its own entries on that same argument in its global etc/di.xml, and an area-scoped array argument replaces the global array rather than merging into it. Declaring these in etc/adminhtml/di.xml unregisters the core handles and breaks the core Bulk Actions page with:

Not registered handle bulk_listing_data_source

Keep any future data-source registration in the global file.

Performance

The bulk grid derives its counts with correlated sub-selects against magento_operation, which is indexed on bulk_uuid. Fine for the page sizes of an admin grid; on installs with millions of operation rows, prefer filtering the grid before sorting on a derived column.

Demo data (development only)

# backdated bulks over the last two weeks, uuid prefix mp-seed-
php app/code/MagePsycho/AsyncOperationsUi/dev/seed-demo-data.php synthetic

# three genuine async bulks; existing catalog data is never touched
php app/code/MagePsycho/AsyncOperationsUi/dev/seed-demo-data.php real
bin/magento queue:consumers:start async.operations.all --max-messages=10

# remove the synthetic rows again (real bulks are left alone - delete those from the grid)
php app/code/MagePsycho/AsyncOperationsUi/dev/seed-demo-data.php clean

Static analysis

vendor/bin/phpstan analyse -c app/code/MagePsycho/AsyncOperationsUi/phpstan.neon --memory-limit=1G
vendor/bin/phpcs --standard=Magento2 --extensions=php,phtml \
    --ignore=*/dev/* app/code/MagePsycho/AsyncOperationsUi/

dev/ is excluded from both: it is a standalone CLI script that bootstraps Magento itself, which the Magento2 sniffs and phpstan rightly reject as module code.

Changelog

Version 1.0.0 (2026-08-07)

  • Initial Release.

Authors

  • Raj KB Twitter Follow

Contributors

Contributors

To Contribute

Any contribution to the development of Magento 2 Async Bulk Operations UI is highly welcome.
The best possibility to provide any code is to open a pull request on GitHub.

Need Support?

If you encounter any problems or bugs, please create an issue on GitHub.

Please visit our store for more FREE / paid extensions OR contact us for customization / development services.