Sunday, September 27, 2020

Salesforce: Send List Email

To continue our previous blog Send and Log Email from Salesforce, in this blog, we will share how to send mass emails from a ListView. Why I specifically mentioned ListView? because ListView is simple yet powerful feature in Salesforce, your user can create their own list view easily (define columns and filters), and by combining this to mass email, it would be very productive. Imagine that your rep needs to mass email only to Contact with a certain value, such as Lead Source is "webinar", they can easily create a list view for this.

Salesforce by default provides a button called Sent List Email, and the admin needs to check user permission and make sure it is enabled for your users. 

Users need to have "Allow sending of List Emails" permission, otherwise, they will not see this button, if you are enabling this permission, it will also enable 3 other permissions related to this permission: Send Email, Edit Task, and Mass Email.

Enable Button
Admin needs to make sure that "Sent List Email" is enabled, go to the Lead or Contact object, then edit the Search layout. As of Winter '21 release, this option is still under "Search Layouts for Salesforce Classic"

Email Limits
In the previous blog Send and Log Email from Salesforce, we mentioned when sending email from Salesforce, we can use email templates, merge fields, attach files, signature, and so on, the same feature is available when sending mass email from "Sent List Email". 

The difference is, you will see a limit tell you on how many emails can be sent, and this limit is for the whole user in the org., not for the individual user. As per this article, we can send 5,000 external emails per day based on GMT.

Save as Draft
Another difference when using "Sent List Email", offers "Save as Draft", when composing your email, but unable to send it yet because of something else, instead of discard that email, you can save it as a draft. You will not see this when sending email from a record. See the button before Send in the above screenshot.

How to access email save as draft? Open the "List Emails" tab (from 9-dots if you do not see it), open the email composed, then click Edit. 

You will see the status of the email sent, name, owner, and etc.

Notification Email
After all of the emails sent, you will get an email notification from Salesforce

Email icon
Another difference of email sent via "Sent List Email" is the icon of email under Activities, instead of the standard envelope icon, the icon for email sent with "Sent List Email" is multiple envelopes.

As per this blog EmailMessage object, emails will be stored under EmailMessage object with prefix 02s, however, email sent via "Sent List Email" will be stored in a different object called ListEmail with the prefix of 0XB.

Send Later
The Winter '21 release offers an additional feature for email sent via "Sent List Email", users can select the email to be sent later, set a perfect date and time when the email will be sent.


Send and Log Email from Salesforce

Email is a very basic thing in our daily work, we can send emails from Google or Outlook, then manually log the email to a record Salesforce for the future reference.

Email logged to Salesforce will appear as under the activities component. If we logged it against Contact, it will appear in the Account too, this is because of the special parent-child relationship between Account and Contact.

Salesforce also offers the capability to send emails directly from the platform, and the emails sent will be automatically logged to the Contact or Lead.

If you notice that email sent from the Salesforce will be tracked; if the recipient opens the email, we will see when it opened. While email sent from Outlook and manually logged to Salesforce will have no such tracking information in Salesforce.

Salesforce offers the below features when sending an email:

  • rich-text format
  • image
  • link
  • quick text
  • attach file
  • merge field from Salesforce
  • template
  • preview
  • popout to dock view
  • signature (from user Settings >> Email >> My Email Settings), check out this and this article to add image and link to your email signature

One thing to note when sending from Salesforce using standard configuration, if the recipient uses Gmail, they will see the email is sent via Salesforce.

To understand how email is stored in Salesforce, check out this blog EmailMessage object.

Note: this blog is written without Inbox and EAC configured. 


Monday, August 10, 2020

Einstein Analytics: Deploying Conditional Formatting

When you deploy the Einstein Analytics dashboards contain conditional formatting using ANT or Change Set or simply copy and paste the dashboard JSON, conditional formatting will not follow, you need to manually configure the conditional formatting again in the target environment manually.

This is because conditional formatting in the dashboard is stored under dashboard XMD, so if you deploy the dashboard XMD, then conditional formatting will be deployed.

In this blog, I will use Workbench to deploy this.

1. Create package.xml file

<?xml version="1.0" encoding="UTF-8"?>

<Package xmlns="">







Test_Conditional_Formatting in above sample is the dashboard API name, you can check dashboard API name from Workbench too

2. Retrieve dashboard XMD file

From workbench login to source org. > migration > retrieve, select package.xml prepared in step (1) >  click Next button > click Retrieve button > download zip file

3. Deploy Zip file

Make sure the dashboard has been created/deployed in the target org. From workbench login to target org. > migration > deploy > select zip file from step (2) > click Next button > click Deploy button 

Now open the dashboard in target org and the conditional formatting has been applied.

Sunday, August 9, 2020

Salesforce Lookup Filter

Lookup is a very simple feature in Salesforce, but also powerful, with just a few clicks you can relate an object to another object easily as a parent-child relationship.

However, in some business needs, the system should NOT look up ALL parent records, here is the scenario between Account (parent) and Case (child):

  • Case External --> Account Type = Customer
  • Case Internal --> Account Type = any

External and Internal is Case record type, and Type is the standard Account field. Of course, we can use validation rule to block Case creation for record type = External, but validation rule does not offer the best experience, as users will not easily know if an Account is a Customer or not.

Good that Salesforce offers Lookup Filter criteria and even better you can define the logic. For the above requirement, here my lookup filters. 

Make sure to enable the lookup filter.


Wednesday, July 1, 2020

Salesforce: Sharing Rules via Public Group & Role

Sharing records using Public Groups may or may not rollup via role hierarchies, this is determined by the object setup in Organization-Wide Defaults and also Public Groups setup.

By default, Salesforce standard objects will grant access via hierarchies, meaning users with higher role hierarchy will be able to view or edit records where the records are viewable or editable by any users below that user in the role hierarchy.

While for custom objects, you can configure the  "Grant Access Using Hierarchies" under Organization-Wide Defaults for that object.

In the Public Group setting, you also can configure to enable "Grant Access Using Hierarchies". Let us see a few scenarios as below:

1. Sharing to Public Group for objects enabled for "Grant Access Using Hierarchies"

1A. Public Group is "Grant Access Using Hierarchies" enabled
This will share records to users with higher role hierarchy.

reason for the user in Public Group

reason for the user with higher role hierarchy

Group Staff 2 only has 1 user which is Song Lee, when I expand the list, it will show all users able to access the record. From the screenshot below, Jack Bob is the record owner, while Maria Ann and Platform User 1 are users with roles higher than Song Lee's in the role hierarchy.

1B. Public Group is not "Grant Access Using Hierarchies" enabled
Let us use the same object as above, but share it to a different group which is without enabled "Grant Access Using Hierarchies". This will NOT share records to users with higher role hierarchy. 

** This is applicable for standard objects too

2. Sharing to Public Group for objects NOT enabled for "Grant Access Using Hierarchies"

2A. Public Group is "Grant Access Using Hierarchies" enabled
In this testing, we are using a custom object not enabled for "Grant Access Using Hierarchies", then we create a sharing rule to share with the same group with Grant Access Using Hierarchies as in (1B).

The result, this share will NOT grant access to the users in the higher role hierarchy.

2B. Public Group is not "Grant Access Using Hierarchies" enabled
The result of this sharing is exactly the same with (2A), where he users in the higher role hierarchy are NOT shared.

3. Sharing to Roles for objects enabled for "Grant Access Using Hierarchies"
The result is similar to (1A), users in the higher role hierarchy will get access.

4. Sharing to Roles for objects NOT enabled for "Grant Access Using Hierarchies"
The result is similar to (2A), users in the higher role hierarchy NOT will get access.

Sunday, June 14, 2020

Einstein Analytics: Working with Multi-Pages Dashboard

Einstein Analytics offers multi-pages dashboard feature, where we can design the pages similar one to another, or totally different across pages. If the new page similar to an existing page, you can simply clone the page. 

Clone Page
Click the arrow next to the page name (the default page name is "Untitled") and select Clone.

This will create a new page located at the last with page name "Clone of xyz", xyz is the original page name.

But what actually happened at the backend?
Page is defined as pages key under gridLayouts, so when you clone a page:
  1. a new section will be added with label "Clone of xyz" under pages key
  2. the number of widgets in the new page section is exactly the same with the source page
  3. all the keys in the new widgets are exactly the same with the widgets in source page, including name

With this knowledge, you can easily move the page order and label as needed

If we open one of the widgets and looks for the "name" key, as mentioned above, it would be the same, and name here actually is widget ID when you see it in dashboard designer UI.

For our sample of "chart_3", in addition to "name" key in the widget, it also has: 
  • colspan
  • column
  • row
  • rowspan
  • widgetStyle

While the widget details such as color, label, action menu, etc. are defined in the "widgets" section. So "chart_3" from gridLayouts refer to "chart_3" under widgets, then further, "chart_3" in "widgets" refer to a step (which is query) called "Dept_1".

Checkout below screenshot from the dashboard designer for widget ID = "chart_3", if you click Query tab, the ID should be "Dept_1".

From above screenshot, the red arrow tells us that the widget is used on multiple pages, so if we change anything on the widget, it will change for the same on the other page using the same widget, e.g. title, font size, background color, etc. 

Now, let us click unlink and see what will happen, before that let us summarize:
  • chart_3 widget used in Page1A and Page1B page
  • chart_3 widget use step "Dept_1"

Click unlink will change the widget name on that page from chart_3 to something else such as chart_4, however, the query/step remain the same, now if you change the widget background color in one page, it will not change the other one, although the step used still the same. Step refers to the data query from a dataset.

What happened on the backend?
  1. The "name" on page on where we clicked unlink, the widget name under gridLayouts will change to the new name, and no change for other pages.
  2. In the "widgets" section, there will be a new widget added "chart_4", it uses the same step use in "chart_3", which is "Dept_1".

A few more things worth to note:
1. If you remember that we mentioned row, rowspan, column, colspan of the widget in page section; this mean, location and size of linked widgets in a page can be different from other pages.

2. A page can be clone multiple times, from the original or cloned page. Unlink a widget from a page only applicable for that page, the widgets could be still linked in other pages.

3. For a widget has been unlinked to use different query (such as from different dataset), the step needs to be changed manually.

4. You can use faceting (include/exclude) if need to prevent selection in one page not to impact result in other pages.

Benefits using pages compare to using many individual dashboards:
For dashboard builders:
- it is cleaner and easier to maintain only one dashboard, instead of many dashboards in Analytics Studio.
- ability to re-use the same step across pages.

For end-users, working only with one tab, opening a new "page" will not open as new tabs, including when bookmarking the dashboard.

Copy Widget
Einstein Analytics also offers another capability for us to clone widget, select the widget, and click the "Copy Widget" icon. So, what is behind the screen?
  1. Create a new step
  2. Create a new widget, and assigned with step created (1)
  3. Add the widget created (2) to the current page

Compare this to the Clone tab:
  • No new steps created
  • No new widgets created
  • Create a new page under gridLayouts

Tuesday, May 26, 2020

What is Salesforce Marketing Cloud?

Written by Alina Makarova, Growth Marketer at DESelect. DESelect is an app for Salesforce Marketing Cloud that makes it easy for marketers to segment without relying on SQL.  

Salesforce Marketing Cloud (SFMC) is a product of Salesforce, which is the biggest CRM platform in the world. It enables marketers to get to know customers, personalize, and engage with them during their customer journey. It provides an opportunity to engage with customers using various digital marketing channels: email, web, mobile, social, or digital advertising. 

What else can you do with Salesforce Marketing Cloud?

First off, it is possible to create unified views of every customer combining known and unknown customer profiles. Moreover, SFMC allows measuring and reporting to optimize your marketing performance and achieve better campaign results while improving your customer relationship. Lastly, you can take your marketing to the next level by enabling Salesforce Einstein’s artificial intelligence capabilities.

What does Salesforce Marketing Cloud consist of?

There are several built-in apps in SFMC that help to get a 360 view of your customers and communicate with them at every level. Here you can see how these apps look like in SFMC itself:

Below you can find a quick description of the main functions of these apps.


Name of the app


What is it used for?

Content Builder

Create content for your emails, in-app messages, push notifications.

Journey Builder

Deliver cross-channel personalized experiences at every step of the customer lifecycle with campaign management.

Interaction Studio

Visualize, track, and manage customer experiences with real-time interaction management—driving valuable engagement at the right moment.

Email Studio

Use data from every department to build smarter email—from basic marketing campaigns to sophisticated 1-to-1 messages.

Mobile Studio

Send consistent SMS, push, and chat app messages in real-time.

Advertising Studio

Use CRM to securely power 1-to-1 advertising across Google, Facebook, LinkedIn, Twitter, Pinterest, and Display at scale.

Social Studio

Listen, publish, and engage to create customer advocates. Connect social to marketing, sales, and service in one platform powered by AI.

Web Studio

Allows you to create Cloud pages.


Einstein Email and Web Recommendations power delivery of relevant content based on prior user behavior.

Analytics Studio

Allows to get deep insights into the behaviors and interests of your contacts across channels. You can use these insights to set marketing goals and refine customer journeys.

Beyond the above-mentioned apps, there are many possibilities to expand the capabilities of the Salesforce Marketing Cloud. On AppExchange you can find various apps that can be added to your SFMC instance. For example, DESelect enables you to segment your customers easily without the need to rely on SQL which is the default approach in SFMC. 

Who is Salesforce Marketing Cloud made for?

If you need to create long-term relationships with your customers, then SFMC is a great solution for you. It enables multichannel communication, and possibilities to track, analyze, and report the received data to create a 360 view of an individual customer. It is primarily used for B2C marketing purposes. 

Moreover, SFMC always changes and evolves opening new horizons for marketers to communicate with their customers. Lastly, the apps found in AppExchange make the platform even more powerful, aiming to save marketers time and improve their user experience.

Wednesday, May 13, 2020

Einstein Analytics: Compare Table functions

Compare Table is another powerful and easy to use feature in Einstein Analytics to meet your business requirements, you do not need to manually construct the SAQL or edit with JSON. It allows you to aggregate data into a table (or chart) based on formulas defined.

You can use familiar SAQL syntax to create your own formula for a column of multiple columns in a table or visualize it to a chart. 

On top of manually building custom formula with SAQL, Einstein Analytics also provide defined windowing functions to analyze data across rows.

Sliding Window
Applies an aggregate function to the current row with respect to a configurable range of rows.
- Column: source data (a column)
- Function: Average, Sum, Min, Max
- Start: start row to analyze, -1 mean 1 previous row from the current row
- End: end row to analyze, 0 mean current rows, 1 mean 1 row after current row
- Reset Group: if only you have more than 1 grouping, you can reset by 

Let us see a sample:

This function compares the current value with 1 previous row (start = -1, and end = 0), then get the Max value. 

Once saved, we will see the formula
max(A) over ([-1..0] partition by all order by ('Opty.CloseDate_Year~~~Opty.CloseDate_Month~~~Opty.CloseDate_Day'))

Now, let us add Account Name as 2nd level grouping without adding Reset Group

Because there are 3 accounts on 13-May-2017, the row explodes to 3 lines, but the logic still the same, which is comparing max value between the current and previous row. In this change, the formula will not change.

Now, let us add Reset Group = Opty.CloseDate

The formula will be changed by adding Account Name (in this sample the API name is "Name")
max(A) over ([-1..0] partition by all order by ('Opty.CloseDate_Year~~~Opty.CloseDate_Month~~~Opty.CloseDate_Day','Name'))

Because Close Date added as Reset Group, windowing functions only compare within the same date.

Percentage of Group
Calculates the percentage each row is of its group total, or of the grand total. The percentage only applicable for data shown in the table, NOT for the whole data in dataset (if you apply filter).

The formula: A/sum(A) over ([..] partition by all)

The function can be reset on a grouping defined in the table, this is the same with Sliding Window, you need to have minimum 2 grouping to apply Reset Group.

The formula: A/sum(A) over ([..] partition by 'Opty.CloseDate_Year~~~Opty.CloseDate_Month~~~Opty.CloseDate_Day')

Rank Within Group
There are 4 functions offered by Rank Within Group:
  • Rank: assigns rank based on order. Repeats rank when the value is the same, and skips as many on the next non-match
  • Dense Rank: same as rank() but doesn’t skip values on previous repetitions.
  • Cumulative Distribution: calculates the cumulative distribution (relative position) of the data in the reset group
  • Row-number: assigns a number incremented by 1 for every row in the reset group.

Formulas for each function:
  • Rank: rank() over([..] partition by all order by A desc)
  • Dense Rank: dense_rank() over([..] partition by all order by A desc)
  • Cumulative Distribution: cume_dist() over([..] partition by all order by A desc)
  • Row Number: row_number() over([..] partition by all order by A desc)

The Order menu determines the direction of ranking based on the values being ranked. Ascending ranks the lowest value as number 1, while descending ranks the highest value as number 1. Same as Sliding Window and Percentage of Group function, we can add Reset Group in Rank Within Group.

Period Over Period
To use Period Over Period, the table must be group by Date field. Period Over Period function compare periods of time to calculate changes in values, for example: year-over-year, quarter-over-quarter, month-over-month, week-over-week, or day-over-day.

Then, we have option to show the result as: % Change or Unit Change

Formula for % Change
(A - sum(A) over ([-1..-1] partition by all order by ('Opty.CloseDate_Year~~~Opty.CloseDate_Month~~~Opty.CloseDate_Day')))/(sum(A) over ([-1..-1] partition by all order by ('Opty.CloseDate_Year~~~Opty.CloseDate_Month~~~Opty.CloseDate_Day')))

Formula for Unit Change
A - sum(A) over ([-1..-1] partition by all order by ('Opty.CloseDate_Year~~~Opty.CloseDate_Month~~~Opty.CloseDate_Day'))

Period Over Period do not offer Reset Group.

Change from Previous
Compares the value of the current row with that of the previous row and calculates the difference. This function similar with Period Over Period, but you do not need to group by Date. 

Change from Previous offer Reset Group function and similar with Period Over Period, we have option to show the result as: % Change or Unit Change.

Formula for % Change
(A - sum(A) over ([-1..-1] partition by all order by ('Opty.CloseDate_Year~~~Opty.CloseDate_Month~~~Opty.CloseDate_Day')))/(sum(A) over ([-1..-1] partition by all order by ('Opty.CloseDate_Year~~~Opty.CloseDate_Month~~~Opty.CloseDate_Day')))

Formula for Unit Change
A - sum(A) over ([-1..-1] partition by all order by ('Opty.CloseDate_Year~~~Opty.CloseDate_Month~~~Opty.CloseDate_Day'))

Above table looks very similar with Period Over Period, but Change from Previous can be implemented to any grouping, not just Date over a period.

Running Total
Calculates the total value of the current row summed with all previous rows. 

Running Total formula without Reset Group: sum(A) over ([..0] partition by all order by ('Opty.CloseDate_Year~~~Opty.CloseDate_Month~~~Opty.CloseDate_Day'))

Running Total also offer Reset Group if you have more than 1 grouping in table. Let us see in sample with and without reset Group:


Sunday, May 10, 2020

Einstein Analytics: computeExpression return Date

In some scenarios, we need to return a Date field from computeExpression node. 

Date value can be null
case when ClosedDate_sec_epoch > date_to_epoch(now()) then toDate(ClosedDate,"yyyy-MM-ddTHH:mm:ss.000Z") end
case when ClosedDate_sec_epoch > date_to_epoch(now()) then toDate(ClosedDate_sec_epoch) end

Date or Date/Time
For a Numeric field, Precision and Scale are required, while for a Date field, Date Format is required. Einstein Analytics by default will "explode" a Date field into:
- Year
- Quarter
- Month
- Week
- Day
- Hour
- Minute
- Second
- Epoch days
- Epoch seconds

Let us see some samples of computeExpression returning Date type. In this sample, ClosedDate is a Date/Time field in Salesforce:

(1) SAQL Expression: toDate(ClosedDate,"yyyy-MM-ddTHH:mm:ss.000Z")
Date Format: yyyy-MM-ddTHH:mm:ss.000Z

(2) SAQL Expression: toDate(ClosedDate_sec_epoch)
Date Format: yyyy-MM-ddTHH:mm:ss.000Z

(3) SAQL Expression: toDate(ClosedDate,"yyyy-MM-ddTHH:mm:ss.000Z")
Date Format: yyyy-MM-dd

(4) SAQL Expression: toDate(ClosedDate,"yyyy-MM-dd")
Date Format: yyyy-MM-ddTHH:mm:ss.000Z
this will cause error because date format with HH:mm:ss, while toDate() format string parameter does not have.

(5) SAQL Expression: toDate(ClosedDate_sec_epoch)
Date Format: yyyy-MM-dd

(6) SAQL Expression: ClosedDate
Date Format: yyyy-MM-dd
this will cause error, we can't copy directly even it is a date field 

(7) SAQL Expression: now()
Date Format: yyyy-MM-dd
this is okay, it will return UTC date and time

As the screenshot above, the date format determines how the values stored in Einstein Analytics. But, let us drill further:

Even hour, minute, and second are not added in the date format then not shown in the date field, Einstein Analytics still maintain hour, minute, and second in the exploded fields.

yyyy-MM-ddTHH:mm:ss.000Z is similar with yyyy-MM-ddTHH:mm:ss.SSSZ and similar with yyyy-MM-dd'T'HH:mm:ss.SSS'Z'

Now, let us try to use Date field from Salesforce, instead of Date/Time field. CloseDate here is a Date field in Salesforce:

(1) SAQL Expression: toDate(CloseDate,"yyyy-MM-dd")
Date Format: yyyy-MM-ddTHH:mm:ss.000Z

(2) SAQL Expression: toDate(CloseDate_sec_epoch)
Date Format: yyyy-MM-ddTHH:mm:ss.000Z

(3) SAQL Expression: toDate(CloseDate,"yyyy-MM-ddTHH:mm:ss.000Z")
Date Format: yyyy-MM-dd
this will cause error

(4) SAQL Expression: toDate(CloseDate_sec_epoch)
Date Format: yyyy-MM-dd

(5) SAQL Expression: toDate(CloseDate,"yyyy-MM-ddTHH:mm:ss.000Z")
Date Format: yyyy-MM-ddTHH:mm:ss.000Z
this will cause error

(6) SAQL Expression: toDate(CloseDate,"yyyy-MM-dd")
Date Format: yyyy-MM-dd

Because there is no time in the source field, so all HH mm ss will be 00.

In summary:
  • for SAQL Expression, it is easier to use toDate(xxxx_sec_epoch) 
  • for Date Format, use yyyy-MM-ddTHH:mm:ss.000Z for Date/Time, or use yyyy-MM-dd for Date field

Side Note: can we refer a field in computeExpression within the same note?
Yes, as long as the field referred is located above (in order) from the field call it, see screenshot below, CloseDate_2 can use CloseDate_1, but not CloseDate_4 and CloseDate_6.

Wednesday, May 6, 2020

Einstein Analytics: Hidden Step

Hidden step probably not the official term in Einstein Analytics, but you probably heard when having a conversation with Einstein Analytics experts with many years of experience, furthermore, Salesforce no longer calls it "step", but now it calls "query".

So, what is the hidden step? it is a query that used to support other queries, but not directly uses for widgets. The query can be in compact form, Salesforce Direct, SAQL, and SOQL.

As mentioned above, we can use a hidden step to support other queries, in this case, usually, we use "result" binding, remember selection binding will be trigger when user change/select something.sim

Sample SOQL query:
 "QueryLoginUser_1": {
                "groups": [],
                "numbers": [],
                "query": "SELECT Name,Id FROM User Where Name = '!{User.Name}'",
                "selectMode": "single",
                "strings": [],
                "type": "soql"

use it in a "real" query:
         "filters": [

Sample SAQL query:
q = load "Quote_History";
q = filter q by 'StatusChange' == "Created to In Progress";
q = group q by all;
q = foreach q generate avg('Cycle_Time') as 'avg_Cycle_Time';
q = foreach q generate avg_Cycle_Time as 'ave_seconds', floor(avg_Cycle_Time / 3600) as 'hours', floor((avg_Cycle_Time / 60) - (floor(avg_Cycle_Time / 3600) * 60)) as 'minutes';
q = foreach q generate ave_seconds as 'ave_seconds', hours as 'hours', minutes as 'minutes', floor(ave_seconds - ((3600 * hours) + (60 * minutes))) as 'seconds';
q = limit q 2000;

If you open in JSON, it would become
"cycle_time_1": {        
"query": "q = load \"Quote_History\";\nq = filter q by 'StatusChange' == \"Created to In Progress\";\nq = group q by all;\nq = foreach q generate avg('Cycle_Time') as 'avg_Cycle_Time';\nq = foreach q generate avg_Cycle_Time as 'ave_seconds', floor(avg_Cycle_Time / 3600) as 'hours', floor((avg_Cycle_Time / 60) - (floor(avg_Cycle_Time / 3600) * 60)) as 'minutes';\nq = foreach q generate ave_seconds as 'ave_seconds', hours as 'hours', minutes as 'minutes', floor(ave_seconds - ((3600 * hours) + (60 * minutes))) as 'seconds';\nq = limit q 2000;"}

use it in a "real" query
"text": "{{column(cycle_time_1.result, [\"seconds\"]).asObject()}}",


Thursday, April 23, 2020

Einstein Analytics: Parent with and without Child dataflow

This is regarding the parent-child relationship.

So here the needs:
  • if 0 child found --> 1 row
  • if 1 child found --> 1 row + child fields
  • if >1 child found --> number of child row + child fields 

Is there a way to achieve the following in Einstein Analytics dataflow?

In Salesforce, this is pretty easy using a report type:

But, Einstein Analytics is a different story. The granular level should come from an object. However, we always can find ways to get things done.

So here is the solution by Roman Michalik from Salesforce:
  • It's starting at Child level and giving you one row per child. 
  • Then it's checking for additional parents and adding those parents to the list (with blank child fields).
Here is the sample JSON for dataflow.

Note: to use this dataflow, Parent ID must be unique.

Another option is to use Recipe with Left Join.


Tuesday, April 14, 2020

myTrailhead Introduction

My experience and journey with myTrailhead

Rebecca “Becky” Sweetman, Global Business Operations Manager, Adstream
Salesforce Singapore User Group and SG Women in Tech member
Twitter @BeckySweetman3

What is myTrailhead and how it is different from Trailhead?

myTrailhead is a modern gamified online learning enterprise platform tailored for your company that allows you to leverage the power of Trailhead. It allows you to create personalized self-paced integrated learning journeys, efficient and consistent onboarding - reducing ramp-up time to accelerate time to revenue - increases employee productivity, drives adoption of resources to maximize their investment, and cultivates a developmental company culture. It isn’t just to train Salesforce tools either, as we use it for internal training of our own products and services, soft sales skills, etc. I also use it to manage  Salesforce releases and introduce the tools that matter to them. 

Users can proudly show off badge-swag on their profile and analytics can show the best and most diligent learners. Internal badges will only be visible inside myTrailhead, not on the public site.

You will require at least a standard Salesforce user license (sales cloud, service cloud or platform) and then an added myTrailhead license. The charge comes per user as a package and that is the only fee - except if want to add on Premier Support. The advertised price is $25 USD per user per month annual billing. $2/login or $5/member for Customers and Partners

myTrailhead uses a separate personal domain to Trailhead for user access and uses a connector user in Salesforce to send analytical data to Trail Tracker. Users login with Salesforce credentials to this domain but auto-links with any pre-existing so everything is retained in one place and you will still retain and view your ranks, points, and badges in one profile.

myTrailhead Features

myTrailhead comes with its own set of features that work together with the Salesforce platform.
Trailmaker is the tool to create, preview and release the content. You can have as many users access content creation as you wish as this is not limited and just a matter of permission sets.

myTrailhead has the ability to embed videos, images, and tables, and you can easily copy/paste your content. It also allows you to add quiz questions. For anyone familiar with Trailhead – it is basically, the same format.

You can use Trailhead content in Trailmixes to publish inside trails too, but these cannot be modified further. Trailmixes can also link to external content hosted elsewhere to bring this into your training journey.

Trailmaker allows you to preview your content and send it to others to view prior to publish and you can easily add, delete or update the content at any point in a separate Releases tab. In this tab, once you easily upload your content to the Release it will inform you of any errors (cannot publish with these) or warnings (not ideal but will still allow you to publish) prior to publication. 

Same as in Trailhead, users can track their own way through the modules and trails with a countdown of time left until completion. 

You or anyone you give permission rights to can add your own company logo and banner to myTrailhead and add your brand’s color so it feels familiar and personalized for you. 

You will need to create your own badge art which can be stored in the Documents object (accessible in Classic only right now). You can assign modules (badges) and Trailmixes. This enables it to come upfront and center on their myTrailhead home page and once complete will show in your reports.

Trail Tracker is your tool to track completion for users, and it syncs daily at a time of your choice. It also comes with a set of standard Reports and Dashboards which you can customize. Examples include Total Number of Badges Assigned, Number of Badges Awarded This Month and a Leaderboard of Badges and Points.

You can also add the badges component to Profiles for users and their managers to see their progress.

Finally, you can use the power of Process Builder to set up and automate an onboarding or learning journey for your users. 

Your users can also access myTrailhead from the Trailhead Go app for training on the run, currently available for iOS devices only.

Tips and Recommendations

This tool works perfectly for our global workforce and their training needs. However, here are a few tips and gotchas I wished I had known at the start of my journey configuring and using myTrailhead.
You do not need an implementation partner. It is very easy to enable and set up myTrailhead. As soon as your contract is signed you just need to wait for an email from them directly. You also need to install Trail Tracker with the use of a connector user. The time resource is in the content creation itself and adapting it for the system.

As previously mentioned, permissions are all done through permission sets in Salesforce. You can give certain users permission to also preview content prior to go-live for that particular trail or module and permission to add or change your branding. No extra cost or limits on numbers of each of these.

Onboarding them I found a little more complex.  The users don’t automatically get an alert when they are assigned permissions of course, nor to introduce them to the myTrailhead system and if they are not already set up in Trailhead, you need to wait for them to login and add their own profile before you can assign anything or the assignment sync will fail for them. You also will not know they have created this profile unless they tell you. The best steps I found for this process is: 
  1. Offer users to login to your myTrailhead link
  2. Have them login with their own Salesforce credentials to enable the connection to both systems (not to login with just their email or will just set up a public profile and won’t link the permissions).
  3. Set up their own profile (if they do not already have a Trailhead profile)
  4. Then inform you so you can assign to them with Trail Tracker if applicable
You can assign in bulk using Trail Tracker but if one person is not set up it will fail all. Refer to your included “Debug Log” report to view any sync errors. 

myTrailhead is not a traditional LMS, it is a learning experience system LXP, so you cannot use LMS files and may have problems shifting from a different LMS system, but it does hold videos, tables, images and links to other hosted external content. Because of this, it is best to get executive buy-in straight away and make the content creators realize this is not a system to throw just bullet points and PowerPoints, this is a journey and the content should be in the format of the story with examples, not short form. I find the best thing to do is have them set up and undertake a module in Trailhead to gather what the journey will be like.

myTrailhead is not a high maintenance system for admins, but the content creation often takes a lot of time, so prepare the team for this resource outlay. Also, have someone available to edit it for voice and spelling/grammar and make sure objectives given to meet the content and questions. Also, schedule a regular session to update and add new content so it does not become stale.

For content creation, you need to use your own version control or content management system such as Quip or Google docs to keep up with changes and involve others then update content in myTrailhead. This is not provided with myTrailhead, although you can download ‘backpacks’ of your files for backup and make changes and re-upload.

It is easy to copy/paste directly into Trailmaker but there are only one font and 2 sizes (header and body text).

API names for each unit, module, and trail need to be unique across all of Trailhead or errors may show.

Badge art needs to meet certain specs and as you use a URL to add it to Trailmaker if the URL is not shortened it can send an error and not sync. Use Bitly or similar to shorten the link. There is a myTrailhead Badge Style guide here:

If you deactivate a user and reactivate them, all their badges will still be retained.

You can set internal Trailmixes which will not be seen in public Trailhead, however, you cannot yet set controls around who can see or do these in myTrailhead – so, for example, anyone with a myTrailhead license in your company can search for or find a module and do the training. They will come up in reports that they have completed it, whether assigned or not.

Make use of automation like Process Builder and add badge recommendations to the in-app guidance.

It is still early days in myTrailhead, only launched last year, so flexibility in the tool can appear to be a bit limited. For example, you cannot change role, level, tags, products, etc. in the modules, but these are on the roadmap. There is no Playground as they have on Trailhead, but it is on the roadmap. Also, Trail Tracker only syncs once daily at this stage but looking forward to when we can get it on demand.

Reports will pull all Trailhead and myTrailhead data to reports for you. Filter on namespace or URL subdomain value to get internal-only data. For managers to see the report data you need to give them at least read access to Trailhead-related objects and user data.

Further Information and Training

Search for “myTrailhead Basics” Trailhead Trail

Quip doc for myTrailhead and Trail Tracker:

Separate logging of support cases (although some cases need to route through usual Salesforce help)

This is just the beginning of our journey with myTrailhead. I am always keen to talk to others and hear tips and recommendations from those with similar or other experiences. Please feel free to reach out to me on LinkedIn, Salesforce Community or Twitter.

Happy Trails! 

Page-level ad