Skip to content
This repository was archived by the owner on Jan 28, 2020. It is now read-only.

Commit ae4b395

Browse files
author
George Schneeloch
committed
Implemented import status
1 parent 74914b8 commit ae4b395

File tree

8 files changed

+142
-9
lines changed

8 files changed

+142
-9
lines changed

importer/tests/test_forms.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import logging
77

8+
from django.contrib.sessions.backends.cache import SessionStore
89
from django.core.files.storage import default_storage
910
from django.core.files.uploadedfile import InMemoryUploadedFile
1011

@@ -36,7 +37,7 @@ def test_init(self):
3637
{"course_file": self.get_upload_file(".zip")},
3738
)
3839
self.assertTrue(form.is_valid())
39-
form.save(self.user.id, self.repo.id)
40+
form.save(self.user.id, self.repo.id, SessionStore())
4041

4142
def test_extensions(self):
4243
"""Only certain extensions are valid."""

rest/tasks.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
TASK_KEY = 'tasks'
1818
EXPORT_TASK_TYPE = 'resource_export'
1919
EXPORTS_KEY = 'learning_resource_exports'
20+
IMPORT_TASK_TYPE = 'import_course'
2021

2122

2223
def create_initial_task_dict(task, task_type, task_info):

ui/forms.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
from importer.tasks import import_file
1818
from learningresources.models import Repository
19+
from rest.tasks import track_task, IMPORT_TASK_TYPE
1920

2021
log = logging.getLogger(__name__)
2122

@@ -45,13 +46,14 @@ def clean_course_file(self):
4546
log.debug("got to end, so the file is bad")
4647
raise ValidationError("Unsupported file type.")
4748

48-
def save(self, user_id, repo_id):
49+
def save(self, user_id, repo_id, session):
4950
"""
5051
Receives the request.FILES from the view.
5152
5253
Args:
5354
user_id (int): primary key of the user uploading the course.
54-
repo_id (int): primary key of repository we're uploading to
55+
repo_id (int): primary key of repository we're uploading to.
56+
session (SessionStore): User's session to store task data.
5557
"""
5658
# Assumes a single file, because we only accept
5759
# one at a time.
@@ -66,7 +68,14 @@ def save(self, user_id, repo_id):
6668
),
6769
uploaded_file
6870
)
69-
import_file.delay(path, repo_id, user_id)
71+
task = import_file.delay(path, repo_id, user_id)
72+
73+
repo_slug = Repository.objects.get(id=repo_id).slug
74+
# Save task data in session so we can keep track of it.
75+
track_task(session, task, IMPORT_TASK_TYPE, {
76+
"repo_slug": repo_slug,
77+
"path": path
78+
})
7079

7180

7281
class RepositoryForm(ModelForm):

ui/static/ui/js/import_status.jsx

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
define("import_status", ["React", "utils"], function(React, Utils) {
2+
var ImportStatus = React.createClass({
3+
getInitialState: function() {
4+
return {
5+
importTasks: {},
6+
hasRefreshed: false
7+
};
8+
},
9+
updateImportStatus: function() {
10+
var thiz = this;
11+
12+
var oldImportTasks = this.state.importTasks;
13+
14+
Utils.getCollection("/api/v1/tasks/").then(function(tasks) {
15+
// Only deal with tasks on this repo that deal with imports.
16+
var tasksMap = {};
17+
_.each(tasks, function(task) {
18+
if (task.task_type === 'import_course' &&
19+
task.task_info.repo_slug === thiz.props.repoSlug) {
20+
tasksMap[task.id] = task;
21+
}
22+
});
23+
thiz.setState({importTasks: tasksMap});
24+
25+
var notProcessing = _.filter(tasks, function(task) {
26+
return task.status === 'success' || task.status === 'failure';
27+
});
28+
29+
var deferred = $.Deferred();
30+
31+
// Delete one after another to avoid race conditions with
32+
// Django session.
33+
_.each(notProcessing, function(task) {
34+
deferred.then(function() {
35+
return $.ajax({
36+
method: 'DELETE',
37+
url: '/api/v1/tasks/' + task.id + '/'
38+
});
39+
});
40+
});
41+
deferred.resolve();
42+
43+
var numProcessing = _.filter(tasks, function(task) {
44+
return task.status === 'processing';
45+
}).length;
46+
if (numProcessing > 0) {
47+
thiz.setState({hasRefreshed: true});
48+
setTimeout(thiz.updateImportStatus, 3000);
49+
} else {
50+
if (thiz.state.hasRefreshed) {
51+
// There were tasks and they're finished now so we should refresh.
52+
thiz.props.refreshFromAPI();
53+
}
54+
}
55+
});
56+
},
57+
componentDidMount: function() {
58+
this.updateImportStatus();
59+
},
60+
render: function() {
61+
var numSuccessful = _.filter(this.state.importTasks, function(task) {
62+
return task.status === 'success';
63+
}).length;
64+
var numProcessing = _.filter(this.state.importTasks, function(task) {
65+
return task.status === 'processing';
66+
}).length;
67+
68+
var importWord = function(n) {
69+
if (n === 1) {
70+
return "import";
71+
} else {
72+
return "imports";
73+
}
74+
};
75+
76+
var messages = [];
77+
if (numSuccessful !== 0) {
78+
messages.push(
79+
numSuccessful + ' ' + importWord(numSuccessful) + ' finished');
80+
}
81+
if (numProcessing !== 0) {
82+
messages.push(
83+
numProcessing + ' ' + importWord(numProcessing) +
84+
' still processing'
85+
);
86+
}
87+
88+
var failed = _.filter(this.state.importTasks, function(task) {
89+
return task.status === 'failure';
90+
});
91+
var errorMessages = [];
92+
_.each(failed, function(task) {
93+
errorMessages.push("Import failed: " + task.result.error);
94+
errorMessages.push(<br />);
95+
});
96+
97+
var messageString = '';
98+
if (messages.length > 0) {
99+
messageString = messages.join(", ") + ".";
100+
}
101+
102+
return <span>
103+
<p>{messageString}</p>
104+
<p>{errorMessages}</p>
105+
</span>;
106+
}
107+
});
108+
109+
return {
110+
ImportStatus: ImportStatus
111+
};
112+
});

ui/static/ui/js/listing.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
define('listing',
22
['csrf', 'react', 'jquery', 'lodash', 'uri', 'history', 'manage_taxonomies',
33
'learning_resources', 'static_assets', 'utils',
4-
'lr_exports', 'listing_resources', 'xml_panel',
4+
'lr_exports', 'listing_resources', 'xml_panel', 'import_status',
55
'bootstrap', 'icheck'],
66
function (CSRF, React, $, _, URI, History,
77
ManageTaxonomies, LearningResources, StaticAssets,
8-
Utils, Exports, ListingResources, XmlPanel) {
8+
Utils, Exports, ListingResources, XmlPanel, ImportStatusModule) {
99
'use strict';
1010

1111
var EMAIL_EXTENSION = '@mit.edu';
@@ -184,7 +184,13 @@ define('listing',
184184
facetCounts: this.state.facetCounts,
185185
ref: "listingResources"
186186
};
187-
return React.createElement(ListingResources.ListingPage, options);
187+
return React.DOM.div(null,
188+
React.createElement(ImportStatusModule.ImportStatus, {
189+
repoSlug: this.props.repoSlug,
190+
refreshFromAPI: this.refreshFromAPI
191+
}),
192+
React.createElement(ListingResources.ListingPage, options)
193+
);
188194
},
189195
/**
190196
* Clears exports on page. Assumes DELETE to clear on server already

ui/static/ui/js/require_config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ var REQUIRE_PATHS = {
55
bootstrap: "bootstrap/dist/js/bootstrap.min",
66
icheck: "icheck/icheck.min",
77
retina: "retina.js/dist/retina.min",
8-
React: "react/react-with-addons.min",
8+
React: "react/react-with-addons",
99
react_infinite: "react-infinite/dist/react-infinite.min",
1010
react_datagrid: "../lib/js/react-datagrid.min",
1111
lodash: "lodash/lodash.min",
@@ -21,6 +21,7 @@ var REQUIRE_PATHS = {
2121
listing: "../ui/js/listing",
2222
manage_taxonomies: "../ui/js/manage_taxonomies.jsx?noext",
2323
xml_panel: "../ui/js/xml_panel.jsx?noext",
24+
import_status: "../ui/js/import_status.jsx?noext",
2425
learning_resources: "../ui/js/learning_resources.jsx?noext",
2526
listing_resources: "../ui/js/listing_resources.jsx?noext",
2627
static_assets: "../ui/js/static_assets.jsx?noext",

ui/templates/repository.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@
6060
<script type="text/jsx"
6161
src="{% static "ui/js/xml_panel.jsx" %}">
6262
</script>
63+
<script type="text/jsx"
64+
src="{% static "ui/js/import_status.jsx" %}">
65+
</script>
6366
<script type="text/jsx"
6467
src="{% static "ui/js/pagination.jsx" %}">
6568
</script>

ui/views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def upload(request, repo_slug):
6363
data=request.POST, files=request.FILES)
6464
if form.is_valid():
6565
try:
66-
form.save(request.user.id, repo.id)
66+
form.save(request.user.id, repo.id, request.session)
6767
return redirect("/repositories/{0}/".format(repo_slug))
6868
except ValueError as ex:
6969
# Coverage exception reasoning: After successful upload,

0 commit comments

Comments
 (0)