Friday, May 10, 2019

Making Community a Priority: 8 Community Events to Attend this Summer

By Kate Toporski, Educational and Onboarding Program Coordinator

So, I went to a Community-led event.

And I was totally blown away.

In the middle of March, I took the way-too-long plane trip from San Francisco to London to attend a Community-centric event all about Salesforce products and capabilities. This event is known as London's Calling (or on Twitter as #LDNsCall19).


Boasting an agenda filled with seasoned, Salesforce experts and over 600 users, community members, and sponsors, London's Calling is currently the largest community-led event for Salesforce professionals in all of Europe! With an emphasis on knowledge sharing, plenty of Einstein Analytics sessions, and free food (duh), I was ecstatic to be in attendance. My teammates and I came together to build several sessions to help Community members better understand and use all of the amazing capabilities of Einstein Analytics Plus, and we were pleased to see so many others doing the same...and don't worry- you didn't miss all of the magic; all of the sessions at London's Calling were recorded! You can access the talks here.



But, with all of that said, I was hungry for more. I was eager to learn from the Community, hear their stories, and engage with users from around the world. So, in attempt to connect myself (and you) with Trailblazer around the globe, I'd love to share the experience and opportunities that Community-led events provide by giving you the Top 8 Community Events to Attend This Summer! Keep on reading for 8 amazing events happening around the world this summer:

Texas Dreamin'

June 13-14th, Austin, TX


Ready for a Texas-sized Ohana event?! Texas Dreamin' is happening in Austin, TX on June 13 &14. The Salesforce Community has come together to empower and celebrate over 400 users, admins, and developers from Texas and beyond. You can still register to attend here!

Check out Altheas Taite's blog about Texas Dreamin' 2018!

YeurDreamin'

June 14, Amsterdam, Netherlands


NEW EVENT ALERT 👏YeurDreamin' is a new event on the European calendar this year! Centered around Lightning Platform, the Benelux Community will be hosting this educational and networking get-together on June 14. Save your spot here

NorCal Dreamin'

June 27-28, Sacramento, CA


Salesforce + California Sunshine = ☀️😍Another new event on this year's Community Events Calendar! Join the Salesforce Community in the beautiful, Northern California for two days of non-stop learning and fun. Registration is still open- so snag a ticket here.


Truth North Dreamin'

July 11-12, Ottawa, ON


Can anybody say, "Oh, Canada!" The first EVER Canada-wide Community Conference is happening in July. Join the Salesforce Ohana in Ottawa on July 11-12 for everything from building professional connections to rocking out at the Demo Jam. Ready to book? Reserve your spot here.

Big Sky Dreamin'

July 18-19, Bozeman, MT


Big Sky Dreamin' is coming to Montana this summer! Join the Salesforce Community at a destination Dreamin' Event, where the Ohana aims to enable developers, admins, veterans, high school students, and more. With lots of opportunities to learn and engage, get registered for Big Sky here.

Midwest Dreamin'

August 7-9, Chicago, IL


650 people, 3 days, and 7 tracks? COUNT ME IN! The original Community Conference will be taking place in Chicago on August 7-9 for some major learning and networking. And did I mention that there will be a track specifically dedicated to Einstein Analytics?! Get excited and register today! 


Czech Dreamin'

August 16, Prague, Czech Republic


Another first in the books. This summer, the Prague Community will be hosting the first technical conference about Salesforce in the Czech Republic! The organizers say, "Be prepared for a day full of great sessions!" Ready to hop a plane to Prague? Register here for the event on August 16.

Down Under Dreaming: Sydney

August 28, Sydney, Australia


Australia's largest Community Event is back! After last year's first ever (and completely sold out) Dreamin' Event, the Sydney Community is back to deliver a day full of breakout sessions for admins, developers, and business users on topics ranging from technical deep dives to equality at work. Get ready to dream down under by registering here




Now, this list doesn't even cover all of our events. Be sure to check out the Trailhead site for a complete list of events, conferences, meetups, and more. Check it out here. Hope to see you at a Dreamin' Event this summer or beyond!

Interested in being part of the #DataTribe? We can help get you involved. Find me, Rikke Hovgaard, or Ziad Fayad on Twitter, and send us a DM.

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

}());