title: "We're sorry, a server error occurred. Please wait a bit and try again" while creating charts
body: <p>We are running into sporadic errors (more often than not) on a project that generates Google Doc documents based on info entered into Google Sheet spreadsheets.</p>
  <p>The Google Apps Script project pulls data (from sheets), caches it in-memory with standard var statements, massages the data a bit (formatting, de-duping, etc), then copies a Doc template and does a bunch of token substitution and also inserts some charts based on the in-memory variables.</p>
  <p>We are encountering the following error during a high quantity of executions:</p>
  <pre class="lang-none prettyprint-override"><code>Exception: We're sorry, a server error occurred. Please wait a bit and try again.
      at insertChart(Code:5890:10)
      at processForm(Code:4540:5)
  </code></pre>
  <p>The main method creates several (between 5 - 20 or more) charts depending on parameters entered at run-time by the user (which happens via a standard Web Form created by GAS:</p>
  <pre><code>function doGet() {
    return HtmlService
        .createTemplateFromFile('index')
        .evaluate();
  }
  </code></pre>
  <p>The web page returned by the above is a very standard GAS <code>HTMLService</code> webapp. It contains a form that allows a user to select a few standard types of reporting criteria (date range, filter by certain types of data, etc).</p>
  <p>We are duplicating a &quot;template&quot; Docs file which has a bunch of common text and some tokens that we use as placeholders to find and replace with data loaded from the Sheets. The Sheets are very common spreadsheets with the kind of data you'd imagine: date ranges, selections from drop-down lists, free-text columns, etc. (The filtering info specified by the user is used to query the Sheets for the data we want to &quot;merge&quot; into the new Doc. It's basically a much more complex version of your standard &quot;mail merge&quot;.)</p>
  <pre><code>  var templateId = 'asdfasdfasdfasdfasdfasdfasdfasdfasdf'; // DEV
    var documentId = DriveApp.getFileById(templateId).makeCopy().getId();
    
    DriveApp.getFileById(documentId).setName('Demo Report - Project Report');
    DriveApp.getFileById(documentId).setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.COMMENT);
    
    Logger.log('templateId: ' + templateId);
    Logger.log('documentId: ' + documentId);
      
    var doc = DocumentApp.openById(documentId);
    var body = doc.getBody();
  </code></pre>
  <p>We have the following method which takes the Doc body, placeholder, and formatted data from a chart builder. It then identifies the Doc element token to replace (simply a string in the Doc file), and runs a <code>body.insertImage</code> after that token element.</p>
  <pre><code>function insertChart(body, placeholder, chart) {
    if (placeholder.getParent().getType() === DocumentApp.ElementType.BODY_SECTION &amp;&amp; typeof chart !== 'undefined' &amp;&amp; chart !== null) {
      var offset = body.getChildIndex(placeholder);
      
      //Logger.log(chart);
      
      body.insertImage(offset + 1, chart);
    }
  }
  </code></pre>
  <p>We have several helper methods like the following <code>buildStackedColumnChart()</code>. It's meant to wrap the Chart API commands to use a dataTable and Chart Builder to return a specific type of chart (bar, stacked bar, line, etc).</p>
  <p>Its resulting chart gets passed into the <code>insertChart()</code> method above.</p>
  <pre><code>function buildStackedColumnChart(title, header, data) {
    Logger.log('buildStackedColumnChart');
    Logger.log('title: ' + title);
    Logger.log('header: ' + header);
    Logger.log('data: ' + data);
    
    try {
      var dataTable = Charts.newDataTable();
      
      for (var hr = 0; hr &lt; header.length; hr++) {
        var headerRow = header[hr];
        
        dataTable.addColumn(headerRow[0], headerRow[1]);
      }
      
      for (var r = 0; r &lt; data.length; r++) {
        var row = data[r];
        
        dataTable.addRow(row);
      }
      
      dataTable.build();
      
      
      var chart = Charts.newColumnChart()
        .setDataTable(dataTable)
        .setDimensions(600, 500)
        .setTitle(title)
        .setStacked()
        .build();
      return chart;
    } catch (ex) {
      Logger.log('ex: ' + ex);
      return null;
    }
  }
  </code></pre>
  <p>In the main <code>processForm()</code> method (called directly by the webapp menioned above when the user selects their criteria and clicks a &quot;Generate Report&quot; button. We have a few calls to a method which finds the token in the template file (just text with {{}} around it as shown below). It iterates through a hard-coded list of values in the in-memory data variables storing accumulations from the Sheets and creates chartHeader and chartData (which contains the values to be charted in a mechanism the helper methods above can translate back into calls that make sense for the Chart API) and uses the <code>insertChart()</code> and `` helper methods to insert the chart after the bookmark token and then remove the bookmark from the document (cleanup so the tokens aren't present in the end report Doc).</p>
  <pre><code>  var chartTrainingsSummaryBookmark = findPlaceholder(body, '{{CHART_TRAININGS_SUMMARY}}');
    
    Logger.log('chartTrainingsSummaryBookmark: ' + chartTrainingsSummaryBookmark);
    
    var chartCategories = [
      'Call',
      'Conference Attendee',
      'Meeting (External)',
      'Partnership',
      'Quarterly Board Meeting',
      'Alliance or Workgroup',
      'Training (Others)',
      'Training (Professional Development)',
      'Youth Training',
      'Webinar or Zoom',
      'Other',
      'Coalition',
      'Email',
      'Face-to-face',
      'Phone',
      'Site Visit',
    ];
    
    //  {Call=10.0, Conference Attendee=10.0, Quarterly Board Meeting=10.0, Alliance or Workgroup=10.0, Coalition=10.0, Meeting (External)=10.0}
  
    var chartHeaderData = [];
    var chartData = [];
    
    var monthChartHeader = [[Charts.ColumnType.STRING, 'Month']];
    var monthChartData = [monthName];
    for (var cci = 0; cci &lt; chartCategories.length; cci++) {
      var chartCategory = monthName + ':' + chartCategories[cci];
      
      if (getChartData(chartCategory, trainingsChartData) &gt; 0) {
        monthChartHeader.push([Charts.ColumnType.NUMBER, chartCategory.split(':')[1]]);
        monthChartData.push(getChartData(chartCategory, trainingsChartData));
      }
    }
    if (monthChartData.length &gt; 1) {
      if (chartHeaderData.length &lt; 1) {
        chartHeaderData = chartHeaderData.concat(monthChartHeader);
      }
      chartData.push(monthChartData);
    }
  
    Logger.log('----- CHART - TRAININGS: SUMMARY -----');
    
    if (chartData.length &gt; 0 &amp;&amp; chartData[0].length &gt; 1) {
      insertChart(body, chartTrainingsSummaryBookmark, buildStackedColumnChart('', chartHeaderData, chartData));
    }
    chartTrainingsSummaryBookmark.removeFromParent();
  </code></pre>
  <p>We are also seeing slight variations that also error out the document generation with a Timeout. (This process can take a while if there is a lot of data that fits within the parameters which the user specified.)</p>
  <p>We were wondering why these errors would happen on a seemingly random interval. We can literally just click the button that makes the call again and it might work, or might error out on a different chart, etc.</p>
  <p>This script has been working for the most part (we did encounter a few specific errors) in months previous; however, this &quot;Exception: We're sorry, a server error occurred. Please wait a bit and try again.&quot; started occuring last weekend and got far worse on Monday / Tuesday (nearly every execution failed, very few succeeded). It's not happening quite as badly today; but we are seeing a few errors.</p>
  <p>Also, it's a bit strange, but copying the GAS Project and executing the copy has thrown the error less frequently than the original project in production.</p>
  <p>We have some ideas for workarounds; bu, ideally, we would like to identify a root cause so we can correctly fix the issue.</p>