Saturday, 7 September 2019

Mentz - The Story Continues

Mentz - The Story Continues

Introduction

It’s been over three months since I launched Mentz on an unsuspecting Salesforce ecosystem, and the results have far exceeded my expectations. I’d have been quite happy with a couple of people attempting the challenges that I mentored myself, but it’s fair to say we are well past that. At the time of writing (September 2019) we have 90 mentees, 24 mentors and 45 solutions that have been mentored. The standard of mentoring is incredible - a lot of very smart people are putting a lot of effort into helping others in their development journey.

Release, Review, Repeat

  • Creating and maintaining the tooling around Mentz has been an interesting aspect for me, not least because of how wrong I’ve been about some of it. 
  • My original plan was to have two stages in a solution lifecycle - mentoring and publication. A mentee would iterate on their solution based on mentor feedback and when they were completely happy with it, publish it for the wider mentee community to see and comment on. This was pretty much entirely unsuccessful and just caused confusion about where to post solutions. We now have a single place where solutions are published which anyone can access.
  • Solutions were originally uploaded as chatter files until one of the Mentors asked me to turn on "Allow Inclusion of Code Snippets from UI” - now if the solution fits int the 10k chatter message limit it is uploaded as a snippet, which is a lot easier to respond to (thanks Adam Lasek).
  • The challenges typically involved a class with multiple methods to be built out. While I always created my own (unpublished) reference solutions, as I’d come up with the scenario it didn’t take me very long. When a few solutions stacked up and I mentored them on a weekend, it took me almost all day! So I created a couple of short challenges to see what kind of reception they would get.
  • I used to regularly post into the Mentor group to let everyone know if there were any solutions awaiting a response. This always lead to apologetic replies from the Mentors, which wasn’t what I was after at all - I just wanted to avoid them having to poll the org to see if there was anything they could help with. I replaced this with a lightning web component rendered in in the Mentors group home page that listed any unanswered solutions, which seems to have helped.

There have also been some other changes to try to make things easier/more interesting:

  • A Suggestion Box repository for mentees (or anyone really) to suggest challenges they’d like to take
  • The ability to lock a solution to a single mentor. The idea here is that a mentor claims a solution and is the only one (aside from the original author) that can respond to it. I haven’t turned this on yet as AFAIK we’ve only had one instance where two mentors were working on responses to the same solution at the same time.
  • Mentee and Mentor leaderboards - people seem to like these so I’ve added them to the group home pages. I’m at the top of the Mentor leaderboard, but only because I mop up any solutions that haven’t had a response after a few days. I don’t have to do this, but I do feel a sense of responsibility having enticed mentees to join.
  • The Mentz Salesforce CLI plugin now has a challenges topic that lists the available challenges (optionally including those already completed) and clones the repo for the user:
            $ sfdx mentz:publish --targetusername myOrg@example.com --all

              Select a challenge
              1) COLLECTION SIMPLE 1
              2) CONDITIONAL SIMPLE 1 (Completed)
              0) Quit

              Choose a challenge: 1
              Cloning repository = https://github.com/mentzbb/SimpleCollections1
              ...
             Done

What’s Next

As I wrote in my original Mentz blog post:

If things do take off, I don’t want to handle everything out of a single org myself, as that will limit scale. Instead I'll make the code available as a package so that others can host their own instance of Mentz, in their own org. We'll all use a common set of challenges, but the actual mentoring will be distributed.

With 90 mentees it feels like my first org is getting pretty close to as big as I want it to get, so it’s time to test the waters and see if anyone else wants to host Mentz. Here’s a few things to think about before you jump in:

  • This will be a developer edition setup by me that you then take over - this isn’t (only) because I’m a megalomaniac, but more because I want to go through the setup a few times to get it documented before leaving people to face it on their own. The Mentz code will be deployed as unpackaged code  - it might be packaged up in the future, but at present it won’t add much and just adds more work for me :) It does also allow me to keep an eye on the standard of mentoring, as it’s been stellar so far and I’d like to keep it up there.
  • There’s not very much housekeeping - mostly it’s emailing the Mentee/Mentor requestors and then executing a lightning action to convert the request to a user. I usually do half a dozen or so a week.
  • The challenges are the same for every Mentee, all that changes is the org they post solutions to and who mentors.
  • If you host a Mentz instance, be prepared to act as the Mentor of last resort - this doesn’t happen often, but I think it’s quite important to make sure that posts are getting answered. I usually allow about a week for the mentors to dive in and then start picking things up myself, usually over the weekend.
  • Lots of people will register an interest and never even login - remember that this is Mentz where we do what we want, so this is absolutely fine. Never try to make people do anything, although it’s okay to check from time to time to make sure they aren’t trying and struggling/failing.
  • You need to have some reach to attract Mentees/Mentors - I’d imagine it’s a bit dispiriting to announce this is happening and receive zero interest :)

If this sounds like the sort of thing you’d be interested in, fill in the short form at https://bobbuzz.me.uk/MentzHosting - it will almost certainly take me a week or two to do anything so don’t panic if you don’t hear back quickly.

Related Posts

 

 

Sunday, 1 September 2019

Parallel Apex Unit Tests and Salesforce CLI Plugins

Parallel Apex Unit Tests and Salesforce CLI Plugins

 

Introduction

In the Salesforce Winter 20 release notes was something I’ve been looking forward to for a few years - the ability to turn off parallel Apex unit test execution. By default parallel unit test are enabled (somewhat confusingly, by the fact that the Disable Parallel Apex Testing option is not checked):

 

and this typically results in a number of failures in my automated test runner package, as the tests can’t get exclusive access to write to the account others object tables.

This setting is one of the last items that I have to manually turn on when creating a scratch org, and that has been annoying me for a while. I could automate it via Selenium, but that feels like overkill, so I was very pleased to see the new Apex Settings metadata type. Among other features, this has the enableDisableParallelApexTesting field which allows me to check or uncheck the Disable Parallel Apex Testing checkbox, albeit via a metadata API deployment. The release notes also mentioned the to-be-deprecated OrgPreferenceSettings metadata object, and it turns out that this also has a mechanism for turning off parallel testing via the DisableParallelApexTesting setting, so there was no need for me to wait until Winter 20 as long as I could switch between the two mechanisms based on the API version the org is at.

Salesforce CLI Plugin

Regular readers of this blog will know that I’m a huge fan of the Salesforce Cli - I use it all the time and when I’m looking to do anything around developer tooling I always try to create a CLI plugin to host it. This was no different, although it presented a couple of challenges that I hadn’t taken on before:

  • Determining the API version that the org is at. If this is 46 or less (not sure how it would be possible to be on an earlier version of the API than Summer 19, but if Salesforce ever allow it I wanted to be covered) I need to deploy an OrgPreferences metadata type, 47 or higher I need to deploy an ApexSettings.
  • Metadata deployment from inside a plugin. 

After I scaffolded a new bbsfdx plugin and copied the commands/hello/org.ts example command to bb/test/parallel.ts, I set about solving them.

Determine the API Version

Whenever I’m doing anything new with a CLI plugin, my first port of call is the reference documentation for the Salesforce DX Core Library, and I wasn’t disappointed. I can find the API version for the org via the Connection.retrieveMaxAPIVersion function. Getting a Connection object is simple in a CLI plugin - just specify the requiresUsername property as true and a connection comes up with the rations via the org property supplied by the plugin. Getting the target API version is as simple as:

const conn = this.org.getConnection();
const apiVersion = await conn.retrieveMaxApiVersion();

So far so good.

Metadata Deployment

The simplest way to do this is to execute an existing Salesforce CLI deployment command, either force:source:deploy or force:mdapi:deploy, but I’m not a fan of this approach. Spawning a process to execute a Salesforce CLI command from within a CLI plugin seems clunky and inelegant, and it binds me to a command that I don’t control and which may be retired. I should be able replicate anything the standard commands do as I have access to the same underlying libraries.

This time the core library docs weren’t much help - there was a metadata property on the Connection object, but it didn’t have any detail, so time to look elsewhere. The Core library is built on Shinichi Tomita’s JSforce library, so I headed over to the docs for that. The API reference for the Metadata class was exactly what I was looking for, specifically the deploy method.

To deploy metadata, I need a zip file containing a manifest (package.xml) and the metadata files themselves in the directory structure mandated by the metadata API. In order to achieve this, I create a temporary directory and write the appropriate information depending on the API version (stored as a float value in the fltVersion variable):

let packageFile=join(targetDir, 'package.xml');
let packageContents='<?xml version="1.0" encoding="UTF-8"?>\n' + 
    '<Package xmlns="http://soap.sforce.com/2006/04/metadata">\n' +
    ' <types>\n' + 
    '   <name>Settings</name>\n';

let fltVersion=parseFloat(apiVersion);

if (fltVersion>46) {
  packageContents+='    <members>Apex</members>\n' + 
                   '  </types>\n' +
                   '  <version>47.0</version>\n' + 
                   '</Package>';
  let apex=join(settingsDir, 'Apex.settings');
  writeFileSync(apex, 
        '<?xml version="1.0" encoding="UTF-8"?>\n' + 
        '<ApexSettings xmlns="http://soap.sforce.com/2006/04/metadata">\n' +
        '  <enableDisableParallelApexTesting>' + attr + '</enableDisableParallelApexTesting>\n' +
        '</ApexSettings>\n'
          );
}
else {
  packageContents+='    <members>OrgPreference</members>\n' + 
                   '  </types>\n' +
                   '  <version>46.0</version>\n' + 
                   '</Package>';
  let orgPref=join(settingsDir, 'OrgPreference.settings');
  writeFileSync(orgPref, 
    '<?xml version="1.0" encoding="UTF-8"?>\n' + 
    '<OrgPreferenceSettings xmlns="http://soap.sforce.com/2006/04/metadata">\n' + 
    '  <preferences>\n' + 
    '     <settingName>DisableParallelApexTesting</settingName>\n' + 
    '     <settingValue>' + attr + '</settingValue>\n' + 
    '  </preferences>\n' + 
    '</OrgPreferenceSettings>\n'
  );
}

writeFileSync(packageFile, packageContents);

Now I have my directory structure, I need to zip it. Searching on npm for zip packages returns a lot of results, so how to choose? I bounced around sites like stack exchange to see what others were using and eventually settled on compressing for a couple of reasons. First, it supports other compression files than zip, and I might need that flexibility in the future, and second it has a really simple API and is already promisified. After installing it into my plugin node modules and importing it into the parallel.ts file, generating a zip file is a couple of lines:

const zipFile=join(tmpDir, 'pkg.zip');
await compressing.zip.compressDir(targetDir, zipFile);

Getting there. Back to the docs for the metadata deploy function, it wants a zip stream rather than a filename, so I create one of those and add the code to deploy the metadata;

let zipStream=createReadStream(zipFile);
let result=await conn.metadata.deploy(zipStream, {});

The deploy function returns information about the deployment job, so I then enter a loop to poll for the status until it is finished:

let done=false;

let deployResult:DeployResult;
while (!done) {
  deployResult=await conn.metadata.checkDeployStatus(result.id);
  done=deployResult.done;
  if (!done) {
    this.ux.log(deployResult.status + messages.getMessage('sleeping'));
    await new Promise(sleep => setTimeout(sleep, 5000));
  }
}

and there it is - a plugin to enable or disable parallel Apex unit testing in under a couple of hundred lines of Typescript (and obviously a ton of existing node modules that allow me to stand on the shoulders of giants). 

Where’s the Code?

The full code for the plugin can be found in the Github repository at : https://github.com/keirbowden/bbsfdx 

The plugin itself is published on npm at : https://www.npmjs.com/package/bbsfdx - it has been tested on MacOS.

To install the plugin into your version of sfdx, execute:

sfdx plugins:install bbsfdx

Related Posts

Monday, 26 August 2019

Salesforce Unlocked Packages and Supplementary Code

Salesforce Unlocked Packages and Supplementary Code

Introduction

Unlocked packages have been Generally Available since the Winter 19 release, so almost a year at the time of writing (August 2019). Unlike 1.0 packaging, source of truth is the version controlled source, rather than the contents of a packaging org. In fact the only time you need anything other than a scratch org is if you are namespacing your package, in which case you’ll have to create a developer edition to annex the namespace. In packaging 2.0 the contents are determined by the code on the filesystem where you run the packaging commands and the Salesforce DX project configuration file (sfdx-project.json).

I’m not going to go into the detail of creating and publishing packages - the Trailhead module does a pretty good job of introducing the concepts, and if you want a deep dive then check out Fabien Taillon’s blog post about how Texei moved to unlocked packages (they were called Developer Controlled Packages back then, but the principles apply even if the commands have changed slightly.

SalesforceDX Project File

To generate an unlocked package, you need to define where the code lives in your SalesforceDX project file. Here’s the key part of the file for an example package:

"packageDirectories": [
        {
            "path": "force-app",
            "default": true,
            "package": "bbexample",
            "versionName": "Version 1.0",
            "versionNumber": "1.0.0.NEXT"
        }
    ]

When I run the Salesforce CLI commands to generate a package version, everything under the

force-app

directory will be added to the package and uploaded. And this to my mind is a key difference between 1.0 and 2.0 packaging. In 1.0 I create the package from the packaging org and I get to choose which components get added. In 2.0 not so much - everything that I have under the packaging directory goes in.

Supplementary Code

When I create a package there is typically a bunch of supplementary code that helps the development and test process, but I don’t want going into the package. If the package contains Lightning Web Components for example, I might have a flexipage with a  number of instances of the component showing various aspects of the functionality, allowing me to see at a glance that everything is working correctly. If the package works against generic sobjects, I might have some sample sobjects to execute tests against that I don’t want ending up in the subscriber org. I want these items available in the scratch o rgs where I’m developiing the package, but I don’t want to release them. In 1.0 I could just avoid adding them to the package, but in 2.0 I need a way to separate them from the packaged code. I could keep it in a separate repo, but that adds complexity to the development setup, and it’s always best to keep things as simple as possible.

Multiple Package Directories

The SalesforceDX project file can define multiple package directories for a single project. When you execute force:source:push, the contents of all of these directories are pushed to the scratch org, so i can store my supplementary code in another subdirectory and have it included in the development process. 

"packageDirectories": [
        {
            "path": "force-app",
            "default": true,
            "package": "bbexample",
            "versionName": "Version 1.0",
            "versionNumber": "1.0.0.NEXT"
        },
        {
            "path": “supplementary",
            "default": false
        }
    ]

When I push to my scratch org, the contents of both the force-app and supplementary subdirectories will be deployed, but when I generate a package version, only the directory with the package attribute gets included. All of my supplementary code stays out of the package and by extension the subscriber org.

There is one wrinkle to this - if I make some changes in the scratch org, when I execute force:source:pull to retrieve them, they will go into the package directory with the default attribute set to true - in this case force-app. If I don’t want these items in the package, I have to manually move them over to the supplementary subdirectory. Not a huge amount of effort but easy to forget.

Bonus - Installations via the CLI

If you haven’t used packaging in conjunction with the CLI yet, there’s one killer feature. Anyone who has worked with packaging 1.0 knows the fun and games around uploading a package, receiving the email that it has been published, then trying and failing to install it as it turns out not to be available after all. There’s a perfectly sound explanation for this - the emails means it was successfully uploaded to wherever packages live, but after that it has to be propagated around Salesforce infrastructure world, and until it’s made it to the instance you are trying to install in, you’ll get failures.

When you install via the CLI force:package:install subcommand, you can specify two wait times. The --wait switch that, like many other commands, specifies how long to wait for the command (installation) to complete, and the —publishwait that specifies how long to wait for the package to become available to the subscriber org. I typically specify big numbers for both of these switches and then fire and forget the installation. It’s a far less stressful user experience!

Related Posts

 

Saturday, 3 August 2019

Mentz - Where We Do What We Want!

Mentz - Where We Do What We Want!

Introduction

In the TV adaptation of David Peace’sRed Riding" quartert of books, there’s a great scene where a group of corrupt West Yorkshire policemen are toasting their success in moving towards controlled vice : "Off the streets and out the shop windows, under our wing and in our pocket”. The final line of the toast is “To the North - where we do what we want!”.

An unusual introduction to a tech blog post, I’m sure you’ll agree, but there is a link, however tennis. Doing what we want is the ethos behind the Mentz community initiative (and I’ve always loved that scene).

Moar Developers

One reason I started Mentz is that we need more developers in the Salesforce ecosystem, and by developers I mean people that can write code to solve complex business problems, understanding the fine details of the Apex language and the pros and cons to specific approaches. There are plenty of initiatives around to get started learning Apex, but not too many where a more experienced developer casts an eye over your code and gives you feedback, about things both good and bad, and pointing you to resources where you can learn more.

Tapping into Top Talent

Another reason I started Mentz is that I felt we were missing out on accessing a lot of top talent by insisting on commitments to a set number of weeks, or a fixed set of hours (duration and/or fixed start/end times). People at the very top of our profession often can’t make those type of commitments - they don’t have that many hours to spare, they can’t commit to specific hours every week, or they are subject to the whims of a project or customer which means that plans have to change at the last minute. These people are often running companies so they can’t step away when the pressure is on. The upshot of all this is that they may not be participating as much as they want to, because they don’t want to let others down. This is a crying shame, as these are often extremely experienced and talented developer who have a lot to share.

Fear of Commitment

When you join Mentz, as a mentor or mentee, you commit to absolutely nothing. Nada. Zip. When you register an interest, you’ll receive a chatter login and if you never sign in, that’s fine. Mentors don’t get assigned mentees, and vice versa. Mentors and mentees aren’t assigned to each other, it’s much more relaxed than that. If a mentee feels like taking a challenge, they do so and upload their solution to the challenge chatter group. If a mentor feels like looking at someone’s work and giving feedback, they pick any unanswered solution (there’s a lightning web component that shows these, because we don’t want mentors wasting their valuable time scouring chatter groups). There are no programs, timelines, schedules, courses. Just people who take challenges and people who review the solutions, doing what they want when they want to. I’m not the boss of you, and nor is anyone else.

Dipping a Toe

If you’ve been put off signing up for Mentz because you think you might disappoint your assigned mentor or mentees, don’t be. If you want to try the challenges without signing up you can, just take a look at the Github organisation - you won’t be able to submit your solution until you sign up for Mentz, but it will give you an idea of what is involved. If you want to sign up for Mentz, head over to the home page and follow the appropriate signup link. It might take a while for your request to be approved, as I also do what I want with regard to Mentz, but it will happen eventually.

And Finally

Please join me in raising a glass to Mentz - where we do what we want!

 

Related Posts

 

Sunday, 23 June 2019

Mentz - The Mentee Workflow

Mentz - The Mentee Workflow

This post was updated on 03/08/2019 to remove the concept of separate mentoring and solutions chatter groups for a single challenge - this turned out to be confusing so we now have a single chatter group per challenge.

 

Introduction

Mentz has been around for a couple of weeks now and we’ve had our first solutions published from Jesse Twum-Boafo that received some great mentoring from Clara Perez. The one key thing that this tells me is that the process works - up until now it was only me that had been through this so I had no idea if it would work on anyone else’s setup. Now that I know it does I’ll start adding the rest of those people that have registered an interest in being a Mentor or Mentee, so if you have registered, look out for an email. 

I’m also getting rid of the ask that anybody registering be in the same timezone. I originally added this because I had some weird idea that people could only register for a single Mentz login, but I’ve since realised that this is bonkers - there’s no reason why someone can’t cut their teeth in my instance and then start/join another instance to leverage that experience. There’s also no reason why someone couldn’t have a login to every Mentz instance there is, although I’d be rather suprised if that was the case as Mentz is aimed at those that don’t necessarily have a huge amount of time. I guess we’ll see what happens.

Mentees

One area I don’t think I’ve done a fantastic job on so far is explaining the Mentee process, so a blog post dedicated to this seemed an appropriate next step.

Mentz Login

The first thing a Mentee needs is a login to a Mentz instance. Without one of these you are still free to work on the challenges, but you won’t be able to publish your solutions and receive mentoring. To sign up for a login, visit the Candidate Signup page. (I’ve also not been very consistent about what to call Mentees, but I hope to improve upon that!).

Git and Github

The Mentz challenges are hosted on Github and you need to use Git to create your local copy. If you haven’t used Git before, complete the Git and Github Basics Trailhead module.

Salesforce CLI Plugin

The next thing a Mentee needs to do is install the Mentz Salesforce CLI Plugin. This provides the solution publishing capability, ensuring that the solution is published to the correct chatter group in the appropriate format. 

If you don’t have the Salesforce CLI installed, head over to the Home Page and follow the instructions.

Once the CLI is in place, install the Mentz plugin by executing the following command:

sfdx plugins:install mentz

You can verify that the installation was successful by executing:

sfdx mentz -h

and confirming that you see the following output:

Mentz commands

USAGE
  $ sfdx mentz:COMMAND

COMMANDS
  mentz:publish publishes a solution, optionally asking for mentor feedback

Finally, authorise the Salesforce CLI to use your Mentz login:

sfdx force:auth:web:login -a MENTZ

when the browser opens, login using your Mentz instance credentials. The ‘-a MENTZ’ switch creates a shortcut alias of ‘MENTZ’ which you can use when submitting challenges - use anything you like here, but remember to replace any instances of ‘MENTZ’ in the examples below with your chosen alias.

And that’s it - you are now all set to take on a challenge.

Challenges

All Mentz challenges live at the mentzbb organisation on Github - each challenge has a brief description of what it covers:

Click into a challenge to see the underlying details and code. If you want to take a challenge you need to clone the repository and change some of the code. For the purposes of this post I’ll be using the SimpleConditional challenge as an example, but all challenges follow the same pattern.

First step is to clone the repository - there’s a helpful button on the challenge main page to steer you in the right direction

Click the ‘Clone or Download’ dropdown button and you can copy the URL you need to clone. 

I use Git from the command line, so from my home directory I execute:

$ git clone https://github.com/mentzbb/SimpleConditional1.git

which does its thing and produces the following output:

Cloning into 'SimpleConditional1'...
remote: Enumerating objects: 25, done.
remote: Counting objects: 100% (25/25), done.
remote: Compressing objects: 100% (17/17), done.
remote: Total 25 (delta 7), reused 19 (delta 4), pack-reused 0
Unpacking objects: 100% (25/25), done.

The contents of the repository are as follows:

Where:

  • CHALLENGE is a file that the Salesforce CLI plugin uses to determine which challenge this is.
  • force-app contains the source format version of the challenge.
  • README.md is the home page for the challenge repository on Github.
  • src contains the metadata format of the challenge.

Completing a Challenge

To complete a challenge, you build out the sample class so that all the tests in the associated test class pass.

In the case of the SimpleConditional1 challenge, the sample class to be built out is SimpleConditional1.cls, and the tests from SimpleConditional1_Test.cls need to pass to complete the challenge. It doesn’t matter which of the source or metadata format classes you build out, and you only need to build out one of them.

Each method in the sample class has a comment that explains what processing it needs to carry out, e.g.

public static Boolean IsPositive(Integer num)
{
    // change this method to return true when
    // the number is greater than zero and false otherwise
    return null;
}

In order to run the unit tests, you need to send the code to a Salesforce instance. There are several ways of doing this, detailed in the following sections.

Scratch Org Development

If you want to develop against a scratch org, you’ll work on the source format code. Edit the code under the force-app directory and send to the scratch org using the command:

sfdx force:source:push

You can execute the tests in the org using the developer console, or the Salesforce CLI command:

sfdx force:apex:test:run -t SimpleConditional1_Test

Once you are happy with your code, you can request mentoring using the command:

sfdx mentz:publish -c "<comment>" -u MENTZ -m
-f ./force-app/main/default/classes/SimpleConditional1.cls

Where ‘<comment>’ is anything you wish to say to draw the mentor’s attention too.

Developer Edition Development

If you want to work against a developer edition, you’ll work on the metadata format code. You will need to authenticate the Salesforce CLI against the developer edition instance using the same approach as you did to authenticate against Mentz - don’t forget to set up a different alias!

Edit the code under the src directory and deploy to the developer edition using the command:

sfdx force:mdapi:deploy -d src -w 10 -u <username>

Where <username> is the alias for this org.

You can execute the tests in the org using the developer console, or the Salesforce CLI command:

sfdx force:apex:test:run -t SimpleConditional1_Test -u <username>

If you don’t want to actually deploy the code anywhere, but just run the tests, you can add the -c switch and ask the deploy command to run the tests using the following command:

sfdx force:mdapi:deploy -d src -w 10 -u <username> -c
-t SimpleConditional1_Test

Once you are happy with your code, you can request mentoring using the command:

sfdx mentz:publish -c "<comment>" -u MENTZ -m
-f ./src/classes/SimpleConditional1.cls

Where ‘<comment>’ is anything you wish to say to draw the mentor’s attention too.

Using the Developer Console

If you don’t want to deploy the code, you can use the developer console as follows:

  1. Open the developer console
  2. Click the File menu and select New -> Apex Class
  3. Name the Apex class SimpleConditional1
  4. Copy the contents of the force-app/main/default/classes/SimpleConditional1.cls and paste this into the new apex class.
  5. Save the class
  6. Click the File menu and select New -> Apex Class
  7. Name the Apex class SimpleConditional1_Test
  8. Copy the contents of the force-app/main/default/classes/SimpleConditional1_Test.cls and paste this into the new apex class.
  9. Save the class
  10. Work on the SimpleConditional1 apex class as you usually would until all the tests in SimpleConditional1_Test pass.
  11. Copy the contents of the SimpleConditional1 class from the developer console and paste this into force-app/main/default/classes/SimpleConditional1.cls in your local filesystem.

You can then publish a solution, optionally requesting mentoring using the command:

sfdx mentz:publish -c "<comment>" -u MENTZ -m
-f ./force-app/main/default/classes/SimpleConditional1.cls

Where ‘<comment>’ is anything you wish to draw the mentor’s attention to and the ‘-m’ switch adds a comment requesting mentoring.

Discussion

Once you have published your solution, a Mentor will pick this up when one is available. As the idea behind Mentz is that people dip in when they have time, it may take a few days before anyone responds. It will be worth the wait.  The discussion takes place in a chatter group dedicated to the specific challenge, so you just login with your Mentz credentials and start talking.

Still in Beta

Mentz is still in beta, so things are likely to change, for example:

  • Now that Summer 19 is live and deployment from source format to non-scratch orgs is GA, the metadata format version of the challenges will be going away
  • Separate commands and chatter groups for mentoring and publishing solutions may be merged.

Related Posts

 Follow @bob_buzzard

Sunday, 26 May 2019

Introducing Mentz - Salesforce Developer Mentoring

Introducing Mentz - Developer Mentoring

This post was updated on 03/08/2019 to remove the concept of separate mentoring and solutions chatter groups for a single challenge - this turned out to be confusing so we now have a single chatter group per challenge.

Introduction

Mentz is something I've been putting together over the last few months that is now ready to take it's first faltering steps on jelly legs into the wider world. It will allow me and others like me to share our experience with the next generation of Apex developers.

 What Mentz Is Not

Mentz is not Trailhead-like, as it there's no learning. badges or characters associated with it. There's plenty of that around already., It's more about digging deeply into various aspects of (currently) the Apex programming language to really understand it, rather than a follow-along copy/paste type of exercise, or one that sends you down a specific route without explaining why. If you just want to be able to understand enough Apex to skim read a class and figure out roughly what it does, Mentz isn't for you.

Mentz also isn't a scheduled course of sessions, as that is really hard to scale, and again there are also a number of existing options out there.

What Mentz Is

Mentz allows me to dip in and help people when I have some time, whenever that is. It allows fledgeling developers to get feedback from experienced professionals without formal sessions.

 The key drivers for me are:

  • I want to encourage and assist more people to become Salesforce developers
  • I can't commit to a set number of hours a week
  • I can’t commit to being available at specific times

The first round of exercises are aimed at beginners, as they take a bit of time to write and I'm only one man. Hopefully others will get involved and help with this. A Mentz challenge currently consists of a stub class and a bunch of associated unit tests. You clone a repository, build out the stubbed methods and get
the tests to pass.

So far, so good, but nothing ground breaking here.

The interesting aspect is what happens next - having finished your class and passed the unit tests, using a Salesforce CLI plugin, you can then upload it to a Salesforce org and request mentoring. The class and your request then get posted to a chatter group specific to the challenge, and some point (hopefully not too much) later a mentor claims it. The mentor then takes a look at your class and comes back with some useful advice which can evolve into a discussion.

The benefits of this approach are:

  • There are always going to be multiple ways to solve the same problem. Receiving guidance from a seasoned developer will help you understand the pros and cons of your approach, and what the other options are.
  • As it's chatter, mentors and mentees can interact with each other wherever they are and on any supported device.
  • It will be relatively straightforward to scale if there is interest - it's just a case of adding chatter users
  • I can run it in a Developer Edition, as chatter posts don't consume storage and the files are very small. Obviously when my Ohana Edition idea becomes reality I'll run it out of one of those and make things a bit slicker, but for now this works fine.

What Happens Next

In the first instance I'm looking for a small number of volunteers to be mentors and mentees. If you want to mentor, you must be PD2 certified or have a solid and public track record of Salesforce development expertise. If you want to be a mentee then ideally you’ll just have started to learn to program and have chosen Apex. That will change over time as the challenges get more complex, but for now they would be trivial for an experienced developer to solve.

It will also help if you are a Mac user - I use this exclusively so I haven't tested the Salesforce CLI extension outside of MacOS. Although if you are on another OS and have experience of Salesforce CLI plugins, feel free to sign up but expect to be asked to test/fix the extension.

An additional requirement is that I'd like these people to be in much the same timezone as me if possible. It make sense to me that mentees in other timezones receive mentoring from developers reasonably local to them. If things do take off, I don’t want to handle everything out of a single org myself, as that will limit scale. Instead I'll make the code available as a package so that others can host their own instance of Mentz, in their own org. We'll all use a common set of challenges, but the actual mentoring will be distributed.

If you are interested in getting involved, then head over to the Mentz home page and follow the appropriate signup link. As I mentioned earlier, I'm looking for a small amount of volunteers to start with, so if you don't get accepted right away, don't fret, you’ll be on the list!

 

Tuesday, 14 May 2019

Custom Notifications in Summer 19 Part 2 - Apex

Custom Notifications in Summer 19 Part 2 - Apex

Introduction

In Part 1 of this post I covered sending a custom notification from Process Builder when a record was created or edited with a specific attribute - a High Priority case to be exact. In this post I’ll cover sending a custom notification from Apex, although the notification is still actually sent from a Process Builder process, the logic around whether to send it lives in Apex.

To Code or Not to Code

The first question to ask yourself in these situations is do I actually need to go the Apex route. A process can send a notification when a record is created or edited to match a specific set of criteria, which coves a multitude of use cases. A couple of reasons why you might consider Apex are:

  • You want to send notifications for a large number of sObject typed and don’t want to create hundreds of processes. Not the greatest reason, but it would keep the process count down and thus remove some of the burden on your admins.
  • Separation of duties - if you are a developer and need a mechanism to send a notification without creating a new process, as you aren’t allowed to and the admins have a backlog of work. Again, not a great reason, as the governance is driving the solution, but this kind of thing does happen so we shouldn’t pretend it doesn’t.
  • When you want to send a notification only in specific circumstances, not every single time a record is created or edited. This is the most appropriate reason in my opinion - while you could create attributes to represent the circumstances in the record, you would be polluting your actual data with metadata to trigger functionality.

Platform Events

A process can be started in response to a number of things happening, but the one that can be used from Apex that isn’t changing a record is in response to a Platform Event. For the purposes of this post I’m reusing the Demo Event that I created for my Lightning Emp API in Winter 19 blog post.

Message sObject

In Part 1 of this post I mentioned in passing that the mechanism for generating notifications is better but not perfect, and this is a big reason why. As a process cannot use the payload of a platform event in decisions or actions, only as part of the entry criteria and choosing the record to use, I have to burn a custom object to hold details of the messages that I want to send. Not a huge deal, but it does add a certain clunkiness to the solution.

Custom Notification and Process Builder

As in Part 1, I need a custom notification type, Message in this case, to dovetail with the sObject name. I can then create a process builder that is started when a platform event is received and accesses a Message sObject whose ID is the platform event payload:

The notify action is trigged in all cases, as the logic is applied by Apex before the platform event is published, and sends a notification based on the contents of the associated Message record:

The Apex Code

Again from my my Lightning Emp API in Winter 19 post, I’m using the PlatformEventsDemo class to send the platform event after creating the Message record with details of the notification message and the user I want to send it to (there’s a link to the full solution at the end of this post):

Message__c message=new Message__c(Name='Demo Message',
                                  Message__c='Hello Bob!', 
                                  User__c='005B00000015moV');
insert message;
PlatformEventsDemo.PublishDemoEvent(message.Id);

Executing this code causes the red bell to display on the desktop and mobile, and the notification contains the message I sent:

Sharing is Caring

You can find the objects/events/code for this and the Part 1 post in my Summer 19 Samples repository.

Related Posts 

 

Sunday, 5 May 2019

Custom Notifications in Summer 19 Part 1 - Process Builder

Custom Notifications in Summer 19 Part 1 - Process Builder

Introduction

The Summer 19 Salesforce release is around a month away (check the trust site for the official dates) and my trusty pre-release org has been upgraded which allows me to play with some of the new functionality.

One of the items in the release notes that caught my eye was custom notifications, possibly because I’d been on the pilot and had been looking forward to using it in anger, possibly because I had some solutions that used the old method (see below) of sending notifications and it hurt my eyes every time I looked at it. Either way, I was keen to jump in.

The Old Way

To be clear (oh dear, I’m sounding like a UK politician) we’ve always been able to send notifications through custom code, but it’s been clunky, to put it nicely. You can read the full details in Salesforce1 Notifications from Apex, but the short version is that you have to programmatically create a chatter post using the ChatterConnect API and @mention the user you wanted to notify, and they received a notification that you had mentioned them and had to go to the chatter post to find out what you wanted, Not particularly elegant, I’m sure you’ll agree.

The New Way

Custom notifications are a much better solution - still not perfect but a big improvement on what we currently have. At the moment they are sent from Process Builder, so there’s still a little bit of hoop jumping required to send from Apex code.

Defining the Notification

Before you can send a custom notification, you need to define one via Setup -> Notification Builder -> Notification Types. Click New to create a new notification - for my first example I’m creating a custom notification for when a high priority case is received. Note that I can specify which channels a notification will be sent through. This is a nice feature as I’m more likely to want to notify Mobile users as a rule, as they are out of the office and may not be checking Salesforce that often. That said, as this is a high priority case notification, I’m sending it out everywhere that I can:

Once I have my notification, I can create a process that uses it -note that this is the only way that I can fire a notification directly.

Process Builder

My process is defined for the Case subject, when a record is created or edited:

The high priority case test looks for cases that are new or edited to change the priority field, and the priority field is ‘High’:

And the High Priority Case action sends me a notification that we are not in a good place.

After saving and activating the process, creating a new case with a priority of ‘High’ gives me red bell icon on desktop and my mobile, and a notification that is entirely appropriate to the case - clicking on the detail takes me through to the case itself. which is exactly what I want.

In Part 2 of this post I’ll show how notifications can be started from Apex. You still need a process builder to actually send the notification, but the logic around it can live elsewhere.

Related Posts

 

Sunday, 14 April 2019

Push Source Faster with the Salesforce CLI and VS Code

Push Source Faster with the Salesforce CLI and VS Code

Introduction

When working with SalesforceDX scratch orgs and VS Code, deploying source is as simple as opening the command palette and choosing the correct command. If there are errors, these are captured in the problems view and I can simply click on the problem to open the file:

Not too arduous right? Actually, it can be, especially when I’ve been on a train or plane and done a bunch of offline work that I’m then trying to deploy. After about a thousand push, fix, repeat cycles, opening the menu and choosing an option seems slow.

Configuring the Default Build Task

Note: this isn’t the way you want to do it when pushing source to a scratch org - it is for the metadata API and it’s also a useful thing to understand. I’m trying to build some suspense really.

As I’ve written before, I can configure any shell command as the default build task in VS Code.

To set up source push as the default, I :

  • open up the command palette and choose ‘Tasks: Configure Default Build Task’
  • choose ‘Create tasks.json file from template'
  • choose ‘Other’

I then replace the contents of the tasks.json file with :

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "build",
            "command": "sfdx",
            "args": [
                "force:source:push"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            }
        }
    ]
}

and I can now run the push as the default build task using the key sequence SHIFT+COMMAND+B (on a Mac):

There’s one downside to this and it’s quite a biggie - the problems view is empty so I have to look at the command output to figure the problem and manually locate the problem file and line. Luckily I can solve this relatively easily (it requires regular expressions so obviously there’s a number of attempts to get this right) by adding a problem matcher to parse the output and find any errors:

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "build",
            "command": "sfdx",
            "args": [
                "force:source:push"
            ],
            "problemMatcher": [
                {
                    "owner": "KAB-apex",
                    "fileLocation": [
                        "relative",
                        "${workspaceFolder}"
                    ],
                    "pattern": {
                        "regexp": "^(.*)  (.*) \\((\\d+):(\\d+)\\)$",
                        "file": 1,
                        "line": 3,
                        "column": 4,
                        "message": 2
                    }
                },
                {
                    "owner": "KAB-lc",
                    "fileLocation": [
                        "relative",
                        "${workspaceFolder}"
                    ],
                    "pattern": {
                            "regexp": "^(.*) \\s \\w*:(\\w*:)(\\d+),(\\d+):\\s(.*)$",
                            "file": 1,
                            "line": 3,
                            "column": 4
                    }
                }
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            }
        }
    ]
}

Now the problems view is populated again, so I have the same functionality with shortcut to kick the deployment off:

The Easy Way

Replacing the default build task is something that I’ve been doing on most of my projects to wire in a node script that manages deployments via the Metadata API. In that scenario I didn’t have any other options, but in this case it felt like I was having to recreate a lot of things to replace standard functionality. It felt like I should be able to create a shortcut to the entry in the command palette, but every time I googled I only got results around binding keyboard shortcuts to tasks.

A long time after I’d given up, I was looking to create a regular keyboard shortcut and opened the menu Code -> Preferences -> Keyboard Shortcuts and absent-mindedly typed ‘SFDX’ into the resulting screen, probably because it reminded me of the command palette:

The set of SFDX commands appeared! Scrolling down I found the source push command, and hovering over this showed a ‘+’ button that allowed me to define a shortcut key sequence - I choose SHIFT+OPTION+P:

Having configured this, I can now push the source simply by pressing that key combination, and I haven't had to replace any of the standard functionality:

As mentioned earlier, I’ve looked for a way to do this a number of times and had no luck - why that is the case I don’t know, but at least I got there in the end! As long as I don’t think about how much time I could have saved, it’s fine.

Related Posts

 

Saturday, 6 April 2019

Lightning Web Component Animated Progress Bar

Lightning Web Component Animated Progress Bar

Introduction

Lightning Web Components went GA in Spring 19, to the delight of some and consternation of others. Rather than a critique, which would be pretty short at the moment given how little real life usage I’ve had out of them, I decided to take one of my Aura Components (remember, Lightning Components are renamed Aura Components to avoid confusion with Lightning Web Components) and convert that to a Lightning Web Component.

The component in question is from my Animated Lightning Progress Bar blog post, which as the name suggests animates a progress bar from the current value to the desired value. 

Tracking Values

Properties of the Lightning Web Component annotated with @track annotation are tracked (no kidding!) by the framework and the contents of the component are automatically re-rendered when a tracked value changes. In this example I have one tracked property:

  • value - the current value being displayed in the progress bar

As mentioned in the original blog post, animating a progress bar is simply a matter of making small changes to the current value via a timer function, until it matches the desired value, and redrawing the progress bar every time the current value changes. By binding the progress bar element to the value property, every time I change it in JavaScript the progress bar is redrawn. Nice.

The other great thing about this is that I don’t have to use any special functions to read or write the value of the tracked property - I just use it or set it as I need to and the framework takes care of the rest, simply because of the annotation. I don’t think I’ll miss the component.get/set(‘v.<name>’) type functions that are liberally sprinkled over my Aura Component controllers.

Re-Animator

One aspect immediately flagged up as different - I’m using the standard  lightning-input component with an onchange handler to fire when the user changes the value. The change handler is called every time the user types something, rather than when they tab out etc. Initially I was starting the animation as soon as the change event was received, but if the user then continued typing it wasn’t the greatest experience. To solve this, I delayed taking any action until 300ms after the user last pressed a key, via the standard JavaScript setTimeout function. When the change handler fires, it clears any existing timeout and detects if the value input by the user has changed. If it has, a  timeout is scheduled for 300ms time to start the animation. If the user continues typing then the timeout is continually cleared and rescheduled for another 300ms time. When the user finally stops typing (or stops for 300ms!) the timer fires and the animation is started.

The example animation increments or decrements the current value until it matches the amount input by the user, decrementing being used if a desired value is entered that is smaller than the current value. Another standard JavaScript function, setInterval, is used to execute a function every 100ms that updates the current value.  Once the final value matches the current value, the interval is cancelled.

 

 

What’s Different?

There are a few differences compared to the Aura component:

  • As mentioned above, property/attribute access is much cleaner. I also don’t have to pollute my markup with declarations of attributes.
  • In the Aura Component when the timer function fired it was outside the framework lifecycle, so needed to use the $A.getCallback function. In Lightning Web Components there’s no need to do this, I can just treat it as any other asynchronous Javascript function. This is very much the experience that I’ve been having with Lightning Web Components - everything feels a lot closer to standard JavaScript rather than programming against a library or framework.
  • My event handlers don’t have to have a “c.” prefix, again it looks like I’ve defined a regular JavaScript function and am using it.
  • Only one JavaScript file! No more JavaScript controllers that delegate to helpers, or business logic striped across these two and a renderer.
  • To make the component available to the Lightning App Builder, I add a targets stanza to the XML metadata for the Lightning Web Component, again avoiding polluting my markup with information for the framework.

The Code

There are three elements to this component, available at the following Gists:

One More Thing

In true Columbo style, there’s one more thing to mention that you’ll see in the component markup. The ESLint for Lightning Web Components will complain about setTimeout and setInterval. As I am using them for good reason, I don’t need to hear any more of it’s whining. I can stop this by adding a comment before the line of code that it dislikes:

// eslint-disable-next-line @lwc/lwc/no-async-operation
let timeoutRef = setInterval(function() {…});

Related Posts

 

  

Friday, 15 March 2019

O Trailhead! myTrailhead!

O Trailhead! myTrailhead!

Captain

Introduction

After a fairly lengthy wait (announced at DF 17 and 18) myTrailhead officially launched on March 6th 2019. Simply put, this allows you to provide much of the Trailhead features that you know and love (minus some, plus some others) within your organisation to create your own continuous learning system. Trails, modules, badges and points are all there.

The main addition is you can add items such as corporate presentations and documents to a trail, so you can re-use existing collateral without having to migrate it to Trailhead quiz format. You can also combine standard Salesforce Trailhead modules into your own trails, so there’s no need to reinvent the wheel.

The big minus is that (I think) the automated checking of code/configuration assignments isn’t (currently) available. While I’ve seen references to Trail Checker, as far as I can tell this is more of a manual check than the kind of automation we are familiar with from Superbadges. I can’t say I’m surprised by this - turning a verification system of this nature into a product is no small undertaking, but it would be a very powerful addition for those of us regularly on boarding admins and developers to maintain our existing Salesforce applications.

Familiarity Goes a Long Way

The familiar styling should help adoption. Trailhead is extremely widely used by Salesforce admins, devs, and users (and many of their children, for some reason) and a UI/UX that they are already intimately familiar with will immediately remove some barriers. I’m also pretty sure that the chance to earn more badges and points will be jumped on with alacrity.

Content is King

myTrailhead also includes content management - modules and trails are combined into releases that can be published (accessible to your users) or unpublished (the next iteration, currently being created or reviewed), giving fine grained control on when users can access content and ensuring that it is fit for purpose. As mentioned earlier, you can link to existing Trailhead content so you have a head start in terms of material that isn’t specific to your business.

This is the mechanism of managing content that is provided by myTrailhead. However, creating the content is a task that is all yours and should absolutely not be underestimated. Managing a library of engaging training content and ensuring this is kept up to date when underlying processes or applications change can be a huge task. I can’t imagine how long the Trailhead content team spend on this for a new Salesforce release, but one thing I can tell you is that they don’t ask the developers to do it in their spare time, so if you want a system that will be used, be prepared to invest in a content team of your own. By the same token, if you library consist of half a dozen badges from a couple of years ago, don’t be surprised if nobody is interested in earning them.

Licensing

Up until now I’ve been broadly positive about this, which might come as a surprise to regular readers of this blog, and here comes the negativity. I think Salesforce have made a bit of a mistake with the licensing.

myTrailhead is a $25/user/month add-on. While this isn’t cheap, that isn’t where my beef lies. Instead it’s the fact that it’s an add-on to an Enterprise Sales/Service/Platform license. So if I’m part of a large company (I’m not) and I want to use myTrailhead for staff that aren’t Salesforce users, I have to buy them a Salesforce license they will never use and then add myTrailhead on top of it. And of course I won’t do that, as it makes the cost prohibitive. Instead I’ll do it all on another platform or run two training systems, one for Salesforce users and one for everyone else, which will cause confusion. Or I’ll buy 10 licenses and continually cycle them around users when new training content becomes available.

A standalone license including myTrailhead and chatter, for example, would make much more sense from a purchasing perspective and allow me to train all my users through a single system. Hopefully this is being used to throttle uptake in the short term and we’ll see more licensing options in the future, otherwise I fear that it will only achieve a fraction of its potential.

Related Posts

 

  

Monday, 21 January 2019

Unsaved Changes in Spring 19

Unsaved Changes in Spring 19

Introduction

As I mentioned in my last post, it’s often the little things in a release that make all the difference. Another small change in the Spring 19 release is the lightning:unsavedChanges aura component (yes aura, remember that in Spring 19 Lightning Web Components are GA and Lightning Components are renamed Aura Components). The release notes are somewhat understated - "Notify the UI about unsaved changes in your component”, but what this means is that I can retire a bunch of code/components that I’ve written in the past to sop the user losing their hard earned changes to a record. Even better, I’m wiring in to the standard Lightning Experience behaviour so I don’t need to worry if Salesforce change this behaviour, I’ll just pick it up automatically.

Example

My example component is a simple form with a couple of inputs - first and last name:

<aura:component implements="flexipage:availableForAllPageTypes">
    <aura:attribute name="firstname" type="String" />
    <aura:attribute name="lastname" type="String" />
    <aura:handler name="change" value="{!v.firstname}" action="{!c.valueChanged}"/>
    <aura:handler name="change" value="{!v.lastname}" action="{!c.valueChanged}"/>

    <lightning:card variant="narrow" title="Unsaved Example">
        <div class="slds-p-around_medium">
            <lightning:input type="text" label="First Name" value="{!v.firstname}" />
            <lightning:input type="text" label="Last Name" value="{!v.lastname}" />
            <lightning:button variant="brand" label="Save" title="Save" onclick="{! c.save }" />
        </div>
    </lightning:card>

    <lightning:unsavedChanges aura:id="unsaved"
                onsave="{!c.save}"
                ondiscard="{!c.discard}" /> 
</aura:component>	

I have change handlers on the first name and last name attributes, so that I can notify the container that there are unsaved changes. I achieve this via the embedded lightning:unsavedChanges component, which exposes a method named setUnsavedChanges. In my change handler I invoke this method:

valueChanged : function(component, event, helper) {
    var unsaved = component.find("unsaved");
    unsaved.setUnsavedChanges(true, { label: 'Unsaved Example' });
}

So now if I add this component to a Lightning App page, enter some text and then try to click on another tab, I get a nice popup telling me that I may be making a big mistake. It also displays the label that I passed the setUnsavedChanges method, so that if there are multiple forms on the page then the user can easily identify which one I am referring to. 

Screenshot 2019 01 20 at 11 20 02

Of course, my Evil Co-Worker immediately spotted that they could pass the label of a different component and keep the user in a loop where they believe they have saved the changes but keep getting the popup. If I’m honest I expected something a bit more evil from them, like calling a method that clears the unsaved changes without saving the record when the user clicks the Save button,  clearly getting lazy as well as Evil in their old age.

What’s even nicer is that if I click the Discard Changes or Save buttons, my controller methods to discard or save the record are invoked, because I specified these on the component:

<lightning:unsavedChanges ..." onsave="{!c.save}" ondiscard="{!c.discard}" /> 

so I have full control over what happens.

This kind of interaction with the user is possible when I click on a link that is managed by the Lightning Experience, but not so much if I reload the page or navigate to a bookmark. In this scenario I’m at the mercy of the beforeunload event, which is browser specific and much more limited.

Screenshot 2019 01 20 at 11 36 40

This behaviour can’t be customised, the idea being that you can’t hold a user on your page against their will, regardless of whether you are doing it for their own good. 

On another note, when I created the original lightning app page for this I used a single column, but the inputs then spanned the whole page. Thanks to the Spring 19 feature of changing lightning page templates, I was able to switch to header, body and right sidebar in a few seconds, rather than having to create a whole new page. I sense that feature is the gift that keeps giving.

Related Posts

 

Saturday, 12 January 2019

Change Lightning Page Template in Spring 19

Change Lightning Page Template in Spring 19

Introduction

Sometimes it’s the little things that make all the difference, and in the Spring 19 release of Salesforce there’s a small change that will save me a bunch of time - support for changing the template of a Lightning Page. I seem to spend a huge amount of time recreating Lightning pages once I add a component that needs more room than is available. Often this is historic Bob's fault, because I’ve written a custom component that needs full width, but when I originally created the page I didn’t foresee this and chose a template that is now obviously unsuitable.

Changing Template

Here’s my demo page - as you can see the dashboard is a bit squashed as I’ve gone for main region and sidebar:

Screenshot 2019 01 12 at 16 13 44

Changing template is pretty simple - in the Lightning App builder (it can’t be long before this is renamed the Lightning Page builder can it?) edit the page and find the new option on the right hand side:

Screenshot 2019 01 12 at 16 15 13

Clicking this opens the change template dialog - initially, much like creating a page I get to choose from a list. I’ve gone for header and right sidebar so that my dashboard can take the full width header section:

Screenshot 2019 01 12 at 16 15 51

The next page is a departure from previous experience, in that I need to tell the dialog how to map the components from the old to the new template - I’ve left it as the defaults aside from the main region (which has my dashboard) which I add to the new header region:

Screenshot 2019 01 12 at 16 16 10

Once I’ve saved the page, I can view it and see the dashboard in it’s full width glory:

Screenshot 2019 01 12 at 16 16 41

Deploying Changes

While it’s great that we can do this through the UI, if I change the template of a Lightning Page in a sandbox I’d like to be able to deploy it via the metadata Api to production. When I tried this initially I had my API version set to 44.0 in my package.xml, which meant that I wasn’t hitting the Spring 19 metadata Api, but the Winter 19 version which doesn’t support template changes. Once I updated that, it all worked fine. If you are wondering how I can make such an obvious mistake, allow me to point you at this post, which explains everything.

Related Posts