Author Archives: Ezra Kenigsberg

Unknown's avatar

About Ezra Kenigsberg

#Salesforce architecture/data guru & @BigCommerce employee; film noir buff; expatriate Jewish New Yorker; proud papa

Useful Takeaways from Spring ’23 (Part 1 in a Series, Hopefully)

1] Hey, the new Migrate Process to Flow utility works!
It still has a list of Things It Won’t Do, but at least at first blush, it took one hairy legacy Process and converted it to Flow (apparently) correctly. hm!

2] Hell yah, relative values have come to Reporting! Check it out:

Of course the documentation says it’s only available in Lightning, but who ever totally trusted the documentation.

Further updates as events warrant!

Convert 15-Character to 18-Character IDs in Spreadsheets without Code

Q: “How do I convert a 15-character Salesforce ID into an 18-character ID in a spreadsheet like Excel or Google Sheets, without code?”

A: Use the formula below. This formula assumes your 15-character ID is in cell A2.

=A2 & 
MID( 
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345", 
    AND(CODE(MID(A2,5,1))>64,CODE(MID(A2,5,1))<91) * 16 + 
    AND(CODE(MID(A2,4,1))>64,CODE(MID(A2,4,1))<91) * 8 + 
    AND(CODE(MID(A2,3,1))>64,CODE(MID(A2,3,1))<91) * 4 + 
    AND(CODE(MID(A2,2,1))>64,CODE(MID(A2,2,1))<91) * 2 + 
    AND(CODE(MID(A2,1,1))>64,CODE(MID(A2,1,1))<91) * 1 + 1, 
    1) & 
MID( 
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345", 
    AND(CODE(MID(A2,10,1))>64,CODE(MID(A2,10,1))<91) * 16 + 
    AND(CODE(MID(A2,9,1))>64,CODE(MID(A2,9,1))<91) * 8 + 
    AND(CODE(MID(A2,8,1))>64,CODE(MID(A2,8,1))<91) * 4 + 
    AND(CODE(MID(A2,7,1))>64,CODE(MID(A2,7,1))<91) * 2 + 
    AND(CODE(MID(A2,6,1))>64,CODE(MID(A2,6,1))<91) * 1 + 1, 
    1) & 
MID( 
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345", 
    AND(CODE(MID(A2,15,1))>64,CODE(MID(A2,15,1))<91) * 16 + 
    AND(CODE(MID(A2,14,1))>64,CODE(MID(A2,14,1))<91) * 8 + 
    AND(CODE(MID(A2,13,1))>64,CODE(MID(A2,13,1))<91) * 4 + 
    AND(CODE(MID(A2,12,1))>64,CODE(MID(A2,12,1))<91) * 2 + 
    AND(CODE(MID(A2,11,1))>64,CODE(MID(A2,11,1))<91) * 1 + 1, 
    1)

If you’re curious how this logic works, you can check out Alan Shanahan’s wonderful original post about it from 2010.

And if for some reason you’re allergic to the Salesforce formula function CASESAFEID(), you can use the logic I created in this blog post from 2011.

And while I’m at it, I recommend everyone makes a copy of this wonderful Field Utilities for Salesforce Administrators, Architects, and Developers spreadsheet for tools that can help you

  • change 15-character IDs to 18-character IDs
  • convert Field UI Labels (with characters and spaces) into Field API Names (with underscores)
  • generate XML for creating new fields and
  • fix the capitalization of broken 18-character IDs

Flow gets a little less bad

Salesforce.com’s annoying half-a-loaf Flow capability* got a little less egregious with the release of Spring ’22’s Flow Trigger Explorer, which helps an admin visualize the sequencing of Flows:

* Q: “Ezra, how can you think Flow is half-assed when it’s got so many cool toys?”

A: I’m a cynical person. Also, I said “half a loaf”, not “half-assed”. But either will do.

Some things I’d like Salesforce.com to address:

  • should I put all of one object’s automation in a single Flow? lots of different Flows?
  • can I be confident that Flow doesn’t fail in weird, opaque ways?
  • how do I migrate to Flow when there are a million shortcomings in the “Migrate to Flow” tool? namely (courtesy of Salesforce’s Migrate to Flow Tool Considerations help topic):

How do I turn off cloud Jira’s keyboard shortcuts?

Q: “If I type before a cloud Jira page loads completely, Jira thinks I’m trying to use keyboard shortcuts (like ‘unassign ticket’!). This is super-dangerous. How do I fix this?

A: Turn off keyboard shortcuts in cloud Jira by following these two steps:

  1. From any cloud Jira page, type a questionmark. This should bring up the Keyboard Shortcuts window:
Cloud Jira Keyboard Shortcuts pop-up window

2. Turn off the “Keyboard shortcuts are enabled” toggle.

(Thanks to Atlassian’s Matt Tse for the tip.)

What the heck are all these Chat settings?

Q: “What the heck are all these Chat settings?”

A: Great question. Salesforce implemented Chat with so many weird objects squirreled away in so many obscure corners.

I assume that’s what happens when they acquire companies left and right and have neither the time nor the inclination to make it tidier!

Save Me from “Last Activity”!

Q: “When was this Salesforce record last worked?”

A: If you mean “when was an Activity last completed for it?”, I got a great solution for you.

But first, make sure NOT to use Salesforce’s built-in “Last Activity” field (API Name: LastActivityDate). It behaves really strangely, like allowing “Last Activity” to occur in the future.1

(I’ve got a full tirade about it. Once you’re done with that, come on back here to implement a Better Mousetrap.)

1) Create a new Activity Custom Field, with Type “Formula (Date/Time)”, named Date/Time MARKED Complete, with the formula

IF(IsTask,
   CompletedDateTime,
   IF(ActivityDateTime < NOW(),
      ActivityDateTime,
      NULL))

2) Create a new Activity Custom Field, with Type “Formula (Checkbox)”, named “Has Date/Time MARKED Complete?“, with the formula2

NOT(ISBLANK(Date_MARKED_Complete__c))

3) Create a new Activity Custom Field, with Type “Formula (Number(0))”, named Days Since MARKED Complete, with the formula2

IF(Has_Date_Time_MARKED_Complete__c,
   NOW() - Date_Time_MARKED_Complete__c,
   999)

4) In a Report that has both Activities and the object you’re rolling up to (like Accounts), create a Custom Summary Formula (I named it Days Since Last Activity MARKED Complete) with this formula

IF(Activity.Has_Date_Time_MARKED_Complete__c:MAX = 1,
   Activity.Days_Since_MARKED_Complete__c:MIN,
   NULL)

You’re done! The Custom Summary Formula field enables reporting when someone last completed an Activity for an Account, Contact, Opportunity, Lead, Case, etc. . .

“Activities with Accounts” Report, with Custom Summary Field “Days Since Last Activity MARKED Complete”

1
Q: How can Last Activity Date occur in the future?
A: Salesforce Help explains that Last Activity Date is either

  • The latest Due Date of the Closed Tasks on a record, or
  • The latest Date of Events on a record.

Out-of-box, Salesforce allows both

  • completed Tasks to have Due Dates in the future or
  • Events to have Dates in the future

. . . so either Tasks or Events can render, say, an Opportunity’s “Last Activity” in the future. wtf.

2
Q: Why are “Has Date/Time MARKED Complete?” and “Days Since MARKED Complete” needed?
A:
Two reasons:

  1. Reports can’t roll dates up to parent records, so we need to convert “Date MARKED Complete” into the numeric “Days Since MARKED Complete”.
  2. Reports’ Custom Summary Formulas treat nulls as zeroes, so we need an arbitrary, large error value–I’m using 999 here–to disqualify null “Days Since MARKED Complete” from MIN calculations.

“Where Is this Used?”

Q: “How do I run ‘Where Is this Used?’ on all custom fields at once?”

A: Jupyter notebook spfy-where-used!

spfy-where-used leverages the “Simple Salesforce” Python library and Tooling API calls to generate a spreadsheet of all custom fields’ “Where Is this Used?” results.

Update the config.py file in the resources subdirectory to reflect your org’s username, password, and security token.

Sadly, “Where Is this Used?” doesn’t cover Workflow or Data Validation. 😐

Bucketing by Hour (Not Day)

For reports that need to bucket by hours—not days, weeks, or months—create a formula like this.

  • It returns a date+time string in 'YY: MM/DD HHh format (like '22: 03/23 09h), which is the longest string I could fit in a graph label without Salesforce truncating it.
  • It uses the American schedule for Daylight Saving. I haven’t yet tweaked it for European Daylight Saving, which diverges from American DS for about six weeks each year
  • It’s formatted for the the United States’ Central time zone—if you’re in a different US timezone, tweak the 5/24 and 6/24 terms
IF(
    DATEVALUE(CreatedDate) >= DATE(YEAR(DATEVALUE(CreatedDate)), 3, 1) + 
    (14 - CASE(MOD(DATE(YEAR(DATEVALUE(CreatedDate)), 3, 1) - 
    DATE(1900, 1, 7), 7), 0, 7, MOD(DATE(YEAR(DATEVALUE(CreatedDate)), 3, 1) - DATE(1900, 1, 7), 7)))
    &&
    DATEVALUE(CreatedDate) < DATE(YEAR(DATEVALUE(CreatedDate)), 11, 1) + 
    (7 - CASE(MOD(DATE(YEAR(DATEVALUE(CreatedDate)), 11, 1) - 
    DATE(1900, 1, 7), 7), 0, 7, MOD(DATE(YEAR(DATEVALUE(CreatedDate)), 11, 1) - DATE(1900, 1, 7), 7))),
    
    "'" & MID(TEXT(CreatedDate - 5/24), 3, 2) & ": " & 
        MID(TEXT(CreatedDate - 5/24), 6, 2) & "/" & MID(TEXT(CreatedDate - 5/24), 9, 2) & " " & 
            MID(TEXT(CreatedDate - 5/24), 12, 2),
    
    "'" & MID(TEXT(CreatedDate - 6/24), 3, 2) & ": " & 
        MID(TEXT(CreatedDate - 6/24), 6, 2) & "/" & MID(TEXT(CreatedDate - 6/24), 9, 2) & " " & 
            MID(TEXT(CreatedDate - 6/24), 12, 2)
) & "h"

The first clause in the huge IF statement determines whether we’re on Daylight Saving Time

The second clause returns the Daylight-Saving-formatted version of the date/time

The third clause returns the non-Daylight-Saving-formatted version of the date/time