Saturday, 14 July 2018

Lighting Component Attributes - Expect the Unexpected

Lighting Component Attributes - Expect the Unexpected

 

UnexpectedIntroduction

A couple of weeks ago my BrightGen colleague, Firoz Mirza, described some unexpected (by us at least) behaviour when dealing with Lightning Component attributes. I was convinced that there must be something else going on so spent some time creating a couple of simple tests, only to realise that he was absolutely correct and attributes work a little differently to how I thought, although if I’m honest, I’ve never really thought about it.

Tales of the Unexpected 1

The basic scenario was assigning a Javascript variable to the result of extracting the attribute via component.get(), then calling another method that also extracted the value of the attribute, but crucially also changed it and set it back into the component. A sample component helper that works like this is:

({
    propertyTest : function(cmp) {
	var testVar=cmp.get('v.test');
        alert('Local TestVar = ' + JSON.stringify(testVar));
        this.changeAttribute(cmp);
        alert('Local TestVar after leaving it alone = ' + JSON.stringify(testVar));
    },
    changeAttribute : function(cmp) {
        var differentVar=cmp.get('v.test');
        differentVar.value1='value1';
        cmp.set('v.test', differentVar);
    }
})

The propertyTest function gets the attribute from the component and assigns it to testVar. After showing the value, it then invokes changeAttribute, which gets the attribute and assigns it to a new variable, updates it and then sets it back into the component.

Executing this shows the value of the property is empty, as expected to begin with:

Screen Shot 2018 07 14 at 13 49 09

but after the attribute has been updated elsewhere, my local variable reflects this:

Screen Shot 2018 07 14 at 13 50 27

 

Tales of the Unexpected 2

After a bit more digging, I came across something that I expected even less - updating a local variable that stored the result of the component.get() for the attribute, changed the value in the component. So I updated my sample component to test this:

    propertyTest2: function(cmp) {
	var test2=cmp.get('v.test');
        test2.value2='value2';
        alert('Local test2 = ' + JSON.stringify(test2));
        this.checkAttribute(cmp);
    },
    checkAttribute: function(cmp) {
        var otherVar=cmp.get('v.test');
        alert('Other var = ' + JSON.stringify(otherVar));
    }

Executing this shows my local property value being updated as expected:

Screen Shot 2018 07 14 at 14 01 51

but then retrieving this via another variable showed that it appeared to have been updated in the component:

Screen Shot 2018 07 14 at 14 02 28

“Maybe it’s just because everything is happening in the same request” I thought, but after this request completed I executed the first test, so extracting the value in a separate request (transaction) showed that the attribute had indeed been updated in a way that persisted across requests:

Screen Shot 2018 07 14 at 14 03 44

Checking the Docs

Convinced that I must have missed this in the docs, I went back to the Lightning Components Developer Guide, Working with Attribute Values in JavaScript section, but only found the following:

component.get(String key) and component.set(String key, Object value) retrieves and assigns values associated with the specified key on the component.

which didn’t add anything to what I already knew. 

Asking the Experts

I then asked the question of Salesforce, with my sample components, and received the following response:

The behavior you’re seeing is expected. 

component.set() is a signal to the framework that you’ve changed a value. The framework then fires change handlers, rerenders, and so forth. 

The underlying value is a JavaScript object, array, primitive, or otherwise. JavaScript object’s and arrays are mutable. Aura’s model requires that you send that signal when you mutate an object or array.

Conclusion

So there you have it - a local variable assigned the result of component.get() gives you a live hook to the component attribute, and if it’s mutable then any changes you make to the local variable update the attribute regardless of whether you call component.set() or not. Good to know!

I think the docs could certainly use with some additional information to make this clear. It’s written down here, at least, so hopefully more people will know going forward!

Related Posts

 

 

Saturday, 7 July 2018

Exporting Metadata with the Salesforce CLI

Exporting Metadata with the Salesforce CLI

Exit

Introduction

Regular readers of this blog are all too aware that I’m a big fan of the Salesforce CLI. I rarely use anything else to interact with Salesforce orgs, outside of the main UI. Prior to this I used the Force.com CLI, which is another excellent tool, though didn’t give me quite enough information about operations to switch to it exclusively.

One area where the Force.com CLI still beats the default functionality of the Salesforce CLI is the export command, which pull back most of the metadata from an org (except reports, dashboards, and email templates, where you have to jump through a few hoops to get folder names first). Switching back to the Force.com CLI to take a backup of an org started to get a bit repetitive, so I decided to write something to replicate the functionality using the Salesforce CLI.

To Plugin or Not to Plugin

After some consideration, I decided not to plugin. I need to do some work and then execute a deployment via the Salesforce CLI, and there’s currently no way to execute a command from a plugin. I could shell out of the plugin and execute the CLI as a new child process, but that seemed a bit clunky. Plus where does it end? We’d end up with plugins creating child sfdx commands that were plugins that created child sfdx commands - a house of cards made of processes and just as fragile. I’d also seen Dave Carroll call this approach an anti-pattern on twitter and I didn’t want him mad at me, so I went with a node script that wraps everything up into a single command from the users point of view.

The Node Code

As I plan to do more of this sort of thing, rather than having loads of different scripts to execute I went with one script that offers subcommands. Kind of like a CLI all of it’s own, but very lightweight.

In order to support subcommands I used the commander module - I’ve used this a few times in the past and it’s really straightforward. I just configure it with the subcommands and their options and it figures out which one to execute and parses the arguments for me:

var exportCmd=program.command('export')
		 .option("-u, --sfdx-user [which]", "Salesforce CLI username")
		 .option("-d, --directory [which]", "Output directory")
                 .action(function(options) {new ExportCommand(options).execute()});

program.parse(process.argv);

The export command is responsible for generating a package.xml manifest file containing details of all the metadata to retrieve and then executing a metadata API retrieve.

 

It contains a list of the names metadata components to extract:

const allMetadata=[
    "AccountSettings",
    "ActivitiesSettings",
    "AddressSettings",
    "AnalyticSnapshot",
    "AuraDefinitionBundle",
         ...
“CustomObject”,
... "ValidationRule", "Workflow" ];

The one wrinkle here is the standard objects. If I wildcard the CustomObject metadata type, this will just retrieve my custom objects. To include the standard objects, I have to explicitly name each of them. Luckily the Salesforce CLI provides a way for me to extract these - force:schema:sobject:list, so I can pull these back and turn them into a list of names:

var standardObjectsObj=child_process.execFileSync('sfdx', 
                    ['force:schema:sobject:list', 
                        '-u', this.options.sfdxUser, 
                        '-c', 'standard']);

var standardObjectsString=standardObjectsObj.toString();
standardObjects=standardObjectsString.split('\n');

for (var idx=standardObjects.length-1; idx>=0; idx--) {
    if ( (standardObjects[idx].endsWith('__Tag')) ||
         (standardObjects[idx].endsWith('__History')) ||
         (standardObjects[idx].endsWith('__Tags')) ) {
         standardObjects.splice(idx, 1);
    }
}

Then I can generate the package.xml by iterating the list of metadata names and adding a wildcard entry for each of them, and append the standard objects when the metadata name is CustomObject:

for (var idx=0; idx<allMetadata.length; idx++) {
    var mdName=allMetadata[idx];
    this.startTypeInPackage();
    if (mdName=='CustomObject') {
        for (var soIdx=0; soIdx<standardObjects.length; soIdx++) {
            this.addPackageMember(standardObjects[soIdx]);
        }
    }
    this.addPackageMember('*');
    this.endTypeInPackage(mdName);
}

Once the package.xml is written, I can then execute a force:mdapi:retrieve to pull back all of the available metadata:

child_process.execFileSync('sfdx', 
                ['force:mdapi:retrieve', 
                    '-u', this.options.sfdxUser, 
                    '-r', this.options.directory, 
                    '-k', this.packageFilePath]);

Export all the Metadata!

(The full source code is available at the Github repo. If you want to try this out yourself, clone the repo, change into the directory containing the clone and run npm install to pull down the dependencies.)

To export the metadata I execute:

node index.js export -u keir.bowden@train.bb -d output

Where -u specifies the username that I’ve previously authorised with the Salesforce CLI (I’m using the client dev org for my training system), and -d is the directory where the package.xml will be created and the metadata extracted to. If this directory doesn’t exist it will be created.

The output is just a couple of lines telling me it succeeded:

Exporting metadata
Metadata written to output/unpackaged.zip

The contents of the output subdirectory shows the package.xml and zip file retrieved from Salesforce:

$ ls -lrt
total 928
-rw-r--r-- 1 kbowden staff 20110 7 Jul 12:49 package.xml
-rw-r--r-- 1 kbowden staff 453236 7 Jul 12:50 unpackaged.zip

unzipping the unpackaged.zip file creates an unpackaged directory containing the retrieved metadata:

$ unzip unpackaged.zip
Archive: unpackaged.zip
inflating: unpackaged/labels/CustomLabels.labels
   …

$ cd unpackaged
$ ls -l
total 48
drwxr-xr-x 16 kbowden staff 512 7 Jul 12:52 applications
drwxr-xr-x 4 kbowden staff 128 7 Jul 12:52 assignmentRules
    …
drwxr-xr-x 7 kbowden staff 224 7 Jul 12:52 tabs
drwxr-xr-x 4 kbowden staff 128 7 Jul 12:52 triggers

and hey presto, my org is exported without having to revert back to the Force.com CLI.

Related Posts

 

Friday, 29 June 2018

Adding Signature Capture to a Lightning Flow

Adding Signature Capture to a Lightning Flow

Screen Shot 2016 11 07 at 18 36 19

Introduction

As regular readers of this blog know, a few years ago I wrote a Lightning Component for the launch of the Component Exchange called Signature Capture which, in a shocking turn of events, allows a user to capture a signature image and attach it to a Salesforce record. Over the years it’s received various enhancements and when these are notable I write a blog post about it. The latest version satisfies this requirement as it adds support for inclusion in Lightning flows. Note that the rest of this post assumes that you are working with V1.31 of Signature Capture or higher.

The Scenario

I’m going for a pretty simple scenario - a custom button on the a Salesforce record starts a flow that contains just the Signature Capture component. Once the user is happy with the signature and saves it, finishing the flow returns them to the record that they started from.

Creating the Flow

If you are a metadata type, you can install the demo flow from the Signature Capture Samples Git repository. It’s called   don’t forget to activate it.

If not, carry out the following steps to create the flow:

  1. Open the flow designer
  2. Click on the ‘Resources’ tab and on the resulting list click ‘Variable'

    Screen Shot 2018 06 28 at 16 51 15

  3. Create a new variable named ‘recordId’ and click OK - this will contain the Id of the record that the user wishes to capture a signature against.

    Screen Shot 2018 06 28 at 16 47 32
     
  4. Click the ‘Palette’ tab and on the resulting list drag ‘Screen’ onto the canvas:FillFil

    Screen Shot 2018 06 28 at 16 52 41

  5. Fill in the ‘Name’ as desired - I went for ‘Signature Capture’ as I’m a creative type:

    Screen Shot 2018 06 28 at 16 56 54

  6. Click the ‘Add a Field’ tab and scroll to the bottom of the list and choose ‘Lightning Component’:

    Screen Shot 2018 06 28 at 16 57 27

  7. Click the ‘Field Settings’ and configure as shown below - note that I had to remove the entry that was automatically added to the ‘Outputs’ tab before I could save. Make sure to set the value of the 'Record Id' attribute to the 'recordId’ variable - this ensures that the captured signature is stored against the correct Salesforce record.

    Screen Shot 2018 06 28 at 16 58 23

  8. Set the screen as the start element
  9. Save the flow
  10. Activate the flow

And that’s it! Straightforward but the flow isn’t very useful as it’s not attached to anything.

Creating the Custom Button

Using the URL of the flow seems pretty clunky, but what can we do?

  1. Open the flow and copy the URL value:

    Screen Shot 2018 06 28 at 17 02 40

  2. Navigate to the setup page for the object you want to be able to launch the flow from (I’ve gone for Account) and click ‘Buttons, Actions, and Links’
  3. On the resulting page, click ‘New Button or Link'
  4. Configure the button as follows, replacing the ‘Button or Link URL’ with the concatenation of your flow URL (copied in step 1) and the appropriate record id for for the sobject type. The retURL parameter is the secret sauce to send the user back to the record when they finish the flow:

    Screen Shot 2018 06 28 at 17 19 11

  5. After saving, click the ‘Page Layouts’ link from the sObject setup page.
  6. Choose the layout to add the button to.
  7. On the resulting page builder, select the 'Mobile & Lightning Actions’ item from the palette:

    Screen Shot 2018 06 28 at 17 08 23

  8. And drag your new custom button on to the 'Salesforce Mobile and Lightning Experience Actions’ section:

    Screen Shot 2018 06 28 at 17 08 26

  9. Save the page layout

Enable Lightning Flow

Flows containing Lightning components require lightning flow to be enabled.

  1. Navigate to Setup -> Process Automation -> Process Automation Settings
  2. Check the ‘Enable Lightning runtime for flows’ box:


    Screen Shot 2018 06 28 at 17 12 53

  3. Save the settings.

Take it for a Spin

Once everything is in place you can launch the flow by navigating to a record of the appropriate sObject type and clicking the custom button. The video below shows my flow configured above being launched and completed from an account record:

Related Posts

 

Sunday, 24 June 2018

Sharing your Salesforce App on Github

Sharing your Salesforce App on Github

Share

Introduction

Over the years there’s been a few ways to give away Salesforce apps. When I first started 10 years ago there was a Google Code Share accessible from the Developerforce site, but that star waned some time ago. There’s also unmanaged packages, but that typically requires someone to install your package into an org before they can look at the contents of the app, which is fine if they know they want to use it in it’s entirety, but less useful if they want to use your app as s starting point or example for something that they are doing.

Over the last few years, Github has become the de-facto way to share and collaborate on code. As well as providing (free for public) Git repository hosting, it has a bunch of other cool features to allow people to collaborate:

  • Pull requests, to allow changes to be reviewed, discussed and refined before they are merged.
  • Issue tracking, to capture ideas, feature requests, bugs
  • Releases, so that you can wrap and ship specific versions

A collection of useful applications in Github is also a signal to potential employers that you are passionate about app creation and I’m sure won’t do your career any harm. It’s also a great way to share a sample application after presenting at a user group or World Tour event.

I’m using my Summer 18 samples codebase as the example Salesforce application for this post. Note that this post assumes you have created a Github account and completed Git setup.

But Github is Microsoft now

It is, but under Satya Nadella we’re seeing a new Microsoft, particularly around open source. Even if you are nervous around what might happen to Github in the future, you are putting your application out there for everyone to use, so it’s not like it’s some big secret that Microsoft might help themselves to. I can see how a Microsoft competitor with a business account might be worried about security,  but for public applications it’s not something I’d be concerning myself with.

Metadata

The most common mechanism of storing an application outside of Salesforce is extracting the metadata via the Metadata API (SalesforceDX Scratch Org format is the new kid on the block, but at the moment is more likely to be of interest to ISVs in my opinion). There are various mechanisms for extracting metadata, requiring various degrees of setup, such as:

  • An IDE, such as the Force.com IDE, where you can choose from a subset of metadata to retrieve
  • The Force.com Migration Tool (ant), where you’ll need to define a manifest file (package.xml) describing the metadata you want to retrieve
  • The Salesforce CLI, via the force:mdapi:retrieve subcommand - again you’ll need a manifest file
  • The Force.com CLI, via the export and fetch commands.

In my view the Force.com CLI is still the easiest tool to use to export metadata - I wrote a blog post explaining how to do this a while ago.

Once you have your metadata exported you will have a structure similar to the following:

Screen Shot 2018 06 24 at 10 27 04

 

Creating a Local Repository

Github uses Git to manage the set of files that make up your application, stored in a data structure known as a repository. This enables tracking of changes to files (and much more besides). In order to create a repository, however, you need to extract your application from Salesforce.

Once your metadata is extracted from Salesforce it’s in a state where you can easily migrate it to other orgs, but it isn’t set up to be managed by Git. To turn your codebase into a local Git repository, you use the git init command:

$ git init .

Initialized empty Git repository in /Users/kbowden/GithubBlog/.git/

You can then run git status and see that you have indeed turned your app into a repository:

$ git status 

On branch master
No commits yet
Untracked files:

  (use "git add ..." to include in what will be committed)

       src/

nothing added to commit but untracked files present (use "git add" to track)

So you have a repository, but none of your applications files are being tracked by it. To achieve this, execute git add to stage the files and then git commit to commit them to the repository:

$ git add src

$ git commit
[master (root-commit) 35fa327] Created.
31 files changed, 202 insertions(+)
create mode 100644 src/aura/Navigator/Navigator.auradoc
create mode 100644 src/aura/Navigator/Navigator.cmp
      ...

create mode 100644 src/classes/UtilityController.cls
create mode 100644 src/classes/UtilityController.cls-meta.xml

Note that the git commit command will open an editor for your commit message.

Create the Remote Repository

Now that the code is being tracked by Git, the final step is to create the remote Github repository and push your local changes to it.

There are various ways to set up a remote repository, I use the following steps as it reminds me to set up the README.md file that serves as the “home page” for the repository on Github:

  1. Login to Github
  2. Click the + button on the top right and on the resulting dropdown menu, select New Repository

    Screen Shot 2018 06 24 at 11 02 08
  3. Fill out the resulting form, making sure to tick the box titled 'Initialize this repository with a README’, and click the Create Repository button

    Screen Shot 2018 06 24 at 11 03 45

  4. On the resulting page, click the ‘Clone or Download’ button and copy the Web URL:

    Screen Shot 2018 06 24 at 11 06 44

  5. Add the remote repository, using the Web URL copied in the step above. I’ve named the remote ‘origin’ as this is going to be the main repository going forward:
    $ git remote add origin https://github.com/keirbowden/GithubBlog.git
  6. Verify the remote:
    $ git remote -v

    origin https://github.com/keirbowden/GithubBlog.git (fetch)
    origin https://github.com/keirbowden/GithubBlog.git (push)

Pushing Local to Remote

The remote Github repository is now created and associated with the local repository. There’s nothing in the remote repository apart from the README.md file, so the next step is to push the contents of the local repository. Before this can be done, the contents of the remote repository must be merged in, as it has changes (README.md) that aren’t in the local repo.

The git pull command fetches the changes from the remote repository and merges them into the named branch in a single action. As the local and the remote repository have been created separately, use the '--allow-unrelated-histories' option to allow everything to be coerced into a single set of files:

$ git pull origin master --allow-unrelated-histories

From https://github.com/keirbowden/GithubBlog
* branch master -> FETCH_HEAD
Merge made by the 'recursive' strategy.
README.md | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 README.md

Finally, push the consolidated repository to Github via the git push command:

$ git push origin master

Counting objects: 30, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (24/24), done.
Writing objects: 100% (30/30), 4.71 KiB | 1.57 MiB/s, done.
Total 30 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), done.
To https://github.com/keirbowden/GithubBlog.git
f956682..be01baf master -> master

All Done

Accessing the repository on Github shows the entire Salesforce application is now available:

Screen Shot 2018 06 24 at 11 28 15

Related Posts

 

Thursday, 14 June 2018

Building my own Learning System - Part 7

Building my own Learning System - Part 7

Learning

Introduction

Since I last blogged about my learning system I’ve started using it at BrightGen, for example to onboard developers onto our BrightMedia accelerator, where it has been generally well received. Not least by me, as it means that I don’t need to spend multiple hours with every developer, going through the same content which they may or may not be listening to. I’m also seeing some customers indicating that they’d like to pick up development tasks in BrightMedia projects. Thanks to my original design decision of distributing content across multiple endpoints, I can expose content to upskill the customers without giving away internal BrightGen content, or them even knowing that other content exists.

I’ve added in some features that are useful, to me at least, including:

  • Filtering by topic
  • Executing as a user (the github repo has the concept of running as a specific email address, as it’s unauthenticated)
  • Added an info modal, detailing the recent changes, which I keep forgetting to update, so if you use the unmanaged package to install it will likely be at least one version out of date :)

The great thing about building my own learning system though, is that I am learning by building it.

Learning by Building

As I’m adding features to my learning system I’m also learning more. Not just the Salesforce features that I expected to learn about, such as using the latest standard lightning components, but other areas that aren’t even Salesforce related.

Most recently I’ve been learning more about how to operate on Github. Up until now I’ve just typically created a repo when I wrote a blog post and added to it when I wrote another blog post on the same topic. Now that I’ve got a project that I’m trying to keep a bit more organised, I’m :

  • Adding details of fixes/features into CHANGELOG.md (I’m pretty good on this one - much better than my modal. I probably need to automate updating the modal from the change log).
  • Creating releases
  • Adding diffs to CHANGELOG.md that show all changes between releases 

This wasn’t something that I expected when I started working on my learning system and, once again, shows the value of side projects. I knew that I’d learn, but surprised even myself.

Related Information

All previous posts about the learning system have moved to a dedicated page on my blog. The code is available in the following repositories.

 

 

Tuesday, 12 June 2018

Accessing Lightning Component URL Parameters

Accessing Lightning Component URL Parameters

Introduction

One of the more common challenges when working with Lightning Components is how to access parameters passed on the URL. In a Lightning App it’s straightforward - simply define an attribute with the parameter name:

<aura:application >
    <aura:attribute name="param1" type="String" />
        Param1 = {!v.param1}
</aura:application>

and then add that parameter to the URL for the app:

    https://kabtutorial-developer-edition.lightning.force.com/KAB_TUTORIAL/UAApp.app?param1=test

and this is picked up and displayed in the resulting page:

Screen Shot 2018 06 11 at 08 07 30

However, Lightning Apps are rarely the solution - not least because they execute outside the Lightning Experience.

Accessing via JavaScript

In a Lightning Component’s JavaScript controller or helper, the parameters can be accessed directly from the window location, or with the locker service enabled for a component at API 40 and higher, the secure window location:

getURLParameter : function(param) {
    var result=decodeURIComponent
        ((new RegExp('[?|&]' + param + '=' + '([^&;]+?)(&|#|;|$)').
            exec(location.search)||[,""])[1].replace(/\+/g, '%20'))||null;
    console.log('Param ' + param + ' from URL = ' + result);
    return result;
}

In case you were wondering, I didn’t come up with the regular expression myself. If it weren’t for stack overflow I suspect I’d be using indexOf and working around errors for some time after.

There’s nothing wrong with this approach, but it feels a little .. clunky.

isUrlAddressable Interface

Summer 18 introduces the lightning:isUrlAddressable interface - I wrote a post about it’s navigational capability a short while ago. Near the bottom of the documentation for this component is an example of how to replace the deprecated force:navigateToComponent with a custom component implementing isUrlAddressable. This example includes an attribute that I hadn’t come across before - v.pageReference - which allows access to the parameters on the URL via the state property. 

I can now create a component that accesses the parameters as easily as an app:

<aura:component implements="flexipage:availableForAllPageTypes,lightning:isUrlAddressable" 
                access="global" >
    <lightning:card>
        Param1 = {!v.pageReference.state.param1}
    </lightning:card>
</aura:component>

Really? That easy?

Yes and no. It’s that easy if you are navigating directly to a component that implements the isUrlAddressable interface, but not if you are:

  • Opening a Lightning Application Page created with the Lightning App Builder
  • Inside another component, even if that component implements the isUrlAddressable interface

In these situations, and any other where the component accessing the v.pageReference attribute isn’t that component that you navigated to, the v.pageReference attribute will be undefined and you’ll have to fall back on accessing via JavaScript, as shown earlier. 

So definitely a step in the right direction, but not the silver bullet I’m still looking for.

Related Posts

 

Sunday, 27 May 2018

Building a Salesforce CLI Plugin

 

Introduction

Plug

As regular readers of this blog will know, I’m a big fan of the command line in general and the Salesforce CLI in particular. Up until now I’ve been layering functionality onto the CLI by wrapping it in bash or node scripts, so I was interested to read about the Plugin Development beta program as this seemed like a neater way to extend it.

The examples I’ve seen to date are mainly focused on extracting information from the org, or performing local processing on files that have already been generated by another CLI command, so I wanted to push things a little further and change something in the org. In the command line tool that I’ve written to manage our BrightMedia projects we update the org with a bunch of information, including the Git commit id, after a successful deployment, and this felt like a good challenge to start with

Creating the Plugin

Simply following the instructions from the plugin generator Github repo gave me the initial version of the plugin. This creates fairly hefty folder structure, but the most interesting bit is the src/commands folder - anything I put in here becomes a command for my plugin and the folder structure from here down gives me the topic/subtopic etc.

I have the structure :

Screen Shot 2018 05 27 at 15 08 58

which gives me the topic bbuzz and the command gitstamp. This concluded the simple part of creating my plugin.

More to Learn

The example plugin that the generator creates is coded in TypeScript, a typed superset of JavaScript that compiles down to plain old JavaScript. I hadn’t used this before, but it’s not too different to JavaScript so I was able to get going reasonably quickly, with the compiler telling me what I’d missed or broken.

The Command

A plugin command needs to extend SfdxCommand - I inadvertently left the name of mine as Org, which is the name for the command created by the plugin generator, but thus far it hasn’t caused me any problems.

export default class Org extends SfdxCommand {

and as I will be connecting to an org I’ll need to enable that. I also want this to be executed from a SalesforceDX project, as the name of the custom setting and the specific field to write the commit id to can be overridden through the sfdx-project.json file:

 protected static requiresUsername = true;
 protected static requiresProject = true;

I then fetch the configuration from the project configuration file, supplying defaults if no config is found:

const projectJson = await this.project.retrieveSfdxProjectJson();
const settingConfig = _.get(projectJson.get('plugins'),'bb.gitSetting');
const settingName=settingConfig.settingName || 'Git_Info__c';
const commitIdField=settingConfig.commitIdField || 'Commit_Id__c';

I then query the setting from the org, as if there is already one I need to update it with it’s id:

const query = 'Select Id, SetupOwnerId, ' + commitIdField + ' from ' + settingName + ' where SetupOwnerId = \'' + orgId + '\'';

interface Setting{
  Id? : string,
  SetupOwnerId : string,
  [key: string] : string
}

// Query the setting to see if we need to insert or update
const result = await conn.query<Setting>(query);

let settingInstance : Setting;
settingInstance={"SetupOwnerId": orgId};

if (result.records && result.records.length > 0) {
  settingInstance.Id=result.records[0].Id
}

Note that I define a typescript interface for the custom setting. As I don’t know the name of the field that the commit id will be stored in, I just define the generic [key:string] rather than a specific name.

Then I get the current commit id and fill that in on the settingInstance:

const util=require('util');
const exec=util.promisify(require('child_process').exec);

const {error, stdout, stderr} = await exec('git rev-parse HEAD');

const commitId=stdout.trim();
settingInstance[commitIdField]=commitId;

Then I had to figure out how to insert or update the custom setting record. Checking the Org class of the Salesforce DX Core library didn’t show me many options, but after a short while I realised that the core library is built on top of jsforce, and sure enough Connection extends jsforce.Connection which had the CRUD commands that I needed:

let opResult;
if (settingInstance.Id) {
  opResult=await conn.sobject(settingName).update(settingInstance);
}
else {
  opResult=await conn.sobject(settingName).insert(settingInstance);
}

this.ux.log('Done');

if (!opResult.success) {
  this.ux.log('Error updating commit id' + JSON.stringify(opResult.errors));
}
else {
  this.ux.log('Stamped the Git commit id in the org');
}

and finally I output a JSON format result string so that if I need to use it in a script I can:

return { orgId: this.org.getOrgId(), 
         commitId: commitId, 
         success: opResult.success, 
         errors: opResult.errors };

Something I wasn’t expecting was that every time I made code changes, simply executing the plugin caused it to be recompiled, which was a real time saver.

Publishing

Once I had my plugin working, I then had to figure out how to publish to the npm.js registry - once again I hadn’t done this before so it was another opportunity to learn.  Luckily this is well documented so it wasn’t as hard as I was expecting. There’s a little bit of hoop jumping around creating the releases on Github, but I’d recently been through at least this aspect with my training system.

Take it for a Spin

To try out the plugin, follow the instructions on the npm page. If you’d like to dig into the code, it's available at the Github repo.

Related Posts