diff --git a/model/src/main/java/org/cbioportal/model/ClinicalDataCollection.java b/model/src/main/java/org/cbioportal/model/ClinicalDataCollection.java index 7e731104b59..a1ed029ef22 100644 --- a/model/src/main/java/org/cbioportal/model/ClinicalDataCollection.java +++ b/model/src/main/java/org/cbioportal/model/ClinicalDataCollection.java @@ -5,9 +5,16 @@ public class ClinicalDataCollection { + /** + * Paginated resource + */ private List sampleClinicalData = new ArrayList<>(); - private List patientClinicalData = new ArrayList<>(); + /** + * Patient info associated with paginated samples + */ + private List patientClinicalData = new ArrayList<>(); + public List getSampleClinicalData() { return sampleClinicalData; } diff --git a/persistence/persistence-api/src/main/java/org/cbioportal/persistence/ClinicalDataRepository.java b/persistence/persistence-api/src/main/java/org/cbioportal/persistence/ClinicalDataRepository.java index 0b9f6bf4093..2ff7c09087f 100644 --- a/persistence/persistence-api/src/main/java/org/cbioportal/persistence/ClinicalDataRepository.java +++ b/persistence/persistence-api/src/main/java/org/cbioportal/persistence/ClinicalDataRepository.java @@ -47,9 +47,13 @@ BaseMeta fetchMetaClinicalDataInStudy(String studyId, List ids, List fetchClinicalData(List studyIds, List ids, List attributeIds, String clinicalDataType, String projection); - List fetchSampleClinicalDataClinicalTable(List studyIds, List ids, - Integer pageSize, Integer pageNumber, String searchTerm, - String sortBy, String direction); + List fetchSampleClinicalTable(List studyIds, List ids, + Integer pageSize, Integer pageNumber, String searchTerm, + String sortBy, String direction); + + @Cacheable(cacheResolver = "generalRepositoryCacheResolver", condition = "@cacheEnabledConfig.getEnabled()") + Integer fetchSampleClinicalTableCount(List studyIds, List ids, String searchTerm, + String sortBy, String direction); @Cacheable(cacheResolver = "generalRepositoryCacheResolver", condition = "@cacheEnabledConfig.getEnabled()") BaseMeta fetchMetaClinicalData(List studyIds, List ids, List attributeIds, diff --git a/persistence/persistence-mybatis/src/main/java/org/cbioportal/persistence/mybatis/ClinicalDataMapper.java b/persistence/persistence-mybatis/src/main/java/org/cbioportal/persistence/mybatis/ClinicalDataMapper.java index 77f92028796..1f4209deffb 100644 --- a/persistence/persistence-mybatis/src/main/java/org/cbioportal/persistence/mybatis/ClinicalDataMapper.java +++ b/persistence/persistence-mybatis/src/main/java/org/cbioportal/persistence/mybatis/ClinicalDataMapper.java @@ -18,9 +18,12 @@ List getPatientClinicalData(List studyIds, List pa String projection, Integer limit, Integer offset, String sortby, String direction); - List getSampleClinicalDataClinicalTable(List studyIds, List sampleIds, String projection, - Integer limit, Integer offset, String searchTerm, - String sortBy, String direction); + List getSampleClinicalTable(List studyIds, List sampleIds, String projection, + Integer limit, Integer offset, String searchTerm, + String sortBy, String direction); + + Integer getSampleClinicalTableCount(List studyIds, List sampleIds, String projection, + String searchTerm, String sortBy, String direction); BaseMeta getMetaPatientClinicalData(List studyIds, List patientIds, List attributeIds); @@ -33,5 +36,4 @@ List fetchPatientClinicalDataCounts(List studyIds, Li List getPatientClinicalDataDetailedToSample(List studyIds, List patientIds, List attributeIds, String projection, Integer limit, Integer offset, String sortBy, String direction); - } diff --git a/persistence/persistence-mybatis/src/main/java/org/cbioportal/persistence/mybatis/ClinicalDataMyBatisRepository.java b/persistence/persistence-mybatis/src/main/java/org/cbioportal/persistence/mybatis/ClinicalDataMyBatisRepository.java index 6725e94957e..15c692f84bc 100644 --- a/persistence/persistence-mybatis/src/main/java/org/cbioportal/persistence/mybatis/ClinicalDataMyBatisRepository.java +++ b/persistence/persistence-mybatis/src/main/java/org/cbioportal/persistence/mybatis/ClinicalDataMyBatisRepository.java @@ -138,17 +138,24 @@ public List fetchClinicalData(List studyIds, List } @Override - public List fetchSampleClinicalDataClinicalTable(List studyIds, List ids, - Integer pageSize, Integer pageNumber, String searchTerm, - String sortBy, String direction) { + public List fetchSampleClinicalTable(List studyIds, List ids, + Integer pageSize, Integer pageNumber, String searchTerm, + String sortBy, String direction) { if (ids.isEmpty()) { return new ArrayList<>(); } int offset = offsetCalculator.calculate(pageSize, pageNumber); - return clinicalDataMapper.getSampleClinicalDataClinicalTable(studyIds, ids,"SUMMARY", pageSize, + return clinicalDataMapper.getSampleClinicalTable(studyIds, ids,"SUMMARY", pageSize, offset, searchTerm, sortBy, direction); } + @Override + public Integer fetchSampleClinicalTableCount(List studyIds, List sampleIds, + String searchTerm, String sortBy, String direction) { + return clinicalDataMapper.getSampleClinicalTableCount(studyIds, sampleIds,"SUMMARY", + searchTerm, sortBy, direction); + } + @Override public BaseMeta fetchMetaClinicalData(List studyIds, List ids, List attributeIds, String clinicalDataType) { diff --git a/persistence/persistence-mybatis/src/main/resources/org/cbioportal/persistence/mybatis/ClinicalDataMapper.xml b/persistence/persistence-mybatis/src/main/resources/org/cbioportal/persistence/mybatis/ClinicalDataMapper.xml index d9af9df72e2..29e6cd536c5 100644 --- a/persistence/persistence-mybatis/src/main/resources/org/cbioportal/persistence/mybatis/ClinicalDataMapper.xml +++ b/persistence/persistence-mybatis/src/main/resources/org/cbioportal/persistence/mybatis/ClinicalDataMapper.xml @@ -47,6 +47,11 @@ INNER JOIN cancer_study ON patient.CANCER_STUDY_ID = cancer_study.CANCER_STUDY_ID + + + LEFT JOIN clinical_patient ON clinical_patient.INTERNAL_ID = patient.INTERNAL_ID + + FROM clinical_patient INNER JOIN patient ON clinical_patient.INTERNAL_ID = patient.INTERNAL_ID @@ -221,7 +226,7 @@ GROUP BY clinical_patient.ATTR_ID, clinical_patient.ATTR_VALUE - SELECT @@ -230,28 +235,55 @@ + + - INNER JOIN - (SELECT DISTINCT clinical_sample.INTERNAL_ID + INNER JOIN ( + SELECT DISTINCT clinical_sample.INTERNAL_ID - , clinical_sample.ATTR_ID, clinical_sample.ATTR_VALUE + + , sample.STABLE_ID AS SORT_BY + + + , patient.STABLE_ID AS SORT_BY + + + + + , CASE + WHEN clinical_sample.ATTR_ID = #{sortBy} THEN clinical_sample.ATTR_VALUE + WHEN clinical_patient.ATTR_ID = #{sortBy} THEN clinical_patient.ATTR_VALUE + ELSE NULL + END AS SORT_BY + - + AND - clinical_sample.ATTR_VALUE LIKE CONCAT('%','${searchTerm}', '%') + clinical_sample.ATTR_VALUE LIKE CONCAT('%', #{searchTerm}, '%') - AND - clinical_sample.ATTR_ID = '${sortBy}' - ORDER BY clinical_sample.ATTR_VALUE ${direction} + + + + ORDER BY ISNULL(SORT_BY), SORT_BY ${direction} + + + ORDER BY SORT_BY ${direction} NULLS LAST + + + LIMIT #{limit} OFFSET #{offset} - ) as sample_id_subquery - ON clinical_sample.INTERNAL_ID = sample_id_subquery.INTERNAL_ID + ) AS sample_id_subquery + ON clinical_sample.INTERNAL_ID = sample_id_subquery.INTERNAL_ID diff --git a/persistence/persistence-mybatis/src/test/java/org/cbioportal/persistence/mybatis/ClinicalDataMyBatisRepositoryTest.java b/persistence/persistence-mybatis/src/test/java/org/cbioportal/persistence/mybatis/ClinicalDataMyBatisRepositoryTest.java index ded95f04cf3..406e830c8fb 100644 --- a/persistence/persistence-mybatis/src/test/java/org/cbioportal/persistence/mybatis/ClinicalDataMyBatisRepositoryTest.java +++ b/persistence/persistence-mybatis/src/test/java/org/cbioportal/persistence/mybatis/ClinicalDataMyBatisRepositoryTest.java @@ -354,9 +354,9 @@ public void fetchClinicalDataNullAttributeSummaryProjection() { @Test public void fetchClinicalSampleDataClinicalTabPagingSuccess() { - List resultFirstPage = clinicalDataMyBatisRepository.fetchSampleClinicalDataClinicalTable(studyIds, + List resultFirstPage = clinicalDataMyBatisRepository.fetchSampleClinicalTable(studyIds, sampleIds, 1, 0, noSearch, noSort, "DESC"); - List resultSecondPage = clinicalDataMyBatisRepository.fetchSampleClinicalDataClinicalTable(studyIds, + List resultSecondPage = clinicalDataMyBatisRepository.fetchSampleClinicalTable(studyIds, sampleIds, 1, 1, noSearch, noSort, "DESC"); Assert.assertEquals(4, resultFirstPage.size()); @@ -380,7 +380,7 @@ public void fetchClinicalSampleDataClinicalTabPagingSuccess() { public void fetchClinicalSampleDataClinicalTablePagingHandleNoneExistingPage() { // There are only two patients in total. The second page (index 1) with pageSize 2 does not refer to any records. - List resultNonExistingPage = clinicalDataMyBatisRepository.fetchSampleClinicalDataClinicalTable( + List resultNonExistingPage = clinicalDataMyBatisRepository.fetchSampleClinicalTable( studyIds, sampleIds, 2, 1, noSearch, noSort, "DESC"); Assert.assertEquals(0, resultNonExistingPage.size()); @@ -389,7 +389,7 @@ public void fetchClinicalSampleDataClinicalTablePagingHandleNoneExistingPage() { @Test public void fetchClinicalSampleDataClinicalTabSearchTermSuccess() { - List resultSample1 = clinicalDataMyBatisRepository.fetchSampleClinicalDataClinicalTable( + List resultSample1 = clinicalDataMyBatisRepository.fetchSampleClinicalTable( studyIds, sampleIds, noPaging, noPaging, "5C631CE8", noSort, "DESC"); Assert.assertEquals(4, resultSample1.size()); @@ -397,7 +397,7 @@ public void fetchClinicalSampleDataClinicalTabSearchTermSuccess() { Assert.assertEquals(1, observedSampleIds.size()); Assert.assertEquals("TCGA-A1-A0SB-01", observedSampleIds.get(0)); - List resultSample2 = clinicalDataMyBatisRepository.fetchSampleClinicalDataClinicalTable( + List resultSample2 = clinicalDataMyBatisRepository.fetchSampleClinicalTable( studyIds, sampleIds, noPaging, noPaging, "F3408556-9259", noSort, "DESC"); Assert.assertEquals(4, resultSample2.size()); @@ -409,7 +409,7 @@ public void fetchClinicalSampleDataClinicalTabSearchTermSuccess() { @Test public void fetchClinicalSampleDataEClinicalTabEmptyStringSearchTerm() { - List result = clinicalDataMyBatisRepository.fetchSampleClinicalDataClinicalTable( + List result = clinicalDataMyBatisRepository.fetchSampleClinicalTable( studyIds, sampleIds, noPaging, noPaging, "", noSort, "DESC"); Assert.assertEquals(8, result.size()); @@ -418,9 +418,9 @@ public void fetchClinicalSampleDataEClinicalTabEmptyStringSearchTerm() { @Test public void fetchClinicalSampleDataClinicalTabSortSuccess() { - List resultSortAsc = clinicalDataMyBatisRepository.fetchSampleClinicalDataClinicalTable(studyIds, + List resultSortAsc = clinicalDataMyBatisRepository.fetchSampleClinicalTable(studyIds, sampleIds, 1, 0, noSearch, "SAMPLE_TYPE", "ASC"); - List resultSortDesc = clinicalDataMyBatisRepository.fetchSampleClinicalDataClinicalTable(studyIds, + List resultSortDesc = clinicalDataMyBatisRepository.fetchSampleClinicalTable(studyIds, sampleIds, 1, 0, noSearch, "SAMPLE_TYPE", "DESC"); Assert.assertEquals(4, resultSortAsc.size()); diff --git a/service/src/main/java/org/cbioportal/service/ClinicalDataService.java b/service/src/main/java/org/cbioportal/service/ClinicalDataService.java index 3ba05c8f784..972913ce2e4 100644 --- a/service/src/main/java/org/cbioportal/service/ClinicalDataService.java +++ b/service/src/main/java/org/cbioportal/service/ClinicalDataService.java @@ -52,6 +52,9 @@ BaseMeta fetchMetaClinicalData(List studyIds, List ids, List getPatientClinicalDataDetailedToSample(List studyIds, List patientIds, List attributeIds); - List fetchSampleClinicalDataClinicalTable(List studyIds, List sampleIds, Integer pageSize, - Integer pageNumber, String searchTerm, String sortBy, String direction); + List fetchSampleClinicalTable(List studyIds, List sampleIds, Integer pageSize, + Integer pageNumber, String searchTerm, String sortBy, String direction); + + Integer fetchSampleClinicalTableCount(List studyIds, List sampleIds, String searchTerm, + String sortBy, String direction); } diff --git a/service/src/main/java/org/cbioportal/service/impl/ClinicalDataServiceImpl.java b/service/src/main/java/org/cbioportal/service/impl/ClinicalDataServiceImpl.java index 1d786d09b55..a4d41a8f343 100644 --- a/service/src/main/java/org/cbioportal/service/impl/ClinicalDataServiceImpl.java +++ b/service/src/main/java/org/cbioportal/service/impl/ClinicalDataServiceImpl.java @@ -1,6 +1,5 @@ package org.cbioportal.service.impl; -import org.apache.commons.collections4.list.UnmodifiableList; import org.cbioportal.model.*; import org.cbioportal.model.meta.BaseMeta; import org.cbioportal.persistence.ClinicalDataRepository; @@ -228,10 +227,17 @@ public List getPatientClinicalDataDetailedToSample(List st } @Override - public List fetchSampleClinicalDataClinicalTable(List studyIds, List sampleIds, Integer pageSize, - Integer pageNumber, String searchTerm, String sortBy, - String direction) { - return clinicalDataRepository.fetchSampleClinicalDataClinicalTable(studyIds, sampleIds, pageSize, pageNumber, searchTerm, + public List fetchSampleClinicalTable(List studyIds, List sampleIds, Integer pageSize, + Integer pageNumber, String searchTerm, String sortBy, + String direction) { + return clinicalDataRepository.fetchSampleClinicalTable(studyIds, sampleIds, pageSize, pageNumber, searchTerm, sortBy, direction); } + + @Override + public Integer fetchSampleClinicalTableCount(List studyIds, List sampleIds, + String searchTerm, String sortBy, String direction) { + return clinicalDataRepository.fetchSampleClinicalTableCount(studyIds, sampleIds, searchTerm, + sortBy, direction); + } } diff --git a/web/src/main/java/org/cbioportal/web/StudyViewController.java b/web/src/main/java/org/cbioportal/web/StudyViewController.java index fa1fd12f046..3216dd5054a 100644 --- a/web/src/main/java/org/cbioportal/web/StudyViewController.java +++ b/web/src/main/java/org/cbioportal/web/StudyViewController.java @@ -13,16 +13,14 @@ import org.cbioportal.service.*; import org.cbioportal.service.exception.StudyNotFoundException; import org.cbioportal.service.util.ClinicalAttributeUtil; -import org.cbioportal.service.util.CustomDataSession; import org.cbioportal.web.config.annotation.InternalApi; import org.cbioportal.model.AlterationFilter; import org.cbioportal.web.parameter.*; -import org.cbioportal.web.parameter.sort.ClinicalDataSortBy; -import org.cbioportal.web.studyview.CustomDataController; import org.cbioportal.web.util.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.Cacheable; import org.springframework.context.ApplicationContext; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -887,39 +885,56 @@ public ResponseEntity> fetchGenericAssayDataBinCounts( @ApiOperation("Fetch clinical data for the Clinical Tab of Study View") public ResponseEntity fetchClinicalDataClinicalTable( @ApiParam(required = true, value = "Study view filter") - @Valid @RequestBody(required = false) StudyViewFilter studyViewFilter, + @Valid @RequestBody(required = false) + StudyViewFilter studyViewFilter, @ApiIgnore // prevent reference to this attribute in the swagger-ui interface - @RequestAttribute(required = false, value = "involvedCancerStudies") Collection involvedCancerStudies, + @RequestAttribute(required = false, value = "involvedCancerStudies") + Collection involvedCancerStudies, @ApiIgnore // prevent reference to this attribute in the swagger-ui interface. this attribute is needed for the @PreAuthorize tag above. - @Valid @RequestAttribute(required = false, value = "interceptedStudyViewFilter") StudyViewFilter interceptedStudyViewFilter, + @Valid @RequestAttribute(required = false, value = "interceptedStudyViewFilter") + StudyViewFilter interceptedStudyViewFilter, @ApiParam("Page size of the result list") @Max(CLINICAL_TAB_MAX_PAGE_SIZE) @Min(PagingConstants.NO_PAGING_PAGE_SIZE) - @RequestParam(defaultValue = PagingConstants.DEFAULT_NO_PAGING_PAGE_SIZE) Integer pageSize, + @RequestParam(defaultValue = PagingConstants.DEFAULT_NO_PAGING_PAGE_SIZE) + Integer pageSize, @ApiParam("Page number of the result list") @Min(PagingConstants.MIN_PAGE_NUMBER) - @RequestParam(defaultValue = PagingConstants.DEFAULT_PAGE_NUMBER) Integer pageNumber, + @RequestParam(defaultValue = PagingConstants.DEFAULT_PAGE_NUMBER) + Integer pageNumber, @ApiParam("Search term to filter sample rows. Samples are returned " + "with a partial match to the search term for any sample clinical attribute.") - @RequestParam(defaultValue = "") String searchTerm, - @ApiParam("Name of the property that the result list is sorted by") - @RequestParam(required = false) ClinicalDataSortBy sortBy, + @RequestParam(defaultValue = "") + String searchTerm, + @ApiParam(value = "sampleId, patientId, or the ATTR_ID to sorted by") + @RequestParam(required = false) + // TODO: Can we narrow down this string to a specific enum? + String sortBy, @ApiParam("Direction of the sort") - @RequestParam(defaultValue = "ASC") Direction direction) { - + @RequestParam(defaultValue = "ASC") + Direction direction + ) { List sampleStudyIds = new ArrayList<>(); List sampleIds = new ArrayList<>(); List filteredSampleIdentifiers = studyViewFilterApplier.apply(interceptedStudyViewFilter); studyViewFilterUtil.extractStudyAndSampleIds(filteredSampleIdentifiers, sampleStudyIds, sampleIds); - List sampleClinicalData = clinicalDataService.fetchSampleClinicalDataClinicalTable( + List sampleClinicalData = clinicalDataService.fetchSampleClinicalTable( sampleStudyIds, sampleIds, pageSize, pageNumber, searchTerm, - sortBy != null ? sortBy.getOriginalValue() : null, - direction.name()); + sortBy, + direction.name() + ); + Integer total = clinicalDataService.fetchSampleClinicalTableCount( + sampleStudyIds, + sampleIds, + searchTerm, + sortBy, + direction.name() + ); // Return empty when possible. if (sampleClinicalData.isEmpty()) { @@ -942,8 +957,10 @@ public ResponseEntity fetchClinicalDataClinicalTable( final ClinicalDataCollection clinicalDataCollection = new ClinicalDataCollection(); clinicalDataCollection.setSampleClinicalData(sampleClinicalData); clinicalDataCollection.setPatientClinicalData(patientClinicalData); - - return new ResponseEntity<>(clinicalDataCollection, HttpStatus.OK); + + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.add(HeaderKeyConstants.TOTAL_COUNT, total.toString()); + return new ResponseEntity<>(clinicalDataCollection, responseHeaders, HttpStatus.OK); } @PreAuthorize("hasPermission(#involvedCancerStudies, 'Collection', T(org.cbioportal.utils.security.AccessLevel).READ)") diff --git a/web/src/test/java/org/cbioportal/web/StudyViewControllerTest.java b/web/src/test/java/org/cbioportal/web/StudyViewControllerTest.java index dcafee1fae7..027b928a951 100644 --- a/web/src/test/java/org/cbioportal/web/StudyViewControllerTest.java +++ b/web/src/test/java/org/cbioportal/web/StudyViewControllerTest.java @@ -765,9 +765,8 @@ public void fetchGenericAssayDataCounts() throws Exception { @Test public void fetchClinicalDataClinicalTable() throws Exception { - // For this sake of this test the sample clinical data and patient clinical data are identical. - when(clinicalDataService.fetchSampleClinicalDataClinicalTable(anyList(), anyList(), + when(clinicalDataService.fetchSampleClinicalTable(anyList(), anyList(), anyInt(), anyInt(), anyString(), any(), anyString())).thenReturn(clinicalData); when(clinicalDataService.fetchClinicalData(anyList(), anyList(), any(), anyString(), anyString())).thenReturn(clinicalData);