google-apps-scriptgmail-addons

Building a card and updating it after fetching data in Google Apps Script


I am trying to build a Gmail addon which includes 2 external API calls. The first one is fast (~200ms) and the second one is slow (~5s). Because of this I would like to first build the card with the results of the first fetch, and then update the card after the second call finishes.

Would it be possible to either:

Any preferred way to render a card and then update it after data is fetched would be appreciated!

Below is an example function if useful.

function onGmailMessage(e) {
  // Fetching email
  var messageId = e.gmail.messageId;
  var accessToken = e.gmail.accessToken;
  GmailApp.setCurrentMessageAccessToken(accessToken);
  var message = GmailApp.getMessageById(messageId);

  // Preparing requests
  var data = {
    'text': message.getPlainBody(),
  };
  var options = {
    'method' : 'post',
    'contentType': 'application/json',
    'payload' : JSON.stringify(data)
  };

  // Fetching responses. Here I would love to first display
  // createCard(response_1) and then when the second call finishes
  // return createCard(response_1 + '/n' + response_2)
  var response_1 = UrlFetchApp.fetch('http://API_1/', options);
  var response_2 = UrlFetchApp.fetch('http://API_2/', options);
  return createCard(response_1 + '/n' + response_2);
  

Solution

  • Answer:

    Unfortunately, this is not possible to do.

    More Information:

    This is a bit tricky so I'll split this answer down into your three points:

    [Is it possible to] call fetchAll and build and render the card each time a request finishes?

    A fetchAll function could be made to get all API responses, but you'll still end up waiting for API 2 to respond before updating what can be seen in the card.

    The problem with this is that in order to display the rendered card, you need to make a return of some kind. Once you return the response of the first API your second API won't be made at all as the function will have already executed. Which leads onto point two:

    [Is it possible to] trigger a function after the initial rendering is done (after return card.build())

    I did a test with this, instead of returning API 1's response directly I stored its value in a Script Property and made a trigger execute 200 ms later with the call to API 2:

    function onGmailMessage(e) {
     // previous code
      var response_1 = UrlFetchApp.fetch('http://API_1/', options);
      ScriptApp.newTrigger("getSecondResponse").timeBased().after(200).create();
      PropertiesService.getScriptProperties().setProperty('response1', response_1);  
      
      return createCard(response_1);  
    }
    
    function getSecondResponse() {
      // options 2 definition here;
      var response_1 = PropertiesService.getScriptProperties().getProperty("response1");
      var response_2 = UrlFetchApp.fetch('http://API_2/', options);
      return createCard(response_1 + '/n' + response_2);  
    }
    

    and adding the correct scopes in the manifest:

    {
      "oauthScopes": [
        "https://www.googleapis.com/auth/script.external_request",
        "https://www.googleapis.com/auth/script.locale",
        "https://www.googleapis.com/auth/gmail.addons.current.action.compose",
        "https://www.googleapis.com/auth/gmail.addons.execute",
        "https://mail.google.com/",
        "https://www.googleapis.com/auth/script.scriptapp"
      ]
    }
    

    And which this did call the first API, display the response in the card and make the trigger, the card didn't update. I presume this is because the trigger acts as a cron job being executed from somewhere which isn't the add-on itself, so the second card return is never seen in the UI.

    [Is it possible to] update the root card without returning it (I tried CardService.newNavigation().popToRoot().updateCard(card.build()) without success)

    updateCard() is a method of the Navigation class. There's a whole page in the documentation which details the uses of Card navigation but the important parts to take away here is that the navigation methods are used in response to user interaction. From the documentation:

    If a user interaction or event should result in re-rendering cards in the same context, use Navigation.pushCard(), Navigation.popCard(), and Navigation.updateCard() methods to replace the existing cards.

    The following are navigation examples:

    • If an interaction or event changes the state of the current card (for example, adding a task to a task list), use updateCard().
    • If an interaction or event provides further detail or prompts the user for further action (for example, clicking an item's title to see more details, or pressing a button to create a new Calendar event), use pushCard() to show the new page while allowing the user to exit the new page using the back-button.
    • If an interaction or event updates state in a previous card (for example, updating an item's title from with the detail view), use something like popCard(), popCard(), pushCard(previous), and pushCard(current) to update previous card and the current card.

    You can create multiple cards which have different content - for example one which contains response_1 and one which contains response_1 + "\n" + response_2, but some kind of interaction from a user is still needed to switch between the two views, and it won't get around the wait time you need to get a response from API 2.

    Feature Request:

    You can however let Google know that this is a feature that is important and that you would like to request they implement it. Google's Issue Tracker is a place for developers to report issues and make feature requests for their development services. I would suggest using the feature request template for G Suite Add-ons for this, rather than Apps Script directly.

    References: