Cloud Function triggered, but not executed - javascript

I have following function:
exports.onDataAdded = functions.database.ref('/Lager/Shafts/Rescue/582/582001').onWrite((change, context) => {
if (change.before.exists()) {
return null;
}
// Exit when the data is deleted.
if (!change.after.exists()) {
return null;
}
const original = change.after.val();
return change.after.ref('/Lager/Shafts/Rescue/583/583001').set(original);
});
I am trying to keep the count of product 1 equal to the count of product two (Can't put it in the same ID for several reasons). It executes the function and says the status is ok but does not update the new value of product 2.
What am I missing?

Please try this, Your function is exiting without executing the update.
exports.onDataAdded = functions.database.ref('/Lager/Shafts/Rescue/582/582001').onWrite((change, context) => {
if (change.after.exists()) {
const original = change.after.val();
return admin.database().ref('/Lager/Shafts/Rescue/583/583001').set(original);
}
// Exit when the data is deleted.
if (!change.after.exists()) {
return null;
}
});

This seems like a noop:
exports.onDataAdded = functions.database.ref('/Lager/Shafts/Rescue/582/582001').onWrite((change, context) => {
if (change.before.exists()) {
return null;
}
Or more precisely: it will only get past this code when you delete /Lager/Shafts/Rescue/582/582001, which is not what you seem to be trying. My guess is that you meant the inverse in your check:
if (!change.before.exists()) {
return null;
}

Related

Can't get rid of a global variable (Need to make it non-global)

I'm working on a web app that shows the total duration of a playlist. Here we're dealing with the YouTube API. And I want to know how should I get rid of the global variable newPageToken. Also I still need to use it in the third function on this snippet.
let newPageToken = null;
// Next page for more results (Max 50 per page)
function getNextTokenURL() {
console.log(newPageToken);
return newPageToken
? `${playlistItemsURL}&playlistId=${extractedPlaylistIDId}&pageToken=${newPageToken}&key=${API_KEY}`
: `${playlistItemsURL}&playlistId=${extractedPlaylistIDId}&key=${API_KEY}`;
}
async function getVideoIdsForPageToken() {
try {
const { data } = await axios.get(getNextTokenURL());
const nextPageToken = data.nextPageToken;
const videoIds = data.items.map((video) => {
return video.contentDetails.videoId;
});
return { videoIds, nextPageToken };
} catch (e) {
if (e.response) {
const { code, message } = e.response.data.error;
throw new Error(`StatusCode ${code}. Reason: ${message}`);
console.log("Errow while fetching videos list.");
} else {
throw new Error(e.message);
}
}
}
// Navigates between the videos per page and adds them (Maximum 50)
async function getPlaylistData() {
try {
const { videoIds, nextPageToken } = await getVideoIdsForPageToken();
let pageToken = nextPageToken;
newPageToken = pageToken;
const returnedVideoIds = [];
returnedVideoIds.push(getDetailsForVideoIds(videoIds));
const videoGroups = await Promise.all(returnedVideoIds);
for (const group of videoGroups) {
for (const video of group) {
finalTotalDuration += returnedToSeconds(video.contentDetails.duration);
}
}
// console.log(videoIds);
if (nextPageToken) {
await getPlaylistData();
}
} catch (e) {
throw new Error(e.message);
console.log("Error while navigating between video pages.");
}
}```
Assumptions
finalTotalDuration is also a global variable declared somewhere (Not a good idea)
You call getPlaylistData for multiple playlist for multiple users
Solution
You need to ensure the getPlaylistData is standalone and returns the finalTotalDuration as a return value (not set a global one)
To make it standalone it has to be iterative in nature. It should be a recursive function which does the following
async function getPlaylistTotalDuration(newPageToken) {
// Step 1: Create the required query URL based on the newPageToken parameter
// Step 2: Start a local duration counter
// Step 3: Get the video details based on the URL created in Step 1
// Step 4: Get the durations in seconds and add it to the local duration counter created in Step 2
// Step 5: Check if the return of Step 3 has a nextPageToken, if so do a recursive call to self with the new token
// Step 6: Return the final value, which will propogate back in a recursive function
}
You can simply call the function like
let finalTotalDuration = getPlaylistTotalDuration(null); // or getPlaylistTotalDuration();
for example the below getPlaylistTotalDuration is a replacement to your getPlaylistData method
async function getVideoIdsForPageToken(url) {
try {
const { data } = await axios.get(url);
const nextPageToken = data.nextPageToken;
const videoIds = data.items.map((video) => {
return video.contentDetails.videoId;
});
return { videoIds, nextPageToken };
} catch (e) {
if (e.response) {
const { code, message } = e.response.data.error;
throw new Error(`StatusCode ${code}. Reason: ${message}`);
console.log("Errow while fetching videos list.");
} else {
throw new Error(e.message);
}
}
}
async function getPlaylistTotalDuration(newPageToken) {
try {
// Step 1: Create the required query URL based on the newPageToken parameter
let url = newPageToken
? `${playlistItemsURL}&playlistId=${extractedPlaylistIDId}&pageToken=${newPageToken}&key=${API_KEY}`
: `${playlistItemsURL}&playlistId=${extractedPlaylistIDId}&key=${API_KEY}`;
// Step 2: Start a local duration counter
let totalDuration = 0;
// Step 3: Get the video details based on the URL created in Step 1
const { videoIds, nextPageToken } = await getVideoIdsForPageToken(url);
const returnedVideoIds = [];
returnedVideoIds.push(getDetailsForVideoIds(videoIds));
const videoGroups = await Promise.all(returnedVideoIds);
for (const group of videoGroups) {
for (const video of group) {
// Step 4: Get the durations in seconds and add it to the local duration counter created in Step 2
totalDuration += returnedToSeconds(video.contentDetails.duration);
}
}
// Step 5: Check if the return of Step 3 has a nextPageToken, if so do a recursive call to self with the new token
if (nextPageToken) {
totalDuration += await getPlaylistTotalDuration(nextPageToken);
}
// Step 6: Return the final value, which will propogate back in a recursive function
return totalDuration;
} catch (e) {
throw new Error(e.message);
console.log("Error while navigating between video pages.");
}
}
Note: I have not actually ran the above code, but hopefully you get an idea of what needs to be done.

botpress - increment vlaue

I am trying to get a custom action running to simply incrementing a value on passing a specific node on the flow.
My custom actions looks like this:
function action(bp: typeof sdk, event: sdk.IO.IncomingEvent, args: any, { user, temp, session } = event.state) {
/** Your code starts below */
let i = undefined
const p = new Promise((resolve, reject) => {
if (i === undefined) {
resolve((i = 0))
} else if (i >= 0) {
resolve(i + 1)
} else {
reject('i cannot be < 0')
}
})
const runCount = async () => {
try {
const counter = await p
i = counter
return (session.count = counter)
} catch (err) {
console.log(err)
}
}
return runCount()
/** Your code ends here */
}
When I runCount() variable i will be set to 0. But then, after in rerun runCount() it does not increment further.
What do I need to do to save the variable so it increments on every runCount() call.
Greetings
Lorenz
I just managed to solve the problem.
I had to declare i = session.count at the beginning.
Now it gets the value out of the session state and increments the state on every call.
Maybe someone gets some help out of this.
Lorenz

Why does my forEach() loop only execute once?

I seem to have encountered a problem while looping through an array. The loop seems to only execute once, no matter the size of the array. I tried using different methods of looping and the error still persists.
As background information, I'm trying to make a bot with which users can award each other points. Everything else seemed alright. The only issue is that I wish to set up a maximum amount of points one user can give to another in a day, and I'm having problems looping through the array which stores this information.
These are the relevant parts of my code:
var timer = []; //Timer stores the values.
const getTimerSenderIdTable = (id) => {
let found = false;
timer.forEach(function(dat) { // This is the problematic loop.
if (dat.id === id) {
found = dat;
}
})
console.log("loop end, results: " + found);
return found;
};
const timerManager = (senderId, targetId, pointSurp) => { //All arguments are integers.
let d = new Date()
if (getTimerSenderIdTable("date") !== d.getDate()) {
timer = [];
timer.push({"id":"date", "time":d.getDate()});
if (getTimerSenderIdTable("date")) {
if (getTimerSenderIdTable(senderId)) {
console.log("path 1");
} else {
console.log("path 2");
timer.push({"id":senderId, [targetId]:pointSurp});
}
}
} else {
if (getTimerSenderIdTable("date")) {
if (getTimerSenderIdTable(senderId)) {
console.log("path 3");
} else {
console.log("path 4");
timer.push({"id":senderId, [targetId]:pointSurp});
}
}
}
console.log(timer)
};
*Edit:
Thank you for your comments. Here is an example:
Calling timerManager(123456, 654321, 3) will produce the following output:
loop end, results: false
loop end, results: [object Object]
loop end, results: false
path 2
[ { id: 'date', time: 28 }, { '654321': 3, id: 123456 } ]
(This is a repost from comments. My appologies.)
It seems because of this line
if (getTimerSenderIdTable("date") !== d.getDate()) {
timer = [];
This will empty the array and next lines of code will only push single element
as #mbojko has pointed out, you'll want to use the find method for returning the found obj inside getTimerSenderIdTable function, like this
const getTimerSenderIdTable = (id) => {
return timer.find(item => item.id === id});
};

How do I wait until a cookie is set?

I am writing the acceptance tests for my application's login feature. At some point, I want to double-check the cookie's expiry time.
Upon clicking on the "Login" button, a graphql query is sent to my server which responds with a Jwt. Upon reception of the jwt, the application sets the cookie with
document.cookie = ...
In my Cypress test, I check the token in the following way:
Then("sa session s'ouvre pour {SessionDurationType}", expectedDuration => {
cy.get('#graphql').then(() => {
cy.wait(1000)
cy.getCookie('token').then(cookie => {
const tokenDuration = getTokenDuration(cookie.value)
expect(tokenDuration.asSeconds()).to.equal(expectedDuration.asSeconds())
})
})
})
With cy.get('#graphql'), I am waiting for the graphql query to return a response. The alias is defined like this:
cy.stub(win, 'fetch', fetch).as('graphql')
Upon reception, the application sets the cookie.
My problem is that I am not fond of the following call:
cy.wait(1000)
Without that call, I always get an undefined cookie.
Is there a way to get that cookie within some time that might be much less than 1000 ms? I tried many things without success...
You must write a recursive promise function, try the following
function checkCookie() {
// cy.getCookie returns a thenebale
return cy.getCookie('token').then(cookie => {
const tokenDuration = getTokenDuration(cookie.value);
// it checks the seconds right now, without unnecessary waitings
if(tokenDuration.asSeconds() !== expectedDuration.asSeconds()) {
// waits for a fixed milliseconds amount
cy.wait(100);
// returns the same function recursively, the next `.then()` will be the checkCookie function itself
return checkCookie();
}
// only when the condition passes returns a resolving promise
return Promise.resolve(tokenDuration.asSeconds());
})
}
Then("sa session s'ouvre pour {SessionDurationType}", expectedDuration => {
cy.get('#graphql').then(() => {
checkCookie()
.then(seconds => {
expect(seconds).to.equal(expectedDuration.asSeconds())
})
})
})
Note that the function must be improved because
I didn't parametrize the expectedDuration etc. (it's out of the scope of showing you how to do that)
it waits forever without a loop counter check
But it works (I checked in another context before replying to you) and if you have some more troubles please share a "working" GitHub repo so I can clone and check it with your own solution.
Let me know if it isn't enough clear ๐Ÿ˜‰
UPDATE
We (me and Tommaso) have written a plugin to help you with this kind of checks, its name is cypress-wait-until.
Please thank the Open Source Saturday community for that, we developed it during one of them Saturdays ๐Ÿ˜Š
I dont like the timeout in this i have to say for dom changes. I have come up with this solution based on #NoriSte Answer together with DomMutation Observers.
getFileUploadItem().get(".upload-item--state i")
.should("have.class", "ngx-fileupload-icon--start")
.then(item => {
const iconEl = item.get(0);
const states: string[] = [];
return new Promise((resolve, reject) => {
const observer = new MutationObserver((mutations: MutationRecord[]) => {
const mutationEl = mutations[0].target as HTMLElement;
const className = mutationEl.getAttribute("class");
states.push(className);
if (className === "ngx-fileupload-icon--uploaded") {
resolve(states);
}
});
observer.observe(iconEl, {
subtree: true,
attributes: true,
attributeFilter: ["class"]
});
});
})
.then((value) => expect(value).to.deep.equal(
["ngx-fileupload-icon--progress", "ngx-fileupload-icon--uploaded"])
);
Based on #NoriSte's answer, I came up with the following working code:
function awaitNonNullToken(elapsedTimeInMs = 0) {
let timeDeltaInMs = 10
if (elapsedTimeInMs > Cypress.env('timeoutInMs')) {
return Promise.reject(new Error('Awaiting token timeout'))
}
return getTokenCookie().then(cookie => {
if (cookie === null) {
cy.wait(timeDeltaInMs)
elapsedTimeInMs += timeDeltaInMs
return awaitNonNullToken(elapsedTimeInMs)
}
return Promise.resolve(cookie.value)
})
}
I transformed that into an ES6 class that I find a bit more elegant:
class TokenHandler {
constructor () {
this.TIME_DELTA_IN_MS = Cypress.env('timeDeltaInMs')
this.TIMEOUT_IN_MS = Cypress.env('timeoutInMs')
this.elapsedTimeInMs = 0
}
getToken () {
if (this.elapsedTimeInMs > this.TIMEOUT_IN_MS) {
return Promise.reject(new Error('Awaiting token timeout'))
}
return getTokenCookie().then(cookie => {
if (cookie === null) {
cy.wait(this.TIME_DELTA_IN_MS)
this.elapsedTimeInMs += this.TIME_DELTA_IN_MS
return this.getToken()
}
return Promise.resolve(cookie.value)
})
}
}
and reworked my step like this:
cy.get('#graphql').then(() => {
const handler = new TokenHandler
handler.getToken().then(token => {
const tokenDuration = getTokenDuration(token)
expect(tokenDuration.asSeconds()).to.equal(expectedDuration.asSeconds())
})
})
This is working perfectly, thanks.

What's the best(right) way to write a polling method (with Typescript & AngularJS)?

I am trying to write a polling method that polls a server periodically to check whether a zip file has already been created or not.
What I want to accomplish are the following:
Calls(ajax) an API that creates a zip file on server
Calls(ajax) another API that checks if the zip file has already been created (polling method)
Some subsequent process
Here is my code snippet โ†“
var success: boolean = false;
//1. requests a server to create a zip file
this.apiRequest.downloadRequest(params,ApiUrl.URL_FOR_DOWNLOAD_REQUEST)
.then((resObj) => {
var apiRes: IDownloadService = resObj.data;
if (apiRes.status[0].statusCode == "000") {
success = true;
} else {
//Error
}
}).then(() => {
if (success) {
//2. polls the server to check if the zip file is ready
<- Polling methodโ†“ ->
this.polling(params).then((zipUrl) => {
console.log(zipUrl); //always logs zipUrl
//some subsequent process...
});
}
});
Could anyone give some examples of polling method that would work in this case?
Added:
private polling(params: any): ng.IPromise<any> {
var poller = () => this.apiRequest.polling(params, ApiUrl.URL_FOR_POLLING);
var continuation = () => poller().then((resObj) => {
var apiRes: IDownloadService = resObj.data;
if (apiRes.zipFilePath == "") {
return this.$timeout(continuation, 1000);
} else {
return apiRes.zipFilePath;
}
})
var result: ng.IPromise<any> = continuation();
return result;
}
Basically abstract the methods out as shown below:
let poll = () => this.apiRequest.downloadRequest(params,ApiUrl.URL_FOR_DOWNLOAD_REQUEST)
let continuation = () => poll().then((/*something*/)=> {
/*if still bad*/ return continuation();
/*else */ return good;
})
continuation().then((/*definitely good*/));
Update
As requested in the comment below:
return this.$timeout(continuation, 1000);
This is needed to get angular to kick off a digest cycle.

Categories