From 492fc565872b8a7edb339f4fb86deccee3be454f Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Wed, 24 Jan 2018 17:04:53 -0500 Subject: [PATCH] Restart dropdown, added task can_rerun_from field, fixed jest tests --- app/api/tasks.py | 20 +++++++++++ app/static/app/js/ModelView.jsx | 1 + app/static/app/js/components/EditTaskForm.jsx | 2 -- app/static/app/js/components/SharePopup.jsx | 1 + app/static/app/js/components/TaskList.jsx | 1 + app/static/app/js/components/TaskListItem.jsx | 34 +++++++++++++++---- .../components/tests/ClipboardInput.test.jsx | 4 +-- app/static/app/js/tests/setup/browserMock.js | 12 ++++++- app/static/app/js/tests/setup/setupTests.js | 5 +++ app/static/app/js/tests/setup/shims.js | 9 +++++ app/tests/test_api.py | 3 ++ jest.config.js | 4 ++- package.json | 5 +-- 13 files changed, 86 insertions(+), 15 deletions(-) create mode 100644 app/static/app/js/tests/setup/setupTests.js create mode 100644 app/static/app/js/tests/setup/shims.js diff --git a/app/api/tasks.py b/app/api/tasks.py index 617c1635..87d7b469 100644 --- a/app/api/tasks.py +++ b/app/api/tasks.py @@ -30,10 +30,30 @@ class TaskSerializer(serializers.ModelSerializer): project = serializers.PrimaryKeyRelatedField(queryset=models.Project.objects.all()) processing_node = serializers.PrimaryKeyRelatedField(queryset=ProcessingNode.objects.all()) images_count = serializers.SerializerMethodField() + can_rerun_from = serializers.SerializerMethodField() def get_images_count(self, obj): return obj.imageupload_set.count() + def get_can_rerun_from(self, obj): + """ + When a task has been associated with a processing node + and if the processing node supports the "rerun-from" parameter + this method returns the valid values for "rerun-from" for that particular + processing node. + + TODO: this could be improved by returning an empty array if a task was created + and purged by the processing node (which would require knowing how long a task is being kept + see https://github.com/OpenDroneMap/node-OpenDroneMap/issues/32 + :return: array of valid rerun-from parameters + """ + if obj.processing_node is not None: + rerun_from_option = list(filter(lambda d: 'name' in d and d['name'] == 'rerun-from', obj.processing_node.available_options)) + if len(rerun_from_option) > 0 and 'domain' in rerun_from_option[0]: + return rerun_from_option[0]['domain'] + + return [] + class Meta: model = models.Task exclude = ('processing_lock', 'console_output', 'orthophoto_extent', 'dsm_extent', 'dtm_extent', ) diff --git a/app/static/app/js/ModelView.jsx b/app/static/app/js/ModelView.jsx index 3153b6be..e11d7699 100644 --- a/app/static/app/js/ModelView.jsx +++ b/app/static/app/js/ModelView.jsx @@ -81,6 +81,7 @@ class ModelView extends React.Component { componentDidMount() { let container = this.container; + if (!container) return; // Enzyme tests don't have support for all WebGL methods so we just skip this window.viewer = new Potree.Viewer(container); viewer.setEDLEnabled(true); diff --git a/app/static/app/js/components/EditTaskForm.jsx b/app/static/app/js/components/EditTaskForm.jsx index c25297f2..080d9c16 100644 --- a/app/static/app/js/components/EditTaskForm.jsx +++ b/app/static/app/js/components/EditTaskForm.jsx @@ -163,7 +163,6 @@ class EditTaskForm extends React.Component { .fail((jqXHR, textStatus, errorThrown) => { // I don't expect this to fail, unless it's a development error or connection error. // in which case we don't need to notify the user directly. - console.error("Error retrieving processing nodes", jqXHR, textStatus); failed(); }); } @@ -256,7 +255,6 @@ class EditTaskForm extends React.Component { .fail((jqXHR, textStatus, errorThrown) => { // I don't expect this to fail, unless it's a development error or connection error. // in which case we don't need to notify the user directly. - console.error("Error retrieving processing nodes", jqXHR, textStatus); failed(); }); } diff --git a/app/static/app/js/components/SharePopup.jsx b/app/static/app/js/components/SharePopup.jsx index a267db3d..075910e4 100644 --- a/app/static/app/js/components/SharePopup.jsx +++ b/app/static/app/js/components/SharePopup.jsx @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'; import ErrorMessage from './ErrorMessage'; import Utils from '../classes/Utils'; import ClipboardInput from './ClipboardInput'; +import $ from 'jquery'; class SharePopup extends React.Component{ static propTypes = { diff --git a/app/static/app/js/components/TaskList.jsx b/app/static/app/js/components/TaskList.jsx index 853135c8..f1f489ed 100644 --- a/app/static/app/js/components/TaskList.jsx +++ b/app/static/app/js/components/TaskList.jsx @@ -2,6 +2,7 @@ import React from 'react'; import '../css/TaskList.scss'; import TaskListItem from './TaskListItem'; import PropTypes from 'prop-types'; +import $ from 'jquery'; class TaskList extends React.Component { static propTypes = { diff --git a/app/static/app/js/components/TaskListItem.jsx b/app/static/app/js/components/TaskListItem.jsx index 0e1f2757..f1cb37e5 100644 --- a/app/static/app/js/components/TaskListItem.jsx +++ b/app/static/app/js/components/TaskListItem.jsx @@ -241,6 +241,31 @@ class TaskListItem extends React.Component { this.setAutoRefresh(); } + getRestartSubmenuItems(task){ + // Map rerun-from parameters to display items + const rfMap = { + "odm_meshing": { + label: "Meshing", + icon: "fa fa-cube", + onClick: (cb) => { + console.log("mesh"); + } + }, + + "mvs_texturing": { + label: "Texturing", + icon: "fa fa-connectdevelop", + onClick: (cb) => { + console.log("tex"); + } + } + }; + + return task.can_rerun_from + .map(rf => rfMap[rf]) + .filter(rf => rf !== undefined); + } + render() { const task = this.state.task; const name = task.name !== null ? task.name : `Task #${task.id}`; @@ -297,6 +322,7 @@ class TaskListItem extends React.Component { if ([statusCodes.FAILED, statusCodes.COMPLETED, statusCodes.CANCELED].indexOf(task.status) !== -1 && task.processing_node){ + addActionButton("Restart", "btn-primary", "glyphicon glyphicon-repeat", this.genActionApiCall("restart", { success: () => { if (this.console) this.console.clear(); @@ -305,13 +331,7 @@ class TaskListItem extends React.Component { defaultError: "Cannot restart task." } ), { - subItems: [{ - label: "Meshing", - icon: "glyphicon glyphicon-remove-circle", - onClick: (cb) => { - console.log("OK"); - } - }] + subItems: this.getRestartSubmenuItems(task) }); } diff --git a/app/static/app/js/components/tests/ClipboardInput.test.jsx b/app/static/app/js/components/tests/ClipboardInput.test.jsx index 8c2e3159..a5d99e20 100644 --- a/app/static/app/js/components/tests/ClipboardInput.test.jsx +++ b/app/static/app/js/components/tests/ClipboardInput.test.jsx @@ -1,10 +1,10 @@ import React from 'react'; -import { shallow } from 'enzyme'; +import { mount } from 'enzyme'; import ClipboardInput from '../ClipboardInput'; describe('', () => { it('renders without exploding', () => { - const wrapper = shallow(); + const wrapper = mount(); expect(wrapper.exists()).toBe(true); }) }); \ No newline at end of file diff --git a/app/static/app/js/tests/setup/browserMock.js b/app/static/app/js/tests/setup/browserMock.js index bb68f9bd..0ea269a5 100644 --- a/app/static/app/js/tests/setup/browserMock.js +++ b/app/static/app/js/tests/setup/browserMock.js @@ -6,4 +6,14 @@ currentScript.src = "http://bogus"; Object.defineProperty(document, 'currentScript', { value: currentScript -}); \ No newline at end of file +}); + +// local storage mock +global.localStorage = { + _dict: {}, + getItem: (key) => global.localStorage._dict[key], + setItem: (key, value) => global.localStorage._dict[key] = value +} + +// Missing XMLHttpRequest methods +XMLHttpRequest.prototype.abort = () => {}; \ No newline at end of file diff --git a/app/static/app/js/tests/setup/setupTests.js b/app/static/app/js/tests/setup/setupTests.js new file mode 100644 index 00000000..20fba368 --- /dev/null +++ b/app/static/app/js/tests/setup/setupTests.js @@ -0,0 +1,5 @@ +import { configure } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; + +configure({ adapter: new Adapter() }); + diff --git a/app/static/app/js/tests/setup/shims.js b/app/static/app/js/tests/setup/shims.js new file mode 100644 index 00000000..9d6e0f9a --- /dev/null +++ b/app/static/app/js/tests/setup/shims.js @@ -0,0 +1,9 @@ +// Prevent warning on missing polyfill +global.requestAnimationFrame = function(callback) { + setTimeout(callback, 0); +}; + +import $ from 'jquery'; + +// Bootstrap polyfills +$.prototype.modal = () => {}; diff --git a/app/tests/test_api.py b/app/tests/test_api.py index b2d3cf7c..0828de2f 100644 --- a/app/tests/test_api.py +++ b/app/tests/test_api.py @@ -124,6 +124,9 @@ class TestApi(BootTestCase): # images_count field exists self.assertTrue(res.data["images_count"] == 0) + # TODO: test can_rerun_from + + # Get console output res = client.get('/api/projects/{}/tasks/{}/output/'.format(project.id, task.id)) self.assertEqual(res.status_code, status.HTTP_200_OK) diff --git a/jest.config.js b/jest.config.js index 49cd244d..01222a65 100644 --- a/jest.config.js +++ b/jest.config.js @@ -4,5 +4,7 @@ module.exports = { "^.*\\.s?css$": "/app/static/app/js/tests/mocks/empty.scss.js", "jquery": "/app/static/app/js/vendor/jquery-1.11.2.min.js" }, - setupFiles: ["/app/static/app/js/tests/setup/browserMock.js"] + setupFiles: ["/app/static/app/js/tests/setup/shims.js", + "/app/static/app/js/tests/setup/setupTests.js", + "/app/static/app/js/tests/setup/browserMock.js"] }; \ No newline at end of file diff --git a/package.json b/package.json index d7a6d1f5..99ab1723 100644 --- a/package.json +++ b/package.json @@ -32,10 +32,12 @@ "clipboard": "^1.7.1", "css-loader": "^0.25.0", "d3": "^3.5.5", - "enzyme": "^2.9.1", + "enzyme": "^3.3.0", + "enzyme-adapter-react-16": "^1.1.1", "extract-text-webpack-plugin": "^3.0.0", "file-loader": "^0.9.0", "gl-matrix": "^2.3.2", + "history": "^4.7.2", "immutability-helper": "^2.0.0", "jest": "^21.0.1", "json-loader": "^0.5.4", @@ -46,7 +48,6 @@ "proj4": "^2.4.3", "raw-loader": "^0.5.1", "react": "^16.2.0", - "react-addons-test-utils": "^15.6.0", "react-dom": "^16.2.0", "react-router": "^4.1.1", "react-router-dom": "^4.1.1",