Can't figure out where to initiate CronJob in react app - reactjs

I have a react app, which must perform a weekly task every Monday #7:58 am. The task is setup as a separate function "notification()". And I want to use the 'CRON' package from NPM to call notification() at the appropriate time.
I have CRON wrapped inside of a function like this:
let mondayNotif = () => {
new CronJob('* 58 7 * * 2', function() {
notification()
}, null, true, 'America/Los_Angeles');
};
My question: where should I call the function mondayNotif(), to make sure that the CronJob is initiated correctly? I thought at first it must be on the backend, but the NPM package doesn't seem to support server-side. But if I call mondayNotif() on the client side, will the CronJob still happen if the site is inactive?

From what I know React JS is front end - it runs on client side. You need a server. In this case a node.js based server. Theroetically if nobody opens the website nothing will be fired up in react js. Look up how to schedule cron jobs on node.js
enter link description here

I found my own answer. But first, a few insights on CronJobs that would have helped me:
CronJobs are essentially a third-party function with an embedded clock. Once they are "initiated", you don't have to call them. The third-party calls them remotely, based on the time that you scheduled in the parameters (ie: "30 6 * * 5").
There is some discrepancy in different blogs about the CRON time. For instance some blogs insisted there are 6 time variables, but I found it worked with 5.
CronJobs should be in a separate file from the body of your main code, typically at the top of your folder structure near your "package.json" & "server.js" files.
It seems to be cleanest to setup all of your CronJob utilities directly inside the cronjob.js file. For instance: I used a separate database connection directly in cronjob.js and by-passed the api routes completely.
CronJobs should be initiated exactly once, at the beginning of the app launch. There are a few ways to do this: package.json or server.js are the most obvious choices.
Here is the file structure I ended up using:
-App
--package.json
--server.js
--cronjob.js
--/routes
--/src
--/models
--/public
...And then I imported the cronjob.js into "server.js". This way the cronjob function is initiated one time, when the server.js file is loaded during "dev" or "build".
For reference, here's the raw cronjob.js file (this is for an email notification):
const CronJob = require('cron').CronJob;
const Department = require('./models/department.js');
const template_remind = require('./config/remindEmailTemplate.js');
const SparkPost = require('sparkpost');
const client = new SparkPost('#############################');
const mongoose = require("mongoose");
const MONGODB_URI =
process.env.MONGODB_URI || "mongodb://localhost:27017/app";
mongoose.Promise = Promise;
// -------------------------- MongoDB -----------------------------
// Connect to the Mongo DB
mongoose.connect(MONGODB_URI, { useNewUrlParser: true }, (err, db) => {
if (err) {
console.log("Unable to connect to the mongoDB server. Error:", err);
} else {
console.log("Connection established to", MONGODB_URI);
}
});
const db = mongoose.connection;
// Show any mongoose errors
db.on("error", error => {
console.log("Mongoose Error: ", error);
});
// Once logged in to the db through mongoose, log a success message
db.once("open", () => {
console.log("Mongoose CRON connection successful.");
});
// ------------------------ Business Logic --------------------------
function weekday(notifications) {
Department.find({"active": true, "reminders": notifications, "week": {$lt: 13}}).distinct('participants', function(err, doc) {
if(err){
// console.log("The error: "+err)
} else {
console.log("received from database... "+JSON.stringify(doc))
for(let i=0; i<doc.length; i++){
client.transmissions.send({
recipients: [{address: doc[i]}],
content: {
from: 'name#sparkmail.email.com',
subject: 'Your email notification',
html: template_remind()
},
options: {sandbox: false}
}).then(data => {})
}
}
})
}
function weeklyNotif() {
new CronJob('45 7 * * 1', function() {weekday(1)}, null, true, 'America/New_York');
new CronJob('25 15 * * 3', function() {weekday(2)}, null, true, 'America/New_York');
new CronJob('15 11 * * 5', function() {weekday(3)}, null, true, 'America/New_York');
}
module.exports = weeklyNotif()
As you can see, I setup a unique DB connection and email server connection (separate from my API file), and ran all of the logic inside this one file, and then exported the initiation function.
Here's what appears in server.js:
const cronjob = require("./cronjob.js");
All you have to do here is require the file, and because it is exported as a function, this automatically initiates the cronjob.
Thanks for reading. If you have feedback, please share.

Noway, do call CronJob from client-side, because if there are 100 users, CronJob will be triggered 100 times. You need to have it on Server-Side for sure

Related

Cloud Tasks are stuck in queue and are not executed

I am using Cloud Functions to put tasks into Cloud Tasks Queue and invoke a service (worker) function. Both the task generator and task Handler functions are deployed to Cloud Functions.
This is my createTask.js:
const {CloudTasksClient} = require('#google-cloud/tasks');
const client = new CloudTasksClient();
exports.createTask = async (req, res) => {
const location = 'us-central1';
const project = 'project-id';
const queue = 'queueid';
const payload = 'Hello, World!';
const parent = client.queuePath(project, location, queue);
const task = { appEngineHttpRequest: {
httpMethod: 'POST',
relativeUri : '/log_payload'},
const [ response ] = await tasksClient.createTask({ parent: queuePath, task })
if (payload) {
task.appEngineHttpRequest.body = Buffer.from(payload).toString('base64');
}
let inSeconds = 0 ;
if (inSeconds) {
// The time when the task is scheduled to be attempted.
task.scheduleTime = {
seconds: inSeconds + Date.now() / 1000,
};
}
console.log('Sending task:');
console.log(task);
// Send create task request.
const request = {parent: parent, task: task};
const [response] = await client.createTask(request);
const name = response.name;
console.log(`Created task ${name}`);
res.send({message : "Ok"});
}
server.js
const express = require('express');
const app = express();
app.enable('trust proxy');
app.use(bodyParser.raw({type: 'application/octet-stream'}));
app.get('/', (req, res) => {
// Basic index to verify app is serving
res.send('Hello, World!').end();
});
app.post('/log_payload', (req, res) => {
// Log the request payload
console.log('Received task with payload: %s', req.body);
res.send(`Printed task payload: ${req.body}`).end();
});
app.get('*', (req, res) => {
res.send('OK').end();
});
app.listen(3000 , () => {
console.log(`App listening on port`);
console.log('Press Ctrl+C to quit.');
});
When I run trigger the task generator function via HTTP trigger in Postman, the task is added to the queue but it stays there forever.
The queue looks like this:
The logs of the handler task show it was never triggered. The task in the queue cannot reach its handler.
The logs of task in queue looks like this:
The task is failed and is in the queue:
enter image description here
I have tried to reproduce the issue by following doc. Successfully tasks are executed.I assume you also followed Cloud Task quickstart & Github code samples for set-up. This quickstart attempts to set up following components -
a) Create Task (~ createTask.js) - This can be either run locally or deployed as a Cloud function. In your case, this has been created as a Cloud Function.
b) Task Queue Creation - This is the creation of a Cloud Task queue.
c) Task Target / Handler (~ server.js) - The quickstart assumes this component to be deployed as an App Engine worker instance. This can also be seen in the corresponding Task Creation script (~ createTask.js).
Based on the description, assuming you are deploying the Task Target / Handler also as a cloud function If this assumption is correct then you need to follow this public doc to create a HTTP Target Task which uses "httpRequest" instead of "appEngineHttpRequest" construct. There is also a tutorial, that you may find helpful.
If you are using Cloud Functions instead of App Engine, Target for Tasks is also supported by the error - "404 - Not Found" in those screenshots you provided. This error signifies that the target App Engine instance endpoint (~ log_payload) is not found. This is also the reason why the task is not getting executed.
I suggest you to try out the above steps if those does not help I think you may raise support case here as your issue seems to require more in-depth analysis in your project logs to see why task queues are not being triggered.

Updating state when database gets updated

I got a schema looking something like this:
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
//Create Schema
const PhoneNumbersSchema = new Schema({
phone_numbers: {
phone_number: 072382838232
code: ""
used: false
},
});
module.exports = PhoneNumbers = mongoose.model(
"phonenumbers",
PhoneNumbersSchema
);
And then I got an end-point that gets called from a 3rd party application that looks like this:
let result = await PhoneNumbers.findOneAndUpdate(
{ country_name: phoneNumberCountry },
{ $set: {"phone_numbers.$[elem1].services.$[elem2].sms_code": 393} },
{ arrayFilters: [ { "elem1.phone_number": simNumberUsed }, { "elem2.service_name": "steam" } ] },
Basically the end-point updates the "code" from the phone numbers in the database.
In react this is how I retrieve my phone numbers from the state:
const phonenumbers_database = useSelector((state) => {
console.log(state);
return state.phonenumbers ? state.phonenumbers.phone_numbers_details : [];
});
Every time the code gets changed in my database from the API call I would like to update "phonenumbers_database" in my state automatically.
How would I be able to do that?
MongoDB can actually watch for changes to a collection or a DB by opening a Change Stream.
First, you would open up a WebSocket from your React app to the server using something like Socket.io, and then watch for changes on your model:
PhoneNumbers
.watch()
.on('change', data => socket.emit('phoneNumberUpdated', data));
Your third party app will make the changes to the database to your API, and then the changes will be automatically pushed back to the client.
You could do a polling and check the Database every N secs or by using change streams
After that, to notify your frontend app, you need to use WebSockets, check on Socket IO

Set retry with dropzone-react-component when upload fails

In my project I'm using React-Dropzone-Component (https://github.com/felixrieseberg/React-Dropzone-Component) based on Dropzone.js.
I'm using this component because I'm developing a SharePoint webpart and there is already an example based on this solution on Microsoft PnP GitHub repository.
Anyway, the upload is working fine, but sometimes, mainly when I keep a web page opened for a couple of minutes doing nothing, I receive an error trying to upload new files. I retry an upload and it fails returning Server responded with (0) code error. I also see on Google Chrome console an ERR_CONNECTION_RESET error. If I try to upload 5 files in second instance, I get error on first 2-3 and then the remaining files works fine. Weird.
I've already investigated my network, but there are no failures. I've also tried with 3 different networks and I've received the same error.
I've also updated the component with the latest Dropzone.js (5.7.2).
This is my code:
let componentConfig = {
iconFiletypes: this.props.fileTypes.split(','),
showFiletypeIcon: true,
postUrl: _context.pageContext.web.absoluteUrl,
autoProcessQueue: true
};
var djsConfig = {
headers: {
"X-RequestDigest": digest1
},
addRemoveLinks:false
};
let myDropzone;
let eventHandlers = {
// This one receives the dropzone object as the first parameter
// and can be used to additional work with the dropzone.js
// object
init: function(dz){
myDropzone=dz;
},
sending: async function (file, xhr) {
var fileName = file.name;
fileName = fileName.replace(/[&\/\\#,+()$~%='":*?<>{}]/g, "");
if (file.size <= 10485760) {
// small upload
await web.getFolderByServerRelativeUrl("/test/"+_listName).files.add(fileName, file, true).then(_ => console.log("Ok!"));
} else {
// large upload
await web.getFolderByServerRelativeUrl("/test/"+_listName).files.addChunked(fileName, file, data => {}, true).then(_ => console.log("Ok!"));
}
},
error:function(file,error,xhr){
file.status = myDropzone.ADDED;
myDropzone.removeFile(file);
myDropzone.enqueueFile(file);
}
};
<DropzoneComponent eventHandlers={eventHandlers} djsConfig={djsConfig} config={componentConfig}>
<div className="dz-message icon ion-upload">Drop files here to upload</div>
</DropzoneComponent>
If I can't prevent this ERR_CONNECTION_RESET error, I would like to set up an automatic retry for these files. The code I've posted above is not working fine or it returns "Uncaught Error: This file can't be queued because it has already been processed or was rejected.".
Is there a solution or a good way to set up a retry?

Fetch status 200 but pending endllessly, except first call

I've been searching to solve this problem for a while but couldn't find a working solution.
I'm making a simple social network website and this API returns a article data such as text, image and video url, etc, all saved in server's local MySQL Database. My front-end is React and server is Nginx reverse proxy with Node.js using Express. When I load the page, I create 5 React components that each make fetch request for given article number.
The following code snippet is the fetch API that asks the server to fetch data from database:
//server-side script
app.get('/api/getArticle/:id', (req, res) => {
const con = mysql.createConnection({
host: 'myhost_name',
user: 'myUser',
password: 'myPassword',
database: 'myDB',
});
con.connect(function (err) {
if (err) {
throw err;
}
console.log("Connected!");
})
const idInterest = req.params.id.toString();
console.log(idInterest)
let sql = 'some_sql';
con.query(sql, function (err, result) {
if (err) {
res.status(500).send("Error while getting article data");
return;
}
else {
res.set('Connection', 'close')
res.status(200).send(result);
console.log("ended")
con.end();
return;
}
})
}
//React script
//index.js
fetch('http://mywebsite.com/api/getMaxArticleId/')//Retrieve top 5 article ID
.then((response) => {
for (let i = 0; i < data.length; i++) {
nodesList.push(<Container articleId={data[i]['id']}/>)
}
ReactDOM.render(<React.StrictMode><NavBar />{nodesList}<Writer writer="tempWriter" /></React.StrictMode>, document.getElementById('root'));
})
//Container.jsx; componentDidMount
const url = "http://mywebsite.com/api/getArticle/" + this.props.articleId.toString();
fetch(url, {
method: 'GET',
credentials: "include",
}).then((response) => {
response.json().then((json) => {
console.log(json);
//processing json data
This used to work very fine, but suddenly the getArticle/:id calls started to show 200 status but 'pending' in 'time' column in Chrome network tab, endlessly, all except the first*getArticle/:idcall. This prevents my subsequent .then() in each Container from being called and thus my entire tab is frozen.
Link to image of network tab
As you see from the image, all pending fetches are missing 'Content Download' and stuck in 'Waiting(TTFB)', except the first call, which was '39'
I checked the API is working fine, both on Postman and Chrome, the server sends result from DB query as expected, and first call's Json response is intact. I also see that console.log(response.json()) in React front-end shows Promise{<pending>} with *[[PromiseStatus]]: "Resolved"* and *[[PromiseValue]]* of Array(1) which has expected json data inside.
See Image
This became problematic after I added YouTube upload functionality with Google Cloud Platform API into my server-side script, so that looks little suspicious, but I have no certain clue. I'm also guessing maybe this could be problem of my React code, probably index.js, but I have no idea which specific part got me so wrong.
I've been working on this for a few days, and maybe I need common intelligence to solve this (or I made a silly mistake XD). So, any advices are welcomed :)

Getting “TypeError: failed to fetch” when sending an email with SendGrid on ReactJS project

I am trying to send email with SendGrid in ReactJS project.
This is my componnet:
//Email.js
import React from 'react'
const sgMail = require('#sendgrid/mail');
sgMail.setApiKey(process.env.SENDGRID_API_KEY);
const msg = {
to: 'aaaaa#gmail.com',
from: 'bbbb#gmail.com',
subject: 'This is a test mail',
text: 'and easy to do anywhere, even with Node.js',
html: '<strong>and easy to do anywhere, even with Node.js</strong>',
};
sgMail.send(msg).catch(error => {alert(error.toString()); });
export const Email= () => (
<h1>Email Sending Page</h1>
)
When I am trying to run the app with "npm start" on localhost, the email is not sent and I got the error message "TypeError: Failed to fetch".
But, if I am using this code:
//Email.js
const sgMail = require('#sendgrid/mail');
sgMail.setApiKey(process.env.SENDGRID_API_KEY);
const msg = {
to: 'aaaaa#gmail.com',
from: 'bbbb#gmail.com',
subject: 'This is a test mail',
text: 'and easy to do anywhere, even with Node.js',
html: '<strong>and easy to do anywhere, even with Node.js</strong>',
};
sgMail.send(msg)
and do this command: "node Email.js" the mail is sent. It works only this way and I cannot understand why.
I tried any solution that I could find but nothing works.
(I tried even to put the api_key hardcoded in the code just for the test and I got the same result).
EDIT
After looking around a bit I found out that you can't use Sendgrid to send email directly from the browser.
Sendgrid won't let you send an email directly using Javascript in the
browser.
You will need to have a server set-up and use the server to send the
email instead (using your favourite back-end framework/language,
Node.js, php, Java, etc.).
The steps for sending a mail will be similar to this:
Write email details in the React application
Send a POST request to
your server endpoint (for example, /sendemail) with the email data
(recipient, title, content, etc.) Receive Email data in the server and
send it to Sendgrid api Here is the official Sendgrid documentation
regarding their CORS policy:
https://sendgrid.com/docs/for-developers/sending-email/cors/
Source: React unable to send email with SendGrid
EDIT 2
If you want to implement Sendgrid without actually building and deploying a server, you can use a simple Firebase function which is free to host.
I know this may look intimidating but in reality its pretty easy. Also I just put this together real quick so if anything doesn't work out for you, shoot me a comment.
Follow steps 1-3 on the getting started page for firebase functions. It is pretty straightforward and you end up with the firebase tools CLI installed.
Navigate to the functions/ folder inside your project on the command line/terminal.
Install the Sendgrid and cors libraries in the functions folder
npm i #sendgrid/mail cors
Add your Sendgrid API key to your firebase environment with the following command in your project:
firebase functions:config:set sendgrid.key="THE API KEY"
Copy this into your functions/index.js file:
const functions = require("firebase-functions");
const cors = require("cors")({ origin: true });
const sgMail = require("#sendgrid/mail");
exports.sendEmail = functions.https.onRequest((req, res) => {
sgMail.setApiKey(functions.config().sendgrid.api);
return cors(req, res, () => {
const { msg } = req.body;
sgMail.send(msg).catch(error => {
alert(error.toString());
});
res.status(200).send(msg);
});
});
Save it and run firebase deploy --only functions on the command line. Your function should now be live at https://us-central1-<project-id>.cloudfunctions.net/sendEmail
Now change your React file to:
//Email.js
import React, { useEffect } from 'react'
export const Email= () => {
useEffect(() => {
const sendEmail = async() => {
const msg = {
to: 'aaaaa#gmail.com',
from: 'bbbb#gmail.com',
subject: 'This is a test mail',
text: 'and easy to do anywhere, even with Node.js',
html: '<strong>and easy to do anywhere, even with Node.js</strong>',
};
const response = await fetch(
'https://us-central1-FIREBASE-PROJECT-ID-HERE.cloudfunctions.net/sendEmail', {
method: 'POST',
body: JSON.stringify(msg),
headers: {
'Content-Type': 'application/json'
}
});
console.log("response", response);
}
sendEmail();
}, []);
return <h1>Email Sending Page</h1>
}
And thats it! You basically have a server side function without making a server and its free!
Feel free to ignore this if you don't feel like putting in the work but if you need any help, let me know.

Resources