6

BACKGROUND

I found this really useful tool called Salesforce Config Switch which allows me to easily enable and disable processes on mass in one go.

But it's throwing a 500 error at the moment.

QUESTION

Are there any other similar tools out there?

9

I wrote a lightning component to allow me to easily enable and disable flows en masse.

It lists all the latest Flows, and give you buttons to:

  • Toggle Selected Flows
  • Enable All Flows
  • Disable All Flows

Component

<aura:component controller="FlowManagerController" implements="flexipage:availableForAllPageTypes">

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

<aura:attribute name="flows" type="Object"/>
<aura:attribute name="selectedFlows" type="List"/>
<aura:attribute name="columns" type="List" default="[                                        
                                                    {label: 'Developer Name', fieldName: 'DeveloperName', initialWidth: 500, type: 'text' },                                             
                                                    {label: 'Active', fieldName: 'Active', initialWidth: 150, type: 'boolean'},                                             
                                                    {label: 'Action', type: 'button', initialWidth: 200, typeAttributes:
                                                    {label: 'Change Status', name: 'change_status' }},
                                                    {label: 'Latest Version', fieldName: 'LatestVersionNumber', initialWidth: 150, type: 'number' },                                             
                                                    {label: 'Active Version', fieldName: 'ActiveVersionNumber', initialWidth: 150, type: 'number' },                                             
                                                    ]" />


<aura:attribute name="loading" type="Boolean" default="false"/>

<aura:if isTrue="{!v.loading}">
    <lightning:spinner alternativeText="Loading" size="large" />
</aura:if>

<div class="slds-card slds-p-around_small">

    <div class="slds-p-bottom_small">
        <lightning:button 
                          label="Toggle selected Flows"
                          variant="brand"
                          onclick="{! c.handleToggleSelectedFlows }"
                          />

        <lightning:button 
                          label="Enable All Flows"
                          variant="brand"
                          onclick="{! c.handleEnableAllFlows }"
                          />

        <lightning:button 
                          label="Disable All Flows"
                          variant="brand"
                          onclick="{! c.handleDisableAllFlows }"
                          />

    </div>

    <lightning:datatable keyField="id"
                         data="{! v.flows }"
                         onrowaction="{! c.handleRowAction }"
                         columns="{! v.columns }"
                         onrowselection="{! c.handleRowSelection }"
                         showRowNumberColumn="true" />

</div>

</aura:component>

JavaScript Controller

({
    doInit : function(cmp, event, helper) {
        helper.getFlows(cmp);
    },
    handleToggleSelectedFlows : function(cmp, event, helper){
        helper.showLoading(cmp);
        let flows = cmp.get("v.selectedFlows");
        console.log(flows);
        if (flows && flows.length > 0){
            let promises = [];
            flows.forEach(function(row) {
                promises.push(helper.updateFlowPromise(cmp, row));
            });
            Promise.all(promises).then(function(results){
                console.log(results); 
                helper.getFlows(cmp);
                helper.hideLoading(cmp);
            });
        }
    },
    handleEnableAllFlows : function(cmp, event, helper){
        helper.showLoading(cmp);
        let flows = cmp.get("v.flows");
        if (flows && flows.length > 0){
            let promises = [];
            flows.forEach(function(row){
                if (row.Active == false) promises.push(helper.updateFlowPromise(cmp, row));          
            });
            Promise.all(promises).then(function(results){
                console.log(results); 
                helper.getFlows(cmp);
                helper.hideLoading(cmp);
            });
        }
    },
    handleDisableAllFlows : function(cmp, event, helper){
        helper.showLoading(cmp);
        let flows = cmp.get("v.flows");
        if (flows && flows.length > 0){
            let promises = [];
            flows.forEach(function(row) {
                if (row.Active) promises.push(helper.updateFlowPromise(cmp, row));              
            });
            Promise.all(promises).then(function(results){
                console.log(results); 
                helper.getFlows(cmp);
                helper.hideLoading(cmp);
            });
        }
    },
    handleRowSelection : function(cmp, event, helper){
        let flows = event.getParam('selectedRows');
        console.log(flows);
        cmp.set("v.selectedFlows", flows);
    },
    handleRowAction: function(cmp, event, helper) {
        var action = event.getParam('action');               
        if (action.name =='change_status') {
            var row = event.getParam('row');        
            console.log(row);   
            helper.updateFlow(cmp, row);
        }
    }
})

JavaScript Helper

({
    showLoading : function(cmp){
        cmp.set("v.loading", true);  
    },
    hideLoading : function(cmp){
        cmp.set("v.loading", false);  
    },
    getFlows : function(cmp) {
        this.callAction(cmp, 'c.getFlows', null, function(results) {
            if (results == null) return;
            let data = JSON.parse(results);
            let records = data.records;
            if (records && records.length > 0) {
                records.forEach(function(r){
                    if (r.ActiveVersion){
                        r.ActiveVersionNumber = r.ActiveVersion.VersionNumber;     
                        r.Active = true;
                    } else {
                        r.ActiveVersionNumber = null;
                        r.Active = false;
                    }
                    r.LatestVersionNumber = r.LatestVersion.VersionNumber;
                }); 
            }
            cmp.set("v.flows", records);
        });  
    },
    updateFlow : function(cmp, row){
        let self = this;
        let params = this.getParams(row);
        this.callAction(cmp, 'c.updateFlow', params, function(result){
            console.log(result);
            if (result == true) self.getFlows(cmp);
        })  
    },
    updateFlowPromise : function(cmp, row){
        let params = this.getParams(row);
        return this.promiseAction(cmp, 'c.updateFlow', params);
    },
    getParams : function(row){
        let flowId = row.Id;
        let versionNumber = row.LatestVersionNumber;
        if (row.Active) versionNumber = null;        
        let metadata = {
            'Metadata': {
                'activeVersionNumber': versionNumber
            }
        }
        let params = {};
        params.flowId = flowId;
        params.metadata = JSON.stringify(metadata);
        return params;
    },
    promiseAction : function(cmp, methodName, params){
        console.log(methodName);
        console.log(params);
        var self = this;
        return new Promise(function(resolve, reject) {     
            var action = cmp.get(methodName); 
            if (params != null) action.setParams(params);   
            action.setCallback(this, function(response) {
                var state = response.getState();
                console.log(methodName + ' ' + state);
                if(cmp.isValid() && state === "SUCCESS"){
                    var result = response.getReturnValue();
                    //console.log(result);
                    resolve(result);
                } else if (state === "ERROR"){
                    var errors = response.getError();
                    self.handleErrors(errors);
                    reject(errors);
                }
            });
            $A.getCallback(function() {
                $A.enqueueAction(action); 
            })(); 
        }); 
    },
    callAction : function(cmp, methodName, params, callback){
        var self = this;
        var action = cmp.get(methodName); 
        if (params != null) action.setParams(params);
        action.setCallback(this, function(response) {
            var state = response.getState();
            console.log(methodName + ' ' + state);
            if(cmp.isValid() && state === "SUCCESS"){
                var result = response.getReturnValue();
                //console.log(result);
                if (callback) callback(result);
            } else if (state === "ERROR"){
                self.handleErrors(response.getError());
            }
        });

        $A.getCallback(function() {
            $A.enqueueAction(action); 
        })();  
    },
    handleErrors : function(errors) {
        // Configure error toast
        let toastParams = {
            title: "Error",
            message: "Unknown error", // Default error message
            type: "error"
        };
        // use the error message if any
        if (errors) {
            if (errors[0] && errors[0].message) {
                console.log(errors[0].message);
                toastParams.message = errors[0].message;
            }
        }
        // Fire error toast
        let toastEvent = $A.get("e.force:showToast");
        toastEvent.setParams(toastParams);
        toastEvent.fire();
    },
})

Apex Controller

public class FlowManagerController {

    private static final string RESOURCE_TOOLING_QUERY_FLOW = '/tooling/query?q=Select+Id,ActiveVersion.VersionNumber,LatestVersion.VersionNumber,DeveloperName+From+FlowDefinition+ORDER+BY+DeveloperName';
    private static final string RESOURCE_TOOLING_SOBJECT_FLOW = '/tooling/sobjects/FlowDefinition/';

    @AuraEnabled
    public static String getFlows() {

        HttpResponse response = executeCallout('GET', RESOURCE_TOOLING_QUERY_FLOW, null);

        System.debug(response);

        if (response.getStatusCode() == 200) {
            return response.getBody();
        }

        throw new AuraHandledException(response.toString());
    }

    @AuraEnabled
    public static boolean updateFlow(String flowId, String metadata) {

        HttpResponse response = executeCallout('PATCH', RESOURCE_TOOLING_SOBJECT_FLOW + flowId + '/?_HttpMethod=PATCH', metadata);

        System.debug(response);

        if (response.getStatusCode() == 200 || response.getStatusCode() == 204) {
            return true;
        }

        return false;
    }

    public static HttpResponse executeCallout(String method, String resourceName, String requestBody) {

        HttpRequest request = new HttpRequest();

        if (method == 'PATCH') {
            request.setMethod('POST');    
        } else {
            request.setMethod(method);    
        }

        request.setEndpoint(getUrl() + resourceName);

        request.setHeader('Authorization', 'OAuth {!$Credential.OAuthToken}');

        request.setHeader('Accept', 'application/json');
        request.setHeader('X-PrettyPrint', '1');
        request.setHeader('Content-Type', 'application/json');

        if (String.isNotBlank(requestBody)) {
            request.setBody(requestBody);
        }

        System.debug(request);

        return new Http().send(request);
    }

    private static String getUrl(){
        return 'callout:Named_Credential'; // CHANGE THIS
    }
}

SETUP

  1. Create Connected App
  2. Create Auth. Provider
  3. Create Named Credential
  4. Create Lightning App Page called Flow Manager
  • Do you have a GitHub account? If so, maybe you could make this a repo? If not, may I? I can link it up to the GitHub Deploy to Salesforce feature. – sfdcfox Jul 29 '18 at 5:03
  • This would have made for a great Session at DF18! Its unfortunate that you didn't create this sooner and make a proposal to present it at DF18. I'd strongly encourage you to write a blog post about it and submit it to the Developer Blog for publication. Contact me if you need help with the submission. – crmprogdev Jul 29 '18 at 12:46
  • 1
    @crmprogdev Thanks. I've recently been thinking about setting up a Salesforce related blog. Based on your recommendation, I will crack-on and make it happen and post this. Thanks again. – Robs Jul 29 '18 at 13:05
  • 1
    Be sure to post a link to it on Twitter and LinkedIn when you do! Feel free to @mention me to get you some extra coverage on it. I'll look forward to reading it. – crmprogdev Jul 29 '18 at 13:58
  • 1
    Hi, Robs. Salesforce Flow PM here. This is awesome and I'd like to get a post about this, preferably with an installable package, up on UnofficialSF here: unofficialsf.com/flow-resources If that sounds interesting, contact me at aedelstein at salesforce.com – Alex Edelstein Mar 3 '19 at 17:42
1

I've also made a similar tool using javascript. It is web based and use OAuth 2.0 to connect to your SF instance.

https://vuesf.azurewebsites.net/

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.