bplmp / metabase-google-sheets-add-on

Import a Metabase question directly to a Google Sheet.
MIT License
108 stars 34 forks source link

Exception: Could not parse text #10

Open pc-amaral opened 2 years ago

pc-amaral commented 2 years ago

Hey @bplmp , how have you been? I'm struggling in here with this: image

Can you please help me out?

Thanks in advance.

manghat commented 2 years ago

Same issue here. There seems to be particular queries that are running into this issue.

pc-amaral commented 2 years ago

@manghat I was able to solve the issue. I had to change some things:

I'll paste all of it down here

function onInstall() {
  onOpen();
}

function onOpen() {
  var ui = SpreadsheetApp.getUi();
  ui.createMenu('Metabase')
    .addItem('Import All Questions in Sheets', 'importAllQuestions')
    .addToUi();
}

function importQuestion() {
  var metabaseQuestionNum = Browser.inputBox('Metabase question number (This will replace all data in the current tab with the result)', Browser.Buttons.OK_CANCEL);
  if (metabaseQuestionNum != 'cancel' && !isNaN(metabaseQuestionNum)) {
    var status = getQuestionAsCSV(metabaseQuestionNum, false);

    var log = {
      'user': Session.getActiveUser().getEmail(),
      'function': 'importQuestion',
      'questionNumber': metabaseQuestionNum,
      'status': status
    };
    if (log.status === true) {
      console.log(log);
    } else {
      console.error(log);
    }

    if (status.success === true) {
      SpreadsheetApp.getUi().alert('Question ' + metabaseQuestionNum + ' successfully imported.');
    } else {
      SpreadsheetApp.getUi().alert('Question ' + metabaseQuestionNum + ' failed to import. ' + status.error);
    }
  } else if (metabaseQuestionNum == 'cancel') {
    SpreadsheetApp.getUi().alert('You have canceled.');
  } else {
    SpreadsheetApp.getUi().alert('You did not enter a number.');
  }
}

function importAllQuestions() {

    var questions = getSheetNumbers();
    for (var i = 0; i < questions.length; i++) {
      questions[i].done = false;
    }

    if (questions.length === 0) {
      return;
    }

    var questionNumbers = [];
    for (var i = 0; i < questions.length; i++) {
      questionNumbers.push(questions[i].questionNumber);
    }

      var startDate = new Date().toLocaleTimeString();
      var htmlOutput = HtmlService.createHtmlOutput('<p>Started running at ' + startDate + '...</p>');
      var questionsSuccess = [];
      var questionsError = [];
      for (var i = 0; i < questions.length; i++) {
        var questionNumber = questions[i].questionNumber;
        var sheetName = questions[i].sheetName;
        var status = getQuestionAsCSV(questionNumber, sheetName);
        if (status.success === true) {
          questionsSuccess.push(questionNumber);
        } else if (status.success === false) {
          questionsError.push({
            'number': questionNumber,
            'errorMessage': status.error
          });
        }
      }

      var endDate = new Date().toLocaleTimeString();
      htmlOutput.append('<p>Finished at ' + endDate + '.</p></hr>');
      if (questionsSuccess.length > 0) {
        htmlOutput.append('<p>Successfully imported:</p>');
        for (var i = 0; i < questionsSuccess.length; i++) {
          htmlOutput.append('<li>' + questionsSuccess[i] + '</li>');
        }
      }
      if (questionsError.length > 0) {
        htmlOutput.append('<p>Failed to import:</p>');
        for (var i = 0; i < questionsError.length; i++) {
          htmlOutput.append('<li>' + questionsError[i].number + '</br>(' + questionsError[i].errorMessage + ')</li>');
        }
      }

      var finalStatus;
      if (questionsError.length === 0) {
        finalStatus = true;
      } else {
        finalStatus = false;
      }
      var log = {
        'user': Session.getActiveUser().getEmail(),
        'function': 'importAllQuestions',
        'questionNumber': questionNumbers,
        'status': {
          'success': finalStatus,
          'questionsSuccess': questionsSuccess,
          'questionsError': questionsError
        }
      };
      if (log.status === true) {
        console.log(log);
      } else {
        console.error(log);
      }
}

function getSheetNumbers() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheets = ss.getSheets();
  var questionNumbers = [];
  for (var i in sheets) {
    var sheetName = sheets[i].getName();
    if (sheetName.indexOf('(metabase/') > -1) {
      var questionMatch = sheetName.match('\(metabase\/[0-9]+\)');
      if (questionMatch !== null) {
        var questionNumber = questionMatch[0].match('[0-9]+')[0];
        if (!isNaN(questionNumber) && questionNumber !== '') {
          questionNumbers.push({
            'questionNumber': questionNumber,
            'sheetName': sheetName
          });
        }
      }
    }
  }
  return questionNumbers;
}

function getToken(baseUrl, username, password) {
  var sessionUrl = baseUrl + "api/session";
  var options = {
    "method": "post",
    "headers": {
      "Content-Type": "application/json"
    },
    "payload": JSON.stringify({
      username: username,
      password: password
    })
  };
  var response;
  try {
    response = UrlFetchApp.fetch(sessionUrl, options);
  } catch (e) {
    throw (e);
  }
  var token = JSON.parse(response).id;
  return token;
}

function getQuestionAndFillSheet(baseUrl, token, metabaseQuestionNum, sheetName) {
  var questionUrl = baseUrl + "api/card/" + metabaseQuestionNum + "/query/csv";
  if(metabaseQuestionNum === "XXXX" || metabaseQuestionNum === "YYY"){
    var currentDate = new Date();
    currentDate.setDate(currentDate.getDate() -1);
    var strCurrentDate = currentDate.toISOString().split('T')[0];
    var datainicio = "data_inicial";
    var dataFim = "data_final";
    if(metabaseQuestionNum === "1897"){
      datainicio = "datainicial";
      dataFim = "datafinal";
    }
    var data = [
      {
        "type": "date/single",
        "value": "2022-01-01",
        "target": [
          "variable",
          [
            "template-tag",
            datainicio
          ]
        ],
        "id": "7c104fe5-1a8f-7876-dba9-589f73a03758"
      },
      {
        "type": "date/single",
        "value": strCurrentDate,
        "target": [
          "variable",
          [
            "template-tag",
            dataFim
          ]
        ],
        "id": "3d6f5315-3f68-22d6-8605-3f9f4df4db79"
      }
    ];
    questionUrl = questionUrl + `?parameters=${JSON.stringify(data)}`;
  }
  var options = {
    "method": "post",
    "headers": {
      "Content-Type": "application/json",
      "X-Metabase-Session": token
    },
    "muteHttpExceptions": true
  };

  var response;
  try {
    response = UrlFetchApp.fetch(encodeURI(questionUrl), options);
  } catch (e) {
    return {
      'success': false,
      'error': e
    };
  }
  var statusCode = response.getResponseCode();

  if (statusCode == 200 || statusCode == 202) {
    var values = Utilities.parseCsv(response.getContentText());
    try {
      fillSheet(values, sheetName);
      return {
        'success': true
      };
    } catch (e) {
      return {
        'success': false,
        'error': e
      };
    }
  } else if (statusCode == 401) {
    var scriptProp = PropertiesService.getScriptProperties();
    var username = scriptProp.getProperty('USERNAME');
    var password = scriptProp.getProperty('PASSWORD');

    var token = getToken(baseUrl, username, password);
    scriptProp.setProperty('TOKEN', token);
    var e = "Error: Could not retrieve question. Metabase says: '" + response.getContentText() + "'. Please try again in a few minutes.";
    return {
      'success': false,
      'error': e
    };
  } else {
    var e = "Error: Could not retrieve question. Metabase says: '" + response.getContentText() + "'. Please try again later.";
    return {
      'success': false,
      'error': e
    };
  }
}

function fillSheet(values, sheetName) {
  var colLetters = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "AA", "AB", "AC", "AD", "AE", "AF", "AG", "AH", "AI", "AJ", "AK", "AL", "AM", "AN", "AO", "AP", "AQ", "AR", "AS", "AT", "AU", "AV", "AW", "AX", "AY", "AZ", "BA", "BB", "BC", "BD", "BE", "BF", "BG", "BH", "BI", "BJ", "BK", "BL", "BM", "BN", "BO", "BP", "BQ", "BR", "BS", "BT", "BU", "BV", "BW", "BX", "BY", "BZ", "CA", "CB", "CC", "CD", "CE", "CF", "CG", "CH", "CI", "CJ", "CK", "CL", "CM", "CN", "CO", "CP", "CQ", "CR", "CS", "CT", "CU", "CV", "CW", "CX", "CY", "CZ", "DA", "DB", "DC", "DD", "DE", "DF", "DG", "DH", "DI", "DJ", "DK", "DL", "DM", "DN", "DO", "DP", "DQ", "DR", "DS", "DT", "DU", "DV", "DW", "DX", "DY", "DZ", "EA", "EB", "EC", "ED", "EE", "EF", "EG", "EH", "EI", "EJ", "EK", "EL", "EM", "EN", "EO", "EP", "EQ", "ER", "ES", "ET", "EU", "EV", "EW", "EX", "EY", "EZ", "FA", "FB", "FC", "FD", "FE", "FF", "FG", "FH", "FI", "FJ", "FK", "FL", "FM", "FN", "FO", "FP", "FQ", "FR", "FS", "FT", "FU", "FV", "FW", "FX", "FY", "FZ", "GA", "GB", "GC", "GD", "GE", "GF", "GG", "GH", "GI", "GJ", "GK", "GL", "GM", "GN", "GO", "GP", "GQ", "GR", "GS", "GT", "GU", "GV", "GW", "GX", "GY", "GZ", "HA", "HB", "HC", "HD", "HE", "HF", "HG", "HH", "HI", "HJ", "HK", "HL", "HM", "HN", "HO", "HP", "HQ", "HR", "HS", "HT", "HU", "HV", "HW", "HX", "HY", "HZ", "IA", "IB", "IC", "ID", "IE", "IF", "IG", "IH", "II", "IJ", "IK", "IL", "IM", "IN", "IO", "IP", "IQ", "IR", "IS", "IT", "IU", "IV", "IW", "IX", "IY", "IZ", "JA", "JB", "JC", "JD", "JE", "JF", "JG", "JH", "JI", "JJ", "JK", "JL", "JM", "JN", "JO", "JP", "JQ", "JR", "JS", "JT", "JU", "JV", "JW", "JX", "JY", "JZ"];

  var sheet;
  if (sheetName == false) {
    sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  } else {
    sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
  }

  sheet.clear({
    contentsOnly: true
  });

  var rows = values;
  var header = rows[0];
  var minCol = colLetters[0];
  var maxCol = colLetters[header.length - 1];
  var minRow = 1;
  var maxRow = rows.length;
  var range = sheet.getRange(minCol + minRow + ":" + maxCol + maxRow);
  range.setValues(rows);
}

function getQuestionAsCSV(metabaseQuestionNum, sheetName) {
  var scriptProp = PropertiesService.getScriptProperties();
  var baseUrl = scriptProp.getProperty('BASE_URL');
  var username = scriptProp.getProperty('USERNAME');
  var password = scriptProp.getProperty('PASSWORD');
  var token = scriptProp.getProperty('TOKEN');

  if (!token) {
    token = getToken(baseUrl, username, password);
    scriptProp.setProperty('TOKEN', token);
  }

  status = getQuestionAndFillSheet(baseUrl, token, metabaseQuestionNum, sheetName);
  return status;
}
johannkaupen commented 2 years ago

@pc-amaral could you share why it is necessary to add dates and which dates to add? Thank you!

pc-amaral commented 2 years ago

Hey @johannkaupen Most of the questions that failed for me were questions that needed a date range. The original script doesn't have a place to define it. The questions that worked didn't need it. I believe that the dates were the main issue to show the parse error.

If I'm not wrong, dataInicio is 2022-01-01and dataFim is D-1