Skip to content

Commit

Permalink
[ALS-7179] Dashboard
Browse files Browse the repository at this point in the history
  • Loading branch information
Luke Sikina authored and Luke-Sikina committed Sep 16, 2024
1 parent c6be055 commit abf9fe4
Show file tree
Hide file tree
Showing 15 changed files with 384 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package edu.harvard.dbmi.avillach.dictionary.dashboard;

import java.util.List;
import java.util.Map;

public record Dashboard(List<DashboardColumn> columns, List<Map<String, String>> rows) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package edu.harvard.dbmi.avillach.dictionary.dashboard;

public record DashboardColumn(String label, String dataElement) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package edu.harvard.dbmi.avillach.dictionary.dashboard;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;
import java.util.Map;

@Configuration
public class DashboardConfig {
private final Map<String, String> labelDisplayElementPairs;
private final List<String> columnOrder;

@Autowired
public DashboardConfig(
@Value("#{${dashboard.columns}}") Map<String, String> labelDisplayElementPairs,
@Value("${dashboard.column-order}") List<String> columnOrder
) {
this.labelDisplayElementPairs = labelDisplayElementPairs;
this.columnOrder = columnOrder;
}

@Bean
public List<DashboardColumn> getColumns() {
return labelDisplayElementPairs.entrySet().stream()
.map(e -> new DashboardColumn(e.getKey(), e.getValue()))
.sorted((a, b) -> Integer.compare(calculateOrder(a), calculateOrder(b)))
.toList();
}

private int calculateOrder(DashboardColumn column) {
if (columnOrder.contains(column.label())) {
return columnOrder.indexOf(column.label());
} else {
return Integer.MAX_VALUE;
}
}



}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package edu.harvard.dbmi.avillach.dictionary.dashboard;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class DashboardController {
private final DashboardService dashboardService;

@Autowired
public DashboardController(DashboardService dashboardService) {
this.dashboardService = dashboardService;
}

@GetMapping("/dashboard")
public ResponseEntity<Dashboard> getDashboard() {
return ResponseEntity.ok(dashboardService.getDashboard());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package edu.harvard.dbmi.avillach.dictionary.dashboard;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;

@Repository
public class DashboardRepository {
private final NamedParameterJdbcTemplate template;
private final List<DashboardColumn> columns;
private final Set<String> nonMetaColumns;
private final DashboardRowResultSetExtractor extractor;

@Autowired
public DashboardRepository(
NamedParameterJdbcTemplate template,
List<DashboardColumn> columns,
@Value("${dashboard.nonmeta-columns}")
Set<String> nonMetaColumns, DashboardRowResultSetExtractor extractor
) {
this.template = template;
this.columns = columns;
this.nonMetaColumns = nonMetaColumns;
this.extractor = extractor;
}

public List<Map<String, String>> getRows() {
String sql = """
SELECT
abbreviation, ref AS name,
dataset_meta.KEY AS key,
dataset_meta.VALUE AS value
FROM
dataset
JOIN dataset_meta ON dataset.dataset_id = dataset_meta.dataset_id
WHERE
dataset_meta.KEY IN (:keys)
ORDER BY name ASC, abbreviation ASC
""";
List<String> keys = columns.stream()
.map(DashboardColumn::label)
.filter(Predicate.not(nonMetaColumns::contains))
.toList();
MapSqlParameterSource params = new MapSqlParameterSource().addValue("keys", keys);
return template.query(sql, params, extractor);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package edu.harvard.dbmi.avillach.dictionary.dashboard;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.stereotype.Component;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Component
public class DashboardRowResultSetExtractor implements ResultSetExtractor<List<Map<String, String>>> {
// This is a template of all the configured columns.
// It's used to ensure that empty values exist for all cells,
// even if there is no matching val in the database.
private final Map<String, String> template;

@Autowired
public DashboardRowResultSetExtractor(List<DashboardColumn> columns) {
template = columns.stream()
.collect(Collectors.toMap(DashboardColumn::label, (ignored) -> ""));
}

@Override
public List<Map<String, String>> extractData(ResultSet rs) throws SQLException, DataAccessException {
String currentRow = "";
Map<String, String> row = new HashMap<>(template);
List<Map<String, String>> rows = new ArrayList<>();
while (rs.next()) {
String abbreviation = rs.getString("abbreviation");
String name = rs.getString("name");
if (!currentRow.equals(name + abbreviation)) {
currentRow = name + abbreviation;
if (!row.isEmpty()) {
row.put("abbreviation", abbreviation);
row.put("name", name);
rows.add(row);
row = new HashMap<>(template);
}
}
row.put(rs.getString("key"), rs.getString("value"));
}
return rows;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package edu.harvard.dbmi.avillach.dictionary.dashboard;

import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;

@Service
public class DashboardService {

private final DashboardRepository repository;
private final List<DashboardColumn> columns;

public DashboardService(DashboardRepository repository, List<DashboardColumn> columns) {
this.repository = repository;
this.columns = columns;
}

public Dashboard getDashboard() {
List<Map<String, String>> rows = repository.getRows();
return new Dashboard(
columns,
rows
);
}
}

This file was deleted.

3 changes: 3 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package edu.harvard.dbmi.avillach.dictionary.dashboard;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
class DashboardConfigTest {

@Autowired
DashboardConfig subject;

@Test
void shouldGetColumns() {
List<DashboardColumn> actual = subject.getColumns();
List<DashboardColumn> expected = List.of(
new DashboardColumn("abbreviation", "Abbreviation"),
new DashboardColumn("name", "Name"),
new DashboardColumn("clinvars", "Clinical Variables"),
new DashboardColumn("melast", "This one goes last"),
new DashboardColumn("participants", "Participants")
);

Assertions.assertEquals(expected, actual);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package edu.harvard.dbmi.avillach.dictionary.dashboard;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

import java.util.List;

@SpringBootTest
class DashboardControllerTest {
@MockBean
private DashboardService service;

@Autowired
private DashboardController subject;

@Test
void shouldGetDashboard() {
Dashboard dashboard = new Dashboard(List.of(), List.of());
Mockito.when(service.getDashboard())
.thenReturn(dashboard);

ResponseEntity<Dashboard> actual = subject.getDashboard();

Assertions.assertEquals(dashboard, actual.getBody());
Assertions.assertEquals(HttpStatus.OK, actual.getStatusCode());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package edu.harvard.dbmi.avillach.dictionary.dashboard;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.MountableFile;

import java.util.List;
import java.util.Map;

@Testcontainers
@SpringBootTest
class DashboardRepositoryTest {

@Autowired
DashboardRepository subject;

@Container
static final PostgreSQLContainer<?> databaseContainer =
new PostgreSQLContainer<>("postgres:16")
.withReuse(true)
.withCopyFileToContainer(
MountableFile.forClasspathResource("seed.sql"), "/docker-entrypoint-initdb.d/seed.sql"
);

@DynamicPropertySource
static void mySQLProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", databaseContainer::getJdbcUrl);
registry.add("spring.datasource.username", databaseContainer::getUsername);
registry.add("spring.datasource.password", databaseContainer::getPassword);
registry.add("spring.datasource.db", databaseContainer::getDatabaseName);
}

@Test
void shouldGetDashboardRows() {
List<Map<String, String>> actual = subject.getRows();
List<Map<String, String>> expected = List.of(
Map.of("name", "phs000007", "abbreviation", "FHS", "melast", "", "clinvars", "", "participants", ""),
Map.of("name", "phs000284", "abbreviation", "CFS", "melast", "", "clinvars", "12546", "participants", "3435"),
Map.of("name", "phs001963", "abbreviation", "DEMENTIA-SEQ", "melast", "", "clinvars", "12321", "participants", "867876"),
Map.of("name", "phs002385", "abbreviation", "HCT_for_SCD", "melast", "", "clinvars", "653", "participants", "65"),
Map.of("name", "phs002715", "abbreviation", "NSRR CFS", "melast", "", "clinvars", "7567", "participants", "33"),
Map.of("name", "phs002808", "abbreviation", "nuMoM2b", "melast", "", "clinvars", "500", "participants", "23432"),
Map.of("name", "phs003463", "abbreviation", "RECOVER_Adult", "melast", "", "clinvars", "2", "participants", "111"),
Map.of("name", "phs003543", "abbreviation", "NSRR_HSHC", "melast", "", "clinvars", "654645", "participants", "6654"),
Map.of("name", "phs003566", "abbreviation", "SPRINT", "melast", "", "clinvars", "434", "participants", "53435")
);
Assertions.assertEquals(expected, actual);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package edu.harvard.dbmi.avillach.dictionary.dashboard;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;

import java.util.List;
import java.util.Map;

@SpringBootTest
class DashboardServiceTest {
@MockBean
DashboardRepository repository;

@MockBean
List<DashboardColumn> columns;

@Autowired
DashboardService subject;

@Test
void shouldGetDashboard() {
List<Map<String, String>> rows = List.of(Map.of("a", "1", "b", "2"));
Mockito.when(repository.getRows())
.thenReturn(rows);

Dashboard actual = subject.getDashboard();

Dashboard expected = new Dashboard(columns, rows);
Assertions.assertEquals(expected, actual);
}
}
6 changes: 5 additions & 1 deletion src/test/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@ spring.application.name=dictionary
spring.datasource.url=jdbc:postgresql://localhost:5432/search
spring.datasource.username=picsure
spring.datasource.password=foo
spring.datasource.driver-class-name=org.postgresql.Driver
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
Loading

0 comments on commit abf9fe4

Please sign in to comment.