Google apps script UI how to insert message into VerticalPanel()? - user-interface

I'm trying to write this simple UI which should show a message with two parameters (name, email2send). But, when I run it, I get only this:
The content of the variable msg is not shown in the pannel, only the pannel title. What is the right way to do this?
// Show confirmation
function showMSG(name, email2Send) { // for ease of use I give the urls as parameters but you could define these urls in the function as well
var app = UiApp.createApplication().setHeight(60).setWidth(200);
app.setTitle("Msg send with sucess!");
var msg = "You request to " + email2Send + " a response from " + name;
app.add(app.createVerticalPanel().setTag(msg));
var doc = SpreadsheetApp.getActive();
doc.show(app);
}
THANKS in advance for any help!

The TAG of a widget is not visible, its purpose is actually to store string data in a way that is not visible.
To show a text use a label or an html widget.
example in your code :
function showMSG(name, email2Send) { // for ease of use I give the urls as parameters but you could define these urls in the function as well
var app = UiApp.createApplication().setHeight(60).setWidth(200);
app.setTitle("Msg send with sucess!");
var msg = "You request to " + email2Send + " a response from " + name;
app.add(app.createVerticalPanel().add(app.createLabel(msg)));
var doc = SpreadsheetApp.getActive();
doc.show(app);
}

Related

GAS sends multiple slack notifications

I'd like to send notifications to slack using Google Apps Scripts.
It's supposed to happen when any change is made on Gsheet but sometimes the program sends multiple notifications even after only a single change.
I guess my code has any fault or the triggers cause any problem - because I apply this function to more than one sheet.
But it doesn't happen "always", so is it an error caused by GAS specification?
My code is as below. Any idea or advice will be appreciated.
function slackNotification(sheetname,slack_channel){
Logger.log(sheetname)
var mySheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetname);
var myActiveRange = mySheet.getActiveRange();
var lastRow = mySheet.getLastRow();
var pdfColumn = PDF_column_n
var checkColumn = slack_column_n
Logger.log(lastRow)
var unslacked = [];
for (var i=1; i <= lastRow; i++){
var cell = mySheet.getRange(i, checkColumn);
if(cell.isBlank()){
Logger.log(i + "is not yet notified");
unslacked.push(i);
}
}
Logger.log("unslcked yet"+unslacked)
for (var i=unslacked[0]; i <= unslacked[unslacked.length - 1]; i++){
var pdfCell = mySheet.getRange(extention_column+i)
var checkSlack = mySheet.getRange(slack_column+i)
Logger.log(checkSlack.isBlank())
Logger.log(pdfCell.getValue())
if (pdfCell.getValue() == "pdf" && checkSlack.isBlank() == true)
{
var requestId = mySheet.getRange(request_column+i).getValue();
var clientId = mySheet.getRange(client_column+i).getValue();
var claimId = mySheet.getRange(claim_column+i).getValue();
var groupId = mySheet.getRange(group_column+i).getValue();
Logger.log(groupId)
if (branches.includes(groupId)){
var message = "\nrequestId:" + requestId + "\nclientId:" + clientId + "\nclaimId:" + claimId
var postUrl = slack_channel
var username = 'botbot';
var icon = ':hatching_chick:';
var jsonData =
{
"username" : username,
"icon_emoji": icon,
"text" : message,
link_names: 1
};
var payload = JSON.stringify(jsonData);
var options =
{
"method" : "post",
"contentType" : "application/json",
"payload" : payload
};
UrlFetchApp.fetch(postUrl, options);
checkSlack.setValue(true);
}
}
}
}
Apps Script triggers are bound project-based, not script filed based
If you have multiple functions with the same name in your project - there is no way to know to which one the trigger becomes bound.
Most likely your 5 onChange triggers are all bound to the same function and thus the function is executed multiple times (sending multiple Slack notifications as a result)
It does not make a difference if the functions are located within the same script file or within different script files within the same project
Splitting up your code into different code files is only for visibility/structure purposes, for the trigger the functions are still located all within the same project
Solution
Remove from your project all existing triggers that are bound to functions with not unique names
Give to all functions in your project (not just script file) unique names and save all script files
Install new triggers either programmatically or manually
Now you can be sure that each trigger is bound to a different function
Mind that if the content of your functions is the same (e.g. containing request to send Slack notifications), this content will be executed the same amount of times as the number of respective triggers in your project, so if the content of your functions is identical, you actually need to keep only one function bound to one trigger and remove all the functions of identic content.

Unable to view PDF attached with ServiceNow table records

I am able to successfully attach PDF file with ServiceNow table record using GlideSysAttachment API and attachment.write() function in script, however whenever I download and try to open same, I get the error shown in below screenshot.
Code snippet
(function execute() {
try{
var rec = new GlideRecord('incident');
var attachment = new GlideSysAttachment();
var incidentSysID = incident.number;
rec.get(incidentSysID);
var fileName = 'Test_Incident.pdf';
var contentType = 'application/pdf'; // Also tried with contentType as 'text/pdf'
var content = pdf_content;
var agr = attachment.write(rec, fileName, contentType, content);<br>
gs.info('The PDF attachment sys_id is: ' + agr);
}catch(err){
gs.log('Got Error: ' + err);
gs.info(err);
}
})()
I also tried "AttachmentCreator" with ecc_queue within script but same error occurs. Below is code for it.
(function execute()
{var attCreator = new GlideRecord('ecc_queue');
attCreator.agent = "AttachmentCreator";
attCreator.topic = "AttachmentCreator";
attCreator.name = "Test.pdf" + ":" + "text/pdf";
//Also tried, "Test.pdf:application/pdf"
attCreator.source = "incident"+":"+ incident.number;
// Record Table name and sys_id of the particular record
var content = pdf_content; // pdf_content is any string variable
var stringUtil = new GlideStringUtil();
var base64String = stringUtil.base64Encode(content);
var isValid=GlideStringUtil.isBase64(base64String);
var base64String= gs.base64Encode(content);
gs.info("Is valid base64 format in ecc_queue ? "+ isValid);
attCreator.payload = base64String; //base64 content of the file
attCreator.insert();
})()
I am able to attach and view excel and word files with similar scripts without any issues. I have checked system properties for attachments but everything looks fine. I am able to view the PDF file uploaded from UI to particular table records however not the one I attach via REST API or scripts.
I have also tried sending encoded data as bytes, base64 or simple string but nothing seems to work. I don't get any errors and attachment id is returned each time on creation of attachment.
After modifying my code slightly for above functions w.r.t scoped application instead of global; I got some information from logs when I debug:
05:38:38.411 Security restricted: File type is not allowed or does not match the content for file Test.pdf
05:38:38.410 Security restricted: MIME type mismatch for file: Test.pdf. Expected type:application/pdf, Actual type: text/plain
05:38:38.394 App:XYZ App x_272539_xyz_ap: Is valid base64 format in ecc_queue ? true
First a comment: This line in your code is accidentally working -- make sure you understand that a task number is not the object sys_id
var incidentSysID = incident.number; // should be incident.sys_id
Next, it's unclear where the PDF content is coming from. IF your complete code is listed, I would expect the errors given as you have noted that pdf_content is "any string variable."
ServiceNow does have a the capability to create a PDF from an HTML argument.
Generating a PDF from HTML
Here's a helpful blog post for getting a PDF (Platform generated) of an existing record:
Love PDF? PDF loves you too

How the bot's cards containg qnaid and context behave like QnA test?

In the test in QnA portal like the below screenshot, those buttons are created by the follow-up prompts from QnA, and when click those buttons, the next Http request contains all the prompts information like text and qnaid. Those the next response will be the answer of the specific qnaid.
But in the Bot -qnamaker-prompting Sampleenter link description here, when click the button, the Http request just contain the text as questions, thus the QnA will not get the answer bind with the qnaid. The answer may will not bind with qnaid and just context.
enter image description here
So anyone have ideas on how to create a bot like QnA test?
Generally, this is how you can get the same returned results generated from the getAnswers() API call as you get from QnA.
First, pass the current context into getAnswers() (which contains the user's message: "help", "Where did trees come from", "Why is the sky blue?", etc.) and then map the result to a variable:
const stepResults = turnContext.context;
let qnaResults = await this.qnaMaker.getAnswers(stepResults);
After validating a response is returned, you can pass the result text into an activity:
await innerDc.prompt('ConfirmPrompt', qnaResults[0].context.prompts[0].displayText);
Logging the above qnaResults[0].context.prompts shows the returned prompt values align with request payload seen in devtools:
With regards to the sample you linked, the prompt value is the returned QnAPrompts[] results (i.e. the follow-up prompt). If a prompt is present in the overall QnA results, it is parsed and displayed as a button. The displayText is coming from that prompt.
public static Activity GetHeroCard(string cardTitle, QnAPrompts[] prompts)
{
var chatActivity = Activity.CreateMessageActivity();
var buttons = new List<CardAction>();
var sortedPrompts = prompts.OrderBy(r => r.DisplayOrder);
foreach (var prompt in sortedPrompts)
{
buttons.Add(
new CardAction()
{
Value = prompt.DisplayText,
Type = ActionTypes.ImBack,
Title = prompt.DisplayText,
});
}
var plCard = new HeroCard()
{
Title = cardTitle,
Subtitle = string.Empty,
Buttons = buttons
};
var attachment = plCard.ToAttachment();
chatActivity.Attachments.Add(attachment);
return (Activity)chatActivity;
}
Hope of help!

outlook add-in image & files

I try to find solution to my problems but didn't find any where,hope that someone here can save me.
I write add-in in JavaScript on VS2015 that encrypte and decrypte body messages.
1. The first problem is with images that the receiver can't see .
(Talk about images that copy into the body by "insert picture inline" button)
In Compose mode we encrypte the message and then when we decrypte it's works good because the compose mode is a client side and he his recognize the local images .
In read mode when user want to decrypte the message and to see the images he couldn't see because the encrypte prevent outlook to convert the local image to data on the server .
In my code I take the body message like this ( compose mode )
item.body.getAsync(
item.body.getAsync(
"html",
{ asyncContext: "This is passed to the callback" },
function callback(resultbody) {
......Here we send the body for ENCRYPT.
}))
then , the user send the encrypte message by clicking 'send' regular.
In the read mode I just print it to my html to check if the decrypte is good :
(JSON.parse(xhr.responseText).Data.Content));
and then i get icon of picture ,but not success to show the real pic .
The src of the icon is going for place that not access for him ..
<img src="https://attachment.outlook.office.net/owa/*****/service.svc/s/GetFileAttachment?id=AAMkADUwMDE0YWM1LTYwODctNG ......
How can i take this tag of image and do something that the receiver can see the image ? I don't want that user will be need to upload image to body from my my add-in instead of the original outlook. I try to convert the image to base-64 string, but with what I have in the tag it not enough ,just with original picture and also it success to show in html but not in the body of message with SetAsync function..
2. The second problem is with attachments .
I upload files with dropzone plug-in (because outlook don't give access to take attachment and change him). So, after I upload files and encrypte him I make some new file with the response from server with File API of JS :
ar f = new File([""], "filename.txt", {type: "text/plain", lastModified: date}) . .. .
than I want to attach the file to mail, so the only method that do this is:
addFileAttachmentAsync(uri, attachmentName, optionsopt, callback opt)
then,I need to create a url for file for this method so I use this method:
var objectURL = URL.createObjectURL(f);
But now when I use the method addFileAttachmentAsync with objectURL it's write that there is a problem and its can't attach it , I think that the URL is incorrect .
Thanks all!!
For everyone who look any solution to this problems..
**In outlook web this solutions works good but in Outlook Desktop there is a problem of synchronize with server so there is a delay with saveAsync function without any solution to this right now , so it's work but need to wait a little bit.You could read more about it here.
First Question:
There is a problem in outlook add-in with when using getAsync and then setAsync functions . The problem occurs when there is some image inside the body . It's happen because when you take the body in Html format and then return the body with some different the image still not 'upload' and the src is being wrong .
I success to workaround this problem using Outlook rest API.
So the workaround is going like this:
Get the body message in type of Html by getAsync method. create div
element and set the return body message inside the div.
To get message id, you need to save your message as a draft with saveAsync function.
To make request to Outlook rest
API you need to get access token , so call to getCallbackTokenAsync function and save the access
token.
Make Http Request to outlook rest API to get all attachment exist in
the message.
Find the right ID of your image and replace the image src to the
base-64 of the image that you get from your request to outlook rest
API.
Finally , you could set your new body with SetAsync function .
Code:
item.body.getAsync(
Office.CoercionType.Html,
{ asyncContext: "This is passed to the callback" },
function callback(resultbody) {
var bodyDiv = document.createElement('div');
bodyDiv.innerHTML = content;
Office.context.mailbox.item.saveAsync(
function callback(result) {
var myNewItemSaved = result.value;
Office.context.mailbox.getCallbackTokenAsync({ isRest: true },
function (result) {
if (result.status === "succeeded") {
var accessToken = result.value;
var itemId = "";
if (Office.context.mailbox.diagnostics.hostName === 'OutlookIOS')
itemId = Office.context.mailbox.item.itemId;
else
itemId = Office.context.mailbox.convertToRestId(myNewItemSaved,
Office.MailboxEnums.RestVersion.v2_0);
var xhr3 = new XMLHttpRequest();
xhr3.open("GET", "https://outlook.office.com/api/v2.0/me/messages/" + itemId + "/attachments", true);
xhr3.setRequestHeader("Content-type", "application/json");
xhr3.setRequestHeader("Access-Control-Allow-Origin", "*");
xhr3.setRequestHeader("Authorization", "Bearer " + accessToken);
xhr3.send();
xhr3.onreadystatechange = function () {
if (xhr3.readyState == 4) {
if (xhr3.status == 200) {
var allImages = JSON.parse(xhr3.response).value;
var isDesktop = false;
var imgSrcId = bodyDiv.getElementsByTagName('img')[0].getAttribute("src");
if (imgSrcId.indexOf("cid") != -1) //Outlook Desktop
isDesktop = true;
for (var i = 0; i < allImages.length; i++) {
if (bodyDiv.getElementsByTagName('img')[i].getAttribute("src").indexOf("base64")!=-1)
continue;
if (isDesktop)
imgSrcId = bodyDiv.getElementsByTagName('img')[i].getAttribute("src");
else
imgSrcId = bodyDiv.getElementsByTagName('img'[i].getAttribute("originalsrc");
imgSrcId = imgSrcId.substr(4, imgSrcId.length);
var wantedImg;
for (var j = 0; j < allImages.length; j++) {
if ((allImages[j].ContentId).localeCompare(imgSrcId) != -1) {
wantedImg = allImages[j]; break;}
}
bodyDiv.getElementsByTagName('img')[i].src = 'data:' + wantedImg.ContentType + ';base64,' + wantedImg.ContentBytes;
}
}
setAsync......
}
}}}})})};
Second question
The problem with addFileAttachmentAsync that this is work only with files that is on external server, and it's not add a blob , local files.
So also here the solution is with Outlook rest API . The solution will attach our file to the message but we can't see this-no preview of the attachment in message , but when we send it this will attach to message , and we could see in our message that the attachment is there.
The solution is really similar to the one of the image in body - Save the message as a draft , get access token and this time the Http Request will be 'POST' request to our message id to attach our file to the current message.
Code to the request to add attachment to message ( all the way until here is the same like question 1):
var attachment ={
"#odata.type": "#Microsoft.OutlookServices.FileAttachment",
"Name": "smile.png",
"ContentBytes": "AAACFAMxLjAeKUDndY7EKF4P7QiWE7HgHLa7UiropGUTiDp5V07M0c5jaaTteauhzs0hOU+EOmVT0Lb6eSQ2MzgkCre/zCV9+kIB9PjWnOzoufau67J9PQdXapsOQSMcpt9X2QpcIjnl7H3sLu9iu2rqcvSjwhDnK6JygtghUB405EZHZ9LQcfJ1ZTYHylke2T9zbViq2BPqU/8IHZWsb/KQ/qzV4Jwv3NHnI583JvOuAtETJngh964edC4cU2IY6FkIWprksRw7d4fEQ/+3KbEyW0trIZm59jpTSV01/PhOI0RDKj1xI1Vr+lgMRZpOrYDfChWWWbByNzSXbIsTjHMU6GmQ5Cb09H3kv/2koFa5Pj2z8i+NGywYKw8ZSu3NVblM9I0EkQVLrxkM8gqyrDEtAobxPRxEzGTEXdnjws5UIiiGFBq3khuxejFGCNvUbmPM9guVZO0ccDe1FICTFHkrPlLZW/TvJYMou0HBrvH7s4taBHyZw5x03dhps+WG19D5na44vaVX2Vni6ZrrxfqFo7JTUpCJxCcPyoG7/nEWtJ/V/J+oXdypeapN9Agl6Q81WvCbzuyZgbLTfj6NXWDoliie069Hvk/k2lP+HyO7Iu5ffeRX2WWguwdfGXiNbqInrxn18tX+N7/KqWbRJv96tmijdCmCvsF9Lpr9k7QFKB93wuHfTuE6Qi2IVNBfzNBaz1iJYjY="
}
var xhr4 = new XMLHttpRequest();
xhr4.open("POST", "https://outlook.office.com/api/v2.0/me/messages/" + itemId + "/attachments", true);
xhr4.setRequestHeader("Content-type", "application/json");
xhr4.setRequestHeader("Access-Control-Allow-Origin", "*");
xhr4.setRequestHeader("Authorization", "Bearer " + accessToken);
xhr4.send(JSON.stringify(attachment));
xhr4.onreadystatechange = function () {
if (xhr4.readyState == 4) {
if (xhr4.status == 200)
console.log("ok");
else
console.log(xhr4.response);
}};
Hope it's will help someone , good luck !

Parse.com - .get() is returning an [object Object]?

I have Posts and Topics on Parse. There are ~20 different Topics - each Post stores up to 5 of them in an array relationship.
When a Post is updated, I need to check the Topics it's assigned to and potentially send out notifications.
So I wrote this:
Parse.Cloud.afterSave("Post", function(request) {
Parse.Cloud.useMasterKey();
var postObject = request.object;
var postTitle = postObject.get("title");
var topics = postObject.get("topic");
var topicCount = topics.length;
console.log("Post with title " + postTitle + " has " + topicCount + " topics: " + topics);
// code continues to push logic,
// but already the log above is wrong, so I'll leave that off.
}
For example, if I make a post with a title of "Porsche Takes Aim at Tesla" and give it a single topic, the one for "tech", I would expect it to log something like:
Post with title Porsche Takes Aim at Tesla has 1 topics: tech
But instead when I do this, it logs:
Post with title Porsche Takes Aim at Tesla has undefined topics: [object Object]
What am I doing wrong? The documentation suggests that when you call get with the name of an array field, it should return a JavaScript array. Instead it seems to be returning a blank JavaScript object without any attributes or contents at all.
I just need some way of seeing which topics are attached and iterating through them.
Note that I know this object is being created properly because I can see it in the data browser just fine. Navigating to the Post and then clicking on View Relationship under topic shows me that it's properly connected to the tech topic.
First: [object Object] doesn't mean that object is empty. Use JSON.stringify() to see it's contents.
As I understand topic is an object. In this case you shouldn't expect it to be printed as "tech". I guess you meant some property of this topic, like "name".
There may be also problem with setting the topic. Make sure to always use something like postObject.set("topic", arrayWithTopicsInside) because I think that you have set this to topic that is not in an array. You may need to remove that column so it can be added with different type.
I think it should look like this:
Parse.Cloud.afterSave("Post", function(request) {
Parse.Cloud.useMasterKey();
var postObject = request.object;
var postTitle = postObject.get("title");
var topics = postObject.get("topic");
Parse.Object.fetchAll(topic).then(function(topics) {
var topicNames = []
_.each(topics, function(topic) {
var name = topic.get("name");
names.push(name);
});
console.log("Post with title " + postTitle + " has " + topicNames.count + " topics: " + topicNames);
}, function(error) {
concole.log("error fetching objects" + JSON.stringify(error));
});
}

Resources