Progress display, refactor

pull/1589/head
Piero Toffanin 2025-01-24 14:23:18 -05:00
rodzic 85c9df1265
commit b16557037c
12 zmienionych plików z 41 dodań i 35 usunięć

Wyświetl plik

@ -17,7 +17,14 @@ class CheckTask(APIView):
res = TestSafeAsyncResult(celery_task_id)
if not res.ready():
return Response({'ready': False}, status=status.HTTP_200_OK)
out = {'ready': False}
# Copy progress meta
if res.state == "PROGRESS":
for k in res.info:
out[k] = res.info[k]
return Response(out, status=status.HTTP_200_OK)
else:
result = res.get()
@ -29,6 +36,9 @@ class CheckTask(APIView):
msg = self.on_error(result)
return Response({'ready': True, 'error': msg})
if isinstance(result.get('file'), str) and not os.path.isfile(result.get('file')):
return Response({'ready': True, 'error': "Cannot generate file"})
return Response({'ready': True})
def on_error(self, result):

Wyświetl plik

@ -15,8 +15,8 @@ def run_function_async(func, *args, **kwargs):
return eval_async.delay(source, func.__name__, *args, **kwargs)
@app.task
def eval_async(source, funcname, *args, **kwargs):
@app.task(bind=True)
def eval_async(self, source, funcname, *args, **kwargs):
"""
Run Python code asynchronously using Celery.
It's recommended to use run_function_async instead.
@ -24,4 +24,11 @@ def eval_async(source, funcname, *args, **kwargs):
ns = {}
code = compile(source, 'file', 'exec')
eval(code, ns, ns)
if kwargs.get("with_progress"):
def progress_callback(status, perc):
self.update_state(state="PROGRESS", meta={"status": status, "progress": perc})
kwargs['progress_callback'] = progress_callback
del kwargs['with_progress']
return ns[funcname](*args, **kwargs)

Wyświetl plik

@ -1,7 +1,8 @@
import $ from 'jquery';
export default {
waitForCompletion: (celery_task_id, cb, checkUrl = "/api/workers/check/") => {
waitForCompletion: (celery_task_id, cb, progress_cb) => {
const checkUrl = "/api/workers/check/";
let errorCount = 0;
let url = checkUrl + celery_task_id;
@ -15,6 +16,9 @@ export default {
}else if (result.ready){
cb();
}else{
if (typeof progress_cb === "function" && result.progress !== undefined && result.status !== undefined){
progress_cb(result.status, result.progress);
}
// Retry
setTimeout(() => check(), 2000);
}

Wyświetl plik

@ -109,14 +109,6 @@ class TaskContoursGenerate(TaskView):
except ContoursException as e:
return Response({'error': str(e)}, status=status.HTTP_200_OK)
class TaskContoursCheck(CheckTask):
def on_error(self, result):
pass
def error_check(self, result):
contours_file = result.get('file')
if not contours_file or not os.path.exists(contours_file):
return _('Could not generate contour file. This might be a bug.')
class TaskContoursDownload(GetTaskResult):
pass

Wyświetl plik

@ -1,7 +1,6 @@
from app.plugins import PluginBase
from app.plugins import MountPoint
from .api import TaskContoursGenerate
from .api import TaskContoursCheck
from .api import TaskContoursDownload
@ -15,6 +14,5 @@ class Plugin(PluginBase):
def api_mount_points(self):
return [
MountPoint('task/(?P<pk>[^/.]+)/contours/generate', TaskContoursGenerate.as_view()),
MountPoint('task/[^/.]+/contours/check/(?P<celery_task_id>.+)', TaskContoursCheck.as_view()),
MountPoint('task/[^/.]+/contours/download/(?P<celery_task_id>.+)', TaskContoursDownload.as_view()),
]

Wyświetl plik

@ -241,7 +241,7 @@ export default class ContoursPanel extends React.Component {
this.setState({[loadingProp]: false});
}
}
}, `/api/plugins/contours/task/${taskId}/contours/check/`);
});
}else if (result.error){
this.setState({[loadingProp]: false, error: result.error});
}else{

Wyświetl plik

@ -2,7 +2,7 @@ import os
from rest_framework import serializers
from rest_framework import status
from rest_framework.response import Response
from app.api.workers import GetTaskResult, TaskResultOutputError, CheckTask
from app.api.workers import GetTaskResult, TaskResultOutputError
from app.models import Task
from app.plugins.views import TaskView
from django.utils.translation import gettext_lazy as _
@ -34,9 +34,6 @@ class TaskVolume(TaskView):
except Exception as e:
return Response({'error': str(e)}, status=status.HTTP_200_OK)
class TaskVolumeCheck(CheckTask):
pass
class TaskVolumeResult(GetTaskResult):
def get(self, request, pk=None, celery_task_id=None):
task = Task.objects.only('dsm_extent').get(pk=pk)

Wyświetl plik

@ -1,6 +1,6 @@
from app.plugins import MountPoint
from app.plugins import PluginBase
from .api import TaskVolume, TaskVolumeCheck, TaskVolumeResult
from .api import TaskVolume, TaskVolumeResult
class Plugin(PluginBase):
def include_js_files(self):
@ -12,6 +12,5 @@ class Plugin(PluginBase):
def api_mount_points(self):
return [
MountPoint('task/(?P<pk>[^/.]+)/volume$', TaskVolume.as_view()),
MountPoint('task/[^/.]+/volume/check/(?P<celery_task_id>.+)$', TaskVolumeCheck.as_view()),
MountPoint('task/(?P<pk>[^/.]+)/volume/get/(?P<celery_task_id>.+)$', TaskVolumeResult.as_view()),
]

Wyświetl plik

@ -139,7 +139,7 @@ export default class MeasurePopup extends React.Component {
else this.setState({volume: parseFloat(volume)});
}, `/api/plugins/measure/task/${task.id}/volume/get/`);
}
}, `/api/plugins/measure/task/${task.id}/volume/check/`);
});
}else if (result.error){
this.setState({error: result.error});
}else{

Wyświetl plik

@ -2,11 +2,11 @@ import os
import json
from rest_framework import status
from rest_framework.response import Response
from app.plugins.views import TaskView, CheckTask, GetTaskResult, TaskResultOutputError
from app.plugins.views import TaskView, GetTaskResult, TaskResultOutputError
from app.plugins.worker import run_function_async
from django.utils.translation import gettext_lazy as _
def detect(orthophoto, model):
def detect(orthophoto, model, progress_callback=None):
import os
from webodm import settings
@ -17,7 +17,7 @@ def detect(orthophoto, model):
return {'error': "GeoDeep library is missing"}
try:
return {'output': gdetect(orthophoto, model, output_type='geojson')}
return {'output': gdetect(orthophoto, model, output_type='geojson', progress_callback=progress_callback)}
except Exception as e:
return {'error': str(e)}
@ -34,11 +34,9 @@ class TaskObjDetect(TaskView):
if not model in ['cars', 'trees']:
return Response({'error': 'Invalid model'}, status=status.HTTP_200_OK)
celery_task_id = run_function_async(detect, orthophoto, model).task_id
return Response({'celery_task_id': celery_task_id}, status=status.HTTP_200_OK)
celery_task_id = run_function_async(detect, orthophoto, model, with_progress=True).task_id
class TaskObjCheck(CheckTask):
pass
return Response({'celery_task_id': celery_task_id}, status=status.HTTP_200_OK)
class TaskObjDownload(GetTaskResult):
def handle_output(self, output, result, **kwargs):

Wyświetl plik

@ -1,7 +1,6 @@
from app.plugins import PluginBase
from app.plugins import MountPoint
from .api import TaskObjDetect
from .api import TaskObjCheck
from .api import TaskObjDownload
@ -15,6 +14,5 @@ class Plugin(PluginBase):
def api_mount_points(self):
return [
MountPoint('task/(?P<pk>[^/.]+)/detect', TaskObjDetect.as_view()),
MountPoint('task/[^/.]+/check/(?P<celery_task_id>.+)', TaskObjCheck.as_view()),
MountPoint('task/[^/.]+/download/(?P<celery_task_id>.+)', TaskObjDownload.as_view()),
]

Wyświetl plik

@ -28,6 +28,7 @@ export default class ObjDetectPanel extends React.Component {
loading: true,
task: props.tasks[0] || null,
detecting: false,
progress: null,
objLayer: null,
};
}
@ -124,7 +125,7 @@ export default class ObjDetectPanel extends React.Component {
}
handleDetect = () => {
this.setState({detecting: true, error: ""});
this.setState({detecting: true, error: "", progress: null});
const taskId = this.state.task.id;
this.saveInputValues();
@ -153,7 +154,9 @@ export default class ObjDetectPanel extends React.Component {
}
});
}
}, `/api/plugins/objdetect/task/${taskId}/check/`);
}, (_, progress) => {
this.setState({progress});
});
}else if (result.error){
this.setState({detecting: false, error: result.error});
}else{
@ -169,7 +172,7 @@ export default class ObjDetectPanel extends React.Component {
}
render(){
const { loading, permanentError, objLayer, detecting, model } = this.state;
const { loading, permanentError, objLayer, detecting, model, progress } = this.state;
const models = [
{label: _('Cars'), value: 'cars'},
{label: _('Trees'), value: 'trees'},
@ -189,7 +192,7 @@ export default class ObjDetectPanel extends React.Component {
</select>
<button onClick={this.handleDetect}
disabled={detecting} type="button" className="btn btn-sm btn-primary btn-detect">
{detecting ? <i className="fa fa-spin fa-circle-notch"/> : <i className="fa fa-search fa-fw"/>} {_("Detect")}
{detecting ? <i className="fa fa-spin fa-circle-notch"/> : <i className="fa fa-search fa-fw"/>} {_("Detect")} {detecting && progress !== null ? ` (${progress.toFixed(0)}%)` : ""}
</button>
</div>