Tuesday, May 16, 2017

Automatic package license assignment to Users

We have been asked by one of our customer at SharinPix if it was possible to assign Licenses to newly created user automatically and removing the license on user upon deactivation. The solution that I was asked to do was a trigger. But this solution requires coding which will require a test class, changeset for deployment, giving user permission to execute the trigger which it quite a lot to do. In case something is wrong with the trigger, we will be required to modify on the customer sandbox and deploying again, which is kind of tedious. 

But recently I did a module on Trailhead about Process Automation which explain how to achieve complex logic without having the need to write a single piece of Apex Code. It is only by configuring Flows and Process Builders. 

Here is the solution for the above requirement

The following objects will be used to automate assigning user license: 

Below are the steps to create a Flow and Process Builder.

Flow


1. Go to Setup, Search Flows from the Quick Find textbox
2. Click on New Flow, the Flow Designer will open. 
On the left sidebar, click on the tab Resources. Create two variables var_UserId of type text and var_isNew which will store the package license id and the user id. See screenshot below. 

Variable User Id

Variable IsNew
Create a third variable of type SObject variable. Name is SharinPixLicense and Object Type is PackageLicense.

SharinPix License sobject variable

3. We now need to retrieve the Id of the license for the SharinPix package. Drag and drop a Record Lookup element to the canvas. Enter a name and insert the following properties: 
  • Look up: PackageLicense
  • Field: NamespacePrefix, Operator: 'equals', Value: 'sharinpix' (The namespace prefix of the package: Setup | Build | Installed Packages)
  • Variable assignment 
    • Field: Id, Variable: '{!SharinPixLicense.Id}'
    • Field: AllowedLicenses, Variable: '{!SharinPixLicense.AllowedLicenses}'
    • Field: UsedLicenses, Variable: '{!SharinPixLicense.UsedLicenses}'





4. Drag and drop the Record Create element to the canvas. Enter a name and insert the following properties: 
  • Create: UserPackageLicense
  • Field: PackageLicenseId, Value: {!SharinPixLicense.Id}
  • Field: UserId, Value: {!var_UserId} 
Assign license to user


Now that we have the create record, we now need to have the delete license equivalent

5. Drag and drop the Record Delete element. Enter a name and the following properties
  • Delete: UserPackageLicense
  • Field: UserId, Value: {!var_UserId} 
  • Field: PackageLicenseId, Value: {!SharinPixLicense.Id}
Remove license from user

6. Let's create the logic and assemble all the component. 
Drag and drop the Decision element on the canvas. Enter a name and create two outcomes
  • Outcome 1 
    • Name: Created
    • Resource: {!var_isNew}, Operator: equals, Value: {!$GlobalConstant.True}
    • Resource: {!SharinPixLicense.UsedLicenses}, Operator: less than, Value: {!SharinPixLicense.AllowedLicenses}
Note: I have added the condition that Used licenses should be less than Allowed Licenses in order for the flow not to crash if all licenses have been used. 
  • Outcome 2
    • Name: Deleted
    • Resource: {!var_isNew}, Operator: equals, Value: {!$GlobalConstant.False}



7. Link the component same as in the image below and set the Record Lookup as the start of the flow (click on the green arrow icon)
Wrap everything

8. Save your Flow with a name, select type Autolaunched Flow and activate it once done. 
Save flow

Process Builder


Now that our flow is completed. We need to trigger the flow and we will use a process builder to do that.

1. Go to Setup | App Setup | Create | Workflows & Approvals | Process Builder
Click on the New button, Enter the Process Name and API Name and click on Save button.
New Process

2. We need to trigger the process on a specific Object. For this, click on Add Object and in the properties, select User and select when a record is created or edited. Click on Save.
Entry criteria

3. Next step is to add the Process Criterias. In our case, we will have two criteria. One will cater for new user and the other one for user which has been deactivated.
  • Let's create the first one. Click on Add Criteria
    • Criteria Name: User is active
    • Select Formula evaluates to true
    • In the formula field, insert the following formula
      • ( ISNEW() && [User].IsActive ) || ( ISCHANGED([User].IsActive) &&  [User].IsActive )
Save the criteria
New user active or update a user to active

  • Let's create the first one. Click on Add Criteria
    • Criteria Name: User is not active
    • Select Formula evaluates to true
    • In the formula field, insert the following formula
      • ( ISCHANGED([User].IsActive) && NOT( [User].IsActive ))
Save the criteria 


4. Now we add the actions for each criteria. 
For the User is active criteria. In the Intermediate Action section, click Add Action with the following properties:
  • Action Type: Flows
  • Action name: Create License
  • Flow: Automatic Package License assignment (Yours may be under a different name)
  • Set Flow Variables
    • Flow Variable: var_UserId, Type: Reference, Value: [User].Id
    • Flow Variable: var_isNew, Type: Boolean, Value: True

5. Let's repeat step 4 for the User is not active criteria. Change the Action Name to Delete License and set the flow variable var_isNew to False.

Your process builder should look like below. 
Finally, Click on Activate to enable it. 



Every new user shall be affected a License to the package and user being deactivated will have their license removed. 

Do not hesitate to contact me in case something is missing or needs improvements. 

Cheers :)

Monday, May 15, 2017

Upload attachment on Salesforce without having heap size limit exceeded

Have you ever had a form for uploading attachments on your Visualforce page but had to restrict it to file size limit so as not to exceed Salesforce heap size governor limit?

Well this limit can be override to a maximum of 25 megabytes instead of around 3 megabytes using apex controller. It also support multiple upload at once.

Here is how I have achieved it.
Integrate the following code to your visualforce page. Basically to just need to replace the variable 'PARENT_ID' and maybe change the listener of the 'input' if yours have a different selector. 


Document of size 9 mb has been uploaded.


Attachment being created on the Contact record.


Hope it helps. Cheers!!
``` html
<apex:page >
    <apex:includeScript value="https://code.jquery.com/jquery-2.2.4.js"/>
    <script>
    jQuery(document).ready(function($) {
        $('input').on('change', function(e){
            for (var i = 0; i < this.files.length; i++) {
                uploading += 1;
                upload_file(this.files[i], PARENT_ID, function(err, res){ //pass the parentId here
                    if (uploading === uploaded){
                        console.log('uploaded'); //your operation once finish
                    }
                })
            }
        });
        var uploading = 0;
        var uploaded = 0;
        var upload_file = function(file, parentId, callback) {
            filetoBase64(file, function(err, content){
                var attachment_object = {
                    parentId: parentId,
                    Body: content,
                    Name: file.name,
                    ContentType: file.type
                };
                $.ajax({
                    url: '/services/data/v38.0/sobjects/Attachment',
                    data: JSON.stringify(attachment_object),
                    type: 'POST',
                    processData: false,
                    contentType: false,
                    headers: {'Authorization': 'Bearer {!$Api.Session_ID}', 'Content-Type': 'application/json'},
                    xhr: function(){
                        var xhr = new window.XMLHttpRequest();
                        //Upload progress
                        xhr.upload.addEventListener("progress", function(evt){
                            if (evt.lengthComputable) {
                                var percentComplete = evt.loaded / evt.total;
                                //Do something with upload progress
                            }
                        }, false);
                        return xhr;
                    },
                    success: function(response) {
                        uploaded += 1;
                        console.log(response); // the id of the attachment
                        callback(null, true)
                    }
                });
            })
        }
     
        var filetoBase64 = function(file, callback){
            var reader = new FileReader();
            reader.onload = function() {
                var fileContents = reader.result;
                var base64Mark = 'base64,';
                var dataStart = fileContents.indexOf(base64Mark) + base64Mark.length;
                fileContents = fileContents.substring(dataStart);
                callback(null, fileContents);
            }
            reader.readAsDataURL(file);
        }
    });
    </script>
    <input type="file" multiple="multiple"/>
</apex:page>
```