Invoke Tooling API From LWC Salesforce

by Rijwan Mohmmed
invoke-tooling-api-from-lwc-salesforce

Hello friends, today we will discuss Invoke Tooling API From LWC Salesforce. While Apex provides a robust set of features, there are scenarios where advanced development tasks demand more flexibility and control. This is where the Tooling API steps in as a valuable resource.

The Tooling API is a RESTful API offered by Salesforce, specifically designed to interact with the metadata and tooling aspects of the platform programmatically.

Also, check this: Run Flow Asynchronously In Salesforce

Key Highlights :

  1. Perform a wide range of tasks, including querying metadata, deploying changes, running tests, and more.
  2. We can create execute code window and run the code.
  3. Tooling API allows developers to dynamically query metadata information using SOQL.
  4. Can query List View and fetch query.
  5. The Tooling API simplifies this process by providing REST endpoints to deploy metadata changes programmatically.
  6. The Tooling API enables developers to run tests programmatically, retrieve test results, and perform test-related operations.

Code :

Here we will create a Execute anonymous code window that can run any code like the developer console in Salesforce. We will create an LWC component for the front-end window and in the backend, we call tooling API in apex to perform code.

First of all, create a VF page to get the Session-Id of the current Logged In User, We can also authorize the tooling API by connected App and name credential

GetSessionId.vfp :

<apex:page >
    Start_Of_Session_Id{!$Api.Session_ID}End_Of_Session_Id
</apex:page>

UtilsGetSessionId.cls: here we get the session Id from the VF page.

/**
* @description       : This class is used to get Session Id of logged In User.
* @coverage          : ExecuteCodeCtrlTest | @CC100%
* @author            : Techdicer
* @group             : Techdicer
* @last modified on  : 06-19-2023
* @last modified by  : Techdicer
* Modifications Log
* Ver   Date         Author                        Modification
* 1.0   06-16-2023   Techdicer                       Initial Version
**/
global class UtilsGetSessionId{
    /**
    * @description get Session Id of logged In User.
    * @author Techdicer | 06-16-2023
    * @param PageReference visualforcePage name
    * @return String
    **/
    global static String getSessionIdFromVFPage(PageReference visualforcePage){
        String content = Test.isRunningTest() ? 'Start_Of_Session_Idtest1234567889999999999999End_Of_Session_Id' : visualforcePage.getContent().toString();
        Integer s = content.indexOf('Start_Of_Session_Id') + 'Start_Of_Session_Id'.length(),
                e = content.indexOf('End_Of_Session_Id');
        return content.substring(s, e);
    }
}

ExecuteCodeCtrl.cls :

/**
* @description       : This class is used to fetch metadata and execute apex code.
* @coverage          : ExecuteCodeCtrlTest | @CC90%
* @author            : Techdicer
* @group             : Techdicer
* @last modified on  : 06-21-2023
* @last modified by  : Techdicer
* Modifications Log
* Ver   Date         Author                        Modification
* 1.0   06-21-2023   Techdicer                       Initial Version
**/
public Without Sharing class ExecuteCodeCtrl {
    
    /**
    * @description execute the apex code by tooling API
    * @author Techdicer | 06-21-2023
    * @param String script
    * @return String success/error info
    **/
    @AuraEnabled
    public static String executeCode(String script) {
        //String baseURL='callout:Execute_Code/?'; //is use when you create connected salesforce app and name creds
        String baseURL = URL.getSalesforceBaseUrl().toExternalForm() + '/services/data/v57.0/tooling/executeAnonymous/?';
        Http h = new Http();
        HTTPRequest req = new HTTPRequest();
        //set endpoint with script code
        req.setEndpoint(baseUrl + 'anonymousBody=' + EncodingUtil.urlEncode(script, 'UTF-8'));
        req.setMethod('GET');
        req.setTimeout(120000);

        // OAuth header
        req.setHeader('Authorization','Bearer ' + UtilsGetSessionId.getSessionIdFromVFPage(Page.GetSessionId));      
        HttpResponse res = h.send(req);
        system.debug('Session Id - ' + res.getBody());
        System.debug(res.getBody());
        // Response to a execute successful should be 201
        if (res.getStatusCode() != 200 && res.getStatusCode() != 201) {
            System.debug(res.getBody());
            throw newMessageException('Error: Code did not execute. Please check your code | Status code : ' + res.getStatus());
        } else {
            //parse the json response body
            WrapperClass wrap = parse(res.getBody());
            if(wrap.success){
                return 'Successfully executed the Apex script code.';
            } else{
               String errorMessage = wrap.compileProblem != null ? wrap.compileProblem : (wrap.exceptionMessage != null ? wrap.exceptionMessage : '');
               throw newMessageException('Error: Code did not execute. Please check your code | Issue : ' + errorMessage);
            }
        }
    }
    
    /**
    * @description used to manage error message
    * @author Techdicer | 06-21-2023
    * @param String message
    * @return AuraHandledException
    **/
    private static AuraHandledException newMessageException(String message) {
    	AuraHandledException e = new AuraHandledException(message);
    	e.setMessage(message);
    	return e;
  	}
    
   /**
    * @description wrapper class for parse the JSON resonse
    * @author Techdicer | 06-21-2023
    **/
    public class WrapperClass {
        public Integer line;
        public Integer column;
        public Boolean compiled;
        public Boolean success;
        public String compileProblem;
        public String exceptionStackTrace;
        public String exceptionMessage;
    }
    
    /**
    * @description used to parse the JSON resonse
    * @author Techdicer | 06-21-2023
    * @param String json
    * @return WrapperClass
    **/
    public static WrapperClass parse(String json) {
		return (WrapperClass) System.JSON.deserialize(json, WrapperClass.class);
	}
}

executeCodeLWC.HTML :

<!--
  @description       : This html template is used to show Execute code screen with button.
  @author            : Rijwan
  @group             : Techdicer
  @last modified on  : 06-21-2023
  @last modified by  : Rijwan
  Modifications Log 
  Ver   Date         Author              Modification
  1.0   06-21-2023   Rijwan             Initial Version | Story Number
-->
<template>
	<lightning-card variant="Narrow" title="Execute Code" icon-name="standard:apex">
		<!-- loader -->
		<div if:true={showSpinner}>
			<lightning-spinner alternative-text="Loading..." variant="brand" class="slds-is-fixed">
			</lightning-spinner>
		</div>
		<!-----/loader-------->
		<div class="slds-p-horizontal_medium">
			<lightning-textarea name="Code" label="Code" data-id="code" value={scriptCode} onchange={handleFieldChange}
				class="scriptCode" required>
			</lightning-textarea>

			<div class="slds-text-align_center slds-m-top_small">
				<lightning-button variant="brand" label="Execute Code" title="Execute Code" onclick={handleExecute}
					disabled={isDisableExecuteButton}>
				</lightning-button>
			</div>
		</div>
	</lightning-card>
</template>

executeCodeLWC.JS:

/**
 * @description       : This JS file is used to fetch the metadata, call execute method in apex class. 
 * @author            : Techdicer
 * @group             : Techdicer
 * @last modified on  : 06-21-2023
 * @last modified by  : Techdicer
 * Modifications Log 
 * Ver   Date         Author         Modification
 * 1.0   06-21-2023   Techdicer     Initial Version
**/

import { LightningElement, track, wire, api } from 'lwc';
import executeCode from "@salesforce/apex/ExecuteCodeCtrl.executeCode";
import { ShowToastEvent } from 'lightning/platformShowToastEvent';

export default class ExecuteCodeLWC extends LightningElement {
    @track scriptCode;
    @track data;
    output;
    showSpinner = false;

    /**
     * @description handleFieldChange for detected field change of text area
     * @author Rijwan | 06-21-2023
     * @summary in this method assign the value in scriptCode variable
     */
    handleFieldChange(event) {
        this.scriptCode = event.detail.value;
    }

    /**
     * @description handleValidation for put validation on text area field used to write script code
     * @author Rijwan | 06-21-2023
     * @param   message error message
     * @param   isError show error or not
     * @summary here we pass params so according these we custom validate the script code text area field 
     */
    handleValidation(message, isError) {
        let scriptCode = this.template.querySelector(".scriptCode");

        if (isError) {
            scriptCode.setCustomValidity(message);
        } else {
            scriptCode.setCustomValidity(""); // clear previous value
        }

        scriptCode.reportValidity();
    }

    /**
     * @description handleExecute called when we click execute button
     * @author Rijwan | 06-21-2023
     * @summary in this method we call apex method executeCode and pass paramters which is scriptCode to run the script.
     */
    handleExecute(event) {
        this.showSpinner = true;
        executeCode({ script: this.scriptCode })
            .then(res => {
                this.showToast('Success', res, 'success', 'dismissable');
                this.handleValidation('', false);
            }).catch(err => {
                this.showToast('Error', err.body.message, 'error', 'dismissable');
                this.handleValidation(err.body.message, true);
            }).finally(() => {
                this.showSpinner = false;
            })
    }

    /**
     * @description isDisableExecuteButton
     * @author Rijwan | 06-21-2023
     * @summary this is used for enable/disable Execute Code button in HTML template according text area scriptCode value
     */
    get isDisableExecuteButton() {
        return !this.scriptCode || this.scriptCode == undefined;
    }

    /**
     * @description showToast is used show toast message
     * @author Rijwan | 06-16-2023
     * @param   title toast title
     * @param   message message to show
     * @param   variant type of toast
     * @param   mode dismissable/sticky
     * @summary here we pass params so according these we show the toast message. 
     */
    showToast(title, message, variant, mode) {
        const evt = new ShowToastEvent({
            title: title,
            message: message,
            variant: variant,
            mode: mode
        });
        this.dispatchEvent(evt);
    }
}

executeCodeLWC.CSS:

:host {
    --slds-c-textarea-sizing-min-height: 500px;
}

executeCodeLWC.JS-meta.xml:

<?xml version="1.0"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
	<apiVersion>57.0</apiVersion>
	<isExposed>true</isExposed>
	<targets>
		<target>lightning__HomePage</target>
	</targets>
</LightningComponentBundle>

Output :

invoke-tooling-api-from-lwc-salesforce

Reference :

  1. Tooling API
What’s your Reaction?
+1
1
+1
0
+1
0
+1
0
+1
0
+1
0

You may also like

2 comments

SFDCUser February 14, 2024 - 11:33 pm

That was a good article but it doesn’t seem to work anymore. Salesforce has removed access to the Session Id through Apex or the VF page for a lwc. You will have to use Named Credentials to access the Tooling API through a lwc now.

Reply
Rijwan Mohmmed February 15, 2024 - 8:07 am

In that case you have to create connected app for session Id

Reply

Leave a Comment