Skip to content

Commit e9705f2

Browse files
authored
Merge pull request #677 from sanger/release_rows
x1575: Combine rows of the same sample in the release file
2 parents c258495 + d7b8ecd commit e9705f2

7 files changed

Lines changed: 167 additions & 10 deletions

File tree

src/main/java/uk/ac/sanger/sccp/stan/ReleaseFileController.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ public ReleaseFileController(ReleaseFileService releaseFileService) {
2828

2929
@RequestMapping(value="/release", method = RequestMethod.GET, produces = "text/tsv")
3030
@ResponseBody
31-
public TsvFile<ReleaseEntry> getReleaseFile(@RequestParam(name="id") List<Integer> ids,
31+
public TsvFile<?> getReleaseFile(@RequestParam(name="id") List<Integer> ids,
3232
@RequestParam(name="groups", required=false) List<String> groupNames,
3333
@RequestParam(name="type", required=false) String fileType) {
3434
final String filename = filenameForType(fileType);
3535
ReleaseFileContent rfc = releaseFileService.getReleaseFileContent(ids, parseOptions(groupNames));
36-
List<? extends TsvColumn<ReleaseEntry>> columns = releaseFileService.computeColumns(rfc);
37-
return new TsvFile<>(filename, rfc.getEntries(), columns);
36+
List<? extends TsvColumn<List<ReleaseEntry>>> columns = releaseFileService.computeRowColumns(rfc);
37+
return new TsvFile<>(filename, rfc.getRows(), columns);
3838
}
3939

4040
/**

src/main/java/uk/ac/sanger/sccp/stan/service/releasefile/ReleaseFileContent.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,14 @@ public class ReleaseFileContent {
1111
private final Set<ReleaseFileMode> modes;
1212
private final List<ReleaseEntry> entries;
1313
private final Set<ReleaseFileOption> options;
14+
private final List<List<ReleaseEntry>> rows;
1415

15-
public ReleaseFileContent(Set<ReleaseFileMode> modes, List<ReleaseEntry> entries, Set<ReleaseFileOption> options) {
16+
public ReleaseFileContent(Set<ReleaseFileMode> modes, List<ReleaseEntry> entries, Set<ReleaseFileOption> options,
17+
List<List<ReleaseEntry>> rows) {
1618
this.modes = modes==null ? Set.of() : modes;
1719
this.entries = entries;
1820
this.options = options;
21+
this.rows = rows;
1922
}
2023

2124
public Set<ReleaseFileMode> getModes() {
@@ -30,6 +33,10 @@ public Set<ReleaseFileOption> getOptions() {
3033
return this.options;
3134
}
3235

36+
public List<List<ReleaseEntry>> getRows() {
37+
return this.rows;
38+
}
39+
3340
@Override
3441
public boolean equals(Object o) {
3542
if (this == o) return true;
@@ -38,11 +45,12 @@ public boolean equals(Object o) {
3845
return (this.modes.equals(that.modes)
3946
&& Objects.equals(this.entries, that.entries)
4047
&& Objects.equals(this.options, that.options)
48+
&& Objects.equals(this.rows, that.rows)
4149
);
4250
}
4351

4452
@Override
4553
public int hashCode() {
46-
return Objects.hash(modes, entries, options);
54+
return Objects.hash(modes, entries, options, rows);
4755
}
4856
}

src/main/java/uk/ac/sanger/sccp/stan/service/releasefile/ReleaseFileService.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import uk.ac.sanger.sccp.stan.service.releasefile.Ancestoriser.Ancestry;
1515
import uk.ac.sanger.sccp.stan.service.releasefile.Ancestoriser.SlotSample;
1616
import uk.ac.sanger.sccp.utils.UCMap;
17+
import uk.ac.sanger.sccp.utils.tsv.CombinerColumn;
1718
import uk.ac.sanger.sccp.utils.tsv.TsvColumn;
1819

1920
import javax.persistence.EntityNotFoundException;
@@ -101,7 +102,7 @@ public ReleaseFileService(Ancestoriser ancestoriser,
101102
*/
102103
public ReleaseFileContent getReleaseFileContent(Collection<Integer> releaseIds, Set<ReleaseFileOption> options) {
103104
if (releaseIds.isEmpty()) {
104-
return new ReleaseFileContent(EnumSet.of(ReleaseFileMode.NORMAL), List.of(), options);
105+
return new ReleaseFileContent(EnumSet.of(ReleaseFileMode.NORMAL), List.of(), options, List.of());
105106
}
106107
List<Release> releases = getReleases(releaseIds);
107108
Map<Integer, Snapshot> snapshots = loadSnapshots(releases);
@@ -142,7 +143,21 @@ public ReleaseFileContent getReleaseFileContent(Collection<Integer> releaseIds,
142143
}
143144
loadXeniumFields(entries, slotIds);
144145
loadFlags(entries);
145-
return new ReleaseFileContent(modes, entries, options);
146+
return new ReleaseFileContent(modes, entries, options, groupRows(entries));
147+
}
148+
149+
/** Groups the entries by labware and sample */
150+
public List<List<ReleaseEntry>> groupRows(List<ReleaseEntry> entries) {
151+
record RowKey(int labwareId, int sampleId) {
152+
RowKey(ReleaseEntry entry) {
153+
this(entry.getLabware().getId(), entry.getSample().getId());
154+
}
155+
}
156+
Map<RowKey, List<ReleaseEntry>> rows = new LinkedHashMap<>();
157+
for (ReleaseEntry entry : entries) {
158+
rows.computeIfAbsent(new RowKey(entry), k -> new ArrayList<>()).add(entry);
159+
}
160+
return asList(rows.values());
146161
}
147162

148163
/**
@@ -1391,6 +1406,11 @@ public String describeFlags(Collection<FlagDetail.FlagSummary> summaries) {
13911406
.collect(joining(" "));
13921407
}
13931408

1409+
public List<TsvColumn<List<ReleaseEntry>>> computeRowColumns(ReleaseFileContent rfc) {
1410+
var innerColumns = computeColumns(rfc);
1411+
return innerColumns.stream().<TsvColumn<List<ReleaseEntry>>>map(CombinerColumn::new).toList();
1412+
}
1413+
13941414
public List<? extends TsvColumn<ReleaseEntry>> computeColumns(ReleaseFileContent rfc) {
13951415
List<ReleaseColumn> modeColumns = ReleaseColumn.forModesAndOptions(rfc.getModes(), rfc.getOptions());
13961416
int dualColumnIndex = modeColumns.indexOf(ReleaseColumn.Dual_index_plate_name);
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package uk.ac.sanger.sccp.utils.tsv;
2+
3+
import java.util.*;
4+
5+
import static uk.ac.sanger.sccp.utils.BasicUtils.nullOrEmpty;
6+
import static uk.ac.sanger.sccp.utils.BasicUtils.toLinkedHashSet;
7+
8+
/**
9+
* Column value generator that joins values from combined entries
10+
* @author dr6
11+
*/
12+
public class CombinerColumn<E> implements TsvColumn<List<E>> {
13+
public final TsvColumn<? super E> innerColumn;
14+
15+
public CombinerColumn(TsvColumn<? super E> innerColumn) {
16+
this.innerColumn = innerColumn;
17+
}
18+
19+
@Override
20+
public String get(List<E> entries) {
21+
if (nullOrEmpty(entries)) {
22+
return null;
23+
}
24+
if (entries.size() == 1) {
25+
return innerColumn.get(entries.getFirst());
26+
}
27+
Set<String> values = entries.stream()
28+
.map(innerColumn::get)
29+
.filter(Objects::nonNull)
30+
.collect(toLinkedHashSet());
31+
if (values.isEmpty()) {
32+
return null;
33+
}
34+
if (values.size()==1) {
35+
return values.iterator().next();
36+
}
37+
return String.join(", ", values);
38+
}
39+
40+
@Override
41+
public String toString() {
42+
return this.innerColumn.toString();
43+
}
44+
}

src/test/java/uk/ac/sanger/sccp/stan/integrationtest/TestReleaseMutation.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ public void testRelease() throws Exception {
146146

147147
String tsvString = getReleaseFile(releaseIds, EnumSet.allOf(ReleaseFileOption.class));
148148
var tsvMaps = tsvToMap(tsvString);
149-
assertEquals(4, tsvMaps.size());
149+
assertEquals(3, tsvMaps.size());
150150
Set<String> expectedColumns = Arrays.stream(ReleaseColumn.values())
151151
.filter(c -> c.getMode()!=ReleaseFileMode.CDNA)
152152
.map(ReleaseColumn::toString)
@@ -158,11 +158,13 @@ public void testRelease() throws Exception {
158158
assertMapValue(row0, ReleaseColumn.Last_section_number, "6");
159159
assertMapValue(row0, ReleaseColumn.Released_from_box_name, "Box 1");
160160
assertMapValue(row0, ReleaseColumn.Released_from_box_location, "4");
161+
assertMapValue(row0, ReleaseColumn.Slot_of_labware, "A1");
161162
var row1 = tsvMaps.get(1);
162163
assertMapValue(row1, ReleaseColumn.Released_from_box_name, "Box 1");
163164
assertMapValue(row1, ReleaseColumn.Released_from_box_location, "C4");
165+
assertMapValue(row1, ReleaseColumn.Slot_of_labware, "A1, A2");
164166
String bsName = sample.getBioState().getName();
165-
for (int i = 1; i < 4; ++i) {
167+
for (int i = 1; i < tsvMaps.size(); ++i) {
166168
var row = tsvMaps.get(i);
167169
assertMapValue(row, ReleaseColumn.Released_labware_barcode, lw.getBarcode());
168170
assertMapValue(row, ReleaseColumn.Labware_type, lw.getLabwareType().getName());

src/test/java/uk/ac/sanger/sccp/stan/service/releasefile/TestReleaseFileService.java

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
import uk.ac.sanger.sccp.stan.service.releasefile.Ancestoriser.Ancestry;
1717
import uk.ac.sanger.sccp.stan.service.releasefile.Ancestoriser.SlotSample;
1818
import uk.ac.sanger.sccp.stan.service.releasefile.ReleaseFileService.StorageDetail;
19+
import uk.ac.sanger.sccp.utils.Zip;
20+
import uk.ac.sanger.sccp.utils.tsv.CombinerColumn;
21+
import uk.ac.sanger.sccp.utils.tsv.TsvColumn;
1922

2023
import javax.persistence.EntityNotFoundException;
2124
import java.time.LocalDate;
@@ -234,6 +237,26 @@ public void testGetReleaseFileContent(StorageDetail detail, boolean includeVisiu
234237
verify(service).loadFlags(entries);
235238
}
236239

240+
@Test
241+
void testGroupRows() {
242+
setupLabware();
243+
Address A2 = new Address(1,2);
244+
ReleaseEntry[] entries = {
245+
new ReleaseEntry(lw1, lw1.getFirstSlot(), sample),
246+
new ReleaseEntry(lw1, lw1.getSlot(A2), sample),
247+
new ReleaseEntry(lw1, lw1.getFirstSlot(), sample2),
248+
new ReleaseEntry(lw2, lw2.getFirstSlot(), sample),
249+
new ReleaseEntry(lw2, lw2.getFirstSlot(), sample2),
250+
new ReleaseEntry(lw2, lw2.getSlot(A2), sample)
251+
};
252+
List<List<ReleaseEntry>> grouped = service.groupRows(Arrays.asList(entries));
253+
assertThat(grouped).hasSize(4);
254+
assertThat(grouped.get(0)).containsExactly(entries[0], entries[1]);
255+
assertThat(grouped.get(1)).containsExactly(entries[2]);
256+
assertThat(grouped.get(2)).containsExactly(entries[3], entries[5]);
257+
assertThat(grouped.get(3)).containsExactly(entries[4]);
258+
}
259+
237260
@ParameterizedTest
238261
@CsvSource(delimiter=';', value={
239262
"null,null;null,null;null,null;NONE",
@@ -1217,6 +1240,24 @@ public void testLoadParaffinProcessingProgram() {
12171240
assertEquals("Pears", entries.get(1).getParaffinProcessingProgram());
12181241
}
12191242

1243+
@SuppressWarnings("rawtypes")
1244+
@Test
1245+
public void testComputeRowColumns() {
1246+
List<TsvColumn<?>> innerColumns = List.of(
1247+
v -> "Alpha", v -> "Beta"
1248+
);
1249+
doReturn(innerColumns).when(service).computeColumns(any());
1250+
ReleaseFileContent rfc = new ReleaseFileContent(null, null, null, null);
1251+
1252+
var rowColumns = service.computeRowColumns(rfc);
1253+
assertThat(rowColumns).hasSameSizeAs(innerColumns);
1254+
verify(service).computeColumns(same(rfc));
1255+
Zip.of(innerColumns.stream(), rowColumns.stream()).forEach((innerCol, outerCol) -> {
1256+
assertThat(outerCol).isInstanceOf(CombinerColumn.class);
1257+
assertSame(innerCol, ((CombinerColumn) outerCol).innerColumn);
1258+
});
1259+
}
1260+
12201261
@SuppressWarnings({"unchecked", "rawtypes"})
12211262
@ParameterizedTest
12221263
@CsvSource({
@@ -1246,7 +1287,8 @@ public void testComputeColumns(boolean anyTagData, String joinedOptions) {
12461287
entries.get(1).setTagData(orderedMap("Alpha", "A", "Beta", "B"));
12471288
entries.get(2).setTagData(orderedMap("Beta", "8", "Gamma", "9"));
12481289
}
1249-
var columns = service.computeColumns(new ReleaseFileContent(EnumSet.of(mode), entries, options));
1290+
List<List<ReleaseEntry>> rows = entries.stream().map(List::of).toList();
1291+
var columns = service.computeColumns(new ReleaseFileContent(EnumSet.of(mode), entries, options, rows));
12501292
List modeColumns = ReleaseColumn.forModesAndOptions(modes, options);
12511293
if (!anyTagData || !options.contains(ReleaseFileOption.Visium)) {
12521294
assertThat(columns).containsExactlyElementsOf(modeColumns);
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package uk.ac.sanger.sccp.utils.tsv;
2+
3+
import org.junit.jupiter.api.BeforeEach;
4+
import org.junit.jupiter.api.Test;
5+
import uk.ac.sanger.sccp.stan.service.releasefile.ReleaseColumn;
6+
import uk.ac.sanger.sccp.stan.service.releasefile.ReleaseEntry;
7+
8+
import java.util.Arrays;
9+
import java.util.List;
10+
import java.util.stream.IntStream;
11+
12+
import static org.junit.jupiter.api.Assertions.assertEquals;
13+
14+
/** Test {@link CombinerColumn} */
15+
class TestCombinerColumn {
16+
private TsvColumn<ReleaseEntry> innerColumn;
17+
private TsvColumn<List<ReleaseEntry>> column;
18+
@BeforeEach
19+
void setup() {
20+
innerColumn = ReleaseColumn.Bond_barcode;
21+
column = new CombinerColumn<>(innerColumn);
22+
}
23+
24+
@Test
25+
void testGet() {
26+
ReleaseEntry[] entries = IntStream.range(0,4)
27+
.mapToObj(i -> new ReleaseEntry(null, null, null))
28+
.toArray(ReleaseEntry[]::new);
29+
entries[0].setBondBarcode("Bond1");
30+
entries[1].setBondBarcode("Bond2");
31+
entries[2].setBondBarcode(null);
32+
entries[3].setBondBarcode("Bond1");
33+
34+
assertEquals("Bond1, Bond2", column.get(Arrays.asList(entries)));
35+
}
36+
37+
@Test
38+
void testToString() {
39+
assertEquals(innerColumn.toString(), column.toString());
40+
}
41+
}

0 commit comments

Comments
 (0)