Saturday, 17 October 2020

Automation with Platform Events


Introduction

During the Trailblazers Innovate live session on 13th October, Wade Wegner made a comment about modern development being event driven rather than trigger driven, which is an approach I've been following for a while now, as in my view it maximises flexibility. Most of us are experiencing event driven development on the client side as we are working more and more with JavaScript, where user interactions generate events. It also applies server side, where platform events can be used to drive automation.

(As an aside, if you google trigger driven development with Salesforce, one of the top hits is an April Fool's Day post of mine about moving all of your code into triggers and faking record changes to make it run. Don't do that!)

Trigger Driven

The trigger approach (and I'm using this term to cover pro/low/no code mechanisms including Apex triggers, process builders, flows, workflow rules) typically means that you (via the Salesforce platform) monitor records and react to specific changes involving those records. 

An example of this, loosely based on some of the code behind my toolbox site where I keep details of really useful sites, tools, documents etc, would be to notify me if the number of clicks on an Entry goes up.  I can add automation that fires when an Entry record is updated and the clicks field is updated, and sends me the an email to that effect. This works a treat and I'd imagine there are about a billion automations similar to this out in the wild.

The downside to this is the automation is tightly coupled to the data model. If I decided for that I wanted to move the dynamic information out of the Entry and into another object, let's call it EntryInteraction, my automation has to reflect the change to the data model. I need something that monitors EntryInteraction records and emails me when the clicks field on that changes. 

Event Driven

In the event approach, rather than monitor records, my automation subscribes to a platform event that is fired when the clicks are updated. It neither knows nor cares where the clicks are stored - it could be off platform - it just knows that when it receives this event, the clicks changed somewhere, so it should send the email. The code that updates the clicks fires this platform event, and if the location of the clicks changes, that code changes. But regardless of what the code does, the event that it fires stays the same.

The automation is now decoupled from the data model and knows nothing of the internals of how the application works. As the application author, I don't need to worry about dependencies on automation, and I can change my data model as I want to reflect my changing needs. A much better situation, I'm sure you will agree.

Resources

Sunday, 11 October 2020

Salesforce CLI Scanner Custom XPath Rules - Part 2


Introduction

In Part 1 of this post I showed how to create an XPath expression that looks for SOQL queries outside of test method or accessor classes via the PMD designer. This in itself was good fun, and there's a definite rush when you finally come up with the appropriate runes, but is of limited value. In this post I'll embed the expression in a custom rule and add it to the Salesforce CLI Scanner.

Rule

A rule needs to be inside a ruleset XML file. The PMD site has excellent documentation on this, so I won't reproduce it here for the sake of padding my post out.

A couple of key aspects of my rule is that it is an XPath expression to evaluate against Apex coded, so I need to add that detail to the definition so that the appropriate class can be instantiated to handle it:

<rule name="SoqlOnlyInTestAndAccessorClasses" 
      language="apex" 
      message="SOQL queries can only appear in test methods and accessor classes"
      class="net.sourceforge.pmd.lang.apex.rule.ApexXPathRule">

and the XPath expression itself appears in the rule properties, along with the XPath version:

<properties>
  <property name="version" value="2.0"/>
  <property name="xpath">
    <value>
      <![CDATA[
//UserClass[not(ends-with(@Image, 'Accessor'))]/Method/ModifierNode[@Test=false()]/..//
(SoqlExpression | MethodCallExpression[lower-case(@FullMethodName)='database.query'])
]]>
    </value>
  </property>
</properties>

Packaging the Rule

The ruleset xml needs to be packaged as a JAR file (Java ARchive) using the tool of the same name. If I'd written a custom Java rule, I'd need to compile the class and include that in the JAR, but as I'm relying on a pre-existing class (net.sourceforge.pmd.lang.apex.rule.ApexXPathRule) I can just package the ruleset xml file.

Per the official Authoring Custom Rules documentation, my ruleset file lives at:

    ./category/apex/bobbuzzard.xml

so to create my JAR I execute the following command, which gives me plenty of information about what is being added:

$ jar -cvf bobbuzzard.jar ./category

added manifest
adding: category/(in = 0) (out= 0)(stored 0%)
adding: category/apex/(in = 0) (out= 0)(stored 0%)
adding: category/apex/bobbuzzard.xml(in = 1105) (out= 545)(deflated 50%)

Installing the Rule

Once I've packaged the rule, I can install it using the scanner:rule:add Salesforce CLI command, specifying the language as Apex and the JAR I just created:

$ sfdx scanner:rule:add -l apex -p bobbuzzard.jar 
Successfully added rules for apex.
1 Path(s) added: /Users/kbowden/PMDRULE/bobbuzzard.jar

Per the official docs, I list the commands to ensure it went in correctly (when I was figuring all this out but didn't have my ruleset.xml file set up right, the command would succeed but the rule wouldn't be added, so verifying via the list command is definitely a step worth taking). So that I don't have to wade through a tone of output, I pipe it through grep to exclude any lines that don't contain the characters Buzzard :

$ sfdx scanner:rule:list | grep Buzzard
SoqlOnlyInTestAndAccessorClasses          apex         Bob Buzzard     pmd

so all is well - my rule is all present and correct.

Running the Rule

All that is left is to run my rule against some sample Apex classes. The classes, and the ruleset and JAR re available at the Github repository.

I have:

  • ScannerExample.cls - this has a SOQL expression and a Database.query method call, so I expect it to be flagged twice.
  • ScannerExampleAccessor.cls - as above, but as it's a class name ending in Accessor I don't expect it to be flagged.
  • ScannerExampleTest.cls - a test class with one SOQL expression in a test method and one in a regular method. I expect it to be flagged once regarding the regular method
When running the scanner, I set the category to "Bob Buzzard" as I only want the code evaluated against my rule:
$ sfdx scanner:run --target './**/*.cls' -c "Bob Buzzard"
LOCATION                                                  DESCRIPTION                                          CATEGORY     U R L
────────────────────────────────────────────────────────  ───────────────────────────────────────────────────  ───────────  ─────
force-app/main/default/classes/ScannerExample.cls:5         SOQL queries can only appear in test methods and   Bob Buzzard
                                                            accessor classes
force-app/main/default/classes/ScannerExample.cls:10        SOQL queries can only appear in test methods and   Bob Buzzard
                                                            accessor classes
force-app/main/default/classes/ScannerExampleTest.cls:19    SOQL queries can only appear in test methods and   Bob Buzzard
                                                            accessor classes
and there we have it - the lines that break my rule have been identified.

Resources

Saturday, 10 October 2020

Salesforce CLI Scanner Custom XPath Rules - Part 1

Introduction

Earlier this week (6th October 2020) a tweet popped up in my feed about the Salesforce CLI Scanner, which obviously caught my attention as I seem to have some kind of tunnel vision on the CLI and plug-ins right now. I'd seen something about this before, but at the time it felt like it was aimed more at ISVs to help them pass the security review. This wasn't something I had any immediate need for, so it went on my never ending mental list of things to look at when I have time. 

The tweet linked to a Salesforce Developers Blog post and quick skim read of that suggested there was plenty more to it, so I set aside some time to read it properly and take a look at the code, which was well worth the investment. The Plug-in docs cover installation and running, so I won't go into them here.

Static Code Analysis

Simply put, a static code code analysis tool examines source code before the code is run, evaluating it against a set of rules. Assuming you've crafted your rules correctly, it will find every occurrence of code that breaks a rule. Essentially a code review carried out by a Terminator:

    "It can’t be bargained with. It can’t be reasoned with. It doesn’t feel pity, or remorse, or fear."

The CLI Scanner combines a bunch of static code analysis tools into a single plug-in. The power of the CLI is that this can be installed in a single command using the CLI itself. As the CLI is pretty ubiquitous, it's straightforward for most of us to install the scanner and start using it immediately. 

Apex

While the scanner supports other languages, I'm going to stick to Apex for this post, as it's where most of my code lives right now.

The scanner evaluates Apex using the Apex rules for PMD originally created by Robert Sösemann, part of the open-source PMD source code analyser. I'd looked at PMD several years ago and got the basics working, but things weren't quite as slick back then so I didn't proceed with it. I was also under the impression that I had to write any custom rules in Java, which I wasn't particularly interested in at the time.

Once I had the scanner working I decided to check out the custom rules again, as I was definitely going to need some to apply the BrightGen house rules. The documentation again suggests that Java is the way to go, so I went to the PMD site to learn more about it. Reading through the documentation I was delighted to stumble on the following:

XML rule definition

New rules must be declared in a ruleset before they’re referenced. This is the case for both XPath and Java rules. To do this, the rule element is used, but instead of mentioning the ref attribute, it mentions the class attribute, with the implementation class of your rule.

  • For Java rules: this is the class extending AbstractRule (transitively)
  • For XPath rules: this is net.sourceforge.pmd.lang.rule.XPathRule


My old friend XPath - the mechanism I've used many times in the past with Selenium automated testing. As long ago as March 2019 I was telling people to learn about this, as evidenced by Don Robin's tweet from my London's Calling session:


It now felt like I had a fighting chance of coming up with a custom rule in fairly short order, although obviously in PMD I'm not working with HTML. Instead the source code is parsed into an Abstract Syntax Tree, which I can navigate in a similar fashion.

One of the major helpers for Selenium is being able to run XPath expressions in the Chrome inspector and gradually build up a complex rule from simple steps. Reading more on the PMD documentation I was delighted to find the designer - a GUI where I could write some Apex code, execute an XPath expression against that code and see the matches live. It also has an excellent interactive reference where I can click on one of the tree elements and see its properties.  Well worth spending some time with:


Sample Rule

My sample rule was to check that SOQL statements are only present inside test methods or accessor classes, where accessor classes have the naming convention <item>Accessor.

The rule is somewhat lengthy - here it is in it's entirety before I break it down into sections:

//UserClass[not(ends-with(@Image, 'Accessor'))]/Method/ModifierNode[@Test=false()]/..//(SoqlExpression | MethodCallExpression[lower-case(@FullMethodName)='database.query'])

The first part checks that the class name doesn't end with Accessor - if it is, I am in an accessor class and we can stop now:

//UserClass[not(ends-with(@Image, 'Accessor'))]

if I'm not in an accessor, I look for any method that isn't a test method:

/Method/ModifierNode[@Test=false()]

The @Test property is part of the magic of PMD - the method modifier tree element knows whether a method is a test or not, so I don't have to worry about annotations or keywords.

I then back up from the ModifierNode to the method and consider all the child nodes of all non-test methods:

/..//

Then comes the meat of the rule - I look for the union of nodes that are SOQL expressions or have a database.query method call - note that I convert the FullMethodName property to lower case so that I don't have to care about capitalisation:

(SoqlExpression | MethodCallExpression[lower-case(@FullMethodName)='database.query'])

Executing this rule against an Apex class snippet:

class MyClass
{
   void SoqlQuery()
   {
       List<Contact> contacts=[select id from Contact];
   }

   void SoqlQuery2()
   {
        List<Contact> contacts=database.query('select id from Contact');
   }
}

matched as expected against the SOQL query on line 5 and the Database.query method call on line 10:


That's as far as I'm going for part 1 - in part 2 I'll look at how to create a ruleset for this rule and install it into the CLI Scanner. You won't have to wait too long as I'm hoping to write it in the next day or so while it's fresh in my mind.

Resources


Saturday, 3 October 2020

Working remotely (and locally)

Work has changed


Work is no longer somewhere you go - it’s something you do

It's something you do from pretty much anywhere, if you can and your team/employer can manage the timezones. The COVID-19 outbreak has compressed 5-10 years worth of gradual working practice changes into a couple of quarters. It’s been 6 months for us in the UK and bar, a short government push to get people back to the office, there’s been very little sign of a return to how things were. The last round of advice suggested we are looking at another 6 months, which feels like a number chosen because it’s the biggest we will quietly accept. 

It probably won't change back


If this is the shape of things to come, rather than a one off pandemic, it would be a brave company that maintains a huge office portfolio for on-again off-again occupation. It’s like the world’s worst Hokey Cokey at the moment – right team in, right team out, left team in, left team out, but with no idea of when you’ll be able to put your whole team back in for any length of time.

Working remotely affects other workers


The lack of commuters to cities is also having an impact on the service industry – coffee, sandwiches, food trucks, restaurants and bars. I used to work out of our London office one day a week and typically spent £10–20 each time on sundries (and maybe a pint after work). I know many who spent this amount every day! I haven’t been for 6 months now and that is not looking likely to change any time soon. 

That money won’t entirely go away as people continue to work from home, although it seems like people are still being cautious with spending, but a fair bit will likely end up being spent locally. This isn’t a bad thing – instead of someone making a 70 minute commute to serve me a coffee after my 70 minute commute, I’ll walk to my local café and be served by someone who also walked there. (Or I would if there were any cafés that I could walk to in under an hour without taking my life in my hands, but you get the idea). Of course there’s some irony that in the great enabler of remote working, the coffee shop, the last thing they now want is people working there all day. 

Many of the local outlets are suffering too, but for those that can survive, the changes to our habits forced on us by the pandemic might just give them a brighter future. And the cities will have to focus on the people that live there, rather than those that are transported in and out each day. 

Monday, 31 August 2020

Now What for Dreamforce?



The cancellation of the in-person 2020 Dreamforce conference brought my 10 year attendance streak to a shuddering halt. I'll shortly be taking my (late) summer holiday, and it will be the first one I can remember where I'm not building applications, putting together presentations for one or more sessions, or joining conference calls to cover how round table events will be run. While it will be nice to have that time back, I'll very much miss the trip to San Francisco and the chance to catch up with a bunch of people that I rarely see in person.

We're now notionally a couple of months out from the digital version of Dreamforce, which has already been put in doubt by comments made by Marc Benioff to The Information. For what it's worth I don't think a like for like replacement of Dreamforce will take place online, nor would it make a lot of sense to do that.

Duration

Dreamforce is a 4 day event, often bookended by additional summits (think CTA) or training events (get certified for half price!). For those outside of North America it typically involves a week plus out of the office - this aspect is key - we've all flown over with not much more to do but attend the conference and network. 

For a virtual event, nobody is taking a week off work to sit in front of their screen watching presentations. The best that can be hoped for is that the audience watch some of the sessions live, fitting in around their real work. 

Location

Dreamforce takes place in San Francisco, which is a popular destination in its own right. A virtual event takes place in your own home (or possibly in an office now that some have reopened) which doesn't have the same cachet. 

Attendees are also going to be in their own timezone, rather than struggling to adjust to PST. They aren't likely to stay up until 2am to see the latest report builder enhancements, but by the same token they won't be jet-lagged and should be able to stay awake.

Networking

This is one of the biggest attractions for me - the chance to meet with a whole bunch of people from product managers, to answer burning questions, to fellow developers from the community, to find out what cool things others are working on. Hang around the Trailhead forest for a few hours and you'll be amazed how many people you can tick off your list. This just doesn't happen at virtual events - you can fire off questions via videoconference chat channels, but the one to one connection simply won't be an option at the scale you can achieve in person.

I'd also include things like parties, the Gala and after parties in the networking column, and clearly none of that is going to happen virtually. You might see some virtual happy hour concept where a group of people sip drinks and try not to talk over each other on a zoom call, but it won't be the same.

Expo

I've attended a bunch of virtual events this year, sometimes because I'm interested in the topic and sometimes just to see the approach. In my opinion, one area that consistently struggles is the expo. People dialling in from home to watch a session on a specific topic typically sign off afterwards rather than browsing a virtual expo. If you are interested in a product and know Dreamforce is coming up, you might wait to talk to the vendor in person. When you can't do that, you'll sign up for a virtual demo at the first possible opportunity rather than wait for a day when you are already going to be stuck in front of a screen for hours. There is also the swag, or lack thereof.. From personal experience, I can tell you that a surprising number of people at any event expo just want free swag, and they aren't joining your virtual expo demo without some incentive. 

This feels to me like one area that will have to be completely rethought in these continuing Covid times - simply taking the expo concept online isn't cutting it. Organisers will still want sponsor dollars, but they'll have to find a new mechanism. Some kind of session sponsorship is my guess, which showcases the partner's offering alongside the main product, in this case Salesforce. 

Community Content

This is the other area that will need to be rethought. If it will be a struggle to get people to join sessions involving product management, I really can't see Salesforce giving up space to speakers from the community. They won't have loads of breakout rooms to fill, so I can see them keeping the vast majority of virtual space to themselves. Maybe this will push the community content completely to community events like London's Calling or the many flavours of Dreamin' 

Diluting the Brand

Dreamforce is the Salesforce event of the year. It's something that everyone wants to go to and always sells out. By running something less than this online, the name is devalued. I'm sure the numbers would still be pretty good, because of the sheer reach that Salesforce has, but it would always be compared to the in person event and found wanting.

Now What?

Instead of trying to recreate Dreamforce in a virtual setting, do something different. It could still leverage the Dreamforce name, but not try to be a like for like replacement. Rather than squashing a ton of content into 4 days that we won't have time to consume, spread it out - maybe as a half-day event around a theme every month or six weeks - Dreamforce Bi-Fortnightly has a nice ring, as does Dreamforce Semi-Quarterly. The sessions need to be useful though - if they aren't the audience will vote with their feet, and they won't have the ticket cost to guilt them into staying.

The one exception to the above is the keynote. This is something that should still happen as a one-off with attendant folderol. We need this see the performance versus the previous year, the focus for the next year and some key customer stories. Also, without the keynote I won't be able to hear about the new features that I won't make the pilot for and thus won't get access to for a couple of years, and I need that envy to keep my interest up. I'm not sure how well the multiple product keynotes would work - maybe a half-day keynotes event that starts with Marc Benioff and friends, and hands over to the the various clouds, or perhaps each Dreamforce Semi-Quarterly includes a keynote and has a major focus on the specific cloud. 


Saturday, 29 August 2020

Adding IP Ranges from a Salesforce CLI Plug-in


Introduction

I know, another week, another post about a CLI plug-in. I really didn't intend this to be the case, but I recently submitted a (second generation) managed package for security review and had to open up additional IP addresses to allow the security team access to a test org. I'm likely to submit more managed packages for review in the future, so I wanted to find a scriptable way of doing this. 

It turned out to be a little more interesting than I'd originally expected, for a couple of reasons.

It Requires the Metadata API


To add an IP range, you have to deploy security settings via the metadata API. I have done this before, to enable/disable parallel Apex unit tests, but this was a little different. If I create a security settings file with the new range(s) and deploy them, per the Metadata API Docs, all existing IP ranges will be turned off:

    To add an IP range, deploy all existing IP ranges, including the one you
    want to add. Otherwise, the existing IP ranges are replaced with the ones
    you deploy. 

Definitely not what I want!

It Requires a Retrieve and Deploy


In order to get the existing IP addresses, I have to carry out a metadata retrieve of the security settings from the Salesforce org, add the ranges, then deploy them. No problem here, I can simply use the retrieve method on the metadata class that I'm already using to deploy. Weirdly, unlike the deploy function, the retrieve function doesn't return a promise, instead it expected me to supply a callback. I couldn't face returning to callback hell after the heaven of async/await in my plug-in development, so I used the Node Util.Promisify function that turns it into a function that returns a promise. Very cool.

const asyncRetrieve = promisify(conn.metadata.retrieve);
const retrieveCheck = await asyncRetrieve.call(...);
The other interesting aspect is that I get the settings back in XML format, but I want a JavaScript object to manipulate, which I then need to turn back into XML to deploy.

To turn XML into JavaScript I use fast-xml-parser, as this has stood me in good stead with my Org Documentor. To get at the NetworkAccess element:

import { parse} from 'fast-xml-parser';

  ...

const settings = readFileSync(join(tmpDir, 'settings', 'Security.settings'), 'utf-8');
const md = parse(settings);

let networkAccess = md.SecuritySettings.networkAccess;

Once I've added my new ranges, I convert back to XML using xml2js:

import { Builder } from 'xml2js';

  ...

const builder = new Builder(
   {renderOpts:
      {pretty: true,
       indent: '    ',
       newline: '\n'},
    stringify: {
       attValue(str) {
           return str.replace(/&/g, '&amp;')
                     .replace(/"/g, '&quot;')
                     .replace(/'/g, '&apos;');
       }
    },
    xmldec: {
        version: '1.0', encoding: 'UTF-8'
    }
});

const xml = builder.buildObject(md);

The Plug-In


This is available as a new command on the bbsfdx plug-in - if you have it installed already, just run 

sfdx plugins: update 

to update to version 1.4

If you don't have it installed already (I won't lie, that hurts) you can run:

sfdx plugins:install bbsfdx
 
and to add one or more ranges: 

sfdx bb:iprange:add -r 192.168.2.1,192.168.2.4:192.168.2.255 -u <user>

The -r switch defines the range(s) - comma separate them. For a range, separate the start and end addresses with a colon. For a single address, just skip the colon and the end address.

Related Posts


Saturday, 22 August 2020

App Builder Page Aware Lightning Web Component

Introduction

This week I've been experimenting with decoupled Lightning Web Components in app builder pages, now that we have the Lightning Message Service to allow them to communicate with each other without being nested. 

A number of the components are intended for use in record home and app pages, which can present challenges, in my case around initialisation. Some of my components need to initialise by making a callout to the server, but if they are part of a record home page then I want to wait until the record id has been supplied. Ideally I want my component to know what type of page it is currently being displayed in and take appropriate action.

Inspecting the URL

One way to achieve this is to inspect the URL of the current page, but this is a pretty brittle approach as if Salesforce change the URL scheme then it stands a good chance of breaking. I could use the navigation mixin to generate URLs from page references and compare those with the current URL, but that seems a little clunky and adds a delay while I wait for the promises to resolve.

targetConfig Properties

The solution I found with the least impact was to use targetConfig stanzas in the component's js-meta.xml configuration file. From the docs, these allow you to:

Configure the component for different page types and define component properties. For example, a component could have different properties on a record home page than on the Salesforce Home page or on an app page.

It was this paragraph that gave me the clue - different properties depending on the page type!

You can define the same property across multiple page types, but define different default values depending on the specific page type. In my case, I define a pageType property and default to the type of page that I am targeting:

<targetConfigs>
    <targetConfig targets="lightning__RecordPage">
        <property label="pageType" name="pageType" type="String" default="record" required="true"/>
    </targetConfig>
    <targetConfig targets="lightning__AppPage">
        <property label="pageType" name="pageType" type="String" default="app" required="true"/>
    </targetConfig>
    <targetConfig targets="lightning__HomePage">
        <property label="pageType" name="pageType" type="String" default="home" required="true"/>
    </targetConfig>
</targetConfigs>

so for a record page, the pageType property is set as 'record' and so on.

In my component, I expose page type as a public property with getter and setter methods (you only need the @api decorator on one of the methods, and convention right now seems to be the getter) :

@api get pageType() {
    return this._pageType;
}

set pageType(value) {
    this._pageType=value;
    this.details+='I am in a(n) ' + this._pageType + ' page\n';
    switch (this._pageType) {
        case 'record' :
             this.details+='Initialisation will happen when the record id  is set (which may already have happened)\n';
             break;
            ;;
        case 'app' :
        case 'home' :
            this.details+='Initialising\n';
            break;
    }                
 }

and similar for the record id, so that I can take some action when that is set:

@api get recordId() {
    return this._recordId;
}

set recordId(value) {
    this._recordId=value;
    this.details+='I have received record id ' + this._recordId + ' - initialising\n';
}

then I can add the component to the record page for Accounts:



a custom app page:



and the home page for the sales standard application:

and in each case the component knows what type of page it has been added to. 

Of course this isn't foolproof - my Evil Co-Worker could edit the pages and change the values in the app builder, leading to all manner of hilarity as my components wait forlornly for the record id that never comes. I could probably extend my Org Documentor to process the flexipage metadata and check the values haven't been changed, but in reality this is fairly low impact sabotage and probably better that the Evil Co-Worker focuses on this rather than something more damaging.

Show Me The Code!

You can find the code at the Github repo.

Related Posts