appmaker - Error With Email Change Notifications Using Project Tracker Template -
short version:
i using code project tracker template send out emails showing change in status field (contact name changed from: billy -> susan).
everything works expect when have field date instead of string. if have date field in code following error:
'string' value expected 'newvalue' field in model 'systemordershistory', found object. error: 'string' value expected 'newvalue' field in model 'systemordershistory', found object. @ onsystemorderssave_ (datasources:218) @ models.systemorders.onsaveevent:1
modifying records: (error) : 'string' value expected 'newvalue' field in model 'systemordershistory', found object.
(error) : 'string' value expected 'newvalue' field in model 'systemordershistory', found object.
any appreciated!
long version
i using code below (adjusted fit names of models , fields).
whenever add date field (ex: deliverydate) function "notifyaboutitemchanges_" function , "onsystemorderssave_" function error "expecting string, found object".
note: oldvalue , newvalue fields in "history" model both strings.
notifications server script:
/** * sends email. * @param {!string} - email address of recipient. * @param {!string} subject - subject of email message. * @param {!string} body - body of email message. */ function sendemail_(to, subject, body) { try { mailapp.sendemail({ to: to, subject: subject, htmlbody: body, noreply: true }); } catch (e) { // suppressing errors in email sending because email notifications // not critical functioning of app. console.error(json.stringify(e)); } } /** * sends email notification recent project item changes item owner * , assignee. * @param {!array<itemhistory>} changes - list of recent project item changes. */ function notifyaboutitemchanges_(changes) { if (!changes || changes.length < 2) { return; } var settings = getappsettingsrecord_()[0]; if (!settings.enableemailnotifications) { return; } var data = { appurl: settings.appurl, itemshowname: changes[0].showname, itemusersposition: changes[0].usersposition, itemdeliveryinfo: changes[0].deliveryinfo, itemdeliverydate: changes[0].deliverydate, itemkey: changes[0]._key, itemname: changes[0].name, modifiedby: changes[0].modifiedby, changes: changes }; // email subject. var subjecttemplate = htmlservice.createtemplate(settings.notificationemailsubject); subjecttemplate.data = data; var subject = subjecttemplate.evaluate().getcontent(); // email body. var emailtemplate = htmlservice.createtemplate(settings.notificationemailbody); emailtemplate.data = data; var htmlbody = emailtemplate.evaluate().getcontent(); sendemail_('user@gmail.com', subject, htmlbody);
datasources server script:
/** * item key url parameter. */ var item_key = 'itemkey'; /** * checks application settings record exists. * otherwise creates new one. * @return {!array<appsettings>} app settings record array. */ function getappsettingsrecord_() { var newquery = app.models.appsettings.newquery(); var settingsrecords = newquery.run(); if (settingsrecords.length > 1) { console.warn('there more one(%s) app settings entries' + 'in database', settingsrecords.length); } if (settingsrecords.length === 0) { var settingsrecord = app.models.appsettings.newrecord(); settingsrecord.appurl = scriptapp.getservice().geturl(); settingsrecord.notificationemailsubject = 'a change has been made <?= data.itemshowname?>: <?= data.itemusersposition?>'; settingsrecord.notificationemailbody = 'hello!\n<br/>\n<p><b><?= data.modifiedby ?></b> ' + 'made following changes: </p>\n' + '<? (var = 1; < data.changes.length; i++) {\n' + '\tvar change = data.changes[i]; ?>\n' + '\t<b><?= change.fieldname ?>: </b>\n' + '\t<? if (change.fieldname === "comment") { ?>\n' + '\t\t<div style="white-space: pre-line;"><?= change.newvalue ?></div>' + '\n\t<? } else { ?>\n ' + '\t\t<?= change.oldvalue ?> → <?= change.newvalue ?>' + '\n\t<? } ?>\n\t<br/>\n' + '<? } ?>\n<br/>\n' + '<a href="<?= data.appurl ?>?' + item_key + '=<?= data.itemkey ?>' + '#edititem" target="_blank">go project item</a>'; app.saverecords([settingsrecord]); return [settingsrecord]; } else { return settingsrecords; } } /** * populates project record required data on project create event. * @param {!project} project - project being created. */ function onprojectcreate_(project) { var date = new date(); project.createddate = date; project.modifieddate = date; project.modifiedby = currentuseremail_(); } /** * audits project on changes. * @param {!project} project - project being modified. */ function onprojectsave_(project) { project.modifieddate = new date(); project.modifiedby = currentuseremail_(); } /** * populates project item required data on item create event, adds * comment entry project item history. * @param {!systemorders} systemorders - project item being created. */ function onsystemorderscreate_(systemorders) { var date = new date(); var editor = currentuseremail_(); if (systemorders.comment) { systemorders.comment = systemorders.comment.trim(); } systemorders.createddate = date; systemorders.owner = editor; systemorders.modifieddate = date; systemorders.modifiedby = editor; if (systemorders.comment) { var history = app.models.systemordershistory.newrecord(); history.createdby = currentuseremail_(); history.createddate = new date(); history.fieldname = 'comment'; history.newvalue = systemorders.comment; app.saverecords([history]); systemorders.history.push(history); } } /** * calculates history entries sum {array<systemorders>}. * @param {!number} historysum - accumulated number of history entries * returned in last invocation of callback, or * initialvalue, if supplied. * @param {!systemorders} systemorders - current {systemorders} being * processed in array. * @return {!number} history entries sum. */ function sumhistory_(historysum, systemorders) { return historysum + systemorders.history.length; } /** * calculates potential project deletion impact. * throws error if there no project key provided. * @param {!string} projectkey - project key calculate deletion impact. */ function getdeleteprojectimpact(projectkey) { var projectquery = app.models.project.newquery(); projectquery.prefetch.items._add(); projectquery.prefetch.items.history._add(); projectquery.filters._key._equals = projectkey; var projects = projectquery.run(); if (projects.length === 0) { throw new error('project key ' + projectkey + ' not found.'); } var systemorderss = projects[0].items; return { affecteditems: systemorderss.length, affectedhistory: systemorderss.reduce(sumhistory_, 0) }; } /** * checks project item readonly fields not modified. * throws error if user attempts modify read fields. * @param {!systemorders} record - modified project item. * @param {!systemorders} oldrecord - project item before modification. */ function validateitemchange_(record, oldrecord) { var readonlyfields = [ 'createddate', 'modifiedby', 'modifieddate', 'owner' ]; (var = 0; < readonlyfields.length; i++) { var field = readonlyfields[i]; var newvalue = record[field]; var oldvalue = oldrecord[field]; var isdate = newvalue instanceof date && oldvalue instanceof date; if (isdate === true) { newvalue = record[field].getdate(); oldvalue = oldrecord[field].getdate(); } if (newvalue === oldvalue) { continue; } throw new error(field + ' field read only'); } } /** * handles project item change event, creates history entries each changed * field. * @param {!systemorders} record - modified project item. * @param {!systemorders} oldrecord - project item before modification. */ function onsystemorderssave_(record, oldrecord) { validateitemchange_(record, oldrecord); var editablefields = [ 'showname', 'usersposition', 'deliveryinfo', 'deliverydate' ]; var editor = currentuseremail_(); var date = new date(); var changes = [record]; record.modifiedby = editor; record.modifieddate = date; (var = 0; < editablefields.length; i++) { var field = editablefields[i]; var newvalue = record[field]; var oldvalue = oldrecord[field]; if (newvalue !== oldvalue) { var history = app.models.systemordershistory.newrecord(); history.item = record; history.createdby = editor; history.createddate = date; history.fieldname = field; history.newvalue = newvalue; history.oldvalue = oldvalue; changes.push(history); } } app.saverecords(changes); notifyaboutitemchanges_(changes); } /** * counts project items grouping criteria(field). * @param {!string} projectkey - project key calculate stats. * @param {!string} grouping - project item field group items by. * @param {!array<string>} groupingvalues - possible field values. * @return {!array<systemorderssbreakdown>} grouped project items counts. */ function getsystemorderssbreakdown_(projectkey, grouping, groupingvalues) { if (!grouping || !groupingvalues || groupingvalues.length === 0) { return []; } var itemsquery = app.models.systemorders.newquery(); itemsquery.prefetch.project._add(); itemsquery.filters.project._key._equals = projectkey; var items = itemsquery.run(); if (items.length === 0) { return []; } var records = []; var map = {}; (var = 0; < items.length; i++) { var itemgrouping = items[i][grouping]; if (!map[itemgrouping]) { map[itemgrouping] = 0; } map[itemgrouping]++; } (i = 0; < groupingvalues.length; i++) { var breakdownrecord = app.models.systemorderssbreakdown.newrecord(); var groupingvalue = groupingvalues[i]; breakdownrecord.grouping = groupingvalue; breakdownrecord.itemscount = map[groupingvalue] || 0; records.push(breakdownrecord); } return records; }
it fails here:
// history.newvalue , history.oldvalue strings // newvalue , oldvalue can of type (boolean, number, date, // not relation of now) // getting exception because not casting types history.newvalue = newvalue; history.oldvalue = oldvalue;
you can fix adding fields of each possible type history model (newstringvalue, newdatevalue, newboolvalue, newnumbervalue, oldstringvalue...). approach you'll benefits of strong typing, code , ui become more complex...
you can store fields' history strings(like doing now), in case you'll need think formatting , localization in advance:
function fieldtostring(field, fieldvalue) { // todo: pass field metadata individually handle // different data types. return fieldvalue !== null ? fieldvalue.tostring() : null; } ... history.newvalue = fieldtostring(field, newvalue); history.oldvalue = fieldtostring(field, oldvalue); ...
Comments
Post a Comment