# Grid - Bulk Action

When it comes to updating the data from a grid, we might hit the limits like a CPU time, memory, DML.... To bypass those limitations, the updates should be executed in asynchronous mode using queueables or batches jobs.&#x20;

To support this type of transactions, we provide **GM - Bulk Action.** With **Bulk Action** you get a framework to track the running tasks from a developer perspective. We also provide a monitor to visualise background tasks and let the users see the outcomes including the occurred errors.

## Bulk Action Monitor Setup

To track the running bulk actions, the **GM - Bulk Action Monitor** should be configured on the Lightning App. Each app requiring this type of tracking should have this utility bar configured.

<figure><img src="/files/IaePYASegk1E69yQ3224" alt=""><figcaption><p>Action Monitor Setup #1</p></figcaption></figure>

When the utility item is added, it should be configured as below. The width and the height should adjusted based on the label of actions, screen resolution...

{% hint style="info" %}
**Start automatically** should be always checked for the monitor. If not we may loose initiated jobs before opening the monitor at the first time.
{% endhint %}

<figure><img src="/files/jyPH6lX9VeOmj5UiCeEG" alt=""><figcaption><p>Action Monitor Setup #2</p></figcaption></figure>

The cleanup delay defines the number of second before removing a successfully completed job. If you want your users to get a chance, put a long delay.&#x20;

{% hint style="success" %}
When a job is completed, the user get success toast message if the job is completed without any error. Otherwise he will get an error notification.
{% endhint %}

## Bulk Action Implementation

### Aura Action Component

Our monitor is configured, let's go ahead and start our first bulk action. Let's say that we want to move to close date by one day for the whole scope defined by the user.&#x20;

This action will be triggered from an Opportunity grid. Let's create an Aura component **TestBulkActionActionComponent.** The component has all the required properties including:

* Grid Name : Grid name coming from the configuration,
* Grid Label : Grid label coming from the configuration,
* Action Name : Action name coming from the configuration,
* Action Label : Action label coming from the configuration,
* Query : The SOQL query defining the scope

The component should also fire **onsucess** or **oncancel** based on the user flow. Finally we will use **bulkActionPublisherLWC** utility component to publish the job to the monitor. It's a useful component to streamline the implementation process.

{% tabs %}
{% tab title="TestBulkActionActionComponent.cmp" %}

```html
<aura:component implements="force:hasRecordId" controller="TestBulkActionController" access="global">
    <!-- Action properties coming from GridMate-->
    <aura:attribute name="relatedObjectName" type="String" access="global" />    
    <aura:attribute name="gridLabel" type="String" access="global" />
    <aura:attribute name="gridName" type="String" access="global" />
    <aura:attribute name="gridCode" type="String" access="global" />
    <aura:attribute name="actionName" type="String" access="global" />
    <aura:attribute name="actionLabel" type="String" access="global" />

    <!-- Soql Query coming from GridMate-->
    <aura:attribute name="query" type="String" access="global" />

    <!-- Internal flag to control the spinner-->
    <aura:attribute name="isWorking" type="Boolean" access="global" />

    <aura:registerEvent name="onsuccess" type="gmpkg:DataGridActionEvent" />
    <aura:registerEvent name="oncancel" type="gmpkg:DataGridActionEvent" />

    <!-- overlayLib API -->
    <lightning:overlayLibrary aura:id="overlayLib" />
        
    <!-- Bulk Action Publisher -->
    <gmpkg:bulkActionPublisherLWC aura:id="bulkActionPublisher" />
    
    <div class="slds-theme_default">
        <div class="content-wrapper">
            <span> {! v.query }</span>
        </div>

        <div class="slds-modal__footer actions-wrapper">
            <button class="slds-button slds-button--neutral" onclick="{!c.handleCancel}">Cancel</button>
            <button class="slds-button slds-button--brand" onclick="{!c.handleSubmit}">Submit</button>
        </div>

        <aura:if isTrue="{! v.isWorking }">
            <lightning:spinner variant="brand" alternativeText="Processing" style="background: transparent" />
        </aura:if>
    </div>
</aura:componen
```

{% endtab %}

{% tab title="TestBulkActionComponentController.js" %}

```javascript
({
    handleSubmit: function (component, event, helper) {
        component.set('v.isWorking', true);

        //Submit the action to the Salesforce
        let action = component.get('c.submitBulkAction');
        action.setParams({
            gridLabel: component.get('v.gridLabel'),
            gridName: component.get('v.gridName'),
            actionLabel: component.get('v.actionLabel'),
            actionName: component.get('v.actionName'),
            query: component.get('v.query'),
            url: window.location.href
        });

        action.setCallback(this, function (res) {
            component.set('v.isWorking', false);
            if (res.getState() === 'SUCCESS') {
                //Publish the JobId for the monitor
                let bulkActionPublisher = component.find('bulkActionPublisher');
                bulkActionPublisher.publish(res.getReturnValue());

                //Show a toast for the end user
                let toastEvent = $A.get('e.force:showToast');

                toastEvent
                    .setParams({
                        title: 'Success',
                        type: 'success',
                        message: 'Action Executed Successfully'
                    })
                    .fire();

                component
                    .getEvent('onsuccess')
                    .setParams({
                        action: 'ActionExecuted'
                    })
                    .fire();

                //Close the modal
                component.find('overlayLib').notifyClose();
            } else if (res.getState() === 'ERROR') {
                helper.handleServerErr(res);
            }
        });

        $A.enqueueAction(action);
    },
    handleCancel: function (component, event, helper) {
        component
            .getEvent('oncancel')
            .setParams({
                action: 'ActionCancelled'
            })
            .fire();

        component.find('overlayLib').notifyClose();
    }
});
```

{% endtab %}
{% endtabs %}

### Apex Controller Classes

Our Aura component is ready, let's go ahead and implement our Apex controller. In our example, the job of the controller is straightforward; creates a Bulk Action Job and starts an Apex batch. To get the number of records to process, we just replace the **Id** with **Count().**&#x20;

The batch class track the progress of the job and keep track of the occurred errors. At the end of the batch, we close the job.&#x20;

When a job is closed, the monitor is notified and the toast message is displayed to the user.

{% tabs %}
{% tab title="TestBulkActionController" %}

```apex
public with sharing class TestBulkActionController {
    @AuraEnabled
    public static Id submitBulkAction(
        String gridLabel,
        String gridName,
        String actionLabel,
        String actionName,
        String query,
        String url
    ) {
        String countQuery = query.replaceAll('Select Id ', 'Select count()');

        //Submit the job to the action manager
        Id jobId = gmpkg.BulkActionManager.createJob(
            new gmpkg.BulkActionManager.BulkActionRequest(
                gridLabel,
                gridName,
                actionLabel,
                actionName,
                query,
                url,
                database.countQuery(countQuery)
            )
        );

        //Start the batch to process all the opportunities
        Database.executeBatch(new TestBulkActionBatch(jobId, query));
        return jobId;
    }
}
```

{% endtab %}

{% tab title="TestBulkActionBatch" %}

```apex
public with sharing class TestBulkActionBatch implements Database.Batchable<sObject>, Database.Stateful {
    public Id jobId; // Monitored jobId
    public String scopeQuery; // Query coming from the action
    public Integer totalProgress = 0; // Current progress of the job

    public TestBulkActionBatch(Id jobId, String scopeQuery) {
        this.jobId = jobId;
        this.scopeQuery = scopeQuery;
    }

    public Database.QueryLocator start(Database.BatchableContext bc) {
        //Just replace the Id with the expected columns for the our batch
        return Database.getQueryLocator(this.scopeQuery.replace('Id', 'Id, Name, StageName, CloseDate'));
    }

    public void execute(Database.BatchableContext bc, List<Opportunity> opportunities) {
        if (opportunities.size() > 0) {
            for (Opportunity opp : opportunities) {
                opp.CloseDate += 1;
            }

            //We prepare the list of BulkActionError to keep track of save errors 
            List<gmpkg.BulkActionManager.BulkActionError> errorList = new List<gmpkg.BulkActionManager.BulkActionError>();
            List<Database.SaveResult> resultList = database.update(opportunities, false);
            for (Database.SaveResult res : resultList) {
                if (!res.isSuccess()) {
                    errorList.add(new gmpkg.BulkActionManager.BulkActionError(res.getId(), JSON.serialize(res.getErrors())));
                }
            }

            //If we have errors, we report them back to the monitor
            if (errorList.size() > 0) {
                gmpkg.BulkActionManager.reportErrors(this.jobId, errorList);
            }

            //We increase the progress and we report it back to the monitor
            this.totalProgress += opportunities.size();
            gmpkg.BulkActionManager.reportProgress(this.jobId, this.totalProgress);
        }
    }

    public void finish(Database.BatchableContext bc) {
        //At the end, we close the job
        gmpkg.BulkActionManager.closeJob(this.jobId);
    }
}
```

{% endtab %}
{% endtabs %}

## Bulk Action Configuration

Our Aura component and Apex Classes are ready for use. Let's go ahead and configure the grid action as below:

```json
[
    {
        "name": "Move",
        "label": "Move",
        "bulkAction": true,
        "confirmationRequired": true,
        "component": "c:TestBulkActionComponent"
    }
]
```

{% hint style="info" %}
To trigger a **Bulk Action,** bulkAction flag should be set to true. If not the action will be considered as synchronous action and the query will not be passed to your component.
{% endhint %}

{% hint style="info" %}
If confirmationRequired is set to true, the user will be asked to select the scope of the action. It could be either the selected records or the whole filter scope. See the screenshot below.
{% endhint %}

<figure><img src="/files/7hQnWs3d2yD1RIgxsNm6" alt=""><figcaption><p>Bulk Action Scope</p></figcaption></figure>

<figure><img src="/files/Vy9OqAxMEfYhGQjnQdEk" alt=""><figcaption><p>Bulk Action Progress</p></figcaption></figure>

## Source Code

The full source code of this example is available here :point\_down:

<https://github.com/GridMate/gridmate-examples/tree/main>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.gridmate.io/advanced-guides/grid-bulk-action.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
