Michael Shaheen

Calling a Sugar Integrate Procedure from an external platform

Blog Post created by Michael Shaheen Employee on May 13, 2020

There is a lot you can do with Sugar Integrate. In most of the demos we have released, our examples show a Procedure that is triggered by an event in a platform like Sugar Sell. But what about when we want a Procedure to be called when a user clicks a button in a platform (any other platform, but for this article we'll look at Sugar Sell).

 

Converting an event-based procedure to manual

I had a procedure already set up that sends a message to Slack whenever a new lead is added to Sugar. So, you know, it is event-based. I decided that I wanted to use that same procedure but have it triggered with a button press inside of Sugar Sell. In Sugar Integrate, only manual procedures can be called externally. It turns out, there isn't a simple way to convert a trigger-based procedure into a manual one. So, I improvised. There are at least 2 ways to convert a trigger. Each method is a bit of a work-around. You decide which method you prefer.

 

Method 1 - Export, Edit, Import

If you are comfortable editing JSON, this method may be for you. The first thing I did was export my existing event-based procedure. Then, I opened the json file in a text editor and located the triggers object. It looked like this:

"triggers": [{
     "id": 8068,
     "onSuccess": ["js-step"],
     "onFailure": [],
     "type": "event",
     "async": true,
     "name": "trigger",
     "properties": {
          "elementInstanceId": "trigger1"
     }
}],

 

I can see that the type is set to "event". That's the first value to change. The other change to make will be to remove the values in the "properties" object because it is not necessary for a manual trigger. So, my new trigger will be:

"triggers": [{
     "id": 8068,
     "onSuccess": ["js-step"],
     "onFailure": [],
     "type": "manual",
     "async": true,
     "name": "trigger",
     "properties": {}
}],

 

Now, I can create a new procedure template by uploading this json. It will create the full procedure with all of the steps and variables. The only difference will be that the trigger is now manual.

 

Method 2 - Delete Original Trigger, Add a New One

The second method is all in the UI. First, open your procedure and take note of the first step AFTER the trigger. Now you can click on the trigger step and choose "Edit". In the edit screen, there is an option for "Delete". When you click on Delete (and then confirm), you will be shown the trigger type select screen. Choose your new Trigger type - in this case we would select "Manual". After making a selection, you will be brought back to the procedure editing screen. OH NO... all of my steps are gone! Not to worry. This is why we took note of the first step.

 

Click on the Plus sign to add a new step. From the "Add Procedure Step" screen, switch to "Add from Existing" and select the step with the same name that you took note of. After selecting the step to add, you'll see that all of the original child steps have been added back in.

 

So what do I need to send in as the data? All we need is the minimum values required for an event-triggered formula with a payload that contains the 'events' object. The json should look like this:

{
     "events": [{
          "objectType": "leads",
          "objectId": "37cf6e1e-9130-11ea-92bb-02d60046d9de",
          "eventType": "CREATED"
     }]
}

 

What data did I send in? From my previous procedure, I know that objectType should equal "leads". objectId comes from the GUID of the lead in Sugar. Since it is a new lead, the eventType will be "CREATED". That should do it!

 

Wait, wait, wait… My first step in the procedure was looking for data at trigger.event.objectType. But when I send in this json object, i get an error telling me that trigger.event is undefined. So by logging out (console.log) the trigger object, I see that it now sees this:

[
  {
    "args": {
      "events": [
        {
          "date": "2019-02-10T19:07:00Z",
          "eventType": "CREATED",
          "objectId": "37cf6e1e-9130-11ea-92bb-02d60046d9de",
          "objectType": "leads"
        }
      ]
    }
  },
  "new applicant"
]

 

No problem, I will update my step to now look for trigger.args.events[0].objectType. I chose to use the 0 index on events because I control the data and I am planning on only sending one lead in at a time. So, all I need is the first event in the array.

 

After making these updates, my procedure is working as I had hoped - doing exactly what it was doing as an event-based procedure but via a manual trigger.

 

That's fine for in the debugger. But how do I set this up so that I can use the procedure as a resource (call it externally)?

 

Calling the Procedure from Postman

There are currently two ways to call this procedure externally. One is by posting to /formulas/instances/{instanceId}/executions. This method is asynchronous and will complete usually within 15 minutes. The other method uses a feature that is currently in beta but will execute the procedure immediately. I'm going to try the beta method. The standard method will only send a response telling me that the request was received. The synchronous method will show me my results directly from the procedure. I also like this new method because I can give my endpoint a custom name.

 

In the procedure editor, I can hit the Edit button in the top right and show the advanced options. In the section titled "Execute Procedure via API (BETA)" I will select POST and create an endpoint name. For my purposes, I called it "shaheen". I chose this name because there's no way I will forget that name and I know it won't conflict with any other endpoint.

 

This is important to remember because each Procedure that you open up for external use is added as an endpoint to the Sugar Integrate API. This API is technically shared with other users and organizations. So, if someone in your organization knows your procedure instance ID, they can call this endpoint. Don't worry, no one outside of your organization will be able to. They will get an "invalid organization or user secret" error.

 

Now all that's left to do is try calling this new endpoint externally. Let's move into Postman. I know I will need to set up some headers. In Sugar Integrate, I can grab my org and user secret by clicking on the profile icon in the bottom left of the application screen.

 

I will also need the ID of the procedure instance. That can be found in the instances section of Sugar Integrate.

 

For postman, I must add 2 headers: Authorization and Elements-Formula-Instance-Id (those are case-sensitive). The value for Elements-Formula-Instance-Id is simply the Procedure instance ID that we just located. The Authorization value should look like this:

User xxxxYOUR_USER_SECRET_FROM_INTEGRATExxxx=, Organization xxxxYOUR_ORGANIZATION_SECRET_FROM_INTEGRATExxxx

 

Now I can add the body which is the JSON we used earlier:

{
     "events": [{
          "objectType": "leads",
          "objectId": "37cf6e1e-9130-11ea-92bb-02d60046d9de",
          "eventType": "CREATED"
     }]
}

The URL of the endpoint in Sugar Integrate is https://api-us.integrate.sugarapps.com/elements/api-v2/shaheen. This will be similar for yours as well.

 

That's it. We should be able to make the request now. When I hit "send", I will receive a success response that looks something like this:

 

There should also be a message that was sent to Slack (because that's what this procedure does). And, finally I can look at the executions for the Procedure instance and see a successful one.

 

 

Calling the Procedure from Sugar Sell

I'm feeling good about our external manual procedure. All that's left is to create the button in Sugar and have it call this new endpoint.

 

To do so, I have created a Module Loadable Package (MLP). The package is attached to this article. The package will copy 3 files into the custom directory:

  1. record.php - which is an altered copy of /modules/Leads/clients/base/views/record/record.php that adds the button to the record view of the Leads module
  2. record.js - the controller that will contain the JavaScript code for the button's action
  3. en_us.slack-button.php - a language file for the text of our new button

 

In record.php, I added the following item to the buttons array:

array(
    'type' => 'divider',
),
array(
    'type' => 'rowaction',
    'event' => 'button:slack_button:click',
    'name' => 'slack_button',
    'label' => 'LBL_SLACK_BUTTON_LABEL',
    'acl_action' => 'view',
),

This creates a button in the record view dropdown menu for a Lead. The text for it will be defined as LBL_SLACK_BUTTON_LABEL in our language file. When clicked, the button will call the slack_button function from our controller. Oh, and I added a divider to make it look nicer. If we upload our MLP with just the record.php and language file, we would see our new menu item at the bottom of the menu with a divider like this:

 

Now, let's hook up the button. In the JS controller file, I added a listener to the init function for the new button.

this.context.on('button:slack_button:click', this.slack_button, this);

Then, I added the actual function that the listener is referring to.

 

    slack_button: function() {
        //example of getting field data from current record
        var LeadID = this.model.get('id');
        var evtData = {
             "events": [
                 {
                     "objectType": "leads",
                     "objectId": LeadID,
                     "date": "2019-02-10T19:07:00Z",
                     "eventType": "CREATED"
                 }
             ]
        };

        $.ajax({
            url : "https://api-us.integrate.sugarapps.com/elements/api-v2/shaheen",
            type: "POST",
            data : JSON.stringify(evtData),
            contentType : "application/json",
            dataType: "json",
            beforeSend: function (xhr) {
                xhr.setRequestHeader("Authorization", "User xxxxYOUR_USER_SECRET_FROM_INTEGRATExxxx=, Organization xxxxYOUR_ORGANIZATION_SECRET_FROM_INTEGRATExxxx");
                xhr.setRequestHeader("Elements-Formula-Instance-Id", "1422812");
            },
            success: function(data, textStatus, jqXHR) {
                app.alert.show('slack-button-pressed', {
                    level: 'success',
                    messages: 'Successful API call for Applicant with ID# ' + LeadID + '.',
                    autoClose: false
                });
            },
            error: function (jqXHR, textStatus, errorThrown) {
                app.alert.show('slack-button-pressed', {
                    level: 'error',
                    messages: 'API call failed for Applicant with ID# ' + LeadID + '.',
                    autoClose: false
                });
            }
        });

    }

This function first grabs the GUID of the current Lead record by calling this.model.get('id'). Then I constructed the JSON for the body of the request and assigned it to a variable called evtData. Now all we need is a jQuery AJAX call. Sugar Integrate allows cross-domain requests so it is not necessary to proxy the request from the back-end. BUT you really should. In this example, all of the code is written in javascript. That means my org and user secrets are exposed to anyone who wants to inspect the code. I do not recommend this practice. But, for this example exercise, it was the simplest method.

 

I should point out a few things about the AJAX request we are making. The first 2 properties should make sense: the URL of the endpoint is the same as we used in Postman. The method is POST. For the data, it is important to set the contentType to application/json and the dataType to json. Then, ensure that the data is formatted correctly, remember to call JSON.stringify on the data object.

 

For our headers (like in Postman), we will use the beforeSend function. Follow the example above to add the 2 headers that we used in Postman. 

 

In the event handlers (success and error), I simply made calls to app.alert.show that will display a success or failure message.

 

Upload and install the MLP, and we should have a working button that will trigger our procedure from Sugar Integrate. All the same tests apply: see the success/error message in Sugar Sell, verify there's a new message in Slack, look at the executions for the instance in Sugar Integrate.

 

That is it! We have successfully converted a procedure in Sugar Integrate to a manual trigger that is accessible from the outside. We then added a button in Sugar Sell to call that procedure. It took some work to get here. I had to console.log in the procedure for debugging and use the API docs often. But now that I know how to do it, it's not that difficult.

 

Enabling Debug Logging

Oh, speaking of debugging, console.logs don't show up in the procedure debugger by default. You have to turn on debugLogging for EACH procedure template. I've touched on this in a couple videos. Here's a quick explanation:

  1. Go to the API docs for the Sugar Integrate Platform (top right of entire screen) - not the API docs for any particular Adapter or Procedure
  2. Choose Procedures from submenu on left
  3. Choose PATCH endpoint
  4. Click TRY IT OUT button
  5. Enter the procedure ID
  6. Use this as the body: {"debugLoggingEnabled": true}
  7. Execute

 

Now you will see more verbose logging in the Procedure debugger for this instance. Hope that helps!

Attachments

Outcomes