Nodejs Firestore: Returning nothing - javascript

I am using firebase to check a users punishment history, and when I call it:
I want it to return the array of embeds to post rather than just that. I am awaiting the function so I'm kind of confused why this is happening
This is what happens in the console.log:
[AsyncFunction: CheckHistory]
CALL
let hist = await CheckHistory(id);
console.log(CheckHistory);
FUNCTION
async function CheckHistory(user){
CheckBan(user);
let historyArray = [];
const colRef = db.collection('punishments')
colRef.where('user', '==', user).get().then(async querySnapshot => {
length = querySnapshot.size;
if (querySnapshot.empty){
console.log(querySnapshot.size)
return 0;
} else {
let docs = querySnapshot.docs;
for (let documentSnapshot of docs) {
const revoke = documentSnapshot.get('revoke')
let active = documentSnapshot.get('active')
if (active = true){
active = "Active"
} else {
active = `Revoked for ${revoke}`
}
const mod = documentSnapshot.get('mod')
const reason = documentSnapshot.get('reason');
const time = documentSnapshot.get('time');
let type = documentSnapshot.get('type');
if (type == 1){
type = "Blacklist";
} else if (type == 2){
type = "Dayban";
} else if (type == 3){
type = "Warning";
}
const username = await API.getUsernameFromId(user);
const modname = await API.getUsernameFromId(mod);
const historyEmbed = new MessageEmbed()
.setColor('#0099ff')
.setTitle(`'Punishment of ${username}:${user}`)
.setDescription(`This is a punishment log.`)
.addFields(
{ name: 'Status', value: `${active}`},
{ name: 'Moderator', value: `${modname}:${mod}` },
{ name: 'Reason', value: `${reason}` },
{ name: 'Type', value: `${type}` },
{ name: 'Time', value: `<t:${time}>`}
)
historyArray.push(historyEmbed);
if (historyArray.length >= length) {
return historyArray;
}
}
}
});
}

As stated by #Sergey Sosunov:
You forgot to actually return anything from your method, you should
return colRef.where('user', '==', user).get().then....
Or to change .then() callback approach to actually await one.
Like:
const querySnapshot = await colRef.where('user', '==', user).get();
and so on.
Also, the way you call the function only returns function definition; you should pass with parameters or print the variable value equal to your function. For example:
//Sample function
function test(id) { return id; }
//Returns function definition
console.log(test)
//Returns function value
console.log(test(1))
//Print variable value
let x = test(1)
console.log(x)

Related

Code works fine when value is hardcoded, but fails when value is dynamic

I'm working on a chrome extension that grabs data from Rate My Professor's GraphQL API and then puts that rating on my universities courses portal. Here's my code:
background.js
const {GraphQLClient, gql} = require('graphql-request');
console.log("background.js loaded");
const searchTeacherQuery = gql`
query NewSearchTeachersQuery($text: String!, $schoolID: ID!)
{
newSearch {
teachers(query: {text: $text, schoolID: $schoolID}) {
edges {
cursor
node {
id
firstName
lastName
school {
name
id
}
}
}
}
}
}
`;
const getTeacherQuery = gql`
query TeacherRatingsPageQuery(
$id: ID!
) {
node(id: $id) {
... on Teacher {
id
firstName
lastName
school {
name
id
city
state
}
avgDifficulty
avgRating
department
numRatings
legacyId
wouldTakeAgainPercent
}
id
}
}
`;
const AUTH_TOKEN = 'dGVzdDp0ZXN0';
const client = new GraphQLClient('https://www.ratemyprofessors.com/graphql', {
headers: {
authorization: `Basic ${AUTH_TOKEN}`
}
});
const searchTeacher = async (professorName, schoolID) => {
console.log("searchTeacher called");
console.log(professorName);
console.log(typeof professorName);
console.log(schoolID);
const response = await client.request(searchTeacherQuery, {
text: professorName,
schoolID
});
if (response.newSearch.teachers === null) {
return [];
}
return response.newSearch.teachers.edges.map((edge) => edge.node);
};
const getTeacher = async (id) => {
const response = await client.request(getTeacherQuery, {id});
return response.node;
};
async function getAvgRating(professorName) {
console.log('1: ', professorName);
const teachers = await searchTeacher(professorName, 'U2Nob29sLTE0OTU=');
console.log(teachers);
const teacherID = teachers[0].id;
const teacher = await getTeacher(teacherID);
const avgRating = teacher.avgRating;
console.log(teacher);
console.log(avgRating);
return avgRating;
}
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
console.log('received message from content script:', request);
console.log('test:', request.professorName);
getAvgRating(request.professorName).then(response => {
sendResponse(response);
});
return true;
});
and here's content.js:
const professorLinks = document.querySelectorAll('td[width="15%"] a');
professorLinks.forEach(link => {
const professorName = link.textContent;
console.log(professorName);
chrome.runtime.sendMessage({ professorName }, (response) => {
console.log(response);
if (response.error) {
link.insertAdjacentHTML('afterend', `<div>Error: ${response.error}</div>`);
} else {
link.insertAdjacentHTML('afterend', `<div>${response}/5</div>`);
}
});
});
Now, if I set professorName to a fixed value, like this:
async function getAvgRating(professorName) {
const teachers = await searchTeacher('Hossein Kassiri', 'U2Nob29sLTE0OTU=');
console.log(teachers);
const teacherID = teachers[0].id;
const teacher = await getTeacher(teacherID);
const avgRating = teacher.avgRating;
console.log(teacher);
console.log(avgRating);
return avgRating;
}
the code works as intended, with the expected output:
but if searchTeacher is called with professorName instead of a fixed value like this:
async function getAvgRating(professorName) {
const teachers = await searchTeacher(professorName, 'U2Nob29sLTE0OTU=');
console.log(teachers);
const teacherID = teachers[0].id;
const teacher = await getTeacher(teacherID);
const avgRating = teacher.avgRating;
console.log(teacher);
console.log(avgRating);
return avgRating;
}
it returns an empty object:
dynamic graphql request vs hardcoded graphql request
i'm not sure if i'm missing something trivial, as the values being passed to searchTeacher appear to be exactly the same, but it only works when hardcoded. please let me know if i'm missing something, thank you.
After much troubleshooting, I found the issue. #Damzaky was correct, using the localeCompare method I was able to determine that the string from the HTML and my hardcoded string were not the same, was localeCompare would return -1. I cleansed the string from the HTML using this line of code:
const normalizedName = name.normalize('NFKD');
and passed that to searchTeacher, and now the behaviour is as expected. Thanks to everyone that helped.

Firebase Cloud Functions Async

I am making a function for firebase cloud functions, I want a function to be called every time a new document is created in "posts". I want this function to perform the tasks that I put inside the "onCeatePost" function.
The problem I have is that I'm not sure if this is the correct way to structure such a function.
In several firebase examples I have seen that it is always called return _; or return null; at the end of a task, but I don't know how to structure the function so that all the tasks are carried out, could someone help me to restructure my function or tell me what is wrong please.
There are several if statements in the function, if the created publication does not comply with them, I would like it to skip them but continue with the other tasks that I put inside the function.
I don't know if it's too much to ask, but I'm new to this language and I haven't been able to find the answer I'm looking for. Thank you!
exports.onPostCreate = functions.firestore.document("/posts/{postId}").onCreate(async (snap) => {
const post = snap.data();
if (post) {
try {
const topic = post.topic;
const contentForFeed = post.contentForFeed;
const uid = post.uid;
const previous = post.prev;
await db.collection("users").doc(uid).update({"stats.posts": admin.firestore.FieldValue.increment(1)});
if (topic) {
await db.collection("topics").doc(topic.id).collection("user-authors").doc(uid).set({"date": snap.createTime});
}
if (contentForFeed == true) {
const userPath = db.collection("users").doc(uid);
await userPath.update({"stats.lastUpdate": snap.createTime});
}
if (previous) {
const previousId = previous.id;
const previousUid = previous.uid;
const refPrev = db.collection("posts").doc(previousId);
await db.runTransaction(async (t) => {
const doc = await t.get(refPrev);
const priority = doc.data().stats.date;
const newDate = new admin.firestore.Timestamp(priority.seconds + 120, priority.nanoseconds);
await db.collection("posts").doc(previousId).update({"newDate": newDate});
});
if (previousUid != uid) {
const path = db.collection("users").doc(uid).collection("user-posts");
const dataToSet = {"timestamp": snap.createTime, "uid": uid, "postId": onReplyToPostId};
await path(dataToSet);
}
}
} catch (err) {
functions.logger.log(err);
}
} else {
return null;
}
});
You'll find below the adapted code (untested) with 4 corrections.
Here are explanations for the two most important ones:
(Correction 2) In a transaction you need to use the transaction's update() method and not the "standard one"
(Correction 4) When all the asynchronous work is complete you need to return a value or a Promise. See this documntation page for more details.
exports.onPostCreate = functions.firestore
.document('/posts/{postId}')
.onCreate(async (snap) => {
const post = snap.data();
if (post) {
try {
const topic = post.topic;
const contentForFeed = post.contentForFeed;
const uid = post.uid;
const previous = post.prev;
await db
.collection('users')
.doc(uid)
.update({
'stats.posts': admin.firestore.FieldValue.increment(1),
});
if (topic) {
await db
.collection('topics')
.doc(topic.id)
.collection('user-authors')
.doc(uid)
.set({ date: snap.createTime });
}
if (contentForFeed == true) {
const userPath = db.collection('users').doc(uid);
await userPath.update({ 'stats.lastUpdate': snap.createTime });
}
let previousUid; // <= Correction 1
if (previous) {
const previousId = previous.id;
previousUid = previous.uid; // <= Correction 1
const refPrev = db.collection('posts').doc(previousId);
await db.runTransaction(async (t) => {
const doc = await t.get(refPrev);
const priority = doc.data().stats.date;
const newDate = new admin.firestore.Timestamp(
priority.seconds + 120,
priority.nanoseconds
);
t.update(refPrev, { newDate: newDate }); // <= Correction 2
});
if (previousUid != uid) {
const path = db
.collection('users')
.doc(uid)
.collection('user-posts');
const dataToSet = {
timestamp: snap.createTime,
uid: uid,
postId: onReplyToPostId,
};
await path.add(dataToSet); // <= Correction 3
}
}
return null; // <= Correction 4
} catch (err) {
functions.logger.log(err);
}
} else {
return null;
}
});

NoSuchMethodError: The method '[]' was called on null. I/flutter (23049): Receiver: null I/flutter (23049): Tried calling: []("conversationID")

I am trying to implement messages feature on my application with firebase firestore using flutter/dart language but I am having the following error.
NoSuchMethodError: The method '[]' was called on null.
I/flutter (23049): Receiver: null
I/flutter (23049): Tried calling: [] ("conversationID")
Firestore function:
"use strict";
Object.defineProperty(exports, "__esModule",{value: true});
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
exports.onConversationCreated =
functions.firestore.document("Conversations/{conversationID}")
.onCreate((snapshot, context) => {
let data = snapshot.data();
let conversationID = context.params.conversationID;
if (data) {
let members = data.members;
for (let index = 0; index < members.length; index++) {
let currentUserID = members[index];
let remainingUserIDs = members.filter((u) => u !== currentUserID);
remainingUserIDs.forEach((m) => {
return admin.firestore().collection("Users").doc(m).get().then((_doc) => {
let userData = _doc.data();
if (userData) {
return admin.firestore().collection("Users").doc(currentUserID).collection("Conversations").doc(m).create({
"conversationID": conversationID,
"image": userData.image,
"name": userData.name,
"unseenCount": 0,
});
}
return null;
}).catch(() => { return null; });
});
}
}
return null;
});
Flutter DatabaseService Class:
Future<void> createOrGetConversartion(String _currentID, String _recepientID,
Future<void> _onSuccess(String _conversationID)) async {
var _ref = _db.collection(_conversationsCollection);
var _userConversationRef = _db
.collection(_userCollection)
.doc(_currentID)
.collection(_conversationsCollection);
try {
var conversation =
await _userConversationRef.doc(_recepientID).get();
if (conversation.data != null) {
return _onSuccess(conversation.data()["conversationID"]);
} else {
var _conversationRef = _ref.doc();
await _conversationRef.set(
{
"members": [_currentID, _recepientID],
"ownerID": _currentID,
'messages': [],
},
);
return _onSuccess(_conversationRef.id);
}
} catch (e) {
print(e);
}
}
Your null check seems incorrect here.
if (conversation.data != null) {
return _onSuccess(conversation.data()["conversationID"]);
}
You are checking for conversation.data being null or not but using conversation.data() after null check. This only checks for the method is existing or not.
Instead of this, you should be doing something like this.
final data = conversation.data();
if (data != null) {
return _onSuccess(data["conversationID"]);
}

Cypress: Cypress custom command's return value actually returns null in the test file

I created a Cypress custom command which displays the value using console.log (so I know it works). However, when I call the custom command in my Cypress test file, it returns blank / null.
support/commands.js:
Cypress.Commands.add('scanAWSDb', (siteId) => {
let siteName = null //default it to null
... some AWS SDK function which scans the DB and check for corresponding Name of the ID: 12345 ...
siteName = <new value>
console.log("Value returned is: " + siteName //This displays the value in the web console for the corresponding ID: 12345, let's say name is Taylor
return siteName //Expected to return "Taylor" in the test file
})
integration/test1.spec.js:
describe('Display value from the Custom command that scanned the AWS DB', ()=> {
it('Display value from the Custom command that scanned the AWS DB', () => {
const siteId = "12345"
cy.scanAWSDb(siteId)
.then((returned_value) => {
cy.log(returned_value) //This displays a null value so it is not picking up the return value from the custom command which is supposedly Taylor
})
})
})
===
Update:
This worked but when trying to do an assertion, it does not work as I am unable to convert an Object Promise to a string.
export const scanTable = async (tableName, recordId) => {
const params = {
TableName: tableName,
FilterExpression: '#Id = :RecordId',
ExpressionAttributeNames: {
'#Id': 'Id',
},
ExpressionAttributeValues: {
':RecordId': recordId
}
};
let scanResults = [];
let items
let index = 0
do{
items = await docClient.scan(params).promise()
items.Items.forEach((item) => scanResults.push(item))
params.ExclusiveStartKey = items.LastEvaluatedKey
let scannedRecordId = JSON.stringify(items.Items[index].Id)
cy.log('Record successfully found in table: ' + scannedRecordId )
index += 1
}while(typeof items.LastEvaluatedKey != "undefined")
return scannedRecordId;
};
Cypress.Commands.add('scanDB', (tableName, recordId, cb) => {
const record = scanTable(tableName, recordId)
cb(record) // Callback function
})
Test file:
const tableName = 'table1';
let recordId = '';
cy.scanDB(tableName, recordId, $returnValue => {
cy.log($returnValue) //<-- THIS DISPLAYS THE OBJECT BUT I NEED TO CONVERT IT TO STRING SO I CAN DO ASSERTION like this:
//expect($returnValue).to.eq(recordId)
})
===
Update#2: This displays the returned value but not picked up for assertion
aws.js file:
const AWS = require('aws-sdk')
const region = Cypress.env('aws_region')
const accessKeyId = Cypress.env('aws_access_key_id')
const secretAccessKey = Cypress.env('aws_secret_access_key')
const sessionToken = Cypress.env('aws_session_token')
let scannedRecordId = ''
AWS.config.update({region: region})
AWS.config.credentials = new AWS.Credentials(accessKeyId, secretAccessKey, sessionToken)
const docClient = new AWS.DynamoDB.DocumentClient();
export const scanTable = async (tableName, recordId) => {
const params = {
TableName: tableName,
FilterExpression: '#Id = :RecordId',
ExpressionAttributeNames: {
'#Id': 'RecordId',
},
ExpressionAttributeValues: {
':RecordId': recordId // Check if Id is stored in DB
}
};
let scanResults = [];
let items
let index = 0
do{
items = await docClient.scan(params).promise()
items.Items.forEach((item) => scanResults.push(item))
params.ExclusiveStartKey = items.LastEvaluatedKey
scannedRecordId = JSON.stringify(items.Items[index].Id)
cy.log('Record successfully found in table: ' + scannedRecordId)
index += 1 // This may not be required as the assumption is that only a unique record is found
}while(typeof items.LastEvaluatedKey != "undefined")
return scannedRecordId;
};
Cypress.Commands.add('scanDB', (tableName, recordId, cb) => {
const record = scanTable(tableName, recordId)
cb(record) // Callback function
// const record = scanTable(tableName, recordId).then(record => { cb(record) }) //This does not work and returns a console error
})
awsTest.js file:
const tableName = 'random-dynamodb-table'
let myId = '12345'
it('Verify if ID is stored in AWS DynamoDB', () => {
cy.scanDB(tableName, myId, $returnValue => {
cy.log($returnValue)
cy.log(`Record ID: ${myId} is found in table: ` + $returnValue)
expect($returnValue).to.deep.eq(myId) //This asserts that the Id is found
})
})
If this AWS function is async you should handle it like promise so instead:
let siteName = null //default it to null
... some AWS SDK function which scans the DB and check for corresponding Name of the ID: 12345 ...
siteName = <new value>
AWSFunction().then((newValue) => {
siteName = newValue
console.log("Value returned is: " + siteName)
return siteName
});
Also if in test you need to only read this value you can use callback instead of promise: e.g.
Cypress.Commands.add('scanAWSDb', (siteId, cb) => {
AWSFunction().then((newValue) => {
cb(newValue);
})
})
// test
cy.scanAWSDb(siteId, (returned_value) => {
cy.log(returned_value)
});
Updated:
To assert string from object you can use wrap and invoke cypress methods: docs
scan function is async, so you have to call it like this:
Cypress.Commands.add('scanDB', (tableName, recordId, cb) => {
const record = scanTable(tableName, recordId).then(record => { cb(record) };
})

How to bypass jest setTimeout error of 5000ms by managing promises (Async and Await)

I wrote an Async/Await function to return promises for drivers report and analysis.
I have three different promise API files I extracted details from to do my analysis. However running test ith jest I get the error
Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Error:
I have refactored my code more than three times in two days but the error returns.
I will like to know how to manage my promises, perhaps there is something am not doing well and I am keen on this for optimization.
Is there a way to manage the promises in the code below to bypass the jest error?
any other suggestion will be highly appreciated.
NB: sorry I have post all the code for better insight.
code
const { getTrips } = require('api');
const { getDriver } = require('api')
const { getVehicle } = require('api')
/**
* This function should return the data for drivers in the specified format
*
* Question 4
*
* #returns {any} Driver report data
*/
async function driverReport() {
// Your code goes here
let trip = await getTrips()
trip = trip.map(item => {
item.billedAmount = parseFloat(item.billedAmount.toString().replace(',', '')).toFixed(2);
return item;
})
let getId = trip.reduce((user, cur) => {
user[cur.driverID] ? user[cur.driverID] = user[cur.driverID] + 1 : user[cur.driverID] = 1
return user
}, {})
// console.log(getId)
let mapId = Object.keys(getId)
// console.log(mapId)
let eachTripSummary = mapId.reduce((acc, cur) => {
let singleTrip = trip.filter(item => item.driverID == cur)
acc.push(singleTrip)
return acc
}, [])
// eachTripSummary = eachTripSummary[0]
// console.log(eachTripSummary)
// console.log(trip)
let reducedReport = eachTripSummary.reduce(async(acc, cur) =>{
acc = await acc
// console.log(acc)
let user = {}
let cash = cur.filter(item => item.isCash == true)
// console.log(cash.length)
let nonCash = cur.filter(item => item.isCash == false)
let driverSummary = await getDriverSummary(cur[0]['driverID'])
let trips = []
let customer = {}
cur[0].user ? (customer['user'] = cur[0]['user']['name'], customer['created'] = cur[0]['created'], customer['pickup'] = cur[0]['pickup']['address'],
customer['destination'] = cur[0]['destination']['address'], customer['billed'] = cur[0]['billedAmount'], customer['isCash'] = cur[0]['isCash']) : false
trips.push(customer)
let vehicles = []
if(driverSummary == undefined){
// console.log(cur)
user = {
id: cur[0]['driverID'],
vehicles: vehicles,
noOfCashTrips: cash.length,
noOfNonCashTrips: nonCash.length,
noOfTrips: cur.length,
trips: trips
}
acc.push(user)
// console.log(user)
return acc
}
let driverInfo = driverSummary[0]
let vehicleInfo = driverSummary[1]
let { name, phone } = driverInfo
let { plate, manufacturer } = vehicleInfo[0]
// console.log(plate)
let vpm = {
plate,
manufacturer
}
vehicles.push(vpm)
// console.log(cash.length)
user ={
fulName: name,
phone,
id: cur[0]['driverID'],
vehicles: vehicles,
noOfCashTrips: cash.length,
noOfNonCashTrips: nonCash.length,
noOfTrips: cur.length,
trips: trips
}
acc.push(user)
// console.log(acc)
return acc
}, [])
// reducedReport.then(data =>{console.log(data)})
return reducedReport
}
async function getDriverSummary(param) {
let driverDetails = await getDriver(param)
.then(data => {return data}).catch(err => {return err})
// console.log(driverDetails)
let vehicleDetails;
let { vehicleID } = driverDetails
if(driverDetails != "Error" & vehicleID != undefined){
// console.log(vehicleID)
vehicleDetails = vehicleID.map(async item => {
let vehicleSummary = getVehicle(item)
return vehicleSummary
})
// console.log(await vehicleDetails)
return await Promise.all([driverDetails, vehicleDetails])
}
}
driverReport().then(data => {
console.log(data)
})
module.exports = driverReport;
Use jest.setTimeout(30000); to increase the timeout. It will increase the timeout globally.
// jest.config.js
module.exports = {
setupTestFrameworkScriptFile: './jest.setup.js'
}
// jest.setup.js
jest.setTimeout(30000)
Or you can use user test example like this
describe("...", () => {
test(`...`, async () => {
...
}, 30000);
});

Categories

Resources