Start/stop cronjob on button click in Nodejs Express app - javascript

I have been working on a project which requires the start and stop of cron scheduler when a user clicks on a button on the front end. Basically when a user clicks on a button, the cron job will start. And clicking the stop button will stop the timer. It is as simple as that.
To achieve that, I am making post requests to the Nodejs/Express backend on button click which triggers start/stop function of the scheduler. This is how the endpoint looks like:
const cron = require('node-cron');
router.post('/scheduler', async (req, res) => {
// gets the id from the button
const id = req.body.id;
try{
// finds the scheduler data from the MongoDB
const scheduler = await Scheduler.find({ _id: id });
// checks whether there is a scheduler or not
if ( !scheduler ) {
return res.json({
error: 'No scheduler found.'
});
}
// creates the cronjob instance with startScheduler
const task = cron.schedule('*/10 * * * * *', () => {
console.log('test cronjob running every 10secs');
}, {
scheduled: false
});
// checks if the scheduler is already running or not. If it is then it stops the scheduler
if ( scheduler.isRunning ) {
// scheduler stopped
task.stop();
return res.json({
message: 'Scheduler stopped!'
});
}
// starts the scheduler
task.start();
res.json({
message: 'Scheduler started!'
});
}catch(e) {
console.log(e)
}
});
Right now the scheduler runs perfectly but it doesn't stop on second button click. It keeps on running. I feel like I'm not calling task.start() and task.stop() at correct places where it would work. And I don't know where the correct places are. I'm actually new to cronjobs.
It would be great if someone tells me what I am doing wrong.
Thanks in advance.

Every time you hit the scheduler api a new instance of cron-job is made and you are stopping the newly defined instance of cron-job not the previous one.
Solution is to define the cron-job out of the scope of router so that whenever you hit the scheduler api the instance won't change
Like this:
const cron = require('node-cron');
// creates the cronjob instance with startScheduler
const task = cron.schedule('*/10 * * * * *', () => {
console.log('test cronjob running every 10secs');
}, {
scheduled: false
});
router.post('/scheduler', async (req, res) => {
// gets the id from the button
const id = req.body.id;
try{
// finds the scheduler data from the MongoDB
const scheduler = await Scheduler.find({ _id: id });
// checks whether there is a scheduler or not
if ( !scheduler ) {
return res.json({
error: 'No scheduler found.'
});
}
// checks if the scheduler is already running or not. If it is then it stops the scheduler
if ( scheduler.isRunning ) {
// scheduler stopped
task.stop();
return res.json({
message: 'Scheduler stopped!'
});
}
// starts the scheduler
task.start();
res.json({
message: 'Scheduler started!'
});
}catch(e) {
console.log(e)
}
});

The problem might come from the line:
const task = cron.schedule('*/10 * * * * *', () => {
which, actually, creates a new task and uses a new Scheduler if you read the source code of node-cron:
https://github.com/node-cron/node-cron/blob/fbc403930ab3165ffef7d53387a29af92670dfea/src/node-cron.js#L29
function schedule(expression, func, options) {
let task = createTask(expression, func, options);
storage.save(task);
return task;
}
(which, internally, uses: https://github.com/node-cron/node-cron/blob/fbc403930ab3165ffef7d53387a29af92670dfea/src/scheduled-task.js#L7:
let task = new Task(func);
let scheduler = new Scheduler(cronExpression, options.timezone, options.recoverMissedExecutions);
So, when you call:
task.stop();
As far as I understand, what you do is calling the method "stop" of a brand new task, not the method stop of the task you launched the first time you clicked the button.
Judging by your code, the problem is that you are not actually using your scheduler while using the task.
PS:
The module also exposes a function that lets you retrieve tasks from its storage: https://github.com/node-cron/node-cron/blob/fbc403930ab3165ffef7d53387a29af92670dfea/src/node-cron.js#L58
But as I haven't found any documentation about it, I do not recommend using it.

Related

Cypress intercept blocks the request when it's called several times in a test run

I've created some tests in Cypress to add and duplicate article in our Angular application. The code for test ArticlesTest.js
describe('Shop articles single station', () => {
const productManagementPage = new ProductManagementPage()
const shopArticlesPage = new ShopArticlesPage()
before(() => {
var credentials =
{
"username": "someusername#user.name",
"password": "strongPassword!"
}
cy.navigateToProductManagement(credentials)
})
beforeEach(() => {
productManagementPage.shopArticlesProgrammingButton().click()
shopArticlesPage.WaitUntilPageLoaded('/productmanagement/api/v1/articles/get', 'GetArticles')
})
it('Add article', () => {
var randomNumber = RandomDataGenerator.GenerateRandomInt(1000,4999)
var randomName = RandomDataGenerator.GenerateRandomString(20)
var randomPrice = RandomDataGenerator.GenerateRandomDecimal(1,99)
shopArticlesPage.newArticleButton().click()
shopArticlesPage.saveButton().should('be.disabled')
shopArticlesPage.undoButton().should('be.disabled')
shopArticlesPage.deleteButton().should('be.disabled')
shopArticlesPage.articlesList().should('not.exist')
shopArticlesPage.articleNumberTextBox().should('be.enabled')
shopArticlesPage.articleNumberTextBox().type(randomNumber)
shopArticlesPage.articleNameTextBox().type(randomName)
shopArticlesPage.articleUnitPriceTextBox().type(randomPrice)
shopArticlesPage.undoButton().should('be.enabled')
shopArticlesPage.saveButton().click()
shopArticlesPage.newArticleButton().should('exist')
shopArticlesPage.articlesList().should('exist')
shopArticlesPage.saveButton().should('be.disabled')
shopArticlesPage.undoButton().should('be.disabled')
})
it('Duplicate article', () => {
var articleNumber = RandomDataGenerator.GenerateRandomInt(51,65)
var newArticleNumber = RandomDataGenerator.GenerateRandomInt(1000, 4999)
var newArticleName = RandomDataGenerator.GenerateRandomString(20)
shopArticlesPage.articlesList().selectFromList(articleNumber)
const articleUnitPrice = shopArticlesPage.articleUnitPriceTextBox().invoke('text')
const vatCodeValue = shopArticlesPage.vatCodeDropDown().invoke('text')
const cardCodeValue = shopArticlesPage.cardCodeDropDown().invoke('text')
shopArticlesPage.duplicateArticleButton().click()
shopArticlesPage.WaitUntilPageLoaded()
shopArticlesPage.articleNumberTextBox().type(newArticleNumber)
shopArticlesPage.articleNameTextBox().type(newArticleName)
shopArticlesPage.saveButton().click()
shopArticlesPage.newArticleButton().should('be.enabled')
})
WaitUntilPageLoaded() method code is:
WaitUntilPageLoaded(path, alias) {
return cy.waitForRequestToComplete(path, alias)
}
which, in turn, is custom Cypress command:
Cypress.Commands.add('waitForRequestToComplete', (path, alias) => {
cy.intercept('POST', path).as(alias)
cy.wait('#' + alias).its('response.statusCode').should('be.ok')
})
In 1st beforeEach() run, there's no problem with intercepting GetArticles and waiting for it to complete.
The problem starts in 2nd test, as it looks like GetArticles is not intercepted, it's not called at all, though it's supposed to be. The problem doesn't exist when clicking through the application manually, and /articles/get is always invoked.
The test ends up with error message
Timed out retrying after 30000ms: cy.wait() timed out waiting 30000ms for the 1st request to the route: GetArticles. No request ever occurred.
I've also tried using other endpoint e.g. vatcodes/get, and it works perfectly. The problem occurs only for articles/get, but I don't see any trail that would tell my why this happens for articles endpoint.
What is the problem? Why Cypress "blocks" 2nd call to this endpoint? What's more interesting, the problem doesn't exist for GetFeatures alias, which is created in an identical way.
Make sure the network intercept is registered before the application makes the call.
it('is registered too late', () => {
cy.intercept('/todos').as('todos')
cy.visit('/')
cy.wait('#todos')
})
In our case, we need to register the intercept before visiting the page. Once the page is loaded, the application fetches the todo items, and everything is working as expected.
you can see this link: https://glebbahmutov.com/blog/cypress-intercept-problems/
If I'm reading the situation correctly, the last log image is the failing test.
There is no (xhr) 200 /productmanagement/api/v1/articles/get showing there.
It goes straight from api/v1/subscriptionfeatures/get to api/v1/vatcodes/get, but in the first test the api/v1/articles/get was between those two calls.
If it occurs later in the screenshot, add an increased timeout to catch it (the same intercept can use the longer timeout in both tests, but it won't delay the first test).
This may mean you have found a bug in the app - it seems that a "Duplicate" action should have the same POSTs as an "Add" action.
Have you resolved this?
I'm using this config:
Given('a GraphQL service error is thrown', () => {
cy.intercept({ method: 'POST', url: '/uat/graphql', times: 1 }, { forceNetworkError: true });
});
With times: 1. But the interception does not block the request now.
I found times in the docs.

How to get wix chat channel Id when user enterto my website

I want to get channel Id so I used corvid documentation and follow instructions
First I added wix chat app
then I added the following function :
export async function wixGetChannelId() {
let channel = await $w("#myChatbox").getChannel({type: "Business"});
console.log("channel id",channelId) }
and call wixGetChannelId function from onReady
But I got undefied, what I need to change?
So I tried the below code to loop for the channel id.
$w.onReady(function () {
setInterval( () => {
getId();
}, 1500);
});
function getId() {
$w("#wixChat1").getChannel({type: "Business"})
.then((channel) => {
console.log(channel);
})
.catch((err) => {
console.log(err);
});
}
Basically, I get error the first few times (you are receiving the undefined because you dont catch the error) but as soon as I click on the chatbox icon (which I think triggers the creation of the channel) I start getting the channel information.
So I think the user needs to first initiate a conversation which triggers a new channel creation.

Firebase database trigger - onCreate gets triggered on delete and update

I have three Firebase database trigger function to push notifications to users. However, I have noticed that .onCreate() gets triggered on database update and delete. Is this expected? Is there a way to prevent this?
const functions = require('firebase-functions')
exports.onNoteCreate = functions
.region('europe-west1')
.database
.ref('/notes/{noteId}')
.onCreate((snapshot, context) => {
...
//Push notification to affected users
//Compose notification object
const notificationObject = { "test": true }
membersToAlert.forEach((memberId, index) => {
let isAlreadyPresent = false
//Do not write if already present! - This code should not be needed?
const ref = snapshot.ref.root.child(`/notes/${personId}/noteAdditions`)
ref.orderByChild('originId')
.equalTo(noteId)
.on("value", (removeSnapshot) => {
isAlreadyPresent = true
})
//Write notification to all affected users
if(!isAlreadyPresent) {
snapshot.ref.root.child(`/notifications/${personId}/noteAdditions`).push(notificationObject)
}
})
return true
})
My .onUpdate() and .onDelete() triggers are also listening to .ref('/notes/{noteId}'). Is that a problem?
How can I make sure .onCreate() only gets triggered when a new object is inserted?
EDIT:
My testing workflow is as follows:
Create a new node in /notes using .push() -> works as expected
Update the same node using .update() -> works as expected
Delete the node in /notes/{noteId} directly from the Firebase Console
Step 3 triggers both .onCreate() and .onUpdate(). See log below:
I 2019-08-12T17:17:25.867Z onNoteCreate 670055884755913 onNoteCreate ... onNoteCreate 670055884755913
I 2019-08-12T17:17:26.053Z onNoteUpdate 670048941917608 onNoteUpdate ... onNoteUpdate 670048941917608
D 2019-08-12T17:17:26.843878505Z onNoteDelete 670054292162841 Function execution started onNoteDelete 670054292162841
D 2019-08-12T17:17:26.849773576Z onNoteDelete 670054292162841 Function execution took 7 ms, finished with status: 'ok' onNoteDelete 670054292162841
Database before delete
-notifications
-userId
-noteAdditions
-guid01
-notificationData
-noteUpdates
-guid03
-notificationData
Database after delete
//guid01 gets deleted by .onDelete() as expected
//guid03 gets deleted by .onDelete() as expected
-notifications
-userId
-noteAdditions
-guid02
-notificationData //Inserted by .onCreate() upon delete
-noteUpdates
-guid04
-notificationData //Inserted by .onUpdate() upon delete
The listeners are attached to /notes/{noteId} and updates are being made at /notifications/{userId}/...
onNoteCreate
exports.onNoteCreate = functions
.region('europe-west1')
.database
.ref('/notes/{noteId}')
.onCreate((snapshot, context) => {
...
snapshot.ref.root.child(`/notifications/${personId}/noteAdditions`).push(notificationObject)
...
console.log('onNoteCreate', '...')
...
})
onNoteUpdate
exports.onNoteUpdate = functions
.region('europe-west1')
.database
.ref('/notes/{noteId}')
.onUpdate((change, context) => {
...
change.after.ref.root.child(`/notifications/${personId}/noteUpdates`).push(notificationObject)
...
console.log('onNoteUpdate', '...')
...
})
Does it matter that I import the functions like so?
const create = require('./src/db-functions/notes').onNoteCreate
const update = require('./src/db-functions/notes').onNoteUpdate
const delete = require('./src/db-functions/notes').onNoteDelete
exports.onNoteCreate = create
exports.onNoteUpdate = update
exports.onNoteDelete = delete
I failed to include the code in my example where I call
//Get user data - also updated by onNoteCreate, onNoteUpdate , onNoteDelete
dbRoot.child(`users/${userId}`)
.on('value', (snapshot) => {
.on() attached a listener that was being triggered each time the value was updated, thus triggering "onNoteCreate", "onNoteUpdate" and "onNoteDelete" when not expected. I should have used .once() if I did not wish to attach a listener which I did not.
All credits to #doug for pointing this out to me in this post:
Firebase database trigger - how to wait for calls

NodeJS infinite running terminal app with read from terminal

I am trying to create a terminal app that will run indefinitely and will have the ability to read from the terminal.
I tried to user the "readline" api but the app terminates without waiting for any input.
I added a "while(true)" loop but it seems that the thread gets stacked in the loop and does not respond to my input.
I need a series of random numbers.
To accomplice it I added an interval of 1000ms and the result was the same with while loop.
To summary I need to create an app that reads from the terminal and create random numbers on a given interval.
Any guidance will be appreciated.
Edit 1
Additional information I just thought to give you.
I tried to put either the readline call or the interval in a separate forked process but nothing changed.
Also I tried to use recursion for the readline.
Edit 2
Although I accepted #amangpt777`s answer I would like to give another problem that you might encounter.
I was calling my script like this 'clear | node ./script.js' on windows` powershell.
I believe that it was the pipe that was blocking my input.
I don't know if this can happen on linux, I haven't tested it.
I just add it here so you keep it in mind.
I am not sure what you are trying to accomplish here. But following code will take input from user using readline and will keep on storing the input in an array. Note that I have some commented code in this which can be uncommented if you want a publish subscriber model. Also that you will need to add more code to sanitize and validate your input. I hope you will get some pointers to achieve what you want with this:
var readline = require('readline');
//var redis = require('redis');
//let subscriber = redis.createClient();
//let publisher = redis.createClient();
let numEntered = [];
var r1 = readline.createInterface(
{
"input": process.stdin,
"output": process.stdout
}
);
// subscriber.subscribe('myFunc');
// subscriber.on('message', (channel, msg) => {
// //Your logic
// });
function printMyArr(){
console.log("Numbers entered till now: ", numEntered);
}
function askNumber(){
askQuestion('Next Number?\n')
.then(ans => {
handleAnswer(ans);
})
.catch(err => {
console.log(err);
})
}
function handleAnswer(inputNumber) {
if(inputNumber === 'e') {
console.log('Exiting!');
r1.close();
process.exit();
}
else {
numEntered.push(parseInt(inputNumber));
//publisher.publish('myFunc', parseInt(inputNumber));
// OR
printMyArr();
askNumber();
}
}
function askQuestion(q) {
return new Promise((resolve, reject) => {
r1.question(q, (ans) => {
return resolve(ans);
});
});
}
function init() {
askQuestion('Enter Stream. Press e and enter to end input stream!\n')
.then(ans => {
handleAnswer(ans);
})
.catch(err => {
console.log(err);
})
}
init();

Reassigning a variable after already in use?

I've got an app I'm writing in React Native. It's socketed and I have a file that controls all socket information.
import {Alert, AppState} from 'react-native';
import store from '../store/store';
import {updateNotifications} from '../reducers/notifications';
import {setError, clearError} from '../reducers/error';
import {updateCurrentEvent, updateEventStatus, setCurrentEvent} from '../reducers/event_details';
import {setAlert} from '../reducers/alert';
import {ws_url} from '../api/urls'
let conn = new WebSocket(ws_url);
/*
handleSocketConnections handles any actions that require rerouting. The rest are passed off to handleOnMessage
This is being called from authLogin on componentDidMount. It would be ideal to only initialize a socket conn
when a user logs in somehow, but this package gets ran when a user opens the app, meaning there are socket
connections that don't need to exist yet.
*/
function setAppStateHandler() {
AppState.addEventListener('change', cstate => {
if(cstate === 'active') {
reconnect()
}
})
}
export const handleSocketConnections = (navigator, route) => {
setAppStateHandler();
conn.onmessage = e => {
const state = store.getState();
const msg = JSON.parse(e.data);
const { type, payload, event_id } = msg;
const { event } = state.event_details.event_details;
if (type == "SET_EVENT_STATUS" && payload == "CLOSED" && event_id == event.event_id) {
navigator.push(route)
// store.dispatch(setAlert({
// message:"Event is closed, click to navigate to checkout."
// , scene: null
// }))
store.dispatch(updateEventStatus(payload));
} else {
handleOnMessage(msg, state)
}
}
}
export function reconnect() {
//TODO: Fatal errors should redirect the mainNav to a fatal error screen. Not dismount the nav entirely, as it does now
//and this should pop the error screen when it's fixed.
let state = store.getState();
conn = new WebSocket(ws_url);
setTimeout(function () {
if (conn.readyState == 1) {
if (typeof state.event_details.event_details != 'undefined') {
setSocketedEventInfo(state.event_details.event_details.event.event_id);
}
store.dispatch(clearError());
} else {
store.dispatch(setError('fatal',`Socket readyState should be 1 but it's ${conn.readyState}`))
}
}, 1000);
}
//Preform function on ES close.
conn.onclose = e => {
console.log("Closing wsbidder, ", `${e.code} -- ${e.reason}`);
//TODO: Set error here saying they need to restart the app. Maybe a 'reconnect' somehow?
//Maybe set a store variable to socketErr and if null, all is good. Else, panic the app?
//Use Case: Server is not started and user tries to connect to the app. String of e.message contains "Connection refused"
store.dispatch(setError("fatal", `Socket onclose: ${e.code} -- ${e.reason}`))
};
conn.onerror = e => {
console.log("Error at socket, ", e);
store.dispatch(setError("fatal", `Socket onerror: ${e.message}`))
};
//Initialization function for websocket.
// conn.onopen = e => console.log("Opening wsbidder, ", e)
function handleOnMessage(msg, state) {
switch (msg.type) {
//These types come from the SocketWrappers on the server.
//updateCurrentEvent should be filtering the event by event_id.
case "EVENT_ITEMS":
store.dispatch(updateCurrentEvent(
msg.payload
, state.user_info.uid
, state.event_details.event_details.event.event_id));
break;
case "NOTIFICATIONS":
//bug: this needs to filter notifications per event on the client-side.
store.dispatch(updateNotifications(
msg.payload
, state.event_details.event_details.event.event_id
, state.user_info.uid)
);
break;
case "NOT_BIDDABLE":
if (msg.event_id == state.event_details.event_details.event.event_id) {
store.dispatch(updateEventStatus("CLOSED"));
}
break;
case "PUSH_NOTIFICATION":
const {title, message} = msg.payload;
Alert.alert(title, message);
break;
default:
console.warn(`Unrecognized socket action type: ${msg.type}`);
}
}
//closes the socket connection and sends a reason to the server.
export const closeConn = reason => conn.close(null, reason);
export const setSocketedEventInfo = event_id => {
//Gives the event ID to the socketed connection, which pulls end dates.
const msg = {
type: "UPDATE_EVENT_DETAILS"
, payload: { event_id }
}
conn.send(JSON.stringify(msg));
}
export const createBid = (bid, cb) => {
/*
Expects:
const new_bid = {
item_id: item.item_id,
bid: amount, //Storage keeps storing it as a string
uid: 0, //Not needed here, but can't be null since the server wants an int.
event_id, key, bidder
};
*/
const new_bid = {
type: 'BID'
, payload: bid
};
// Send this to the server socket
conn.send(JSON.stringify(new_bid));
//Returning the callback so the front-end knows to flip the card back over.
return cb()
};
Some of the code is crap, I know. Unless you're giving true advice, which I'm always glad to follow, no need to bash it :-)
The issue I'm having is that when the socket dies (the conn variable), I can't re-initialize the socket and assign it to that conn variable. What I think is happening is all functions using the conn variable aren't using the 'new' one, still stuck to the 'old' one.
Line 9 -- Creating the original one.
Line 28 -- Creating an onMessage function for the conn object, within the handleSocketConnections function that gets called elsewhere at the start of the program
Line 57 -- Trying to re-assign a new connection to the conn variable in the reconnect function, that gets run whenever the app goes on standby (killing the socket connections).
Line 131 -- This gets called correctly from the reconnect function, connecting the socket to the server again
The reconnect() function runs correctly - the server registers the new connection with all the right info, but the app seems to still be in a weird state where there's no conn error (possibly looking at the new one??) but no actions are formed on the conn (possibly looking at the old one?).
Any ideas?
If you have to start a replacement webSocket connection, then you will need to rerun all the code that hooks up to the webSocket (installs event handlers, etc...). Because it's a new object, the old event listeners aren't associated with the new webSocket object.
The simplest way to do that is usually to create a single webSocketInit() function that you call both when you first create your webSocket connection and then call again any time you have to replace it with a new one. You can pass the latest webSocket object to webSocketInit() so any other code can see the new object. Individual blocks of code can register for onclose themselves if they want to know when the old one closes.
There are also more event-driven ways to do this by creating an EventEmitter that gets notified whenever the webSocket has been replaced and individual blocks of code can subscribe to that event if they want to get notified of that occurrence.

Categories