Related
import 'dart:io';
void main(List<String> arguments) {
if (arguments.isEmpty) {
print('Usage: dart totals.dart <inputFile.csv>');
exit(1);
}
final inputFile = arguments.first;
//access to file present in the folder
final lines = File(inputFile).readAsLinesSync();
final totalDurationByTag = <String, double>{};
lines.removeAt(0);
var totalDuration = 0.0;
//print all the lines present in the csv file
for (var line in lines) {
final values = line.split(',');
final durationStr = values[3].replaceAll('"', '');
final duration = double.parse(durationStr);
final tag = values[5].replaceAll('"', '');
final previousTotal = totalDurationByTag[tag];
if (previousTotal == null) {
totalDurationByTag[tag] = duration;
} else {
totalDurationByTag[tag] = previousTotal + duration;
}
totalDuration += duration;
}
for (var entry in totalDurationByTag.entries) {
final durationFormatted = entry.value.toStringAsFixed(1);
final tag = entry.key == '' ? 'Unallocated' : entry.key;
print('$tag: ${durationFormatted}h');
}
print('Total for all tags: ${totalDuration.toStringAsFixed(1)}h');
}
final previousTotal = totalDurationByTag[tag] Why is the value obtained by this tag is null the 1st time the relevant 'tag' value is encountered?
I found a couple of functions that met my needs, but they don't want to play nice together. The goal is to pull data once a day, then add it to a new column. The history function is fine, and I've got a trigger setup to run it once a day, but it's erroring when attempting to run automatically, and sending me an email with the following.
6/18/18 5:29 PM loadRegionAggregates TypeError: Cannot find function
forEach in object [object Object]. (line 19, file
"Code") time-based 6/18/18 5:29 PM
here's the full code.gs (I bolded the section with line 19, first line of the function)
// Requires a list of typeids, so something like Types!A:A
// https://docs.google.com/spreadsheets/d/1IixV0eNqg19FE6cLzb83G1Ucb0Otl-Jnvm6csAlPKwo/edit?usp=sharing for an example
function loadRegionAggregates(priceIDs,regionID){
if (typeof regionID == 'undefined'){
regionID=10000002;
}
if (typeof priceIDs == 'undefined'){
throw 'Need a list of typeids';
}
var prices = new Array();
var dirtyTypeIds = new Array();
var cleanTypeIds = new Array();
var url="https://market.fuzzwork.co.uk/aggregates/?station=60003760&types=34,35,36,37,38,39,40"
**priceIDs.forEach(function (row) {
row.forEach(function (cell) {
if (typeof(cell) === 'number' ) {
dirtyTypeIds.push(cell);
}**
});
});
cleanTypeIds = dirtyTypeIds.filter(function(v,i,a) {
return a.indexOf(v)===i;
});
prices.push(['TypeID','Buy volume','Buy Weighted Average','Max Buy','Min Buy','Buy Std Dev','Median Buy','Percentile Buy Price','Sell volume','Sell Weighted Average','Max sell','Min Sell','Sell Std Dev','Median Sell','Percentile Sell Price'])
var parameters = {method : "get", payload : ""};
var o,j,temparray,chunk = 100;
for (o=0,j=cleanTypeIds.length; o < j; o+=chunk) {
temparray = cleanTypeIds.slice(o,o+chunk);
Utilities.sleep(100);
var types=temparray.join(",").replace(/,$/,'')
var jsonFeed = UrlFetchApp.fetch(url+types, parameters).getContentText();
var json = JSON.parse(jsonFeed);
if(json) {
for(i in json) {
var price=[parseInt(i),
parseInt(json[i].buy.volume),
parseInt(json[i].buy.weightedAverage),
parseFloat(json[i].buy.max),
parseFloat(json[i].buy.min),
parseFloat(json[i].buy.stddev),
parseFloat(json[i].buy.median),
parseFloat(json[i].buy.percentile),
parseInt(json[i].sell.volume),
parseFloat(json[i].sell.weightedAverage),
parseFloat(json[i].sell.max),
parseFloat(json[i].sell.min),
parseFloat(json[i].sell.stddev),
parseFloat(json[i].sell.median),
parseFloat(json[i].sell.percentile)];
prices.push(price);
}
}
}
return prices;
}
function storeData() {
var sheet = SpreadsheetApp.getActiveSheet();
var datarange = sheet.getDataRange();
var numRows = datarange.getNumRows();
var numColumns = datarange.getNumColumns();
sheet.getRange(1,numColumns + 1).setValue(new Date());
for (var i=2; i <= numRows; i++) {
var prices = sheet.getRange(i, 8).getValue();
sheet.getRange(i, numColumns + 1).setValue(prices);
}
}
I found this link:
TypeError: Cannot find function forEach in object
Which explains some of it, but when the code runs in debug mode, I don't have a range defined, and then the live sheet bugs with:
ReferenceError: "row" is not defined (line 21).
Note: My original code works fine when I run the script locally, just not when the daily timer goes off. I'm assuming the issues listed on the linked query regarding no forEach class on the nested object are applying to whatever runs the triggered .gs
Edit: Ok so i've been working on this all day. I've learned how to set an array var (since I didn't need to change the array, static works) and i've been trying to get the rest of the code adjusted ever since. Here's where I am so far:
// Requires a list of typeids, so something like Types!A:A
// https://docs.google.com/spreadsheets/d/1IixV0eNqg19FE6cLzb83G1Ucb0Otl-Jnvm6csAlPKwo/edit?usp=sharing for an example
function loadRegionAggregates(priceIDs,regionID){
if (typeof regionID == 'undefined'){
regionID=10000002;
}
if (typeof priceIDs == 'undefined'){
priceIDs = [34,35,36,37,38,39,40,11399];
}
var prices = new Array();
var dirtyTypeIds = new Array();
var cleanTypeIds = new Array();
var url="https://market.fuzzwork.co.uk/aggregates/?station=60003760&types=34,35,36,37,38,39,40"
// for (var priceIDs) {
// if (row.hasOwnProperty(column)) {
// var cell = row[column];
// if (typeof cell == "number") {
priceIDs.push(priceIDs);
// }
// }
// }
cleanTypeIds = dirtyTypeIds.filter(function(v,i,a) {
return a.indexOf(v)===i;
});
prices.push(['TypeID','Buy volume','Buy Weighted Average','Max Buy','Min Buy','Buy Std Dev','Median Buy','Percentile Buy Price','Sell volume','Sell Weighted Average','Max sell','Min Sell','Sell Std Dev','Median Sell','Percentile Sell Price'])
var parameters = {method : "get", payload : ""};
var o,j,temparray,chunk = 100;
for (o=0,j=cleanTypeIds.length; o < j; o+=chunk) {
temparray = cleanTypeIds.slice(o,o+chunk);
Utilities.sleep(100);
var types=temparray.join(",").replace(/,$/,'')
var jsonFeed = UrlFetchApp.fetch(url+types, parameters).getContentText();
var json = JSON.parse(jsonFeed);
if(json) {
for(i in json) {
var price=[parseInt(i),
parseInt(json[i].buy.volume),
parseInt(json[i].buy.weightedAverage),
parseFloat(json[i].buy.max),
parseFloat(json[i].buy.min),
parseFloat(json[i].buy.stddev),
parseFloat(json[i].buy.median),
parseFloat(json[i].buy.percentile),
parseInt(json[i].sell.volume),
parseFloat(json[i].sell.weightedAverage),
parseFloat(json[i].sell.max),
parseFloat(json[i].sell.min),
parseFloat(json[i].sell.stddev),
parseFloat(json[i].sell.median),
parseFloat(json[i].sell.percentile)];
prices.push(price);
}
}
}
return prices;
}
function storeData() {
var sheet = SpreadsheetApp.getActiveSheet();
var datarange = sheet.getDataRange();
var numRows = datarange.getNumRows();
var numColumns = datarange.getNumColumns();
sheet.getRange(1,numColumns + 1).setValue(new Date());
for (var i=2; i <= numRows; i++) {
var prices = sheet.getRange(i, 8).getValue();
sheet.getRange(i, numColumns + 1).setValue(prices);
}
}
So far, so good. It's not throwing any more errors on debug, and the column headers are coming in, but it's not bringing in the data anymore from the push.
Help!
I wanted to convert a string to map.
String value = "{first_name : fname,last_name : lname,gender : male, location : { state : state, country : country, place : place} }"
into
Map = {
first_name : fname,
last_name : lname,
gender : male,
location = {
state : state,
country : country,
place : place
}
}
How do I convert the string into a map<String, dynamic> where the value consists of string, int, object, and boolean?
I wanted to save the string to a file and obtain the data from the file.
That's not possible.
If you can change the string to valid JSON, you can use
import 'dart:convert';
...
Map valueMap = json.decode(value);
// or
Map valueMap = jsonDecode(value);
The string would need to look like
{"first_name" : "fname","last_name" : "lname","gender" : "male", "location" : { "state" : "state", "country" : "country", "place" : "place"} }
You would have to change the way you create the string.
I'm guessing you are creating the string using the yourMap.toString() method. You should rather use json.encode(yourMap), which converts your map to valid JSON, which you can the parse with json.decode(yourString).
create two objects
class User {
final String firstName;
final String lastName;
final String gender;
final location;
User({
this.firstName,
this.lastName,
this.gender,
this.location,
});
User.fromJson(Map json)
: firstName = json['firstName'],
lastName = json['lastName'],
gender = json['gender'],
location = Location.fromJson(json['location']);
}
class Location {
final String state;
final String country;
final String place;
Location({
this.state,
this.country,
this.place,
});
Location.fromJson(Map json)
: state = json['state'],
country = json['country'],
place = json['place'];
}
then use it like this
var user = User.fromJson(value);
print(user.firstName);
or convert it to list like this
var user = User.fromJson(value).toList();
you can do like this ->
import 'dart:convert';
...
if your data like this **
{'bus1':'100Tk','bus2':'150TK','bus3':'200TK'}
**;
then you can do like this ->
Map valueMap = json.decode(value);
// or
Map valueMap = jsonDecode(value);
or if like this ->var data = {'1':'100TK','2':'200TK','3':'300TK'};
var dataSp = data.split(',');
Map<String,String> mapData = Map();
dataSp.forEach((element) => mapData[element.split(':')[0]] = element.split(':')[1]);
Note: Map first value was Int that's why I did that.
Make a wrapper class for the location where you define the methods fromMap, toMap
Yeah, that's not possible.
But i have workaround to fix that.
Remove space in ur invalid json
Fix ur invalid string json to valid string json
Convert valid string json to map
Here's the full code for above process:
import 'dart:convert';
void main() {
String value = "{first_name : fname,last_name : lname,gender : male, location : { state : state, country : country, place : place} }";
String jsonString = _convertToJsonStringQuotes(raw: value);
print("Test 1: $jsonString");
final Map<dynamic, dynamic> result = json.decode(jsonString);
print('Test 2: $result');
}
String _convertToJsonStringQuotes({required String raw}) {
/// remove space
String jsonString = raw.replaceAll(" ", "");
/// add quotes to json string
jsonString = jsonString.replaceAll('{', '{"');
jsonString = jsonString.replaceAll(':', '": "');
jsonString = jsonString.replaceAll(',', '", "');
jsonString = jsonString.replaceAll('}', '"}');
/// remove quotes on object json string
jsonString = jsonString.replaceAll('"{"', '{"');
jsonString = jsonString.replaceAll('"}"', '"}');
/// remove quotes on array json string
jsonString = jsonString.replaceAll('"[{', '[{');
jsonString = jsonString.replaceAll('}]"', '}]');
return jsonString;
}
To convert a string into a map<String, dynamic>, you can use the
following code:
String value = "{first_name : fname,last_name : lname,gender : male, location : { state : state, country : country, place : place} }";
String result = value
.replaceAll("{","{\"")
.replaceAll("}","\"}")
.replaceAll(":","\":\"")
.replaceAll(",","\",\"");
print(result);
Here, we first replace the opening and closing curly braces with double quotes, and then replace the colons and commas with quotes to create a valid JSON string. Then, we use the jsonDecode method to convert the JSON string into a map.
I found a way to cast that string
Ok, lets use a complex model to cast:
final testMap = {
'userName': 'Igor',
'age': 22,
'totalCash': 138.57,
'isMale:': true,
'userStatus': {
'isUserActive': true,
'isAPremiumUser': false,
},
'userTags': ['Flutter Developer', 'Proactive', 'Clean code'],
'userCourses': [
{
'title': 'How to use TDD in flutter',
'finished': false,
'coursePercentage': 47.4,
'buyDate': '1969-07-20T20:18:04.000Z',
'courseTag': ['New', 'Popular'],
'courseDetails': null,
},
{
'title': 'Clean arquiteture in flutter',
'finished': false,
'coursePercentage': 20.8,
'buyDate': '1969-07-20T20:18:04.000Z',
'courseTag': ['New'],
'courseDetails': {
'teacherName': 'Tayler Mostoult',
'totalSubscribers': 5402,
},
},
{
'title': 'Micro-frontends in flutter',
'finished': true,
'coursePercentage': 100.0,
'buyDate': '1969-07-20T20:18:04.000Z',
'courseTag': [],
'courseDetails': {},
},
]
};
Know, cast it to string:
final testMapInStringFormat = testMap.toString();
To convert this String to map, we can use:
final String response = _getJsonFromString(testMap.toString());
final Map jsonConvertido = jsonDecode(response); // Decoded, back to map format
The function that will effectively do the casting:
String _getJsonFromString(String rawText) {
// Will find, for exemple, the text: "{isUserActive:"
final regexMapKeyWithOpenBracket = RegExp('(?<={)(.*?):+');
// Will find, for exemple, the text: ", userCourses:"
final regexMapKeyWithCommaAndSpace = RegExp(r'(?<=, )([^\]]*?):');
final regexOnlyKeyInLine = RegExp(r'^.+:$');
final splitedSentences = rawText
.replaceAllMapped(regexMapKeyWithCommaAndSpace,
(Match match) => '\n${match.text.trim()}\n')
.replaceAllMapped(regexMapKeyWithOpenBracket,
(Match match) => '\n${match.text.trim()}\n')
.replaceAll(RegExp(r'}(?=,|]|}|$|\s+)'), '\n}\n')
.replaceAll(RegExp(r'(?<=(,|:|^|\[)\s?){'), '\n{\n')
.replaceAll(RegExp('\\[\\s?\\]'), '\n[\n]\n')
.replaceAll(RegExp('\\{\\s?\\}'), '\n{\n}\n')
.split('\n')
..removeWhere((element) => element.replaceAll(' ', '').isEmpty);
final List<String> correctLines = [];
for (String line in splitedSentences) {
final isMapKey = regexOnlyKeyInLine.hasMatch(line);
if (isMapKey) {
final lineWithoutFinalTwoDots = line.substring(0, line.length - 1);
final lineWithQuaot = _putQuotationMarks(lineWithoutFinalTwoDots);
correctLines.add('$lineWithQuaot:');
} else {
String l = line.trim();
// If it falls in this else, it is a value of a key or a map structure
final isNumber = double.tryParse(l) != null || int.tryParse(l) != null;
final isBolean = l == 'false' || l == 'true';
final isStructureCaracter = ['{', '}', '[', ']', ','].any((e) => e == l);
final isNull = l == 'null';
if (isStructureCaracter || isNumber || isBolean || isNull) {
correctLines.add(l);
continue;
}
final hasCommaInFinal = l.endsWith(',');
if (hasCommaInFinal) {
l = l.substring(0, l.length - 1);
}
// If you got to this point, i'm sure it's a value string, so lets add a double quote
final lineWithQuaot = _putQuotationMarks(l);
if (hasCommaInFinal) {
correctLines.add('$lineWithQuaot,');
} else {
correctLines.add(lineWithQuaot);
}
}
}
return correctLines.join('');
}
extension MatchExtension on Match {
String get text => input.substring(start, end);
}
String _putQuotationMarks(String findedText) {
if (!findedText.startsWith('\'') && !findedText.startsWith('"')) {
findedText = findedText[0] + findedText.substring(1);
}
if (!findedText.endsWith('\'')) {
final lastIndex = findedText.length - 1;
findedText = findedText.substring(0, lastIndex) + findedText[lastIndex];
}
return '"$findedText"';
}
Use below method
just pass String json data it will give Map data
jsonStringToMap(String data){
List<String> str = data.replaceAll("{","").replaceAll("}","").replaceAll("\"","").replaceAll("'","").split(",");
Map<String,dynamic> result = {};
for(int i=0;i<str.length;i++){
List<String> s = str[i].split(":");
result.putIfAbsent(s[0].trim(), () => s[1].trim());
}
return result;
}
I have got following script in Google docs spreadsheet which is fetching data from Fitbit. Script worked fine so far but recently on 6th July Google stopped using OAuthConfig so script is not working since:-(
I am not programmer, I am just advanced user. So I would like to kindly ask some programmer to help tune script below in order to make it work again.
// Key of ScriptProperty for Firtbit consumer key.
var CONSUMER_KEY_PROPERTY_NAME = "fitbitConsumerKey";
// Key of ScriptProperty for Fitbit consumer secret.
var CONSUMER_SECRET_PROPERTY_NAME = "fitbitConsumerSecret";
// Default loggable resources (from Fitbit API docs).
var LOGGABLES = ["activities/log/steps", "activities/log/distance",
"activities/log/activeScore", "activities/log/activityCalories",
"activities/log/calories", "foods/log/caloriesIn",
"activities/log/minutesSedentary",
"activities/log/minutesLightlyActive",
"activities/log/minutesFairlyActive",
"activities/log/minutesVeryActive", "sleep/timeInBed",
"sleep/minutesAsleep", "sleep/minutesAwake", "sleep/awakeningsCount",
"body/weight", "body/bmi", "body/fat",];
// function authorize() makes a call to the Fitbit API to fetch the user profile
function authorize() {
var oAuthConfig = UrlFetchApp.addOAuthService("fitbit");
oAuthConfig.setAccessTokenUrl("https://api.fitbit.com/oauth/access_token");
oAuthConfig.setRequestTokenUrl("https://api.fitbit.com/oauth/request_token");
oAuthConfig.setAuthorizationUrl("https://api.fitbit.com/oauth/authorize");
oAuthConfig.setConsumerKey(getConsumerKey());
oAuthConfig.setConsumerSecret(getConsumerSecret());
var options = {
"oAuthServiceName": "fitbit",
"oAuthUseToken": "always",
};
// get the profile to force authentication
Logger.log("Function authorize() is attempting a fetch...");
try {
var result = UrlFetchApp.fetch("https://api.fitbit.com/1/user/-/profile.json", options);
var o = Utilities.jsonParse(result.getContentText());
return o.user;
}
catch (exception) {
Logger.log(exception);
Browser.msgBox("Error attempting authorization");
return null;
}
}
// function setup accepts and stores the Consumer Key, Consumer Secret, firstDate, and list of Data Elements
function setup() {
var doc = SpreadsheetApp.getActiveSpreadsheet();
var app = UiApp.createApplication().setTitle("Setup Fitbit Download");
app.setStyleAttribute("padding", "10px");
var consumerKeyLabel = app.createLabel("Fitbit OAuth Consumer Key:*");
var consumerKey = app.createTextBox();
consumerKey.setName("consumerKey");
consumerKey.setWidth("100%");
consumerKey.setText(getConsumerKey());
var consumerSecretLabel = app.createLabel("Fitbit OAuth Consumer Secret:*");
var consumerSecret = app.createTextBox();
consumerSecret.setName("consumerSecret");
consumerSecret.setWidth("100%");
consumerSecret.setText(getConsumerSecret());
var firstDate = app.createTextBox().setId("firstDate").setName("firstDate");
firstDate.setName("firstDate");
firstDate.setWidth("100%");
firstDate.setText(getFirstDate());
// add listbox to select data elements
var loggables = app.createListBox(true).setId("loggables").setName(
"loggables");
loggables.setVisibleItemCount(4);
// add all possible elements (in array LOGGABLES)
var logIndex = 0;
for (var resource in LOGGABLES) {
loggables.addItem(LOGGABLES[resource]);
// check if this resource is in the getLoggables list
if (getLoggables().indexOf(LOGGABLES[resource]) > -1) {
// if so, pre-select it
loggables.setItemSelected(logIndex, true);
}
logIndex++;
}
// create the save handler and button
var saveHandler = app.createServerClickHandler("saveSetup");
var saveButton = app.createButton("Save Setup", saveHandler);
// put the controls in a grid
var listPanel = app.createGrid(6, 3);
listPanel.setWidget(1, 0, consumerKeyLabel);
listPanel.setWidget(1, 1, consumerKey);
listPanel.setWidget(2, 0, consumerSecretLabel);
listPanel.setWidget(2, 1, consumerSecret);
listPanel.setWidget(3, 0, app.createLabel(" * (obtain these at dev.fitbit.com)"));
listPanel.setWidget(4, 0, app.createLabel("Start Date for download (yyyy-mm-dd)"));
listPanel.setWidget(4, 1, firstDate);
listPanel.setWidget(5, 0, app.createLabel("Data Elements to download:"));
listPanel.setWidget(5, 1, loggables);
// Ensure that all controls in the grid are handled
saveHandler.addCallbackElement(listPanel);
// Build a FlowPanel, adding the grid and the save button
var dialogPanel = app.createFlowPanel();
dialogPanel.add(listPanel);
dialogPanel.add(saveButton);
app.add(dialogPanel);
doc.show(app);
}
// function sync() is called to download all desired data from Fitbit API to the spreadsheet
function sync() {
// if the user has never performed setup, do it now
if (!isConfigured()) {
setup();
return;
}
var user = authorize();
// Spatny kod, oprava nize - var doc = SpreadsheetApp.getActiveSpreadsheet();
var doc = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Pavel');
doc.setFrozenRows(1);
var options = {
"oAuthServiceName": "fitbit",
"oAuthUseToken": "always",
"method": "GET"
};
// prepare and format today's date, and a list of desired data elements
var dateString = formatToday();
var activities = getLoggables();
// for each data element, fetch a list beginning from the firstDate, ending with today
for (var activity in activities) {
var currentActivity = activities[activity];
try {
var result = UrlFetchApp.fetch("https://api.fitbit.com/1/user/-/"
+ currentActivity + "/date/" + getFirstDate() + "/"
+ dateString + ".json", options);
} catch (exception) {
Logger.log(exception);
Browser.msgBox("Error downloading " + currentActivity);
}
var o = Utilities.jsonParse(result.getContentText());
// set title
var titleCell = doc.getRange("a1");
titleCell.setValue("date");
var cell = doc.getRange('a2');
// fill the spreadsheet with the data
var index = 0;
for (var i in o) {
// set title for this column
var title = i.substring(i.lastIndexOf('-') + 1);
titleCell.offset(0, 1 + activity * 1.0).setValue(title);
var row = o[i];
for (var j in row) {
var val = row[j];
cell.offset(index, 0).setValue(val["dateTime"]);
// set the date index
cell.offset(index, 1 + activity * 1.0).setValue(val["value"]);
// set the value index index
index++;
}
}
}
}
function isConfigured() {
return getConsumerKey() != "" && getConsumerSecret() != "";
}
function setConsumerKey(key) {
ScriptProperties.setProperty(CONSUMER_KEY_PROPERTY_NAME, key);
}
function getConsumerKey() {
var key = ScriptProperties.getProperty(CONSUMER_KEY_PROPERTY_NAME);
if (key == null) {
key = "";
}
return key;
}
function setLoggables(loggable) {
ScriptProperties.setProperty("loggables", loggable);
}
function getLoggables() {
var loggable = ScriptProperties.getProperty("loggables");
if (loggable == null) {
loggable = LOGGABLES;
} else {
loggable = loggable.split(',');
}
return loggable;
}
function setFirstDate(firstDate) {
ScriptProperties.setProperty("firstDate", firstDate);
}
function getFirstDate() {
var firstDate = ScriptProperties.getProperty("firstDate");
if (firstDate == null) {
firstDate = "2012-01-01";
}
return firstDate;
}
function formatToday() {
var todayDate = new Date;
return todayDate.getFullYear()
+ '-'
+ ("00" + (todayDate.getMonth() + 1)).slice(-2)
+ '-'
+ ("00" + todayDate.getDate()).slice(-2);
}
function setConsumerSecret(secret) {
ScriptProperties.setProperty(CONSUMER_SECRET_PROPERTY_NAME, secret);
}
function getConsumerSecret() {
var secret = ScriptProperties.getProperty(CONSUMER_SECRET_PROPERTY_NAME);
if (secret == null) {
secret = "";
}
return secret;
}
// function saveSetup saves the setup params from the UI
function saveSetup(e) {
setConsumerKey(e.parameter.consumerKey);
setConsumerSecret(e.parameter.consumerSecret);
setLoggables(e.parameter.loggables);
setFirstDate(e.parameter.firstDate);
var app = UiApp.getActiveApplication();
app.close();
return app;
}
// function onOpen is called when the spreadsheet is opened; adds the Fitbit menu
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var menuEntries = [{
name: "Sync",
functionName: "sync"
}, {
name: "Setup",
functionName: "setup"
}, {
name: "Authorize",
functionName: "authorize"
}];
ss.addMenu("Fitbit", menuEntries);
}
// function onInstall is called when the script is installed (obsolete?)
function onInstall() {
onOpen();
}
Problem solved with updated code at https://github.com/loghound/Fitbit-for-Google-App-Script
I am making my first migration script that involves the use of the Groovy/Grails DSL. It looks like this:
import my.package.MyObject
import my.package.MyObjectUtil
import javax.xml.bind.DatatypeConverter
databaseChangeLog = {
changeSet(author: 'wheresjim', id: '1387238199-1') {
comment {'Sets the timestamp in each MyObject where null using the message text'}
grailsChange {
change {
MyObjectUtil myObjectUtil = new MyObjectUtil()
def criteria = MyObject.where {
isNull("timestamp")
}
def PAGESIZE = 10
int numRows = criteria.count()
int pages = Math.ceil(numRows / PAGESIZE)
(pages..0).each { page ->
int offset = PAGESIZE * page + PAGESIZE
def data = criteria.list(offset: offset, max: PAGESIZE, sort: 'id', order: 'asc')
data.each { MyObject myObject ->
Date timestamp = new Date(0L)
try {
def thisMessage = myObjectUtil.createMyObjectFromMessage(myObject.messageText)
String dateStr = thisMessage.messageIdentification?.timestamp
timestamp = dateStr ? DatatypeConverter.parseDateTime(dateStr).getTime() : timestamp
} catch (Exception e) {
// Do nothing, this will be logged in the finally which catches another error condition
} finally {
if (timestamp == new Date(0L)) {
log.warn "Error attempting to set timestamp in MyObject ${myObject.id}, setting to $eventDateTime"
}
}
myObject.timestamp = timestamp
myObject.save(flush: true)
}
log.warn "Updated ${myObject.id}"
}
}
}
}
}
The MyObject.messageText is a clob in the database, and to my knowledge, there has been no effort to lazily load it.
I should note that this exact script works (it can find the clob text) using the grails console plugin on the app.
In MyObject make sure your have the following lines:
static mapping = {
messageText type: 'text'
}