Search code examples
google-apps-scriptgoogle-appsgoogle-app-makergoogle-workspace

Getting user information for a large number of G Suite users quickly with AppScript


I'm using a slightly modified version of this code snippet to get the last login time from a large number of users on a G Suite domain. Due to the large number of users, this code can't finish running in under 30 minutes and hits the execution time limit of 30 minutes and fails. I'm looking for a way to speed up the execution.

The application this runs on combines this data with a similar code snipped that reads values from the enterprise license manager API, which I expect will hit the same issue.

If it's not possible to make the code faster, I at least need to ensure it doesn't hit the execution time limit. I need to call this API for all users at least once for the application to work properly - it sorts a list of users by their last login time. The app is in App Maker using App Script on a G Suite Enterprise domain.

function getParameterValues(parameters) {
  return parameters.reduce(function(result, parameter) {
    var name = parameter.name;
    var value;
    if (parameter.intValue !== undefined) {
      value = parameter.intValue;
    } else if (parameter.stringValue !== undefined) {
      value = parameter.stringValue;
    } else if (parameter.datetimeValue !== undefined) {
      value = new Date(parameter.datetimeValue);
    } else if (parameter.boolValue !== undefined) {
      value = parameter.boolValue;
    }
    result[name] = value;
    return result;
  }, {});
}

function generateLoginActivityReport() {
  var today = new Date();
  var oneWeekAgo = new Date(today.getTime() - (7 * 24 * 60 * 60 * 1000));
  var timezone = Session.getScriptTimeZone();
  var date = Utilities.formatDate(oneWeekAgo, timezone, 'yyyy-MM-dd');

  var parameters = [
    'accounts:last_login_time',
    'drive:num_items_created'
  ];
  var rows = [];
  var pageToken;
  var page;
    do {
    page = AdminReports.UserUsageReport.get('all', date, {
      parameters: parameters.join(','),
      maxResults: 500,
      pageToken: pageToken,
    });

    var reports = page.usageReports;

    if (reports) {
      for (var i = 0; i < reports.length; i++) { 
        var report = reports[i];
        try {
          var parameterValues = getParameterValues(report.parameters);
          var row = [
            report.date,
            report.entity.userEmail,
            parameterValues['accounts:last_login_time'],
            //parameterValues['drive:num_items_created']
          ];
          rows.push(row);
          //var ar = app.models.ActivityReport.newRecord();
          //ar.LastLogin = parameterValues['accounts:last_login_time'];
          console.log(report.entity.userEmail);
          //ar.DocsAdded = 0; //getting this value is another issue but unrelated so it's set to 0 for now.
          //ar.Email = report.entity.userEmail.toString();
          //app.saveRecords([ar]);
        }
        catch(error) {
          console.error("Error: \n"+error);
        }
      }
    }
  } while (pageToken);
  }

And here's a sample execution:

[19-07-15 15:58:30:784 CDT] Starting execution
[19-07-15 15:58:30:796 CDT] Session.getScriptTimeZone() [0 seconds]
[19-07-15 15:58:30:797 CDT] Utilities.formatDate([Mon Jul 08 13:58:30 PDT 2019, America/Mexico_City, yyyy-MM-dd]) [0 seconds]
[19-07-15 15:58:32:202 CDT] console.log([[email protected], []]) [0.003 seconds]
[19-07-15 15:58:32:203 CDT] console.log([[email protected], []]) [0 seconds]
[19-07-15 15:58:32:204 CDT] console.log([[email protected], []]) [0 seconds]
///more entries, roughly 195 total
[19-07-15 15:58:32:441 CDT] console.log([[email protected], []]) [0 seconds]
[19-07-15 15:58:32:441 CDT] console.log([[email protected], []]) [0 seconds]
[19-07-15 15:58:32:443 CDT] Execution succeeded [1.645 seconds total runtime]



Solution

  • I got around this by using App Maker's client scripting. The client script calls the server script, which gets 100 users' reports and then passes the page token back to the client script, which calls it again using the page token- this gets around the 30 minute time limit and continues the call in order.

    function crunLoginActivityReport(page) {
      // this loops over every page token and gets the data and writes it to the DB.
      console.log("Running activity report for page: " + page);
      google.script.run.withSuccessHandler(function(result) {
        console.log("Got the following from the server, handing back to client runner" + result);
        if (result === null) {
          console.log("Result was null, stopping get");
          return; 
        } else {
        crunLoginActivityReport(result); }
      }).runLoginActivityReport(page);
      return;
    }
    
    function cinitLoginActivityReport() {
      google.script.run.withSuccessHandler(function(result) {
        crunLoginActivityReport(result);
        return;
      }).initLoginActivityReport();
      return;
    }