diff --git a/qiita_pet/handlers/api_proxy/prep_template.py b/qiita_pet/handlers/api_proxy/prep_template.py index a3a5cc887..506162ded 100644 --- a/qiita_pet/handlers/api_proxy/prep_template.py +++ b/qiita_pet/handlers/api_proxy/prep_template.py @@ -299,7 +299,8 @@ def prep_template_summary_get_req(prep_id, user_id): Format {'status': status, 'message': message, 'num_samples': value, - 'category': [(val1, count1), (val2, count2), ...], ...} + 'category': [(val1, count1), (val2, count2), ...], + 'editable': bool} """ exists = _check_prep_template_exists(int(prep_id)) if exists['status'] != 'success': @@ -309,17 +310,21 @@ def prep_template_summary_get_req(prep_id, user_id): access_error = check_access(prep.study_id, user_id) if access_error: return access_error + + editable = Study(prep.study_id).can_edit(User(user_id)) df = prep.to_dataframe() out = {'num_samples': df.shape[0], - 'summary': {}, + 'summary': [], 'status': 'success', - 'message': ''} + 'message': '', + 'editable': editable} - cols = list(df.columns) + cols = sorted(list(df.columns)) for column in cols: counts = df[column].value_counts() - out['summary'][str(column)] = [(str(key), counts[key]) - for key in natsorted(counts.index)] + out['summary'].append( + (str(column), [(str(key), counts[key]) + for key in natsorted(counts.index)])) return out @@ -417,10 +422,11 @@ def prep_template_patch_req(user_id, req_op, req_path, req_value=None, Returns ------- - dict of {str, str} + dict of {str, str, str} A dictionary with the following keys: - status: str, whether if the request is successful or not - message: str, if the request is unsuccessful, a human readable error + - row_id: str, the row_id that we tried to delete """ req_path = [v for v in req_path.split('/') if v] if req_op == 'replace': @@ -458,13 +464,15 @@ def prep_template_patch_req(user_id, req_op, req_path, req_value=None, return {'status': status, 'message': msg} elif req_op == 'remove': - # The structure of the path should be /prep_id/{columns|samples}/name - if len(req_path) != 3: + # The structure of the path should be: + # /prep_id/row_id/{columns|samples}/name + if len(req_path) != 4: return {'status': 'error', 'message': 'Incorrect path parameter'} prep_id = int(req_path[0]) - attribute = req_path[1] - attr_id = req_path[2] + row_id = req_path[1] + attribute = req_path[2] + attr_id = req_path[3] # Check if the user actually has access to the study pt = PrepTemplate(prep_id) @@ -478,12 +486,13 @@ def prep_template_patch_req(user_id, req_op, req_path, req_value=None, # Store the job id attaching it to the sample template id r_client.set(PREP_TEMPLATE_KEY_FORMAT % prep_id, dumps({'job_id': job_id, 'is_qiita_job': False})) - return {'status': 'success', 'message': ''} + return {'status': 'success', 'message': '', 'row_id': row_id} else: return {'status': 'error', 'message': 'Operation "%s" not supported. ' 'Current supported operations: replace, remove' - % req_op} + % req_op, + 'row_id': '0'} def prep_template_samples_get_req(prep_id, user_id): diff --git a/qiita_pet/handlers/api_proxy/sample_template.py b/qiita_pet/handlers/api_proxy/sample_template.py index 0adeeeac6..e6bf953b2 100644 --- a/qiita_pet/handlers/api_proxy/sample_template.py +++ b/qiita_pet/handlers/api_proxy/sample_template.py @@ -521,13 +521,15 @@ def sample_template_patch_request(user_id, req_op, req_path, req_value=None, if req_op == 'remove': req_path = [v for v in req_path.split('/') if v] - if len(req_path) != 3: + # format: study_id/row_id/column|sample/attribute_id + if len(req_path) != 4: return {'status': 'error', 'message': 'Incorrect path parameter'} st_id = req_path[0] - attribute = req_path[1] - attr_id = req_path[2] + row_id = req_path[1] + attribute = req_path[2] + attr_id = req_path[3] # Check if the user actually has access to the template st = SampleTemplate(st_id) @@ -542,9 +544,10 @@ def sample_template_patch_request(user_id, req_op, req_path, req_value=None, r_client.set(SAMPLE_TEMPLATE_KEY_FORMAT % st_id, dumps({'job_id': job_id})) - return {'status': 'success', 'message': ''} + return {'status': 'success', 'message': '', 'row_id': row_id} else: return {'status': 'error', 'message': 'Operation "%s" not supported. ' - 'Current supported operations: remove' % req_op} + 'Current supported operations: remove' % req_op, + 'row_id': 0} diff --git a/qiita_pet/handlers/api_proxy/tests/test_prep_template.py b/qiita_pet/handlers/api_proxy/tests/test_prep_template.py index c33a1219f..9b825518f 100644 --- a/qiita_pet/handlers/api_proxy/tests/test_prep_template.py +++ b/qiita_pet/handlers/api_proxy/tests/test_prep_template.py @@ -206,40 +206,9 @@ def test_prep_template_graph_get_req_no_exists(self): def test_prep_template_summary_get_req(self): obs = prep_template_summary_get_req(1, 'test@foo.bar') - exp = {'summary': { - 'experiment_center': [('ANL', 27)], - 'center_name': [('ANL', 27)], - 'run_center': [('ANL', 27)], - 'run_prefix': [('s_G1_L001_sequences', 27)], - 'primer': [('GTGCCAGCMGCCGCGGTAA', 27)], - 'target_gene': [('16S rRNA', 27)], - 'sequencing_meth': [('Sequencing by synthesis', 27)], - 'run_date': [('8/1/12', 27)], - 'platform': [('Illumina', 27)], - 'pcr_primers': [('FWD:GTGCCAGCMGCCGCGGTAA; ' - 'REV:GGACTACHVGGGTWTCTAAT', 27)], - 'library_construction_protocol': [( - 'This analysis was done as in Caporaso et al 2011 Genome ' - 'research. The PCR primers (F515/R806) were developed against ' - 'the V4 region of the 16S rRNA (both bacteria and archaea), ' - 'which we determined would yield optimal community clustering ' - 'with reads of this length using a procedure similar to that ' - 'of ref. 15. [For reference, this primer pair amplifies the ' - 'region 533_786 in the Escherichia coli strain 83972 sequence ' - '(greengenes accession no. prokMSA_id:470367).] The reverse ' - 'PCR primer is barcoded with a 12-base error-correcting Golay ' - 'code to facilitate multiplexing of up to 1,500 samples per ' - 'lane, and both PCR primers contain sequencer adapter ' - 'regions.', 27)], - 'experiment_design_description': [( - 'micro biome of soil and rhizosphere of cannabis plants from ' - 'CA', 27)], - 'study_center': [('CCME', 27)], - 'center_project_name': [], - 'sample_center': [('ANL', 27)], - 'samp_size': [('.25,g', 27)], - 'qiita_prep_id': [('1', 27)], - 'barcode': [ + exp = { + 'status': 'success', 'message': '', + 'summary': [('barcode', [ ('AACTCCTGTGGA', 1), ('ACCTCAGTCAAG', 1), ('ACGCACATACAA', 1), ('AGCAGGCACGAA', 1), ('AGCGCTCACATC', 1), ('ATATCGCGATGA', 1), ('ATGGCCTGACTA', 1), ('CATACACGCACC', 1), ('CCACCCAGTAAC', 1), @@ -248,15 +217,44 @@ def test_prep_template_summary_get_req(self): ('CGTAGAGCTCTC', 1), ('CGTGCACAATTG', 1), ('GATAGCACTCGT', 1), ('GCGGACTATTCA', 1), ('GTCCGCAAGTTA', 1), ('TAATGGTCGTAG', 1), ('TAGCGCGAACTT', 1), ('TCGACCAAACAC', 1), ('TGAGTGGTCTGT', 1), - ('TGCTACAGACGT', 1), ('TGGTTATGGCAC', 1), ('TTGCACCGTCGA', 1)], - 'emp_status': [('EMP', 27)], - 'illumina_technology': [('MiSeq', 27)], - 'experiment_title': [('Cannabis Soil Microbiome', 27)], - 'target_subfragment': [('V4', 27)], - 'instrument_model': [('Illumina MiSeq', 27)]}, - 'num_samples': 27, - 'status': 'success', - 'message': ''} + ('TGCTACAGACGT', 1), ('TGGTTATGGCAC', 1), ('TTGCACCGTCGA', 1) + ]), ('center_name', [('ANL', 27)]), ('center_project_name', []), + ('emp_status', [('EMP', 27)]), + ('experiment_center', [('ANL', 27)]), + ('experiment_design_description', [ + ('micro biome of soil and rhizosphere of cannabis plants ' + 'from CA', 27)]), + ('experiment_title', [('Cannabis Soil Microbiome', 27)]), + ('illumina_technology', [('MiSeq', 27)]), + ('instrument_model', [('Illumina MiSeq', 27)]), + ('library_construction_protocol', [ + ('This analysis was done as in Caporaso et al 2011 Genome ' + 'research. The PCR primers (F515/R806) were developed ' + 'against the V4 region of the 16S rRNA (both bacteria ' + 'and archaea), which we determined would yield optimal ' + 'community clustering with reads of this length using a ' + 'procedure similar to that of ref. 15. [For reference, ' + 'this primer pair amplifies the region 533_786 in the ' + 'Escherichia coli strain 83972 sequence (greengenes ' + 'accession no. prokMSA_id:470367).] The reverse PCR ' + 'primer is barcoded with a 12-base error-correcting ' + 'Golay code to facilitate multiplexing of up to 1,500 ' + 'samples per lane, and both PCR primers contain ' + 'sequencer adapter regions.', 27)]), + ('pcr_primers', [( + 'FWD:GTGCCAGCMGCCGCGGTAA; REV:GGACTACHVGGGTWTCTAAT', 27)]), + ('platform', [('Illumina', 27)]), + ('primer', [('GTGCCAGCMGCCGCGGTAA', 27)]), + ('qiita_prep_id', [('1', 27)]), ('run_center', [('ANL', 27)]), + ('run_date', [('8/1/12', 27)]), + ('run_prefix', [('s_G1_L001_sequences', 27)]), + ('samp_size', [('.25,g', 27)]), + ('sample_center', [('ANL', 27)]), + ('sequencing_meth', [('Sequencing by synthesis', 27)]), + ('study_center', [('CCME', 27)]), + ('target_gene', [('16S rRNA', 27)]), + ('target_subfragment', [('V4', 27)])], + 'editable': True, 'num_samples': 27} self.assertEqual(obs, exp) def test_prep_template_summary_get_req_no_access(self): @@ -476,8 +474,8 @@ def test_prep_template_patch_req(self): # Delete a prep template column obs = prep_template_patch_req( 'test@foo.bar', 'remove', - '/%s/columns/target_subfragment/' % pt.id) - exp = {'status': 'success', 'message': ''} + '/%s/10/columns/target_subfragment/' % pt.id) + exp = {'status': 'success', 'message': '', 'row_id': '10'} self.assertEqual(obs, exp) self._wait_for_parallel_job('prep_template_%s' % pt.id) self.assertNotIn('target_subfragment', pt.categories()) @@ -489,7 +487,8 @@ def test_prep_template_patch_req(self): 'Cancer Genomics') exp = {'status': 'error', 'message': 'Operation "add" not supported. ' - 'Current supported operations: replace, remove'} + 'Current supported operations: replace, remove', + 'row_id': '0'} self.assertEqual(obs, exp) # Incorrect path parameter obs = prep_template_patch_req( diff --git a/qiita_pet/handlers/api_proxy/tests/test_sample_template.py b/qiita_pet/handlers/api_proxy/tests/test_sample_template.py index 4e6daadcc..d7f226189 100644 --- a/qiita_pet/handlers/api_proxy/tests/test_sample_template.py +++ b/qiita_pet/handlers/api_proxy/tests/test_sample_template.py @@ -525,28 +525,28 @@ def test_sample_template_meta_cats_get_req_no_template(self): def test_sample_template_patch_request(self): # Wrong operation operation obs = sample_template_patch_request( - "test@foo.bar", "add", "/1/columns/season_environment/") + "test@foo.bar", "add", "/1/10/columns/season_environment/") exp = {'status': 'error', 'message': 'Operation "add" not supported. ' - 'Current supported operations: remove'} + 'Current supported operations: remove', + 'row_id': 0} self.assertEqual(obs, exp) # Wrong path parameter obs = sample_template_patch_request( - "test@foo.bar", "remove", "/columns/season_environment/") + "test@foo.bar", "remove", "10/columns/season_environment/") exp = {'status': 'error', 'message': 'Incorrect path parameter'} self.assertEqual(obs, exp) # No access obs = sample_template_patch_request( - "demo@microbio.me", "remove", "/1/columns/season_environment/") + "demo@microbio.me", "remove", "/1/10/columns/season_environment/") exp = {'status': 'error', 'message': 'User does not have access to study'} self.assertEqual(obs, exp) # Success obs = sample_template_patch_request( - "test@foo.bar", "remove", "/1/columns/season_environment/") - exp = {'status': 'success', - 'message': ''} + "test@foo.bar", "remove", "/1/10/columns/season_environment/") + exp = {'status': 'success', 'message': '', 'row_id': '10'} self.assertEqual(obs, exp) # This is needed so the clean up works - this is a distributed system diff --git a/qiita_pet/handlers/study_handlers/prep_template.py b/qiita_pet/handlers/study_handlers/prep_template.py index 573ad36e5..78bc6dade 100644 --- a/qiita_pet/handlers/study_handlers/prep_template.py +++ b/qiita_pet/handlers/study_handlers/prep_template.py @@ -44,9 +44,11 @@ class PrepTemplateSummaryAJAX(BaseHandler): @authenticated def get(self): prep_id = to_int(self.get_argument('prep_id')) + res = prep_template_summary_get_req(prep_id, self.current_user.id) + self.render('study_ajax/prep_summary_table.html', pid=prep_id, - stats=res['summary']) + stats=res['summary'], editable=res['editable']) class PrepTemplateAJAX(BaseHandler): @@ -54,11 +56,14 @@ class PrepTemplateAJAX(BaseHandler): def get(self): """Send formatted summary page of prep template""" prep_id = to_int(self.get_argument('prep_id')) + row_id = self.get_argument('row_id', '0') res = prep_template_ajax_get_req(self.current_user.id, prep_id) res['prep_id'] = prep_id + res['row_id'] = row_id # Escape the message just in case javascript breaking characters in it res['alert_message'] = url_escape(res['alert_message']) + self.render('study_ajax/prep_summary.html', **res) diff --git a/qiita_pet/handlers/study_handlers/sample_template.py b/qiita_pet/handlers/study_handlers/sample_template.py index eb035260d..5387bd486 100644 --- a/qiita_pet/handlers/study_handlers/sample_template.py +++ b/qiita_pet/handlers/study_handlers/sample_template.py @@ -74,6 +74,8 @@ class SampleTemplateAJAX(BaseHandler): def get(self): """Send formatted summary page of sample template""" study_id = self.get_argument('study_id') + row_id = self.get_argument('row_id', '0') + files = [f for _, f in get_files_from_uploads_folders(study_id) if f.endswith(('txt', 'tsv'))] data_types = sorted(data_types_get_req()['data_types']) @@ -95,6 +97,7 @@ def get(self): stats['files'] = files stats['study_id'] = study_id stats['data_types'] = data_types + stats['row_id'] = row_id # URL encode in case message has javascript-breaking characters in it stats['alert_message'] = url_escape(stats['alert_message']) self.render('study_ajax/sample_summary.html', **stats) diff --git a/qiita_pet/templates/study_ajax/prep_summary.html b/qiita_pet/templates/study_ajax/prep_summary.html index eacaa726d..60e9af770 100644 --- a/qiita_pet/templates/study_ajax/prep_summary.html +++ b/qiita_pet/templates/study_ajax/prep_summary.html @@ -181,18 +181,18 @@ * prep information * */ - function delete_prep_column(prep_id, column_name) { + function delete_prep_column(prep_id, column_name, row_id) { if(confirm("Are you sure you want to delete '" + column_name + "' information?")) { $.ajax({ url: '{% raw qiita_config.portal_dir %}/prep_template/', type: 'PATCH', - data: {'op': 'remove', 'path': '/' + prep_id + '/columns/' + column_name}, + data: {'op': 'remove', 'path': '/' + prep_id + '/' + row_id + '/columns/' + column_name}, success: function(data) { if(data.status == 'error') { bootstrapAlert(data.message, "danger"); } else { - populate_main_div('/study/description/prep_template/', { prep_id: prep_id, study_id: {{study_id}} }); + populate_main_div('/study/description/prep_template/', { prep_id: prep_id, study_id: {{study_id}}, row_id: row_id }); } } }); @@ -329,6 +329,22 @@ } } + /* + * Autoscroll prep info list + * + * This is a helper function so we can scroll once the prep table is ready + */ + function autoscroll_prep_list() { + // taken from: http://stackoverflow.com/a/2906009 + if ({{row_id}} > 1) { + var container = $("html, body"), scrollTo = $("#row_{{row_id}}"); + + container.animate({ + scrollTop: scrollTo.offset().top - container.offset().top + container.scrollTop() + }); + } + } + $(document).ready(function () { if("{{investigation_type}}" !== "None") { // The prep information already has an investigation type @@ -402,6 +418,8 @@ {% else %} $('#alert-message').alert('close'); {% end %} + + }); diff --git a/qiita_pet/templates/study_ajax/prep_summary_table.html b/qiita_pet/templates/study_ajax/prep_summary_table.html index 34704cd83..024dafb57 100644 --- a/qiita_pet/templates/study_ajax/prep_summary_table.html +++ b/qiita_pet/templates/study_ajax/prep_summary_table.html @@ -1,3 +1,10 @@ + + + {% from future.utils import viewitems %}
@@ -5,10 +12,10 @@
- {% for category, summary in viewitems(stats) %} - + {% for i, (category, summary) in enumerate(stats, -1) %} + {% if len(summary) == 1 %}
- + diff --git a/qiita_pet/templates/study_ajax/sample_summary.html b/qiita_pet/templates/study_ajax/sample_summary.html index 628644514..b50bdc10a 100644 --- a/qiita_pet/templates/study_ajax/sample_summary.html +++ b/qiita_pet/templates/study_ajax/sample_summary.html @@ -18,18 +18,18 @@ * sample information * */ - function delete_column(column_name) { + function delete_column(column_name, row_id) { if(confirm("Are you sure you want to delete '" + column_name + "' information?")) { $.ajax({ url: '{% raw qiita_config.portal_dir %}/study/description/sample_template/', type: 'PATCH', - data: {'op': 'remove', 'path': '/{{study_id}}/columns/' + column_name}, + data: {'op': 'remove', 'path': '/{{study_id}}/' + row_id + '/columns/' + column_name}, success: function(data) { if(data.status == 'error') { bootstrapAlert(data.message, "danger"); } else { - populate_main_div('{% raw qiita_config.portal_dir %}/study/description/sample_template/', { study_id: {{study_id}} }); + populate_main_div('{% raw qiita_config.portal_dir %}/study/description/sample_template/', { study_id: {{study_id}}, row_id: row_id }); } } }); @@ -82,6 +82,14 @@ {% else %} $('#alert-message').alert('close'); {% end %} + + // taken from: http://stackoverflow.com/a/2906009 + if ({{row_id}} > 1) { + var container = $("html, body"), scrollTo = $("#row_{{row_id}}"); + container.animate({ + scrollTop: scrollTo.offset().top - container.offset().top + container.scrollTop() + }); + } }); @@ -145,11 +153,11 @@

- {% for i, (category, summary) in enumerate(viewitems(stats)) %} - + {% for i, (category, summary) in enumerate(viewitems(stats), -1) %} +
{% if editable %} - + {% else %}   {% end %}