diff --git a/qiita_pet/handlers/analysis_handlers/__init__.py b/qiita_pet/handlers/analysis_handlers/__init__.py index 366c5bb23..0c130e6b4 100644 --- a/qiita_pet/handlers/analysis_handlers/__init__.py +++ b/qiita_pet/handlers/analysis_handlers/__init__.py @@ -8,11 +8,11 @@ from .util import check_analysis_access from .base_handlers import (CreateAnalysisHandler, AnalysisDescriptionHandler, - AnalysisGraphHandler) + AnalysisGraphHandler, AnalysisJobsHandler) from .listing_handlers import (ListAnalysesHandler, AnalysisSummaryAJAX, SelectedSamplesHandler) __all__ = ['CreateAnalysisHandler', 'AnalysisDescriptionHandler', - 'AnalysisGraphHandler', 'ListAnalysesHandler', - 'AnalysisSummaryAJAX', 'SelectedSamplesHandler', - 'check_analysis_access'] + 'AnalysisGraphHandler', 'AnalysisJobsHandler', + 'ListAnalysesHandler', 'AnalysisSummaryAJAX', + 'SelectedSamplesHandler', 'check_analysis_access'] diff --git a/qiita_pet/handlers/analysis_handlers/base_handlers.py b/qiita_pet/handlers/analysis_handlers/base_handlers.py index bc5de4568..cc23847eb 100644 --- a/qiita_pet/handlers/analysis_handlers/base_handlers.py +++ b/qiita_pet/handlers/analysis_handlers/base_handlers.py @@ -98,8 +98,41 @@ def analyisis_graph_handler_get_request(analysis_id, user): class AnalysisGraphHandler(BaseHandler): @authenticated @execute_as_transaction - def get(self): - analysis_id = to_int(self.get_argument('analysis_id')) + def get(self, analysis_id): + analysis_id = to_int(analysis_id) response = analyisis_graph_handler_get_request( analysis_id, self.current_user) self.write(response) + + +def analyisis_job_handler_get_request(analysis_id, user): + """Returns the job information of the analysis + + Parameters + ---------- + analysis_id: int + The analysis id + user : qiita_db.user.User + The user performing the request + + Returns + ------- + dict with the jobs information + """ + analysis = Analysis(analysis_id) + # Check if the user actually has access to the analysis + check_analysis_access(user, analysis) + return { + j.id: {'status': j.status, 'step': j.step, + 'error': j.log.msg if j.log else ""} + for j in analysis.jobs} + + +class AnalysisJobsHandler(BaseHandler): + @authenticated + @execute_as_transaction + def get(self, analysis_id): + analysis_id = to_int(analysis_id) + response = analyisis_job_handler_get_request( + analysis_id, self.current_user) + self.write(response) diff --git a/qiita_pet/handlers/analysis_handlers/tests/test_base_handlers.py b/qiita_pet/handlers/analysis_handlers/tests/test_base_handlers.py index 485174878..1f0d31eb3 100644 --- a/qiita_pet/handlers/analysis_handlers/tests/test_base_handlers.py +++ b/qiita_pet/handlers/analysis_handlers/tests/test_base_handlers.py @@ -51,6 +51,11 @@ def test_analyisis_graph_handler_get_request(self): class TestBaseHandlers(TestHandlerBase): def test_post_create_analysis_handler(self): + user = User('test@foo.bar') + dflt_analysis = user.default_analysis + dflt_analysis.add_samples( + {4: ['1.SKB8.640193', '1.SKD8.640184', '1.SKB7.640196', + '1.SKM9.640192', '1.SKM4.640180']}) args = {'name': 'New Test Analysis', 'description': 'Test Analysis Description'} response = self.post('/analysis/create/', args) @@ -64,7 +69,7 @@ def test_get_analysis_description_handler(self): self.assertEqual(response.code, 200) def test_get_analysis_graph_handler(self): - response = self.get('/analysis/description/graph/', {'analysis_id': 1}) + response = self.get('/analysis/description/1/graph/') self.assertEqual(response.code, 200) # The job id is randomly generated in the test environment. Gather # it here. There is only 1 job in the first artifact of the analysis @@ -78,6 +83,23 @@ def test_get_analysis_graph_handler(self): self.assertItemsEqual(obs['edges'], exp['edges']) self.assertItemsEqual(obs['nodes'], exp['nodes']) + def test_get_analysis_jobs_handler(self): + user = User('test@foo.bar') + dflt_analysis = user.default_analysis + dflt_analysis.add_samples( + {4: ['1.SKB8.640193', '1.SKD8.640184', '1.SKB7.640196', + '1.SKM9.640192', '1.SKM4.640180']}) + new = Analysis.create(user, "newAnalysis", "A New Analysis", + from_default=True) + response = self.get('/analysis/description/%s/jobs/' % new.id) + self.assertEqual(response.code, 200) + + # There is only one job + job_id = new.jobs[0].id + obs = loads(response.body) + exp = {job_id: {'status': 'queued', 'step': None, 'error': ""}} + self.assertEqual(obs, exp) + if __name__ == '__main__': main() diff --git a/qiita_pet/templates/analysis_description.html b/qiita_pet/templates/analysis_description.html index 8d850248a..cf859e78d 100644 --- a/qiita_pet/templates/analysis_description.html +++ b/qiita_pet/templates/analysis_description.html @@ -77,37 +77,94 @@ } }; - /** - * Draws the aritfact + job graph in the `analysis-network-div` - * - */ - function populate_grap_div() { - // Put the loading gif in the div - show_loading('{% raw qiita_config.portal_dir %}', 'analysis-network-div'); - // Retrieve the information for the graph - $.get("{% raw qiita_config.portal_dir %}/analysis/description/graph/", { analysis_id: {{analysis_id}}}, function(data) { - var nodes = []; - var edges = []; - // Format edge list data - for(var i = 0; i < data.edges.length; i++) { - edges.push({from: data.edges[i][0], to: data.edges[i][1], arrows:'to'}); - } - // Format node list data - for(var i = 0; i < data.nodes.length; i++) { - nodes.push({id: data.nodes[i][1], label: data.nodes[i][2], group: data.nodes[i][0]}); + $(document).ready(function(){ + // Create the new VUE component that is going to hold the artifact + job graph + Vue.component('analysis-graph', { + template: '
', + props: ['nodes', 'edges'] + }); + + new Vue({ + el: "#analysis-graph-vue", + data: { + nodes: [], + edges: [] + }, + methods: { + update_graph: function () { + let vm = this; + $.get("{% raw qiita_config.portal_dir %}/analysis/description/" + {{analysis_id}} + "/graph/", function(data) { + // If there are no nodes in the graph, it means that we are waiting + // for the jobs to generate the initial set of artifacts. Update + // the job list + if (data.nodes.length == 0) { + vm.update_jobs(); + } + else { + // The initial set of artifacts has been created! Format the graph + // data in a way that Vis.Network likes it + // Format edge list data + for(var i = 0; i < data.edges.length; i++) { + vm.edges.push({from: data.edges[i][0], to: data.edges[i][1], arrows:'to'}); + } + // Format node list data + for(var i = 0; i < data.nodes.length; i++) { + vm.nodes.push({id: data.nodes[i][1], label: data.nodes[i][2], group: data.nodes[i][0]}); + } + draw_processing_graph(vm.nodes, vm.edges, 'analysis-network-div', populateContentArtifact, populateContentJob); + // At this point we can show the graph and hide the job list + $("#analysis-network-div").show(); + $("#analysis-job-div").hide(); + } + }) + .fail(function(object, status, error_msg) { + // Show an error message if something wrong happen, rather than + // leaving the spinning wheel of death in there. + $("#analysis-network-div").html("Error loading graph: " + status + " " + error_msg); + $("#analysis-network-div").show(); + $("#analysis-job-div").hide(); + } + ); + }, + update_jobs: function () { + let vm = this; + $.get("{% raw qiita_config.portal_dir %}/analysis/description/" + {{analysis_id}} + "/jobs/", function(data) { + $("#analysis-job-div").html(""); + $("#analysis-job-div").append("

Hang tight, we are generating the initial set of files for your analysis:

"); + for(var jobid in data){ + var contents = " Job: " + jobid + " Status: " + data[jobid]['status']; + // Only show step if error if they actually have a useful message + if (data[jobid]['step']) { + contents = contents + " Step: " + data[jobid]['step'] + "
"; + } + if (data[jobid]['error']) { + contents = contents + " Error: " + data[jobid]['error'] + "
"; + } + $("#analysis-job-div").append(contents); + } + }) + .fail(function(object, status, error_msg) { + $("#analysis-job-div").html("Error loading job information: " + status + " " + error_msg); + } + ); + } + }, + mounted() { + let vm = this; + show_loading('{% raw qiita_config.portal_dir %}', 'analysis-network-div'); + $("#analysis-network-div").hide(); + // This call to udpate graph will take care of updating the jobs + // if the graph is not available + vm.update_graph(); + setInterval(function() { + // Only update if the graph has not been generated yet + if (vm.nodes.length == 0) { + vm.update_graph(); + } + }, 5000); } - draw_processing_graph(nodes, edges, 'analysis-network-div', populateContentArtifact, populateContentJob); }) - .fail(function(object, status, error_msg) { - $("#analysis-network-div").html("Error loading graph: " + status + " " + error_msg); - } - ); - } - - $(document).ready(function(){ - populate_grap_div(); }); -