diff --git a/app/migrations/0023_task_running_progress.py b/app/migrations/0023_task_running_progress.py new file mode 100644 index 00000000..68e2b2c2 --- /dev/null +++ b/app/migrations/0023_task_running_progress.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.3 on 2018-12-07 18:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0022_auto_20181205_1644'), + ] + + operations = [ + migrations.AddField( + model_name='task', + name='running_progress', + field=models.FloatField(blank=True, default=0.0, help_text="Value between 0 and 1 indicating the running progress (estimated) of this task's."), + ), + ] diff --git a/app/models/task.py b/app/models/task.py index c9bf404f..cd78d596 100644 --- a/app/models/task.py +++ b/app/models/task.py @@ -156,6 +156,22 @@ class Task(models.Model): (pending_actions.RESIZE, 'RESIZE'), ) + # Not an exact science + TASK_OUTPUT_MILESTONES = { + 'Running ODM Load Dataset Cell': 0.01, + 'Running ODM Load Dataset Cell - Finished': 0.05, + 'opensfm/bin/opensfm match_features': 0.10, + 'opensfm/bin/opensfm reconstruct': 0.20, + 'opensfm/bin/opensfm export_visualsfm': 0.30, + 'Running ODM Meshing Cell': 0.60, + 'Running MVS Texturing Cell': 0.65, + 'Running ODM Georeferencing Cell': 0.70, + 'Running ODM DEM Cell': 0.80, + 'Running ODM Orthophoto Cell': 0.85, + 'Running ODM OrthoPhoto Cell - Finished': 0.90, + 'Compressing all.zip:': 0.95 + } + id = models.UUIDField(primary_key=True, default=uuid_module.uuid4, unique=True, serialize=False, editable=False) uuid = models.CharField(max_length=255, db_index=True, default='', blank=True, help_text="Identifier of the task (as returned by OpenDroneMap's REST API)") @@ -187,6 +203,9 @@ class Task(models.Model): resize_progress = models.FloatField(default=0.0, help_text="Value between 0 and 1 indicating the resize progress of this task's images.", blank=True) + running_progress = models.FloatField(default=0.0, + help_text="Value between 0 and 1 indicating the running progress (estimated) of this task's.", + blank=True) def __init__(self, *args, **kwargs): super(Task, self).__init__(*args, **kwargs) @@ -429,12 +448,14 @@ class Task(models.Model): # We also remove the "rerun-from" parameter if it's set self.options = list(filter(lambda d: d['name'] != 'rerun-from', self.options)) + self.upload_progress = 0 self.console_output = "" self.processing_time = -1 self.status = None self.last_error = None self.pending_action = None + self.running_progress = 0 self.save() else: raise ProcessingError("Cannot restart a task that has no processing node") @@ -468,7 +489,14 @@ class Task(models.Model): current_lines_count = len(self.console_output.split("\n")) console_output = self.processing_node.get_task_console_output(self.uuid, current_lines_count) if len(console_output) > 0: - self.console_output += console_output + '\n' + self.console_output += "\n".join(console_output) + '\n' + + # Update running progress + for line in console_output: + for line_match, value in self.TASK_OUTPUT_MILESTONES.items(): + if line_match in line: + self.running_progress = value + break if "errorMessage" in info["status"]: self.last_error = info["status"]["errorMessage"] @@ -527,6 +555,7 @@ class Task(models.Model): logger.info("Populated extent field with {} for {}".format(raster_path, self)) self.update_available_assets_field() + self.running_progress = 1.0 self.save() from app.plugins import signals as plugin_signals diff --git a/app/static/app/js/classes/Css.js b/app/static/app/js/classes/Css.js new file mode 100644 index 00000000..f7a13a3a --- /dev/null +++ b/app/static/app/js/classes/Css.js @@ -0,0 +1,16 @@ +const values = {}; +export default { + getValue: function(className, property, element = 'div'){ + const k = className + '|' + property; + if (values[k]) return values[k]; + else{ + let d = document.createElement(element); + d.style.display = "none"; + d.className = className; + document.body.appendChild(d); + values[k] = getComputedStyle(d)[property]; + document.body.removeChild(d); + return values[k]; + } + } +} \ No newline at end of file diff --git a/app/static/app/js/components/BasicTaskView.jsx b/app/static/app/js/components/BasicTaskView.jsx index bccaf157..f08e8a76 100644 --- a/app/static/app/js/components/BasicTaskView.jsx +++ b/app/static/app/js/components/BasicTaskView.jsx @@ -112,7 +112,7 @@ class BasicTaskView extends React.Component { } this.tearDownDynamicSource(); - this.setState({lines: [], currentRf: 0, loaded: false}); + this.setState({lines: [], currentRf: 0, loaded: false, rf: this.state.rf}); this.setupDynamicSource(); } @@ -122,9 +122,18 @@ class BasicTaskView extends React.Component { } componentDidUpdate(prevProps){ - let taskFailed = [StatusCodes.RUNNING, StatusCodes.QUEUED].indexOf(prevProps.taskStatus) !== -1 && - [StatusCodes.FAILED, StatusCodes.CANCELED].indexOf(this.props.taskStatus) !== -1; - this.updateRfState(taskFailed); + + let taskFailed; + let taskCompleted; + let taskRestarted; + + if (prevProps.taskStatus !== this.props.taskStatus){ + taskFailed = [StatusCodes.FAILED, StatusCodes.CANCELED].indexOf(this.props.taskStatus) !== -1; + taskCompleted = this.props.taskStatus === StatusCodes.COMPLETED; + taskRestarted = this.props.taskStatus === null; + } + + this.updateRfState(taskFailed, taskCompleted, taskRestarted); } componentWillUnmount(){ @@ -173,7 +182,7 @@ class BasicTaskView extends React.Component { if (this.props.onAddLines) this.props.onAddLines(lines); } - updateRfState(taskFailed){ + updateRfState(taskFailed, taskCompleted, taskRestarted){ // If the task has just failed, update all items that were either running or in queued state if (taskFailed){ this.state.rf.forEach(p => { @@ -181,9 +190,14 @@ class BasicTaskView extends React.Component { }); } - // The last is always dependent on the task status - this.state.rf[this.state.rf.length - 1].state = this.getInitialStatus(); + // If completed, all steps must have completed + if (taskCompleted){ + this.state.rf.forEach(p => p.state = 'completed'); + } + if (taskRestarted){ + this.state.rf.forEach(p => p.state = 'queued'); + } } suffixFor(state){ diff --git a/app/static/app/js/components/TaskListItem.jsx b/app/static/app/js/components/TaskListItem.jsx index 1770f966..c1c8eaee 100644 --- a/app/static/app/js/components/TaskListItem.jsx +++ b/app/static/app/js/components/TaskListItem.jsx @@ -11,6 +11,7 @@ import PropTypes from 'prop-types'; import TaskPluginActionButtons from './TaskPluginActionButtons'; import PipelineSteps from '../classes/PipelineSteps'; import BasicTaskView from './BasicTaskView'; +import Css from '../classes/Css'; class TaskListItem extends React.Component { static propTypes = { @@ -49,6 +50,10 @@ class TaskListItem extends React.Component { this.checkForCommonErrors = this.checkForCommonErrors.bind(this); this.handleEditTaskSave = this.handleEditTaskSave.bind(this); this.setView = this.setView.bind(this); + + // Retrieve CSS values for status bar colors + this.backgroundSuccessColor = Css.getValue('theme-background-success', 'backgroundColor'); + this.backgroundFailedColor = Css.getValue('theme-background-failed', 'backgroundColor'); } shouldRefresh(){ @@ -351,7 +356,7 @@ class TaskListItem extends React.Component { const name = task.name !== null ? task.name : `Task #${task.id}`; let status = statusCodes.description(task.status); - if (status === "") status = "Uploading images"; + if (status === "") status = "Uploading images to processing node"; if (!task.processing_node) status = "Waiting for a node..."; if (task.pending_action !== null) status = pendingActions.description(task.pending_action); @@ -464,10 +469,6 @@ class TaskListItem extends React.Component {