R1D47 Completing the Sorting and Searching Algorithms Course in C#

Way back in day 22, I started the third course in the C# series on edX. It had to do with sorting and searching algorithms in C#. This made my brain hurt a bit, so I stepped away for a few days. Now we are on Day 47 and I got an email from edX telling me that the course was about to expire.

I jumped right back in and finished the sorting and searching module. My brain still hurts, but it was a good exercise.

The final lab project had to deal with the selection sort algorithm. The questions were pretty challenging. I took full advantage of the debugger in Visual Studio Code to step through this algorithm in order to figure out how it actually works.

My plan is to finish the entire course over the next few days so that edX does not keep sending me sad emails about how I am failing at life.

R1D46 Infinite Loops in Process Builder

Process Builder in Salesforce is a great way to do things based on some complex business logic without having to write triggers or a lot of the code yourself. As a trailblazer, I combined this with Queuable Apex that would grab the result of an HTTP POST method.

For some reason, this resulted in an infinite loop for me. I accidentally created 300+ Clubhouse tickets in a few seconds. The only way I could figure out how to stop this was to delete the opportunity record in order for the process to error out due to the opportunity no longer existing.

What I Learned

  1. More than ever, I wish that apex had feature flags. 🙂
  2. I have no idea how or why this happened, but the “solution” was to post the response URL to the salesforce record and then add a condition in the process builder to only run the process if the field was empty. This seems prone to failure, so we will see how things go.

I think ultimately the main issue is that I don’t have a deep understanding of how asynchronous apex actually works and I am probably going about solving this problem the wrong way.



R1D45 Queueable Apex

I am pretty sure that by the third week of any budding Apex developers journey they run into the following error in their code.

Error: ‘You have uncommitted work pending. Please commit or rollback before calling out.’

The help documentation does a decent job explaining what is happening. It turns out that if you have any DML in your code,  you cannot make a “callout” (HTTP call) in the same method.

Unfortunately, there is no way to “commit” in Apex. What they really mean, is you need to do this step asynchronously. There are two ways of doing this. Using the @future annotation (I still have no idea how this works) or using Queueable Apex.

This allows you to essentially schedule jobs on a separate “thread” and not block the other parts of salesforce while your code runs.

The thing that threw me off was that I was not actually doing any DML in my code. My code is triggered with a DB trigger when a record is inserted or updated. I assumed that since the record was already inserted or updated then it would also already be “committed”, but this turns out to not be the case and I suppose this rule applies to anything that has to do with the Salesforce database.

R1D44 What is GlassFish?

I jumped down another rabbit hole trying to figure out how to get started with java ee without using an ide. Although IDE’s are very handy when it comes to Java development, they also are sometimes a crutch. For instance, if you want to transition to CI, do you actually know what commands the IDE runs when you right click and run tests?

First, I have no idea what Java EE actually is. There is something called GlassFish, which is an open source Java EE “reference implementation”. It also the same thing that is installed when you go to the main Java EE website.

Java EE does not support the latest Java JDK 1.9. On my Mac I had a tough time trying to get two versions of Java to run at the same time.

I think 99.9% of all tutorials about getting started with Java EE include using Netbeans or Eclipse. I wanted to write one that used the CLI. This involves using maven.

Maven has a concept called “archetypes” which creates the necessary directory structure for a new Java project. The main problem is that I could not find a bare bones archetype definition.

At the end of the day, I dug deep into the rabbit hole and came up empty. I will figure this out at some point and write a blog post about it.

R1D43 Getting Hands Dirty with Apex

I started my Salesforce Trailhead journey so that I could solve real problems that we have on my team. I took everything that I learned over the last few weeks and started working on one of those problems.

Like most things in life, everything looks very simple when you see it presented in a tutorial format, and then when you start to get in the weeds things become a bit more complicated.

The problem that I am trying to solve is to send some data to a third party service any time a deal closes. This requires thinking about a few things.

  1. Ensuring validation when the state of an opportunity changes to closed won.
  2. Firing off an event when that happens.
  3. Taking the data from the opportunity and formatting it properly as JOSN to send to the third party system.
  4. Find all the files attached to the opportunity (this turned out to be a rabbit hole of epic proportions.)
  5. Firing off the HTTP POST to the third party system. This includes figuring out some sane way to store the token safely.

Outside of #4 (so far) I have everything else more or less working. I learned a whole lot about Apex doing this hands on exercise.


  • Very handy JSONGenerator class that makes it very easy to create well formatted JSON objects.
  • Static typing takes some getting used to.
  • Apex does not support multiline string formatting out of the box so you have to do things the old fashioned way.
String descriptionTemplate = 
  'Account Name: {0}\n' + 
  'Account Email: {1}\n' +
  'Plan: {2}\n' +
  'Plan Description: {3}\n' +
  'Invoice Amount: {4}\n' +
  'Invoice Frequency: {5}\n';

I am looking forward to wrapping this up and moving on to the next, slightly more complicated problem that I am trying to solve with Salesforce.

R1D42 levops

As I approach the midpoint of my 100 Days of Code journey, I realize that I have spent a whole lot of time learning and not as much time as I would like actually making things.

Naturally, I am going to add to my long list of unfinished projects by creating a meta project called levops. This will be a collection of hacky dev tools that I find useful and hope others do as well.

I started working on a couple of things today.

  1. Wheater — Webhook Eater? It is basically this blog post as a service.
  2. Crass — Cron as a Service; this is already a bit more complex than I would have liked. Should be fun though.
  3. Flask-Boiler — I love Flask. Every time I start a new project I find myself doing the same things over and over again. So I finally made some boilerplate that I will use for my projects going forward. Its very opinionated, but check it out if you are curious.

You can see these projects and whatever else I come up with in the levops org on GitHub.

I also started a site to keep track of all these things over here: https://levops.net/


R1D41 Debugging Webhooks with Flask and Ngrok

Webhooks are the magic glue that allows you to integrate many different applications together. Essentially a webhook sends a JSON payload of data to some pre-defined endpoint from one system to another.

When webhooks go wrong

While they are great, and usually easy to get started with, it can be a bit tough to debug them when things are not quite working as expected. Recently, there have been some issues with webhooks at CircleCI. There are many different failure modes since there are many different combinations of systems that are possibly involved.

The key questions to ask when debugging webhooks are:

  1. Was it sent?
  2. What did the payload look like?
  3. What payload does the receiving system expect?

I whipped up a dead simple Flask app and hooked up Ngrok for debugging purposes. This allows me to verify that the webhook is sent and also allows me to inspect the payload.

Answering question #3 will be left as an exercise to the reader since there is no way to have a one size fits all answer. This will depend strictly on the receiving system.

Using the Tool

The flask app is about as simple as it can get.

# app.py

from flask import Flask, request
import pprint

app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'GET':
        return 'Send me a POST'
        return 'Check Console'

This will “pretty print” the JSON payload to console. You can start this up with:

FLASK_APP=app.py && flask run

The app will be running on port 5000 locally. Then you can use ngrok to forward traffic from the internet to any local port on your computer.

Once you have downloaded and installed ngrok you can start it up with:

./ngrok http 5000

This will start up ngrok and start forwarding traffic to your local computer. In the terminal you should see a screen like this:

Ngrok running in a terminal

You can then use the “Forwarding” address anywhere on the web to send requests back to your local computer.

In our specific example, I would add the following section to my config.yml file in CircleCI in order to see the webhooks being sent from CircleCI.

    - url: https://a6f2fcd7.ngrok.io

With the app running, I can now receive and inspect webhooks locally in my console.

 * Serving Flask app "app"
 * Running on (Press CTRL+C to quit)
{u'payload': {u'all_commit_details': [{u'author_date': u'2018-03-10T10:24:08-08:00',
                                       u'author_email': u'lev@levlaz.org',
                                       u'author_login': u'levlaz',
                                       u'author_name': u'Lev Lazinskiy',
                                       u'body': u'',
                                       u'branch': u'webhooks',
                                       u'commit': u'4ee0d32afb7ff5e821593366616a660c9720b70e',
                                       u'commit_url': u'https://github.com/levlaz/sandbox/commit/4ee0d32afb7ff5e821593366616a660c9720b70e',
                                       u'committer_date': u'2018-03-10T10:24:08-08:00',
                                       u'committer_email': u'lev@levlaz.org',
                                       u'committer_login': u'levlaz',
                                       u'committer_name': u'Lev Lazinskiy',
                                       u'subject': u'test hooks'}],
              u'author_date': u'2018-03-10T10:24:08-08:00',
              u'author_email': u'lev@levlaz.org',
              u'author_name': u'Lev Lazinskiy',
              u'body': u'',
              u'branch': u'webhooks',
              u'build_num': 92,
              u'build_parameters': None,
              u'build_time_millis': 1419,
              u'build_url': u'https://circleci.com/gh/levlaz/sandbox/92',
              u'canceled': False,
              u'canceler': None,
              u'circle_yml': {u'string': u"version: 2\njobs:\n  build:\n    working_directory: ~/sandbox\n    docker:\n      - image: circleci/python\n    steps:\n      - checkout\n      - run:\n          name: Do Nothing\n          command: |\n            echo 'Hello!'\n\nnotify:\n  webhooks:\n    - url: https://7eab6a64.ngrok.io\n"},
              u'committer_date': u'2018-03-10T10:24:08-08:00',
              u'committer_email': u'lev@levlaz.org',
              u'committer_name': u'Lev Lazinskiy',
              u'compare': u'https://github.com/levlaz/sandbox/commit/4ee0d32afb7f',
              u'dont_build': None,
              u'fail_reason': None,
              u'failed': False,
              u'has_artifacts': True,
              u'infrastructure_fail': False,
              u'is_first_green_build': False,
              u'job_name': None,
              u'lifecycle': u'finished',
              u'messages': [],
              u'no_dependency_cache': False,
              u'node': None,
              u'oss': True,
              u'outcome': u'success',
              u'owners': [u'levlaz'],
              u'parallel': 1,
              u'picard': {u'build_agent': {u'image': None,
                                           u'properties': {u'build_agent': u'0.0.4732-ed067b9',
                                                           u'executor': u'docker'}},
                          u'executor': u'docker',
                          u'resource_class': {u'class': u'medium',
                                              u'cpu': 2.0,
                                              u'ram': 4096}},
              u'platform': u'2.0',
              u'previous': {u'build_num': 91,
                            u'build_time_millis': 1919,
                            u'status': u'success'},
              u'previous_successful_build': {u'build_num': 91,
                                             u'build_time_millis': 1919,
                                             u'status': u'success'},
              u'pull_requests': [],
              u'queued_at': u'2018-03-10T18:29:32.788Z',
              u'reponame': u'sandbox',
              u'retries': None,
              u'retry_of': 91,
              u'ssh_disabled': True,
              u'ssh_users': [],
              u'start_time': u'2018-03-10T18:29:34.680Z',
              u'status': u'success',
              u'steps': [{u'actions': [{u'allocation_id': u'5aa4240cc9e77c00013bc7e5-0-build/4C67169D',
                                        u'background': False,
                                        u'bash_command': None,
                                        u'canceled': None,
                                        u'continue': None,
                                        u'end_time': u'2018-03-10T18:29:35.593Z',
                                        u'exit_code': None,
                                        u'failed': None,
                                        u'has_output': True,
                                        u'index': 0,
                                        u'infrastructure_fail': None,
                                        u'insignificant': False,
                                        u'name': u'Spin up Environment',
                                        u'output_url': u'https://circle-production-action-output.s3.amazonaws.com/64c10c100094c9d1e0424aa5-levlaz-sandbox-0-0?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20180310T182936Z&X-Amz-SignedHeaders=host&X-Amz-Expires=431999&X-Amz-Credential=AKIAIQ65EYQDTMSJK2DQ%2F20180310%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=db22e946ba891c0c89503a3b0f5de635be92e6802e3941db778a198658f0afaa',
                                        u'parallel': True,
                                        u'run_time_millis': 877,
                                        u'start_time': u'2018-03-10T18:29:34.716Z',
                                        u'status': u'success',
                                        u'step': 0,
                                        u'timedout': None,
                                        u'truncated': False,
                                        u'type': u'test'}],
                          u'name': u'Spin up Environment'},
                         {u'actions': [{u'allocation_id': u'5aa4240cc9e77c00013bc7e5-0-build/4C67169D',
                                        u'background': False,
                                        u'bash_command': u'#!/bin/sh\nset -e\n\n# Workaround old docker images with incorrect $HOME\n# check https://github.com/docker/docker/issues/2968 for details\nif [ "${HOME}" = "/" ]\nthen\n  export HOME=$(getent passwd $(id -un) | cut -d: -f6)\nfi\n\nmkdir -p ~/.ssh\n\necho \'github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==\nbitbucket.org ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAubiN81eDcafrgMeLzaFPsw2kNvEcqTKl/VqLat/MaB33pZy0y3rJZtnqwR2qOOvbwKZYKiEO1O6VqNEBxKvJJelCq0dTXWT5pbO2gDXC6h6QDXCaHo6pOHGPUy+YBaGQRGuSusMEASYiWunYN0vCAI8QaXnWMXNMdFP3jHAJH0eDsoiGnLPBlBp4TNm6rYI74nMzgz3B9IikW4WVK+dc8KZJZWYjAuORU3jc1c/NPskD2ASinf8v3xnfXeukU0sJ5N6m5E8VLjObPEO+mN2t/FZTMZLiFqPWc/ALSqnMnnhwrNi2rbfg/rd/IpL8Le3pSBne8+seeFVBoGqzHM9yXw==\n\' >> ~/.ssh/known_hosts\n\n(umask 077; touch ~/.ssh/id_rsa)\nchmod 0600 ~/.ssh/id_rsa\n(cat < ~/.ssh/id_rsa\n$CHECKOUT_KEY\nEOF\n)\n\n# use git+ssh instead ofhttps\ngit config --global url."ssh://git@github.com".insteadOf "https://github.com" || true\n\nif [ -e /home/circleci/sandbox/.git ]\nthen\n  cd /home/circleci/sandbox\n  git remote set-url origin "$CIRCLE_REPOSITORY_URL" || true\nelse\n  mkdir -p /home/circleci/sandbox\n  cd /home/circleci/sandbox\n  git clone "$CIRCLE_REPOSITORY_URL" .\nfi\n\nif [ -n "$CIRCLE_TAG" ]\nthen\n  git fetch --force origin "refs/tags/${CIRCLE_TAG}"\nelse\n  git fetch --force origin "webhooks:remotes/origin/webhooks"\nfi\n\n\nif [ -n "$CIRCLE_TAG" ]\nthen\n  git reset --hard "$CIRCLE_SHA1"\n  git checkout -q "$CIRCLE_TAG"\nelif [ -n "$CIRCLE_BRANCH"]\nthen\n  git reset --hard "$CIRCLE_SHA1"\n  git checkout -q -B "$CIRCLE_BRANCH"\nfi\n\ngit reset --hard"$CIRCLE_SHA1"',
                                        u'canceled': None,
                                        u'continue': None,
                                        u'end_time': u'2018-03-10T18:29:36.075Z',
                                        u'exit_code': 0,
                                        u'failed': None,
                                        u'has_output': True,
                                        u'index': 0,
                                        u'infrastructure_fail': None,
                                        u'insignificant': False,
                                        u'name': u'Checkout code',
                                        u'parallel': True,
                                        u'run_time_millis': 415,
                                        u'start_time': u'2018-03-10T18:29:35.660Z',
                                        u'status': u'success',
                                        u'step': 101,
                                        u'timedout': None,
                                        u'truncated': False,
                                        u'type': u'test'}],
                          u'name': u'Checkout code'},
                         {u'actions': [{u'allocation_id': u'5aa4240cc9e77c00013bc7e5-0-build/4C67169D',
                                        u'background': False,
                                        u'bash_command': u"#!/bin/bash -eo pipefail\necho 'Hello!'\n",
                                        u'canceled': None,
                                        u'continue': None,
                                        u'end_time': u'2018-03-10T18:29:36.093Z',
                                        u'exit_code': 0,
                                        u'failed': None,
                                        u'has_output': True,
                                        u'index': 0,
                                        u'infrastructure_fail': None,
                                        u'insignificant': False,
                                        u'name': u'Do Nothing',
                                        u'parallel': True,
                                        u'run_time_millis': 13,
                                        u'start_time': u'2018-03-10T18:29:36.080Z',
                                        u'status': u'success',
                                        u'step': 102,
                                        u'timedout': None,
                                        u'truncated': False,
                                        u'type': u'test'}],
                          u'name': u'Do Nothing'}],
              u'stop_time': u'2018-03-10T18:29:36.099Z',
              u'subject': u'test hooks',
              u'timedout': False,
              u'usage_queued_at': u'2018-03-10T18:29:32.763Z',
              u'user': {u'avatar_url': u'https://avatars2.githubusercontent.com/u/7981032?v=4',
                        u'id': 7981032,
                        u'is_user': True,
                        u'login': u'levlaz',
                        u'name': u'Lev Lazinskiy',
                        u'vcs_type': u'github'},
              u'username': u'levlaz',
              u'vcs_revision': u'4ee0d32afb7ff5e821593366616a660c9720b70e',
              u'vcs_tag': None,
              u'vcs_type': u'github',
              u'vcs_url': u'https://github.com/levlaz/sandbox',
              u'why': u'retry'}} - - [10/Mar/2018 10:29:36] "POST / HTTP/1.1" 200 -

R1D40 Swagger and OpenAPI

I first learned about Swagger when I started working at LaunchDarkly since our REST API follows the OpenAPI specification. This is such an amazing tool. I am sad that I did not learn about it before.

Swagger and OpenAPI allow you to create a spec for your REST API. You can then use some tooling in order to create clients in pretty much every language under the sun for your API.

It took me a few solid days to write a python wrapper for the CircleCI API. In the same amount of time, I could have theoretically written a swagger spec and made clients for all the languages.

I spent a few hours today looking through the documentation for Swagger and learning more about tooling. Why doesn’t every product use this?

R1D39 Secrets in Salesforce

My entire Trailhead journey started because I wanted to write a couple of custom integrations for work. I made a working POC hacking together various bits and pieces of information that I found online, but taking a step back to actually learn how Salesforce works has been really great.

I am ready to make a non-hacky solution to the problem that I initially set out to solve. In order to get this to work though, I need some way to manage secrets within Salesforce so that I can securely store my API authentication token for the third party service that I am integrating with.

Luckily, there is Trailhead module on Secure Secret Storage in Salesforce.

Salesforce offers a feature called Named Credentials which offers a very straightforward way to manage secrets. Specifically those involving authenticating against a third party API.

Rather than hard-coding the value into your code, you can leverage named credentials to store secrets, allowing you to refer to the named credential to access the secret value, as if it were any other variable in your code.

Sadly, this did not seem to work for me because the API I was using expects a token in the URL rather than allowing for basic authentication.

There are a couple other strategies in place for storing secrets, but they seem like overkill for my specific project.

R1D38 SOQL and SOSL Queries in Apex

I’m working through the Apex Basics & Database module on Trailhead.

I learned about using built in sObjects and also about making custom sObjects. Next I learned about how to use DML in order to manipulate records within the system. After getting the basics down we dove into SOQL.


SOQL is shot for Salesforce Object Query Language. If you are familiar with SQL you will feel right at home. SOQL makes querying data very simple. One pretty poweful feature is the ability to run inline queries and return the results to a variable. In pretty much every other language you would need to create a cursor and iterate over the results in order to read the values from a database. In SOQL you simply assing the results to an array.

# Python Example 

conn = sqlite3.connect('example.db')
c = conn.cursor()
results = c.execute("SELECT Name, Phone FROM Accounts")

for row in results:
    print row
# SOQL Example 

Accounts[] accts = [SELECT Name, Phone FROM Account];

I think this bit trips up a lot of new programmers so it is great to see how SOQL makes it easy.

One thing worth noting is that you canno use  the handy “SELECT *” that is found in SQL.

Unlike other SQL languages, you can’t specify * for all fields. You must specify every field you want to get explicitly. If you try to access a field you haven’t specified in the SELECT clause, you’ll get an error because the field hasn’t been retrieved.

The built in developer console in Salesforce makes it very easy to run queries and inspect your objects without writing any additional code.

Cool things about SOQL:

  • you can pass in variables directly into a query
  • you can use dot notation to get related records in a query
  • you can use for loops in queries


SOSL is short for Salesforce Object Search Language and it allows you to do robust full text search on records similar to Apache Lucene. The main difference is that it allows you to search accross multiple objects instead of just a single one.

FIND 'SearchQuery' [IN SearchGroup] [RETURNING ObjectsAndFields]

In order to pass a variable into the search query, you use the colon symbol.

public class MultiSearch {

    public static List> searchAll (String searchTerm) {
        List> searchList = [FIND :searchTerm IN ALL FIELDS RETURNING Account(Name),Contact(FirstName,LastName)];
        return searchList;