Example: Creating a custom process app for Salesforce opportunity management

Overview

In an opportunity management process, the first step is to create an opportunity in Salesforce, which is then followed by series of processes or process variants. The data of the closed opportunity is then stored and analyzed for the future course of actions or reference.

In this example, you create a custom process app, which is named Salesforce Opportunities, to extract, transform, and load the data from a Salesforce opportunity management process. The Python script for the Salesforce Opportunities process app must extract data of events in the OpportunityCreation and OpportunityChange processes. In addition, the data must have the following event attributes:

  • opportunityId
  • activity
  • startTime
  • resource
  • opportunityAmount
  • opportunityStage

Prerequisites

Before you start creating a custom process app, you must do the following mandatory configurations:

  • Download the backup file of a process in IBM Process Mining for mapping data in IDP format.

  • Create an account in the Salesforce instance of your organization. Remember the username and password for future use in Process App.

  • Connect your IBM Process Mining with the Salesforce account by using the Salesforce REST API through Connected App in Salesforce. You can use the OAuth parameters, consumer key, and consumer secret to validate the connection between IBM Process Mining and Salesforce. For more information about creating a Connected App in Salesforce, see Create a Connected App.

Procedure

The creation of a custom process app includes the following activities:

Creating Summary card

In the Summary card step, do the following steps:

  1. Enter the name of the process app, 'Salesforce Opportunities', in the Name field.

  2. Enter a description about the process app in the Brief description field.

  3. Switch the toggle to Closed source or Open source to set the permissions for the process app users. You can choose one of the following options per your requirement:

    • Switch the toggle to Open source if you want to give the Use, View, Export, and Duplicate permissions to the process app users.

    • Switch the toggle to Closed source if you want to give only the Use and Export permissions to the process app users.


    ⓘ Tip: For more information about permissions in process app, see the Understanding types of users and permissions in custom process app topic.


  4. Click the Next button to go to the Upload your Process App logic step.


ⓘ Important: Optional: If you want to customize icon and add feature highlights and documentation link on the summary card, expand the Customization section. For more information about the Customization section, see the Creating a Summary card for a process app topic.


Upload the Process App logic

In the Upload your Process App logic step, you connect to Salesforce, collect data, and prepare it to be used by IBM Process mining. By clicking the Download template link, you can download the template to write a single Python script to extract and transform the data.

The following steps give the detailed instruction about writing a Python script for the Salesforce opportunity management process:

  1. Define the import libraries:

        import requests
        import json
        from requests.models import PreparedRequest
        from process_app import ProcessAppException
    
  2. Include a method named execute that contains the parameter context:

        def execute(context):
    

    ⓘ Tip: The context parameter contains all the contextual information that you need to write the Python script.


  3. Use the OAuth parameters that you configured in Connected App and the Salesforce account credentials to set up the connection between process app and Salesforce:

        # reading data from accelerator config
            consumerKey = config['<consumerKey>']
            secret = config['<consumerSecret>']
            username = config['<username>']
            password = config['<password>']
    
  4. Define exception message for unsuccessful connection between process app and Salesforce:

        if consumerKey=='' or secret =='' or username =='' or password =='':
            raise ProcessAppException('configuration not valid')
    
  5. Define the Salesforce account log in process and exceptions:

        # login to salesforce
    
        loginParams = {'client_id': consumerKey,'client_secret': secret,
        'grant_type': 'password','username':username,
        'password':password}
        req = PreparedRequest()
        req.prepare_url('https://login.salesforce.com/services/oauth2/token', loginParams)
    
        loginResponse = requests.post(req.url)
        if loginResponse.status_code != 200 :
            raise ProcessAppException('cannot log to salesforce account')
        data = loginResponse.json()
        accessToken = data.get('access_token')
        instanceUrl = data.get('instance_url') 
    
  6. Define the event logs that needs to be extracted:

        events = []
        importOpportunityCreation(accessToken, instanceUrl, events)
        importOpportunityChange(accessToken, instanceUrl, events)
    
  7. Define the event attributes that must be returned in the extracted data:

        df = pd.DataFrame(events,
        columns= ['opportunityId', 'activity', 'startTime', 'resource', 'opportunityAmount', 'opportunityStage'])
    
        return df
    
  8. Define the SOQL query in Salesforce to extract data:

        def runSOQLQuery(query, accessToken, instanceUrl, nextRecordsUrl=None):
            req = PreparedRequest()
            if nextRecordsUrl != None:
            req.prepare_url(instanceUrl+nextRecordsUrl, {} )
                else:
            req.prepare_url(instanceUrl+'/services/data/v39.0/query/', {'q' : query})
            queryResponse = requests.get(req.url,
            headers={'Authorization': 'Bearer ' + accessToken})
    
            if queryResponse.status_code != 200 :
            raise Exception('cannot perform SOQL query to salesforce account')
                return queryResponse.json()
    
  9. Define the SOQL query to extract and transform the data tables for OpportunityCreation:

        def formatDate(date):
            return date[0: date.find('.')].replace('T', ' ')
        
        def importOpportunityCreation(accessToken, instanceUrl, events, nextRecordsUrl=None):
            queryResults = runSOQLQuery('Select Id, Amount, StageName, CreatedBy.Name from Opportunity ', accessToken, instanceUrl, nextRecordsUrl)
            
            records = queryResults.get('records')
            for record in records:
                resource = record.get('CreatedBy').get('Name')
                createDate = formatDate(record.get('CreatedDate'))
                amount = record.get('Amount')
            
            events.append([ record.get('Id'),'Create Opportunity', createDate, resource, '0' if 'null' == amount else amount, record.get('StageName') ])
            
            if queryResults.get('nextRecordsUrl') != None :
                importOpportunityCreation(accessToken, instanceUrl, events, queryResults.get('nextRecordsUrl'))
    
  10. Define the SOQL query to extract and transform the data tables for OpportunityChange:

        def importOpportunityChange(accessToken, instanceUrl, events, nextRecordsUrl=None):
        
            queryResults = runSOQLQuery('Select OpportunityId, CreatedBy.Name, CreatedDate,' + ' Field, NewValue, OldValue from OpportunityFieldHistory', accessToken, instanceUrl, nextRecordsUrl)
    
            records = queryResults.get('records')
            for record in records:
                resource = record.get('CreatedBy').get('Name')
                opportunityId = record.get('OpportunityId')
                createDate = formatDate(record.get('CreatedDate'))
                field = record.get('Field')
                
                if 'StageName' == field:
                    activity='Set Opportunity to ' + record.get('NewValue')
                elif 'Owner' == field:
                    activity='Change opportunity Owner'
                elif 'AccountId' == field:
                    activity = 'Change opportunity Account'
                elif 'Amount' == field:
                    activity= 'Change opportunity Amount'
                
                events.append([ opportunityId, activity, createDate, resource, 0, ‘’ ])
    
            if queryResults.get('nextRecordsUrl') != None :
                importOpportunityChange(accessToken, instanceUrl, events,
                    queryResults.get('nextRecordsUrl'))
    

You can copy and use the following code snippet that contains the complete Python script:

    import requests
    import json
    from requests.models import PreparedRequest
    from process_app import ProcessAppException
    def execute(config):
        # reading data from accelerator config
        consumerKey = config['consumerKey']
        secret = config['consumerSecret']
        username = config['username']
        password = config['password']

        if consumerKey=='' or secret =='' or username =='' or password =='':
            raise ProcessAppException('configuration not valid')
   
        # login to salesforce

        loginParams = {'client_id': consumerKey,'client_secret': secret,'grant_type': 'password','username':username,'password':password}
        req = PreparedRequest()
        req.prepare_url('https://login.salesforce.com/services/oauth2/token', loginParams)

        loginResponse = requests.post(req.url)
        if loginResponse.status_code != 200 :
            raise ProcessAppException('cannot log to salesforce account')
        data = loginResponse.json()
        accessToken = data.get('access_token')
        instanceUrl = data.get('instance_url')

        events = []

        importOpportunityCreation(accessToken, instanceUrl, events)
        importOpportunityChange(accessToken, instanceUrl, events)

        df = pd.DataFrame(events,columns= ['opportunityId', 'activity', 'startTime', 'resource',
        'opportunityAmount', 'opportunityStage'])

        return df

    # SOQL query to extract data
    def runSOQLQuery(query, accessToken, instanceUrl, nextRecordsUrl=None):
       req = PreparedRequest()
       if nextRecordsUrl != None:
        req.prepare_url(instanceUrl+nextRecordsUrl, {} )
            else:
        req.prepare_url(instanceUrl+'/services/data/v39.0/query/', {'q' : query})
        queryResponse = requests.get(req.url,
        headers={'Authorization': 'Bearer ' + accessToken})

        if queryResponse.status_code != 200 :
        raise Exception('cannot perform SOQL query to salesforce account')
            return queryResponse.json()
    
    # retrieve data for opportunity creation
    def formatDate(date):
            return date[0: date.find('.')].replace('T', ' ')
        
    def importOpportunityCreation(accessToken, instanceUrl, events, nextRecordsUrl=None):
        queryResults = runSOQLQuery('Select Id, Amount, StageName, CreatedBy.Name from Opportunity ', accessToken, instanceUrl, nextRecordsUrl)
        
        records = queryResults.get('records')
        for record in records:
            resource = record.get('CreatedBy').get('Name')
            createDate = formatDate(record.get('CreatedDate'))
            amount = record.get('Amount')
        
        events.append([ record.get('Id'),'Create Opportunity', createDate, resource, '0' if 'null' == amount else amount, record.get('StageName') ])
        
        if queryResults.get('nextRecordsUrl') != None :
            importOpportunityCreation(accessToken, instanceUrl, events, queryResults.get('nextRecordsUrl'))

    # retrieve data for opportunity change
    def importOpportunityChange(accessToken, instanceUrl, events, nextRecordsUrl=None):
        
        queryResults = runSOQLQuery('Select OpportunityId, CreatedBy.Name, CreatedDate,' + ' Field, NewValue, OldValue from OpportunityFieldHistory', accessToken, instanceUrl, nextRecordsUrl)

        records = queryResults.get('records')
        for record in records:
            resource = record.get('CreatedBy').get('Name')
            opportunityId = record.get('OpportunityId')
            createDate = formatDate(record.get('CreatedDate'))
            field = record.get('Field')
            
            if 'StageName' == field:
                activity='Set Opportunity to ' + record.get('NewValue')
            elif 'Owner' == field:
                activity='Change opportunity Owner'
            elif 'AccountId' == field:
                activity = 'Change opportunity Account'
            elif 'Amount' == field:
                activity= 'Change opportunity Amount'
            
            events.append([ opportunityId, activity, createDate, resource, 0, ‘’ ])
 
        if queryResults.get('nextRecordsUrl') != None :
            importOpportunityChange(accessToken, instanceUrl, events,
                queryResults.get('nextRecordsUrl'))

After you write the script and save the Python file (.py file) in your system, click the Drag and drop file here to click to upload link in the Upload your Process App logic step to select and upload the Python file in the "Custom process app" wizard.

When you finish uploading the Python file, click the Next button to go to the Define user inputs step in the "Custom process app" wizard.

Define user inputs

In the Define user inputs step, you set up the input data fields that the Process App users must use to specify the data to be transformed during process generation. This step is optional. For more information about the Define user inputs step, see the Defining the user input fields topic.

Click the Next button to go to the Provide user guidance step.

Provide user guidance

In the Provide user guidance step, you create user guides for your custom process app. This step is optional. For more information about the Provide user guidance step, see the Providing the user guidance for the process app topic.

Click the Next button to go to the Process settings step.

Set up a process for the process app

The Process setting step is the last step in process app creation. In this step, you upload a process back up file in the .idp format or duplicate settings from an existing process in IBM Process Mining. The process app uses the mapped data columns, the process settings, filters, and dashboards from the process backup file to generate a new process.

To upload a process back file, do the following steps:

  1. Select the Upload process backup file option in the Process setting step.

  2. Click on the Drag and drop file here or click to upload link to upload the process backup file.

  3. Click the Save button.

To duplicate the settings of an existing process, do the following steps:

  1. Select the Duplicate settings from a process option in the Process setting step.

  2. Select a process from the Find a process dropdown.

  3. Click the Create button.

If you do not have a process backup file, you can create a .csv file with the extracted columns, which are opportunityId, activity, startTime, resource, opportunityAmount, and opportunityStage. You can use the .csv file to create a process in IBM Process Mining, for which you can create a backup file in the .idp format.

Result

After you complete the creation of the custom process app, you can see the Salesforce Opportunities process app in the Process Apps dashboard. You can now use the process app to generate a process in IBM Process Mining. For more information about generating a process by using a process app, see the Generating a process by using the custom Process App topic.

'Figure 1. Salesforce Opportunities process model' shows the process model that you can generate by using the Saleforce Opportunities Process App.

Salesforce Opportunities process model

Figure 1. Salesforce Opportunities process model