Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ALS-7179] Dashboard #29

Merged
merged 1 commit into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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