Celery progress bar in React
Tek Kshetri
Posted on February 12, 2021
1. Overview
2. Backend setup
3. Frontend setup
1. Overview
The progress bar is one of the most useful UI components for tracking the actual progress of the task. But It is still a complex task to track the exact progress. In this tutorial, I will guide you to make the progress bar using celery-progress
library with react.
Please check Celery Progress Bars for Django before starting this tutorial.
In this tutorial, I am going to upload the data to the server using Django Rest Framework (DRF), preprocess the data in the server, and send back the response. In my case, the preprocessing data will take 2-3 min, that's why I will visualize the progress bar of the processing in the frontend using react.
2. Backend setup
I consider you already set up your Django with celery. Now you need to install the celery-progress
library using pip,
pip install celery-progress
Add the endpoint URL to the celery-progress in urls.py
file,
from django.urls import re_path, include
re_path(r'^celery-progress/', include('celery_progress.urls')),
I am just writing the example function as celery_function
. You need to replace the actual progress tracker function.
from celery import shared_task
from celery_progress.backend import ProgressRecorder
import time
@shared_task(bind=True)
def celery_function(self, seconds):
progress_recorder = ProgressRecorder(self)
result = 0
for i in range(seconds):
time.sleep(1)
result += i
progress_recorder.set_progress(i + 1, seconds)
return result
I have a Data
model like following,
class Data(models.Model):
name = models.CharField(max_length=100)
taskId = models.CharField(max_length=200, blank=True)
...
...
...
Now, lets overwrite the create
method in your ViewSet
class of DRF and call the celery_function.delay(time)
function.
In my code, my model name is
Data
, the serializer class isDataSerializer
. I used thepatch
method to append the task_id to the Data model.
class DataViewSet(viewsets.ModelViewSet):
queryset = Data.objects.all()
serializer_class = DataSerializer
permission_classes = [permissions.IsAuthenticated]
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
if serializer.is_valid():
data = serializer.data
id = data['id']
task = celery_function.delay(10)
self.patch(id, {"taskId": task.id})
return Response({'task_id': task.id}, status=status.HTTP_201_CREATED)
def patch(self, pk, taskid):
instance = self.get_object(pk)
serializer = DataSerializer(
instance, data=taskid, partial=True)
if serializer.is_valid():
serializer.save()
We set up everything in the backend. The task progress will be available on this URL: http://localhost:8000/celery-progress/${task_id}.
3. Frontend Setup
In the frontend, I am going to write the custom progress bar using react-bootstrap
. The DRF backend will provide us the task progress in this URL: http://localhost:8000/celery-progress/${task_id}. Now we need to recursively hit this URL until the task status changes to complete. For this let's fetch the progress using axios
.
For this tutorial, I am using react-redux. In the redux actions.js
file, I have created two functions, one for adding the data (named as addData
), and another for getting the progress of the task (named as getProgress
). The addData
function will only useful for adding the data to the server.
export const addData = (data) => (dispatch, getState) => {
axios
.post(`http://localhost:8000/api/data/`, data)
.then((res) => {
dispatch({
type: ADD_DATA,
payload: res.data,
});
const task_id = res.data?.task_id;
dispatch(getProgress(task_id));
})
.catch((err) =>
console.log(err)
);
};
The getProgress
will take the actual progress of the task in with a JSON
response. The example of the json response is as below,
{"complete": true, "success": true, "progress": {"pending": false, "current": 100, "total": 100, "percent": 100}, "result": "Done"}
Here, I recursively call the getProgress
function to get the current progress until the task gets completed.
export const getProgress = (taskId) => {
return (dispatch) => {
return axios
.get(`http://localhost:8000/celery-progress/${taskId}`)
.then((res) => {
dispatch({
type: PROGRESS_BAR,
payload: { taskid: taskId, ...res.data },
});
if (!res.data.complete && !res.data?.progess?.pending) {
return dispatch(getProgress(taskId));
}
})
.catch((err) =>
console.log(err)
);
};
};
Also in the redux reducers.js
file, I added the response as follows,
import { ADD_DATA, PROGRESS_BAR } from "../actions/types";
const initialState = {
progress: {},
data: [],
};
export default function (state = initialState, action) {
switch (action.type) {
case PROGRESS_BAR:
return {
...state,
progress: action.payload,
};
case ADD_DATA:
return {
...state,
data: action.payload,
};
default:
return state;
}
}
Now, let's write the react component to visualize the progress bar, as below,
import React, { Component } from "react";
import { connect } from "react-redux";
import { ProgressBar } from "react-bootstrap";
class ProgressBarUi extends Component {
render() {
const { percent } = this.props;
return (
<ProgressBar
now={percent}
animated
variant="success"
label={`${percent}%`}
/>
);
}
}
export default ProgressBarUi;
The progress bar needs to visualize only when the progress is not completed, and not pending,
import React, { Component } from "react";
import { connect } from "react-redux";
import { addData } from "../../../actions";
import ProgressBarUi from "../../common/ProgressBar";
class AddData extends Component {
onSubmit = (e) => {
const data = {
key1: "value1",
key2: "value2",
};
this.props.addExposure(data);
};
render() {
const { progress } = this.props;
return (
<div>
{/* your progress bar goes here */}
{progress?.progress && !progress?.complete && (
<ProgressBarUi percent={progress.progress?.percent} />
)}
...
...
...
{/* data submit button */}
<input type="submit" value="Add data" onSubmit={this.onSubmit} />
</div>
);
}
}
const mapStateToProps = (state) => ({
progress: state.progress,
});
export default connect(mapStateToProps, {
addData,
})(AddData);
Alright, finally you successfully set up the progress bar using Django, react, and celery.
Posted on February 12, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.