JsForce API calls loading extremely slowly - node.js

I'm currently using JsForce + Express, but I find that when I attempt to manipulate the data in the backend, the API call is extremely slow. Specifically, when I drill down to the Ids/Names of each of the individual objects, it slows down dramatically/fails to load.
It loads in less a second if I use this code:
router.get("/testChain", (req, res) => {
conn
.query(
"SELECT Id, Name, (SELECT Id, Part_Type__c FROM Monster_Parts__r) FROM Monster_Frame__c"
)
.then((result) => {
let gMonster = {};
let frames = result.records;
// Select Frame
let sFrameNo = randomNumber(frames.length);
let sFrame = frames[sFrameNo];
gMonster["Monster_Frame__c"] = sFrame.Id;
// Break into parts
let parts = sFrame.Monster_Parts__r.records;
let sortedParts = parts.reduce((r, o) => {
var k = o.Part_Type__c;
if (r[k] || (r[k] = [])) r[k].push(o);
return r;
}, {});
// Select Part
Object.keys(sortedParts).forEach((key, i, arr) => {
let parts = sortedParts[key];
let partNo = randomNumber(parts.length);
let selectedPart = parts[partNo];
gMonster[key] = selectedPart;
});
// Generate Name
gMonster["Name"] = sFrame.Name + "-" + randomNumber();
return gMonster;
})
.then((result) => {
res.json(result);
});
});
And gives me the following output:
{
"Monster_Frame__c": "a025j000004GsdXAAS",
"Tail__c": {
"attributes": {
"type": "Monster_Parts__c",
"url": "/services/data/v42.0/sobjects/Monster_Parts__c/a015j00000CfGUXAA3"
},
"Id": "a015j00000CfGUXAA3",
"Part_Type__c": "Tail__c"
},
"Leg__c": {
"attributes": {
"type": "Monster_Parts__c",
"url": "/services/data/v42.0/sobjects/Monster_Parts__c/a015j00000CfGUeAAN"
},
"Id": "a015j00000CfGUeAAN",
"Part_Type__c": "Leg__c"
},
"Head__c": {
"attributes": {
"type": "Monster_Parts__c",
"url": "/services/data/v42.0/sobjects/Monster_Parts__c/a015j00000CfGU0AAN"
},
"Id": "a015j00000CfGU0AAN",
"Part_Type__c": "Head__c"
},
"Body__c": {
"attributes": {
"type": "Monster_Parts__c",
"url": "/services/data/v42.0/sobjects/Monster_Parts__c/a015j00000CfGUKAA3"
},
"Id": "a015j00000CfGUKAA3",
"Part_Type__c": "Body__c"
},
"Back__c": {
"attributes": {
"type": "Monster_Parts__c",
"url": "/services/data/v42.0/sobjects/Monster_Parts__c/a015j00000CfGUPAA3"
},
"Id": "a015j00000CfGUPAA3",
"Part_Type__c": "Back__c"
},
"Name": "Serpent-7210"
}
However, if I change this line from:
gMonster[key] = selectedPart;
to populate the specific Id (instead of all the fields):
gMonster[key] = selectedPart.Id;
The API call fails 60% of the time. (Sometimes it returns with the desired output of carrying only the Ids.
Does anyone know how to fix this?

Related

Node - build a tree recursively with API data

I need to build a tree like structure using data from an API.
The structure i start with is as follows:
{
"type": "group",
"id": 1,
"name": "rootGroup",
"members": [],
}
There will always be a root group as the base of the tree.
I have a function named getMembersInGroup(groupId) which is an API call and returns something like:
[
{
"type": "group",
"id": 77,
"name": "IT group",
},
{
"type": "user",
"id": 40,
"name": "John"
}
]
Members can either be of type user or another group. So a user would look like:
{
"type": "user",
"id": 40,
"name": "John"
}
If it's another group it needs to recursively fetch those until there are only users or empty array left in members.
Any group can have users at any level with the tree.
A mock of getMembersInGroup:
const getMembersInGroup = async (groupId) => {
try {
const members = await fetch.callApi('/groups/' + groupId + '/members');
if (members) {
return members;
}
else {
return [];
}
} catch (error) {
return { error };
}
}
The end result should look like this:
{
"type": "group",
"id": 1,
"name": "rootGroup",
"members": [
{
"type": "group",
"id": 88,
"name": "Some group",
"members": [
{
"type": "user",
"id": 231,
"name": "SALLY"
},
{
"type": "user",
"id": 232,
"name": "Henry"
}
]
},
{
"type": "user",
"id": 41,
"name": "Chris"
}
],
}
I need help with the algorithm to create the tree.
Your getMembersInGroup function could look like this:
const getMembersInGroup = async (groupId) => {
const members = (await fetch.callApi(`/groups/${groupId}/members`)) ?? [];
for (const member of members) {
if (member.type == "group") {
member.members = await getMembersInGroup(member.id);
}
}
return members;
}
Call it like this:
async function loadTree() {
return {
type: "group",
id: 1,
name: "rootGroup",
members: await getMembersInGroup(1)
};
}
loadTree().then(result =>
console.log(result);
// Work with the result ...
).catch(error =>
console.log("error: ", error)
);
Demo with a mock implementation of fetch.callApi:
// Mock for fetch.callApi
const fetch = {
mockData: [0,[2,3,4],[5,6,7],[8,9],0,0,0,[10],0,0,[11,12],0,0],
callApi(url) {
return new Promise((resolve, reject) => {
const groupId = +url.split("/")[2];
const children = this.mockData[groupId];
if (!children) return reject("not found: " + groupId);
const result = children.map(id => {
const type = this.mockData[id] ? "group" : "user";
return {type, id, name: type + "_" + id};
});
setTimeout(() => resolve(result), 50);
});
}
}
async function loadTree() {
return {
type: "group",
id: 1,
name: "rootGroup",
members: await getMembersInGroup(1)
};
}
const getMembersInGroup = async (groupId) => {
const members = (await fetch.callApi('/groups/' + groupId + '/members')) ?? [];
for (const member of members) {
if (member.type == "group") {
member.members = await getMembersInGroup(member.id);
}
}
return members;
}
loadTree().then(result =>
console.log(JSON.stringify(result, null, 2))
).catch(error =>
console.log("error: ", error)
);
You can do something like:
const getMembersInGroup = async (groupId) => {
try {
const members = await fetch.callApi('/groups/' + groupId + '/members');
if (members) {
foreach(member in members) {
if (member.type == 'groups') {
member.members = getMembersInGroup(member.groupid)
}
}
return members;
}
else {
return [];
}
} catch (error) {
return { error };
}
}
So you have the recursion only if it's a group type, otherwise the member is returned as is.

How do I modify all occurrences of a property (including nested) in an object before sending a JSON response?

I have an object created by parsing some JSON. The JSON data looks like this:
{
"id": "fan",
"name": "My super awesome fan",
"image": "Icon.png",
"details": {
"parts": [
{
"name": "base",
"image": "base.png"
},
{
"name": "blades",
"image": "blade.png"
}
],
"sale": {
"value": "prime",
"image": "PrimeDay.png"
}
}
}
The values in the image property point to files located under the directory "public" which is served using express.static. For example, the blade.png file is located at:
public
--products
--fan (this is the same as the id property)
--blade.png
Now, when a query is made to fetch the details of this object, I want to modify the image property of the object sent in the response so that the JSON response looks like:
{
"id": "fan",
"name": "My super awesome fan",
"image": "http://localhost:3000/products/fan/icon.png",
"details": {
"parts": [
{
"name": "base",
"image": "http://localhost:3000/products/fan/base.png"
},
{
"name": "blades",
"image": "http://localhost:3000/products/fan/blade.png"
}
],
"sale": {
"value": "prime",
"image": "http://localhost:3000/products/fan/PrimeDay.png"
}
}
}
I went down the path of using an express middleware to create a url to point to the path of the using
const url = req.protocol + '://' + req.get('host')
How do I use this url value to modify the response object?
I don't want to hardcode the full localhost path to the images in the JSON, since the path may change when deployed.
I was able to solve this, partly using Charly's solution. The only reason I could not use it fully was because the fan part of the URL is also dynamic and is dependent on the id property in the JSON data.
Here's my approach:
setImagePaths = (data) => {
const baseUrl = 'http://localhost:3000/products/'
const baseId = data.id;
const patchImagePaths = (obj) => {
Object.keys(obj).forEach(key => {
if (typeof obj[key] === 'object') {
return patchImagePaths(obj[key]);
}
if (key === 'image') {
obj[key] = baseUrl + baseId + '/' + obj[key];
}
});
}
patchImagePaths(data);
}
You could modify your response object with a recursive function to update the image properties:
var myObject = {
"id": "fan",
"name": "My super awesome fan",
"image": "Icon.png",
"details": {
"parts": [
{
"name": "base",
"image": "base.png"
},
{
"name": "blades",
"image": "blade.png"
}
],
"sale": {
"value": "prime",
"image": "PrimeDay.png"
}
}
}
;
const url = "http://localhost:3000/products/fan/";
//You can use here your url function with whatever output it has.
function resetImages (obj) {
Object.keys(obj).forEach(function (key) {
if (Array.isArray(obj[key])) {
let arr = obj[key];
arr.map((i)=>{
return i.image = url+i.image;
});
return arr;
}
if (typeof obj[key] === 'object') {
return resetImages(obj[key]);
}
if(key==="image") {
obj[key]=url+obj[key];
}
});
}
resetImages(myObject);
console.log(JSON.stringify(myObject));
**Output:**
{"id":"fan","name":"My super awesome fan","image":"http://localhost:3000/products/fan/Icon.png","details":{"parts":[{"name":"base","image":"http://localhost:3000/products/fan/base.png"},{"name":"blades","image":"http://localhost:3000/products/fan/blade.png"}],"sale":{"value":"prime","image":"http://localhost:3000/products/fan/PrimeDay.png"}}}

Microsoft teams bot adaptive card carousel deleting a card

I am using Microsoft teams bot with nodejs. I am rendering a carousel of adaptive cards with action on each card. My requirement is to delete an individual card out on which the action was clicked. Is it possible?
Current code looks like below. i have given a try to deleteActive but that deletes entire carousel
const {
TurnContext,
TeamsActivityHandler,
CardFactory,
AttachmentLayoutTypes,
ActionTypes
} = require('botbuilder');
class TeamsConversationBot extends TeamsActivityHandler {
constructor() {
super();
this.onMessage(async (context:any, next:any) => {
TurnContext.removeRecipientMention(context.activity);
console.log("context activigty at the begin is:" + JSON.stringify(context.activity))
let msg = context.activity.text
let action = context.activity.value
if(msg.startsWith('lead')){
msg = 'lead'
}
if(action !== undefined){
console.log("user did some action on a card")
msg = action.action
}
switch (msg) {
case 'lead':
await this.lead(context)
break;
case 'qualify_lead':
await this.qualifyLead(context)
break;
}
await next();
});
}
/**
*
* #param context this method does a lead qualification
*/
async qualifyLead(context:any){
console.log("in qualifyLead:" + JSON.stringify(context.activity))
//await context.deleteActivity(context.activity.replyToId)
const leadId = context.activity.value.objectId
console.log("Lead to qualify is:" + leadId)
await context.sendActivity('Lead is qualified')
}
/**
* Search contact by name
* #param context
* #param keyword
*/
async lead(context:any){
console.log("Start of lead with context:" + JSON.stringify(context))
const cardArr = []
let items = [
{"Name": 'x', "LeadId": "1"},
{"Name": 'a', "LeadId": "2"},
{"Name": 'b', "LeadId": "3"},
{"Name": 'c', "LeadId": "4"},
{"Name": 'd', "LeadId": "5"}
]
for(const item of items){
const header = {
"type": "TextBlock",
"size": "Medium",
"weight": "Bolder",
"text": item.Name
}
const actions = [
{
"type": "Action.Submit",
"title": "Qualify",
"data": { "action" : "qualify_lead", "objectId" : item.LeadId }
}
]
const acard = CardFactory.adaptiveCard(
{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.0",
"body": [
header,
''
],
"actions": actions
}
)
cardArr.push(acard)
console.log("payload is::::" + JSON.stringify(acard))
}
const reply = {
"attachments" : cardArr,
"attachmentLayout" : AttachmentLayoutTypes.Carousel
}
await context.sendActivity(reply);
}
}
module.exports.TeamsConversationBot = TeamsConversationBot;
As with this other answer, the answer will be similar to this one. I can see you're trying to use TypeScript but your code deviates very little from JavaScript so I'll just write my answer in JavaScript.
First, you'll need a way of saving state for your [carousel] so you can update the [carousel]'s activity.
this.carouselState = this.conversationState.createProperty('carouselState');
You'll want a consistent way to generate your [carousel] that you can use when you send the [carousel] initially and when you update the [carousel].
createCarousel(batchId, leads)
{
const cardArr = [];
let items = [
{ "Name": 'x', "LeadId": 1 },
{ "Name": 'a', "LeadId": 2 },
{ "Name": 'b', "LeadId": 3 },
{ "Name": 'c', "LeadId": 4 },
{ "Name": 'd', "LeadId": 5 }
];
items = items.filter(item => leads.includes(item.LeadId));
for (const item of items) {
const header = {
"type": "TextBlock",
"size": "Medium",
"weight": "Bolder",
"text": item.Name
};
const actions = [
{
"type": "Action.Submit",
"title": "Qualify",
"data": { [KEYACTION]: ACTIONQUALIFYLEAD, [KEYOBJECTID]: item.LeadId, [KEYBATCHID]: batchId }
}
];
const acard = CardFactory.adaptiveCard(
{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.0",
"body": [
header
],
"actions": actions
}
);
cardArr.push(acard);
}
return {
"type": "message",
"attachments": cardArr,
"attachmentLayout": AttachmentLayoutTypes.Carousel
};
}
This is similar to your code but there are some important differences. First, I'm filtering the items array to allow for fewer items, which is how you'll end up deleting cards from your carousel. Second, I'm including a "batch ID" in each action's data, which is how your bot will know which activity to update when it receives the action's payload. Also, this isn't relevant to your question but I'm using string constants instead of string literals most everywhere I expect to use that string more than once, which is a practice I follow to avoid typo-related bugs etc.
Using this function, you can send the [carousel] initially like this
async testCarousel(turnContext) {
const batchId = Date.now();
const leads = [1, 2, 3, 4, 5];
const reply = this.createCarousel(batchId, leads);
const response = await turnContext.sendActivity(reply);
const dict = await this.carouselState.get(turnContext, {});
dict[batchId] = {
[KEYACTIVITYID]: response.id,
[KEYLEADS]: leads
};
}
And you can update the [carousel] in response to the card's [qualify] submit action like this
async handleSubmitAction(turnContext) {
const value = turnContext.activity.value;
switch (value[KEYACTION]) {
case ACTIONQUALIFYLEAD:
const dict = await this.carouselState.get(turnContext, {});
const batchId = value[KEYBATCHID];
const info = dict[batchId];
if (info) {
const leads = info[KEYLEADS];
const objectId = value[KEYOBJECTID];
var index = leads.indexOf(objectId);
if (index !== -1) leads.splice(index, 1);
const update = this.createCarousel(batchId, leads);
update.id = info[KEYACTIVITYID];
if (update.attachments.length) {
await turnContext.updateActivity(update);
} else {
await turnContext.deleteActivity(update.id);
}
}
break;
}
}

How to properly handle context.sendActivity?

I just want to ask two simple questions and then show the card. Problem is, in the second "sendActivity" keeps on repeating "please give password" just forever. I tried to place another onTurn after and even inside the function, with worst or same results. Dont want to implement a whole waterfall just for 2 questions. Which ActivityHandler fits better what am trying to achieve?
async processLogin(context, next, res) {
await context.sendActivity({
text: 'please give username'
})
const SelectedCard2 = CARDS2[0];
this.onTurn(async (context, next, res) => {
let txt = `"${context.activity.text}"`;
if (txt) {
var name = JSON.parse(txt);
console.log(name)
}
await context.sendActivity({
text: 'please give password'
})
let txt2 = `"${context.activity.text}"`;
if (txt2) {
var password = JSON.parse(txt2);
console.log(password)
res = password;
}
await next();
});
}
enter link description hereIf you just want to collect some info from user by an easy , you can use adaptive card in one step, try the code below :
const { ActivityHandler,CardFactory } = require('botbuilder');
class EchoBot extends ActivityHandler {
constructor() {
super();
// See https://aka.ms/about-bot-activity-message to learn more about the message and other activity types.
var adaptiveCard = {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.0",
"body": [
{
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"width": 2,
"items": [
{
"type": "TextBlock",
"text": "Pls type your info here . Don't worry, we'll never share or sell your information.",
"isSubtle": true,
"wrap": true,
"size": "Small"
},
{
"type": "TextBlock",
"text": "Username",
"wrap": true
},
{
"type": "Input.Text",
"id": "username",
"placeholder": "your user name here"
},
{
"type": "TextBlock",
"text": "Password",
"wrap": true
},
{
"type": "Input.Text",
"id": "password",
"placeholder": "makre sure no one is around you ..."
}
]
}
]
}
],
"actions": [
{
"type": "Action.Submit",
"title": "Submit"
}
]
};
this.onMessage(async (context, next) => {
if(context.activity.text==="login"){
await context.sendActivity({ attachments: [CardFactory.adaptiveCard(adaptiveCard)] });
}else if(context.activity.value != undefined){
var user = context.activity.value;
await context.sendActivity("hello , your username : " + user.username + ",password :" + user.password);
}else {
await context.sendActivity("send login to do test");
}
await next();
});
this.onMembersAdded(async (context, next) => {
const membersAdded = context.activity.membersAdded;
for (let cnt = 0; cnt < membersAdded.length; ++cnt) {
if (membersAdded[cnt].id !== context.activity.recipient.id) {
await context.sendActivity('Hello and welcome!');
}
}
// By calling next() you ensure that the next BotHandler is run.
await next();
});
}
}
module.exports.EchoBot = EchoBot;
This code is based on official nodejs echo bot , just cover the content of bot.js file to test it :
Hope it helps .

how to generate and store UUDI for a device in loopback an

I want to make a web service to generate UUID and store it with Loopback in a table something like
http://localhost:3000/api/getuudi
The functions for token and uuid are working fine but I dont know where should I had to place for example uuid to get token and store in table
function generateUUID() {
var d = new Date().getTime();
if(window.performance && typeof window.performance.now === "function"){
d += performance.now();; //use high-precision timer if available
}
var uuid = 'xxxxxxxxxxxx7xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = (d + Math.random()*16)%16 | 0;
d = Math.floor(d/16);
return (c=='x' ? r : (r&0x3|0x8)).toString(16);
});
return uuid;
};
Token
function generateToken() {
var d = new Date().getTime();
if(window.performance && typeof window.performance.now === "function"){
d += performance.now();; //use high-precision timer if available
}
var uuid = 'xxxxxxxxxxxx9xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = (d + Math.random()*16)%16 | 0;
d = Math.floor(d/16);
return (c=='x' ? r : (r&0x3|0x8)).toString(16);
});
return uuid;
};
UUDI.json
"name": "UUDI",
"plural": "getuudi",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"uuid": {
"type": "string",
"required": true
},
"date": {
"type": "string",
"required": true
},
"time": {
"type": "string",
"required": true
}
},
"validations": [],
"relations": {},
"acls": [],
"methods": {}
}
You can generate uuid using loopback functional properties.
Check - https://loopback.io/doc/en/lb3/Model-definition-JSON-file.html#properties

Resources