Google FIT api returns different step counts - google-fit

Stepcount is different when accessed by getDailyTotal and getData bucketed by day. Please help.
Fitness.HistoryApi.readDailyTotal(App.gClient, DataType.TYPE_STEP_COUNT_DELTA)-> returns step count as 1515
private void getStepsDataHistory(long startTime, long endTime) {
DataReadRequest dataReadRequest = new DataReadRequest.Builder()
.bucketByTime(1, TimeUnit.DAYS)
.aggregate(DataType.TYPE_STEP_COUNT_DELTA, DataType.AGGREGATE_STEP_COUNT_DELTA)
.setTimeRange(startTime, endTime, TimeUnit.MILLISECONDS).build();
-> returns data for a week, where today's steps =2877
EDIT: I also get this warning in the logcat:
Couldn't find aggregated data in Shared Pref for DataType{com.google.step_count.delta{steps(i)}}.

Please make sure that the start time and end time you gave are correct i.e from 12Am midnight to now. However readDailyTotal can be useful if you require step data for use in areas where you are unable to show the permissions panel (for example, Android Wear watch faces).
So i advised you to use Aggregate method in that way you will get quiet equal step count as google fit app . Check this out .
public int getStepsCount(long startTime, long endTime) {
PendingResult<DataReadResult> pendingResult = Fitness.HistoryApi
.readData(
fitnessClient,
new DataReadRequest.Builder()
.aggregate(DataType.TYPE_STEP_COUNT_DELTA,
DataType.AGGREGATE_STEP_COUNT_DELTA)
.bucketByTime(1, TimeUnit.DAYS)
.setTimeRange(startTime, endTime,
TimeUnit.MILLISECONDS)
.build());
int steps = 0;
DataReadResult dataReadResult = pendingResult.await();
if (dataReadResult.getBuckets().size() > 0) {
for (Bucket bucket : dataReadResult.getBuckets()) {
List<DataSet> dataSets = bucket.getDataSets();
for (DataSet dataSet : dataSets) {
for (DataPoint dp : dataSet.getDataPoints()) {
for (Field field : dp.getDataType().getFields()) {
steps += dp.getValue(field).asInt();
}
}
}
}
} else if (dataReadResult.getDataSets().size() > 0) {
for (DataSet dataSet : dataReadResult.getDataSets()) {
for (DataPoint dp : dataSet.getDataPoints()) {
for (Field field : dp.getDataType().getFields()) {
steps += dp.getValue(field).asInt();
}
}
}
}
return steps;
}

Related

Debug/Fix "Exceeded maximum execution time" error Google Addon

I've created my first Google Workspace Addon which I have deployed to the associated domain successfully although still working on it due to this error.
Without errors, functionally it's (was) working well, however, I now keep getting "Exceeded maximum execution time" errors.
This error is frustratingly intermittent, but it recently started occuring around 90%+ of the time despite not occurring previously for weeks. Reverting to previous versions of my code hasn't fixed the issue.
Most if not all existing answers regarding this error assume the script is genuinely exceeding the time limit for scripts (either 6 minutes or 30 minutes).
To be clear - this is not what's happening here. The script doesn't run for anywhere close to the maximum time allowed (as far as I'm aware). It actually runs for around 45 seconds before giving the error.
It may somehow be running in the background and causing a legitimate Timeout error but I need to figure out if this is the case and the cause of the error and then fix it.
I've contacted Google but their relevant support who are knowledgeable about this and who could help are unfortunately very difficult to get hold of.
In Cloud Logging for this error, I'm seeing (obfuscated id's):
{
"insertId": "28z8v2anf1xq6",
"jsonPayload": {
"context": {
"reportLocation": {
"filePath": "[unknown file]",
"functionName": "[unknown function]"
}
},
"message": "Exceeded maximum execution time",
"serviceContext": {
"service": "AKfycbx-SO5mXBNoe2leEH4wrj02t9fZZwkq5BQlJPuBaNM"
}
},
"resource": {
"type": "app_script_function",
"labels": {
"function_name": "fileMakerPro",
"invocation_type": "unknown",
"project_id": "order-management-713317"
}
},
"timestamp": "2022-03-10T10:09:58.827Z",
"severity": "ERROR",
"labels": {
"script.googleapis.com/process_id": "fIxbX5X5IjrIqLLK-XQgJiJV0U9vQMEAEA1GOxTqw-XzqsaVlKTNK2UcymM7feuXp-qZLshvBUg61TS6u28l_v6szoAq8QBBOvGudISVj0yceQXPKpIHO6HJ2G-uxuqy4xcnv-NzDfBTMbJH7VmK_AjZd6a5KVA-LnhtOE_28mCn_zTpI5AC3-BhX_lcCC1p-3QsMX6blhMZSYgVTTo1T_Z9SovufUWinpJieIbio-L8wzQPkjLYM2l9s5RHGuKMcT3LbvUO7fFS4DV1z_xfaR_nU1LqeayAk1aouGSXc",
"script.googleapis.com/user_key": "wPo7ZL4DSIqpPrZfeqo7E7yAArA430YCNnzbVYhyuVlR6nKybMkISeaDZoigRDx0gAJXIjF49EoL",
"script.googleapis.com/deployment_id": "wkq5BQlJPcbx-SO5mEHAKfywrj02t9fZZ4uBXBNoe2leaNM",
"script.googleapis.com/project_key": "Xf9t90MIwJJbMnu2Z9hQ48TM6DPTcW9w3"
},
"logName": "projects/order-management-317713/logs/script.googleapis.com%2Fconsole_logs",
"receiveTimestamp": "2022-03-10T10:09:59.313440894Z"
}
Which is obscure and isn't helpful at all with the "unknowns".
The function it references, for reference:
async function fileMakerPro(e) {
console.time("fileMakerPro");
//Order Management Named Ranges
let OM_NR = await getNamedRangesAsObject_(ss);
let orderNumber, orderFolder, orderFolderName;
let loadingDate, loadingDateFormatted, dueDate, dueDateFormatted;
let sheetGoodsTable = OM_NR.goodsTable.ranges.filter(function (x) { /* here, x is an array, not an object */
return !(x.every(element => element === (undefined || null || '')))
});
//Get order number if exists or show warning and return
if (orderNumber = OM_NR.OrderNo.range.toString()) {
} else {
ui.alert(`⚠️ ORDER NUMBER \(${OM_NR.OrderNo.rangeA1.toString()}\) appears missing, please correct and try again.`);
return;
}
//Get loading date if exists or show warning and return
if (loadingDate = OM_NR.transportLoadingDate.range) {
loadingDateFormatted = `${loadingDate.getDate()}-${loadingDate.getMonth() + 1}-${loadingDate.getFullYear()}`;
monthName = monthNames[loadingDate.getMonth()];
//Set Due Date 30 days after Loading Date
dueDate = new Date(loadingDate.getFullYear(), loadingDate.getMonth(), loadingDate.getDate() + 30);
dueDateFormatted = `${dueDate.getDate()}-${dueDate.getMonth() + 1}-${dueDate.getFullYear()}`;
} else {
ui.alert(`⚠️ LOADING DATE \(${OM_NR.transportLoadingDate.rangeA1.toString()}\) appears to be missing, please correct and try again.`);
return;
}
let rootFolder = DriveApp.getFileById(sheetId).getParents().next();
let tempFolder = DriveApp.getFolderById("1f4Ll-KODzmvBIuunaCS_7anMCX-w86S6");
let monthFolders = rootFolder.getFolders();
let tickBoxes = e.formInputs.generateDocsCheckboxes;
let packingListTemplateId = '1cZUB4U59Z56456gdgdghhka2QQ9sUNIlQSHYy1M';
let tickCMR = false, tickInvoice = false;
//Find existing order folder
let orderSearch = await searchFolders(monthFolders, monthName, orderNumber);
//Set Order Folder Name if both values present
orderFolderName = `${loadingDate.getDate()}-${loadingDate.getMonth() + 1}-${loadingDate.getFullYear()} \(${orderNumber}\)`;
if (tickBoxes !== undefined) {
if (tickBoxes.indexOf('tickBoxCMRnote') > -1) {
tickCMR = true;
}
if (tickBoxes.indexOf('tickBoxInvoice') > -1) {
tickInvoice = true;
}
} else {
let confirm = ui.alert(`⚠️ No documents selected. Update Order ${orderNumber} Snapshot and Packing List only?`, Browser.Buttons.YES_NO);
if (confirm === ui.Button.YES) {
fileMakerPro();
} else {
ss.toast(`⚠️ Update cancelled.`);
return;
}
}
if (!orderSearch.foundMonthFolder) {
ss.toast(`⚙️ Creating Month Folder...`);
monthFolder = rootFolder.createFolder(monthName);
} else {
monthFolder = orderSearch.foundMonthFolder;
}
if (!orderSearch.foundOrderFolder) {
ss.toast(`⚙️ Creating Order Folder...`);
orderFolder = monthFolder.createFolder(orderFolderName);
} else {
if (!orderSearch.foundOrderFolder.getName().includes(loadingDateFormatted)) {
ui.alert(`⚠️ Duplicate Order Number ${orderNumber} found: ${orderSearch.foundMonthFolder.getName()} / ${orderSearch.foundOrderFolder.getName()}.\n\n Please change Order Number or remove duplicate Order Folder.`);
return;
}
orderFolder = orderSearch.foundOrderFolder;
}
if (tickInvoice && tickCMR) {
assembleCMR(orderFolder, orderNumber, tempFolder, loadingDateFormatted, sheetGoodsTable, OM_NR);
assembleInvoice(orderFolder, orderNumber, tempFolder, loadingDateFormatted, dueDateFormatted, sheetGoodsTable, OM_NR);
ss.toast(`✅ Documents Saved`);
} else {
if (tickCMR) {
assembleCMR(orderFolder, orderNumber, tempFolder, loadingDateFormatted, sheetGoodsTable, OM_NR);
ss.toast(`✅ CMR Document Saved`);
}
if (tickInvoice) {
assembleInvoice(orderFolder, orderNumber, tempFolder, loadingDateFormatted, dueDateFormatted, sheetGoodsTable, OM_NR);
ss.toast(`✅ Invoice Document Saved`);
}
}
let existingOrderSnapshot = orderFolder.getFilesByName(`${orderNumber} - ORDER SNAPSHOT - DO NOT EDIT`);
let existingPackingList = orderFolder.getFilesByName(`${orderNumber} - PACKING LIST`);
let packingListId = await createPackingList(existingPackingList, orderFolder, orderNumber, packingListTemplateId, sheetId, OM_NR);
createOrderSnapshot(existingOrderSnapshot, orderFolder, orderNumber, sheetId, packingListId);
syncOrderNumbers(orderSearch.orderNumbers);
console.timeEnd("fileMakerPro");
return;
}
I suspect maybe the following function could be causing the Timeout since the loop may be malfunctioning but not sure how to check or fix:
async function writePackingList(OM_NR, packingList, packingListId){
let PL_NR = await getNamedRangesAsObject_(packingList);
for (let nr in PL_NR) {
Logger.log(nr);
if (nr == "loadingAddress") {
packingList.getRangeByName(`${nr}`).setValue(OM_NR[`${nr}`].range);
} else if (nr) {
packingList.getRangeByName(`${nr}`).setValue(OM_NR[`${nr}`].range);
} else {
break;
}
}
ss.getRangeByName("transportTotalNetWeight").setFormula(`=IMPORTRANGE("${packingListId}", "PL!I15")`)
ss.getRangeByName("transportTotalGrossWeight").setFormula(`=IMPORTRANGE("${packingListId}", "PL!H15")`)
}
Any help would be appreciated as I've hit a brick wall with this. I need to figure out how to debug this error, understand why it's happening and fix it so it doesn't happen when fully deployed.
Thanks
EDIT to include additional functions:
//Make copy of Invoice template doc, get body, replace all placeholders, return
async function assembleInvoice(orderFolder, orderNumber, tempFolder, loadingDateFormatted, dueDateFormatted, sheetGoodsTable, OM_NR) {
console.time("assembleInvoice");
ss.toast(`⚙️ Assembling Invoice Document...`);
let prefix = orderNumber + " - ";
let fileName = "INVOICE";
let templateTempCopy;
//Blue Invoice Template
let invoiceTemplate1 = DriveApp.getFileById("C2j_q3nUP4UTUR1KiCt07r1lATPKSjzEm-EeY109T8B4");
//Red Invoice Template
let invoiceTemplate2 = DriveApp.getFileById("1uczJT-F-JzKzaBwh_CdhJNcFkgDHfGjypJYom4vqGIo");
//Choose Invoice template based on Movement Type
let staraMovementType = OM_NR.staraMovementType.range;
if (staraMovementType.toString().includes('GB SHIPPING TO')) {
templateTempCopy = invoiceTemplate1.makeCopy(tempFolder);
} else {
templateTempCopy = invoiceTemplate1.makeCopy(tempFolder);
}
//Open working copy
let workingCopy = DocumentApp.openById(templateTempCopy.getId());
//Get body from working copy
let documentBody = workingCopy.getBody();
//Populate table
let allTables = documentBody.getTables();
let invoiceGoodsTable = allTables[3];
let itemRowTemplate = invoiceGoodsTable.getRow(invoiceGoodsTable.getNumRows() - 2);
let totalsRowTemplate = invoiceGoodsTable.getRow(invoiceGoodsTable.getNumRows() - 1);
for (let n in sheetGoodsTable) {
let tableRow = invoiceGoodsTable.appendTableRow(itemRowTemplate.copy());
tableRow.getCell(0).replaceText("{itemNo}", sheetGoodsTable[n][1]);
tableRow.getCell(1).replaceText("{itemDesc}", sheetGoodsTable[n][0]);
tableRow.getCell(2).replaceText("{commodityCode}", sheetGoodsTable[n][2]);
tableRow.getCell(3).replaceText("{cartons}", sheetGoodsTable[n][4]);
tableRow.getCell(4).replaceText("{qtyKgs}", sheetGoodsTable[n][5]);
tableRow.getCell(5).replaceText("{price}", sheetGoodsTable[n][3]);
tableRow.getCell(6).replaceText("{total}", sheetGoodsTable[n][5] * sheetGoodsTable[n][3]);
}
let totalsRow = invoiceGoodsTable.appendTableRow(totalsRowTemplate.copy());
totalsRow.getCell(3).replaceText("{cartons}", OM_NR.transportCasesCartons.range);
totalsRow.getCell(4).replaceText("{qtyKgs}", OM_NR.transportTotalNetWeight.range);
totalsRow.getCell(6).replaceText("{total}", OM_NR.transportInvoiceTotal.range);
itemRowTemplate.removeFromParent();
totalsRowTemplate.removeFromParent();
let importerAddress = `${OM_NR.importerConsigneeIntoEU.range}, ${OM_NR.importerAddress.range}`;
documentBody.replaceText("{currency}", OM_NR.staraInvoiceCurrency.range);
documentBody.replaceText("{shipToAddress}", importerAddress);
documentBody.replaceText("{billToAddress}", importerAddress);
documentBody.replaceText("{exporterCustomsInvoiceNo}", OM_NR.exporterCustomsInvoiceNo.range);
documentBody.replaceText("{exporterVAT}", OM_NR.exporterVAT.range);
documentBody.replaceText("{exporterEORI}", OM_NR.exporterEORI.range);
documentBody.replaceText("{staraOrderNo}", OM_NR.staraOrderNo.range);
documentBody.replaceText("{staraCustomerOrderNo}", OM_NR.staraCustomerOrderNo.range);
documentBody.replaceText("{transportLoadingDate}", loadingDateFormatted);
documentBody.replaceText("{dueDate}", dueDateFormatted);
documentBody.replaceText("{paymentTerms}", "30 DAYS");
documentBody.replaceText("{staraInvoiceNo}", OM_NR.staraInvoiceNo.range);
documentBody.replaceText("{incoTerms}", `${OM_NR.orderIncoterm.range} ${OM_NR.orderCountryOfDestination.range}`);
documentBody.replaceText("{transportTotalGrossWeight}", OM_NR.transportTotalGrossWeight.range);
documentBody.replaceText("{transportTotalNetWeight}", OM_NR.transportTotalNetWeight.range);
documentBody.replaceText("{transportTruckRef}", OM_NR.transportTruckRef.range);
documentBody.replaceText("{transportSeal1}", OM_NR.transportSeal1.range);
documentBody.replaceText("{transportSeal2}", OM_NR.transportSeal2.range);
documentBody.replaceText("{transportInvoiceTotal}", OM_NR.transportInvoiceTotal.range);
documentBody.replaceText("{staraInvoiceCurrency}", OM_NR.staraInvoiceCurrency.range);
workingCopy.saveAndClose();
//Gets a 'blob' of the completed document
let completedDoc = templateTempCopy.getAs('application/pdf');
//Check for existing PDF and move to trash (not fully delete)
let foundFile = orderFolder.getFilesByName(prefix + fileName);
if (foundFile.hasNext()) {
foundFile.next().setTrashed(true);
}
try {
//Create PDF
ss.toast(`📝 Saving ${fileName} PDF...`);
orderFolder.createFile(completedDoc).setName(prefix + fileName);
deleteTempFiles(templateTempCopy.getId());
} catch (err) {
showError(err);
} finally {
ss.toast(`✅ Invoice Document Saved`);
console.timeEnd("assembleInvoice");
}
return;
}
async function createPackingList(existingPackingList, orderFolder, orderNumber, packingListTemplateId, sheetId, OM_NR) {
console.time("createPackingList");
let packingListId;
//Get current sheet container objects and data
if (existingPackingList.hasNext()) {
packingListId = existingPackingList.next().getId();
} else {
ss.toast(`📃 Creating Packing List...`);
let newPackingList = DriveApp.getFileById(packingListTemplateId);
let newPackingListCopy = newPackingList.makeCopy(`${orderNumber} - PACKING LIST`, orderFolder)
packingListId = newPackingListCopy.getId();
}
let packingList = SpreadsheetApp.openById(packingListId);
addImportrangePermission(packingListId, sheetId);
writePackingList(OM_NR, packingList, packingListId);
ss.toast(`✅ Packing List Created/Updated`);
console.timeEnd("createPackingList");
return packingListId;
}
async function createOrderSnapshot(existingOrderSnapshot, orderFolder, orderNumber, sheetId, packingListId) {
console.time("createOrderSnapshot");
let snapshotExists = existingOrderSnapshot.hasNext();
//If snapshot exists, delete.
if (snapshotExists) {
existingOrderSnapshot.next().setTrashed(true);
ss.toast(`📷 Updating Order Snapshot...`);
} else {
ss.toast(`📷 Creating Order Snapshot...`);
}
//Make copy of sheet in order folder
let currentSheet = DriveApp.getFileById(sheetId);
let sheetCopy = currentSheet.makeCopy(`${orderNumber} - ORDER SNAPSHOT - DO NOT EDIT`, orderFolder);
let sheetCopyId = sheetCopy.getId();
let targetSpreadsheet = SpreadsheetApp.openById(sheetCopyId);
//let targetSpreadsheet = SpreadsheetApp.openById(DriveApp.getFileById(sheetId).makeCopy(`${orderNumber} - ORDER SNAPSHOT - DO NOT EDIT`, orderFolder).getId());
addImportrangePermission(targetSpreadsheet.getRangeByName("CONTROL").getValue(), sheetCopyId);
addImportrangePermission(packingListId, sheetCopyId);
targetSpreadsheet.getRangeByName("transportTotalNetWeight").setFormula(`=IMPORTRANGE("${packingListId}", "PL!I15")`)
targetSpreadsheet.getRangeByName("transportTotalGrossWeight").setFormula(`=IMPORTRANGE("${packingListId}", "PL!H15")`)
targetSpreadsheet.getRangeByName("topRow").clearDataValidations()
targetSpreadsheet.getRangeByName("sheetBody").clearDataValidations()
//DELETE UNNEEDED TABS FROM COPY
targetSpreadsheet.deleteSheet(targetSpreadsheet.getSheetByName("PC"));
targetSpreadsheet.deleteSheet(targetSpreadsheet.getSheetByName("C1"));
targetSpreadsheet.deleteSheet(targetSpreadsheet.getSheetByName("C2"));
targetSpreadsheet.deleteSheet(targetSpreadsheet.getSheetByName("SFC"));
if (snapshotExists) {
ss.toast(`✅ Order Snapshot Updated`);
} else {
ss.toast(`✅ Order Snapshot Created`);
}
console.timeEnd("createOrderSnapshot");
return;
}
async function addImportrangePermission(sourceSheetId, authSheetId) {
console.time("addImportrangePermission");
if (sourceSheetId) {
// donor or source spreadsheet id
// adding permission by fetching this url
let url = `https://docs.google.com/spreadsheets/d/${authSheetId}/externaldata/addimportrangepermissions?donorDocId=${sourceSheetId}`;
let token = ScriptApp.getOAuthToken();
let params = {
method: 'post',
headers: {
Authorization: 'Bearer ' + token,
},
muteHttpExceptions: true
};
UrlFetchApp.fetch(url, params);
}
console.timeEnd("addImportrangePermission");
return;
}
Issue:
In Workspace add-ons, callback functions executed when an Action triggers are limited to 30 seconds of execution time:
The Apps Script Card service limits callback functions to a maximum of 30 seconds of execution time. If the execution takes longer than that, your add-on UI may not update its card display properly in response to the Action.
Note:
Since you didn't provide the code related to the functions which are taking most time (e.g. assembleCMR, assembleInvoice, createPackingList) I cannot make any suggestion on that, but in any case you should try to improve the efficiency of those functions, or split it all into several Actions.
Reference:
Callback functions

Getting past permissions for a file through the API/Apps Script

I'm trying to create a list of files stored in my Google Drive and also a list of their current and previous permissions. Specifically, I want to create a list of files in my Google Drive which at any point in the past have had the 'Anyone with a link can view/edit (etc)' permission set.
I have created a Google Apps Script to do this and I can iterate through all the files OK and I can get files which currently have that permission set, but I can't see a way to get the history of the file's permissions.
I have found and activated the revisions list API: https://developers.google.com/drive/api/v2/reference/revisions/list
This gets revisions but I can't see anywhere that it lists the sharing history of a revision.
Is what I'm attempting to do possible?
It's definitely possible using the Drive Activity API. You can use the Quickstart for Google Apps Script to view all the activity of an item (file or folder) or done by a User. In this case I modified the Quickstart to show the Permissions changes of a given Drive Id.
function listDriveActivity() {
var request = {
itemName: "items/1bFQvSJ8pMdss4jInrrg7bxdae3dKgu-tJqC1A2TktMs", //Id of the file
pageSize: 10};
var response = DriveActivity.Activity.query(request);
var activities = response.activities;
if (activities && activities.length > 0) {
Logger.log('Recent activity:');
for (var i = 0; i < activities.length; i++) {
var activity = activities[i];
var time = getTimeInfo(activity);
var action = getActionInfo(activity.primaryActionDetail);
var actors = activity.actors.map(getActorInfo);
var targets = activity.targets.map(getTargetInfo);
if (action == "permissionChange"){ //Only show permissionChange activity
Logger.log(
'%s: %s, %s, %s', time, truncated(actors), action,
truncated(targets));
}
}
} else {
Logger.log('No activity.');
}
}
/** Returns a string representation of the first elements in a list. */
function truncated(array, opt_limit) {
var limit = opt_limit || 2;
var contents = array.slice(0, limit).join(', ');
var more = array.length > limit ? ', ...' : '';
return '[' + contents + more + ']';
}
/** Returns the name of a set property in an object, or else "unknown". */
function getOneOf(object) {
for (var key in object) {
return key;
}
return 'unknown';
}
/** Returns a time associated with an activity. */
function getTimeInfo(activity) {
if ('timestamp' in activity) {
return activity.timestamp;
}
if ('timeRange' in activity) {
return activity.timeRange.endTime;
}
return 'unknown';
}
/** Returns the type of action. */
function getActionInfo(actionDetail) {
return getOneOf(actionDetail);
}
/** Returns user information, or the type of user if not a known user. */
function getUserInfo(user) {
if ('knownUser' in user) {
var knownUser = user.knownUser;
var isMe = knownUser.isCurrentUser || false;
return isMe ? 'people/me' : knownUser.personName;
}
return getOneOf(user);
}
/** Returns actor information, or the type of actor if not a user. */
function getActorInfo(actor) {
if ('user' in actor) {
return getUserInfo(actor.user)
}
return getOneOf(actor);
}
/** Returns the type of a target and an associated title. */
function getTargetInfo(target) {
if ('driveItem' in target) {
var title = target.driveItem.title || 'unknown';
return 'driveItem:"' + title + '"';
}
if ('drive' in target) {
var title = target.drive.title || 'unknown';
return 'drive:"' + title + '"';
}
if ('fileComment' in target) {
var parent = target.fileComment.parent || {};
var title = parent.title || 'unknown';
return 'fileComment:"' + title + '"';
}
return getOneOf(target) + ':unknown';
}
Remember to enable the Drive Activity API in Resources > Advanced Google Services
In my example this returns the logs:
You can also look deeper into the Permissions by using the permissionChange Parameters in the query.
If you have a business/enterprise/edu account the admin audit logs will tell you this for 6 months of data. Or it will at least tell you when a permission was changed from x to y.
Can't think of a method for personal.

Kettle PDI - Modified JavaScript - Json function not available

I'm using Kettle PDI 6.0 running on Windows Server 2012. I need to use the Modified Java Script Value to handle on Json object. I try something like this:
var jsondata = JSON.parse(result);
And get that:
"TypeError: Cannot find function parse in object test value test value test value test value test value test value test value test value test value test value. (script#3)"
I already try to looking for a solution on google, but not looks like that. I think that can be something wrong with my installation.
Note: I already try to use the command:
import java.util.*;
But that command is not recognized (Is not marked in bold).
I get:
missing ; before statement (script#2)
Maybe the Java functions not available.
I made my own function to resolve the problem. I will post here to help who has the same problem. If anyone want to help to solve the initial problem, I am still interested.
You can paste the code bellow on your "Modified Java Script Value" step after receive the Json response from service or get that on file. Note that you need to change the name of variables that you want to find on Json.
Result field is a Json Value.
//Script here
function findInArray(myValue, myArray){
var myResult='';
if(myArray.indexOf(myValue) > -1){
myResult = true;
} else {
myResult = false;
}
return myResult;
}
function getAttributeValue(Atribute, Object)
{
start = indexOf(Object,Atribute);
for (i= start; i < Object.length; i++)
{
if (substr(Object,i,1) == ":")
{
start_value = i+1;
break;
}
}
for (i= start_value; i < Object.length; i++)
{
end_value = i;
if (substr(Object,i,1) == ",")
{
break;
}
}
AttributeValue = replace(substr(Object, start_value, end_value-start_value),'"','');
if (indexOf(AttributeValue, "null") >= 0)
{
AttributeValue = null;
}
return AttributeValue ;
}
// Recupera Status
if (findInArray("status",result))
{
var status = getAttributeValue("status", result);
}
else
{
var status = "";
}
// Recupera _ID
if (findInArray("_id",result))
{
var mandrill_id = getAttributeValue("_id", result);
}
else
{
var mandrill_id = "";
}
// Recupera reject_reason
if (findInArray("reject_reason",result))
{
var reject_reason = replace(getAttributeValue("reject_reason", result),"}","");
}
else
{
var reject_reason = "";
}
yes, the parse json function is not available on the ex4 ecmascript of js rhino engine build in kettle, but you can handle json in kettle using eval.
var resultObj = eval('('+result+')');
//now you can iterate the foo elements of result original json
for(i=0;i< resultObj.length;i++){
Alert('foo number ' + i ' value = ' + resultObj[i].foo);
}
This is not javascript for the browser so eval is perfectly safe.

How do i access a shared calendar using EWS Managed API

How can retrieve only shared calendar using Exchange EWS API.
How can i know which calendar is shared using Exchange EWS API.
You can just specify the calendar, which you want to acces in your FolderId.
FolderId folderIdFromCalendar = new FolderId(WellKnownFolderName.Calendar, "sharedInbox#example.com");
You can then use the folderIdFromCalendar for retrieving the calendar items, you want, e.g.:
FindItemsResults<Item> appointments = ExchangeServive.FindItems(folderIdFromCalendar, ItemView);
Remember that the program has to be run in the context of a user, which the mailbox is shared with.
Update:
If you happen to know the FolderId, you can just check, if the current user has access to this calendar by performing this code:
As above, you will need to initialize a FolderId:
FolderId folderIdFromCalendar = new FolderId("AQMkADAwATM3ZmYAZS1kNjE0LWU1ZmQtMDACLTAwCgAuAAADJRMtiupYBUGD‌​cKdcqUrr3AEA1/sqwbrw‌​UEeFM0Mc+UBFoQAAABzk‌​m1sAAAA=");
After that, try the following code:
if (folderIdFromCalendar != null)
{
try
{
ItemView cView = new ItemView(1000);
FindItemsResults<Item> appointments = service.FindItems(folderIdFromCalendar, cView);
int count = appointments.TotalCount; //just an example of some random action on the folder calendar
}
catch (ServiceResponseException ex)
{
Console.WriteLine("The specified calendar was not shared with you. \n" + ex);
}
}
else
{
Console.WriteLine("The specified calendar ID could not be linked to a calendar folder.");
}
Another Update:
if (folderIdFromCalendar != null)
{
if (folderIdFromCalendar.Mailbox.ToString().ToLower() == "your-Mailadress".ToLower())
{
Console.WriteLine("The folder you specified is your own");
}
else
{
try
{
ItemView cView = new ItemView(1000);
FindItemsResults<Item> appointments = service.FindItems(folderIdFromCalendar, cView);
int count = appointments.TotalCount; //just an example of some random action on the folder calendar
Console.WriteLine("You have been given access to this mailbox. Its owner is: " + folderIdFromCalendar.Mailbox.ToString()); //if you need to know, whos mailbox you are accessing right now
}
catch (ServiceResponseException ex)
{
Console.WriteLine("The specified calendar was not shared with you. \n" + ex);
}
}
}
else
{
Console.WriteLine("The specified calendar ID could not be linked to a calendar folder.");
}
Last Update:
I have googled quite a bit now and found something that worked for me. But to be perfectly honest, I don't understand everything, which is going on in the following code:
FolderId folderIdFromCalendar = new FolderId("AQMkADAwATM3ZmYAZS1kNjE0LWU1ZmQtMDACLTAwCgAuAAADJRMtiupYBUGD‌​cKdcqUrr3AEA1/sqwbrw‌​UEeFM0Mc+UBFoQAAABzk‌​m1sAAAA=");
Folder folderFromCalendar = Folder.Bind(service, folderIdFromCalendar);
AlternateId aiAlternateid = new AlternateId(IdFormat.EwsId, folderFromCalendar.Id.UniqueId, "random#mailadress.com");
AlternateIdBase aiResponse = service.ConvertId(aiAlternateid, IdFormat.EwsId);
var mailbox = ((AlternateId)aiResponse).Mailbox;
if (folderFromCalendar != null)
{
if (mailbox.ToString().ToLower() == "your-Mailadress".ToLower())
{
Console.WriteLine("The folder you specified is your own");
}
else
{
try
{
ItemView cView = new ItemView(1000);
FindItemsResults<Item> appointments = service.FindItems(folderIdFromCalendar, cView);
int count = appointments.TotalCount; //just an example of some random action on the folder calendar
Console.WriteLine("You have been given access to this mailbox. Its owner is: " + mailbox.ToString());
}
catch (ServiceResponseException ex)
{
Console.WriteLine("The specified calendar was not shared with you. \n" + ex);
}
}
}
else
{
Console.WriteLine("The specified calendar ID could not be linked to a calendar folder.");
}
I don't understand, how the string "random#mailadress.com" affects anything in that program, but the syntax needs a string there.

How do I format this date string so that google scripts recognizes it?

I'm getting a json feed with a date string formatted like the following:
//2012-08-03T23:00:26-05:00
I had thought I could just pass this into a new Date as such
var dt = new Date("2012-08-03T23:00:26-05:00");
This works on jsfiddle but not in google scripts. It's returning an invalid date. After reading this post I recognize it may be how GAS interprets the date string so now I'm not sure how to reformat the date to make it work.
Is there a best way to reformat that date string so GAS can recognize it as a date?
Google Apps Script uses a particular version of JavaScript (ECMA-262 3rd Edition) and as you have discovered can't parse date/times in ISO 8601 format. Below is a modified function I use in a number of my Apps Scripts
var dt = new Date(getDateFromIso("2012-08-03T23:00:26-05:00"));
// http://delete.me.uk/2005/03/iso8601.html
function getDateFromIso(string) {
try{
var aDate = new Date();
var regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})" +
"(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\\.([0-9]+))?)?" +
"(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?";
var d = string.match(new RegExp(regexp));
var offset = 0;
var date = new Date(d[1], 0, 1);
if (d[3]) { date.setMonth(d[3] - 1); }
if (d[5]) { date.setDate(d[5]); }
if (d[7]) { date.setHours(d[7]); }
if (d[8]) { date.setMinutes(d[8]); }
if (d[10]) { date.setSeconds(d[10]); }
if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); }
if (d[14]) {
offset = (Number(d[16]) * 60) + Number(d[17]);
offset *= ((d[15] == '-') ? 1 : -1);
}
offset -= date.getTimezoneOffset();
time = (Number(date) + (offset * 60 * 1000));
return aDate.setTime(Number(time));
} catch(e){
return;
}
}
Found this other possible and very simple code that seems to also do the job :
function test(){
Logger.log(isoToDate("2013-06-15T14:25:58Z"));
Logger.log(isoToDate("2012-08-03T23:00:26-05:00"));
Logger.log(isoToDate("2012-08-03T23:00:26+05:00"));
}
function isoToDate(dateStr){// argument = date string iso format
var str = dateStr.replace(/-/,'/').replace(/-/,'/').replace(/T/,' ').replace(/\+/,' \+').replace(/Z/,' +00');
return new Date(str);
}
The hard and sure shot way to make it work if the format is known beforehand is to parse it.
var dtString = "2012-08-03T23:00:26-05:00";
var date = dtString.split('T')[0];
var time = dtString.split('T')[1].split('-')[0];
var tz = dtString.split('T')[1].split('-')[1];
var dt = new Date(date.split('-')[0] , date.split('-')[1] - 1, // month is special
date.split('-')[2], time.split(':')[0],
time.split(':')[1], time.split(':')[2] , 0);
I haven't tested this exact piece of code, but have used similar code. So, this gives you a fair idea of how to proceed.
This worked for me, when converting date-string to date-object in Google Script.
The date-string was taken from the Google Sheet cell by getValues() method.
From: 01.01.2017 22:43:34 to: 2017/01/01 22:43:34 did the job. And then the new Date().
var dateTimeObj = new Date(stringDate.replace(/^(\d{1,2})[-.](\d{1,2})[-.](\d{4})/g,"$3/$2/$1"));
As of now new Date() seems to work:
var dT = new Date("2012-08-03T23:00:26-05:00");
console.info("dT: %s or %d", dT, dT.getTime());
returns dT: Sat Aug 04 06:00:26 GMT+02:00 2012 or 1.344052826E12 in Google Apps Script