From 60d3904a660a37e2cae6f2935756b8435d457463 Mon Sep 17 00:00:00 2001 From: TDeSain Date: Wed, 20 Nov 2024 15:27:39 -0500 Subject: [PATCH] [ALS-7694] Add DashboardDrawer feature with controller, service, and repository (#55) * Add Dashboard Drawer feature - Introduce DashboardDrawerController for drawer-related endpoints - Implement DashboardDrawerService to handle drawer logic - Create DashboardDrawerRepository for database interactions - Define DashboardDrawer and DashboardDrawerList records - Add DashboardDrawerRowMapper for result set mapping - Update DashboardRepository SQL to include dataset_id - Modify application properties to include dashboard layout configuration * Update application-bdc.properties with dashboard and filtering configs - Added `dashboard.enable.bdc_hack` property - Added `dashboard.layout.type` property - Moved `filtering.unfilterable_concepts` property - Ensured newline at end of file * Add 'program_name' column to Dashboard repository - Updated SQL query to include 'program_name' field. - Joined 'dataset_meta' table for 'program_name' metadata. - Added 'program_name' to the column list in DashboardService. * Refactor service and controller to handle Optional returns - Refactor `DashboardDrawerService` methods to return `Optional` - Update controller methods to handle `Optional` results with appropriate HTTP responses - Modify repository methods to return `Optional` for single item queries - Ensure consistency and error handling improvements across the service and controller layers --- .../dashboard/DashboardRepository.java | 5 +- .../dashboard/DashboardService.java | 7 +- .../dashboarddrawer/DashboardDrawer.java | 9 ++ .../DashboardDrawerController.java | 24 ++++++ .../dashboarddrawer/DashboardDrawerList.java | 6 ++ .../DashboardDrawerRepository.java | 82 +++++++++++++++++++ .../DashboardDrawerRowMapper.java | 35 ++++++++ .../DashboardDrawerService.java | 50 +++++++++++ src/main/resources/application-bdc.properties | 5 +- src/main/resources/application.properties | 4 +- src/test/resources/application.properties | 2 +- 11 files changed, 222 insertions(+), 7 deletions(-) create mode 100644 src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboarddrawer/DashboardDrawer.java create mode 100644 src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboarddrawer/DashboardDrawerController.java create mode 100644 src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboarddrawer/DashboardDrawerList.java create mode 100644 src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboarddrawer/DashboardDrawerRepository.java create mode 100644 src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboarddrawer/DashboardDrawerRowMapper.java create mode 100644 src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboarddrawer/DashboardDrawerService.java diff --git a/src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboard/DashboardRepository.java b/src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboard/DashboardRepository.java index 2a0dd7f..93e10d6 100644 --- a/src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboard/DashboardRepository.java +++ b/src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboard/DashboardRepository.java @@ -52,6 +52,7 @@ public List> getHackyBDCRows() { String sql = """ SELECT + dataset.dataset_id as dataset_id, dataset.abbreviation AS abbreviation, dataset.full_name AS name, CASE @@ -70,10 +71,11 @@ public List> getHackyBDCRows() { END AS samples, CASE - WHEN (consent.consent_code IS NOT NULL AND consent.consent_code != '') THEN concat(study_accession_meta.value, '.', consent.consent_code) + WHEN (consent.consent_code IS NOT NULL AND consent.consent_code != '') THEN concat(study_accession_meta.value, '.', consent.consent_code) ELSE study_accession_meta.value END AS accession, + program_name.value as program_name, study_focus_meta.value AS study_focus, additional_info_meta.value AS additional_info_link FROM @@ -82,6 +84,7 @@ public List> getHackyBDCRows() { LEFT JOIN dataset_meta AS study_focus_meta ON study_focus_meta.dataset_id = dataset.dataset_id AND study_focus_meta.KEY = 'study_focus' LEFT JOIN dataset_meta AS study_accession_meta ON study_accession_meta.dataset_id = dataset.dataset_id AND study_accession_meta.KEY = 'study_accession' LEFT JOIN dataset_meta AS additional_info_meta ON additional_info_meta.dataset_id = dataset.dataset_id AND additional_info_meta.KEY = 'study_link' + LEFT JOIN dataset_meta AS program_name ON program_name.dataset_id = dataset.dataset_id AND program_name.KEY = 'program_name' WHERE dataset.dataset_id NOT IN (select dataset_id from dataset_meta where KEY = 'show_dashboad' and VALUE = 'false') ORDER BY name ASC, abbreviation ASC """; diff --git a/src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboard/DashboardService.java b/src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboard/DashboardService.java index be16f9a..0d9315b 100644 --- a/src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboard/DashboardService.java +++ b/src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboard/DashboardService.java @@ -34,8 +34,9 @@ public Dashboard getDashboard() { private static final List hackyBDCColumns = List.of( new DashboardColumn("abbreviation", "Abbreviation"), new DashboardColumn("name", "Name"), - new DashboardColumn("study_focus", "Study Focus"), new DashboardColumn("clinvars", "Clinical Variables"), - new DashboardColumn("participants", "Participants"), new DashboardColumn("samples", "Samples Sequenced"), - new DashboardColumn("accession", "Accession"), new DashboardColumn("additional_info_link", "Study Link") + new DashboardColumn("study_focus", "Study Focus"), new DashboardColumn("program_name", "Program"), + new DashboardColumn("participants", "Participants"), new DashboardColumn("clinvars", "Clinical Variables"), + new DashboardColumn("samples", "Samples Sequenced"), new DashboardColumn("accession", "Accession"), + new DashboardColumn("additional_info_link", "Study Link") ); } diff --git a/src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboarddrawer/DashboardDrawer.java b/src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboarddrawer/DashboardDrawer.java new file mode 100644 index 0000000..b8a107e --- /dev/null +++ b/src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboarddrawer/DashboardDrawer.java @@ -0,0 +1,9 @@ +package edu.harvard.dbmi.avillach.dictionary.dashboarddrawer; + +import java.util.List; + +public record DashboardDrawer( + int datasetId, String studyFullname, String studyAbbreviation, List consentGroups, String studySummary, List studyFocus, + String studyDesign, String sponsor +) { +} diff --git a/src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboarddrawer/DashboardDrawerController.java b/src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboarddrawer/DashboardDrawerController.java new file mode 100644 index 0000000..e94c058 --- /dev/null +++ b/src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboarddrawer/DashboardDrawerController.java @@ -0,0 +1,24 @@ +package edu.harvard.dbmi.avillach.dictionary.dashboarddrawer; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; + +@Controller +@RequestMapping("/dashboard-drawer") +public class DashboardDrawerController { + + @Autowired + private DashboardDrawerService dashboardDrawerService; + + @GetMapping + public ResponseEntity findAll() { + return dashboardDrawerService.findAll().map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build()); + } + + @GetMapping("/{id}") + public ResponseEntity findByDatasetId(@PathVariable Integer id) { + return dashboardDrawerService.findByDatasetId(id).map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build()); + } +} diff --git a/src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboarddrawer/DashboardDrawerList.java b/src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboarddrawer/DashboardDrawerList.java new file mode 100644 index 0000000..4b82955 --- /dev/null +++ b/src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboarddrawer/DashboardDrawerList.java @@ -0,0 +1,6 @@ +package edu.harvard.dbmi.avillach.dictionary.dashboarddrawer; + +import java.util.List; + +public record DashboardDrawerList(List dashboardDrawerList) { +} diff --git a/src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboarddrawer/DashboardDrawerRepository.java b/src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboarddrawer/DashboardDrawerRepository.java new file mode 100644 index 0000000..8f0d298 --- /dev/null +++ b/src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboarddrawer/DashboardDrawerRepository.java @@ -0,0 +1,82 @@ +package edu.harvard.dbmi.avillach.dictionary.dashboarddrawer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.stereotype.Repository; + +import java.util.*; + +@Repository +public class DashboardDrawerRepository { + + private final NamedParameterJdbcTemplate template; + + private static final Logger log = LoggerFactory.getLogger(DashboardDrawerRepository.class); + + @Autowired + public DashboardDrawerRepository(NamedParameterJdbcTemplate template) { + this.template = template; + } + + public List getDashboardDrawerRows() { + String materializedViewSql = """ + select * from dictionary_db.dict.dataset_meta_materialized_view dmmv; + """; + + String fallbackSql = """ + SELECT d.dataset_id, + MAX(d.full_name) study_fullname, + MAX(d.abbreviation) study_abbreviation, + ARRAY_AGG(DISTINCT c.description) consent_groups, + MAX(d.description) study_summary, + ARRAY_AGG(DISTINCT dm.value) FILTER (where dm.key IN ('study_focus')) study_focus, + MAX(DISTINCT dm.value) FILTER (where dm.key IN ('study_design')) study_design, + MAX(DISTINCT dm.value) FILTER (where dm.key IN ('sponsor')) sponsor + FROM dataset d + JOIN dataset_meta dm ON d.dataset_id = dm.dataset_id + JOIN consent c ON d.dataset_id = c.dataset_id + GROUP BY d.dataset_id + """; + + try { + return template.query(materializedViewSql, new DashboardDrawerRowMapper()); + } catch (Exception e) { + log.debug("Materialized view not available, using fallback query. Error: {}", e.getMessage()); + return template.query(fallbackSql, new DashboardDrawerRowMapper()); + } + } + + public Optional getDashboardDrawerRows(Integer datasetId) { + String materializedViewSql = """ + select * from dictionary_db.dict.dataset_meta_materialized_view dmmv where dmmv.dataset_id = :datasetId; + """; + + String fallbackSql = """ + SELECT d.dataset_id dataset_id, + MAX(d.full_name) study_fullname, + MAX(d.abbreviation) study_abbreviation, + ARRAY_AGG(DISTINCT c.description) consent_groups, + MAX(d.description) study_summary, + ARRAY_AGG(DISTINCT dm.value) FILTER (where dm.key IN ('study_focus')) study_focus, + MAX(DISTINCT dm.value) FILTER (where dm.key IN ('study_design')) study_design, + MAX(DISTINCT dm.value) FILTER (where dm.key IN ('sponsor')) sponsor + FROM dataset d + JOIN dataset_meta dm ON d.dataset_id = dm.dataset_id + JOIN consent c ON d.dataset_id = c.dataset_id + where d.dataset_id = :datasetId + GROUP BY d.dataset_id + """; + MapSqlParameterSource params = new MapSqlParameterSource(); + params.addValue("datasetId", datasetId); + + try { + return template.query(materializedViewSql, params, new DashboardDrawerRowMapper()).stream().findFirst(); + } catch (Exception e) { + log.debug("Materialized view not available, using fallback query. Error: {}", e.getMessage()); + return template.query(fallbackSql, params, new DashboardDrawerRowMapper()).stream().findFirst(); + } + } +} diff --git a/src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboarddrawer/DashboardDrawerRowMapper.java b/src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboarddrawer/DashboardDrawerRowMapper.java new file mode 100644 index 0000000..eb555ac --- /dev/null +++ b/src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboarddrawer/DashboardDrawerRowMapper.java @@ -0,0 +1,35 @@ +package edu.harvard.dbmi.avillach.dictionary.dashboarddrawer; + +import org.springframework.jdbc.core.RowMapper; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Array; // For handling SQL Array +import java.util.Arrays; +import java.util.List; + +public class DashboardDrawerRowMapper implements RowMapper { + + @Override + public DashboardDrawer mapRow(ResultSet rs, int rowNum) throws SQLException { + return new DashboardDrawer( + rs.getInt("dataset_id"), rs.getString("study_fullname"), rs.getString("study_abbreviation"), + convertSqlArrayToList(rs.getArray("consent_groups")), rs.getString("study_summary"), + convertSqlArrayToList(rs.getArray("study_focus")), rs.getString("study_design"), rs.getString("sponsor") + ); + } + + private List convertSqlArrayToList(Array sqlArray) throws SQLException { + if (sqlArray == null) { + return List.of(); + } else { + Object[] arrayContents = (Object[]) sqlArray.getArray(); + // Check if the array contains a single empty value + if (arrayContents.length == 1 && "".equals(arrayContents[0])) { + return List.of(); + } else { + return Arrays.asList((String[]) sqlArray.getArray()); + } + } + } +} diff --git a/src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboarddrawer/DashboardDrawerService.java b/src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboarddrawer/DashboardDrawerService.java new file mode 100644 index 0000000..4fbe1d8 --- /dev/null +++ b/src/main/java/edu/harvard/dbmi/avillach/dictionary/dashboarddrawer/DashboardDrawerService.java @@ -0,0 +1,50 @@ +package edu.harvard.dbmi.avillach.dictionary.dashboarddrawer; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@Service +public class DashboardDrawerService { + + private final DashboardDrawerRepository repository; + private final String dashboardLayout; + + @Autowired + public DashboardDrawerService(DashboardDrawerRepository repository, @Value("${dashboard.layout.type}") String dashboardLayout) { + this.repository = repository; + this.dashboardLayout = dashboardLayout; + } + + /** + * Retrieves the Dashboard Drawer for all datasets. + * + * @return All Dashboard Instances and their metadata. + */ + public Optional findAll() { + if (dashboardLayout.equalsIgnoreCase("bdc")) { + List records = repository.getDashboardDrawerRows(); + return Optional.of(new DashboardDrawerList(records)); + } + + return Optional.of(new DashboardDrawerList(new ArrayList<>())); + } + + /** + * Retrieves the Dashboard Drawer for a specific dataset. + * + * + * @param datasetId the ID of the dataset to fetch. + * @return a single Dashboard instance with drawer-specific metadata. + */ + public Optional findByDatasetId(Integer datasetId) { + if ("bdc".equalsIgnoreCase(dashboardLayout)) { + return repository.getDashboardDrawerRows(datasetId); + } + return Optional.empty(); + } +} diff --git a/src/main/resources/application-bdc.properties b/src/main/resources/application-bdc.properties index 00405c0..8d0ce92 100644 --- a/src/main/resources/application-bdc.properties +++ b/src/main/resources/application-bdc.properties @@ -6,5 +6,8 @@ spring.datasource.username=${DATASOURCE_USERNAME} server.port=80 dashboard.enable.extra_details=true +dashboard.enable.bdc_hack=true +dashboard.layout.type=bdc + +filtering.unfilterable_concepts=stigmatized -filtering.unfilterable_concepts=stigmatized \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 0c71723..dc8d4bf 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -3,12 +3,14 @@ spring.datasource.url=jdbc:postgresql://${POSTGRES_HOST}:5432/${POSTGRES_DB}?cur spring.datasource.username=${POSTGRES_USER} spring.datasource.password=${POSTGRES_PASSWORD} spring.datasource.driver-class-name=org.postgresql.Driver + server.port=80 dashboard.columns={abbreviation:'Abbreviation',name:'Name',clinvars:'Clinical Variables'} dashboard.column-order=abbreviation,name,clinvars dashboard.nonmeta-columns=abbreviation,name dashboard.enable.extra_details=true -dashboard.enable.bdc_hack=true +dashboard.enable.bdc_hack=false +dashboard.layout.type=default filtering.unfilterable_concepts=stigmatized diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index 8fb1863..697f376 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -7,8 +7,8 @@ spring.datasource.driver-class-name=org.postgresql.Driver dashboard.columns={abbreviation:'Abbreviation',melast:'This one goes last',name:'Name',clinvars:'Clinical Variables',participants:'Participants'} dashboard.column-order=abbreviation,name,clinvars dashboard.nonmeta-columns=abbreviation,name - dashboard.enable.extra_details=true dashboard.enable.bdc_hack=false +dashboard.layout.type=default filtering.unfilterable_concepts=stigmatized