diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0b7b522b..6ccb9e54 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,19 +8,36 @@ Changelog for the SODAR project. Loosely follows the Unreleased ========== +Added +----- + +- **Samplesheets** + User setting for study and assay table height (#1283) + Changed ------- - **General** - Upgrade to django-sodar-core v0.13.0 (#1617) - Upgrade to python-irodsclient v1.1.8 (#1538) +- **Samplesheets** + - Sample sheet table viewport background color (#1692) + - Contract sheet table height to fit content (#1693) Fixed ----- +- **Ontologyaccess** + - Batch import tests failing from forbidden obolibrary access (#1694) - **Samplesheets** - ``perform_project_sync()`` crash with no iRODS collections created (#1687) +Removed +------- + +- **Samplesheets** + - ``SHEETS_TABLE_HEIGHT`` Django setting (#1283) + v0.13.4 (2023-05-15) ==================== diff --git a/config/settings/base.py b/config/settings/base.py index edc64ac2..c4343ce8 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -748,8 +748,6 @@ def set_logging(level=None): SHEETS_ENABLE_CACHE = True # iRODS file query limit SHEETS_IRODS_LIMIT = env.int('SHEETS_IRODS_LIMIT', 50) -# Study/assay table height -SHEETS_TABLE_HEIGHT = env.int('SHEETS_TABLE_HEIGHT', 400) # Minimum edit config version SHEETS_CONFIG_VERSION = '0.8.0' # Min default column width diff --git a/docs_manual/source/admin_settings.rst b/docs_manual/source/admin_settings.rst index 4e36f3c2..f4499dd3 100644 --- a/docs_manual/source/admin_settings.rst +++ b/docs_manual/source/admin_settings.rst @@ -154,8 +154,6 @@ Sample Sheets Settings Allow critical altamISA warnings on import (boolean). ``SHEETS_IRODS_LIMIT`` iRODS file query limit (integer). -``SHEETS_TABLE_HEIGHT`` - Default study/assay table height. ``SHEETS_MIN_COLUMN_WIDTH`` Minimum default column width in study/assay tables (integer). ``SHEETS_MAX_COLUMN_WIDTH`` diff --git a/docs_manual/source/sodar_release_notes.rst b/docs_manual/source/sodar_release_notes.rst index 3655c2e9..cc5526a0 100644 --- a/docs_manual/source/sodar_release_notes.rst +++ b/docs_manual/source/sodar_release_notes.rst @@ -13,6 +13,8 @@ v0.14.0 (WIP) Major feature update. +- Add user setting for maximum sample sheet table height +- Contract sample sheet table height to fit content in browsing mode - Upgrade to SODAR Core v0.13.0 diff --git a/ontologyaccess/tests/test_io.py b/ontologyaccess/tests/test_io.py index a91c77ca..810a582e 100644 --- a/ontologyaccess/tests/test_io.py +++ b/ontologyaccess/tests/test_io.py @@ -3,8 +3,6 @@ import fastobo import os -from urllib.request import urlopen - from test_plus.test import TestCase from ontologyaccess.io import OBOFormatOntologyIO @@ -23,18 +21,6 @@ 'is_obsolete': 'EX:0000006', 'no_def': 'EX:0000007', } -OBO_BATCH_URLS = [ - 'http://purl.obolibrary.org/obo/hp.obo', - 'http://purl.obolibrary.org/obo/ms.obo', - 'http://purl.obolibrary.org/obo/pato.obo', - # 'http://purl.obolibrary.org/obo/cl.obo', # TODO: Fix (see #1064) - # TODO: Also see issue #944 -] -OWL_BATCH_URLS = [ - 'http://purl.obolibrary.org/obo/duo.owl', - 'http://data.bioontology.org/ontologies/ROLEO/submissions/3/download?' - 'apikey=8b5b7825-538d-40e0-9e9e-5ab9274a9aeb', -] class TestOBOFormatOntologyIO(TestCase): @@ -42,6 +28,7 @@ class TestOBOFormatOntologyIO(TestCase): def setUp(self): self.obo_io = OBOFormatOntologyIO() + self.req_headers = {'User-Agent': 'Mozilla'} def test_import(self): """Test importing an example ontology""" @@ -74,27 +61,7 @@ def test_import(self): term = ontology.get_term_by_id(EX_OBO_TERM_IDS['no_def']) self.assertIsNone(term.definition) - def test_import_batch(self): - """Test importing ontologies in a batch (this may take a while)""" - for url in OBO_BATCH_URLS: - self.assertEqual(OBOFormatOntology.objects.count(), 0) - self.assertEqual(OBOFormatOntologyTerm.objects.count(), 0) - - file_name = url.split('/')[-1] - obo_doc = fastobo.load(urlopen(url)) - ontology = self.obo_io.import_obo( - obo_doc=obo_doc, name=file_name.split('.')[0].upper(), file=url - ) - - self.assertIsNotNone(ontology, msg=file_name) - self.assertEqual( - OBOFormatOntology.objects.count(), 1, msg=file_name - ) - self.assertNotEqual( - OBOFormatOntologyTerm.objects.count(), 0, msg=file_name - ) - ontology.delete() - + ''' def test_import_batch_owl(self): """Test importing OWL ontologies in batch (this may take a while)""" for url in OWL_BATCH_URLS: @@ -116,5 +83,7 @@ def test_import_batch_owl(self): OBOFormatOntologyTerm.objects.count(), 0, msg=file_name ) ontology.delete() + ''' - # TODO: Test import_omim() + # TODO: Test importing OWL with a local file + # TODO: Test import_omim() with a local file diff --git a/samplesheets/plugins.py b/samplesheets/plugins.py index 6383e2f2..57030fa5 100644 --- a/samplesheets/plugins.py +++ b/samplesheets/plugins.py @@ -71,7 +71,6 @@ 'SHEETS_MAX_COLUMN_WIDTH', 'SHEETS_MIN_COLUMN_WIDTH', 'SHEETS_SYNC_INTERVAL', - 'SHEETS_TABLE_HEIGHT', 'SHEETS_VERSION_PAGINATION', 'SHEETS_IGV_OMIT_BAM', 'SHEETS_IGV_OMIT_VCF', @@ -173,6 +172,16 @@ class ProjectAppPlugin( 'source project', 'user_modifiable': True, }, + 'sheet_table_height': { + 'scope': SODAR_CONSTANTS['APP_SETTING_SCOPE_USER'], + 'type': 'INTEGER', + 'label': 'Sample sheet table height', + 'options': [250, 400, 600, 800], + 'default': 400, + 'description': 'Maximum display height for study and assay tables ' + 'in pixels', + 'user_modifiable': True, + }, 'public_access_ticket': { 'scope': SODAR_CONSTANTS['APP_SETTING_SCOPE_PROJECT'], 'type': 'STRING', diff --git a/samplesheets/tests/test_views_ajax.py b/samplesheets/tests/test_views_ajax.py index 773d29b2..671467d4 100644 --- a/samplesheets/tests/test_views_ajax.py +++ b/samplesheets/tests/test_views_ajax.py @@ -55,7 +55,12 @@ ) from samplesheets.utils import get_node_obj, get_ext_link_labels from samplesheets.views import SheetImportMixin -from samplesheets.views_ajax import ALERT_ACTIVE_REQS +from samplesheets.views_ajax import ( + ALERT_ACTIVE_REQS, + RENDER_HEIGHT_HEADERS, + RENDER_HEIGHT_ROW, + RENDER_HEIGHT_SCROLLBAR, +) conf_api = SheetConfigAPI() @@ -225,7 +230,6 @@ def test_get(self): 'external_link_labels': get_ext_link_labels(), 'ontology_url_template': settings.SHEETS_ONTOLOGY_URL_TEMPLATE, 'ontology_url_skip': settings.SHEETS_ONTOLOGY_URL_SKIP, - 'table_height': settings.SHEETS_TABLE_HEIGHT, 'min_col_width': settings.SHEETS_MIN_COLUMN_WIDTH, 'max_col_width': settings.SHEETS_MAX_COLUMN_WIDTH, 'allow_editing': app_settings.get_default( @@ -329,7 +333,6 @@ def test_no_sheets(self): 'irods_webdav_enabled': settings.IRODS_WEBDAV_ENABLED, 'irods_webdav_url': settings.IRODS_WEBDAV_URL, 'external_link_labels': None, - 'table_height': settings.SHEETS_TABLE_HEIGHT, 'min_col_width': settings.SHEETS_MIN_COLUMN_WIDTH, 'max_col_width': settings.SHEETS_MAX_COLUMN_WIDTH, 'allow_editing': app_settings.get_default( @@ -507,6 +510,21 @@ def test_get(self): self.assertNotIn('shortcuts', ret_data['tables']['study']) self.assertEqual(len(ret_data['tables']['assays']), 1) self.assertIn('uuid', ret_data['tables']['study']['table_data'][0][0]) + self.assertIn('table_heights', ret_data) + self.assertEqual( + ret_data['table_heights']['study'], + RENDER_HEIGHT_HEADERS + + len(ret_data['tables']['study']['table_data']) * RENDER_HEIGHT_ROW + + RENDER_HEIGHT_SCROLLBAR, + ) + a_uuid = str(self.assay.sodar_uuid) + self.assertEqual( + ret_data['table_heights']['assays'][a_uuid], + RENDER_HEIGHT_HEADERS + + len(ret_data['tables']['assays'][a_uuid]['table_data']) + * RENDER_HEIGHT_ROW + + RENDER_HEIGHT_SCROLLBAR, + ) self.assertIn('display_config', ret_data) self.assertNotIn('edit_context', ret_data) @@ -530,6 +548,15 @@ def test_get_edit(self): self.assertNotIn('shortcuts', ret_data['tables']['study']) self.assertEqual(len(ret_data['tables']['assays']), 1) self.assertIn('uuid', ret_data['tables']['study']['table_data'][0][0]) + self.assertIn('table_heights', ret_data) + default_height = app_settings.get( + APP_NAME, 'sheet_table_height', user=self.user + ) + self.assertEqual(ret_data['table_heights']['study'], default_height) + a_uuid = str(self.assay.sodar_uuid) + self.assertEqual( + ret_data['table_heights']['assays'][a_uuid], default_height + ) self.assertIn('display_config', ret_data) self.assertIn('study_config', ret_data) self.assertIn('edit_context', ret_data) @@ -557,7 +584,7 @@ def test_get_track_hubs(self): ) ) self.assertEqual(response.status_code, 200) - # TODO fill out ... assays are not yet tested, as well as shortcuts + # TODO: Assert track hub data def test_get_study_cache(self): """Test cached study table creation on retrieval""" diff --git a/samplesheets/views_ajax.py b/samplesheets/views_ajax.py index 10733964..67951120 100644 --- a/samplesheets/views_ajax.py +++ b/samplesheets/views_ajax.py @@ -69,6 +69,10 @@ 'label': 'extract_label', 'performer': 'performer', } +# Approximate magic numbers for header/row height +RENDER_HEIGHT_HEADERS = 79 +RENDER_HEIGHT_ROW = 39 +RENDER_HEIGHT_SCROLLBAR = 12 ALERT_ACTIVE_REQS = ( 'Active iRODS delete requests in this project require your attention. ' @@ -315,7 +319,6 @@ def get(self, request, *args, **kwargs): 'irods_webdav_enabled': settings.IRODS_WEBDAV_ENABLED, 'irods_webdav_url': get_webdav_url(project, request.user), 'external_link_labels': None, - 'table_height': settings.SHEETS_TABLE_HEIGHT, 'min_col_width': settings.SHEETS_MIN_COLUMN_WIDTH, 'max_col_width': settings.SHEETS_MAX_COLUMN_WIDTH, 'allow_editing': app_settings.get( @@ -498,12 +501,27 @@ def get(self, request, *args, **kwargs): class StudyTablesAjaxView(SODARBaseProjectAjaxView): """View to retrieve study tables built from the sample sheet graph""" - def get_permission_required(self): - """Override get_permisson_required() to provide the approrpiate perm""" - if bool(self.request.GET.get('edit')): - return 'samplesheets.edit_sheet' + def _get_table_height(self, table, user, edit): + """ + Return table height in pixels. - return 'samplesheets.view_sheet' + :param table: Study or assay render table + :param user: User object making the request + :param edit: Edit mode enabled (boolean) + :return: Integer + """ + default_height = app_settings.get( + APP_NAME, 'sheet_table_height', user=user + ) + if edit: # When editing we always set tables to fixed user setting + return default_height + # Else limit return value to user setting + return min( + RENDER_HEIGHT_HEADERS + + RENDER_HEIGHT_SCROLLBAR + + len(table['table_data']) * RENDER_HEIGHT_ROW, + default_height, + ) def _get_display_config(self, investigation, user, sheet_config=None): """Get or create display configuration for an investigation""" @@ -567,6 +585,12 @@ def _get_display_config(self, investigation, user, sheet_config=None): ) return display_config + def get_permission_required(self): + """Override get_permisson_required() to provide the approrpiate perm""" + if bool(self.request.GET.get('edit')): + return 'samplesheets.edit_sheet' + return 'samplesheets.view_sheet' + def get(self, request, *args, **kwargs): from samplesheets.plugins import get_irods_content @@ -608,6 +632,19 @@ def get(self, request, *args, **kwargs): ret_data['render_error'] = str(ex) return Response(ret_data, status=200) + # Add table heights + ret_data['table_heights'] = { + 'study': self._get_table_height( + ret_data['tables']['study'], request.user, edit + ), + 'assays': {}, + } + for k, v in ret_data['tables']['assays'].items(): + ret_data['table_heights']['assays'][k] = self._get_table_height( + v, request.user, edit + ) + # logger.debug('Table heights = {}'.format(ret_data['table_heights'])) + # Get iRODS content if NOT editing and collections have been created if not edit: logger.debug('Retrieving iRODS content for study..') @@ -639,7 +676,6 @@ def get(self, request, *args, **kwargs): 'samples': {}, 'protocols': [], } - # Add sample info s_assays = {} for assay in study.assays.all().order_by('pk'): @@ -660,7 +696,6 @@ def get(self, request, *args, **kwargs): if sample.unique_name in s_assays else [], } - # Add Protocol info for protocol in Protocol.objects.filter(study=study).order_by( 'name' diff --git a/samplesheets/vueapp/src/App.vue b/samplesheets/vueapp/src/App.vue index aae4194d..5c0a08d7 100644 --- a/samplesheets/vueapp/src/App.vue +++ b/samplesheets/vueapp/src/App.vue @@ -50,6 +50,7 @@ :grid-options="gridOptions.study" :grid-uuid="currentStudyUuid" :row-data="rowData.study" + :table-height="tableHeights.study" :initial-filter="initialFilter"> @@ -80,6 +81,7 @@ :grid-options="gridOptions.assays[assayUuid]" :grid-uuid="assayUuid" :row-data="rowData.assays[assayUuid]" + :table-height="tableHeights.assays[assayUuid]" :initial-filter="initialFilter"> @@ -271,6 +273,7 @@ export default { sampleColId: null, sampleIdx: null, sourceColSpan: null, + tableHeights: null, unsavedRow: null, // Info of currently unsaved row, or null if none updatingRow: false, // Row update in progress (bool) unsavedData: false, // Other updated data (bool) @@ -415,15 +418,15 @@ export default { if (this.editMode && 'study_config' in data) { this.editStudyConfig = data.study_config } - // Editing: Get edit context if (this.editMode && 'edit_context' in data) { this.editContext = data.edit_context } + // Get table heights + this.tableHeights = data.table_heights // Get display config this.studyDisplayConfig = data.display_config - // Store colspan this.sourceColSpan = data.tables.study.top_header[0].colspan @@ -1314,6 +1317,10 @@ export default { border: 0 !important; } +.ag-body-viewport { + background-color: #eee; +} + .ag-header-group-cell { border-right: 1px solid #dfdfdf !important; } diff --git a/samplesheets/vueapp/src/components/SheetTable.vue b/samplesheets/vueapp/src/components/SheetTable.vue index 7ecfdbb7..cbb8e11b 100644 --- a/samplesheets/vueapp/src/components/SheetTable.vue +++ b/samplesheets/vueapp/src/components/SheetTable.vue @@ -76,6 +76,7 @@ export default { 'gridOptions', 'gridUuid', 'rowData', + 'tableHeight', 'initialFilter' ], data () { @@ -109,7 +110,7 @@ export default { }, beforeMount () { this.cardClass = 'card sodar-ss-data-card sodar-ss-data-card-' - this.gridStyle = 'height: ' + this.app.sodarContext.table_height + 'px;' + this.gridStyle = 'height: ' + this.tableHeight + 'px;' if (!this.assayMode) { this.cardClass += 'study' this.gridName = 'Study' diff --git a/samplesheets/vueapp/tests/unit/data/sodarContext.json b/samplesheets/vueapp/tests/unit/data/sodarContext.json index 63fcceb1..e737ff2b 100644 --- a/samplesheets/vueapp/tests/unit/data/sodarContext.json +++ b/samplesheets/vueapp/tests/unit/data/sodarContext.json @@ -19,7 +19,6 @@ "ontology_url_template": "https://bioportal.bioontology.org/ontologies/{ontology_name}/?p=classes&conceptid={accession}", "ontology_url_skip": ["bioontology.org"] }, - "table_height": 400, "min_col_width": 100, "max_col_width": 300, "allow_editing": true, diff --git a/samplesheets/vueapp/tests/unit/data/sodarContextNoSheet.json b/samplesheets/vueapp/tests/unit/data/sodarContextNoSheet.json index d5ad239a..871b4feb 100644 --- a/samplesheets/vueapp/tests/unit/data/sodarContextNoSheet.json +++ b/samplesheets/vueapp/tests/unit/data/sodarContextNoSheet.json @@ -8,7 +8,6 @@ "irods_webdav_enabled": true, "irods_webdav_url": "http://davrods.local", "external_link_labels": null, - "table_height": 400, "min_col_width": 100, "max_col_width": 300, "allow_editing": false, diff --git a/samplesheets/vueapp/tests/unit/data/studyTables.json b/samplesheets/vueapp/tests/unit/data/studyTables.json index 560bf530..00f41b02 100644 --- a/samplesheets/vueapp/tests/unit/data/studyTables.json +++ b/samplesheets/vueapp/tests/unit/data/studyTables.json @@ -686,6 +686,12 @@ } } }, + "table_heights": { + "study": 400, + "assays": { + "22222222-2222-2222-2222-222222222222": 400 + } + }, "display_config": { "nodes": [ { diff --git a/samplesheets/vueapp/tests/unit/data/studyTablesEdit.json b/samplesheets/vueapp/tests/unit/data/studyTablesEdit.json index 88a04f15..6c661843 100644 --- a/samplesheets/vueapp/tests/unit/data/studyTablesEdit.json +++ b/samplesheets/vueapp/tests/unit/data/studyTablesEdit.json @@ -810,6 +810,12 @@ } } }, + "table_heights": { + "study": 400, + "assays": { + "22222222-2222-2222-2222-222222222222": 400 + } + }, "display_config": { "nodes": [ {