Tuesday, December 4, 2018

Finding Your Classic Dashboards AND other dashboard info

This utility can be used for the purposes of finding Classic dashboards in need of upgrade before end of life AND can also help you easily identify the following dashboard related information: Creator Name, Datasets included (label/name/id), Files, App (Folder - label/name/id), url, indication if it is classic and the label/name/id of the dashboard. You have the option to create a CSV to download, or automatically generate a dataset named Dashboard_Info in the shared app.

About Classic - If you have used Einstein Analytics for more than a few years, then you may have some classic dashboards floating around. Currently if you edit a classic dashboard you will see a popup referencing the end of life program, if you don't see that message, you are editing a new flex dashboard that is fully supported! 



If you have a lot of Classic Dashboards, you might be looking for them programmatically. The following steps will allow you to either download a CSV with a column indicating which dashboards are classic - or create a dataset. Because we have the dashboard IDs you could augment this information into the adoption app to be able to prioritize which classic dashboards you should repair.

These instructions are for google chrome, but the standard bookmarklet functionality SHOULD work for other browsers as well. This code is provided as is without any warranty. The code will use your active session ID to perform these processes. It is recommended to run this script on a sandbox instance.

WARNING: This does not check for the existence of a dataset named "Dashboard_Info" and will overwrite that dataset if it does exist.

1) Within the Chrome browser application - from the "View" menu, make sure the "Always Show Bookmarks" option is activated. 

2) Drag the following link onto the bookmarks bar (It appears that some versions of chrome may not support the drag install for bookmarklets and you should manually copy and paste the "javascript url" behind the link below into your bookmarks or follow more detailed instructions here: https://mreidsma.github.io/bookmarklets/installing.html )


3) Login into your instance and go to the Analytics Studio

4) Click the bookmarklet and it will launch a background task. Once it has the list of dashboards you can access the bookmarklet will begin to log progress in the console (you can look there if it looks like nothing is happening). It may take a few minutes to complete based on the number of dashboards in your instance.

5) Once complete you will see a popup, you may create the dataset (it will require 1 row per dashboard queried) - or you can create a file to download. Clicking "Ok" will create a dataset, clicking "Cancel" will generate a link to CSV data you may download.

6) If you selected a dataset, you will have a new dataset named "Dashboard_Info" typically within 15 seconds unless the process needs to be queued. If selected the file download you will see a link that looks like the following and clicking it will automatically trigger the file download.




7) Now you may either explore the dataset or CSV file and the column or dimension named "likely_classic" will indicate which dashboards are classic.


And the raw code if you want to edit or paste directly into the console (while you are in Analytics studio)


(function () {

function getServerSid () {
var server = window.location.href.replace(/https?:\/\//,"").split("/")[0];
var sid = document.cookie.match(/(^|;\s*)sid=(.+?);/)[2];
var shell_sid = sid.replace(/!/g,"\\!");
return {"server": server, "sid": sid, "shell_sid": shell_sid};
}

function secureSFDCHeaderPrep (sid) {
return function (xhr) {
xhr.setRequestHeader('Authorization', "OAuth " + sid);
}
}

function createAjaxErrorResponse (deco_text) {
return function (jqXHR, text_status, error_thrown ) {
console.error("Error (INFO:", deco_text, ")" , "'jqXHR':", jqXHR, "'text status':", text_status, "'error thrown':", error_thrown)
}
}

function formatBytes (bytes,decimals) {
   if(bytes == 0) return '0 Bytes';
   var k = 1000,
       dm = decimals + 1 || 3,
       sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
       i = Math.floor(Math.log(bytes) / Math.log(k));
   return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}

var api_count = 0;
var dataset_arr = [];
var dataset_idx = 0;
var full_results_arr = [];
var api = "v43.0/";
var proto = "https://";
var base_path = "/services/data/";
var operation = "wave/query";
var credentials = getServerSid();
var em_alias = 'Dashboard_Info';
var metadata_json = {
  "fileFormat" : {
    "charsetName" : "UTF-8",
    "fieldsDelimitedBy" : ",",
    "fieldsEnclosedBy" : "\"",
    "linesTerminatedBy" : "\n",
    "numberOfLinesToIgnore" : 1
  },
  "objects" : [ {
    "name" : em_alias,
    "fullyQualifiedName" : em_alias,
    "connector" : "SalesforceAnalyticsCloudDatasetLoader",
    "label" : em_alias,
    "description" : em_alias,
    "rowLevelSecurityFilter" : null,
    fields: []
  }

  ]
};

// ADDED
var dashboard_arr = []

var dashboard_fields = ["id", "label", "name", "createdBy.name", "datasets[x].id", "datasets[x].label", "datasets[x].name", "files[x].id", "folder.id", "createdDate", "lastAccessedDate", "lastModifiedDate", "refreshDate", "url", "folder.label", "folder.name", "likely_classic"];

function extDataField (field_name, field_label, field_description, type, precision, scale, defaultValue, format, isMultiValue, multiValueSeparator) {
var res = {
      "name" : field_name,
      "fullyQualifiedName" : field_name,
      "label" : field_label,
      "description" : field_label,
      "type" : type,
      "precision" : precision,
      "scale" : scale,
      "decimalSeparator" : ".",
      "defaultValue" : defaultValue,
      "format" : format,
      "isSystemField" : false,
      "isUniqueId" : false,
      "isMultiValue" : isMultiValue,
      "multiValueSeparator" : multiValueSeparator,
      "fiscalMonthOffset" : 0,
      "firstDayOfWeek" : -1,
      "isYearEndFiscalYear" : true,
      "canTruncateValue" : true,
      "isSkipped" : false
    };
return res;
}

function launchDataflow (id) {
post_data = {
Action: 'Process'
}
$.ajax({
type: "PATCH",
data: JSON.stringify(post_data),
    processData: false,
    contentType: "application/json; charset=utf-8",
url: proto + credentials.server + base_path + api + "sobjects/InsightsExternalData/" + id,
beforeSend: secureSFDCHeaderPrep(credentials.sid),
error: createAjaxErrorResponse("Posting InsightsExternalData dataset"),
success: function (data) {
console.info("You should now have a dataset with dashboard info:", em_alias);
}
});
}

var csv_header = "id,label,name,createdBy_name,datasets_id,datasets_label,datasets_name,files_id,folder_id,createdDate,lastAccessedDate,lastModifiedDate,refreshDate,url,folder_label,folder_name,likely_classic\n";

function createDataPart (id) {
var post_data = {
DataFile: btoa(csv_header + full_results_arr.join("\n")),
InsightsExternalDataId: id,
PartNumber: 1
}

$.ajax({
type: "POST",
data: JSON.stringify(post_data),
    processData: false,
    contentType: "application/json; charset=utf-8",
url: proto + credentials.server + base_path + api + "sobjects/InsightsExternalDataPart/",
beforeSend: secureSFDCHeaderPrep(credentials.sid),
error: createAjaxErrorResponse("Posting InsightsExternalDataPart - PART"),
success: function (data) {
launchDataflow(id);
}
});
}


function createDataset () {
api_count++;
var inputs = [
{name: "id", type: "Text", precision: 255, scale: 0, defaultValue: null, format: null, isMultiValue: false, multiValueSeparator: null},
{name: "label", type: "Text", precision: 255, scale: 0, defaultValue: null, format: null, isMultiValue: false, multiValueSeparator: null},
{name: "name", type: "Text", precision: 255, scale: 0, defaultValue: null, format: null, isMultiValue: false, multiValueSeparator: null},
{name: "createdBy_name", type: "Text", precision: 255, scale: 0, defaultValue: null, format: null, isMultiValue: false, multiValueSeparator: null},
{name: "datasets_id", type: "Text", precision: 255, scale: 0, defaultValue: null, format: null, isMultiValue: true, multiValueSeparator: "|"},
{name: "datasets_label", type: "Text", precision: 255, scale: 0, defaultValue: null, format: null, isMultiValue: true, multiValueSeparator: "|"},
{name: "datasets_name", type: "Text", precision: 255, scale: 0, defaultValue: null, format: null, isMultiValue: true, multiValueSeparator: "|"},
{name: "files_id", type: "Text", precision: 255, scale: 0, defaultValue: null, format: null, isMultiValue: true, multiValueSeparator: "|"},
{name: "folder_id", type: "Text", precision: 255, scale: 0, defaultValue: null, format: null, isMultiValue: false, multiValueSeparator: null},
{name: "createdDate", type: "Date", precision: 0, scale: 0, defaultValue: null, format: "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", isMultiValue: false, multiValueSeparator: null},
{name: "lastAccessedDate", type: "Date", precision: 0, scale: 0, defaultValue: null, format: "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", isMultiValue: false, multiValueSeparator: null},
{name: "lastModifiedDate", type: "Date", precision: 0, scale: 0, defaultValue: null, format: "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", isMultiValue: false, multiValueSeparator: null},
{name: "refreshDate", type: "Date", precision: 0, scale: 0, defaultValue: null, format: "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", isMultiValue: false, multiValueSeparator: null},
{name: "url", type: "Text", precision: 255, scale: 0, defaultValue: null, format: null},
{name: "folder_label", type: "Text", precision: 255, scale: 0, defaultValue: null, format: null},
{name: "folder_name", type: "Text", precision: 255, scale: 0, defaultValue: null, format: null},
{name: "likely_classic", type: "Text", precision: 255, scale: 0, defaultValue: null, format: null}
]
for (var i=0;i<inputs.length;i++) {
var field = extDataField(inputs[i].name, inputs[i].name, inputs[i].name, inputs[i].type, inputs[i].precision, inputs[i].scale, inputs[i].defaultValue, inputs[i].format, inputs[i].isMultiValue, inputs[i].multiValueSeparator);
metadata_json.objects[0].fields.push(field);
}

if (!confirm("Please select OK to create a dataset, or cancel to acquire CSV data")) {
var csv_data_str = csv_header + full_results_arr.join("\n");
var blob = new Blob([csv_data_str], { type: 'text/csv' }); //new way
var csvUrl = URL.createObjectURL(blob);

function remove_self () {
$(this).remove();
}

var size_str = formatBytes(csv_data_str.length, 1);
var anchor_class = "csv_download";
if ($('.' + anchor_class).length != 0) $('.' + anchor_class).remove();

var anchor = null;
anchor = $("<a></a>", {
"href": csvUrl,
"download": "dashboard_data.csv",
"text": "Download the data - " + size_str,
"class": anchor_class,
"click": function () {setTimeout( function () {anchor.remove();} , 250)}});
$('.system-bar').append(anchor);

return;
}

var post_data = {
Format: "Csv",
EdgemartAlias: em_alias,
Operation: "Overwrite",
MetadataJson: btoa(JSON.stringify(metadata_json)),
Action: 'None'
}

$.ajax({
type: "POST",
data: JSON.stringify(post_data),
    processData: false,
    contentType: "application/json; charset=utf-8",
url: proto + credentials.server + base_path + api + "sobjects/InsightsExternalData/",
beforeSend: secureSFDCHeaderPrep(credentials.sid),
error: createAjaxErrorResponse("Posting InsightsExternalData dataset"),
success: function (data) {
var id = data.id;
createDataPart(id);
}
});
}

// This only works for 1 or 2 levels
// field, field.field, field[x].field
function getJsonValueForDottedKey (data, key) {
var arrs = key.split("[x]");
var dotted = key.split(".");
if (arrs.length == dotted.length && arrs.length == 1) {
if (key.indexOf("Date") != -1 && (data[key] == "undefined" || !data[key])) return "";
return data[key];
} else if (arrs.length == 2) {
var ret_vals = [];
for (var i=0; i < data[arrs[0]].length; i++) {
ret_vals.push(getJsonValueForDottedKey (data[arrs[0]][i], arrs[1]));
}
return ret_vals.join("|");
} else if (dotted.length == 2 && dotted[0] == "") {
if (dotted[1].indexOf("Date") != -1 && (data[dotted[1]] == "undefined" || !data[key])) return "";
return data[dotted[1]];
} else if (dotted.length == 2) {
if (dotted[1].indexOf("Date") != -1 && (data[dotted[1]] == "undefined" || !data[key])) return "";
return data[dotted[0]][dotted[1]];
} else {
console.error("Unexpected request for dotted key");
}
}

function finalProcessDashboardData() {
for (var i=0; i<dashboard_arr.length; i++) {
var line = "";
for (var j=0; j < dashboard_fields.length; j++) {
line += getJsonValueForDottedKey(dashboard_arr[i], dashboard_fields[j]) + ",";
}
line = line.substring(0, line.length-1)
full_results_arr.push(line);
}
createDataset();
}

var DASHBOARDS_PROCESSED = 0;
var LOG = [];
for (var i=1;i<=100; i*=2) LOG.push(i);

function nextProcessDashboardData () {
if (DASHBOARDS_PROCESSED == dashboard_arr.length) finalProcessDashboardData();
$.ajax({
type: "GET",
url: dashboard_arr[DASHBOARDS_PROCESSED].url,
beforeSend: secureSFDCHeaderPrep(credentials.sid),
error: createAjaxErrorResponse("Getting dashboard details"),
success: function (data) {
dashboard_arr[DASHBOARDS_PROCESSED].likely_classic = (data.state.gridLayouts.length == 0);
var per_complete = Math.floor((DASHBOARDS_PROCESSED / dashboard_arr.length) * 100);
var idx = LOG.indexOf(per_complete);
if (idx != -1) {
console.info(per_complete + "% complete at", new Date().toLocaleTimeString());
LOG.splice(idx, 1);
}
DASHBOARDS_PROCESSED++;
nextProcessDashboardData();
}
})
}

var LOGGED = false;
function getDashboards (specified_url) {
if (!LOGGED) console.info("We started running the script to obtain dashboard info")
LOGGED = true;
api_count++
url = proto + credentials.server + base_path + api + "wave/dashboards";
if (specified_url && specified_url != "") url = specified_url;
$.ajax({
type: "GET",
url: url,
beforeSend: secureSFDCHeaderPrep(credentials.sid),
error: createAjaxErrorResponse("Getting dashboard list"),
success: function (data) {
dashboard_arr = dashboard_arr.concat(data.dashboards);
dataset_arr = dataset_arr.concat(data.datasets)
if (data.nextPageUrl && data.nextPageUrl != "") {
getDashboards(data.nextPageUrl);
} else {
nextProcessDashboardData();
}
}
})
}

getDashboards();

}());


Thursday, May 31, 2018

Key Utility Functions in Einstein Analytics

Short and simple...

I just wanted to pull together a few short videos that show some actions that are common in Analytics. I feel each has a tactical step or set of set of steps within Einstein Analytics that folks may not know are required to implement these solutions.





Change Count of Rows to "Count of Something"



How to create a bar line combo chart.





How to add images to a chart.





Enable the action framework.





Embed a dashboard.





Dynamic Grouping.





Dynamic Target Line.