function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Rachel Linder 20Rachel Linder 20 

Need Help Creating a Lightning Action to run an Apex Class

I am just a couple days on with this company. They have the following Apex Class that runs nightly through a batch. We would like the ability to create a Lightning Action/Quick Action to place on the page layout so that this can also be invoked at any time during the day if needed.
 
global class NightlyJobs Implements Schedulable {
    
    global void execute (SchedulableContext sc) {
        
        SendToOrder_Subscriptions runSendToOrder_Subscriptions = new SendToOrder_Subscriptions();
        database.executebatch(runSendToOrder_Subscriptions,100);
        
        ContractRenewalManager runContractRenewalManager = new ContractRenewalManager();
        database.executebatch(runContractRenewalManager,1);
        
    }
    
    
}
This seems to reference the following sets of Apex as well:

ContractRenewalManager
global class ContractRenewalManager implements Database.Batchable<sObject>, Database.Stateful {

    global Integer totalContractsUpdated = 0;

    global Database.QueryLocator start(Database.BatchableContext BC) {

        datetime eligibilityDate = system.now().addMonths(4);
        string eligibilityDateFormatted = eligibilityDate.format('yyyy-MM-dd');

        // Build Contracts Dataset
        string queryContracts = 'SELECT id, SBQQ__RenewalForecast__c FROM Contract WHERE EndDate <= ' + eligibilityDateFormatted + ' AND SBQQ__RenewalForecast__c = false AND Disable_Renewal_Auto_Creation__c = false AND Status = \'Activated\'';
        return Database.getQueryLocator(queryContracts);
    }

    global void execute(Database.BatchableContext BC, List<Contract> scope) {

        // Vars
        List<Contract> listContractsToUpdate = new List<Contract>();
        List<Exception__c> list_newExceptions = new List<Exception__c>();

        // Loop through all Contracts
        for (Contract eachContract: scope) {

            eachContract.SBQQ__RenewalForecast__c = true;
            listContractsToUpdate.add(eachContract);

        }
        // UPDATE - Contract Records
        List<Database.SaveResult> listContractsToUpdate_SaveResults = database.update(listContractsToUpdate, false);

        // RESULTS
        for (Database.SaveResult eachResult: listContractsToUpdate_SaveResults) {
            // ON SUCCESS
            if (eachResult.isSuccess()) {
                System.debug('== DEBUG: Updated Contract Record ==>> ' + eachResult.getId());
                totalContractsUpdated++;
                // ON FAIL
            } else {
                for (Database.Error eachError: eachResult.getErrors()) {
                    Exception__c newException = new Exception__c(Purpose__c = 'Set Renewal Forecast to TRUE', Object__c = 'Contract (Update)', Process__c = 'ContractRenewalManager (Apex Class)', Details__c = eachError.getStatusCode() + ': ' + eachError.getMessage() + '\n' + '\n' + 'Field affected by the error: ' + eachError.getFields() + '\n' + '\n' + 'Note: This error is related to Contract ID: ' + eachResult.getId());
                    list_newExceptions.add(newException);
                }
            }
        }

        insert list_newExceptions;

    }

    global void finish(Database.BatchableContext BC) {

        system.debug('DEBUG ---->>>> Total Contracts Updated: ' + totalContractsUpdated);

    }
}

SendToOrderSubscriptions
global class SendToOrder_Subscriptions implements Database.Batchable<sObject>, Database.Stateful {
    
    global Database.QueryLocator start(Database.BatchableContext BC) {
        string queryAccounts = 'SELECT id FROM Account WHERE id IN (SELECT SBQQ__Account__c FROM SBQQ__Subscription__c WHERE SBQQ__Quantity__c > 0 AND Send_To_Order__c = true AND Added_To_Order__c = false AND Status__c != \'Cancelled\')';
        return Database.getQueryLocator(queryAccounts);
    }
    
    global void execute(Database.BatchableContext BC, List<Account> scope) {
        
        // Prepare data ----
        List<Account> thisScopeAccounts = new List<Account>();
        thisScopeAccounts.addAll(scope);
        id standardPricebookId;         
        
        if (Test.isRunningTest()) {
            standardPricebookId = Test.getStandardPricebookId();
        } else {
            standardPricebookId = [SELECT id FROM Pricebook2 WHERE isStandard = true].id;
        }
        
        List<PricebookEntry> listofPBEs = [SELECT id, Product2id, Pricebook2id FROM PricebookEntry WHERE Pricebook2id = :standardPricebookId];
        Map<id,id> pricebookEntryids = new Map<id,id>();
        List<Exception__c> newExceptions = new List<Exception__c>();
        
        for (PricebookEntry eachPBE : listofPBEs) {
            pricebookEntryids.put(eachPBE.Product2id,eachPBE.id);           
        }
        
        List<SBQQ__Subscription__c> thisScopeSubscriptions = [SELECT id, SBQQ__Account__c, Bill_To_Account__c, Business_Entity__c, Accounting_Quantity__c, 
                                                              SBQQ__Product__c, Display_Name__c, Product_Description__c, SBQQ__SubscriptionStartDate__c, 
                                                              SBQQ__SubscriptionEndDate__c, SBQQ__ListPrice__c, SBQQ__QuoteLine__c, SBQQ__NetPrice__c, 
                                                              Transaction_Number__c, Purchase_Order__c 
                                                              FROM SBQQ__Subscription__c 
                                                              WHERE SBQQ__Quantity__c > 0 AND Send_To_Order__c = true AND Added_To_Order__c = false AND Status__c != 'Cancelled' 
                                                              AND SBQQ__Account__c IN :thisScopeAccounts];
        
        
        List<AggregateResult> aggregatedSubscriptions = [SELECT SBQQ__Account__c, Bill_To_Account__c, Business_Entity__c, Bill_To_Account__r.Billing_Contact__c, MIN(SBQQ__SubscriptionStartDate__c) StartDate, MAX(SBQQ__SubscriptionEndDate__c) EndDate FROM SBQQ__Subscription__c WHERE SBQQ__Account__c IN :thisScopeAccounts AND Send_To_Order__c = true AND Added_To_Order__c = false GROUP BY SBQQ__Account__c, Bill_To_Account__c, Business_Entity__c, Bill_To_Account__r.Billing_Contact__c];
        List<Order> newOrders = new List<Order>();
        
        // Create Orders
        for (AggregateResult eachNewOrder : aggregatedSubscriptions) {
            Order newOrder = new Order(
                Accountid = (id)eachNewOrder.get('SBQQ__Account__c'),
                Bill_To_Account__c = (id)eachNewOrder.get('Bill_To_Account__c'),
                Business_Entity__c = (id)eachNewOrder.get('Business_Entity__c'),
                BillToContactId = (id)eachNewOrder.get('Billing_Contact__c'),
                EffectiveDate = (date)eachNewOrder.get('StartDate'),
                EndDate = (date)eachNewOrder.get('EndDate'),
                Pricebook2id = standardPricebookId,
                Type = 'Subscription Order',
                Status = 'Draft'
            );
            newOrders.add(newOrder);
        }
        
        // INSERT Orders from source record aggregate data
        List<Database.SaveResult> newOrders_SaveResults = database.insert(newOrders, false);
        integer orderInsertIndex = 0;
        
        // RESULTS
        for (Database.SaveResult eachResult : newOrders_SaveResults) {
            // ON SUCCESS
            if (eachResult.isSuccess()) {
                System.debug('== DEBUG: Inserted Order Record ==>> ' + eachResult.getId());
                // ON FAIL
            } else {
                for (Database.Error eachError : eachResult.getErrors()) {
                    Exception__c newException = new Exception__c(Purpose__c = 'Inserting Order records', Object__c = 'Order (Insert)', Process__c = 'SendToOrder_Subscriptions (Apex Class)', Details__c = eachError.getStatusCode() + ': ' + eachError.getMessage() + '\n' + '\n' + 'Field affected by the error: ' + eachError.getFields() + '\n' + '\n' + 'Note: This error is related to Account ID: ' + newOrders[orderInsertIndex].Accountid);
                    newExceptions.add(newException);
                }
            }
            orderInsertIndex++;
        } 
        
        // Create Order Lines
        List<OrderItem> newOrderLines = new List<OrderItem>();
        
        for (Order eachOrder : newOrders) {
            
            for (SBQQ__Subscription__c eachSubscription : thisScopeSubscriptions) {
                
                if (eachOrder.AccountId == eachSubscription.SBQQ__Account__c && 
                    eachOrder.Bill_To_Account__c == eachSubscription.Bill_To_Account__c && 
                    eachOrder.Business_Entity__c == eachSubscription.Business_Entity__c) {
                        
                        OrderItem newOrderLine = new OrderItem(
                            Orderid = eachOrder.id,
                            SBQQ__Subscription__c = eachSubscription.id,
                            Description = eachSubscription.Product_Description__c,
                            Display_Name__c = eachSubscription.Display_Name__c,
                            ServiceDate = eachSubscription.SBQQ__SubscriptionStartDate__c,
                            EndDate = eachSubscription.SBQQ__SubscriptionEndDate__c,
                            Quantity = eachSubscription.Accounting_Quantity__c,
                            SBQQ__QuotedListPrice__c = eachSubscription.SBQQ__ListPrice__c,
                            UnitPrice = eachSubscription.SBQQ__NetPrice__c,
                            PricebookEntryid = pricebookEntryids.get(eachSubscription.SBQQ__Product__c),
                            Transaction_Number__c = eachSubscription.Transaction_Number__c,
                            Purchase_Order__c = eachSubscription.Purchase_Order__c
                        ); 
                        newOrderLines.add(newOrderLine);
                    }
            }
        }
        
        // INSERT OrderItem records from source records
        List<Database.SaveResult> newOrderLines_SaveResults = database.insert(newOrderLines, false);
        integer orderLinesInsertIndex = 0;
        
        // RESULTS
        for (Database.SaveResult eachResult : newOrderLines_SaveResults) {
            // ON SUCCESS
            if (eachResult.isSuccess()) {
                System.debug('== DEBUG: Inserted OrderItem Record ==>> ' + eachResult.getId());
                // ON FAIL
            } else {
                for (Database.Error eachError : eachResult.getErrors()) {
                    Exception__c newException = new Exception__c(Purpose__c = 'Inserting OrderItem records', Object__c = 'OrderItem (Insert)', Process__c = 'SendToOrder_Subscriptions (Apex Class)', Details__c = eachError.getStatusCode() + ': ' + eachError.getMessage() + '\n' + '\n' + 'Field affected by the error: ' + eachError.getFields() + '\n' + '\n' + 'Note: This error is related to Subscription ID: ' + newOrderLines[orderLinesInsertIndex].SBQQ__Subscription__c);
                    newExceptions.add(newException);
                }
            }
            orderLinesInsertIndex++;
        } 

        database.insert(newExceptions,false);

    }
    
    global void finish(Database.BatchableContext BC) {
        
        // Chain to SendToOrder_Assets Apex Class
        if (!Test.isRunningTest()) {
            SendToOrder_Assets runSendToOrder_Assets = new SendToOrder_Assets();
            database.executebatch(runSendToOrder_Assets,100);
        }
        
    }
}

How would we get started in creating a Lightning Action/Quick Action? Is this possible?
Best Answer chosen by Rachel Linder 20
Alain CabonAlain Cabon

 "static" is missing in the controller (mandatory here).

You can get the Ids of the launched batches for the alert (development) (useless in production)
public class DirectBatch {
    @AuraEnabled
    public static String myExecuteBatch() {    
       
        SendToOrder_Subscriptions runSendToOrder_Subscriptions = new SendToOrder_Subscriptions(); 
        String batchprocessid1 = database.executebatch(runSendToOrder_Subscriptions,100); 

       ContractRenewalManager runContractRenewalManager = new ContractRenewalManager(); 
       String batchprocessid2 = database.executebatch(runContractRenewalManager,1); 

       return batchprocessid1 + ' ' + batchprocessid2 ;
    }
}

<!--quickLaunchBatch.cmp-->
<aura:component controller="DirectBatch" implements="force:lightningQuickAction">

    <aura:handler name="init" value="{!this}" action="{!c.doInit}" />

    <h1>Batch launched!</h1><br/>

    <lightning:button label="Close" onclick="{!c.clickClose}"/>

</aura:component>

({
    "clickClose" : function(cmp) {
       // Close the action panel
       var dismissActionPanel = $A.get("e.force:closeQuickAction");
       dismissActionPanel.fire();
   },

   "doInit" : function(cmp) {
        // create a one-time use instance of the DirectBatch action
        // in the server-side controller

        var action = cmp.get("c.myExecuteBatch");

        // Create a callback that is executed after 
        // the server-side action returns

        action.setCallback(this, function(response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                // Alert the user with the value returned 
                // from the server
                alert("Success from server: " + response.getReturnValue());
            }
            else if (state === "INCOMPLETE") {
                // do something
            }
            else if (state === "ERROR") {
                var errors = response.getError();
                if (errors) {
                    if (errors[0] && errors[0].message) {
                        console.log("Error message: " + 
                                 errors[0].message);
                        alert("Error from server:" + errors[0].message);
                    }
                } else {
                    console.log("Unknown error");
                }
            }
        });

        $A.enqueueAction(action);
    }
})


 

All Answers

Alain CabonAlain Cabon
The sufficient code on the server should be the class DirectBatch below but you need a small lightning component (the popup of the quick action) for the client side that will launch the server code automatically during the "do init" with a message "Batch launched!" and a button "Close" 

This code below should be sufficient on the server (callable from a lightning component as soon as there is the annotation @AuraEnabled)
public class DirectBatch {
 @AuraEnabled
 public void myExecuteBatch() {    

        SendToOrder_Subscriptions runSendToOrder_Subscriptions = new SendToOrder_Subscriptions();
        database.executebatch(runSendToOrder_Subscriptions,100);
        
        ContractRenewalManager runContractRenewalManager = new ContractRenewalManager();
        database.executebatch(runContractRenewalManager,1);
 }
}
The ligthning component must implement="force:lightningQuickAction"​​​​​​​ and use the "controller" (apex class) above.

Component: quickLaunchBatch.cmp
<!--quickLaunchBatch.cmp-->
<aura:component controller="DirectBatch" implements="force:lightningQuickAction">

    <aura:handler name="init" value="{!this}" action="{!c.doInit}" />

    <h1>Batch launched!<h1><br/>

    <lightning:button label="Close" onclick="{!c.clickClose}"/>

</aura:component>


Controller: quickLaunchBatch.js  (verbose because of the return value from the server after the call of the method myExecuteBatch in the controller )
({
    "clickClose" : function(cmp)
       // Close the action panel
       var dismissActionPanel = $A.get("e.force:closeQuickAction");
       dismissActionPanel.fire();
   },

   "doInit" : function(cmp) {
        // create a one-time use instance of the DirectBatch action
        // in the server-side controller

        var action = cmp.get("c.myExecuteBatch");

        // Create a callback that is executed after 
        // the server-side action returns

        action.setCallback(this, function(response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                // Alert the user with the value returned 
                // from the server
                alert("Success from server: " + response.getReturnValue());
            }
            else if (state === "INCOMPLETE") {
                // do something
            }
            else if (state === "ERROR") {
                var errors = response.getError();
                if (errors) {
                    if (errors[0] && errors[0].message) {
                        console.log("Error message: " + 
                                 errors[0].message);
                        alert("Error from server:" + errors[0].message);
                    }
                } else {
                    console.log("Unknown error");
                }
            }
        });

        $A.enqueueAction(action);
    }
})

https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/controllers_server_actions_call.htm

(not tested but that is the idea)
Rachel Linder 20Rachel Linder 20
So, since this would be my first try at this. I started by going through the developer console and I created the public class like the first step above you mention. I saved it and then had to shutdown my machine. Now I can't seem to find that class I created. Any tips on how to find it. 

Then, I am assuming in the Developer Console I would then create a new Lightning Component and choose Lightning Quick Action. After that though how do I build the controller?
Alain CabonAlain Cabon

1)  Developer Console: Menu: File > Open > Classes > button "Refresh" on the bottom right. 

2) Using the Developer Console
The Developer Console provides tools for developing your Aura components and applications.
https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/intro_devconsole.htm

You can see "COMPONENT" and "CONTROLLER" on the right panel.. 
You don't need to create quickLaunchBatch.cmp or quickLaunchBatch.js/
Just click on the submenus of the panel.

User-added image


3) Quick Start: Aura Components

https://trailhead.salesforce.com/en/content/learn/projects/quickstart-lightning-components

 
Rachel Linder 20Rachel Linder 20
Thanks Alain. When I am in the developer console the side bar is not showing. How do i get it to appear?
Alain CabonAlain Cabon
You have to create or reopen an Aura Component that is not the Apex controller.

For the Apex controller ( class DirectBatch  ) there is no right panel.

Create an Aura Component
Create and Add an Aura Component to the Record Page

An Aura component is a combination of markup, JavaScript, and CSS. You first create a component bundle.
  1. In the Developer Console, select File | New | Lightning Component.
  2. For the component name, enter quickLaunchBatch.
  3. Check Lightning Quick Action and then click Submit.
Rachel Linder 20Rachel Linder 20
Ok..so i have the following:
  1. Apex Class - DirectBatch.apxc - had to add a "get" infront of the myExecuteBatch for the public void
  2. Aura Component - quickLaunchBatch
  3. quickLaunchBatch.cmp
  4. Controller - quckLaunchBatchController.js
For the Controller, upon save I am getting the following error:

User-added image

This what i have for the controller:
({
    "clickClose" : function(cmp)
       // Close the action panel
       var dismissActionPanel = $A.get("e.force:closeQuickAction");
       dismissActionPanel.fire();
   },

   "doInit" : function(cmp) {
        // create a one-time use instance of the DirectBatch action
        // in the server-side controller

        var action = cmp.get("c.myExecuteBatch");

        // Create a callback that is executed after 
        // the server-side action returns

        action.setCallback(this, function(response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                // Alert the user with the value returned 
                // from the server
                alert("Success from server: " + response.getReturnValue());
            }
            else if (state === "INCOMPLETE") {
                // do something
            }
            else if (state === "ERROR") {
                var errors = response.getError();
                if (errors) {
                    if (errors[0] && errors[0].message) {
                        console.log("Error message: " + 
                                 errors[0].message);
                        alert("Error from server:" + errors[0].message);
                    }
                } else {
                    console.log("Unknown error");
                }
            }
        });

        $A.enqueueAction(action);
    }
})



 
Alain CabonAlain Cabon
There was a missing parenthesis after  function(cmp)  indeed (copy/paste)
"clickClose" : function(cmp)  {
       // Close the action panel
       var dismissActionPanel = $A.get("e.force:closeQuickAction");
       dismissActionPanel.fire();
 },
Alain CabonAlain Cabon
Error line 4 , column 8  [4, 8] after the missing parenthesis with an expected "var " because of the syntax error but the compiler shoud say "missing open parenthesis" instead for an easy understanding. It is not easy to debug javascript. 
Rachel Linder 20Rachel Linder 20
I think I am missing a step in all of this. I created the following below (with screen shots). But when I got ot edit a page I do not see the component listed to choose it. Once I get this figure out it will Trailheads for sure:

1. DirectBatch.apxc
public class DirectBatch {
 @AuraEnabled
 public void getmyExecuteBatch() {    

        SendToOrder_Subscriptions runSendToOrder_Subscriptions = new SendToOrder_Subscriptions();
        database.executebatch(runSendToOrder_Subscriptions,100);
        
        ContractRenewalManager runContractRenewalManager = new ContractRenewalManager();
        database.executebatch(runContractRenewalManager,1);
 }
}

2. quickLaunchBatch

User-added image

3. quickLaunchBatch.cmp
 
<!--quickLaunchBatch.cmp-->
<aura:component controller="DirectBatch" implements="force:lightningQuickAction">

    <aura:handler name="init" value="{!this}" action="{!c.doInit}" />

    <h1>Batch launched!</h1><br/>

    <lightning:button label="Close" onclick="{!c.clickClose}"/>

</aura:component>

4. qyuckLaunchBatchController.js
 
({
    "clickClose" : function(cmp) {
       // Close the action panel
       var dismissActionPanel = $A.get("e.force:closeQuickAction");
       dismissActionPanel.fire();
   },

   "doInit" : function(cmp) {
        // create a one-time use instance of the DirectBatch action
        // in the server-side controller

        var action = cmp.get("c.myExecuteBatch");

        // Create a callback that is executed after 
        // the server-side action returns

        action.setCallback(this, function(response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                // Alert the user with the value returned 
                // from the server
                alert("Success from server: " + response.getReturnValue());
            }
            else if (state === "INCOMPLETE") {
                // do something
            }
            else if (state === "ERROR") {
                var errors = response.getError();
                if (errors) {
                    if (errors[0] && errors[0].message) {
                        console.log("Error message: " + 
                                 errors[0].message);
                        alert("Error from server:" + errors[0].message);
                    }
                } else {
                    console.log("Unknown error");
                }
            }
        });

        $A.enqueueAction(action);
    }
})

 
Rachel Linder 20Rachel Linder 20
Duh - I forgot to create the quick action. Gonna play around with this. Thanks Alain.
Alain CabonAlain Cabon

 "static" is missing in the controller (mandatory here).

You can get the Ids of the launched batches for the alert (development) (useless in production)
public class DirectBatch {
    @AuraEnabled
    public static String myExecuteBatch() {    
       
        SendToOrder_Subscriptions runSendToOrder_Subscriptions = new SendToOrder_Subscriptions(); 
        String batchprocessid1 = database.executebatch(runSendToOrder_Subscriptions,100); 

       ContractRenewalManager runContractRenewalManager = new ContractRenewalManager(); 
       String batchprocessid2 = database.executebatch(runContractRenewalManager,1); 

       return batchprocessid1 + ' ' + batchprocessid2 ;
    }
}

<!--quickLaunchBatch.cmp-->
<aura:component controller="DirectBatch" implements="force:lightningQuickAction">

    <aura:handler name="init" value="{!this}" action="{!c.doInit}" />

    <h1>Batch launched!</h1><br/>

    <lightning:button label="Close" onclick="{!c.clickClose}"/>

</aura:component>

({
    "clickClose" : function(cmp) {
       // Close the action panel
       var dismissActionPanel = $A.get("e.force:closeQuickAction");
       dismissActionPanel.fire();
   },

   "doInit" : function(cmp) {
        // create a one-time use instance of the DirectBatch action
        // in the server-side controller

        var action = cmp.get("c.myExecuteBatch");

        // Create a callback that is executed after 
        // the server-side action returns

        action.setCallback(this, function(response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                // Alert the user with the value returned 
                // from the server
                alert("Success from server: " + response.getReturnValue());
            }
            else if (state === "INCOMPLETE") {
                // do something
            }
            else if (state === "ERROR") {
                var errors = response.getError();
                if (errors) {
                    if (errors[0] && errors[0].message) {
                        console.log("Error message: " + 
                                 errors[0].message);
                        alert("Error from server:" + errors[0].message);
                    }
                } else {
                    console.log("Unknown error");
                }
            }
        });

        $A.enqueueAction(action);
    }
})


 
This was selected as the best answer