Friday, March 1, 2019

Top 6 Reasons to Attend an EA+ Academy in 2019

Your analytics journey is about to begin, and I know the perfect place to start.

When I first started exploring Einstein Analytics+ (otherwise known as EA+), I wasn't really sure where to start. From navigating Analytics Studio to shaping my data, I felt like I was lost in a world of dashboards! That's when I decided to attend one of our Einstein Analytics+ Academies.

An EA+ Academy is a free, hands-on, learning experience for Analytics customers. We send our seasoned EA+ experts around the world to get our customers started on their analytics journeys. An Academy can be anywhere from one to two days, covering EA+ Basic (beginner to intermediate), EA Advanced (advanced), or ED Advanced (intermediate).

After two days of a deep dive into the navigation, dashboard properties, and compare tables, I was hooked, and I know you will be too. So, here are the Top 6 Reasons to Attend an EA+ Academy this year!


1. Jumpstart your EA+ Journey

Like I said- getting started can be tough! By joining along in one of our academy courses, you are led by a seasoned, analytics expert. This means your questions are answered on the spot and any hiccups that you might run into along the way can be uncovered and solved. Analytics are intricate, but there are endless possibilities about how data can change the way your organization operates. Hear about how Analytics can transform your business this year from our very own Analytics SVP & GM- Ketan Karkhanis!


2. Learn the Latest Release

The ACE Team (Analytics Center for Excellence) is responsible for teaching and creating curriculum for our Academy program. This means, we have some of the leaders and best maintaining our learning material to reflect release updates and changes. We thoughtfully craft exercises and team activities to help you navigate the EA+ Platform, as well as updates to the Salesforce Platform. Spring '19 Release details are being shared and taught in academies all over the world!

Looking for information on Spring '19? Check it out here!


3. Visualize Your Passions and Data

Have you ever seen your data perfectly visualized in a beautiful chart or table? Ugh. What a feeling. During my academy session, I learned how to utilize dashboard properties to build visualizations of my data. Of course, this turned into making a whole dashboard about something I love- PIE!

By collecting data on the ingredients I used and the ratings that my colleagues gave the pies, I was able to see how cost and rating combined could give me the data on which pie was the best value in terms of cost and rating!



Yum or YUM. Am I right?


4. Free Trainings, All Over the World

Did I mention that our academy sessions are FREE?! Yep, you heard it here first. We offer our customers top-of-the-line training at no cost to make sure that you are getting the absolute most out of EA+. Need we say more?




Oh, right. Swag and food provided 😉


5. Hands-On Exercises

Our Analytics documentation is AMAZING, but we are ready to dive in with you- head first! Our curriculum is packed with hands-on exercises. With a seasoned analytics expert guiding the way, you'll walk through use cases, simulations, and mini-projects to see how Einstein Analytics+ functions for you.

From updating color themes on your dashboard to perfecting your faceting skills, our learning material is jam-packed with exercises to complete in class and on your own (c'mon, we need to challenge you a bit!).




6. Meet Other Trailblazers

#DataTribe, now accepting applications! That's right, when you attend an Einstein Analytics+ Academy, you are warmly welcomed into the #DataTribe. The EA+ community is full of brilliant, kind, and helpful Analytics users who are dedicated to building a platform that uplifts data-lovers around the world! You can see this in action on our Success Community, with over 26,000 members.

Whether it's at Dreamforce, World Tour, or a Community Meetup, our users have banded together to grow our product, push our innovative spirits, and help each other succeed through AI and predictive intelligence.





Are you fired up about academies yet?! Our Einstein Analytics+ Academies are an amazing opportunity to grow your skills in EA+ and your network of Trailblazers. If you're interested in attending one of our global Academy sessions, please contact your Account Executive or your Success Manager.

Until then, check out our other amazing Educational and Onboarding Tools that are available to you TODAY! We'll see you at the next academy.

Sunday, January 6, 2019

Einstein Analytics Dataflow REST API by example

The following link may be placed on your bookmarks bar and following the directions below you may view, start and stop dataflows. Please note there is no bullet proofing the script won't check if something is already running, etc. Also, it will acquire your SID, so please review the code carefully for any concerns.

data_flow

Basic instructions.

1) Log into Analytics Studio
2) Launch script
3) Look for popup in the upper left
4) View all the dataflows then select if you want to start, stop, etc.

The code.




/*
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

(function () {

var JQ = null;
var JQ_VERSION = "3.2.1";
var API_STR = "v43.0/";
var PROTO_STR = "https://";
var BASE_PATH_STR = "/services/data/";
var CREDENTIALS_OBJ = getServerSid();
var DIV_DOM_ELEM = document.createElement("div");
var SELECT_DOM_ELEM = document.createElement("SELECT");
var MAX_TIMEOUTS = 15;

  function loadItem (u, i) {
      var d = document;
      if (!d.getElementById(i)) {
          var s = d.createElement('script');
          s.src = u;
          s.id = i;
          d.body.appendChild(s);
      }
setTimeout(checkJQueryVersion, 50);
console.info("waiting on timeout");
  }
  loadItem('//code.jquery.com/jquery-' + JQ_VERSION + '.min.js', 'jquery')

// There's a better way to do this, tired of trying to get onload to work
function checkJQueryVersion () {
function finishJQueryProcessing () {
JQ = jQuery.noConflict( true );
setupHTMLAdds();
}
if (MAX_TIMEOUTS < 0) {
console.warning("We did not detect the correct jquery version, but exceeded timeouts waiting. Could be slow network, or the requested jquery version was not configured correctly.");
finishJQueryProcessing();
return;
}
MAX_TIMEOUTS--;

if ($.fn.jquery == JQ_VERSION) {
console.info("Successfully loaded jquery version:", $.fn.jquery, ". FYI, timeouts remaining:", MAX_TIMEOUTS);
finishJQueryProcessing();
return;
}
setTimeout(checkJQueryVersion, 50);
}

function addBr (dom_elem, n) {
for (var i=0;i<n;i++) {
dom_elem.appendChild(document.createElement("br"));
}
}

function setupHTMLAdds () {
DIV_DOM_ELEM.appendChild(SELECT_DOM_ELEM);
var btns_arr = [
{label: "All Dataflows", proc: getDataflows},
{label: "Start Selected Dataflow", proc: startRunningDataflow},
{label: "Active Dataflows", proc: getRunningDataflows},
{label: "Stop Selected Dataflow", proc: stopRunningDataflow},
{label: "Close Me", proc: closeMe}
];

addBr (DIV_DOM_ELEM, 2);
for (var i=0; i<btns_arr.length; i++) {
var btn = document.createElement("button");
DIV_DOM_ELEM.appendChild(btn);
addBr(DIV_DOM_ELEM, 1);
btn.innerText = btns_arr[i].label;
btn.onclick = btns_arr[i].proc;
}

addBr(DIV_DOM_ELEM, 3);

if (document.body.firstChild) document.body.insertBefore(DIV_DOM_ELEM, document.body.firstChild);
else document.body.appendChild(DIV_DOM_ELEM);
}

function removeAllChildren (elem) {
while (elem.firstChild) {
elem.removeChild(elem.firstChild);
}
}

function closeMe () {
DIV_DOM_ELEM.parentNode.removeChild(DIV_DOM_ELEM);
}

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 secureHeaderPrepSf (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 startRunningDataflow () {
  var val = SELECT_DOM_ELEM.options[SELECT_DOM_ELEM.selectedIndex].value;
  var url = PROTO_STR + CREDENTIALS_OBJ.server + BASE_PATH_STR + API_STR + "wave/dataflowjobs";
  var patch_data = {
command: "Start",
dataflowId: val
};
  JQ.ajax({
  type: "POST",
  data: JSON.stringify(patch_data),
  contentType: "application/json; charset=utf-8",
  url: url,
  beforeSend: secureHeaderPrepSf(CREDENTIALS_OBJ.sid),
  error: createAjaxErrorResponse("starting dataflow"),
  success: function (data) {
  if (data.status) alert("Start request sent, status is currently: " + data.status);
  else alert("Start request sent, but no status indication was recieved");
  console.info("stopRunningDataflow data result:", data);
  }
  })
  }

 function stopRunningDataflow () {
var val = SELECT_DOM_ELEM.options[SELECT_DOM_ELEM.selectedIndex].value;
var url = PROTO_STR + CREDENTIALS_OBJ.server + BASE_PATH_STR + API_STR + "wave/dataflowjobs/" + val ;
var patch_data = {command: "Stop"};
JQ.ajax({
type: "PATCH",
data: JSON.stringify(patch_data),
contentType: "application/json; charset=utf-8",
url: url,
beforeSend: secureHeaderPrepSf(CREDENTIALS_OBJ.sid),
error: createAjaxErrorResponse("stopping dataflow"),
success: function (data) {
if (data.message) alert("Kill request sent and received message: " + data.message);
else alert("Kill request sent, but no message was recieved - likely the job was complete");
console.info("stopRunningDataflow data result:", data);
}
})
 }

function getRunningDataflows () {
var url = PROTO_STR + CREDENTIALS_OBJ.server + BASE_PATH_STR + API_STR + "wave/dataflowjobs";
JQ.ajax({
type: "GET",
url: url,
beforeSend: secureHeaderPrepSf(CREDENTIALS_OBJ.sid),
error: createAjaxErrorResponse("obtaining running dataflows"),
success: function (data) {
console.info("getRunningDataflows data result:", data);
removeAllChildren(SELECT_DOM_ELEM);
for (var i=0;i<data.dataflowJobs.length; i++) {
var duration = data.dataflowJobs[i].duration;
if (!duration) {
if (data.dataflowJobs[i].status == "Queued") duration = 0;
else {
duration = Math.round(( new Date() - new Date(data.dataflowJobs[i].startDate) ) / 1000);
}
}
var opt = document.createElement('option');
    opt.value = data.dataflowJobs[i].id;
opt.innerHTML = data.dataflowJobs[i].label + ", " + duration + " (s), " + data.dataflowJobs[i].status;
    SELECT_DOM_ELEM.appendChild(opt);
}
}
})
}

function getDataflows () {
var url = PROTO_STR + CREDENTIALS_OBJ.server + BASE_PATH_STR + API_STR + "wave/dataflows";
JQ.ajax({
type: "GET",
url: url,
beforeSend: secureHeaderPrepSf(CREDENTIALS_OBJ.sid),
error: createAjaxErrorResponse("getting dataflows"),
success: function (data) {
console.info("getDataflows data result:", data);
removeAllChildren(SELECT_DOM_ELEM);
for (var i=0;i<data.dataflows.length; i++) {
var opt = document.createElement('option');
    opt.value = data.dataflows[i].id;
opt.innerHTML = data.dataflows[i].label;
    SELECT_DOM_ELEM.appendChild(opt);
}
}
})
}

}());

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();

}());