jq and multi-field output to CSV

jq is some of the most underrated tools out there I think. It’s a command line JSON parser that makes it super easy to work with JSON on the command line and in turn makes developing small SalesforceDX tools a breeze. Today I needed to generate a CSV file of all fields from different objects for the integration team that doesn’t have access to Salesforce. Doing the describe is easy using the Salesforce REST API but when using jq different are usually on different lines like below (-r is a nifty switch for getting raw, unquoted strings).

$ sfdx force:schema:sobject:describe -s Account -u myorg --json | jq -r ".result.fields[] | .label, .name"
Age
Age__pc
PO Box
PO_Box__pc
Postal Code Before City
Postal_Code_Before_City__pc
Street No Before Street
Street_No_Before_Street__pc
Archived State
Archived_State__pc

The output is almost what I wanted but really wanted not  to have to edit the file manually to build the output. Some quick googling and it appears that jq supports both CSV and tabular output from arrays. So fixing the issue was as simple as follows:

$ sfdx force:schema:sobject:describe -s Account -u myorg --json | jq -r ".result.fields[] | [.label, .name] | @csv"
"Age","Age__pc"
"PO Box","PO_Box__pc"
"Postal Code Before City","Postal_Code_Before_City__pc"
"Street No Before Street","Street_No_Before_Street__pc"
"Archived State","Archived_State__pc"

This is so cool… Love it!!

Bash one-liner for Apex test coverage percentage using SalesforceDX

Update 3 May 2018: There are issues with the percentages reported by SalesforceDX plus it doesn’t report coverage on classes with 0% coverage which will shrew the results. The approach outlined above can be used as an indication but cannot as of today be used as a measure for code coverage when it comes to production deployments. As an example I’ve had the above snippet report a coverage of 88% where as a production deploy reported 63% coverage. We – Salesforce – are aware of the issue and are working to resolve it. Stay tuned!

Note to self – quick note on how to run all tests in a connected org (as identified by the -u argument) and use jq and awk to grab the overall test coverage percentage.

$ sfdx force:apex:test:run -u mheisterberg@example.com.appdev -c -w 2 -r json | jq -r ".result.coverage.coverage[].coveredPercent" | awk '{s+=$1;c++} END {print s/c}'
> 88.1108

YMMV!

 

Salesforce username/password OAuth flow against a sandbox

We had issues today because our OAuth password flow wouldn’t work against one of our sandboxes although the code worked against production. Instead we got this error:

{"error":"invalid_grant","error_description":"authentication failure"}

After Googling and finding this thread it turned out that when using the username/password flow against a sandbox you have to either relax IP restrictions for login or authenticate against test.salesforce.com instead of login.salesforce.com (which of course makes sense).

Below is curl commands for using the username/password flow against a sandbox:

$ curl -d "grant_type=password 
   &client_id=3MVG9X0_oZyBSzHrnzENlR...JSDz0_MiwxyieREuBhtgZJrF7Lzx8542TFpU_ 
   &client_secret=6235860963257688256 
   &username=mikkel.heisterberg%40example.com.sandboxname 
   &password=Passw0rd.SecurityToken" https://test.salesforce.com/services/oauth2/token
{"access_token":"00D6E000000Cpmu!AQ0AQIj4...cGCRqmNnYc6dmgLT09VNoIFXJtHvsPGLqrBs0VlK",
   "instance_url":"https://someaddress.my.salesforce.com",
   "id":"https://test.salesforce.com/id/00D6E000000CpmuUAC/0056E000000OCcCQAW",
   "token_type":"Bearer","issued_at":"1485850119972","signature":"malODIaSULh1siHzdw...pHKjBpWoQcm66UQ="}

$curl -H 'Authorization: Bearer 00D6E000000Cpmu!AQ0AQIj4...cGCRqmNnYc6dmgLT09VNoIFXJtHvsPGLqrBs0VlK' 
   https://test.salesforce.com/id/00D6E000000CpmuUAC/0056E000000OCcCQAW
{"id":"https://test.salesforce.com/id/00D6E000000CpmuUAC/0056E000000OCcCQAW",
   "asserted_user":true,"user_id":"0056E000000OCcCQAW","organization_id":"00D6E000000CpmuUAC",
   "username":"mikkel.heisterberg@example.com.sandboxname","nick_name":"mheis",
   "display_name":"Mikkel Heisterberg","email":"mheisterberg@foo.com","email_verified":true,"first_name":"Mikkel",
   "last_name":"Heisterberg","timezone":"Europe/Paris","photos":{"picture":"https://someaddress.content.force.com/profilephoto/005/F",
   "thumbnail":"https://someaddress.content.force.com/profilephoto/005/T"},"addr_street":null,"addr_city":null,"addr_state":null,
   "addr_country":null,"addr_zip":null,"mobile_phone":"+45 12345678","mobile_phone_verified":true,"status":{"created_date":null,
   "body":null},"urls":{"enterprise":"https://someaddress.my.salesforce.com/services/Soap/c/{version}/00D6E000000Cpmu",
   "metadata":"https://someaddress.my.salesforce.com/services/Soap/m/{version}/00D6E000000Cpmu",
   "partner":"https://someaddress.my.salesforce.com/services/Soap/u/{version}/00D6E000000Cpmu",
   "rest":"https://someaddress.my.salesforce.com/services/data/v{version}/",
   "sobjects":"https://someaddress.my.salesforce.com/services/data/v{version}/sobjects/",
   "search":"https://someaddress.my.salesforce.com/services/data/v{version}/search/",
   "query":"https://someaddress.my.salesforce.com/services/data/v{version}/query/",
   "recent":"https://someaddress.my.salesforce.com/services/data/v{version}/recent/",
   "profile":"https://someaddress.my.salesforce.com/0056E000000OCcCQAW",
   "feeds":"https://someaddress.my.salesforce.com/services/data/v{version}/chatter/feeds",
   "groups":"https://someaddress.my.salesforce.com/services/data/v{version}/chatter/groups",
   "users":"https://someaddress.my.salesforce.com/services/data/v{version}/chatter/users",
   "feed_items":"https://someaddress.my.salesforce.com/services/data/v{version}/chatter/feed-items",
   "feed_elements":"https://someaddress.my.salesforce.com/services/data/v{version}/chatter/feed-elements",
   "custom_domain":"https://someaddress.my.salesforce.com"},"active":true,"user_type":"STANDARD","language":"en_US","locale":"en_US",
   "utcOffset":3600000,"last_modified_date":"2017-01-26T13:49:33.000+0000","is_app_installed":true}

Quick CLI aliases for Salesforce instance info

Today when at a customer we were trying to figure out the release versions the various sandboxes and development environments and compare this info with the version of the production instance. Of course all this is available from status.salesforce.com but clicking around got a bit tedious so I whipped up a quick set of bash aliases for the Terminal. Since the output is also available as JSON it was pretty easy. First I grabbed the jq JSON parser for bash and then I wrote 4 aliases for bash:

  • sf_instance – returns the instance ID (e.g. “na44”) from the hostname. This is useful if you are using a custom domain to access the instance e.g. lekkim-trailhead-dev-ed.my.salesforce.com
  • sf_release – shows only the release the instance is on e.g. “Spring ’17 Patch 5.5”
  • sf_status – shows release version, whether active or not and type of instance (production, sandbox etc)
  • sf_status_raw – returns the raw JSON

Below are examples of usage and the ailases are at github.com/lekkimworld/sf_aliases.

$ sf_help
Salesforce CLI actions:
-----------------------
- sf_status
  Shows status for instance ID
  Syntax : sf_status 
  Example: sf_status na44

- sf_release
  Shows release version of supplied instance ID
  Syntax: sf_release 
  Example: sf_release na44

- sf_instance
  Get instance ID from hostname
  Syntax: sf_instance 
  Example: sf_instance org62.lightning.force.com

- sf_status_raw
  Shows raw JSON
  Syntax: sf_status_raw

------------------------
$ sf_instance na44.salesforce.com
na44
$ sf_instance lekkim-trailhead-dev-ed.my.salesforce.com
eu11
$ sf_release eu11
Winter '17 Patch 18.10
$ sf_release na44
Spring '17 Patch 5.5
$ sf_status eu11
Release Version : Winter '17 Patch 18.10
Active          : true
Status          : OK
Environment     : production
$ sf_status cs85
Release Version : Spring '17 Patch 6
Active          : true
Status          : OK
Environment     : sandbox