wissel.net

Usability - Productivity - Business - The web - Singapore & Twins

By Date: November 2018

Supercharge SFDX with Ui-licious


You drank the Coolaid and you are all in and really love your Scratch Orgs. Just a few things drive you crazy.

When the API doesn't expose a setting

Your application is querying the OutOfOffice objects. Unfortunately they only exist when the Chatter settings have enabled them. To my best knowledge there is no API or config setting to achieve that from the command line. So automated testing in a scratch org won't work.

Ui-licious to the rescue

Ui-licious is a UI testing service that uses JavaScript as its scripting language. This makes an easy read and edit. A script that can set the missing OOO setting is short and simple (if you agree iFrames are easy to handle). You can (almost) read it out aloud:

// URL Provided by data object from the command line
I.goTo(DATA.result.url);
I.must.see('Chatter Settings');
// To ensure we work on the iFrame we get the id
let iframeId = UI.execute("return document.getElementsByTagName('iframe')[0].getAttribute('id')");
// The iFrame loads slowly
I.wait(10);
UI.context('#' + iframeId, () => {
  I.see(
    'Chatter is a corporate network that lets your users work together, talk to each other, and share information, all in real time.'
  );
  I.click('Edit');
  I.must.see('Save');
  I.select('Users can set Out of Office messages');
  I.click('Save');
});

The interesting piece in the above is DATA.result.url which is an JSON object that needs to be handed over to the Ui-licious. A small command script in tandem with SFDX does the trick. The beauty here: we directly can reuse the output of an sfdx command as input for the uilicious-cli.

sfdx force:org:open -r -p lightning/setup/CollaborationSettings/home --json > local.json
uilicious-cli  run "ProjectName" "ScriptName" --dataFile local.json -u uiliciousUser -p uiliciousPassword

It probably would be part of a larger CI script, but you get the idea! You might not store the output in a file, but use bash to capture it in memory and use --dataObject instead. Full details are in the documentation.

As usual YMMV


Posted by on 27 November 2018 | Comments (1) | categories: Lightning Salesforce

Cleaning up an APEX codebase with PMD


You inherit a large code base, it is perfect, a work of beauty. Then you wake up to the ping of your PMD run completion and find multiple thousands of rule violation (and you haven't even started to assert test assertions). Here is how to fix that.

Divide and conquer

Boiling the ocean and fix all issues in one go is hardly an option. So you need to strategize. PMD gives you a hint: every rule has a priority property, where you can specify how important this rule is. The higher the number, the less important the rule. You can run PMD using the min -[somenumber] parameter which will ignore rules with a higher value.

Next step is to decide what rules constitute your priority 1 and 2 buckets. I strongly recommend to make them mandatory fixes before the next deployment, so pick the rules carefully. The candidates I suggest are around performance and security:

Priority 1

  • ApexXSSFromURLParam
  • ApexSOQLInjection
  • ApexOpenRedirect
  • ApexInsecureEndpoint
  • AvoidDmlStatementsInLoops
  • AvoidSoqlInLoops

Priority 2

  • ApexCRUDViolation
  • ApexSharingViolations

Thereafter you decide on priority 3 onwards. Strong candidates for level 3 are all the rules that ensure code is maintainable and avoid errors, like deep nesting or cyclomatic complexity (a fancy word for "messy code")


Read more

Posted by on 23 November 2018 | Comments (1) | categories: Apex PMD Salesforce

Backing up Salesforce Meta data


Clicks not code makes the Salesforce Admin a super hero, but might send jitters through the compliance and change management team's combined spines. How can you track all of these changes?

Tracking changes

Salesforce does record, when configured, changes in setup for 180 days. The format is a log that isn't actionable (e.g. rollback). An alternative to the build in function is BlueCanvas that stores meta data automatically into a git repository. It's part of their developer focused solution that also handles the deployment and rollback of source code

Downloading meta data

When you are not ready (or haven't allocated a budget yet) to fully automate this, there's a do-it-yourself alternative. A few steps:

  • Make sure you have Java8 installed
  • Deploy the Salesforce Ant Migration tool
  • Download and deploy into your path PackageBuilder.jar developer by my colleague Kim Galant
  • create a build.properties file (see below)
  • create a build.xml file (see below)
  • run the fetchSrc.sh shell command (see below)

PackageBuilder will, when called with no instructions read ALL meta data and create one or more package.xml files. Once created you can use ANT to retrieve the data

build.properties

# Parameters
apiversion=44.0
[email protected]
sf.password=supersecret
sf.serverurl=https://login.salesforce.com
sf.maxPoll = 200

build.xml

<project name="Source backup for Salesforce" default="test" basedir="." xmlns:sf="antlib:com.salesforce">

    <property file="build.properties"/>
    <property environment="env"/>

    <!-- Setting default value for username, password and session id properties to empty string
         so unset values are treated as empty. Without this, ant expressions such as ${sf.username}
         will be treated literally.
    -->
    <condition property="sf.username" value=""> <not> <isset property="sf.username"/> </not> </condition>
    <condition property="sf.password" value=""> <not> <isset property="sf.password"/> </not> </condition>
    <condition property="sf.sessionId" value=""> <not> <isset property="sf.sessionId"/> </not> </condition>

    <taskdef resource="com/salesforce/antlib.xml" uri="antlib:com.salesforce">
        <classpath>
            <pathelement location="ant-salesforce.jar" />
        </classpath>
    </taskdef>

    <!-- Retrieve an unpackaged set of metadata from your org -->
    <!-- The file unpackaged/package.xml lists what is to be retrieved -->
    <target name="retrieveSrc">
      <mkdir dir="src"/>
      <!-- Retrieve the contents into another directory -->
      <sf:retrieve username="${sf.username}" password="${sf.password}" sessionId="${sf.sessionId}" serverurl="${sf.serverurl}" maxPoll="${sf.maxPoll}" retrieveTarget="src" unpackaged="packages/package.xml" pollWaitMillis="30000" />
    </target>
</project>

fetchSrc.sh

#!/bin/bash
java -jar PackageBuilder.jar -o build.properties -d packages
ant retrieveSrc
DATE=`date '+%Y-%m-%d %H:%M:%S'`
git add --all
git commit -m "incoming changes $DATE"
git push origin master
echo "Done with retrieval"

As usual YMMV


Posted by on 08 November 2018 | Comments (0) | categories: Java Salesforce

Lightning Mini Forms


One of my favorite lightning features is the Lightning App Builder. It allows to tailor Salesforce for a given set of users, to show what they need, how they need it.

In the page editor the endless scrolling page, record details followed by related lists, we got to love, can be broken down into multiple tabs. For related lists, the OOTB controls already allow to just pick one. Placed multiple times on a page, the layout can fit a specific audience's precise need, avoiding information overload.

However there's no control to break down the record details. The OOTB record details control will faithfully reproduce the assigned page layout with all sections. Custom controls to the rescue!

Design options

There are two options to consider: where to pull design information from and how to render it. For the later you can consider lightning:recordEdit|ViewForm or lightning:recordForm. Both can be argued for.

The lightning:recordForm only needs the fields / layout options supplied and will take care of all rendering. However you are limited to one or two columns - just like page layouts.

The lightning:record[Edit|View]Form requires coding the fields, probably using an aura:iteration, but leaves you with the freedom of layout. Typically you would use a lightning:layoutItem and its size property to determine the number of columns (with 12 / size = number of columns).

To keep it simple I'll stick to the lightning:recordForm for now.

Next question: where to provision the list of fields from? When you want an universally usable mini form, you can't hardcode them, but provide them using a design property, so they can be provided in the page editor after you dragged the component onto the page.

Still you need to decide what attributes you provide:

  • List of field names
  • Name of a field set
  • Name of a section in a page layout

I'll start with the first one and relegate the other two approaches to future posts.

To make this work you will need a custom lightning component. Let's call it MiniForm. We will only need MiniForm.cmp, MiniFormController.js and MiniForm.design. No helper or Apex will be harmed

The component looks like this:

<aura:component implements="flexipage:availableForRecordHome,force:hasRecordId"
                access="global">
    <aura:attribute name="recordId"
                    type="Id"
                    default=""
                    description="Account Record Id" />
    <aura:attribute name="objectType"
                    type="String"
                    default="Account"
                    description="Sobject Name" />
    <aura:attribute name="fieldList"
                    type="String"
                    description="Fields to show" />
    <aura:attribute name="fieldListArray"
                    type="String[]"
                    default="Id,Name"
                    access="private" />
    <aura:attribute name="columns"
                    type="String"
                    default="1"
                    description="Column count one or two" />
    <aura:attribute name="layoutType"
                    type="String"
                    default="Full" />
    <aura:attribute name="formReady"
                    type="Boolean"
                    default="false"
                    access="private" />
    <aura:handler name="init"
                  action="{!c.doInit}"
                  value="{!this}" />
    <lightning:card>
     <aura:if isTrue="{!v.formReady}">
        <div class="slds-p-horizontal_small">
            <lightning:recordForm
              aura:id="MiniForm"
              recordId="{!v.recordId}"
              objectApiName="{!v.objectType}"
              fields="{!v.fieldListArray}"
              columns="{!v.columns}"
              mode="view" />
        </div>
        </aura:if>
    </lightning:card>
</aura:component>

There are a few caveats, I'll highlight below

The design file will determine what is visible in the page designer. Currently that information is static, so the Awesome Admin needs to be extra careful not to mistype anything.

<design:component label="Mini Form">
    <design:attribute name="layoutType"
                      label="Fields from Layout"
                      description="Should the fields from the full or compact layout be used"
          default="Full"
                      datasource="Empty,Full,Compact"/>
    <design:attribute name="fieldList"
                      label="additional Fields"
                      description="Fields to be shown in addition to layout (if any)"
                      default="Name,Id"/>
    <design:attribute name="objectType"
                      label="Object"
                      description="Which object"
                      default="Account"/>
     <design:attribute name="columns"
                       label="1/2 columns"
                      description="one or two"
                      default="1"
                      datasource="1,2" />
</design:component>

The final piece it the JavaScript controller:

({
  doInit: function (component, event, helper) {
    const layoutType = component.get('v.layoutType');
    // Set the layout type if we have one
    if (layoutType === 'Compact' || layoutType === 'Full') {
      const miniForm = component.find('MiniForm');
      miniForm.set('v.layoutType', layoutType);
    }
    const fieldList = component.get('v.fieldList').split(',');
    component.set('v.fieldListArray', fieldList);
    component.set('v.formReady', true);
  }
});

You can now use the component on a page layout. In addition to specify the fields, it allows to use the predefined main or compact layout. You also can embed it into your own components. You then would need to specify the attributes you need:

<c:MiniForm recordId="{!v.recordId}" objectType="Contact" layoutType="none" />

This would show the Name and Id field in one column - based on the default settings.

Lessons learned

  • Always use defaults in the component. This makes it easier when embedding. Specifying defaults in the design properties isn't enough
  • lightning:recordForm is particular about the layoutType attribute. It can't be empty or anything else than Compact or Full. That's the reason why the attribute isn't in the markup but gets (eventually) added in the controller
  • The design definition doesn't allow for a String array. To mitigate that the onInit function splits the design provided String into an array. The array is private not to disturb that mechanism. You might consider adding a method that allows to change the values in code
  • Timing is everything. That's why the form is enclosed by an aura:if that will render only after the values have been set. While that doesn't seem a problem in an aura-less controller, it might bite you in heavy load pages, so better save than sorry

A variation of this approach: instead of using a design time property in the page editor, supply the field names through a lookup into a custom meta data record. This would allow to alter the fields shown based on some lookup logic. However you would need an Apex controller for that.

Next stop: instead of a field list supply a section name or a fieldset name

As usual YMMV


Posted by on 06 November 2018 | Comments (0) | categories: Lightning Salesforce