While we are create list and list columns using "pnp/sp". Why some of the columns are not created in one go? - sharepoint-online

While we are create list and list columns using pnp/sp. Why some of the columns are not created in one go? We need to run again then it will created. Please help me to resolve this issue.
I have used SPFx solution with react and "pnp/sp". Please refer below configuration for the same.
node 14.15.4
#microsoft/generator-sharepoint#1.14.0
gulp#4.0.2
npm#6.14.10
yo#4.3.0
#pnp/sp#2.11.0
Please refer below code snippet for the same.
import { sp } from '#pnp/sp';
import "#pnp/sp/webs";
import "#pnp/sp/lists";
import "#pnp/sp/items";
import "#pnp/sp/fields";
import "#pnp/sp/views";
import { UrlFieldFormatType } from "#pnp/sp/fields";
export const trainingsList = async () => {
try {
const listEnsureResult = await sp.web.lists.ensure("Trainings", "Trainings", 100, false, { Hidden: false });
if (listEnsureResult.created) {
return listEnsureResult;
}
} catch (error) {
console.log("Error in Trainings List", error);
}
}
export const trainingsFields = async () => {
const courseList = await sp.web.lists.getByTitle("Course")();
try {
await sp.web.lists.getByTitle("Trainings").fields.select("*").get().then(async (res) => {
let listData = [];
res.forEach(ele => {
listData.push(ele.InternalName);
});
if (listData.indexOf("VideoUrl") == -1) {
setTimeout(() => {
sp.web.lists.getByTitle("Trainings").fields.addUrl("VideoUrl", UrlFieldFormatType.Hyperlink).then(() => {
sp.web.lists.getByTitle("Trainings").defaultView.fields.add("VideoUrl");
})
}, 5000);
}
if (listData.indexOf("Walkthrough") == -1) {
sp.web.lists.getByTitle("Trainings").fields.addMultilineText("Walkthrough").then(() => {
sp.web.lists.getByTitle("Trainings").defaultView.fields.add("Walkthrough");
});
}
if (listData.indexOf("Status") == -1) {
sp.web.lists.getByTitle("Trainings").fields.addBoolean("Status").then(() => {
sp.web.lists.getByTitle("Trainings").defaultView.fields.add("Status");
});
}
if (listData.indexOf("TrainingDuration") == -1) {
sp.web.lists.getByTitle("Trainings").fields.addNumber("TrainingDuration").then(() => {
sp.web.lists.getByTitle("Trainings").defaultView.fields.add("TrainingDuration");
});
}
if (listData.indexOf("Course") == -1) {
sp.web.lists.getByTitle("Trainings").fields.addLookup("Course", courseList.Id, "Title").then(() => {
sp.web.lists.getByTitle("Trainings").defaultView.fields.add("Course");
});
}
await sp.web.lists.getByTitle("Trainings").fields.select("*").get().then(async (resListData) => {
let listColumnData = [];
resListData.forEach(ele => {
listColumnData.push(ele.InternalName);
});
console.log("listColumnData...", listColumnData);
if (listColumnData.indexOf("VideoUrl") != -1 && listColumnData.indexOf("Walkthrough") != -1 && listColumnData.indexOf("Status") != -1 && listColumnData.indexOf("TrainingDuration") != -1 && listColumnData.indexOf("Course") != -1) {
console.log("Inside IF.....");
}
else {
console.log("Inside ELSE.....");
await trainingsFields();
}
}).catch((err) => {
console.log("Error in trainings fields creation", err);
});
console.log("Trainings fields created successfully!");
}).catch((err) => {
console.log("Error in Trainings fields", err);
});
} catch (error) {
console.log("Error in Trainings fields", error);
}
}

You need to use await with add functions, otherwise your function may terminate before all fields are created. In general, I would avoid mixing await with .then for simplicity. Consider something like:
if (listData.indexOf("Walkthrough") == -1) {
await sp.web.lists.getByTitle("Trainings").fields.addMultilineText("Walkthrough");
await sp.web.lists.getByTitle("Trainings").defaultView.fields.add("Walkthrough");
}
if (listData.indexOf("Status") == -1) {
await sp.web.lists.getByTitle("Trainings").fields.addBoolean("Status");
await sp.web.lists.getByTitle("Trainings").defaultView.fields.add("Status");
}
Normally, you should get a linter (or compiler) error or warning when you try to call an asynchronous function without waiting for it to terminate in some way (i.e. without await or .then or .catch), i.e. "shooting in the air", but this may depend on the linter settings, as far as I remember.
TLDR: replace all foo().then(result => ..) with const result = await foo() and you should be fine.
Another thing, there may be a much more efficient way to create fields not just in "one go" but in "one call", if you used batches, for example.

Related

Get qldb ledger table data in javascript object

So my issue is simple yet challenging. I am trying to transfer data from one ledger to another. Now for that, I am reading a whole table of data then creating an object for every document than inserting these documents one by one into the other ledger table
Ledger#1 Table1 -> get all data -> convert all data to array of objects -> transfer to Ledger#2 Table1 one by one
The problem is that I cannot create the object from the document. I do this manually by using prototype functions and reading field type and then creating a thing that is messy and causes some data to become null. So I was wondering if there was a better way that is less prone to errors.
I asked a question of migrating ledger but had no luck in getting any response. Please help me in this.
Following is my code. Please copy and paste it inside an IDE so you can better understand
const getValueOfField = (field) => {
const name = field.getType().name;
switch (name) {
case "string":
return field.stringValue();
case "int":
return field.numberValue();
case "null":
return null;
default:
return null;
}
};
const enterDataInNewLedger = async (tableData, tableName) => {
const awsProductionDriver = awsProduction();
console.log(`Starting to insert data inside table ${tableName}`);
try {
for (const data of tableData) {
await awsProductionDriver.executeLambda(async (txn) => {
await txn.execute(`INSERT INTO ${tableName} ?`, data);
});
}
console.log(`Done inserting data inside ${tableName}`);
return { success: true };
} catch (err) {
console.log(err.message);
return { success: false, message: err.message };
}
};
const dataTransferOfTable = async (table) => {
const prodDriver = awsProd();
try {
const allTableData = await prodDriver.executeLambda(async (txn) => {
const result = await txn.execute(`SELECT * FROM ${table.name}`);
const resultList = result.getResultList();
let completeResults = [];
for (const doc of resultList) {
let newDoc = {};
const fields = doc.fields();
for (const field of fields) {
newDoc[field[0]] = getValueOfField(field[1]);
}
completeResults.push(newDoc);
}
return completeResults;
});
const response = await enterDataInNewLedger(allTableData, table.name);
checkForErrors(response);
return { success: true };
} catch (err) {
console.log(err.message);
return { success: false, message: err.message };
}
};
const startDataTransferFromOneLedgerToAnother = async () => {
try {
for (let table of tableName) {
const response = await dataTransferOfTable(table);
checkForErrors(response);
}
} catch (err) {
console.log(err.message);
}
};
startDataTransferFromOneLedgerToAnother();
So apparently I could've done this easily. I was just checking it and figured out the solution.
I can insert the whole fetched document and it will be same so my converted code is as follows
const { awsMainFunction: awsProd } = require("./awsProdConfig");
const { awsMainFunction: awsProduction } = require("./awsProductionConfig");
const { tableNamesAndIndeces: tableName, checkForErrors } = require("./utils");
const enterDataInNewLedger = async (tableData, tableName) => {
const awsProductionDriver = awsProduction();
console.log(`Starting to insert data inside table ${tableName}`);
try {
for (const data of tableData) {
await awsProductionDriver.executeLambda(async (txn) => {
await txn.execute(`INSERT INTO ${tableName} ?`, data);
});
}
console.log(`Done inserting data inside ${tableName}`);
return { success: true };
} catch (err) {
console.log(err.message);
return { success: false, message: err.message };
}
};
const dataTransferOfTable = async (table) => {
const prodDriver = awsProd();
try {
const allTableData = await prodDriver.executeLambda(async (txn) => {
const result = await txn.execute(`SELECT * FROM ${table.name}`);
return result.getResultList();
});
const response = await enterDataInNewLedger(allTableData, table.name);
checkForErrors(response);
return { success: true };
} catch (err) {
console.log(err.message);
return { success: false, message: err.message };
}
};
const startDataTransferFromOneLedgerToAnother = async () => {
try {
for (let table of tableName) {
const response = await dataTransferOfTable(table);
checkForErrors(response);
}
} catch (err) {
console.log(err.message);
}
};
startDataTransferFromOneLedgerToAnother();

Nodejs wait until async map function finishes executing

I have a array mapping and I need to execute some code after finishing the mapping.
Here is the array mapping code
studentList.map( async index => {
try{
const student = await User.findOne({indexNumber: index})
if (student == null) {
emptyStudents.push(index)
}
}
catch(err){
console.log(err)
}
})
How can I do that? As this is asynchronous I was unable to find a solution for this.
you can use the map to return the promises, and then when they are complete, outside of the map you can push onto your array -
const studentPromises = studentList.map( async index => {
return User.findOne({indexNumber: index})
})
const studentResults = await Promise.all(studentPromises)
studentResults.forEach((student) => {
if (student == null) {
emptyStudents.push(index)
}
})
await Promise.all(studentList.map( async index => {
try{
const student = await User.findOne({indexNumber: index})
if (student == null) {
emptyStudents.push(index)
}
}
}))
You can try to wrap your array mapping with Promise (and run it within async function):
await new Promise((resolve, reject) => {
studentList.map( async index => {
try{
const student = await User.findOne({indexNumber: index})
if (student == null) {
emptyStudents.push(index)
}
if (studentList.length - 1 === index) {
resolve();
}
}
catch(err) {
console.log(err);
reject(err);
}
})
});
// YOUR CODE HERE

I need to remove user reaction after reacting

I have a command which allows me to go and query an API to retrieve images and then display them in discord.
Currently I come to search from my API 10 images, I want to set up a navigation system thanks to the discord reactions. Everything works but the problem is that once the user has clicked the reaction remains active. I need to delete the reaction to improve the user experience. Currently it is therefore necessary to double click which is not very practical. Here's the method that doesn't work for deletion :
const removeReaction = (m, msg, emoji) => {
try {
m.reactions.find(r => r.emoji.name == emoji).users.remove(msg.author.id);
} catch(err) {
console.log('err: ', err)
}
}
here is all my code
bot.on('message', async msg => {
if (msg.content.startsWith('!test')) {
const args = msg.content.slice('!test').split(' ')
console.log('args :', args[1])
axios(`https://json-api.example.com/posts?tags=${args[1]}&limit=10`, {
method: 'GET',
})
.then((response) => {
if (response.status === 500) {
msg.channel.send('error')
} else {
if (response.data.posts.length === 0) {
msg.channel.send(`No result for ${args[1]}`)
}
if (response.data.posts.length > 0) {
const resultsLength = response.data.posts.length
const options = {
limit: 60000,
min: 1,
max: resultsLength - 1,
page: 1
}
const pages = []
response.data.posts.map((i, index) => {
pages.push({
"title": `Result page number ${index} for ${args[1]}`,
"url": `${response.data.posts[index].sample_url}`,
"color": 43333,
"footer": {
"icon_url": "https://cdn.discordapp.com/app-icons/708760465999790244/228b2993e942a361518b557ee4511b26.png?size=32",
"text": "Cool footer"
},
"image": {
"url": `${response.data.posts[index].url}`
},
"fields": [
{
"name": "tags",
"value": `${response.data.posts[index].tags[0] || '/'}, ${response.data.posts[index].tags[1] || '/'}, ${response.data.posts[index].tags[2] || '/'}, ${response.data.posts[index].tags[3] || '/'}`
}
]
})
})
const m = msg.channel.send({ embed: pages[options.page] }).then((el) => {
el.react('⬅️')
el.react('➡️')
el.react('🗑️')
})
const filter = (reaction, user) => {
return ['⬅️', '➡️', '🗑️'].includes(reaction.emoji.name) && user.id == msg.author.id
}
const awaitReactions = (msg, m, options, filter) => {
const { min, max, page, limit } = options
m.awaitReactions(filter, { max: 1, time: limit, errors: ['time'] })
.then((collected) => {
const reaction = collected.first()
const removeReaction = (m, msg, emoji) => {
try { m.reactions.find(r => r.emoji.name == emoji).users.remove(msg.author.id); } catch(err) { console.log('err: ', err) }
}
if (reaction.emoji.name === '⬅️') {
removeReaction(m, msg, '⬅️')
if (page != min) {
page = page - 1
m.edit({ embed: pages[page] })
}
awaitReactions(msg, m, options, filter)
}
else if (reaction.emoji.name === '➡️') {
removeReaction(m, msg, '➡️');
if (page != max) {
page = page + 1
m.edit({ embed: pages[page] })
}
awaitReactions(msg, m, options, filter);
}
else if (reaction.emoji.name === '➡️') {
removeReaction(m, msg, '➡️');
if (page != max) {
page = page + 1
m.edit({ embed: pages[page] })
}
awaitReactions(msg, m, options, filter);
}
else if (reaction.emoji.name === '🗑️') {
return m.delete()
}
else {
awaitReactions(msg, m, options, filter)
}
}).catch((err) => { console.log('err: ', err) })
}
awaitReactions(msg, m, options, filter)
Thanks in advance for your help
so, after much research I finally found. I share the answer here.
With the 12+ version of discord.js package a lot has evolved including my problem:
m.reactions.find(r => r.emoji.name == emoji).users.remove(msg.author.id);
must become :
m.reactions.cache.find(r => r.emoji.name == emoji).users.remove(msg.author);
I have made this code a while ago.
It is for an older version of Discord.js but according to docs it should still work.
Basically what you want to do is to use Discord.js createReactionCollector function after you send the picture, then inside this.reactionCollector.on('collect', (reaction, reactionCollector) => {//do your work here}) you can change the picture then call reaction.remove(user); which immediately removes the reaction for that user.
Here's my code:
Note: I have made a class for this command so it might look a bit different for you.
Also in my version, only the user who initially called the command can change the picture. So you will need to modify that part if you want to let anyone change the picture. (It might be a pretty bad idea if your bot is in a large server.)
this.channel.send({embed}).then(msg => {
this.message = msg;
this.message.react('⬅️').then(x => {
this.message.react('➡️');
})
const filter = (reaction, user) => {
return (reaction.emoji.name === '➡️' || reaction.emoji.name === '⬅️') && user.id === this.user.id;
};
this.reactionCollector = this.message.createReactionCollector(filter, { time: 600000 });
this.reactionCollector.on('collect', (reaction, reactionCollector) => {
if(reaction.emoji.name === '➡️') {
if(this.index + 1 < result.length) {
this.index++;
var embed = this.makeEmbed(result[this.index].img);
this.message.edit({embed});
}
}
else if(reaction.emoji.name === '⬅️') {
if(this.index - 1 > -1) {
this.index--;
var embed = this.makeEmbed(result[this.index].img);
this.message.edit({embed});
}
}
reaction.remove(this.user);
});
});

Node's promisify is not working with callback-based function

I am having a function which does some async processing and I want to convert it to a promise, so that I can make a "chain of execution" with other depended functions later on.
Below is the code(slightly modified for privacy):
router.get('/api/prx/ptr', function(req, res) {
let prx = req.params.prx_id
let ptr = {}
let parse_text = (idx_array, prx) => {
for(let name of idx_array) {
if (typeof ptr[name] === 'undefined') {
ptr[name] = []
}
get_text(prx, name, (tg) => {
const pst = new Set(tg.ph.map(ps => ps.label))
pst.forEach(ps => {
ptr[name].push(ps)
})
})
}
return true
}
parse_text = promisify(parse_text)
documentExists(prx, function(err, dbexists){
if (err) {
console.log(err);
return res.status(404).send(err)
}
get_idx_array(prx, function(err, idx_array){
if (err) {
return res.status(err.code || 400).send(err)
}
idx_array = idx_array.map(function(v){return v._id});
parse_text(idx_array, prx)
.then(result => {
res.status(200).send(ptr)
})
})
})
})
The problem is that in the last line, the promise never resolves and thus the request-response cycle never ends. Not sure what I have done wrong in my setup. Any help appreciated.

expressJs - Wait for a promise inside a loop

Novice question; I need to create a conditional loop which makes a call to the database. The value returned from the promise determines if I should break out of the loop as follows:
let id, caged;
do {
caged = false;
id = models.Cage.generateId();
caged = models.Cage.findOne({ where: { dispatchLabelId: id } });
} while( caged );
Can anybody please advise how I can structure my code?
You can do that using promises and recursive function calls:
let getCage = () => {
return models.Cage
.generateId()
.then(id => models.Cage.findOne({ where: { dispatchLabelId: id } }))
.then(caged => {
if (caged) {
return caged;
}
return getCage();
});
}
In the case if you can use node.js v7.6 or higher you can avoid using recursive function calls and implement this using async/await:
let getCage = async () => {
try {
do {
let id = await models.Cage.generateId();
let caged = await models.Cage.findOne({ where: { dispatchLabelId: id } });
if (caged) {
return caged;
}
} while (true);
} catch (err) {
console.log(err);
}
}

Resources