6

I have created simple Gmail addon, now I'm struggling with below strategies.

After installing the addon, when first inbox is opened, we ask basic info input from users, when he opens second mail we won't ask basic info details again.

How I can handle this?

I have tried property service but no luck.

Update

var MAX_THREADS = 5;

/**
 * Returns the array of cards that should be rendered for the current
 * e-mail thread. The name of this function is specified in the
 * manifest 'onTriggerFunction' field, indicating that this function
 * runs every time the add-on is started.
 *
 * @param {Object} e data provided by the Gmail UI.
 * @returns {Card[]}
 */
function buildAddOn(e) {
  // Activate temporary Gmail add-on scopes.
  //Logger.log('E', Session.getActiveUser());
  var accessToken = e.messageMetadata.accessToken;
  GmailApp.setCurrentMessageAccessToken(accessToken);
  var userProperties = PropertiesService.getUserProperties();
  var Token = userProperties.getProperty('Token');
  Logger.log('Token value:',typeof Token);
  if(Token != null ){
    var messageId = e.messageMetadata.messageId;
    var senderData = extractSenderData(messageId);
    var cards = [];

    // Build a card for each recent thread from this email's sender.
    if (senderData.recents.length > 0) {
      senderData.recents.forEach(function(threadData) {
        cards.push(buildRecentThreadCard(senderData.email, threadData));
      });
    } else {
      // Present a blank card if there are no recent threads from
      // this sender.
      cards.push(CardService.newCardBuilder()
        .setHeader(CardService.newCardHeader()
        .setTitle('No recent threads from this sender')).build());
    }
    return cards;
  } 
  else{
    var cards = []
    var login_card = build_login_card()
    cards.push(login_card);
    return cards;
  }
}

/**
 *  This function builds a set of data about this sender's presence in your
 *  inbox.
 *
 *  @param {String} messageId The message ID of the open message.
 *  @return {Object} a collection of sender information to display in cards.
 */
function extractSenderData(messageId) {
  // Use the Gmail service to access information about this message.
  var mail = GmailApp.getMessageById(messageId);
  var threadId = mail.getThread().getId();
  var senderEmail = extractEmailAddress(mail.getFrom());

  var recentThreads = GmailApp.search('from:' + senderEmail);
  var recents = [];

  // Retrieve information about up to 5 recent threads from the same sender.
  recentThreads.slice(0,MAX_THREADS).forEach(function(thread) {
    if (thread.getId() != threadId && ! thread.isInChats()) {
      recents.push({
        'subject': thread.getFirstMessageSubject(),
        'count': thread.getMessageCount(),
        'link': 'https://mail.google.com/mail/u/0/#inbox/' + thread.getId(),
        'lastDate': thread.getLastMessageDate().toDateString()
      });
    }
  });

  var senderData = {
    "email": senderEmail,
    'recents': recents
  };

  return senderData;
}

/**
 *  Given the result of GmailMessage.getFrom(), extract only the email address.
 *  getFrom() can return just the email address or a string in the form
 *  "Name <myemail@domain>".
 *
 *  @param {String} sender The results returned from getFrom().
 *  @return {String} Only the email address.
 */
function extractEmailAddress(sender) {
  var regex = /\<([^\@]+\@[^\>]+)\>/;
  var email = sender;  // Default to using the whole string.
  var match = regex.exec(sender);
  if (match) {
    email = match[1];
  }
  return email;
}

/**
 *  Builds a card to display information about a recent thread from this sender.
 *
 *  @param {String} senderEmail The sender email.
 *  @param {Object} threadData Infomation about the thread to display.
 *  @return {Card} a card that displays thread information.
 */
function buildRecentThreadCard(senderEmail, threadData) {
  var card = CardService.newCardBuilder();
  card.setHeader(CardService.newCardHeader().setTitle(threadData.subject));
  var section = CardService.newCardSection()
    .setHeader("<font color=\"#1257e0\">Recent thread</font>");
  section.addWidget(CardService.newTextParagraph().setText(threadData.subject));
  section.addWidget(CardService.newKeyValue()
    .setTopLabel('Sender')
    .setContent(senderEmail));
  section.addWidget(CardService.newKeyValue()
    .setTopLabel('Number of messages')
    .setContent(threadData.count.toString()));
  section.addWidget(CardService.newKeyValue()
    .setTopLabel('Last updated')
    .setContent(threadData.lastDate.toString()));

  var threadLink = CardService.newOpenLink()
    .setUrl(threadData.link)
    .setOpenAs(CardService.OpenAs.FULL_SIZE);
  var button = CardService.newTextButton()
    .setText('Open Thread')
    .setOpenLink(threadLink);
  section.addWidget(CardService.newButtonSet().addButton(button));

  card.addSection(section);
  return card.build();
}
function build_login_card(){
  var card = CardService.newCardBuilder();
  card.setHeader(CardService.newCardHeader().setTitle("Login Here"));
  var userProperties = PropertiesService.getUserProperties();
  var Token = userProperties.setProperty('Token',"Token");
  return card.build()
}
Nisarg Shah
  • 14,151
  • 6
  • 34
  • 55
Robert
  • 3,373
  • 1
  • 18
  • 34
  • 1
    Are you familiar with troubleshooting techniques? You'll need to start at the beginning an analyze your code to see what is working and what is failing. In the code editor, click "Help" and then "Documentation" and search "troubleshooting." – Alan Wells Feb 01 '18 at 13:30
  • @SandyGood my code is working ,how to scope varable ?? – Robert Feb 01 '18 at 13:33
  • 2
    A global variable will not hold it's value after the script has stopped running. You can't use a global variable to store persistent data. You can't use any variable to store persistent data. So how you have scoped your code will not help you at all in this situation. I understand what your problem is, in general. I've dealt with this myself. You do need to use Properties Service to solve your problem. But, in programming there are literally 100's, maybe thousands, maybe even millions of different ways that something can be programmed, and I can't guess at how you have written your code. – Alan Wells Feb 01 '18 at 13:41
  • is there anyways to use like session or rootscope variables ?? – Robert Feb 01 '18 at 13:45
  • 1
    I see that there are 2 lines: `var Token = userProperties.getProperty('Token'); Logger.log('Token value:',typeof Token);` What does `Logger.log()` print to the log? View the Logs. Tell us what is printed to the log. I don't see a line of code that sets the token value. You're trying to get a token value out of Properties Service, but if you haven't set the value, there won't be anything there. – Alan Wells Feb 01 '18 at 13:57
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/164348/discussion-between-robert-and-sandy-good). – Robert Feb 01 '18 at 14:01
  • How are you testing your add-on? Are you able to get the message printed by `Logger.log('Token value:',typeof Token);`? – Rubén Feb 06 '18 at 19:11
  • 1
    Can I ask you about your current situation? I understood as follows. You want to set and get values using PropertiesService. If my understanding is correct, can I ask you about your current problem? – Tanaike Feb 08 '18 at 23:01
  • @Tanaike now i have used PropertiesService – Robert Feb 09 '18 at 07:17
  • @Robert Yes. PropertiesService can be used for Gmail add-on. Do you have problems for using PropertiesService? – Tanaike Feb 09 '18 at 07:18
  • @Tanaike i have small problem using PropertiesService,while uninstall properties its hold the userpeoperties values – Robert Feb 14 '18 at 05:06
  • I couldn't understand your situation about ``while uninstall properties its hold the userpeoperties value``. I'm sorry. – Tanaike Feb 14 '18 at 07:50
  • @Tanaike,in my addon,i have used one UserProperties value,while uninstall my addon its hold UserProperties value,we need remove userproperties values – Robert Feb 14 '18 at 07:54
  • Do you want to remove values of userproperties used at addon after the addon was uninstalled? – Tanaike Feb 14 '18 at 07:56
  • In that case, who do use and uninstall the addon? – Tanaike Feb 14 '18 at 07:58
  • @Tanaike your correct,but in my case user install/uninstall/reinstall add on ,so while user reinstall my addon we need ask some details,similar what we do after doing install my addon – Robert Feb 14 '18 at 08:03
  • If the event is retrieved when the addon is uninstalled, your problem will be solved. But unfortunately, in the current stage, I think that there are no events for it. I think that if the expiration time of cache service was more long, it could be used for this situation. – Tanaike Feb 14 '18 at 08:10
  • I'm sorry I couldn't think of better solution or workaround soon. – Tanaike Feb 14 '18 at 08:28

2 Answers2

3

According to comments, the primary issue here is that uninstalling the add-on does not remove bits from the UserProperties datastore, and thus if the add-on is re-installed, the add-on configuration steps cannot be performed again.

Generic Add-on Solution

If this were not a Gmail add-on, this could be simply solved with a time-based trigger, since per documentation:

Add-on triggers will stop firing in any of the following situations:
- If the add-on is uninstalled by the user
- If the add-on is disabled in a document (if it is re-enabled, the trigger will become operational again)
- If the developer unpublishes the add-on or submits a broken version to the add-on store

I.e., you would simply write the day's date to a key (e.g. LAST_SEEN) in PropertiesService#UserProperties every day, and then check both TOKEN and LAST_SEEN when deciding which card to display.


Gmail Add-on Solution:

Per Gmail add-on documentation, you cannot create or use Apps Script simple / installable triggers in a Gmail add-on. So, the easiest implementation of the solution is not available. However, we can still use this approach, by moving the region in which that LAST_SEEN key is updated.

Right now (2018 March), the only available trigger for a Gmail add-on is the contextual trigger unconditional:

Currently, the only contextual trigger type available is unconditional, which triggers for all emails regardless of content.

So, if you bind to this contextual trigger, while your add-on is installed it will run a function whenever the user opens an email. The solution is then for your contextually triggered function to include the snippet

PropertiesService.getUserProperties().setProperty("LAST_SEEN", String(new Date().getTime()));

If you have other stuff to do in your contextually triggered function, that code will be uninfluenced by this addition. If you don't have a contextually triggered function, then you'd want to return [] (an empty card stack) in order to avoid showing any UI.

To use this new property, in your buildAddon(e) method you want to query for this value in addition to the TOKEN property you are using:

var userProps = PropertiesService.getUserProperties().getAll();
var Token = userProps["Token"];
var lastSeen = userProps["LAST_SEEN"] || 0; // If found, will be milliseconds since epoch.
var absence = new Date().getTime() - lastSeen; // Time in ms since last use of add-on.
if (Token == null || absence > /* some duration you choose */ ) {
  // New install, or user has started using app again.
  return [build_login_card()];
} else {
  // User is still using add-on, so do normal stuff.
}

  • This is obviously not a perfect solution (i.e. an uninstall contextual trigger would be much better), but can help detect lack-of-use situations
  • There are rate limits on how often you can write to PropertiesService. If you have speedy/"power" users, they might trip quotas.
    • Could combine CacheService and PropertiesService to handle frequent reads in a "short" session (of up to 6hr since last storage into cache).
tehhowch
  • 9,645
  • 4
  • 24
  • 42
0

Robert , have you tried caching the user input ?

I do caching in the event handler.

function onDomainChange(e){
    var cache = CacheService.getScriptCache();
    Logger.log(e.formInput);
    cache.put('domain',e.formInput.domain);
    Logger.log(cache.get('domain'));
}

Refer cache docs

hhsb
  • 560
  • 3
  • 23