Skip to content

Commit 5f924c6

Browse files
jenniferboedkerKochTobiwow-such-code
authored
Only consider NGS samples (#246)
Filters openbis projects for projects containing NGS samples and thus being of type DNA or RNA. This remove most samples for which no tracking information is available. Furthermore, full proteomics projects are removed since we cannot provide reliable tracking information here. Also, this PR fixes the number of samples shown for the project status. Co-authored-by: Tobias Koch <[email protected]> Co-authored-by: wow-such-code <[email protected]>
1 parent 1a03fb6 commit 5f924c6

File tree

6 files changed

+92
-59
lines changed

6 files changed

+92
-59
lines changed

sample-tracking-status-overview-app/src/main/groovy/life/qbic/portal/sampletracking/DependencyManager.groovy

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ class DependencyManager {
107107
"/v2/samples",
108108
"/status",
109109
"/v2/projects",
110-
credentials)
110+
credentials, getNgsSampleRepository())
111111
}
112112

113113
/**
@@ -174,4 +174,8 @@ class DependencyManager {
174174
DownloadManifestProvider getDownloadManifestProvider() {
175175
return sampleTrackingConnector
176176
}
177+
178+
NgsSampleRepository getNgsSampleRepository() {
179+
return openBisConnector
180+
}
177181
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package life.qbic.portal.sampletracking.data;
2+
3+
import java.util.List;
4+
5+
/**
6+
* <b>Repository for loading NGS samples</b>
7+
*
8+
* @since 1.1.3
9+
*/
10+
public interface NgsSampleRepository {
11+
12+
/**
13+
* Finds all NGS samples for a given project
14+
* @param projectCode the code specifying a project
15+
* @return a list of sample codes from all NGS samples of that project
16+
*/
17+
public List<String> findNGSSamplesForProject(String projectCode);
18+
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package life.qbic.portal.sampletracking.data;
22

3+
import static java.util.stream.Collectors.groupingBy;
4+
import static java.util.stream.Collectors.toList;
5+
36
import ch.ethz.sis.openbis.generic.asapi.v3.IApplicationServerApi;
47
import ch.ethz.sis.openbis.generic.asapi.v3.dto.common.search.SearchResult;
58
import ch.ethz.sis.openbis.generic.asapi.v3.dto.project.fetchoptions.ProjectFetchOptions;
@@ -8,28 +11,26 @@
811
import ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.search.SampleSearchCriteria;
912
import ch.systemsx.cisd.common.spring.HttpInvokerUtils;
1013
import java.util.ArrayList;
11-
import java.util.Comparator;
1214
import java.util.HashMap;
1315
import java.util.List;
1416
import java.util.Map;
17+
import java.util.Map.Entry;
1518
import java.util.Optional;
1619
import java.util.function.Function;
1720
import java.util.function.Predicate;
18-
import java.util.stream.Collectors;
1921
import life.qbic.datamodel.dtos.portal.PortalUser;
2022
import life.qbic.portal.sampletracking.view.projects.viewmodel.Project;
2123
import life.qbic.portal.sampletracking.view.samples.viewmodel.Sample;
2224

23-
public class OpenBisConnector implements ProjectRepository, SampleRepository {
25+
public class OpenBisConnector implements ProjectRepository, SampleRepository, NgsSampleRepository {
2426

2527
private final String sessionToken;
2628

2729
private final IApplicationServerApi api;
2830

2931
private static final int TIMEOUT = 10_000;
30-
31-
private final List<ch.ethz.sis.openbis.generic.asapi.v3.dto.project.Project> cachedProjects = new ArrayList<>();
32-
private final Map<String, List<ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.Sample>> cachedSamples = new HashMap<>();
32+
private final Map<String, List<ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.Sample>> sampleCache = new HashMap<>();
33+
private final Map<String, ch.ethz.sis.openbis.generic.asapi.v3.dto.project.Project> projectCache = new HashMap<>();
3334

3435

3536

@@ -43,64 +44,72 @@ public OpenBisConnector(Credentials credentials, PortalUser portalUser,
4344

4445
@Override
4546
public List<Project> findAllProjects() {
46-
if (cachedProjects.isEmpty()) {
47-
ProjectFetchOptions fetchOptions = new ProjectFetchOptions();
48-
fetchOptions.withSpace();
49-
SearchResult<ch.ethz.sis.openbis.generic.asapi.v3.dto.project.Project> projectSearchResult =
50-
api.searchProjects(sessionToken, new ProjectSearchCriteria(), fetchOptions);
51-
52-
List<ch.ethz.sis.openbis.generic.asapi.v3.dto.project.Project> projects = projectSearchResult.getObjects().stream()
53-
.filter(hasValidProjectCode())
54-
.sorted(Comparator.comparing(
55-
ch.ethz.sis.openbis.generic.asapi.v3.dto.project.Project::getModificationDate).reversed())
56-
.collect(Collectors.toList());
57-
cachedProjects.addAll(projects);
47+
if (projectCache.isEmpty()) {
48+
updateCache();
5849
}
59-
return cachedProjects.stream()
60-
.map(it -> new Project(it.getCode(),
61-
Optional.ofNullable(it.getDescription()).orElse("")))
62-
.collect(Collectors.toList());
50+
return projectCache.entrySet().stream()
51+
.map(it -> new Project(it.getKey(), Optional.ofNullable(it.getValue().getDescription()).orElse("")))
52+
.collect(toList());
53+
}
54+
55+
@Override
56+
public List<String> findNGSSamplesForProject(String projectCode){
57+
58+
return findAllSamplesForProject(projectCode).stream()
59+
.map(Sample::code)
60+
.collect(toList());
6361
}
6462

6563
@Override
6664
public List<Sample> findAllSamplesForProject(String projectCode) {
6765

68-
List<ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.Sample> samples;
69-
if (cachedSamples.containsKey(projectCode)) {
70-
samples = cachedSamples.get(projectCode);
71-
} else {
72-
SampleSearchCriteria sampleSearchCriteria = new SampleSearchCriteria();
73-
sampleSearchCriteria.withCode().thatStartsWith(projectCode);
74-
sampleSearchCriteria.withType().withCode().thatEquals("Q_TEST_SAMPLE");
75-
SampleFetchOptions fetchOptions = new SampleFetchOptions();
76-
fetchOptions.withType();
77-
fetchOptions.withProperties();
78-
SearchResult<ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.Sample> sampleSearchResult = api.searchSamples(
79-
sessionToken, sampleSearchCriteria, fetchOptions);
80-
samples = sampleSearchResult.getObjects();
81-
cachedSamples.put(projectCode, samples);
82-
}
83-
return samples.stream()
66+
List<ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.Sample> cachedProjectSamples = sampleCache
67+
.entrySet().stream()
68+
.filter(it -> it.getKey().equals(projectCode)).map(Entry::getValue).findAny().orElse(new ArrayList<>());
69+
return cachedProjectSamples.stream().map(convertToSample()).collect(toList());
70+
}
71+
72+
private void updateCache() {
73+
74+
SampleSearchCriteria isNgs = new SampleSearchCriteria();
75+
isNgs.withOrOperator();
76+
isNgs.withProperty("Q_SAMPLE_TYPE").thatContains("DNA");
77+
isNgs.withProperty("Q_SAMPLE_TYPE").thatContains("RNA");
78+
// this guarantees that no q-entity is contained as only test samples have Q_SAMPLE_TYPE
79+
80+
SampleFetchOptions fetchOptions = new SampleFetchOptions();
81+
fetchOptions.withType();
82+
fetchOptions.withProperties();
83+
SearchResult<ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.Sample> sampleSearchResult = api.searchSamples(
84+
sessionToken, isNgs, fetchOptions);
85+
Map<String, List<ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.Sample>> projectSampleMap = sampleSearchResult.getObjects()
86+
.stream()
8487
.filter(hasValidSampleCode())
85-
.filter(notAnEntity())
86-
.map(convertToSample())
87-
.collect(Collectors.toList());
88+
.collect(groupingBy(this::getProject));
89+
sampleCache.putAll(projectSampleMap);
90+
91+
ProjectFetchOptions projectFetchOptions = new ProjectFetchOptions();
92+
projectFetchOptions.withSpace();
93+
ProjectSearchCriteria projectSearchCriteria = new ProjectSearchCriteria();
94+
95+
SearchResult<ch.ethz.sis.openbis.generic.asapi.v3.dto.project.Project> projectSearchResult = api.searchProjects(
96+
sessionToken, projectSearchCriteria, projectFetchOptions);
97+
List<ch.ethz.sis.openbis.generic.asapi.v3.dto.project.Project> projects = projectSearchResult.getObjects()
98+
.stream().filter(it -> sampleCache.containsKey(it.getCode())).collect(toList());
99+
projects.forEach(it -> projectCache.put(it.getCode(), it));
88100
}
89101

90-
private Predicate<? super ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.Sample> hasValidSampleCode() {
91-
return sample -> sample.getCode().matches("Q[A-X0-9]{4}[0-9]{3}[A-X0-9]{2}$");
102+
private String getProject(
103+
ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.Sample sample) {
104+
return sample.getCode().substring(0, 5);
92105
}
93106

94-
private Predicate<ch.ethz.sis.openbis.generic.asapi.v3.dto.project.Project> hasValidProjectCode() {
95-
return project -> project.getCode().matches("^Q[A-X0-9]{4}$");
107+
private Predicate<? super ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.Sample> hasValidSampleCode() {
108+
return sample -> sample.getCode().matches("Q[A-X0-9]{4}[0-9]{3}[A-X0-9]{2}$");
96109
}
97110

98111
private static Function<ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.Sample, Sample> convertToSample() {
99112
return it -> new Sample(it.getCode(), it.getProperty("Q_SECONDARY_NAME"));
100113
}
101114

102-
private static Predicate<ch.ethz.sis.openbis.generic.asapi.v3.dto.sample.Sample> notAnEntity() {
103-
return it -> !it.getCode().contains("ENTITY");
104-
}
105-
106115
}

sample-tracking-status-overview-app/src/main/groovy/life/qbic/portal/sampletracking/data/SampleTrackingConnector.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,17 @@ public class SampleTrackingConnector implements ProjectStatusProvider, SampleSta
5050
private final String serviceAddress;
5151
private final String serviceUser;
5252
private final String userPass;
53+
private final NgsSampleRepository ngsSampleRepository;
5354

5455
public SampleTrackingConnector(String serviceUrlBase, String samplesSuffix, String statusSuffix,
55-
String projectsSuffix, Credentials credentials) {
56+
String projectsSuffix, Credentials credentials, NgsSampleRepository ngsSampleRepository) {
5657
this.samplesSuffix = samplesSuffix;
5758
this.statusSuffix = statusSuffix;
5859
this.projectsSuffix = projectsSuffix;
5960
this.serviceAddress = Objects.requireNonNull(serviceUrlBase);
6061
this.serviceUser = Objects.requireNonNull(credentials.getUser());
6162
this.userPass = Objects.requireNonNull(credentials.getPassword());
63+
this.ngsSampleRepository = ngsSampleRepository;
6264
}
6365

6466
@Override
@@ -113,14 +115,14 @@ public Instant statusValidSince() {
113115
public Optional<ProjectStatus> getForProject(String projectCode) {
114116
Optional<ProjectStatus> cachedStatusForProject = getCachedStatusForProject(projectCode);
115117
return cachedStatusForProject.isPresent() ? cachedStatusForProject
116-
: askServiceForProject(projectCode).map(ProjectSatusMapper::toProjectStatus);
118+
: askServiceForProject(projectCode).map(ProjectStatusMapper::toProjectStatus);
117119
}
118120

119121
private Optional<ProjectStatus> getCachedStatusForProject(String projectCode) {
120122
synchronized (cachedProjects) {
121123
if (cachedProjects.containsKey(projectCode)) {
122124
Project project = cachedProjects.get(projectCode);
123-
return Optional.of(ProjectSatusMapper.toProjectStatus(project));
125+
return Optional.of(ProjectStatusMapper.toProjectStatus(project));
124126
}
125127
}
126128
return Optional.empty();
@@ -158,6 +160,10 @@ private Optional<Project> askServiceForProject(String projectCode) {
158160
try {
159161
List<Sample> samples = objectMapper.readValue(projectJson, new TypeReference<ArrayList<Sample>>() {
160162
});
163+
164+
List<String> ngsSamples = ngsSampleRepository.findNGSSamplesForProject(projectCode);
165+
samples = samples.stream().filter(sample -> ngsSamples.contains(sample.sampleCode)).collect(Collectors.toList());
166+
161167
Project project = new Project();
162168
project.addAll(samples);
163169
synchronized (cachedProjects) {
@@ -259,7 +265,7 @@ private Optional<Sample> askServiceForSample(String sampleCode) {
259265
}
260266
}
261267

262-
static private class ProjectSatusMapper {
268+
static private class ProjectStatusMapper {
263269

264270
static ProjectStatus toProjectStatus(Project project) {
265271
int totalCount = project.size();

sample-tracking-status-overview-app/src/main/groovy/life/qbic/portal/sampletracking/view/projects/ProjectStatusComponent.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616

1717
public class ProjectStatusComponent extends Composite {
1818

19-
public static final int COLUMN_WIDTH = 100;
20-
2119
private final ExecutorService executorService;
2220
private final ProjectStatusProvider trackingStatusProvider;
2321
private final Label receivedCountLabel;
@@ -90,12 +88,9 @@ public ProjectStatusComponent(Project project, ExecutorService executorService,
9088
panelContent.setMargin(false); // determined by CSS
9189
panelContent.setSpacing(false); // determined by CSS
9290
panelContent.addComponent(spinner);
93-
// panelContent.setComponentAlignment(spinner, Alignment.TOP_CENTER);
9491

9592
panelContent.addComponent(statusLayout);
96-
// panelContent.setComponentAlignment(statusLayout, Alignment.TOP_CENTER);
9793
panelContent.addComponent(errorMessage);
98-
// panelContent.setComponentAlignment(errorMessage, Alignment.TOP_CENTER);
9994

10095
setCompositionRoot(panelContent);
10196
panelContent.setWidthUndefined();

sample-tracking-status-overview-app/src/main/groovy/life/qbic/portal/sampletracking/view/projects/ProjectView.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ private void selectProject(Project project) {
137137
removeFileDownloaders(downloadButton);
138138
}
139139
if (project.projectStatus().totalCount() < 1) {
140+
samplesButton.setEnabled(false);
140141
return;
141142
}
142143
samplesButton.setEnabled(true);

0 commit comments

Comments
 (0)